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