@kontextso/sdk-react-native 0.0.5 → 0.0.7-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.d.mts CHANGED
@@ -33,7 +33,9 @@ interface Ad {
33
33
  isStreaming?: boolean;
34
34
  isError?: boolean;
35
35
  viewed?: boolean;
36
+ clicked?: boolean;
36
37
  messageId?: string;
38
+ rewardContent?: string;
37
39
  product?: {
38
40
  id: string;
39
41
  title: string;
@@ -138,7 +140,7 @@ interface AdsProviderProps {
138
140
  onAdView?: OnAdView;
139
141
  onAdClick?: OnAdClick;
140
142
  logLevel?: any;
141
- conversationId?: string;
143
+ conversationId: string;
142
144
  styles?: AdStyles;
143
145
  }
144
146
  declare const VISITOR_ID_KEY = "brain-visitor-id";
package/dist/index.d.ts CHANGED
@@ -33,7 +33,9 @@ interface Ad {
33
33
  isStreaming?: boolean;
34
34
  isError?: boolean;
35
35
  viewed?: boolean;
36
+ clicked?: boolean;
36
37
  messageId?: string;
38
+ rewardContent?: string;
37
39
  product?: {
38
40
  id: string;
39
41
  title: string;
@@ -138,7 +140,7 @@ interface AdsProviderProps {
138
140
  onAdView?: OnAdView;
139
141
  onAdClick?: OnAdClick;
140
142
  logLevel?: any;
141
- conversationId?: string;
143
+ conversationId: string;
142
144
  styles?: AdStyles;
143
145
  }
144
146
  declare const VISITOR_ID_KEY = "brain-visitor-id";
package/dist/index.js CHANGED
@@ -97,7 +97,7 @@ var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
97
97
  };
98
98
  var fixUrl = (adserverUrl, ad) => {
99
99
  if (ad.content) {
100
- ad.content = ad.content.replace("/ad/", `${adserverUrl}/ad/`);
100
+ ad.content = ad.content.replace("/impression/", `${adserverUrl}/impression/`);
101
101
  }
102
102
  return { ...ad, url: `${adserverUrl}${ad.url}` };
103
103
  };
@@ -105,7 +105,8 @@ var mergeAds = ({
105
105
  initAds,
106
106
  preloadAds,
107
107
  streamAds,
108
- viewedAds
108
+ viewedAds,
109
+ clickedAds
109
110
  }) => {
110
111
  const ads = [...initAds];
111
112
  if (Array.isArray(preloadAds)) {
@@ -132,6 +133,7 @@ var mergeAds = ({
132
133
  }
133
134
  for (const ad of ads) {
134
135
  ad.viewed = viewedAds.includes(ad?.id || "placeholder");
136
+ ad.clicked = clickedAds.includes(ad?.id || "placeholder");
135
137
  }
136
138
  return ads;
137
139
  };
@@ -197,15 +199,91 @@ var parseMessageText = (text) => {
197
199
  return parts;
198
200
  };
199
201
 
200
- // src/hooks/useInitializeAds.tsx
201
- var import_loglevel = __toESM(require("loglevel"));
202
+ // src/log.ts
203
+ var Logger = class {
204
+ localLevel = "log";
205
+ remoteLevel = "error";
206
+ remoteConfig = null;
207
+ levels = {
208
+ debug: 0,
209
+ info: 1,
210
+ log: 2,
211
+ warn: 3,
212
+ error: 4,
213
+ silent: 5
214
+ };
215
+ setLocalLevel(level) {
216
+ this.localLevel = level;
217
+ }
218
+ setRemoteLevel(level) {
219
+ this.remoteLevel = level;
220
+ }
221
+ configureRemote(url, params) {
222
+ this.remoteConfig = { url, params };
223
+ }
224
+ shouldLog(level, targetLevel) {
225
+ if (targetLevel === "silent") {
226
+ return false;
227
+ }
228
+ return this.levels[level] >= this.levels[targetLevel];
229
+ }
230
+ logToConsole(level, ...args) {
231
+ if (this.shouldLog(level, this.localLevel)) {
232
+ if (level === "silent") {
233
+ return;
234
+ }
235
+ console[level](...args);
236
+ }
237
+ }
238
+ logToRemote(level, ...args) {
239
+ if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
240
+ fetch(
241
+ `${this.remoteConfig.url}/log`,
242
+ {
243
+ method: "POST",
244
+ body: JSON.stringify({
245
+ ...this.remoteConfig.params,
246
+ level,
247
+ message: args,
248
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
249
+ })
250
+ }
251
+ ).catch((e) => {
252
+ });
253
+ }
254
+ }
255
+ debug(...args) {
256
+ this.logToConsole("debug", ...args);
257
+ this.logToRemote("debug", ...args);
258
+ }
259
+ info(...args) {
260
+ this.logToConsole("info", ...args);
261
+ this.logToRemote("info", ...args);
262
+ }
263
+ log(...args) {
264
+ this.logToConsole("log", ...args);
265
+ this.logToRemote("log", ...args);
266
+ }
267
+ warn(...args) {
268
+ this.logToConsole("warn", ...args);
269
+ this.logToRemote("warn", ...args);
270
+ }
271
+ error(...args) {
272
+ this.logToConsole("error", ...args);
273
+ this.logToRemote("error", ...args);
274
+ }
275
+ };
276
+ var log = new Logger();
277
+ var log_default = log;
202
278
 
203
279
  // package.json
204
- var version = "0.0.5-rc.8";
280
+ var version = "0.0.7-rc.0";
205
281
 
206
282
  // src/hooks/useInitializeAds.tsx
283
+ var SINGLE_INIT_TIMEOUT_BUDGET_MS = 3e3;
284
+ var SINGLE_INIT_RETRIES = 3;
207
285
  async function initialize(adServerUrl, publisherToken, userId, conversationId, legacyVisitorId, character) {
208
- import_loglevel.default.log("[BRAIN] init ads started");
286
+ log_default.log("[BRAIN] init ads started");
209
287
  const response = await fetchRetry(
210
288
  `${adServerUrl}/init`,
211
289
  {
@@ -221,8 +299,8 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
221
299
  character
222
300
  })
223
301
  },
224
- 3,
225
- 1e3
302
+ SINGLE_INIT_TIMEOUT_BUDGET_MS,
303
+ SINGLE_INIT_RETRIES
226
304
  );
227
305
  const {
228
306
  sessionId,
@@ -232,12 +310,13 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
232
310
  streamAdServer,
233
311
  onlyStream,
234
312
  defaultReactNativeStyles,
235
- overridingReactNativeStyles
313
+ overridingReactNativeStyles,
314
+ remoteLogLevel
236
315
  } = await response.json();
237
316
  const fixedAds = ads.map((ad) => {
238
317
  return fixUrl(adServerUrl, ad);
239
318
  });
240
- import_loglevel.default.log("[BRAIN] init ads done");
319
+ log_default.log("[BRAIN] init ads done");
241
320
  return {
242
321
  sessionDisabled,
243
322
  sessionId,
@@ -246,7 +325,8 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
246
325
  streamAdServer,
247
326
  onlyStream,
248
327
  defaultReactNativeStylesResponse: defaultReactNativeStyles,
249
- overridingReactNativeStylesResponse: overridingReactNativeStyles
328
+ overridingReactNativeStylesResponse: overridingReactNativeStyles,
329
+ remoteLogLevel
250
330
  };
251
331
  }
252
332
  function useInitializeAds({
@@ -267,9 +347,10 @@ function useInitializeAds({
267
347
  const [defaultReactNativeStyles, setDefaultReactNativeStyles] = (0, import_react.useState)();
268
348
  const [overridingReactNativeStyles, setOverridingReactNativeStyles] = (0, import_react.useState)();
269
349
  (0, import_react.useEffect)(() => {
350
+ log_default.setRemoteLevel("debug");
270
351
  setSessionId(void 0);
271
352
  if (!isDisabled && userId) {
272
- import_loglevel.default.debug("[BRAIN] Initializing ads.");
353
+ log_default.debug("[BRAIN] Initializing ads.");
273
354
  initialize(
274
355
  adServerUrl,
275
356
  publisherToken,
@@ -286,8 +367,10 @@ function useInitializeAds({
286
367
  streamAdServer,
287
368
  onlyStream: onlyStream2,
288
369
  defaultReactNativeStylesResponse,
289
- overridingReactNativeStylesResponse
370
+ overridingReactNativeStylesResponse,
371
+ remoteLogLevel
290
372
  }) => {
373
+ log_default.setRemoteLevel(remoteLogLevel || "silent");
291
374
  if (!sessionDisabled) {
292
375
  setSessionId(sessionId2);
293
376
  setEnabledPlacements(enabledPlacements2);
@@ -297,15 +380,15 @@ function useInitializeAds({
297
380
  setDefaultReactNativeStyles(defaultReactNativeStylesResponse);
298
381
  setOverridingReactNativeStyles(overridingReactNativeStylesResponse);
299
382
  } else {
300
- import_loglevel.default.debug("[BRAIN] Session is disabled by server.");
383
+ log_default.debug("[BRAIN] Session is disabled by server.");
301
384
  }
302
385
  }
303
386
  ).catch((e) => {
304
- import_loglevel.default.warn("[BRAIN] Error initializing ads", e);
387
+ log_default.warn("[BRAIN] Error initializing ads", e);
305
388
  setError(e.message);
306
389
  });
307
390
  } else {
308
- import_loglevel.default.debug("[BRAIN] Ads are disabled.");
391
+ log_default.debug("[BRAIN] Ads are disabled.");
309
392
  }
310
393
  }, [
311
394
  adServerUrl,
@@ -328,7 +411,6 @@ function useInitializeAds({
328
411
 
329
412
  // src/hooks/usePreloadAds.tsx
330
413
  var import_react2 = require("react");
331
- var import_loglevel2 = __toESM(require("loglevel"));
332
414
  function usePreloadAds({
333
415
  userId,
334
416
  sessionId,
@@ -345,7 +427,7 @@ function usePreloadAds({
345
427
  (0, import_react2.useEffect)(() => {
346
428
  if (onlyStream) {
347
429
  setPreloadDone(true);
348
- import_loglevel2.default.log("[BRAIN] skipping preload ads");
430
+ log_default.log("[BRAIN] skipping preload ads");
349
431
  return;
350
432
  }
351
433
  async function preload() {
@@ -353,7 +435,7 @@ function usePreloadAds({
353
435
  if (!sessionId) {
354
436
  return;
355
437
  }
356
- import_loglevel2.default.log("[BRAIN] preload ads started");
438
+ log_default.log("[BRAIN] preload ads started");
357
439
  try {
358
440
  const response = await fetchRetry(
359
441
  `${adserverUrl}/preload`,
@@ -380,9 +462,9 @@ function usePreloadAds({
380
462
  return [...oldAds, ...newAds];
381
463
  });
382
464
  setPreloadDone(true);
383
- import_loglevel2.default.log("[BRAIN] preload ads finished");
465
+ log_default.log("[BRAIN] preload ads finished");
384
466
  } catch (e) {
385
- import_loglevel2.default.warn("[BRAIN] Error preloading ads", e);
467
+ log_default.warn("[BRAIN] Error preloading ads", e);
386
468
  }
387
469
  }
388
470
  preload();
@@ -615,7 +697,6 @@ async function* readDataStream(reader, {
615
697
  var import_encoding = require("react-native-polyfill-globals/src/encoding");
616
698
  var import_readable_stream = require("react-native-polyfill-globals/src/readable-stream");
617
699
  var import_react_native = require("react-native");
618
- var import_loglevel3 = __toESM(require("loglevel"));
619
700
  var patchFetch = fetch;
620
701
  if (import_react_native.Platform.OS !== "web") {
621
702
  (0, import_encoding.polyfill)();
@@ -624,9 +705,9 @@ if (import_react_native.Platform.OS !== "web") {
624
705
  }
625
706
  ErrorUtils.setGlobalHandler((error, isFatal) => {
626
707
  if (!isFatal) {
627
- import_loglevel3.default.warn(error);
708
+ log_default.warn(error);
628
709
  } else {
629
- import_loglevel3.default.error(error);
710
+ log_default.error(error);
630
711
  }
631
712
  });
632
713
  function useStreamAds({
@@ -687,7 +768,7 @@ function useStreamAds({
687
768
  );
688
769
  let data = "";
689
770
  let adData = {};
690
- import_loglevel3.default.log(`[BRAIN] streaming ${code} ad started`);
771
+ log_default.log(`[BRAIN] streaming ${code} ad started`);
691
772
  const reader = response.body.getReader();
692
773
  for await (const { type, value } of readDataStream(reader)) {
693
774
  switch (type) {
@@ -715,8 +796,8 @@ function useStreamAds({
715
796
  isLoading: false,
716
797
  isStreaming: true,
717
798
  content: content.replace(
718
- new RegExp(`/ad/${adData.product.id}/redirect`, "g"),
719
- `/ad/${adData.id}/redirect`
799
+ new RegExp(`/impression/${adData.product.id}/redirect`, "g"),
800
+ `/impression/${adData.id}/redirect`
720
801
  )
721
802
  });
722
803
  return newAd;
@@ -730,9 +811,9 @@ function useStreamAds({
730
811
  (ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isStreaming: false } : ad
731
812
  )
732
813
  );
733
- import_loglevel3.default.log(`[BRAIN] streaming ${code} ad done`);
814
+ log_default.log(`[BRAIN] streaming ${code} ad done`);
734
815
  } catch (e) {
735
- import_loglevel3.default.warn("[BRAIN] Error streaming ad", e);
816
+ log_default.warn("[BRAIN] Error streaming ad", e);
736
817
  setAds((oldAds) => [...oldAds, { isError: true, code }]);
737
818
  }
738
819
  };
@@ -748,12 +829,8 @@ function useStreamAds({
748
829
  return { ads };
749
830
  }
750
831
 
751
- // src/context/AdsProvider.tsx
752
- var import_loglevel5 = __toESM(require("loglevel"));
753
-
754
832
  // src/components/ErrorBoundary.tsx
755
833
  var import_react4 = __toESM(require("react"));
756
- var import_loglevel4 = __toESM(require("loglevel"));
757
834
  var captureErrorFn = (adServerUrl, error, componentStack, context) => {
758
835
  fetch(`${adServerUrl}/error`, {
759
836
  method: "POST",
@@ -763,7 +840,7 @@ var captureErrorFn = (adServerUrl, error, componentStack, context) => {
763
840
  context
764
841
  })
765
842
  }).catch((e) => {
766
- import_loglevel4.default.warn("Error reporting client error", e);
843
+ log_default.warn("Error reporting client error", e);
767
844
  });
768
845
  };
769
846
  var ErrorBoundary = class extends import_react4.default.Component {
@@ -813,8 +890,9 @@ var AdsProviderWithoutBoundary = ({
813
890
  ...props
814
891
  }) => {
815
892
  const [viewedAds, setViewedAds] = (0, import_react5.useState)([]);
893
+ const [clickedAds, setClickedAds] = (0, import_react5.useState)([]);
816
894
  const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
817
- import_loglevel5.default.setLevel(logLevel || "ERROR");
895
+ log_default.setLocalLevel(logLevel || "silent");
818
896
  const {
819
897
  ads: initAds,
820
898
  sessionId,
@@ -857,9 +935,9 @@ var AdsProviderWithoutBoundary = ({
857
935
  enabledPlacements,
858
936
  publisherToken
859
937
  });
860
- const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds });
938
+ const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds, clickedAds });
861
939
  const markAdAsViewed = (ad) => {
862
- import_loglevel5.default.debug("[Brain] Calling onAdView");
940
+ log_default.debug("[Brain] Calling onAdView");
863
941
  onAdView && onAdView({
864
942
  id: ad.id,
865
943
  code: ad.code,
@@ -871,13 +949,20 @@ var AdsProviderWithoutBoundary = ({
871
949
  setViewedAds((old) => [...old, ad.id]);
872
950
  };
873
951
  const onAdClickInternal = (ad) => {
874
- import_loglevel5.default.debug("[Brain] Calling onAdClick");
952
+ log_default.debug("[Brain] Calling onAdClick");
875
953
  onAdClick && onAdClick({
876
954
  id: ad.id,
877
955
  code: ad.code,
878
956
  messageId: ad.messageId,
879
957
  content: ad.content
880
958
  });
959
+ if (!ad.id) {
960
+ return;
961
+ }
962
+ if (clickedAds.includes(ad.id)) {
963
+ return;
964
+ }
965
+ setClickedAds((old) => [...old, ad.id]);
881
966
  };
882
967
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
883
968
  AdsContext.Provider,
@@ -892,6 +977,7 @@ var AdsProviderWithoutBoundary = ({
892
977
  ads,
893
978
  sessionId,
894
979
  isInitialised,
980
+ conversationId,
895
981
  isDisabled,
896
982
  onAdClickInternal,
897
983
  enabledPlacements,
@@ -957,7 +1043,6 @@ var AdsProvider = ({
957
1043
 
958
1044
  // src/hooks/useAdViewed.tsx
959
1045
  var import_react7 = require("react");
960
- var import_loglevel6 = __toESM(require("loglevel"));
961
1046
  function useAdViewed(ad) {
962
1047
  const context = (0, import_react7.useContext)(AdsContext);
963
1048
  const [stillMounted, setStillMounted] = (0, import_react6.useState)(false);
@@ -971,40 +1056,36 @@ function useAdViewed(ad) {
971
1056
  serverUrl = context.streamAdServerUrl;
972
1057
  }
973
1058
  try {
974
- const response = await fetch(`${serverUrl}/ad/${ad.id}/view`, {
1059
+ const response = await fetch(`${serverUrl}/impression/${ad.id}/view`, {
975
1060
  method: "POST"
976
1061
  });
977
1062
  if (!response.ok) {
978
1063
  throw new Error("Error sending view request");
979
1064
  }
980
- import_loglevel6.default.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
1065
+ log_default.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
981
1066
  } catch (e) {
982
- import_loglevel6.default.warn("[BRAIN] Error sending view request", e);
1067
+ log_default.warn("[BRAIN] Error sending view request", e);
983
1068
  }
984
1069
  };
985
1070
  (0, import_react6.useEffect)(() => {
986
1071
  if (!ad || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed || stillMounted) {
987
1072
  return;
988
1073
  }
989
- import_loglevel6.default.log("[BRAIN] setting timeout", ad.id, ad.code);
1074
+ log_default.log("[BRAIN] setting timeout", ad.id, ad.code);
990
1075
  setTimeout(() => {
991
- import_loglevel6.default.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
1076
+ log_default.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
992
1077
  setStillMounted(true);
993
1078
  }, 1e3);
994
1079
  }, [ad]);
995
1080
  (0, import_react6.useEffect)(() => {
996
1081
  if (stillMounted) {
997
- import_loglevel6.default.log("[BRAIN] sending request");
1082
+ log_default.log("[BRAIN] sending request");
998
1083
  sendRequest();
999
1084
  }
1000
1085
  }, [stillMounted]);
1001
1086
  }
1002
1087
 
1003
- // src/formats/InlineAd.tsx
1004
- var import_loglevel9 = __toESM(require("loglevel"));
1005
-
1006
1088
  // src/components/MarkdownText.tsx
1007
- var import_loglevel7 = __toESM(require("loglevel"));
1008
1089
  var import_react8 = require("react");
1009
1090
  var import_react_native2 = require("react-native");
1010
1091
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -1018,7 +1099,7 @@ function MarkdownText({
1018
1099
  const linkClickHandler = (href) => {
1019
1100
  onLinkClick();
1020
1101
  import_react_native2.Linking.openURL(href).catch(
1021
- (err) => import_loglevel7.default.warn("Failed to open URL:", err)
1102
+ (err) => log_default.warn("Failed to open URL:", err)
1022
1103
  );
1023
1104
  return false;
1024
1105
  };
@@ -1045,7 +1126,6 @@ function MarkdownText({
1045
1126
 
1046
1127
  // src/components/VideoPlayer.tsx
1047
1128
  var import_expo_av = require("expo-av");
1048
- var import_loglevel8 = __toESM(require("loglevel"));
1049
1129
  var import_react10 = require("react");
1050
1130
  var import_react_native4 = require("react-native");
1051
1131
 
@@ -1140,7 +1220,7 @@ var VideoPlayer = ({
1140
1220
  };
1141
1221
  const handleLinkClick = (url) => {
1142
1222
  if (url) {
1143
- import_react_native4.Linking.openURL(url).catch((err) => import_loglevel8.default.warn("Failed to open URL:", err));
1223
+ import_react_native4.Linking.openURL(url).catch((err) => log_default.warn("Failed to open URL:", err));
1144
1224
  onLinkClick();
1145
1225
  }
1146
1226
  };
@@ -1242,7 +1322,7 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1242
1322
  if (ad.isLoading || ad.content?.trim().toLowerCase().includes("none"))
1243
1323
  return null;
1244
1324
  const onProgress = (progress) => {
1245
- import_loglevel9.default.log(`Progress: ${progress}`);
1325
+ log_default.log(`Progress: ${progress}`);
1246
1326
  };
1247
1327
  const handleImageClick = (url) => {
1248
1328
  if (url) {
@@ -1251,11 +1331,15 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1251
1331
  window.open(url, "_blank");
1252
1332
  } else {
1253
1333
  import_react_native5.Linking.openURL(url).catch(
1254
- (err) => import_loglevel9.default.warn("Failed to open URL:", err)
1334
+ (err) => log_default.warn("Failed to open URL:", err)
1255
1335
  );
1256
1336
  }
1257
1337
  }
1258
1338
  };
1339
+ let adContent = ad.content;
1340
+ if (ad.clicked && ad.rewardContent) {
1341
+ adContent = ad.rewardContent;
1342
+ }
1259
1343
  const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles?.container, children: [
1260
1344
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles?.adBadgeContainer, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: styles?.adBadgeText, children: "Ad" }) }),
1261
1345
  ad.imageUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -1285,7 +1369,7 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1285
1369
  MarkdownText,
1286
1370
  {
1287
1371
  onLinkClick: () => context?.onAdClickInternal(ad),
1288
- content: ad.content
1372
+ content: adContent
1289
1373
  }
1290
1374
  ) }),
1291
1375
  ad.imageUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
package/dist/index.mjs CHANGED
@@ -71,7 +71,7 @@ var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
71
71
  };
72
72
  var fixUrl = (adserverUrl, ad) => {
73
73
  if (ad.content) {
74
- ad.content = ad.content.replace("/ad/", `${adserverUrl}/ad/`);
74
+ ad.content = ad.content.replace("/impression/", `${adserverUrl}/impression/`);
75
75
  }
76
76
  return { ...ad, url: `${adserverUrl}${ad.url}` };
77
77
  };
@@ -79,7 +79,8 @@ var mergeAds = ({
79
79
  initAds,
80
80
  preloadAds,
81
81
  streamAds,
82
- viewedAds
82
+ viewedAds,
83
+ clickedAds
83
84
  }) => {
84
85
  const ads = [...initAds];
85
86
  if (Array.isArray(preloadAds)) {
@@ -106,6 +107,7 @@ var mergeAds = ({
106
107
  }
107
108
  for (const ad of ads) {
108
109
  ad.viewed = viewedAds.includes(ad?.id || "placeholder");
110
+ ad.clicked = clickedAds.includes(ad?.id || "placeholder");
109
111
  }
110
112
  return ads;
111
113
  };
@@ -171,15 +173,91 @@ var parseMessageText = (text) => {
171
173
  return parts;
172
174
  };
173
175
 
174
- // src/hooks/useInitializeAds.tsx
175
- import log from "loglevel";
176
+ // src/log.ts
177
+ var Logger = class {
178
+ localLevel = "log";
179
+ remoteLevel = "error";
180
+ remoteConfig = null;
181
+ levels = {
182
+ debug: 0,
183
+ info: 1,
184
+ log: 2,
185
+ warn: 3,
186
+ error: 4,
187
+ silent: 5
188
+ };
189
+ setLocalLevel(level) {
190
+ this.localLevel = level;
191
+ }
192
+ setRemoteLevel(level) {
193
+ this.remoteLevel = level;
194
+ }
195
+ configureRemote(url, params) {
196
+ this.remoteConfig = { url, params };
197
+ }
198
+ shouldLog(level, targetLevel) {
199
+ if (targetLevel === "silent") {
200
+ return false;
201
+ }
202
+ return this.levels[level] >= this.levels[targetLevel];
203
+ }
204
+ logToConsole(level, ...args) {
205
+ if (this.shouldLog(level, this.localLevel)) {
206
+ if (level === "silent") {
207
+ return;
208
+ }
209
+ console[level](...args);
210
+ }
211
+ }
212
+ logToRemote(level, ...args) {
213
+ if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
214
+ fetch(
215
+ `${this.remoteConfig.url}/log`,
216
+ {
217
+ method: "POST",
218
+ body: JSON.stringify({
219
+ ...this.remoteConfig.params,
220
+ level,
221
+ message: args,
222
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
223
+ })
224
+ }
225
+ ).catch((e) => {
226
+ });
227
+ }
228
+ }
229
+ debug(...args) {
230
+ this.logToConsole("debug", ...args);
231
+ this.logToRemote("debug", ...args);
232
+ }
233
+ info(...args) {
234
+ this.logToConsole("info", ...args);
235
+ this.logToRemote("info", ...args);
236
+ }
237
+ log(...args) {
238
+ this.logToConsole("log", ...args);
239
+ this.logToRemote("log", ...args);
240
+ }
241
+ warn(...args) {
242
+ this.logToConsole("warn", ...args);
243
+ this.logToRemote("warn", ...args);
244
+ }
245
+ error(...args) {
246
+ this.logToConsole("error", ...args);
247
+ this.logToRemote("error", ...args);
248
+ }
249
+ };
250
+ var log = new Logger();
251
+ var log_default = log;
176
252
 
177
253
  // package.json
178
- var version = "0.0.5-rc.8";
254
+ var version = "0.0.7-rc.0";
179
255
 
180
256
  // src/hooks/useInitializeAds.tsx
257
+ var SINGLE_INIT_TIMEOUT_BUDGET_MS = 3e3;
258
+ var SINGLE_INIT_RETRIES = 3;
181
259
  async function initialize(adServerUrl, publisherToken, userId, conversationId, legacyVisitorId, character) {
182
- log.log("[BRAIN] init ads started");
260
+ log_default.log("[BRAIN] init ads started");
183
261
  const response = await fetchRetry(
184
262
  `${adServerUrl}/init`,
185
263
  {
@@ -195,8 +273,8 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
195
273
  character
196
274
  })
197
275
  },
198
- 3,
199
- 1e3
276
+ SINGLE_INIT_TIMEOUT_BUDGET_MS,
277
+ SINGLE_INIT_RETRIES
200
278
  );
201
279
  const {
202
280
  sessionId,
@@ -206,12 +284,13 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
206
284
  streamAdServer,
207
285
  onlyStream,
208
286
  defaultReactNativeStyles,
209
- overridingReactNativeStyles
287
+ overridingReactNativeStyles,
288
+ remoteLogLevel
210
289
  } = await response.json();
211
290
  const fixedAds = ads.map((ad) => {
212
291
  return fixUrl(adServerUrl, ad);
213
292
  });
214
- log.log("[BRAIN] init ads done");
293
+ log_default.log("[BRAIN] init ads done");
215
294
  return {
216
295
  sessionDisabled,
217
296
  sessionId,
@@ -220,7 +299,8 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
220
299
  streamAdServer,
221
300
  onlyStream,
222
301
  defaultReactNativeStylesResponse: defaultReactNativeStyles,
223
- overridingReactNativeStylesResponse: overridingReactNativeStyles
302
+ overridingReactNativeStylesResponse: overridingReactNativeStyles,
303
+ remoteLogLevel
224
304
  };
225
305
  }
226
306
  function useInitializeAds({
@@ -241,9 +321,10 @@ function useInitializeAds({
241
321
  const [defaultReactNativeStyles, setDefaultReactNativeStyles] = useState();
242
322
  const [overridingReactNativeStyles, setOverridingReactNativeStyles] = useState();
243
323
  useEffect(() => {
324
+ log_default.setRemoteLevel("debug");
244
325
  setSessionId(void 0);
245
326
  if (!isDisabled && userId) {
246
- log.debug("[BRAIN] Initializing ads.");
327
+ log_default.debug("[BRAIN] Initializing ads.");
247
328
  initialize(
248
329
  adServerUrl,
249
330
  publisherToken,
@@ -260,8 +341,10 @@ function useInitializeAds({
260
341
  streamAdServer,
261
342
  onlyStream: onlyStream2,
262
343
  defaultReactNativeStylesResponse,
263
- overridingReactNativeStylesResponse
344
+ overridingReactNativeStylesResponse,
345
+ remoteLogLevel
264
346
  }) => {
347
+ log_default.setRemoteLevel(remoteLogLevel || "silent");
265
348
  if (!sessionDisabled) {
266
349
  setSessionId(sessionId2);
267
350
  setEnabledPlacements(enabledPlacements2);
@@ -271,15 +354,15 @@ function useInitializeAds({
271
354
  setDefaultReactNativeStyles(defaultReactNativeStylesResponse);
272
355
  setOverridingReactNativeStyles(overridingReactNativeStylesResponse);
273
356
  } else {
274
- log.debug("[BRAIN] Session is disabled by server.");
357
+ log_default.debug("[BRAIN] Session is disabled by server.");
275
358
  }
276
359
  }
277
360
  ).catch((e) => {
278
- log.warn("[BRAIN] Error initializing ads", e);
361
+ log_default.warn("[BRAIN] Error initializing ads", e);
279
362
  setError(e.message);
280
363
  });
281
364
  } else {
282
- log.debug("[BRAIN] Ads are disabled.");
365
+ log_default.debug("[BRAIN] Ads are disabled.");
283
366
  }
284
367
  }, [
285
368
  adServerUrl,
@@ -302,7 +385,6 @@ function useInitializeAds({
302
385
 
303
386
  // src/hooks/usePreloadAds.tsx
304
387
  import { useEffect as useEffect2, useState as useState2 } from "react";
305
- import log2 from "loglevel";
306
388
  function usePreloadAds({
307
389
  userId,
308
390
  sessionId,
@@ -319,7 +401,7 @@ function usePreloadAds({
319
401
  useEffect2(() => {
320
402
  if (onlyStream) {
321
403
  setPreloadDone(true);
322
- log2.log("[BRAIN] skipping preload ads");
404
+ log_default.log("[BRAIN] skipping preload ads");
323
405
  return;
324
406
  }
325
407
  async function preload() {
@@ -327,7 +409,7 @@ function usePreloadAds({
327
409
  if (!sessionId) {
328
410
  return;
329
411
  }
330
- log2.log("[BRAIN] preload ads started");
412
+ log_default.log("[BRAIN] preload ads started");
331
413
  try {
332
414
  const response = await fetchRetry(
333
415
  `${adserverUrl}/preload`,
@@ -354,9 +436,9 @@ function usePreloadAds({
354
436
  return [...oldAds, ...newAds];
355
437
  });
356
438
  setPreloadDone(true);
357
- log2.log("[BRAIN] preload ads finished");
439
+ log_default.log("[BRAIN] preload ads finished");
358
440
  } catch (e) {
359
- log2.warn("[BRAIN] Error preloading ads", e);
441
+ log_default.warn("[BRAIN] Error preloading ads", e);
360
442
  }
361
443
  }
362
444
  preload();
@@ -589,7 +671,6 @@ async function* readDataStream(reader, {
589
671
  import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
590
672
  import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
591
673
  import { Platform } from "react-native";
592
- import log3 from "loglevel";
593
674
  var patchFetch = fetch;
594
675
  if (Platform.OS !== "web") {
595
676
  polyfillEncoding();
@@ -598,9 +679,9 @@ if (Platform.OS !== "web") {
598
679
  }
599
680
  ErrorUtils.setGlobalHandler((error, isFatal) => {
600
681
  if (!isFatal) {
601
- log3.warn(error);
682
+ log_default.warn(error);
602
683
  } else {
603
- log3.error(error);
684
+ log_default.error(error);
604
685
  }
605
686
  });
606
687
  function useStreamAds({
@@ -661,7 +742,7 @@ function useStreamAds({
661
742
  );
662
743
  let data = "";
663
744
  let adData = {};
664
- log3.log(`[BRAIN] streaming ${code} ad started`);
745
+ log_default.log(`[BRAIN] streaming ${code} ad started`);
665
746
  const reader = response.body.getReader();
666
747
  for await (const { type, value } of readDataStream(reader)) {
667
748
  switch (type) {
@@ -689,8 +770,8 @@ function useStreamAds({
689
770
  isLoading: false,
690
771
  isStreaming: true,
691
772
  content: content.replace(
692
- new RegExp(`/ad/${adData.product.id}/redirect`, "g"),
693
- `/ad/${adData.id}/redirect`
773
+ new RegExp(`/impression/${adData.product.id}/redirect`, "g"),
774
+ `/impression/${adData.id}/redirect`
694
775
  )
695
776
  });
696
777
  return newAd;
@@ -704,9 +785,9 @@ function useStreamAds({
704
785
  (ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isStreaming: false } : ad
705
786
  )
706
787
  );
707
- log3.log(`[BRAIN] streaming ${code} ad done`);
788
+ log_default.log(`[BRAIN] streaming ${code} ad done`);
708
789
  } catch (e) {
709
- log3.warn("[BRAIN] Error streaming ad", e);
790
+ log_default.warn("[BRAIN] Error streaming ad", e);
710
791
  setAds((oldAds) => [...oldAds, { isError: true, code }]);
711
792
  }
712
793
  };
@@ -722,12 +803,8 @@ function useStreamAds({
722
803
  return { ads };
723
804
  }
724
805
 
725
- // src/context/AdsProvider.tsx
726
- import log5 from "loglevel";
727
-
728
806
  // src/components/ErrorBoundary.tsx
729
807
  import React from "react";
730
- import log4 from "loglevel";
731
808
  var captureErrorFn = (adServerUrl, error, componentStack, context) => {
732
809
  fetch(`${adServerUrl}/error`, {
733
810
  method: "POST",
@@ -737,7 +814,7 @@ var captureErrorFn = (adServerUrl, error, componentStack, context) => {
737
814
  context
738
815
  })
739
816
  }).catch((e) => {
740
- log4.warn("Error reporting client error", e);
817
+ log_default.warn("Error reporting client error", e);
741
818
  });
742
819
  };
743
820
  var ErrorBoundary = class extends React.Component {
@@ -787,8 +864,9 @@ var AdsProviderWithoutBoundary = ({
787
864
  ...props
788
865
  }) => {
789
866
  const [viewedAds, setViewedAds] = useState4([]);
867
+ const [clickedAds, setClickedAds] = useState4([]);
790
868
  const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
791
- log5.setLevel(logLevel || "ERROR");
869
+ log_default.setLocalLevel(logLevel || "silent");
792
870
  const {
793
871
  ads: initAds,
794
872
  sessionId,
@@ -831,9 +909,9 @@ var AdsProviderWithoutBoundary = ({
831
909
  enabledPlacements,
832
910
  publisherToken
833
911
  });
834
- const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds });
912
+ const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds, clickedAds });
835
913
  const markAdAsViewed = (ad) => {
836
- log5.debug("[Brain] Calling onAdView");
914
+ log_default.debug("[Brain] Calling onAdView");
837
915
  onAdView && onAdView({
838
916
  id: ad.id,
839
917
  code: ad.code,
@@ -845,13 +923,20 @@ var AdsProviderWithoutBoundary = ({
845
923
  setViewedAds((old) => [...old, ad.id]);
846
924
  };
847
925
  const onAdClickInternal = (ad) => {
848
- log5.debug("[Brain] Calling onAdClick");
926
+ log_default.debug("[Brain] Calling onAdClick");
849
927
  onAdClick && onAdClick({
850
928
  id: ad.id,
851
929
  code: ad.code,
852
930
  messageId: ad.messageId,
853
931
  content: ad.content
854
932
  });
933
+ if (!ad.id) {
934
+ return;
935
+ }
936
+ if (clickedAds.includes(ad.id)) {
937
+ return;
938
+ }
939
+ setClickedAds((old) => [...old, ad.id]);
855
940
  };
856
941
  return /* @__PURE__ */ jsx(
857
942
  AdsContext.Provider,
@@ -866,6 +951,7 @@ var AdsProviderWithoutBoundary = ({
866
951
  ads,
867
952
  sessionId,
868
953
  isInitialised,
954
+ conversationId,
869
955
  isDisabled,
870
956
  onAdClickInternal,
871
957
  enabledPlacements,
@@ -931,7 +1017,6 @@ var AdsProvider = ({
931
1017
 
932
1018
  // src/hooks/useAdViewed.tsx
933
1019
  import { useContext } from "react";
934
- import log6 from "loglevel";
935
1020
  function useAdViewed(ad) {
936
1021
  const context = useContext(AdsContext);
937
1022
  const [stillMounted, setStillMounted] = useState5(false);
@@ -945,40 +1030,36 @@ function useAdViewed(ad) {
945
1030
  serverUrl = context.streamAdServerUrl;
946
1031
  }
947
1032
  try {
948
- const response = await fetch(`${serverUrl}/ad/${ad.id}/view`, {
1033
+ const response = await fetch(`${serverUrl}/impression/${ad.id}/view`, {
949
1034
  method: "POST"
950
1035
  });
951
1036
  if (!response.ok) {
952
1037
  throw new Error("Error sending view request");
953
1038
  }
954
- log6.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
1039
+ log_default.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
955
1040
  } catch (e) {
956
- log6.warn("[BRAIN] Error sending view request", e);
1041
+ log_default.warn("[BRAIN] Error sending view request", e);
957
1042
  }
958
1043
  };
959
1044
  useEffect5(() => {
960
1045
  if (!ad || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed || stillMounted) {
961
1046
  return;
962
1047
  }
963
- log6.log("[BRAIN] setting timeout", ad.id, ad.code);
1048
+ log_default.log("[BRAIN] setting timeout", ad.id, ad.code);
964
1049
  setTimeout(() => {
965
- log6.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
1050
+ log_default.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
966
1051
  setStillMounted(true);
967
1052
  }, 1e3);
968
1053
  }, [ad]);
969
1054
  useEffect5(() => {
970
1055
  if (stillMounted) {
971
- log6.log("[BRAIN] sending request");
1056
+ log_default.log("[BRAIN] sending request");
972
1057
  sendRequest();
973
1058
  }
974
1059
  }, [stillMounted]);
975
1060
  }
976
1061
 
977
- // src/formats/InlineAd.tsx
978
- import log9 from "loglevel";
979
-
980
1062
  // src/components/MarkdownText.tsx
981
- import log7 from "loglevel";
982
1063
  import { useContext as useContext2 } from "react";
983
1064
  import {
984
1065
  Linking,
@@ -995,7 +1076,7 @@ function MarkdownText({
995
1076
  const linkClickHandler = (href) => {
996
1077
  onLinkClick();
997
1078
  Linking.openURL(href).catch(
998
- (err) => log7.warn("Failed to open URL:", err)
1079
+ (err) => log_default.warn("Failed to open URL:", err)
999
1080
  );
1000
1081
  return false;
1001
1082
  };
@@ -1022,7 +1103,6 @@ function MarkdownText({
1022
1103
 
1023
1104
  // src/components/VideoPlayer.tsx
1024
1105
  import { ResizeMode, Video } from "expo-av";
1025
- import log8 from "loglevel";
1026
1106
  import { useContext as useContext4, useEffect as useEffect7, useRef, useState as useState7 } from "react";
1027
1107
  import { Linking as Linking2, Text as Text2, TouchableOpacity, View as View2 } from "react-native";
1028
1108
 
@@ -1117,7 +1197,7 @@ var VideoPlayer = ({
1117
1197
  };
1118
1198
  const handleLinkClick = (url) => {
1119
1199
  if (url) {
1120
- Linking2.openURL(url).catch((err) => log8.warn("Failed to open URL:", err));
1200
+ Linking2.openURL(url).catch((err) => log_default.warn("Failed to open URL:", err));
1121
1201
  onLinkClick();
1122
1202
  }
1123
1203
  };
@@ -1219,7 +1299,7 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1219
1299
  if (ad.isLoading || ad.content?.trim().toLowerCase().includes("none"))
1220
1300
  return null;
1221
1301
  const onProgress = (progress) => {
1222
- log9.log(`Progress: ${progress}`);
1302
+ log_default.log(`Progress: ${progress}`);
1223
1303
  };
1224
1304
  const handleImageClick = (url) => {
1225
1305
  if (url) {
@@ -1228,11 +1308,15 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1228
1308
  window.open(url, "_blank");
1229
1309
  } else {
1230
1310
  Linking3.openURL(url).catch(
1231
- (err) => log9.warn("Failed to open URL:", err)
1311
+ (err) => log_default.warn("Failed to open URL:", err)
1232
1312
  );
1233
1313
  }
1234
1314
  }
1235
1315
  };
1316
+ let adContent = ad.content;
1317
+ if (ad.clicked && ad.rewardContent) {
1318
+ adContent = ad.rewardContent;
1319
+ }
1236
1320
  const content = /* @__PURE__ */ jsxs2(View3, { style: styles?.container, children: [
1237
1321
  /* @__PURE__ */ jsx5(View3, { style: styles?.adBadgeContainer, children: /* @__PURE__ */ jsx5(Text3, { style: styles?.adBadgeText, children: "Ad" }) }),
1238
1322
  ad.imageUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ jsx5(
@@ -1262,7 +1346,7 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1262
1346
  MarkdownText,
1263
1347
  {
1264
1348
  onLinkClick: () => context?.onAdClickInternal(ad),
1265
- content: ad.content
1349
+ content: adContent
1266
1350
  }
1267
1351
  ) }),
1268
1352
  ad.imageUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ jsx5(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextso/sdk-react-native",
3
- "version": "0.0.5",
3
+ "version": "0.0.7-rc.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -10,8 +10,8 @@
10
10
  "dev": "npm-run-all dev:js",
11
11
  "build:js": "tsup",
12
12
  "build": "npm run build:js && cross-env NODE_ENV=development npm run test:run",
13
- "test": "",
14
- "test:run": "",
13
+ "test": "vitest --run",
14
+ "test:run": "vitest --run",
15
15
  "_test": "vitest",
16
16
  "_test:run": "vitest --run",
17
17
  "_test:watch": "vitest --watch",