@kontextso/sdk-react-native 2.1.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,7 +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 { 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";
375
252
  ErrorUtils.setGlobalHandler((error, isFatal) => {
376
253
  if (!isFatal) {
377
254
  log.warn(error);
@@ -379,7 +256,7 @@ ErrorUtils.setGlobalHandler((error, isFatal) => {
379
256
  log.error(error);
380
257
  }
381
258
  });
382
- var getDevice = () => {
259
+ var getDevice = async () => {
383
260
  try {
384
261
  const os = Platform.OS;
385
262
  const systemVersion = DeviceInfo.getSystemVersion();
@@ -389,6 +266,12 @@ var getDevice = () => {
389
266
  const deviceType = DeviceInfo.getDeviceType();
390
267
  const appBundleId = DeviceInfo.getBundleId();
391
268
  const appVersion = DeviceInfo.getVersion();
269
+ let soundOn = false;
270
+ try {
271
+ soundOn = await NativeRNKontext_default.isSoundOn();
272
+ } catch (error) {
273
+ log.warn("Failed to read output volume", error);
274
+ }
392
275
  const rnv = Platform.constants.reactNativeVersion;
393
276
  const reactNativeVersion = `${rnv.major}.${rnv.minor}.${rnv.patch}`;
394
277
  return {
@@ -400,7 +283,8 @@ var getDevice = () => {
400
283
  deviceId,
401
284
  deviceType,
402
285
  appBundleId,
403
- appVersion
286
+ appVersion,
287
+ soundOn
404
288
  };
405
289
  } catch (error) {
406
290
  console.error(error);
@@ -408,7 +292,7 @@ var getDevice = () => {
408
292
  return {};
409
293
  };
410
294
  var AdsProvider = (props) => {
411
- return /* @__PURE__ */ jsx4(AdsProviderInternal, { ...props, getDevice });
295
+ return /* @__PURE__ */ jsx3(AdsProviderInternal, { ...props, getDevice });
412
296
  };
413
297
  export {
414
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.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,10 +55,29 @@
52
55
  "react-native-webview": "^13.15.0"
53
56
  },
54
57
  "dependencies": {
55
- "@kontextso/sdk-react": "^1.2.4"
58
+ "@kontextso/sdk-react": "^1.2.3"
56
59
  },
57
60
  "files": [
58
61
  "dist/*",
59
- "LICENSE"
60
- ]
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
+ }
61
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
+ }