@kontextso/sdk-react-native 2.1.1-rc.0 → 2.1.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  useBid,
8
8
  useIframeUrl
9
9
  } from "@kontextso/sdk-react";
10
- import { Linking, Modal, View, useWindowDimensions } from "react-native";
10
+ import { WebView } from "react-native-webview";
11
+ import { Linking, View, useWindowDimensions } from "react-native";
11
12
 
12
13
  // ../sdk-common/dist/index.mjs
13
14
  function makeIframeMessage(type, opts) {
@@ -27,44 +28,8 @@ function handleIframeMessage(handler, opts) {
27
28
  };
28
29
  }
29
30
 
30
- // src/frame-webview.tsx
31
- import { forwardRef } from "react";
32
- import { WebView } from "react-native-webview";
33
- import { jsx } from "react/jsx-runtime";
34
- var FrameWebView = forwardRef(
35
- ({ iframeUrl, onMessage, style, onError, onLoad }, forwardedRef) => {
36
- return /* @__PURE__ */ jsx(
37
- WebView,
38
- {
39
- ref: forwardedRef,
40
- source: {
41
- uri: iframeUrl
42
- },
43
- onMessage,
44
- style,
45
- allowsInlineMediaPlayback: true,
46
- mediaPlaybackRequiresUserAction: false,
47
- javaScriptEnabled: true,
48
- domStorageEnabled: true,
49
- allowsFullscreenVideo: false,
50
- injectedJavaScript: `
51
- window.addEventListener("message", function(event) {
52
- if (window.ReactNativeWebView && event.data) {
53
- // ReactNativeWebView.postMessage only supports string data
54
- window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
55
- }
56
- }, false);
57
- `,
58
- onError,
59
- onLoad
60
- }
61
- );
62
- }
63
- );
64
- var frame_webview_default = FrameWebView;
65
-
66
31
  // src/formats/Format.tsx
