@kontextso/sdk-react-native 3.0.7-rc.2 → 4.0.0-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
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,21 +15,13 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var index_exports = {};
32
22
  __export(index_exports, {
33
- AdsProvider: () => AdsProvider,
34
- InlineAd: () => InlineAd_default
23
+ InlineAd: () => InlineAd,
24
+ KontextAds: () => KontextAds
35
25
  });
36
26
  module.exports = __toCommonJS(index_exports);
37
27
 
@@ -54,671 +44,534 @@ function handleIframeMessage(handler, opts) {
54
44
  };
55
45
  }
56
46
 
57
- // src/formats/Format.tsx
58
- var import_sdk_react = require("@kontextso/sdk-react");
59
- var import_react2 = require("react");
60
- var import_react_native = require("react-native");
61
-
62
- // src/frame-webview.tsx
47
+ // src/InlineAd.tsx
63
48
  var import_react = require("react");
64
49
  var import_react_native_webview = require("react-native-webview");
65
50
  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
- injectedJavaScript: `
83
- window.addEventListener("message", function(event) {
84
- if (window.ReactNativeWebView && event.data) {
85
- // ReactNativeWebView.postMessage only supports string data
86
- window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
87
- }
88
- }, false);
89
- `,
90
- onError,
91
- onLoad
92
- }
93
- );
94
- }
95
- );
96
- var frame_webview_default = FrameWebView;
97
-
98
- // src/formats/Format.tsx
99
- var import_jsx_runtime2 = require("react/jsx-runtime");
100
- var sendMessage = (webViewRef, type, code, data) => {
101
- const message = makeIframeMessage(type, {
102
- data,
103
- code
51
+ var getIframeUrl = (bidId, messageId, adServerUrl) => {
52
+ const params = new URLSearchParams({
53
+ code: "inlineAd",
54
+ messageId,
55
+ sdk: "sdk-react-native"
104
56
  });
105
- webViewRef.current?.injectJavaScript(`
106
- window.dispatchEvent(new MessageEvent('message', {
107
- data: ${JSON.stringify(message)}
108
- }));
109
- `);
57
+ return `${adServerUrl}/api/frame/${bidId}?${params}`;
110
58
  };
111
- var getCachedContent = (context, bidId) => {
112
- if (!bidId) {
113
- return null;
114
- }
115
- return context?.cachedContentRef?.current?.get(bidId) ?? null;
116
- };
117
- var Format = ({ code, messageId, wrapper, onEvent, ...otherParams }) => {
118
- const context = (0, import_react2.useContext)(import_sdk_react.AdsContext);
119
- const bid = (0, import_sdk_react.useBid)({ code, messageId });
120
- const [height, setHeight] = (0, import_react2.useState)(0);
121
- const cachedContent = getCachedContent(context, bid?.bidId);
122
- const iframeUrl = (0, import_sdk_react.useIframeUrl)(bid, code, messageId, "sdk-react-native", otherParams.theme, cachedContent);
123
- const modalUrl = iframeUrl.replace("/api/frame/", "/api/modal/");
124
- const [showIframe, setShowIframe] = (0, import_react2.useState)(false);
125
- const [iframeLoaded, setIframeLoaded] = (0, import_react2.useState)(false);
126
- const [modalOpen, setModalOpen] = (0, import_react2.useState)(false);
127
- const [modalShown, setModalShown] = (0, import_react2.useState)(false);
128
- const [modalLoaded, setModalLoaded] = (0, import_react2.useState)(false);
129
- const [containerStyles, setContainerStyles] = (0, import_react2.useState)({});
130
- const [iframeStyles, setIframeStyles] = (0, import_react2.useState)({});
131
- const containerRef = (0, import_react2.useRef)(null);
132
- const webViewRef = (0, import_react2.useRef)(null);
133
- const modalWebViewRef = (0, import_react2.useRef)(null);
134
- const modalInitTimeoutRef = (0, import_react2.useRef)(null);
135
- const isModalInitRef = (0, import_react2.useRef)(false);
136
- const { height: windowHeight, width: windowWidth } = (0, import_react_native.useWindowDimensions)();
137
- const keyboardHeightRef = (0, import_react2.useRef)(0);
138
- const isAdViewVisible = showIframe && iframeLoaded;
139
- const reset = () => {
140
- setHeight(0);
141
- setShowIframe(false);
142
- setContainerStyles({});
143
- setIframeStyles({});
144
- setIframeLoaded(false);
145
- resetModal();
146
- context?.resetAll();
147
- context?.captureError(new Error("Processing iframe error"));
148
- };
149
- const resetModal = () => {
150
- debugModal("Format:resetModal", {
151
- params: {
152
- messageId,
153
- code,
154
- otherParams
155
- }
156
- });
157
- if (modalInitTimeoutRef.current) {
158
- debugModal("Format:resetModalTimeout", {
159
- params: {
160
- messageId,
161
- code,
162
- otherParams
163
- }
164
- });
165
- clearTimeout(modalInitTimeoutRef.current);
166
- modalInitTimeoutRef.current = null;
59
+ var InlineAd = ({ messageId, session, onDebugEvent }) => {
60
+ const [bidId, setBidId] = (0, import_react.useState)(null);
61
+ const webViewRef = (0, import_react.useRef)(null);
62
+ const postRef = (0, import_react.useRef)({});
63
+ const sendMessage = (webViewRef2, type, code, data) => {
64
+ if (postRef.current[messageId]) {
65
+ onDebugEvent("InlineAd: message already posted", { messageId, ref: postRef.current });
66
+ return;
167
67
  }
168
- isModalInitRef.current = false;
169
- setModalOpen(false);
170
- setModalLoaded(false);
171
- setModalShown(false);
172
- };
173
- const debug = (name, data = {}) => {
174
- context?.onDebugEventInternal?.(name, {
175
- code,
176
- messageId,
177
- otherParams,
178
- bid,
179
- iframeUrl,
180
- iframeLoaded,
181
- showIframe,
182
- height,
183
- containerStyles,
184
- iframeStyles,
185
- ...data
186
- });
187
- };
188
- const debugModal = (name, data = {}) => {
189
- context?.onDebugEventInternal?.(name, {
190
- code,
191
- messageId,
192
- otherParams,
193
- bid,
194
- modalUrl,
195
- modalOpen,
196
- modalShown,
197
- modalLoaded,
198
- ...data
68
+ const message = makeIframeMessage(type, {
69
+ data,
70
+ code
199
71
  });
72
+ onDebugEvent("InlineAd: post message", { messageId, message });
73
+ webViewRef2.current?.injectJavaScript(`
74
+ window.dispatchEvent(new MessageEvent('message', {
75
+ data: ${JSON.stringify(message)}
76
+ }));
77
+ `);
78
+ postRef.current[messageId] = true;
200
79
  };
201
- debug("Format:updateState", {
202
- params: {
203
- messageId,
204
- code,
205
- otherParams
80
+ const updateBid = (0, import_react.useCallback)(() => {
81
+ const bid = session.getLastBid();
82
+ if (!bid || bid.messageId !== messageId) {
83
+ setBidId(null);
84
+ onDebugEvent("InlineAd: no bid", { messageId, bid });
85
+ return;
206
86
  }
207
- });
87
+ setBidId(bid.bidId);
88
+ onDebugEvent("InlineAd: update bid", { messageId, bid });
89
+ }, [session, messageId]);
90
+ (0, import_react.useEffect)(() => {
91
+ session.setOnUpdateBids(updateBid);
92
+ return () => {
93
+ session.setOnUpdateBids(void 0);
94
+ onDebugEvent("InlineAd: remove update bids callback", { messageId });
95
+ };
96
+ }, [session, updateBid]);
97
+ (0, import_react.useEffect)(() => {
98
+ updateBid();
99
+ }, [updateBid]);
100
+ if (!bidId) {
101
+ onDebugEvent("InlineAd: no bid id", { messageId, bidId });
102
+ return null;
103
+ }
104
+ const iframeUrl = getIframeUrl(bidId, messageId, session.config.adServerUrl);
105
+ if (!iframeUrl) {
106
+ onDebugEvent("InlineAd: no iframe url", { messageId, bidId });
107
+ return null;
108
+ }
208
109
  const onMessage = (event) => {
209
110
  try {
210
111
  const data = JSON.parse(event.nativeEvent.data);
211
- debug("Format:iframeMessage", {
212
- message: data,
213
- params: { data, messageId, code, otherParams }
214
- });
215
112
  const messageHandler = handleIframeMessage(
216
113
  (message) => {
114
+ onDebugEvent("InlineAd: message handler", { messageId, message, bidId });
217
115
  switch (message.type) {
218
116
  case "init-iframe":
219
- setIframeLoaded(true);
220
- debug("Format:iframePostMessage", {
221
- params: {
222
- code,
223
- messages: context?.messages,
224
- sdk: "sdk-react-native",
225
- otherParams,
226
- messageId
227
- }
228
- });
229
- sendMessage(webViewRef, "update-iframe", code, {
230
- messages: context?.messages,
117
+ sendMessage(webViewRef, "update-iframe", "inlineAd", {
118
+ messages: session.getMessages(),
231
119
  sdk: "sdk-react-native",
232
- otherParams,
120
+ otherParams: {},
233
121
  messageId
234
122
  });
235
123
  break;
236
124
  case "error-iframe":
237
- reset();
238
- break;
239
- case "resize-iframe":
240
- setHeight(message.data.height);
241
125
  break;
242
126
  case "click-iframe":
243
- if (message.data.url) {
244
- import_react_native.Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
245
- (err) => console.error("error opening url", err)
246
- );
247
- }
248
- context?.onAdClickInternal(message.data);
249
127
  break;
250
128
  case "view-iframe":
251
- context?.onAdViewInternal(message.data);
252
- break;
253
- case "ad-done-iframe":
254
- if (bid?.bidId && message.data.cachedContent) {
255
- context?.cachedContentRef?.current?.set(bid.bidId, message.data.cachedContent);
256
- }
257
- break;
258
- case "show-iframe":
259
- setShowIframe(true);
260
- break;
261
- case "hide-iframe":
262
- setShowIframe(false);
263
- break;
264
- case "set-styles-iframe":
265
- setContainerStyles(message.data.containerStyles);
266
- setIframeStyles(message.data.iframeStyles);
267
- break;
268
- case "open-component-iframe":
269
- setModalOpen(true);
270
- modalInitTimeoutRef.current = setTimeout(() => {
271
- if (!isModalInitRef.current) {
272
- resetModal();
273
- }
274
- }, message.data.timeout ?? 5e3);
275
- break;
276
- case "event-iframe":
277
- onEvent?.(message.data);
278
- context?.onAdEventInternal(message.data);
279
- break;
280
- }
281
- },
282
- {
283
- code
284
- }
285
- );
286
- messageHandler({ data });
287
- } catch (e) {
288
- debug("Format:iframeMessageError", {
289
- params: { error: e, messageId, code, otherParams },
290
- error: e
291
- });
292
- console.error("error parsing message from webview", e);
293
- reset();
294
- }
295
- };
296
- const onModalMessage = (event) => {
297
- try {
298
- const data = JSON.parse(event.nativeEvent.data);
299
- debugModal("Format:modalIframeMessage", {
300
- params: { data, messageId, code, otherParams },
301
- message: data
302
- });
303
- const messageHandler = handleIframeMessage(
304
- (message) => {
305
- switch (message.type) {
306
- case "close-component-iframe":
307
- resetModal();
308
- break;
309
- case "init-component-iframe":
310
- isModalInitRef.current = true;
311
- if (modalInitTimeoutRef.current) {
312
- clearTimeout(modalInitTimeoutRef.current);
313
- modalInitTimeoutRef.current = null;
314
- }
315
- setModalShown(true);
316
- break;
317
- case "error-component-iframe":
318
- case "error-iframe":
319
- resetModal();
320
- context?.captureError(new Error("Processing modal iframe error"));
321
- break;
322
- case "click-iframe":
323
- if (message.data.url) {
324
- import_react_native.Linking.openURL(`${context?.adServerUrl}${message.data.url}`).catch(
325
- (err) => console.error("error opening url", err)
326
- );
327
- }
328
- context?.onAdClickInternal(message.data);
329
129
  break;
330
130
  case "event-iframe":
331
- onEvent?.(message.data);
332
- context?.onAdEventInternal(message.data);
333
131
  break;
334
132
  }
335
133
  },
336
134
  {
337
- code,
338
- component: "modal"
135
+ code: "inlineAd"
339
136
  }
340
137
  );
341
138
  messageHandler({ data });
342
139
  } catch (e) {
343
- debugModal("Format:modalIframeMessageError", {
344
- params: { error: e, messageId, code, otherParams },
345
- error: e
346
- });
347
140
  console.error("error parsing message from webview", e);
348
- resetModal();
141
+ onDebugEvent("InlineAd: error parsing message from webview", { messageId, error: e });
349
142
  }
350
143
  };
351
- const paramsString = (0, import_sdk_react.convertParamsToString)(otherParams);
352
- (0, import_react2.useEffect)(() => {
353
- if (!iframeLoaded || !context?.adServerUrl || !bid || !webViewRef.current) {
354
- debug("Format:iframePostMessageNotLoaded", {
355
- params: {
356
- messageId,
357
- iframeLoaded,
358
- contextAdServerUrl: context?.adServerUrl,
359
- bid,
360
- code,
361
- otherParams
362
- }
363
- });
364
- return;
365
- }
366
- debug("Format:iframePostMessage", {
367
- params: {
368
- messageId,
369
- otherParams,
370
- code
371
- }
372
- });
373
- sendMessage(webViewRef, "update-iframe", code, {
374
- data: { otherParams },
375
- code
376
- });
377
- }, [paramsString, iframeLoaded, context?.adServerUrl, bid, code]);
378
- const checkIfInViewport = () => {
379
- if (!containerRef.current) {
380
- debug("Format:checkIfInViewportNoContainer", {
381
- params: {
382
- messageId,
383
- code,
384
- otherParams
385
- }
386
- });
387
- return;
388
- }
389
- debug("Format:checkIfInViewportMeasure", {
390
- params: {
391
- windowWidth,
392
- windowHeight,
393
- messageId,
394
- code,
395
- otherParams
396
- }
397
- });
398
- containerRef.current.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
399
- sendMessage(webViewRef, "update-dimensions-iframe", code, {
400
- windowWidth,
401
- windowHeight,
402
- containerWidth,
403
- containerHeight,
404
- containerX,
405
- containerY,
406
- keyboardHeight: keyboardHeightRef.current
407
- });
408
- debug("Format:checkIfInViewportMeasureSend", {
409
- params: {
410
- messageId,
411
- code,
412
- otherParams,
413
- windowWidth,
414
- windowHeight,
415
- containerWidth,
416
- containerHeight,
417
- containerX,
418
- containerY,
419
- keyboardHeight: keyboardHeightRef.current
420
- }
421
- });
422
- });
423
- };
424
- (0, import_react2.useEffect)(() => {
425
- if (!isAdViewVisible) {
426
- debug("Format:checkIfInViewportNotVisible", {
427
- params: {
428
- messageId,
429
- code,
430
- otherParams
431
- }
432
- });
433
- return;
434
- }
435
- const interval = setInterval(() => {
436
- checkIfInViewport();
437
- }, 250);
438
- return () => {
439
- clearInterval(interval);
440
- debug("Format:checkIfInViewportCleanup", {
441
- params: {
442
- messageId,
443
- code,
444
- otherParams
445
- }
446
- });
447
- };
448
- }, [isAdViewVisible]);
449
- (0, import_react2.useEffect)(() => {
450
- const showSubscription = import_react_native.Keyboard.addListener("keyboardDidShow", (e) => {
451
- debug("Format:keyboardDidShow", {
452
- params: {
453
- keyboardHeight: e?.endCoordinates?.height ?? 0,
454
- messageId,
455
- code,
456
- otherParams
457
- }
458
- });
459
- keyboardHeightRef.current = e?.endCoordinates?.height ?? 0;
460
- });
461
- const hideSubscription = import_react_native.Keyboard.addListener("keyboardDidHide", () => {
462
- debug("Format:keyboardDidHide", {
463
- params: {
464
- messageId,
465
- code,
466
- otherParams
467
- }
468
- });
469
- keyboardHeightRef.current = 0;
470
- });
471
- return () => {
472
- showSubscription.remove();
473
- hideSubscription.remove();
474
- keyboardHeightRef.current = 0;
475
- debug("Format:keyboardEffectCleanup", {
476
- params: {
477
- messageId,
478
- code,
479
- otherParams
480
- }
481
- });
482
- };
483
- }, []);
484
- if (!context || !bid || !iframeUrl) {
485
- debug("Format:noContextOrBidOrIframeUrl", {
486
- params: {
487
- context,
488
- bid,
489
- iframeUrl,
490
- messageId,
491
- code,
492
- otherParams
493
- }
494
- });
495
- return null;
496
- }
497
- const inlineContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
498
- frame_webview_default,
144
+ onDebugEvent("InlineAd: iframe url", { messageId, iframeUrl });
145
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
146
+ import_react_native_webview.WebView,
499
147
  {
500
- ref: webViewRef,
501
- iframeUrl,
502
- onMessage,
148
+ source: {
149
+ uri: iframeUrl
150
+ },
503
151
  style: {
504
- height,
152
+ height: 300,
505
153
  width: "100%",
506
- backgroundColor: "transparent",
507
- borderWidth: 0,
508
- ...iframeStyles
154
+ backgroundColor: "red"
509
155
  },
510
- onError: () => {
511
- debug("Format:iframeError", {
512
- params: {
513
- messageId,
514
- code,
515
- otherParams
156
+ ref: webViewRef,
157
+ onMessage,
158
+ allowsInlineMediaPlayback: true,
159
+ mediaPlaybackRequiresUserAction: false,
160
+ javaScriptEnabled: true,
161
+ domStorageEnabled: true,
162
+ allowsFullscreenVideo: false,
163
+ injectedJavaScript: `
164
+ window.addEventListener("message", function(event) {
165
+ if (window.ReactNativeWebView && event.data) {
166
+ // ReactNativeWebView.postMessage only supports string data
167
+ window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
516
168
  }
517
- });
518
- reset();
169
+ }, false);
170
+ `,
171
+ onError: () => {
172
+ onDebugEvent("InlineAd: error loading iframe", { messageId });
519
173
  },
520
174
  onLoad: () => {
521
- debug("Format:iframeLoad", {
522
- params: {
523
- messageId,
524
- code,
525
- otherParams
526
- }
527
- });
175
+ onDebugEvent("InlineAd: iframe loaded", { messageId });
528
176
  }
529
177
  }
530
178
  );
531
- const interstitialContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
532
- import_react_native.Modal,
533
- {
534
- visible: modalOpen,
535
- transparent: true,
536
- onRequestClose: resetModal,
537
- animationType: "slide",
538
- statusBarTranslucent: true,
539
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
540
- import_react_native.View,
541
- {
542
- style: {
543
- flex: 1,
544
- // Don't show the modal until the modal page is loaded and sends 'init-component-iframe' message back to SDK
545
- ...modalShown ? { opacity: 1, pointerEvents: "auto" } : { opacity: 0, pointerEvents: "none" }
546
- },
547
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
548
- frame_webview_default,
549
- {
550
- ref: modalWebViewRef,
551
- iframeUrl: modalUrl,
552
- onMessage: onModalMessage,
553
- style: {
554
- backgroundColor: "transparent",
555
- height: "100%",
556
- width: "100%",
557
- borderWidth: 0
558
- },
559
- onError: () => {
560
- debug("Format:modalError", {
561
- params: {
562
- messageId,
563
- code,
564
- otherParams
565
- }
566
- });
567
- resetModal();
568
- },
569
- onLoad: () => {
570
- debug("Format:modalLoad", {
571
- params: {
572
- messageId,
573
- code,
574
- otherParams
575
- }
576
- });
577
- setModalLoaded(true);
578
- }
579
- }
580
- )
581
- }
582
- )
583
- }
584
- );
585
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
586
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
587
- import_react_native.View,
588
- {
589
- style: isAdViewVisible ? containerStyles : {
590
- height: 0,
591
- overflow: "hidden"
592
- },
593
- ref: containerRef,
594
- children: wrapper ? wrapper(inlineContent) : inlineContent
595
- }
596
- ),
597
- interstitialContent
598
- ] });
599
179
  };
600
- var FormatWithErrorBoundary = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sdk_react.ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Format, { ...props }) });
601
- var Format_default = FormatWithErrorBoundary;
602
180
 
603
- // src/formats/InlineAd.tsx
604
- var import_jsx_runtime3 = require("react/jsx-runtime");
605
- var InlineAd = ({ code, messageId, wrapper, ...props }) => {
606
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Format_default, { code, messageId, wrapper, ...props });
181
+ // src/Configuration.ts
182
+ var DEFAULT_AD_SERVER_URL = "https://server.megabrain.co";
183
+ var DEFAULT_PLACEMENT_CODE = "inlineAd";
184
+ var DEFAULT_LOG_LEVEL = "info";
185
+ var Configuration = class {
186
+ config;
187
+ constructor(config) {
188
+ this.config = config;
189
+ }
190
+ get publisherToken() {
191
+ return this.config.publisherToken;
192
+ }
193
+ get userId() {
194
+ return this.config.userId;
195
+ }
196
+ get conversationId() {
197
+ return this.config.conversationId;
198
+ }
199
+ get character() {
200
+ return this.config.character;
201
+ }
202
+ get placementCode() {
203
+ return this.config.placementCode ?? DEFAULT_PLACEMENT_CODE;
204
+ }
205
+ get variantId() {
206
+ return this.config.variantId;
207
+ }
208
+ get regulatory() {
209
+ return this.config.regulatory;
210
+ }
211
+ get logLevel() {
212
+ return this.config.logLevel ?? DEFAULT_LOG_LEVEL;
213
+ }
214
+ get userEmail() {
215
+ return this.config.userEmail;
216
+ }
217
+ get adServerUrl() {
218
+ return this.config.adServerUrl ?? DEFAULT_AD_SERVER_URL;
219
+ }
607
220
  };
608
- var InlineAd_default = InlineAd;
609
-
610
- // src/context/AdsProvider.tsx
611
- var import_sdk_react2 = require("@kontextso/sdk-react");
612
- var import_netinfo = require("@react-native-community/netinfo");
613
- var import_react_native3 = require("react-native");
614
- var import_react_native_device_info = __toESM(require("react-native-device-info"));
615
221
 
616
- // package.json
617
- var version = "3.0.7-rc.2";
618
-
619
- // src/NativeRNKontext.ts
620
- var import_react_native2 = require("react-native");
621
- var NativeRNKontext_default = import_react_native2.TurboModuleRegistry.getEnforcing("RNKontext");
222
+ // src/Logger.ts
223
+ var Logger = class {
224
+ localLevel = "log";
225
+ remoteLevel = "error";
226
+ remoteConfig = null;
227
+ levels = {
228
+ debug: 0,
229
+ info: 1,
230
+ log: 2,
231
+ warn: 3,
232
+ error: 4,
233
+ silent: 5
234
+ };
235
+ getLocalLevel() {
236
+ return this.localLevel;
237
+ }
238
+ setLocalLevel(level) {
239
+ this.localLevel = level;
240
+ }
241
+ getRemoteLevel() {
242
+ return this.remoteLevel;
243
+ }
244
+ setRemoteLevel(level) {
245
+ this.remoteLevel = level;
246
+ }
247
+ configureRemote(url, params) {
248
+ this.remoteConfig = { url, params };
249
+ }
250
+ shouldLog(level, targetLevel) {
251
+ if (targetLevel === "silent") {
252
+ return false;
253
+ }
254
+ return this.levels[level] >= this.levels[targetLevel];
255
+ }
256
+ logToConsole(level, ...args) {
257
+ if (this.shouldLog(level, this.localLevel)) {
258
+ if (level === "silent") {
259
+ return;
260
+ }
261
+ console[level](...args);
262
+ }
263
+ }
264
+ logToRemote(level, ...args) {
265
+ if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
266
+ fetch(`${this.remoteConfig.url}/log`, {
267
+ method: "POST",
268
+ body: JSON.stringify({
269
+ ...this.remoteConfig.params,
270
+ level,
271
+ message: args,
272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
273
+ })
274
+ }).catch((e) => {
275
+ });
276
+ }
277
+ }
278
+ debug(...args) {
279
+ this.logToConsole("debug", ...args);
280
+ this.logToRemote("debug", ...args);
281
+ }
282
+ info(...args) {
283
+ this.logToConsole("info", ...args);
284
+ this.logToRemote("info", ...args);
285
+ }
286
+ log(...args) {
287
+ this.logToConsole("log", ...args);
288
+ this.logToRemote("log", ...args);
289
+ }
290
+ warn(...args) {
291
+ this.logToConsole("warn", ...args);
292
+ this.logToRemote("warn", ...args);
293
+ }
294
+ error(...args) {
295
+ this.logToConsole("error", ...args);
296
+ this.logToRemote("error", ...args);
297
+ }
298
+ };
622
299
 
