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

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,54 @@ 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 modalInitTimeoutRef = useRef(null);
107
+ const isModalInitRef = useRef(false);
108
+ const { height: windowHeight, width: windowWidth } = useWindowDimensions();
109
+ const keyboardHeightRef = useRef(0);
110
+ const isAdViewVisible = showIframe && iframeLoaded;
111
+ const reset = () => {
112
+ setHeight(0);
113
+ setShowIframe(false);
114
+ setContainerStyles({});
115
+ setIframeStyles({});
116
+ setIframeLoaded(false);
117
+ resetModal();
118
+ context?.resetAll();
119
+ context?.captureError(new Error("Processing iframe error"));
120
+ };
121
+ const resetModal = () => {
122
+ if (modalInitTimeoutRef.current) {
123
+ clearTimeout(modalInitTimeoutRef.current);
124
+ modalInitTimeoutRef.current = null;
125
+ }
126
+ isModalInitRef.current = false;
127
+ setModalOpen(false);
128
+ setModalLoaded(false);
129
+ setModalShown(false);
130
+ };
58
131
  const debug = (name, data = {}) => {
59
132
  context?.onDebugEventInternal?.(name, {
60
133
  code,
@@ -62,36 +135,40 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
62
135
  otherParams,
63
136
  bid,
64
137
  iframeUrl,
138
+ iframeLoaded,
139
+ showIframe,
140
+ height,
141
+ containerStyles,
142
+ iframeStyles,
65
143
  ...data
66
144
  });
67
145
  };