67
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
32
+ import { jsx } from "react/jsx-runtime";
68
33
  var sendMessage = (webViewRef, type, code, data) => {
69
34
  const message = makeIframeMessage(type, {
70
35
  data,
@@ -80,19 +45,13 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
80
45
  const context = useContext(AdsContext);
81
46
  const bid = useBid({ code, messageId });
82
47
  const [height, setHeight] = useState(0);
83
- const iframeUrl = useIframeUrl(context, bid, code, messageId, "sdk-react-native", otherParams.theme);
84
- const modalUrl = iframeUrl.replace("/api/frame/", "/api/modal/");
48
+ const iframeUrl = useIframeUrl(context, bid, code, messageId);
85
49
  const [showIframe, setShowIframe] = useState(false);
86
50
  const [iframeLoaded, setIframeLoaded] = useState(false);
87
- const [modalOpen, setModalOpen] = useState(false);
88
- const [modalShown, setModalShown] = useState(false);
89
- const [modalLoaded, setModalLoaded] = useState(false);
90
51
  const [containerStyles, setContainerStyles] = useState({});
91
52
  const [iframeStyles, setIframeStyles] = useState({});
92
53
  const containerRef = useRef(null);
93
54
  const webViewRef = useRef(null);
94
- const modalWebViewRef = useRef(null);
95
- const modalInitTimeoutRef = useRef(null);
96
55
  const { height: windowHeight, width: windowWidth } = useWindowDimensions();
97
56
  const reset = () => {
98
57
  setHeight(0);
@@ -100,19 +59,9 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
100
59
  setContainerStyles({});
101
60
  setIframeStyles({});
102
61
  setIframeLoaded(false);
103
- resetModal();
104
62
  context?.resetAll();
105
63
  context?.captureError(new Error("Processing iframe error"));
106
64
  };
107
- const resetModal = () => {
108
- if (modalInitTimeoutRef.current) {
109
- clearTimeout(modalInitTimeoutRef.current);
110
- modalInitTimeoutRef.current = null;
111
- }
112
- setModalOpen(false);
113
- setModalLoaded(false);
114
- setModalShown(false);
115
- };
116
65
  const debug = (name, data = {}) => {
117
66
  context?.onDebugEventInternal?.(name, {
118
67
  code,
@@ -128,19 +77,6 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
128
77
  ...data
129
78
  });
130
79
  };
131
- const debugModal = (name, data = {}) => {
132
- context?.onDebugEventInternal?.(name, {
133
- code,
134
- messageId,
135
- otherParams,
136
- bid,
137
- modalUrl,
138
- modalOpen,
139
- modalShown,
140
- modalLoaded,
141
- ...data
142
- });
143
- };
144
80
  debug("format-update-state");
145
81
  const onMessage = (event) => {
146
82
  try {
@@ -188,10 +124,6 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
188
124
  setContainerStyles(message.data.containerStyles);
189
125
  setIframeStyles(message.data.iframeStyles);
190
126
  break;
191
- case "open-component-iframe":
192
- setModalOpen(true);
193
- modalInitTimeoutRef.current = setTimeout(resetModal, message.data.timeout ?? 5e3);
194
- break;
195
127
  }
196
128
  },
197
129
  {
@@ -207,53 +139,6 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
207
139
  reset();
208
140
  }
209
141
  };
210
- const onModalMessage = (event) => {
211
- try {
212
- const data = JSON.parse(event.nativeEvent.data);
213
- debugModal("modal-iframe-message", {
214
- message: data
215
- });
216
- const messageHandler = handleIframeMessage(
217
- (message) => {
218
- switch (message.type) {
219
- case "close-component-iframe":
220
- resetModal();
221
- break;
222
- case "init-component-iframe":
223
- if (modalInitTimeoutRef.current) {
224
- clearTimeout(modalInitTimeoutRef.current);
225
- modalInitTimeoutRef.current = null;
226
- }
227
- setModalShown(true);
228
- break;
229
- case "error-component-iframe":
230
- case "error-iframe":
231
- resetModal();
232
- break;
233
- case "click-iframe":
234
- if (message.data.url) {
235
- Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
236
- (err) => console.error("error opening url", err)
237
- );
238
- }
239
- context?.onAdClickInternal(message.data);
240
- break;
241
- }
242
- },
243
- {
244
- code,
245
- component: "modal"
246
- }
247
- );
248
- messageHandler({ data });
249
- } catch (e) {
250
- debugModal("modal-iframe-message-error", {
251
- error: e
252
- });
253
- console.error("error parsing message from webview", e);
254
- resetModal();
255
- }
256
- };
257
142
  const paramsString = convertParamsToString(otherParams);
258
143
  useEffect(() => {
259
144
  if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
@@ -299,71 +184,57 @@ var Format = ({ code, messageId, wrapper, ...otherParams }) => {
299
184
  }
300
185
  return 0;
301
186
  };
302
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [
303
- /* @__PURE__ */ jsx2(Modal, { visible: modalOpen, transparent: true, onRequestClose: resetModal, children: /* @__PURE__ */ jsx2(
304
- View,
305
- {
306
- style: {
307
- flex: 1,
308
- // Don't show the modal until the modal page is loaded and sends 'init-component-iframe' message back to SDK
309
- ...modalShown ? { opacity: 1, pointerEvents: "auto" } : { opacity: 0, pointerEvents: "none" }
310
- },
311
- children: /* @__PURE__ */ jsx2(
312
- frame_webview_default,
313
- {
314
- ref: modalWebViewRef,
315
- iframeUrl: modalUrl,
316
- onMessage: onModalMessage,
317
- style: {
318
- backgroundColor: "transparent",
319
- height: "100%",
320
- width: "100%",
321
- borderWidth: 0
322
- },
323
- onError: () => {
324
- debug("modal-error");
325
- resetModal();
326
- },
327
- onLoad: () => {
328
- debug("modal-load");
329
- setModalLoaded(true);
330
- }
187
+ const content = /* @__PURE__ */ jsx(View, { style: containerStyles, ref: containerRef, children: /* @__PURE__ */ jsx(
188
+ WebView,
189
+ {
190
+ ref: webViewRef,
191
+ source: {
192
+ uri: iframeUrl
193
+ },
194
+ onMessage,
195
+ style: {
196
+ height: getHeight(),
197
+ width: getWidth(),
198
+ ...iframeStyles
199
+ },
200
+ allowsInlineMediaPlayback: true,
201
+ mediaPlaybackRequiresUserAction: false,
202
+ javaScriptEnabled: true,
203
+ domStorageEnabled: true,
204
+ allowsFullscreenVideo: false,
205
+ injectedJavaScript: `
206
+ function sendToLog(data) {
207
+ window.ReactNativeWebView.postMessage(JSON.stringify({
208
+ type: 'log-iframe',
209
+ data: data
210
+ }));
331
211
  }
332
- )
333
- }
334
- ) }),
335
- /* @__PURE__ */ jsx2(View, { style: containerStyles, ref: containerRef, children: /* @__PURE__ */ jsx2(
336
- frame_webview_default,
337
- {
338
- ref: webViewRef,
339
- iframeUrl,
340
- onMessage,
341
- style: {
342
- height: getHeight(),
343
- width: getWidth(),
344
- background: "transparent",
345
- borderWidth: 0,
346
- ...iframeStyles
347
- },
348
- onError: () => {
349
- debug("iframe-error");
350
- reset();
351
- },
352
- onLoad: () => {
353
- debug("iframe-load");
354
- }
212
+
213
+ window.addEventListener("message", function(event) {
214
+ if (window.ReactNativeWebView && event.data) {
215
+ // ReactNativeWebView.postMessage only supports string data
216
+ window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
217
+ }
218
+ }, false);
219
+ `,
220
+ onError: () => {
221
+ debug("iframe-error");
222
+ reset();
223
+ },
224
+ onLoad: () => {
225
+ debug("iframe-load");
355
226
  }
356
- ) })
357
- ] });
227
+ }
228
+ ) });
358
229
  return wrapper ? wrapper(content) : content;
359
230
  };
360
- var FormatWithErrorBoundary = (props) => /* @__PURE__ */ jsx2(ErrorBoundary, { children: /* @__PURE__ */ jsx2(Format, { ...props }) });
231
+ var FormatWithErrorBoundary = (props) => /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Format, { ...props }) });
361
232
  var Format_default = FormatWithErrorBoundary;