623
- // src/context/AdsProvider.tsx
624
- var import_jsx_runtime4 = require("react/jsx-runtime");
625
- ErrorUtils.setGlobalHandler((error, isFatal) => {
626
- if (!isFatal) {
627
- import_sdk_react2.log.warn(error);
628
- } else {
629
- import_sdk_react2.log.error(error);
300
+ // src/utils/request.ts
301
+ async function fetchWithRetry(url, options = {}) {
302
+ const { retry = {}, timeout, abortController: abortControllerOption, ...fetchOptions } = options;
303
+ const maxRetries = retry.maxRetries ?? 3;
304
+ const baseDelay = retry.baseDelay ?? 1e3;
305
+ const backoffFactor = retry.backoffFactor ?? 2;
306
+ const abortController = abortControllerOption || new AbortController();
307
+ fetchOptions.signal = abortController.signal;
308
+ let timeoutId = null;
309
+ if (timeout) {
310
+ timeoutId = setTimeout(() => abortController.abort("timeout"), timeout);
630
311
  }
631
- });
632
- var getDevice = async () => {
633
- try {
634
- const powerState = await import_react_native_device_info.default.getPowerState();
635
- const deviceType = import_react_native_device_info.default.getDeviceType();
636
- const soundOn = await NativeRNKontext_default.isSoundOn();
637
- const screen = import_react_native3.Dimensions.get("screen");
638
- const networkInfo = await (0, import_netinfo.fetch)();
639
- const mapDeviceTypeToHardwareType = () => {
640
- switch (deviceType) {
641
- case "Handset":
642
- return "handset";
643
- case "Tablet":
644
- return "tablet";
645
- case "Tv":
646
- return "tv";
647
- case "Desktop":
648
- return "desktop";
649
- default:
650
- return "other";
312
+ let lastError = null;
313
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
314
+ try {
315
+ const response = await fetch(url, fetchOptions);
316
+ if (response.status >= 500 || response.status === 429) {
317
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
318
+ error.status = response.status;
319
+ throw error;
651
320
  }
652
- };
653
- return {
654
- hardware: {
655
- brand: import_react_native_device_info.default.getBrand(),
656
- model: import_react_native_device_info.default.getDeviceId(),
657
- type: mapDeviceTypeToHardwareType()
658
- // bootTime: Not available without native module
659
- // sdCardAvailable: Not available without native module
660
- },
661
- audio: {
662
- muted: soundOn
663
- // volume: Requires react-native-volume-manager
664
- // outputPluggedIn: Not available without native module
665
- // outputType: Not available without native module
666
- },
667
- network: {
668
- carrier: networkInfo.type === import_netinfo.NetInfoStateType.cellular && networkInfo.details.carrier || void 0,
669
- userAgent: await import_react_native_device_info.default.getUserAgent(),
670
- type: [import_netinfo.NetInfoStateType.wifi, import_netinfo.NetInfoStateType.cellular, import_netinfo.NetInfoStateType.ethernet].includes(networkInfo.type) ? networkInfo.type : import_netinfo.NetInfoStateType.other,
671
- detail: networkInfo.type === import_netinfo.NetInfoStateType.cellular && networkInfo.details.cellularGeneration || void 0
672
- },
673
- os: {
674
- name: import_react_native3.Platform.OS,
675
- version: import_react_native_device_info.default.getSystemVersion(),
676
- locale: Intl.DateTimeFormat().resolvedOptions().locale,
677
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
678
- },
679
- screen: {
680
- darkMode: import_react_native3.Appearance.getColorScheme() === "dark",
681
- dpr: import_react_native3.PixelRatio.get(),
682
- height: screen.height,
683
- width: screen.width,
684
- orientation: screen.width > screen.height ? "landscape" : "portrait"
685
- },
686
- power: {
687
- batteryLevel: powerState.batteryLevel,
688
- batteryState: powerState.batteryState,
689
- lowPowerMode: powerState.lowPowerMode
321
+ return response;
322
+ } catch (error) {
323
+ if (abortController.signal.aborted) {
324
+ throw new Error(`Fetch aborted: ${abortController.signal.reason}`);
325
+ }
326
+ lastError = error instanceof Error ? error : new Error("Unknown error");
327
+ const shouldRetry = error instanceof TypeError || // Network error
328
+ error.status >= 500 || // Server error
329
+ error.status === 429;
330
+ if (attempt === maxRetries || !shouldRetry) {
331
+ break;
690
332
  }
333
+ const delay = baseDelay * backoffFactor ** attempt;
334
+ await new Promise((resolve) => setTimeout(resolve, delay));
335
+ } finally {
336
+ if (timeoutId) {
337
+ clearTimeout(timeoutId);
338
+ }
339
+ }
340
+ }
341
+ throw lastError;
342
+ }
343
+
344
+ // src/Preload.ts
345
+ var Preload = class {
346
+ session;
347
+ messages = [];
348
+ abortController;
349
+ running = false;
350
+ bid = null;
351
+ constructor(session, messages) {
352
+ this.session = session;
353
+ this.abortController = new AbortController();
354
+ this.setMessages(messages);
355
+ }
356
+ setMessages(messages) {
357
+ this.messages = messages;
358
+ }
359
+ hasBid() {
360
+ return this.bid !== null;
361
+ }
362
+ getBid() {
363
+ return this.bid;
364
+ }
365
+ setBid(bid) {
366
+ this.bid = bid;
367
+ }
368
+ cancel() {
369
+ this.abortController.abort();
370
+ this.running = false;
371
+ }
372
+ isRunning() {
373
+ return this.running;
374
+ }
375
+ /*
376
+ private async getDevice () {
377
+ try {
378
+ return await getDevice()
379
+ } catch (error) {
380
+ this.session.logger.error('Error getting device:', error)
381
+ return {} as DeviceConfig
382
+ }
383
+ }
384
+ */
385
+ async getPreloadBody() {
386
+ const config = this.session.config;
387
+ const preloadBody = {
388
+ messages: this.messages,
389
+ sessionId: this.session.getSessionId(),
390
+ publisherToken: config.publisherToken,
391
+ userId: config.userId,
392
+ userEmail: config.userEmail,
393
+ conversationId: config.conversationId,
394
+ character: config.character,
395
+ enabledPlacementCodes: [config.placementCode],
396
+ variantId: config.variantId,
397
+ regulatory: config.regulatory,
398
+ sdk: this.session.sdk
691
399
  };
692
- } catch (error) {
693
- console.error(error);
694
- return {};
400
+ return preloadBody;
401
+ }
402
+ canRequestAd() {
403
+ if (this.session.isSessionDisabled()) {
404
+ return {
405
+ status: "error",
406
+ message: "Session is disabled"
407
+ };
408
+ }
409
+ if (!this.messages.length) {
410
+ return {
411
+ status: "error",
412
+ message: "No messages"
413
+ };
414
+ }
415
+ return null;
416
+ }
417
+ async requestAd() {
418
+ this.running = true;
419
+ const cannotRequestAdReason = this.canRequestAd();
420
+ if (cannotRequestAdReason) {
421
+ return cannotRequestAdReason;
422
+ }
423
+ const config = this.session.config;
424
+ const preloadBody = await this.getPreloadBody();
425
+ try {
426
+ const response = await fetchWithRetry(`${config.adServerUrl}/preload`, {
427
+ method: "POST",
428
+ body: JSON.stringify(preloadBody),
429
+ headers: {
430
+ "Kontextso-Is-Disabled": "0",
431
+ "Kontextso-Publisher-Token": config.publisherToken
432
+ },
433
+ timeout: 16e3,
434
+ abortController: this.abortController
435
+ });
436
+ const jsonResponse = await response.json();
437
+ const bid = jsonResponse.bids?.[0] ?? null;
438
+ if (jsonResponse.sessionId) {
439
+ this.session.setSessionId(jsonResponse.sessionId);
440
+ }
441
+ if (bid) {
442
+ this.setBid(bid);
443
+ }
444
+ this.session.updateBids();
445
+ return {
446
+ status: "success"
447
+ };
448
+ } catch (error) {
449
+ this.session.logger.error("Error requesting ad:", error);
450
+ return {
451
+ status: "error"
452
+ };
453
+ } finally {
454
+ this.running = false;
455
+ }
695
456
  }
696
457
  };
697
- var getApp = async () => {
698
- try {
699
- return {
700
- bundleId: import_react_native_device_info.default.getBundleId(),
701
- firstInstallTime: await import_react_native_device_info.default.getFirstInstallTime(),
702
- lastUpdateTime: await import_react_native_device_info.default.getLastUpdateTime(),
703
- version: import_react_native_device_info.default.getVersion()
704
- // Not supported in react-native-device-info v10
705
- // startTime: await DeviceInfo.getStartupTime(),
706
- };
707
- } catch (error) {
708
- console.error(error);
709
- return {};
458
+
459
+ // src/Session.ts
460
+ var Session = class {
461
+ config;
462
+ logger;
463
+ sdk;
464
+ sessionId = null;
465
+ sessionDisabled = false;
466
+ messages = [];
467
+ bids = [];
468
+ onUpdateBidsCallback = null;
469
+ // only one preload instance is allowed at a time
470
+ preloadInstance = null;
471
+ constructor(config, { sdk }) {
472
+ this.config = config;
473
+ this.logger = new Logger();
474
+ this.sdk = sdk;
475
+ }
476
+ setOnUpdateBids(callback) {
477
+ this.onUpdateBidsCallback = callback;
478
+ }
479
+ isSessionDisabled() {
480
+ return this.sessionDisabled;
481
+ }
482
+ setSessionDisabled(disabled) {
483
+ this.sessionDisabled = disabled;
484
+ }
485
+ getSessionId() {
486
+ return this.sessionId;
487
+ }
488
+ setSessionId(sessionId) {
489
+ this.logger.info("Session ID set:", sessionId);
490
+ this.sessionId = sessionId;
491
+ }
492
+ addMessage(message) {
493
+ this.messages.push(message);
494
+ this.updateBids();
495
+ }
496
+ getMessages() {
497
+ return this.messages;
498
+ }
499
+ updateBids() {
500
+ if (!this.preloadInstance?.hasBid()) {
501
+ return;
502
+ }
503
+ const bid = this.preloadInstance.getBid();
504
+ if (!bid) {
505
+ return;
506
+ }
507
+ const lastMessage = this.messages[this.messages.length - 1];
508
+ if (!lastMessage || lastMessage.role !== "assistant") {
509
+ return;
510
+ }
511
+ const assignedBid = this.bids.find((b) => {
512
+ if (b.bidId === bid?.bidId) {
513
+ return true;
514
+ }
515
+ if (b.messageId === lastMessage.id) {
516
+ return true;
517
+ }
518
+ return false;
519
+ });
520
+ if (assignedBid) {
521
+ return;
522
+ }
523
+ this.bids.push({
524
+ bidId: bid.bidId,
525
+ messageId: lastMessage.id
526
+ });
527
+ this.onUpdateBidsCallback?.();
528
+ }
529
+ preload() {
530
+ if (this.preloadInstance?.isRunning()) {
531
+ this.preloadInstance.cancel();
532
+ }
533
+ this.preloadInstance = new Preload(this, [...this.messages]);
534
+ return this.preloadInstance;
535
+ }
536
+ getLastBid() {
537
+ return this.bids[this.bids.length - 1];
710
538
  }
711
539
  };
712
- var getSdk = async () => ({
713
- name: "sdk-react-native",
714
- platform: import_react_native3.Platform.OS === "ios" ? "ios" : "android",
715
- version
716
- });
717
- var AdsProvider = (props) => {
718
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sdk_react2.AdsProviderInternal, { ...props, getDevice, getSdk, getApp });
540
+
541
+ // src/KontextAds.ts
542
+ var import_react_native = require("react-native");
543
+
544
+ // package.json
545
+ var version = "4.0.0-rc.0";
546
+
547
+ // src/KontextAds.ts
548
+ var KontextAds = (config) => {
549
+ return {
550
+ createSession: (sessionConfig) => createSession({
551
+ ...config,
552
+ ...sessionConfig
553
+ })
554
+ };
555
+ };
556
+ var createSession = (options) => {
557
+ const sdk = {
558
+ name: "sdk-react-native",
559
+ platform: import_react_native.Platform.OS === "ios" ? "ios" : "android",
560
+ version
561
+ };
562
+ const instance = new Session(new Configuration(options), { sdk });
563
+ return {
564
+ addMessage: (message) => {
565
+ instance.addMessage(message);
566
+ if (message.role === "user") {
567
+ instance.preload().requestAd();
568
+ }
569
+ },
570
+ getInstance: () => instance
571
+ };
719
572
  };
720
573
  // Annotate the CommonJS export names for ESM import in node:
721
574
  0 && (module.exports = {
722
- AdsProvider,
723
- InlineAd
575
+ InlineAd,
576
+ KontextAds
724
577
  });