68
- debug("Format:updateState", {
69
- params: {
70
- messageId,
146
+ const debugModal = (name, data = {}) => {
147
+ context?.onDebugEventInternal?.(name, {
71
148
  code,
72
- otherParams
73
- }
74
- });
149
+ messageId,
150
+ otherParams,
151
+ bid,
152
+ modalUrl,
153
+ modalOpen,
154
+ modalShown,
155
+ modalLoaded,
156
+ ...data
157
+ });
158
+ };
159
+ debug("format-update-state");
75
160
  const onMessage = (event) => {
76
161
  try {
77
162
  const data = JSON.parse(event.nativeEvent.data);
78
- debug("Format:iframeMessage", {
79
- message: data,
80
- params: { data, messageId, code, otherParams }
163
+ debug("iframe-message", {
164
+ message: data
81
165
  });
82
166
  const messageHandler = handleIframeMessage(
83
167
  (message) => {
84
168
  switch (message.type) {
85
169
  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
- });
170
+ setIframeLoaded(true);
171
+ debug("iframe-post-message");
95
172
  sendMessage(webViewRef, "update-iframe", code, {
96
173
  messages: context?.messages,
97
174
  sdk: "sdk-react-native",
@@ -99,6 +176,46 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
99
176
  messageId
100
177
  });
101
178
  break;
179
+ case "error-iframe":
180
+ reset();
181
+ break;
182
+ case "resize-iframe":
183
+ setHeight(message.data.height);
184
+ break;
185
+ case "click-iframe":
186
+ if (message.data.url) {
187
+ Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
188
+ (err) => console.error("error opening url", err)
189
+ );
190
+ }
191
+ context?.onAdClickInternal(message.data);
192
+ break;
193
+ case "view-iframe":
194
+ context?.onAdViewInternal(message.data);
195
+ break;
196
+ case "ad-done-iframe":
197
+ if (bid?.bidId && message.data.cachedContent) {
198
+ context?.cachedContentRef?.current?.set(bid.bidId, message.data.cachedContent);
199
+ }
200
+ break;
201
+ case "show-iframe":
202
+ setShowIframe(true);
203
+ break;
204
+ case "hide-iframe":
205
+ setShowIframe(false);
206
+ break;
207
+ case "set-styles-iframe":
208
+ setContainerStyles(message.data.containerStyles);
209
+ setIframeStyles(message.data.iframeStyles);
210
+ break;
211
+ case "open-component-iframe":
212
+ setModalOpen(true);
213
+ modalInitTimeoutRef.current = setTimeout(() => {
214
+ if (!isModalInitRef.current) {
215
+ resetModal();
216
+ }
217
+ }, message.data.timeout ?? 5e3);
218
+ break;
102
219
  case "event-iframe":
103
220
  onEvent?.(message.data);
104
221
  context?.onAdEventInternal(message.data);
@@ -111,145 +228,200 @@ var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
111
228
  );
112
229
  messageHandler({ data });
113
230
  } catch (e) {
114
- debug("Format:iframeMessageError", {
115
- params: { error: e, messageId, code, otherParams },
231
+ debug("iframe-message-error", {
116
232
  error: e
117
233
  });
118
234
  console.error("error parsing message from webview", e);
235
+ reset();
119
236
  }
120
237
  };
121
- if (!context || !bid || !iframeUrl) {
122
- debug("Format:noContextOrBidOrIframeUrl", {
123
- params: {
124
- context,
125
- bid,
126
- iframeUrl,
127
- messageId,
128
- code,
129
- otherParams
130
- }
238
+ const onModalMessage = (event) => {
239
+ try {
240
+ const data = JSON.parse(event.nativeEvent.data);
241
+ debugModal("modal-iframe-message", {
242
+ message: data
243
+ });
244
+ const messageHandler = handleIframeMessage(
245
+ (message) => {
246
+ switch (message.type) {
247
+ case "close-component-iframe":
248
+ resetModal();
249
+ break;
250
+ case "init-component-iframe":
251
+ isModalInitRef.current = true;
252
+ if (modalInitTimeoutRef.current) {
253
+ clearTimeout(modalInitTimeoutRef.current);
254
+ modalInitTimeoutRef.current = null;
255
+ }
256
+ setModalShown(true);
257
+ break;
258
+ case "error-component-iframe":
259
+ case "error-iframe":
260
+ resetModal();
261
+ context?.captureError(new Error("Processing modal iframe error"));
262
+ break;
263
+ case "click-iframe":
264
+ if (message.data.url) {
265
+ Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
266
+ (err) => console.error("error opening url", err)
267
+ );
268
+ }
269
+ context?.onAdClickInternal(message.data);
270
+ break;
271
+ case "event-iframe":
272
+ onEvent?.(message.data);
273
+ context?.onAdEventInternal(message.data);
274
+ break;
275
+ }
276
+ },
277
+ {
278
+ code,
279
+ component: "modal"
280
+ }
281
+ );
282
+ messageHandler({ data });
283
+ } catch (e) {
284
+ debugModal("modal-iframe-message-error", {
285
+ error: e
286
+ });
287
+ console.error("error parsing message from webview", e);
288
+ resetModal();
289
+ }
290
+ };
291
+ const paramsString = convertParamsToString(otherParams);
292
+ useEffect(() => {
293
+ if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
294
+ return;
295
+ }
296
+ debug("iframe-post-message");
297
+ sendMessage(webViewRef, "update-iframe", code, {
298
+ data: { otherParams },
299
+ code
300
+ });
301
+ }, [paramsString, iframeLoaded, context?.adServerUrl, bid, code]);
302
+ const checkIfInViewport = () => {
303
+ if (!containerRef.current) return;
304
+ containerRef.current.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
305
+ sendMessage(webViewRef, "update-dimensions-iframe", code, {
306
+ windowWidth,
307
+ windowHeight,
308
+ containerWidth,
309
+ containerHeight,
310
+ containerX,
311
+ containerY,
312
+ keyboardHeight: keyboardHeightRef.current
313
+ });
131
314
  });
315
+ };
316
+ useEffect(() => {
317
+ if (!isAdViewVisible) return;
318
+ const interval = setInterval(() => {
319
+ checkIfInViewport();
320
+ }, 250);
321
+ return () => clearInterval(interval);
322
+ }, [isAdViewVisible]);
323
+ useEffect(() => {
324
+ const showSubscription = Keyboard.addListener("keyboardDidShow", (e) => {
325
+ keyboardHeightRef.current = e?.endCoordinates?.height ?? 0;
326
+ });
327
+ const hideSubscription = Keyboard.addListener("keyboardDidHide", () => {
328
+ keyboardHeightRef.current = 0;
329
+ });
330
+ return () => {
331
+ showSubscription.remove();
332
+ hideSubscription.remove();
333
+ keyboardHeightRef.current = 0;
334
+ };
335
+ }, []);
336
+ if (!context || !bid || !iframeUrl) {
132
337
  return null;
133
338
  }
134
- return /* @__PURE__ */ jsx(
135
- View,
339
+ const inlineContent = /* @__PURE__ */ jsx2(
340
+ frame_webview_default,
136
341
  {
342
+ ref: webViewRef,
343
+ iframeUrl,
344
+ onMessage,
137
345
  style: {
138
- height: 300,
346
+ height,
139
347
  width: "100%",
140
348
  backgroundColor: "transparent",
141
- borderWidth: 0
349
+ borderWidth: 0,
350
+ ...iframeStyles
142
351
  },
143
- children: /* @__PURE__ */ jsx(
144
- WebView,
352
+ onError: () => {
353
+ debug("iframe-error");
354
+ reset();
355
+ },
356
+ onLoad: () => {
357
+ debug("iframe-load");
358
+ }
359
+ }
360
+ );
361
+ const interstitialContent = /* @__PURE__ */ jsx2(
362
+ Modal,
363
+ {
364
+ visible: modalOpen,
365
+ transparent: true,
366
+ onRequestClose: resetModal,
367
+ animationType: "slide",
368
+ statusBarTranslucent: true,
369
+ children: /* @__PURE__ */ jsx2(
370
+ View,
145
371
  {
146
- ref: webViewRef,
147
- source: {
148
- uri: iframeUrl
149
- },
150
- onMessage,
151
372
  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
- });
373
+ flex: 1,
374
+ // Don't show the modal until the modal page is loaded and sends 'init-component-iframe' message back to SDK
375
+ ...modalShown ? { opacity: 1, pointerEvents: "auto" } : { opacity: 0, pointerEvents: "none" }
196
376
  },
197
- onLoadProgress: () => {
198
- debug("Format:iframeLoadProgress", {
199
- params: {
200
- messageId,
201
- code,
202
- otherParams
377
+ children: /* @__PURE__ */ jsx2(
378
+ frame_webview_default,
379
+ {
380
+ ref: modalWebViewRef,
381
+ iframeUrl: modalUrl,
382
+ onMessage: onModalMessage,
383
+ style: {
384
+ backgroundColor: "transparent",
385
+ height: "100%",
386
+ width: "100%",
387
+ borderWidth: 0
388
+ },
389
+ onError: () => {
390
+ debug("modal-error");
391
+ resetModal();
392
+ },
393
+ onLoad: () => {
394
+ debug("modal-load");
395
+ setModalLoaded(true);
203
396
  }
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
- }
397
+ }
398
+ )
242
399
  }
243
400
  )
244
401
  }
245
402
  );
403
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
404
+ /* @__PURE__ */ jsx2(
405
+ View,
406
+ {
407
+ style: isAdViewVisible ? containerStyles : {
408
+ height: 0,
409
+ overflow: "hidden"
410
+ },
411
+ ref: containerRef,
412
+ children: wrapper ? wrapper(inlineContent) : inlineContent
413
+ }
414
+ ),
415
+ interstitialContent
416
+ ] });
246
417
  };