362
233
 
363
234
  // src/formats/InlineAd.tsx
364
- import { jsx as jsx3 } from "react/jsx-runtime";
235
+ import { jsx as jsx2 } from "react/jsx-runtime";
365
236
  var InlineAd = ({ code, messageId, wrapper, ...props }) => {
366
- return /* @__PURE__ */ jsx3(Format_default, { code, messageId, wrapper, ...props });
237
+ return /* @__PURE__ */ jsx2(Format_default, { code, messageId, wrapper, ...props });
367
238
  };
368
239
  var InlineAd_default = InlineAd;
369
240
 
@@ -371,8 +242,13 @@ var InlineAd_default = InlineAd;
371
242
  import { AdsProviderInternal, log } from "@kontextso/sdk-react";
372
243
  import { Platform } from "react-native";
373
244
  import DeviceInfo from "react-native-device-info";
374
- import { isSoundOn } from "@kontextso/soundon";
375
- import { jsx as jsx4 } from "react/jsx-runtime";
245
+
246
+ // src/NativeRNKontext.ts
247
+ import { TurboModuleRegistry } from "react-native";
248
+ var NativeRNKontext_default = TurboModuleRegistry.getEnforcing("RNKontext");
249
+
250
+ // src/context/AdsProvider.tsx
251
+ import { jsx as jsx3 } from "react/jsx-runtime";
376
252
  ErrorUtils.setGlobalHandler((error, isFatal) => {
377
253
  if (!isFatal) {
378
254
  log.warn(error);
@@ -392,7 +268,7 @@ var getDevice = async () => {
392
268
  const appVersion = DeviceInfo.getVersion();
393
269
  let soundOn = false;
394
270
  try {
395
- soundOn = await isSoundOn();
271
+ soundOn = await NativeRNKontext_default.isSoundOn();
396
272
  } catch (error) {
397
273
  log.warn("Failed to read output volume", error);
398
274
  }
@@ -416,7 +292,7 @@ var getDevice = async () => {
416
292
  return {};
417
293
  };
418
294
  var AdsProvider = (props) => {
419
- return /* @__PURE__ */ jsx4(AdsProviderInternal, { ...props, getDevice });
295
+ return /* @__PURE__ */ jsx3(AdsProviderInternal, { ...props, getDevice });
420
296
  };
421
297
  export {
422
298
  AdsProvider,
@@ -0,0 +1,26 @@
1
+ import AVFoundation
2
+
3
+ @objc(KontextSDK)
4
+ public class KontextSDK: NSObject {
5
+ private static let MINIMAL_VOLUME_THRESHOLD: Float = 0.0
6
+
7
+ @objc
8
+ public static func isSoundOn() -> NSNumber? {
9
+ let session = AVAudioSession.sharedInstance()
10
+
11
+ do {
12
+ try session.setCategory(
13
+ .ambient,
14
+ mode: .default,
15
+ options: [.mixWithOthers]
16
+ )
17
+ try session.setActive(true)
18
+
19
+ return NSNumber(
20
+ value: session.outputVolume > MINIMAL_VOLUME_THRESHOLD
21
+ )
22
+ } catch {
23
+ return nil
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ #import "RNKontext-Swift.h"
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <RNKontextSpec/RNKontextSpec.h>
5
+
6
+ @interface RNKontext: NSObject <NativeRNKontextSpec>
7
+ #else
8
+ #import <React/RCTBridgeModule.h>
9
+
10
+ @interface RNKontext: NSObject <RCTBridgeModule>
11
+ #endif
12
+
13
+ @end
@@ -0,0 +1,36 @@
1
+ #import "RNKontext.h"
2
+
3
+ @implementation RNKontext
4
+
5
+ RCT_EXPORT_MODULE()
6
+
7
+ #ifdef RCT_NEW_ARCH_ENABLED
8
+ - (void)isSoundOn:(RCTPromiseResolveBlock)resolve
9
+ reject:(RCTPromiseRejectBlock)reject {
10
+ NSNumber *isSoundOn = [KontextSDK isSoundOn];
11
+
12
+ if (isSoundOn == nil) {
13
+ reject(@"soundon_error", @"Failed to read output volume", nil);
14
+ } else {
15
+ resolve(isSoundOn);
16
+ }
17
+ }
18
+
19
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
20
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
21
+ return std::make_shared<facebook::react::NativeRNKontextSpecJSI>(params);
22
+ }
23
+ #else
24
+ RCT_EXPORT_METHOD(isSoundOn : (RCTPromiseResolveBlock)
25
+ resolve rejecter : (RCTPromiseRejectBlock)reject) {
26
+ NSNumber *isSoundOn = [KontextSDK isSoundOn];
27
+
28
+ if (isSoundOn == nil) {
29
+ reject(@"soundon_error", @"Failed to read output volume", nil);
30
+ } else {
31
+ resolve(isSoundOn);
32
+ }
33
+ }
34
+ #endif
35
+
36
+ @end
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@kontextso/sdk-react-native",
3
- "version": "2.1.1-rc.0",
3
+ "version": "2.1.1-rc.1",
4
+ "description": "Kontext SDK for React Native",
4
5
  "main": "./dist/index.js",
5
6
  "module": "./dist/index.mjs",
6
7
  "types": "./dist/index.d.ts",
7
8
  "type": "commonjs",
8
9
  "license": "Apache-2.0",
10
+ "author": "Kontext",
11
+ "homepage": "https://github.com/kontextso",
9
12
  "scripts": {
10
13
  "dev:js": "tsup --watch",
11
14
  "dev": "npm-run-all dev:js",
@@ -52,11 +55,29 @@
52
55
  "react-native-webview": "^13.15.0"
53
56
  },
54
57
  "dependencies": {
55
- "@kontextso/sdk-react": "^1.2.4",
56
- "@kontextso/soundon": "^1.0.0"
58
+ "@kontextso/sdk-react": "^1.2.3"
57
59
  },
58
60
  "files": [
59
61
  "dist/*",
60
- "LICENSE"
61
- ]
62
+ "src",
63
+ "android",
64
+ "ios",
65
+ "*.podspec",
66
+ "LICENSE",
67
+ "!ios/build",
68
+ "!android/build",
69
+ "!android/gradle",
70
+ "!android/gradlew",
71
+ "!android/gradlew.bat",
72
+ "!android/local.properties",
73
+ "!**/.*"
74
+ ],
75
+ "codegenConfig": {
76
+ "name": "RNKontextSpec",
77
+ "type": "modules",
78
+ "jsSrcsDir": "src",
79
+ "android": {
80
+ "javaPackageName": "so.kontext.react"
81
+ }
82
+ }
62
83
  }
@@ -0,0 +1,8 @@
1
+ import type { TurboModule } from 'react-native'
2
+ import { TurboModuleRegistry } from 'react-native'
3
+
4
+ export interface Spec extends TurboModule {
5
+ isSoundOn(): Promise<boolean>
6
+ }
7
+
8
+ export default TurboModuleRegistry.getEnforcing<Spec>('RNKontext')
@@ -0,0 +1,60 @@
1
+ 'use client'
2
+
3
+ import type { AdsProviderProps, Device } from '@kontextso/sdk-react'
4
+ import { AdsProviderInternal, log } from '@kontextso/sdk-react'
5
+ import { Platform } from 'react-native'
6
+ import DeviceInfo from 'react-native-device-info'
7
+ import KontextSDK from '../NativeRNKontext'
8
+
9
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
10
+ if (!isFatal) {
11
+ log.warn(error)
12
+ } else {
13
+ log.error(error)
14
+ }
15
+ })
16
+
17
+ const getDevice = async (): Promise<Device> => {
18
+ try {
19
+ const os = Platform.OS
20
+ const systemVersion = DeviceInfo.getSystemVersion()
21
+ const model = DeviceInfo.getModel()
22
+ const brand = DeviceInfo.getBrand()
23
+ const deviceId = DeviceInfo.getDeviceId()
24
+ const deviceType = DeviceInfo.getDeviceType()
25
+ const appBundleId = DeviceInfo.getBundleId()
26
+ const appVersion = DeviceInfo.getVersion()
27
+
28
+ let soundOn = false
29
+ try {
30
+ soundOn = await KontextSDK.isSoundOn()
31
+ } catch (error) {
32
+ log.warn('Failed to read output volume', error)
33
+ }
34
+
35
+ console.log('soundOn', soundOn)
36
+
37
+ const rnv = Platform.constants.reactNativeVersion
38
+ const reactNativeVersion = `${rnv.major}.${rnv.minor}.${rnv.patch}`
39
+
40
+ return {
41
+ os,
42
+ systemVersion,
43
+ reactNativeVersion,
44
+ model,
45
+ brand,
46
+ deviceId,
47
+ deviceType,
48
+ appBundleId,
49
+ appVersion,
50
+ soundOn,
51
+ }
52
+ } catch (error) {
53
+ console.error(error)
54
+ }
55
+ return {}
56
+ }
57
+
58
+ export const AdsProvider = (props: AdsProviderProps) => {
59
+ return <AdsProviderInternal {...props} getDevice={getDevice} />
60
+ }