@kontextso/sdk-react-native 3.0.7-rc.3 → 3.0.8-rc.0

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.js CHANGED
@@ -56,10 +56,50 @@ function handleIframeMessage(handler, opts) {
56
56
 
57
57
  // src/formats/Format.tsx
58
58
  var import_sdk_react = require("@kontextso/sdk-react");
59
- var import_react = require("react");
59
+ var import_react2 = require("react");
60
60
  var import_react_native = require("react-native");
61
+
62
+ // src/frame-webview.tsx
63
+ var import_react = require("react");
61
64
  var import_react_native_webview = require("react-native-webview");
62
65
  var import_jsx_runtime = require("react/jsx-runtime");
66
+ var FrameWebView = (0, import_react.forwardRef)(
67
+ ({ iframeUrl, onMessage, style, onError, onLoad }, forwardedRef) => {
68
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
69
+ import_react_native_webview.WebView,
70
+ {
71
+ ref: forwardedRef,
72
+ source: {
73
+ uri: iframeUrl
74
+ },
75
+ onMessage,
76
+ style,
77
+ allowsInlineMediaPlayback: true,
78
+ mediaPlaybackRequiresUserAction: false,
79
+ javaScriptEnabled: true,
80
+ domStorageEnabled: true,
81
+ allowsFullscreenVideo: false,
82
+ originWhitelist: ["*"],
83
+ sharedCookiesEnabled: true,
84
+ thirdPartyCookiesEnabled: true,
85
+ injectedJavaScript: `
86
+ window.addEventListener("message", function(event) {
87
+ if (window.ReactNativeWebView && event.data) {
88
+ // ReactNativeWebView.postMessage only supports string data
89
+ window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
90
+ }
91
+ }, false);
92
+ `,
93
+ onError,
94
+ onLoad
95
+ }
96
+ );
97
+ }
98
+ );
99
+ var frame_webview_default = FrameWebView;
100
+
101
+ // src/formats/Format.tsx
102
+ var import_jsx_runtime2 = require("react/jsx-runtime");
63
103
  var sendMessage = (webViewRef, type, code, data) => {
64
104
  const message = makeIframeMessage(type, {
65
105
  data,
@@ -71,24 +111,55 @@ var sendMessage = (webViewRef, type, code, data) => {
71
111
  }));
72
112
  `);
73
113
  };
74
- var getUrl = (code, messageId, bidId) => {
75
- const context = (0, import_react.useContext)(import_sdk_react.AdsContext);
76
- if (!context || !bidId) {
114
+ var getCachedContent = (context, bidId) => {
115
+ if (!bidId) {
77
116
  return null;
78
117
  }
79
- const adServerUrl = context?.adServerUrl;
80
- const params = new URLSearchParams({
81
- code,
82
- messageId,
83
- sdk: "sdk-react-native"
84
- });
85
- return `${adServerUrl}/api/frame/${bidId}?${params}`;
118
+ return context?.cachedContentRef?.current?.get(bidId) ?? null;
86
119
  };
87
120
  var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
88
- const context = (0, import_react.useContext)(import_sdk_react.AdsContext);
121
+ const context = (0, import_react2.useContext)(import_sdk_react.AdsContext);
89
122
  const bid = (0, import_sdk_react.useBid)({ code, messageId });
90
- const iframeUrl = getUrl(code, messageId, bid?.bidId);
91
- const webViewRef = (0, import_react.useRef)(null);
123
+ const [height, setHeight] = (0, import_react2.useState)(0);
124
+ const cachedContent = getCachedContent(context, bid?.bidId);
125
+ const iframeUrl = (0, import_sdk_react.useIframeUrl)(bid, code, messageId, "sdk-react-native", otherParams.theme, cachedContent);
126
+ const modalUrl = iframeUrl.replace("/api/frame/", "/api/modal/");
127
+ const [showIframe, setShowIframe] = (0, import_react2.useState)(false);
128
+ const [iframeLoaded, setIframeLoaded] = (0, import_react2.useState)(false);
129
+ const [modalOpen, setModalOpen] = (0, import_react2.useState)(false);
130
+ const [modalShown, setModalShown] = (0, import_react2.useState)(false);
131
+ const [modalLoaded, setModalLoaded] = (0, import_react2.useState)(false);
132
+ const [containerStyles, setContainerStyles] = (0, import_react2.useState)({});
133
+ const [iframeStyles, setIframeStyles] = (0, import_react2.useState)({});
134
+ const containerRef = (0, import_react2.useRef)(null);
135
+ const webViewRef = (0, import_react2.useRef)(null);
136
+ const modalWebViewRef = (0, import_react2.useRef)(null);
137
+ const messageStatusRef = (0, import_react2.useRef)("none" /* None */);
138
+ const modalInitTimeoutRef = (0, import_react2.useRef)(null);
139
+ const isModalInitRef = (0, import_react2.useRef)(false);
140
+ const { height: windowHeight, width: windowWidth } = (0, import_react_native.useWindowDimensions)();
141
+ const keyboardHeightRef = (0, import_react2.useRef)(0);
142
+ const isAdViewVisible = showIframe && iframeLoaded;
143
+ const reset = () => {
144
+ setHeight(0);
145
+ setShowIframe(false);
146
+ setContainerStyles({});
147
+ setIframeStyles({});
148
+ setIframeLoaded(false);
149
+ resetModal();
150
+ context?.resetAll();
151
+ context?.captureError(new Error("Processing iframe error"));
152
+ };
153
+ const resetModal = () => {
154
+ if (modalInitTimeoutRef.current) {
155
+ clearTimeout(modalInitTimeoutRef.current);
156
+ modalInitTimeoutRef.current = null;
157
+ }
158
+ isModalInitRef.current = false;
159
+ setModalOpen(false);
160
+ setModalLoaded(false);
161
+ setModalShown(false);
162
+ };
92
163
  const debug = (name, data = {}) => {
93
164
  context?.onDebugEventInternal?.(name, {
94
165
  code,
@@ -96,36 +167,41 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
96
167
  otherParams,
97
168
  bid,
98
169
  iframeUrl,
170
+ iframeLoaded,
171
+ showIframe,
172
+ height,
173
+ containerStyles,
174
+ iframeStyles,
99
175
  ...data
100
176
  });
101
177
  };
102
- debug("Format:updateState", {
103
- params: {
104
- messageId,
178
+ const debugModal = (name, data = {}) => {
179
+ context?.onDebugEventInternal?.(name, {
105
180
  code,
106
- otherParams
107
- }
108
- });
181
+ messageId,
182
+ otherParams,
183
+ bid,
184
+ modalUrl,
185
+ modalOpen,
186
+ modalShown,
187
+ modalLoaded,
188
+ ...data
189
+ });
190
+ };
191
+ debug("format-update-state");
109
192
  const onMessage = (event) => {
110
193
  try {
111
194
  const data = JSON.parse(event.nativeEvent.data);
112
- debug("Format:iframeMessage", {
113
- message: data,
114
- params: { data, messageId, code, otherParams }
195
+ debug("iframe-message", {
196
+ message: data
115
197
  });
116
198
  const messageHandler = handleIframeMessage(
117
199
  (message) => {
118
200
  switch (message.type) {
119
201
  case "init-iframe":
120
- debug("Format:iframePostMessage", {
121
- params: {
122
- code,
123
- messages: context?.messages,
124
- sdk: "sdk-react-native",
125
- otherParams,
126
- messageId
127
- }
128
- });
202
+ setIframeLoaded(true);
203
+ debug("iframe-post-message");
204
+ messageStatusRef.current = "message-received" /* MessageReceived */;
129
205
  sendMessage(webViewRef, "update-iframe", code, {
130
206
  messages: context?.messages,
131
207
  sdk: "sdk-react-native",
@@ -133,9 +209,50 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
133
209
  messageId
134
210
  });
135
211
  break;
212
+ case "error-iframe":
213
+ reset();
214
+ break;
215
+ case "resize-iframe":
216
+ setHeight(message.data.height);
217
+ break;
218
+ case "click-iframe":
219
+ if (message.data.url) {
220
+ import_react_native.Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
221
+ (err) => console.error("error opening url", err)
222
+ );
223
+ }
224
+ context?.onAdClickInternal(message.data);
225
+ break;
226
+ case "view-iframe":
227
+ context?.onAdViewInternal(message.data);
228
+ break;
229
+ case "ad-done-iframe":
230
+ if (bid?.bidId && message.data.cachedContent) {
231
+ context?.cachedContentRef?.current?.set(bid.bidId, message.data.cachedContent);
232
+ }
233
+ break;
234
+ case "show-iframe":
235
+ setShowIframe(true);
236
+ break;
237
+ case "hide-iframe":
238
+ setShowIframe(false);
239
+ break;
240
+ case "set-styles-iframe":
241
+ setContainerStyles(message.data.containerStyles);
242
+ setIframeStyles(message.data.iframeStyles);
243
+ break;
244
+ case "open-component-iframe":
245
+ setModalOpen(true);
246
+ modalInitTimeoutRef.current = setTimeout(() => {
247
+ if (!isModalInitRef.current) {
248
+ resetModal();
249
+ }
250
+ }, message.data.timeout ?? 5e3);
251
+ break;
136
252
  case "event-iframe":
137
253
  onEvent?.(message.data);
138
254
  context?.onAdEventInternal(message.data);
255
+ messageStatusRef.current = "message-received" /* MessageReceived */;
139
256
  break;
140
257
  }
141
258
  },
@@ -145,145 +262,226 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
145
262
  );
146
263
  messageHandler({ data });
147
264
  } catch (e) {
148
- debug("Format:iframeMessageError", {
149
- params: { error: e, messageId, code, otherParams },
265
+ debug("iframe-message-error", {
150
266
  error: e
151
267
  });
152
268
  console.error("error parsing message from webview", e);
269
+ reset();
153
270
  }
154
271
  };
155
- if (!context || !bid || !iframeUrl) {
156
- debug("Format:noContextOrBidOrIframeUrl", {
157
- params: {
158
- context,
159
- bid,
160
- iframeUrl,
161
- messageId,
162
- code,
163
- otherParams
272
+ const onModalMessage = (event) => {
273
+ try {
274
+ const data = JSON.parse(event.nativeEvent.data);
275
+ debugModal("modal-iframe-message", {
276
+ message: data
277
+ });
278
+ const messageHandler = handleIframeMessage(
279
+ (message) => {
280
+ switch (message.type) {
281
+ case "close-component-iframe":
282
+ resetModal();
283
+ break;
284
+ case "init-component-iframe":
285
+ isModalInitRef.current = true;
286
+ if (modalInitTimeoutRef.current) {
287
+ clearTimeout(modalInitTimeoutRef.current);
288
+ modalInitTimeoutRef.current = null;
289
+ }
290
+ setModalShown(true);
291
+ break;
292
+ case "error-component-iframe":
293
+ case "error-iframe":
294
+ resetModal();
295
+ context?.captureError(new Error("Processing modal iframe error"));
296
+ break;
297
+ case "click-iframe":
298
+ if (message.data.url) {
299
+ import_react_native.Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
300
+ (err) => console.error("error opening url", err)
301
+ );
302
+ }
303
+ context?.onAdClickInternal(message.data);
304
+ break;
305
+ case "event-iframe":
306
+ onEvent?.(message.data);
307
+ context?.onAdEventInternal(message.data);
308
+ break;
309
+ }
310
+ },
311
+ {
312
+ code,
313
+ component: "modal"
314
+ }
315
+ );
316
+ messageHandler({ data });
317
+ } catch (e) {
318
+ debugModal("modal-iframe-message-error", {
319
+ error: e
320
+ });
321
+ console.error("error parsing message from webview", e);
322
+ resetModal();
323
+ }
324
+ };
325
+ (0, import_react2.useEffect)(() => {
326
+ const interval = setInterval(() => {
327
+ if (messageStatusRef.current === "none" /* None */) {
328
+ return;
329
+ }
330
+ if (messageStatusRef.current === "message-received" /* MessageReceived */) {
331
+ clearInterval(interval);
332
+ return;
164
333
  }
334
+ debug("iframe-post-message-use-effect");
335
+ setIframeLoaded(true);
336
+ sendMessage(webViewRef, "update-iframe", code, {
337
+ messages: context?.messages,
338
+ sdk: "sdk-react-native",
339
+ otherParams: {
340
+ ...otherParams,
341
+ _useEffect: true
342
+ },
343
+ messageId
344
+ });
345
+ }, 500);
346
+ return () => {
347
+ clearInterval(interval);
348
+ };
349
+ }, []);
350
+ const paramsString = (0, import_sdk_react.convertParamsToString)(otherParams);
351
+ (0, import_react2.useEffect)(() => {
352
+ if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
353
+ return;
354
+ }
355
+ debug("iframe-post-message");
356
+ sendMessage(webViewRef, "update-iframe", code, {
357
+ data: { otherParams },
358
+ code
359
+ });
360
+ }, [paramsString, iframeLoaded, context?.adServerUrl, bid, code]);
361
+ const checkIfInViewport = () => {
362
+ if (!containerRef.current) return;
363
+ containerRef.current.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
364
+ sendMessage(webViewRef, "update-dimensions-iframe", code, {
365
+ windowWidth,
366
+ windowHeight,
367
+ containerWidth,
368
+ containerHeight,
369
+ containerX,
370
+ containerY,
371
+ keyboardHeight: keyboardHeightRef.current
372
+ });
373
+ });
374
+ };
375
+ (0, import_react2.useEffect)(() => {
376
+ if (!isAdViewVisible) return;
377
+ const interval = setInterval(() => {
378
+ checkIfInViewport();
379
+ }, 250);
380
+ return () => clearInterval(interval);
381
+ }, [isAdViewVisible]);
382
+ (0, import_react2.useEffect)(() => {
383
+ const showSubscription = import_react_native.Keyboard.addListener("keyboardDidShow", (e) => {
384
+ keyboardHeightRef.current = e?.endCoordinates?.height ?? 0;
385
+ });
386
+ const hideSubscription = import_react_native.Keyboard.addListener("keyboardDidHide", () => {
387
+ keyboardHeightRef.current = 0;
165
388
  });
389
+ return () => {
390
+ showSubscription.remove();
391
+ hideSubscription.remove();
392
+ keyboardHeightRef.current = 0;
393
+ };
394
+ }, []);
395
+ if (!context || !bid || !iframeUrl) {
166
396
  return null;
167
397
  }
168
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
169
- import_react_native.View,
398
+ const inlineContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
399
+ frame_webview_default,
170
400
  {
401
+ ref: webViewRef,
402
+ iframeUrl,
403
+ onMessage,
171
404
  style: {
172
- height: 300,
405
+ height,
173
406
  width: "100%",
174
407
  backgroundColor: "transparent",
175
- borderWidth: 0
408
+ borderWidth: 0,
409
+ ...iframeStyles
410
+ },
411
+ onError: () => {
412
+ debug("iframe-error");
413
+ reset();
176
414
  },
177
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
178
- import_react_native_webview.WebView,
415
+ onLoad: () => {
416
+ debug("iframe-load");
417
+ messageStatusRef.current = "initialized" /* Initialized */;
418
+ }
419
+ }
420
+ );
421
+ const interstitialContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
422
+ import_react_native.Modal,
423
+ {
424
+ visible: modalOpen,
425
+ transparent: true,
426
+ onRequestClose: resetModal,
427
+ animationType: "slide",
428
+ statusBarTranslucent: true,
429
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
430
+ import_react_native.View,
179
431
  {
180
- ref: webViewRef,
181
- source: {
182
- uri: iframeUrl
183
- },
184
- onMessage,
185
432
  style: {
186
- height: 300,
187
- width: "100%",
188
- backgroundColor: "transparent",
189
- borderWidth: 0
190
- },
191
- allowsInlineMediaPlayback: true,
192
- mediaPlaybackRequiresUserAction: false,
193
- javaScriptEnabled: true,
194
- domStorageEnabled: true,
195
- allowsFullscreenVideo: false,
196
- injectedJavaScript: `
197
- window.addEventListener("message", function(event) {
198
- if (window.ReactNativeWebView && event.data) {
199
- // ReactNativeWebView.postMessage only supports string data
200
- window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
201
- }
202
- }, false);
203
- `,
204
- onLoadStart: () => {
205
- debug("Format:iframeLoadStart", {
206
- params: {
207
- messageId,
208
- code,
209
- otherParams
210
- }
211
- });
212
- },
213
- onError: () => {
214
- debug("Format:iframeError", {
215
- params: {
216
- messageId,
217
- code,
218
- otherParams
219
- }
220
- });
221
- },
222
- onLoad: () => {
223
- debug("Format:iframeLoad", {
224
- params: {
225
- messageId,
226
- code,
227
- otherParams
228
- }
229
- });
433
+ flex: 1,
434
+ // Don't show the modal until the modal page is loaded and sends 'init-component-iframe' message back to SDK
435
+ ...modalShown ? { opacity: 1, pointerEvents: "auto" } : { opacity: 0, pointerEvents: "none" }
230
436
  },
231
- onLoadProgress: () => {
232
- debug("Format:iframeLoadProgress", {
233
- params: {
234
- messageId,
235
- code,
236
- otherParams
437
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
438
+ frame_webview_default,
439
+ {
440
+ ref: modalWebViewRef,
441
+ iframeUrl: modalUrl,
442
+ onMessage: onModalMessage,
443
+ style: {
444
+ backgroundColor: "transparent",
445
+ height: "100%",
446
+ width: "100%",
447
+ borderWidth: 0
448
+ },
449
+ onError: () => {
450
+ debug("modal-error");
451
+ resetModal();
452
+ },
453
+ onLoad: () => {
454
+ debug("modal-load");
455
+ setModalLoaded(true);
237
456
  }
238
- });
239
- },
240
- onHttpError: () => {
241
- debug("Format:iframeHttpError", {
242
- params: {
243
- messageId,
244
- code,
245
- otherParams
246
- }
247
- });
248
- },
249
- onRenderProcessGone: () => {
250
- debug("Format:iframeRenderProcessGone", {
251
- params: {
252
- messageId,
253
- code,
254
- otherParams
255
- }
256
- });
257
- },
258
- onNavigationStateChange: () => {
259
- debug("Format:iframeNavigationStateChange", {
260
- params: {
261
- messageId,
262
- code,
263
- otherParams
264
- }
265
- });
266
- },
267
- onContentProcessDidTerminate: () => {
268
- debug("Format:iframeContentProcessDidTerminate", {
269
- params: {
270
- messageId,
271
- code,
272
- otherParams
273
- }
274
- });
275
- }
457
+ }
458
+ )
276
459
  }
277
460
  )
278
461
  }
279
462
  );
463
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
464
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
465
+ import_react_native.View,
466
+ {
467
+ style: isAdViewVisible ? containerStyles : {
468
+ height: 0,
469
+ overflow: "hidden"
470
+ },
471
+ ref: containerRef,
472
+ children: wrapper ? wrapper(inlineContent) : inlineContent
473
+ }
474
+ ),
475
+ interstitialContent
476
+ ] });
280
477
  };
281
- var Format_default = Format;
478
+ var FormatWithErrorBoundary = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sdk_react.ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Format, { ...props }) });
479
+ var Format_default = FormatWithErrorBoundary;
282
480
 
283
481
  // src/formats/InlineAd.tsx
284
- var import_jsx_runtime2 = require("react/jsx-runtime");
482
+ var import_jsx_runtime3 = require("react/jsx-runtime");
285
483
  var InlineAd = ({ code, messageId, wrapper, ...props }) => {
286
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Format_default, { code, messageId, wrapper, ...props });
484
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Format_default, { code, messageId, wrapper, ...props });
287
485
  };
288
486
  var InlineAd_default = InlineAd;
289
487
 
@@ -294,14 +492,14 @@ var import_react_native3 = require("react-native");
294
492
  var import_react_native_device_info = __toESM(require("react-native-device-info"));
295
493
 
296
494
  // package.json
297
- var version = "3.0.7-rc.3";
495
+ var version = "3.0.7";
298
496
 
299
497
  // src/NativeRNKontext.ts
300
498
  var import_react_native2 = require("react-native");
301
499
  var NativeRNKontext_default = import_react_native2.TurboModuleRegistry.getEnforcing("RNKontext");
302
500
 
303
501
  // src/context/AdsProvider.tsx
304
- var import_jsx_runtime3 = require("react/jsx-runtime");
502
+ var import_jsx_runtime4 = require("react/jsx-runtime");
305
503
  ErrorUtils.setGlobalHandler((error, isFatal) => {
306
504
  if (!isFatal) {
307
505
  import_sdk_react2.log.warn(error);
@@ -395,7 +593,7 @@ var getSdk = async () => ({
395
593
  version
396
594
  });
397
595
  var AdsProvider = (props) => {
398
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sdk_react2.AdsProviderInternal, { ...props, getDevice, getSdk, getApp });
596
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sdk_react2.AdsProviderInternal, { ...props, getDevice, getSdk, getApp });
399
597
  };
400
598
  // Annotate the CommonJS export names for ESM import in node:
401
599
  0 && (module.exports = {