247
- var Format_default = Format;
418
+ var FormatWithErrorBoundary = (props) => /* @__PURE__ */ jsx2(ErrorBoundary, { children: /* @__PURE__ */ jsx2(Format, { ...props }) });
419
+ var Format_default = FormatWithErrorBoundary;
248
420
 
249
421
  // src/formats/InlineAd.tsx
250
- import { jsx as jsx2 } from "react/jsx-runtime";
422
+ import { jsx as jsx3 } from "react/jsx-runtime";
251
423
  var InlineAd = ({ code, messageId, wrapper, ...props }) => {
252
- return /* @__PURE__ */ jsx2(Format_default, { code, messageId, wrapper, ...props });
424
+ return /* @__PURE__ */ jsx3(Format_default, { code, messageId, wrapper, ...props });
253
425
  };
254
426
  var InlineAd_default = InlineAd;
255
427
 
@@ -263,14 +435,14 @@ import { Appearance, Dimensions, PixelRatio, Platform } from "react-native";
263
435
  import DeviceInfo from "react-native-device-info";
264
436
 
265
437
  // package.json
266
- var version = "3.0.7-rc.3";
438
+ var version = "3.0.7";
267
439
 
268
440
  // src/NativeRNKontext.ts
269
441
  import { TurboModuleRegistry } from "react-native";
270
442
  var NativeRNKontext_default = TurboModuleRegistry.getEnforcing("RNKontext");
271
443
 
272
444
  // src/context/AdsProvider.tsx
273
- import { jsx as jsx3 } from "react/jsx-runtime";
445
+ import { jsx as jsx4 } from "react/jsx-runtime";
274
446
  ErrorUtils.setGlobalHandler((error, isFatal) => {
275
447
  if (!isFatal) {
276
448
  log.warn(error);
@@ -364,7 +536,7 @@ var getSdk = async () => ({
364
536
  version
365
537
  });
366
538
  var AdsProvider = (props) => {
367
- return /* @__PURE__ */ jsx3(AdsProviderInternal, { ...props, getDevice, getSdk, getApp });
539
+ return /* @__PURE__ */ jsx4(AdsProviderInternal, { ...props, getDevice, getSdk, getApp });
368
540
  };
369
541
  export {
370
542
  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.7",
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/*",