@kontextso/sdk-react-native 0.0.4
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 +147 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +1229 -0
- package/dist/index.mjs +1205 -0
- package/package.json +57 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1205 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/formats/InlineAd.tsx
|
|
9
|
+
import { useContext as useContext6, useEffect as useEffect8, useState as useState8 } from "react";
|
|
10
|
+
import {
|
|
11
|
+
Image,
|
|
12
|
+
Linking as Linking3,
|
|
13
|
+
Platform as Platform2,
|
|
14
|
+
Text as Text3,
|
|
15
|
+
TouchableOpacity as TouchableOpacity2,
|
|
16
|
+
View as View3
|
|
17
|
+
} from "react-native";
|
|
18
|
+
|
|
19
|
+
// src/hooks/useAdViewed.tsx
|
|
20
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
21
|
+
|
|
22
|
+
// src/context/AdsProvider.tsx
|
|
23
|
+
import React, { useState as useState4 } from "react";
|
|
24
|
+
|
|
25
|
+
// src/hooks/useInitializeAds.tsx
|
|
26
|
+
import { useEffect, useState } from "react";
|
|
27
|
+
|
|
28
|
+
// src/utils.ts
|
|
29
|
+
var UNRETRIABLE_ERRORS = [403, 429];
|
|
30
|
+
var fetchWithTimeout = async (input, init) => {
|
|
31
|
+
const { timeout = 16e3 } = init || {};
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(input, {
|
|
36
|
+
...init,
|
|
37
|
+
signal: controller.signal
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(id);
|
|
40
|
+
return response;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
clearTimeout(id);
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
|
|
47
|
+
let retries = 0;
|
|
48
|
+
let response;
|
|
49
|
+
let unretriableError;
|
|
50
|
+
while (retries < maxRetries) {
|
|
51
|
+
try {
|
|
52
|
+
response = await fetchWithTimeout(input, init);
|
|
53
|
+
if (UNRETRIABLE_ERRORS.includes(response.status)) {
|
|
54
|
+
unretriableError = new Error(response.statusText);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (response.ok) {
|
|
58
|
+
return response;
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Failed with status ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
retries++;
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, retryPeriod));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (unretriableError) {
|
|
68
|
+
throw unretriableError;
|
|
69
|
+
}
|
|
70
|
+
throw new Error("Failed to fetch after multiple retries");
|
|
71
|
+
};
|
|
72
|
+
var fixUrl = (adserverUrl, ad) => {
|
|
73
|
+
if (ad.content) {
|
|
74
|
+
ad.content = ad.content.replace("/ad/", `${adserverUrl}/ad/`);
|
|
75
|
+
}
|
|
76
|
+
return { ...ad, url: `${adserverUrl}${ad.url}` };
|
|
77
|
+
};
|
|
78
|
+
var mergeAds = ({
|
|
79
|
+
initAds,
|
|
80
|
+
preloadAds,
|
|
81
|
+
streamAds,
|
|
82
|
+
viewedAds
|
|
83
|
+
}) => {
|
|
84
|
+
const ads = [...initAds];
|
|
85
|
+
if (Array.isArray(preloadAds)) {
|
|
86
|
+
for (const ad of preloadAds) {
|
|
87
|
+
const index = ads.findIndex((a) => a.code === ad.code);
|
|
88
|
+
if (index !== -1) {
|
|
89
|
+
ads[index] = ad;
|
|
90
|
+
} else {
|
|
91
|
+
ads.push(ad);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(streamAds)) {
|
|
96
|
+
for (const ad of streamAds) {
|
|
97
|
+
const index = ads.findIndex(
|
|
98
|
+
(a) => a.code === ad.code && a.messageId === ad.messageId
|
|
99
|
+
);
|
|
100
|
+
if (index !== -1) {
|
|
101
|
+
ads[index] = ad;
|
|
102
|
+
} else {
|
|
103
|
+
ads.push(ad);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const ad of ads) {
|
|
108
|
+
ad.viewed = viewedAds.includes(ad?.id || "placeholder");
|
|
109
|
+
}
|
|
110
|
+
return ads;
|
|
111
|
+
};
|
|
112
|
+
function mergeStyles(propStyles, defaultStyles, overridingStyles) {
|
|
113
|
+
function deepMerge(target, source) {
|
|
114
|
+
if (typeof target !== "object" || typeof source !== "object") {
|
|
115
|
+
return source;
|
|
116
|
+
}
|
|
117
|
+
const result = { ...target };
|
|
118
|
+
for (const key in source) {
|
|
119
|
+
if (source.hasOwnProperty(key)) {
|
|
120
|
+
if (typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
121
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
122
|
+
} else {
|
|
123
|
+
result[key] = source[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
let mergedStyles = deepMerge({ ...defaultStyles }, propStyles || {});
|
|
130
|
+
mergedStyles = deepMerge(mergedStyles, overridingStyles || {});
|
|
131
|
+
return mergedStyles;
|
|
132
|
+
}
|
|
133
|
+
var parseMessageText = (text) => {
|
|
134
|
+
const parts = [];
|
|
135
|
+
let start = 0;
|
|
136
|
+
const regex = /(\*(.*?)\*)|\[([^\]]+)\]\(([^)]+)\)/g;
|
|
137
|
+
let match;
|
|
138
|
+
while ((match = regex.exec(text)) !== null) {
|
|
139
|
+
if (match.index > start) {
|
|
140
|
+
parts.push({ text: text.substring(start, match.index), textType: "normal" });
|
|
141
|
+
}
|
|
142
|
+
if (match[1]) {
|
|
143
|
+
const themedText = match[2];
|
|
144
|
+
const innerMatches = themedText.match(/\[([^\]]+)\]\(([^)]+)\)/g);
|
|
145
|
+
if (innerMatches) {
|
|
146
|
+
let innerStart = 0;
|
|
147
|
+
innerMatches.forEach((linkMatch) => {
|
|
148
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
149
|
+
const innerLink = linkMatch.match(linkRegex);
|
|
150
|
+
const linkIndex = themedText.indexOf(linkMatch, innerStart);
|
|
151
|
+
if (linkIndex > innerStart) {
|
|
152
|
+
parts.push({ text: themedText.substring(innerStart, linkIndex), textType: "themed" });
|
|
153
|
+
}
|
|
154
|
+
parts.push({ text: innerLink[1], textType: "link", url: innerLink[2] });
|
|
155
|
+
innerStart = linkIndex + linkMatch.length;
|
|
156
|
+
});
|
|
157
|
+
if (innerStart < themedText.length) {
|
|
158
|
+
parts.push({ text: themedText.substring(innerStart), textType: "themed" });
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
parts.push({ text: themedText, textType: "themed" });
|
|
162
|
+
}
|
|
163
|
+
} else if (match[3] && match[4]) {
|
|
164
|
+
parts.push({ text: match[3], textType: "link", url: match[4] });
|
|
165
|
+
}
|
|
166
|
+
start = match.index + match[0].length;
|
|
167
|
+
}
|
|
168
|
+
if (start < text.length) {
|
|
169
|
+
parts.push({ text: text.substring(start), textType: "normal" });
|
|
170
|
+
}
|
|
171
|
+
return parts;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/hooks/useInitializeAds.tsx
|
|
175
|
+
import log from "loglevel";
|
|
176
|
+
|
|
177
|
+
// package.json
|
|
178
|
+
var version = "0.0.4";
|
|
179
|
+
|
|
180
|
+
// src/hooks/useInitializeAds.tsx
|
|
181
|
+
async function initialize(adServerUrl, publisherToken, userId, conversationId, legacyVisitorId, character) {
|
|
182
|
+
log.log("[BRAIN] init ads started");
|
|
183
|
+
const response = await fetchRetry(
|
|
184
|
+
`${adServerUrl}/init`,
|
|
185
|
+
{
|
|
186
|
+
timeout: 1e4,
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
publisherToken,
|
|
190
|
+
userId,
|
|
191
|
+
visitorId: userId,
|
|
192
|
+
conversationId,
|
|
193
|
+
sdkVersion: `native-${version}`,
|
|
194
|
+
legacyVisitorId,
|
|
195
|
+
character
|
|
196
|
+
})
|
|
197
|
+
},
|
|
198
|
+
3,
|
|
199
|
+
1e3
|
|
200
|
+
);
|
|
201
|
+
const {
|
|
202
|
+
sessionId,
|
|
203
|
+
enabledPlacements,
|
|
204
|
+
ads,
|
|
205
|
+
sessionDisabled,
|
|
206
|
+
streamAdServer,
|
|
207
|
+
onlyStream,
|
|
208
|
+
defaultReactNativeStyles,
|
|
209
|
+
overridingReactNativeStyles
|
|
210
|
+
} = await response.json();
|
|
211
|
+
const fixedAds = ads.map((ad) => {
|
|
212
|
+
return fixUrl(adServerUrl, ad);
|
|
213
|
+
});
|
|
214
|
+
log.log("[BRAIN] init ads done");
|
|
215
|
+
return {
|
|
216
|
+
sessionDisabled,
|
|
217
|
+
sessionId,
|
|
218
|
+
enabledPlacements,
|
|
219
|
+
ads: fixedAds,
|
|
220
|
+
streamAdServer,
|
|
221
|
+
onlyStream,
|
|
222
|
+
defaultReactNativeStylesResponse: defaultReactNativeStyles,
|
|
223
|
+
overridingReactNativeStylesResponse: overridingReactNativeStyles
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function useInitializeAds({
|
|
227
|
+
adServerUrl,
|
|
228
|
+
publisherToken,
|
|
229
|
+
userId,
|
|
230
|
+
conversationId,
|
|
231
|
+
isDisabled,
|
|
232
|
+
character,
|
|
233
|
+
legacyVisitorId
|
|
234
|
+
}) {
|
|
235
|
+
const [enabledPlacements, setEnabledPlacements] = useState([]);
|
|
236
|
+
const [ads, setAds] = useState([]);
|
|
237
|
+
const [error, setError] = useState();
|
|
238
|
+
const [streamAdServerUrl, setStreamAdServerUrl] = useState();
|
|
239
|
+
const [onlyStream, setOnlyStream] = useState(false);
|
|
240
|
+
const [sessionId, setSessionId] = useState();
|
|
241
|
+
const [defaultReactNativeStyles, setDefaultReactNativeStyles] = useState();
|
|
242
|
+
const [overridingReactNativeStyles, setOverridingReactNativeStyles] = useState();
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
setSessionId(void 0);
|
|
245
|
+
if (!isDisabled && userId) {
|
|
246
|
+
log.debug("[BRAIN] Initializing ads.");
|
|
247
|
+
initialize(
|
|
248
|
+
adServerUrl,
|
|
249
|
+
publisherToken,
|
|
250
|
+
userId,
|
|
251
|
+
conversationId,
|
|
252
|
+
legacyVisitorId,
|
|
253
|
+
character
|
|
254
|
+
).then(
|
|
255
|
+
({
|
|
256
|
+
sessionId: sessionId2,
|
|
257
|
+
enabledPlacements: enabledPlacements2,
|
|
258
|
+
ads: ads2,
|
|
259
|
+
sessionDisabled,
|
|
260
|
+
streamAdServer,
|
|
261
|
+
onlyStream: onlyStream2,
|
|
262
|
+
defaultReactNativeStylesResponse,
|
|
263
|
+
overridingReactNativeStylesResponse
|
|
264
|
+
}) => {
|
|
265
|
+
if (!sessionDisabled) {
|
|
266
|
+
setSessionId(sessionId2);
|
|
267
|
+
setEnabledPlacements(enabledPlacements2);
|
|
268
|
+
setAds(ads2);
|
|
269
|
+
setStreamAdServerUrl(streamAdServer);
|
|
270
|
+
setOnlyStream(onlyStream2);
|
|
271
|
+
setDefaultReactNativeStyles(defaultReactNativeStylesResponse);
|
|
272
|
+
setOverridingReactNativeStyles(overridingReactNativeStylesResponse);
|
|
273
|
+
} else {
|
|
274
|
+
log.debug("[BRAIN] Session is disabled by server.");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
).catch((e) => {
|
|
278
|
+
log.error("[BRAIN] Error initializing ads", e);
|
|
279
|
+
setError(e.message);
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
log.debug("[BRAIN] Ads are disabled.");
|
|
283
|
+
}
|
|
284
|
+
}, [
|
|
285
|
+
adServerUrl,
|
|
286
|
+
publisherToken,
|
|
287
|
+
userId,
|
|
288
|
+
conversationId,
|
|
289
|
+
isDisabled
|
|
290
|
+
]);
|
|
291
|
+
return {
|
|
292
|
+
ads,
|
|
293
|
+
sessionId,
|
|
294
|
+
enabledPlacements,
|
|
295
|
+
error,
|
|
296
|
+
streamAdServerUrl,
|
|
297
|
+
onlyStream,
|
|
298
|
+
defaultReactNativeStyles,
|
|
299
|
+
overridingReactNativeStyles
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/hooks/usePreloadAds.tsx
|
|
304
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
305
|
+
import log2 from "loglevel";
|
|
306
|
+
function usePreloadAds({
|
|
307
|
+
userId,
|
|
308
|
+
sessionId,
|
|
309
|
+
conversationId,
|
|
310
|
+
adserverUrl,
|
|
311
|
+
messages,
|
|
312
|
+
publisherToken,
|
|
313
|
+
character,
|
|
314
|
+
onlyStream
|
|
315
|
+
}) {
|
|
316
|
+
const [preloadDone, setPreloadDone] = useState2(false);
|
|
317
|
+
const [ads, setAds] = useState2([]);
|
|
318
|
+
const userMessagesContent = messages.filter((m) => m.role === "user").map((m) => m.content).join(" ");
|
|
319
|
+
useEffect2(() => {
|
|
320
|
+
if (onlyStream) {
|
|
321
|
+
setPreloadDone(true);
|
|
322
|
+
log2.log("[BRAIN] skipping preload ads");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
async function preload() {
|
|
326
|
+
setPreloadDone(false);
|
|
327
|
+
if (!sessionId) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
log2.log("[BRAIN] preload ads started");
|
|
331
|
+
try {
|
|
332
|
+
const response = await fetchRetry(
|
|
333
|
+
`${adserverUrl}/preload`,
|
|
334
|
+
{
|
|
335
|
+
timeout: 1e4,
|
|
336
|
+
method: "POST",
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
messages,
|
|
339
|
+
sessionId,
|
|
340
|
+
userId,
|
|
341
|
+
visitorId: userId,
|
|
342
|
+
publisherToken,
|
|
343
|
+
conversationId,
|
|
344
|
+
character,
|
|
345
|
+
sdkVersion: "native"
|
|
346
|
+
})
|
|
347
|
+
},
|
|
348
|
+
3,
|
|
349
|
+
1e3
|
|
350
|
+
);
|
|
351
|
+
const { ads: ads2 } = await response.json();
|
|
352
|
+
setAds((oldAds) => {
|
|
353
|
+
const newAds = ads2.map((ad) => fixUrl(adserverUrl, ad));
|
|
354
|
+
return [...oldAds, ...newAds];
|
|
355
|
+
});
|
|
356
|
+
setPreloadDone(true);
|
|
357
|
+
log2.log("[BRAIN] preload ads finished");
|
|
358
|
+
} catch (e) {
|
|
359
|
+
log2.error("[BRAIN] Error preloading ads", e);
|
|
360
|
+
setPreloadDone(true);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
preload();
|
|
364
|
+
}, [sessionId, userMessagesContent, conversationId]);
|
|
365
|
+
return { preloadDone, ads };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/hooks/useStreamAds.tsx
|
|
369
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
370
|
+
|
|
371
|
+
// src/hooks/data-stream.ts
|
|
372
|
+
var textStreamPart = {
|
|
373
|
+
code: "0",
|
|
374
|
+
name: "text",
|
|
375
|
+
parse: (value) => {
|
|
376
|
+
if (typeof value !== "string") {
|
|
377
|
+
throw new Error('"text" parts expect a string value.');
|
|
378
|
+
}
|
|
379
|
+
return { type: "text", value };
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var functionCallStreamPart = {
|
|
383
|
+
code: "1",
|
|
384
|
+
name: "function_call",
|
|
385
|
+
parse: (value) => {
|
|
386
|
+
if (value == null || typeof value !== "object" || !("function_call" in value) || typeof value.function_call !== "object" || value.function_call == null || !("name" in value.function_call) || !("arguments" in value.function_call) || typeof value.function_call.name !== "string" || typeof value.function_call.arguments !== "string") {
|
|
387
|
+
throw new Error(
|
|
388
|
+
'"function_call" parts expect an object with a "function_call" property.'
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
type: "function_call",
|
|
393
|
+
value
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
var dataStreamPart = {
|
|
398
|
+
code: "2",
|
|
399
|
+
name: "data",
|
|
400
|
+
parse: (value) => {
|
|
401
|
+
if (!Array.isArray(value)) {
|
|
402
|
+
throw new Error('"data" parts expect an array value.');
|
|
403
|
+
}
|
|
404
|
+
return { type: "data", value };
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
var errorStreamPart = {
|
|
408
|
+
code: "3",
|
|
409
|
+
name: "error",
|
|
410
|
+
parse: (value) => {
|
|
411
|
+
if (typeof value !== "string") {
|
|
412
|
+
throw new Error('"error" parts expect a string value.');
|
|
413
|
+
}
|
|
414
|
+
return { type: "error", value };
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var assistantMessageStreamPart = {
|
|
418
|
+
code: "4",
|
|
419
|
+
name: "assistant_message",
|
|
420
|
+
parse: (value) => {
|
|
421
|
+
if (value == null || typeof value !== "object" || !("id" in value) || !("role" in value) || !("content" in value) || typeof value.id !== "string" || typeof value.role !== "string" || value.role !== "assistant" || !Array.isArray(value.content) || !value.content.every(
|
|
422
|
+
(item) => item != null && typeof item === "object" && "type" in item && item.type === "text" && "text" in item && item.text != null && typeof item.text === "object" && "value" in item.text && typeof item.text.value === "string"
|
|
423
|
+
)) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
'"assistant_message" parts expect an object with an "id", "role", and "content" property.'
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
type: "assistant_message",
|
|
430
|
+
value
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
var assistantControlDataStreamPart = {
|
|
435
|
+
code: "5",
|
|
436
|
+
name: "assistant_control_data",
|
|
437
|
+
parse: (value) => {
|
|
438
|
+
if (value == null || typeof value !== "object" || !("threadId" in value) || !("messageId" in value) || typeof value.threadId !== "string" || typeof value.messageId !== "string") {
|
|
439
|
+
throw new Error(
|
|
440
|
+
'"assistant_control_data" parts expect an object with a "threadId" and "messageId" property.'
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
type: "assistant_control_data",
|
|
445
|
+
value: {
|
|
446
|
+
threadId: value.threadId,
|
|
447
|
+
messageId: value.messageId
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
var dataMessageStreamPart = {
|
|
453
|
+
code: "6",
|
|
454
|
+
name: "data_message",
|
|
455
|
+
parse: (value) => {
|
|
456
|
+
if (value == null || typeof value !== "object" || !("role" in value) || !("data" in value) || typeof value.role !== "string" || value.role !== "data") {
|
|
457
|
+
throw new Error(
|
|
458
|
+
'"data_message" parts expect an object with a "role" and "data" property.'
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
type: "data_message",
|
|
463
|
+
value
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
var toolCallStreamPart = {
|
|
468
|
+
code: "7",
|
|
469
|
+
name: "tool_calls",
|
|
470
|
+
parse: (value) => {
|
|
471
|
+
if (value == null || typeof value !== "object" || !("tool_calls" in value) || typeof value.tool_calls !== "object" || value.tool_calls == null || !Array.isArray(value.tool_calls) || value.tool_calls.some((tc) => {
|
|
472
|
+
tc == null || typeof tc !== "object" || !("id" in tc) || typeof tc.id !== "string" || !("type" in tc) || typeof tc.type !== "string" || !("function" in tc) || tc.function == null || typeof tc.function !== "object" || !("arguments" in tc.function) || typeof tc.function.name !== "string" || typeof tc.function.arguments !== "string";
|
|
473
|
+
})) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
'"tool_calls" parts expect an object with a ToolCallPayload.'
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
type: "tool_calls",
|
|
480
|
+
value
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
var messageAnnotationsStreamPart = {
|
|
485
|
+
code: "8",
|
|
486
|
+
name: "message_annotations",
|
|
487
|
+
parse: (value) => {
|
|
488
|
+
if (!Array.isArray(value)) {
|
|
489
|
+
throw new Error('"message_annotations" parts expect an array value.');
|
|
490
|
+
}
|
|
491
|
+
return { type: "message_annotations", value };
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var streamParts = [
|
|
495
|
+
textStreamPart,
|
|
496
|
+
functionCallStreamPart,
|
|
497
|
+
dataStreamPart,
|
|
498
|
+
errorStreamPart,
|
|
499
|
+
assistantMessageStreamPart,
|
|
500
|
+
assistantControlDataStreamPart,
|
|
501
|
+
dataMessageStreamPart,
|
|
502
|
+
toolCallStreamPart,
|
|
503
|
+
messageAnnotationsStreamPart
|
|
504
|
+
];
|
|
505
|
+
var streamPartsByCode = {
|
|
506
|
+
[textStreamPart.code]: textStreamPart,
|
|
507
|
+
[functionCallStreamPart.code]: functionCallStreamPart,
|
|
508
|
+
[dataStreamPart.code]: dataStreamPart,
|
|
509
|
+
[errorStreamPart.code]: errorStreamPart,
|
|
510
|
+
[assistantMessageStreamPart.code]: assistantMessageStreamPart,
|
|
511
|
+
[assistantControlDataStreamPart.code]: assistantControlDataStreamPart,
|
|
512
|
+
[dataMessageStreamPart.code]: dataMessageStreamPart,
|
|
513
|
+
[toolCallStreamPart.code]: toolCallStreamPart,
|
|
514
|
+
[messageAnnotationsStreamPart.code]: messageAnnotationsStreamPart
|
|
515
|
+
};
|
|
516
|
+
var StreamStringPrefixes = {
|
|
517
|
+
[textStreamPart.name]: textStreamPart.code,
|
|
518
|
+
[functionCallStreamPart.name]: functionCallStreamPart.code,
|
|
519
|
+
[dataStreamPart.name]: dataStreamPart.code,
|
|
520
|
+
[errorStreamPart.name]: errorStreamPart.code,
|
|
521
|
+
[assistantMessageStreamPart.name]: assistantMessageStreamPart.code,
|
|
522
|
+
[assistantControlDataStreamPart.name]: assistantControlDataStreamPart.code,
|
|
523
|
+
[dataMessageStreamPart.name]: dataMessageStreamPart.code,
|
|
524
|
+
[toolCallStreamPart.name]: toolCallStreamPart.code,
|
|
525
|
+
[messageAnnotationsStreamPart.name]: messageAnnotationsStreamPart.code
|
|
526
|
+
};
|
|
527
|
+
var validCodes = streamParts.map((part) => part.code);
|
|
528
|
+
var parseStreamPart = (line) => {
|
|
529
|
+
const firstSeparatorIndex = line.indexOf(":");
|
|
530
|
+
if (firstSeparatorIndex === -1) {
|
|
531
|
+
throw new Error("Failed to parse stream string. No separator found.");
|
|
532
|
+
}
|
|
533
|
+
const prefix = line.slice(0, firstSeparatorIndex);
|
|
534
|
+
if (!validCodes.includes(prefix)) {
|
|
535
|
+
throw new Error(`Failed to parse stream string. Invalid code ${prefix}.`);
|
|
536
|
+
}
|
|
537
|
+
const code = prefix;
|
|
538
|
+
const textValue = line.slice(firstSeparatorIndex + 1);
|
|
539
|
+
const any = JSON.parse(textValue);
|
|
540
|
+
return streamPartsByCode[code].parse(any);
|
|
541
|
+
};
|
|
542
|
+
var NEWLINE = "\n".charCodeAt(0);
|
|
543
|
+
function concatChunks(chunks, totalLength) {
|
|
544
|
+
const concatenatedChunks = new Uint8Array(totalLength);
|
|
545
|
+
let offset = 0;
|
|
546
|
+
for (const chunk of chunks) {
|
|
547
|
+
concatenatedChunks.set(chunk, offset);
|
|
548
|
+
offset += chunk.length;
|
|
549
|
+
}
|
|
550
|
+
chunks.length = 0;
|
|
551
|
+
return concatenatedChunks;
|
|
552
|
+
}
|
|
553
|
+
async function* readDataStream(reader, {
|
|
554
|
+
isAborted
|
|
555
|
+
} = {}) {
|
|
556
|
+
const decoder = new TextDecoder();
|
|
557
|
+
const chunks = [];
|
|
558
|
+
let totalLength = 0;
|
|
559
|
+
while (true) {
|
|
560
|
+
const { value } = await reader.read();
|
|
561
|
+
if (value) {
|
|
562
|
+
chunks.push(value);
|
|
563
|
+
totalLength += value.length;
|
|
564
|
+
if (value[value.length - 1] !== NEWLINE) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (chunks.length === 0) {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
const concatenatedChunks = concatChunks(chunks, totalLength);
|
|
572
|
+
totalLength = 0;
|
|
573
|
+
const streamParts2 = decoder.decode(concatenatedChunks, { stream: true }).split("\n").filter((line) => line !== "").map(parseStreamPart);
|
|
574
|
+
let i = 0;
|
|
575
|
+
for (const streamPart of streamParts2) {
|
|
576
|
+
if (!(i % 5)) {
|
|
577
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
578
|
+
}
|
|
579
|
+
i++;
|
|
580
|
+
yield streamPart;
|
|
581
|
+
}
|
|
582
|
+
if (isAborted?.()) {
|
|
583
|
+
reader.cancel();
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/hooks/useStreamAds.tsx
|
|
590
|
+
import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
|
|
591
|
+
import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
|
|
592
|
+
import { Platform } from "react-native";
|
|
593
|
+
import log3 from "loglevel";
|
|
594
|
+
var patchFetch = fetch;
|
|
595
|
+
if (Platform.OS !== "web") {
|
|
596
|
+
polyfillEncoding();
|
|
597
|
+
polyfillReadableStream();
|
|
598
|
+
patchFetch = __require("react-native-fetch-api").fetch;
|
|
599
|
+
}
|
|
600
|
+
ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
601
|
+
log3.error(error);
|
|
602
|
+
});
|
|
603
|
+
function useStreamAds({
|
|
604
|
+
userId,
|
|
605
|
+
sessionId,
|
|
606
|
+
conversationId,
|
|
607
|
+
adserverUrl,
|
|
608
|
+
messages,
|
|
609
|
+
isLoading,
|
|
610
|
+
preloadDone,
|
|
611
|
+
enabledPlacements,
|
|
612
|
+
character,
|
|
613
|
+
publisherToken
|
|
614
|
+
}) {
|
|
615
|
+
const [ads, setAds] = useState3([]);
|
|
616
|
+
const fetchStream = async (messages2, code) => {
|
|
617
|
+
const lastAssistantMessage = [...messages2].reverse().find((m) => m.role === "assistant");
|
|
618
|
+
if (!lastAssistantMessage) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
setAds((oldAds) => {
|
|
622
|
+
const newAds = [...oldAds];
|
|
623
|
+
newAds.push({
|
|
624
|
+
isLoading: true,
|
|
625
|
+
code,
|
|
626
|
+
messageId: lastAssistantMessage.id
|
|
627
|
+
});
|
|
628
|
+
return newAds;
|
|
629
|
+
});
|
|
630
|
+
const body = JSON.stringify({
|
|
631
|
+
userId,
|
|
632
|
+
visitorId: userId,
|
|
633
|
+
sessionId,
|
|
634
|
+
code,
|
|
635
|
+
messages: messages2,
|
|
636
|
+
publisherToken,
|
|
637
|
+
character,
|
|
638
|
+
conversationId,
|
|
639
|
+
sdkVersion: "native"
|
|
640
|
+
});
|
|
641
|
+
try {
|
|
642
|
+
const response = await patchFetch(`${adserverUrl}/stream`, {
|
|
643
|
+
method: "POST",
|
|
644
|
+
body,
|
|
645
|
+
headers: {
|
|
646
|
+
"Content-Type": "application/json"
|
|
647
|
+
},
|
|
648
|
+
reactNative: { textStreaming: true }
|
|
649
|
+
});
|
|
650
|
+
if (!response.ok) {
|
|
651
|
+
throw new Error("Error streaming ad");
|
|
652
|
+
}
|
|
653
|
+
if (!response.body) {
|
|
654
|
+
throw new Error("Response body is not a readable stream");
|
|
655
|
+
}
|
|
656
|
+
setAds(
|
|
657
|
+
(oldAds) => oldAds.map((ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isLoading: false, isStreaming: true } : ad)
|
|
658
|
+
);
|
|
659
|
+
let data = "";
|
|
660
|
+
let adData = {};
|
|
661
|
+
log3.log(`[BRAIN] streaming ${code} ad started`);
|
|
662
|
+
const reader = response.body.getReader();
|
|
663
|
+
for await (const { type, value } of readDataStream(reader)) {
|
|
664
|
+
switch (type) {
|
|
665
|
+
case "text":
|
|
666
|
+
data += value;
|
|
667
|
+
break;
|
|
668
|
+
case "data": {
|
|
669
|
+
let val = value;
|
|
670
|
+
if (Array.isArray(val)) {
|
|
671
|
+
val = val[0];
|
|
672
|
+
}
|
|
673
|
+
adData = { ...adData, ...val };
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
case "error":
|
|
677
|
+
throw new Error(`Error streaming ad ${value}`);
|
|
678
|
+
}
|
|
679
|
+
setAds(
|
|
680
|
+
(oldAds) => oldAds.map((ad) => {
|
|
681
|
+
if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
|
|
682
|
+
const content = adData.content ?? data;
|
|
683
|
+
const newAd = fixUrl(adserverUrl, {
|
|
684
|
+
...ad,
|
|
685
|
+
...adData,
|
|
686
|
+
isLoading: false,
|
|
687
|
+
isStreaming: true,
|
|
688
|
+
content: content.replace(
|
|
689
|
+
new RegExp(`/ad/${adData.product.id}/redirect`, "g"),
|
|
690
|
+
`/ad/${adData.id}/redirect`
|
|
691
|
+
)
|
|
692
|
+
});
|
|
693
|
+
return newAd;
|
|
694
|
+
}
|
|
695
|
+
return ad;
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
setAds(
|
|
700
|
+
(oldAds) => oldAds.map(
|
|
701
|
+
(ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isStreaming: false } : ad
|
|
702
|
+
)
|
|
703
|
+
);
|
|
704
|
+
log3.log(`[BRAIN] streaming ${code} ad done`);
|
|
705
|
+
} catch (e) {
|
|
706
|
+
log3.error("[BRAIN] Error streaming ad", e);
|
|
707
|
+
setAds((oldAds) => [...oldAds, { isError: true, code }]);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
useEffect3(() => {
|
|
711
|
+
if (!isLoading && sessionId && messages.length > 2 && messages[messages.length - 1]?.role === "assistant" && preloadDone) {
|
|
712
|
+
enabledPlacements.forEach((placement) => {
|
|
713
|
+
if (["QUERY_STREAM", "INLINE_AD", "BOX_AD"].includes(placement.format)) {
|
|
714
|
+
fetchStream(messages, String(placement.code));
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}, [isLoading, preloadDone, sessionId, messages[messages.length - 1]?.role]);
|
|
719
|
+
return { ads };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/context/AdsProvider.tsx
|
|
723
|
+
import log4 from "loglevel";
|
|
724
|
+
import { jsx } from "react/jsx-runtime";
|
|
725
|
+
var AdsContext = React.createContext(null);
|
|
726
|
+
AdsContext.displayName = "AdsContext";
|
|
727
|
+
var VISITOR_ID_KEY = "brain-visitor-id";
|
|
728
|
+
var AdsProvider = ({
|
|
729
|
+
children,
|
|
730
|
+
messages,
|
|
731
|
+
publisherToken,
|
|
732
|
+
isLoading,
|
|
733
|
+
adserverUrl,
|
|
734
|
+
isDisabled,
|
|
735
|
+
character,
|
|
736
|
+
conversationId,
|
|
737
|
+
userId,
|
|
738
|
+
logLevel,
|
|
739
|
+
onAdView,
|
|
740
|
+
onAdClick,
|
|
741
|
+
...props
|
|
742
|
+
}) => {
|
|
743
|
+
const [viewedAds, setViewedAds] = useState4([]);
|
|
744
|
+
const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
|
|
745
|
+
log4.setLevel(logLevel || "ERROR");
|
|
746
|
+
const {
|
|
747
|
+
ads: initAds,
|
|
748
|
+
sessionId,
|
|
749
|
+
enabledPlacements,
|
|
750
|
+
error,
|
|
751
|
+
streamAdServerUrl,
|
|
752
|
+
onlyStream,
|
|
753
|
+
defaultReactNativeStyles,
|
|
754
|
+
overridingReactNativeStyles
|
|
755
|
+
} = useInitializeAds({
|
|
756
|
+
adServerUrl: adServerUrlOrDefault,
|
|
757
|
+
publisherToken,
|
|
758
|
+
userId,
|
|
759
|
+
legacyVisitorId: void 0,
|
|
760
|
+
conversationId,
|
|
761
|
+
character,
|
|
762
|
+
isDisabled: !!isDisabled
|
|
763
|
+
});
|
|
764
|
+
const styles = mergeStyles(props.styles, defaultReactNativeStyles, overridingReactNativeStyles);
|
|
765
|
+
const isInitialised = !!isDisabled || !!error || !!sessionId;
|
|
766
|
+
const { preloadDone, ads: preloadAds } = usePreloadAds({
|
|
767
|
+
sessionId,
|
|
768
|
+
adserverUrl: adServerUrlOrDefault,
|
|
769
|
+
messages,
|
|
770
|
+
publisherToken,
|
|
771
|
+
conversationId,
|
|
772
|
+
userId,
|
|
773
|
+
character,
|
|
774
|
+
onlyStream
|
|
775
|
+
});
|
|
776
|
+
const { ads: streamAds } = useStreamAds({
|
|
777
|
+
userId,
|
|
778
|
+
sessionId,
|
|
779
|
+
adserverUrl: onlyStream && streamAdServerUrl ? streamAdServerUrl : adServerUrlOrDefault,
|
|
780
|
+
conversationId,
|
|
781
|
+
messages,
|
|
782
|
+
isLoading,
|
|
783
|
+
character,
|
|
784
|
+
preloadDone,
|
|
785
|
+
enabledPlacements,
|
|
786
|
+
publisherToken
|
|
787
|
+
});
|
|
788
|
+
const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds });
|
|
789
|
+
const markAdAsViewed = (ad) => {
|
|
790
|
+
log4.debug("[Brain] Calling onAdView");
|
|
791
|
+
onAdView && onAdView({
|
|
792
|
+
id: ad.id,
|
|
793
|
+
code: ad.code,
|
|
794
|
+
messageId: ad.messageId,
|
|
795
|
+
content: ad.content
|
|
796
|
+
});
|
|
797
|
+
if (!ad.id) return;
|
|
798
|
+
if (viewedAds.includes(ad.id)) return;
|
|
799
|
+
setViewedAds((old) => [...old, ad.id]);
|
|
800
|
+
};
|
|
801
|
+
const onAdClickInternal = (ad) => {
|
|
802
|
+
log4.debug("[Brain] Calling onAdClick");
|
|
803
|
+
onAdClick && onAdClick({
|
|
804
|
+
id: ad.id,
|
|
805
|
+
code: ad.code,
|
|
806
|
+
messageId: ad.messageId,
|
|
807
|
+
content: ad.content
|
|
808
|
+
});
|
|
809
|
+
};
|
|
810
|
+
return /* @__PURE__ */ jsx(
|
|
811
|
+
AdsContext.Provider,
|
|
812
|
+
{
|
|
813
|
+
value: {
|
|
814
|
+
adserverUrl: adServerUrlOrDefault,
|
|
815
|
+
streamAdServerUrl,
|
|
816
|
+
children,
|
|
817
|
+
messages,
|
|
818
|
+
publisherToken,
|
|
819
|
+
isLoading,
|
|
820
|
+
ads,
|
|
821
|
+
sessionId,
|
|
822
|
+
isInitialised,
|
|
823
|
+
isDisabled,
|
|
824
|
+
onAdClickInternal,
|
|
825
|
+
enabledPlacements,
|
|
826
|
+
userId,
|
|
827
|
+
markAdAsViewed,
|
|
828
|
+
onlyStream,
|
|
829
|
+
styles
|
|
830
|
+
},
|
|
831
|
+
children
|
|
832
|
+
}
|
|
833
|
+
);
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// src/hooks/useAdViewed.tsx
|
|
837
|
+
import { useContext } from "react";
|
|
838
|
+
import log5 from "loglevel";
|
|
839
|
+
function useAdViewed(ad) {
|
|
840
|
+
const context = useContext(AdsContext);
|
|
841
|
+
const [stillMounted, setStillMounted] = useState5(false);
|
|
842
|
+
const sendRequest = async () => {
|
|
843
|
+
if (!context?.adserverUrl || !ad?.id) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
context.markAdAsViewed(ad);
|
|
847
|
+
let serverUrl = context.adserverUrl;
|
|
848
|
+
if (context.onlyStream && context.streamAdServerUrl) {
|
|
849
|
+
serverUrl = context.streamAdServerUrl;
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
const response = await fetch(`${serverUrl}/ad/${ad.id}/view`, {
|
|
853
|
+
method: "POST"
|
|
854
|
+
});
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
throw new Error("Error sending view request");
|
|
857
|
+
}
|
|
858
|
+
log5.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
|
|
859
|
+
} catch (e) {
|
|
860
|
+
log5.error("[BRAIN] Error sending view request", e);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
useEffect5(() => {
|
|
864
|
+
if (!ad || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed || stillMounted) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
log5.log("[BRAIN] setting timeout", ad.id, ad.code);
|
|
868
|
+
setTimeout(() => {
|
|
869
|
+
log5.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
|
|
870
|
+
setStillMounted(true);
|
|
871
|
+
}, 1e3);
|
|
872
|
+
}, [ad]);
|
|
873
|
+
useEffect5(() => {
|
|
874
|
+
if (stillMounted) {
|
|
875
|
+
log5.log("[BRAIN] sending request");
|
|
876
|
+
sendRequest();
|
|
877
|
+
}
|
|
878
|
+
}, [stillMounted]);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/formats/InlineAd.tsx
|
|
882
|
+
import log8 from "loglevel";
|
|
883
|
+
|
|
884
|
+
// src/components/MarkdownText.tsx
|
|
885
|
+
import log6 from "loglevel";
|
|
886
|
+
import { useContext as useContext2 } from "react";
|
|
887
|
+
import {
|
|
888
|
+
Linking,
|
|
889
|
+
Text
|
|
890
|
+
} from "react-native";
|
|
891
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
892
|
+
function MarkdownText({
|
|
893
|
+
content,
|
|
894
|
+
onLinkClick
|
|
895
|
+
}) {
|
|
896
|
+
const context = useContext2(AdsContext);
|
|
897
|
+
const textParts = parseMessageText(content);
|
|
898
|
+
const styles = context?.styles?.markdownText;
|
|
899
|
+
const linkClickHandler = (href) => {
|
|
900
|
+
onLinkClick();
|
|
901
|
+
Linking.openURL(href).catch(
|
|
902
|
+
(err) => log6.error("Failed to open URL:", err)
|
|
903
|
+
);
|
|
904
|
+
return false;
|
|
905
|
+
};
|
|
906
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: textParts.map((t, i) => {
|
|
907
|
+
if (t.textType === "link") {
|
|
908
|
+
return /* @__PURE__ */ jsx2(
|
|
909
|
+
Text,
|
|
910
|
+
{
|
|
911
|
+
style: styles?.link,
|
|
912
|
+
onPress: () => {
|
|
913
|
+
linkClickHandler(t.url);
|
|
914
|
+
},
|
|
915
|
+
children: t.text
|
|
916
|
+
},
|
|
917
|
+
i + "link"
|
|
918
|
+
);
|
|
919
|
+
} else if (t.textType === "themed") {
|
|
920
|
+
return /* @__PURE__ */ jsx2(Text, { style: styles?.em, children: t.text }, i + "em");
|
|
921
|
+
} else {
|
|
922
|
+
return /* @__PURE__ */ jsx2(Text, { style: styles?.normal, children: t.text }, i + "normal");
|
|
923
|
+
}
|
|
924
|
+
}) });
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/components/VideoPlayer.tsx
|
|
928
|
+
import { ResizeMode, Video } from "expo-av";
|
|
929
|
+
import log7 from "loglevel";
|
|
930
|
+
import { useContext as useContext4, useEffect as useEffect7, useRef, useState as useState7 } from "react";
|
|
931
|
+
import { Linking as Linking2, Text as Text2, TouchableOpacity, View as View2 } from "react-native";
|
|
932
|
+
|
|
933
|
+
// src/components/VideoProgressBar.tsx
|
|
934
|
+
import { useState as useState6, useEffect as useEffect6, useContext as useContext3 } from "react";
|
|
935
|
+
import { View } from "react-native";
|
|
936
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
937
|
+
function VideoProgressBar({ videoRef }) {
|
|
938
|
+
const [progress, setProgress] = useState6(0);
|
|
939
|
+
const styles = useContext3(AdsContext)?.styles?.videoPlayer?.videoProgress;
|
|
940
|
+
useEffect6(() => {
|
|
941
|
+
const interval = setInterval(() => {
|
|
942
|
+
if (!videoRef.current) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
videoRef.current.getStatusAsync().then((status) => {
|
|
946
|
+
if (status.isLoaded) {
|
|
947
|
+
const currentTime = status.positionMillis;
|
|
948
|
+
const duration = status.durationMillis;
|
|
949
|
+
const percentage = currentTime / duration * 100;
|
|
950
|
+
setProgress(percentage);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
}, 1e3);
|
|
954
|
+
return () => clearInterval(interval);
|
|
955
|
+
}, [videoRef.current]);
|
|
956
|
+
return /* @__PURE__ */ jsx3(View, { style: styles?.progressContainer, children: /* @__PURE__ */ jsx3(View, { style: [styles?.progressBar, { width: `${progress}%` }] }) });
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/components/VideoPlayer.tsx
|
|
960
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
961
|
+
var VideoPlayer = ({
|
|
962
|
+
src,
|
|
963
|
+
onProgress,
|
|
964
|
+
endText,
|
|
965
|
+
badgeText,
|
|
966
|
+
videoPosterUrl,
|
|
967
|
+
videoOrientation,
|
|
968
|
+
ctaText,
|
|
969
|
+
clickUrl,
|
|
970
|
+
mediaPlacement,
|
|
971
|
+
onLinkClick
|
|
972
|
+
}) => {
|
|
973
|
+
const videoRef = useRef(null);
|
|
974
|
+
const [isPlaying, setIsPlaying] = useState7(false);
|
|
975
|
+
const [isPaused, setIsPaused] = useState7(true);
|
|
976
|
+
const [isEnded, setIsEnded] = useState7(false);
|
|
977
|
+
const [showDownloadBadge, setShowDownloadBadge] = useState7(false);
|
|
978
|
+
const lastCallbackTimeRef = useRef(0);
|
|
979
|
+
const styles = useContext4(AdsContext)?.styles?.videoPlayer;
|
|
980
|
+
useEffect7(() => {
|
|
981
|
+
const interval = setInterval(() => {
|
|
982
|
+
if (videoRef.current) {
|
|
983
|
+
videoRef.current.getStatusAsync().then((status) => {
|
|
984
|
+
if (status.isLoaded) {
|
|
985
|
+
const progress = status.positionMillis / status.durationMillis * 100;
|
|
986
|
+
const now = Date.now();
|
|
987
|
+
if (now - lastCallbackTimeRef.current >= 3e3) {
|
|
988
|
+
lastCallbackTimeRef.current = now;
|
|
989
|
+
onProgress(progress);
|
|
990
|
+
}
|
|
991
|
+
if (status.positionMillis >= 5e3 && !showDownloadBadge) {
|
|
992
|
+
setShowDownloadBadge(true);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
}, 1e3);
|
|
998
|
+
return () => clearInterval(interval);
|
|
999
|
+
}, [showDownloadBadge]);
|
|
1000
|
+
const handlePlayPause = () => {
|
|
1001
|
+
if (videoRef.current) {
|
|
1002
|
+
if (isPlaying) {
|
|
1003
|
+
videoRef.current.pauseAsync();
|
|
1004
|
+
setIsPaused(true);
|
|
1005
|
+
handleLinkClick(clickUrl);
|
|
1006
|
+
} else {
|
|
1007
|
+
videoRef.current.playAsync();
|
|
1008
|
+
setIsPaused(false);
|
|
1009
|
+
setIsEnded(false);
|
|
1010
|
+
}
|
|
1011
|
+
setIsPlaying(!isPlaying);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
const handleBadgeClick = () => {
|
|
1015
|
+
if (isPlaying) {
|
|
1016
|
+
videoRef.current?.pauseAsync();
|
|
1017
|
+
setIsPaused(true);
|
|
1018
|
+
setIsPlaying(false);
|
|
1019
|
+
}
|
|
1020
|
+
handleLinkClick(clickUrl);
|
|
1021
|
+
};
|
|
1022
|
+
const handleLinkClick = (url) => {
|
|
1023
|
+
if (url) {
|
|
1024
|
+
Linking2.openURL(url).catch((err) => log7.error("Failed to open URL:", err));
|
|
1025
|
+
onLinkClick();
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
const handleEnded = () => {
|
|
1029
|
+
setIsPlaying(false);
|
|
1030
|
+
setIsEnded(true);
|
|
1031
|
+
setIsPaused(false);
|
|
1032
|
+
onProgress(100);
|
|
1033
|
+
};
|
|
1034
|
+
return /* @__PURE__ */ jsxs(View2, { style: [videoOrientation === "horizontal" ? styles?.videoOuterContainerHorizontal : styles?.videoOuterContainerVertical, mediaPlacement === "top" && styles?.videoPlacementBottom, mediaPlacement === "bottom" && styles?.videoPlacementBottom], children: [
|
|
1035
|
+
/* @__PURE__ */ jsx4(
|
|
1036
|
+
Video,
|
|
1037
|
+
{
|
|
1038
|
+
ref: videoRef,
|
|
1039
|
+
source: { uri: src },
|
|
1040
|
+
resizeMode: ResizeMode.CONTAIN,
|
|
1041
|
+
shouldPlay: false,
|
|
1042
|
+
onTouchStart: handlePlayPause,
|
|
1043
|
+
style: videoOrientation === "horizontal" ? styles?.videoInnerContainerHorizontal : styles?.videoInnerContainerVertical,
|
|
1044
|
+
videoStyle: videoOrientation === "horizontal" ? styles?.videoElementHorizontal : styles?.videoElementVertical,
|
|
1045
|
+
onPlaybackStatusUpdate: (status) => {
|
|
1046
|
+
if (status.isLoaded && status.didJustFinish && !status.isLooping) {
|
|
1047
|
+
handleEnded();
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
),
|
|
1052
|
+
/* @__PURE__ */ jsx4(VideoProgressBar, { videoRef }),
|
|
1053
|
+
!isEnded && /* @__PURE__ */ jsx4(TouchableOpacity, { style: isPlaying ? styles?.playOverlay : styles?.pauseOverlay, onPress: handlePlayPause, children: !isPlaying && /* @__PURE__ */ jsx4(Text2, { style: styles?.playButton, children: "\u25B6" }) }),
|
|
1054
|
+
(isPaused && !isEnded || showDownloadBadge && !isEnded) && badgeText && /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles?.videoBadge, onPress: handleBadgeClick, children: /* @__PURE__ */ jsx4(Text2, { style: styles?.videoBadgeText, children: showDownloadBadge && !isPaused && !isEnded ? ctaText : badgeText }) }),
|
|
1055
|
+
isEnded && /* @__PURE__ */ jsxs(View2, { style: styles?.endOverlay, children: [
|
|
1056
|
+
/* @__PURE__ */ jsx4(TouchableOpacity, { style: styles?.endOverlayTouch, onPress: () => handleLinkClick(clickUrl), children: /* @__PURE__ */ jsx4(Text2, { style: styles?.endButton, children: ctaText }) }),
|
|
1057
|
+
/* @__PURE__ */ jsx4(Text2, { style: styles?.endText, children: endText })
|
|
1058
|
+
] })
|
|
1059
|
+
] });
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// src/hooks/useAd.tsx
|
|
1063
|
+
import { useContext as useContext5 } from "react";
|
|
1064
|
+
function useAd({ code, messageId }) {
|
|
1065
|
+
const context = useContext5(AdsContext);
|
|
1066
|
+
if (!context) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
const ad = context.ads.find((ad2) => {
|
|
1070
|
+
if (messageId) {
|
|
1071
|
+
return ad2.code === code && ad2.messageId === messageId;
|
|
1072
|
+
}
|
|
1073
|
+
return ad2.code === code;
|
|
1074
|
+
});
|
|
1075
|
+
if (!ad) {
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
const placement = context.enabledPlacements.find((p) => p.code === code);
|
|
1079
|
+
if (!placement) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const dependencies = placement.config.dependencies?.split(",").map((d) => d.trim()).filter((d) => d);
|
|
1083
|
+
if (!dependencies || dependencies.length === 0) {
|
|
1084
|
+
return ad;
|
|
1085
|
+
}
|
|
1086
|
+
const dependencyAds = context.enabledPlacements.filter(
|
|
1087
|
+
(p) => dependencies.includes(p.code)
|
|
1088
|
+
);
|
|
1089
|
+
if (!dependencyAds.length) {
|
|
1090
|
+
return ad;
|
|
1091
|
+
}
|
|
1092
|
+
const allAdsLoadedAndNotStreaming = dependencyAds.every((p) => {
|
|
1093
|
+
let a;
|
|
1094
|
+
if (messageId) {
|
|
1095
|
+
a = context.ads.find(
|
|
1096
|
+
(ad2) => ad2.code === p.code && ad2.messageId === messageId
|
|
1097
|
+
);
|
|
1098
|
+
} else {
|
|
1099
|
+
a = context.ads.find((ad2) => ad2.code === p.code);
|
|
1100
|
+
}
|
|
1101
|
+
return a && !a.isLoading && !a.isStreaming || a && a.isError;
|
|
1102
|
+
});
|
|
1103
|
+
if (!allAdsLoadedAndNotStreaming) {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
return ad;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/formats/InlineAd.tsx
|
|
1110
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1111
|
+
var InlineAd = ({ code, messageId, wrapper }) => {
|
|
1112
|
+
const ad = useAd({ code, messageId });
|
|
1113
|
+
const [linkIncluded, setLinkIncluded] = useState8(false);
|
|
1114
|
+
const context = useContext6(AdsContext);
|
|
1115
|
+
const styles = context?.styles?.inlineAd;
|
|
1116
|
+
useAdViewed(ad);
|
|
1117
|
+
useEffect8(() => {
|
|
1118
|
+
if (ad?.content && !ad.isStreaming && !ad.content.match(/\[.*?\]\(.*?\)/) && !linkIncluded) {
|
|
1119
|
+
setLinkIncluded(true);
|
|
1120
|
+
}
|
|
1121
|
+
}, [ad]);
|
|
1122
|
+
if (!ad || !ad.content || context?.isDisabled) return null;
|
|
1123
|
+
if (ad.isLoading || ad.content?.trim().toLowerCase().includes("none"))
|
|
1124
|
+
return null;
|
|
1125
|
+
const onProgress = (progress) => {
|
|
1126
|
+
log8.log(`Progress: ${progress}`);
|
|
1127
|
+
};
|
|
1128
|
+
const handleImageClick = (url) => {
|
|
1129
|
+
if (url) {
|
|
1130
|
+
context?.onAdClickInternal(ad);
|
|
1131
|
+
if (Platform2.OS === "web") {
|
|
1132
|
+
window.open(url, "_blank");
|
|
1133
|
+
} else {
|
|
1134
|
+
Linking3.openURL(url).catch(
|
|
1135
|
+
(err) => log8.error("Failed to open URL:", err)
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
const content = /* @__PURE__ */ jsxs2(View3, { style: styles?.container, children: [
|
|
1141
|
+
/* @__PURE__ */ jsx5(View3, { style: styles?.adBadgeContainer, children: /* @__PURE__ */ jsx5(Text3, { style: styles?.adBadgeText, children: "Ad" }) }),
|
|
1142
|
+
ad.imageUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ jsx5(
|
|
1143
|
+
TouchableOpacity2,
|
|
1144
|
+
{
|
|
1145
|
+
style: styles?.imageContainer,
|
|
1146
|
+
onPress: () => handleImageClick(ad.url),
|
|
1147
|
+
children: /* @__PURE__ */ jsx5(Image, { source: { uri: ad.imageUrl }, style: styles?.image })
|
|
1148
|
+
}
|
|
1149
|
+
),
|
|
1150
|
+
ad.videoUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ jsx5(
|
|
1151
|
+
VideoPlayer,
|
|
1152
|
+
{
|
|
1153
|
+
src: ad.videoUrl,
|
|
1154
|
+
onProgress,
|
|
1155
|
+
endText: ad.videoEndText,
|
|
1156
|
+
badgeText: ad.videoBadgeText,
|
|
1157
|
+
videoPosterUrl: ad.videoPosterUrl,
|
|
1158
|
+
videoOrientation: ad.videoOrientation,
|
|
1159
|
+
mediaPlacement: ad.mediaPlacement,
|
|
1160
|
+
ctaText: ad.videoCtaText,
|
|
1161
|
+
onLinkClick: () => context?.onAdClickInternal(ad),
|
|
1162
|
+
clickUrl: ad.url
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
/* @__PURE__ */ jsx5(Text3, { style: styles?.contentContainer, children: /* @__PURE__ */ jsx5(
|
|
1166
|
+
MarkdownText,
|
|
1167
|
+
{
|
|
1168
|
+
onLinkClick: () => context?.onAdClickInternal(ad),
|
|
1169
|
+
content: ad.content
|
|
1170
|
+
}
|
|
1171
|
+
) }),
|
|
1172
|
+
ad.imageUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ jsx5(
|
|
1173
|
+
TouchableOpacity2,
|
|
1174
|
+
{
|
|
1175
|
+
style: styles?.imageContainer,
|
|
1176
|
+
onPress: () => handleImageClick(ad.url),
|
|
1177
|
+
children: /* @__PURE__ */ jsx5(Image, { source: { uri: ad.imageUrl }, style: styles?.image })
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
ad.videoUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ jsx5(
|
|
1181
|
+
VideoPlayer,
|
|
1182
|
+
{
|
|
1183
|
+
src: ad.videoUrl,
|
|
1184
|
+
onProgress,
|
|
1185
|
+
endText: ad.videoEndText,
|
|
1186
|
+
badgeText: ad.videoBadgeText,
|
|
1187
|
+
videoPosterUrl: ad.videoPosterUrl,
|
|
1188
|
+
videoOrientation: ad.videoOrientation,
|
|
1189
|
+
mediaPlacement: ad.mediaPlacement,
|
|
1190
|
+
ctaText: ad.videoCtaText,
|
|
1191
|
+
onLinkClick: () => context?.onAdClickInternal(ad),
|
|
1192
|
+
clickUrl: ad.url
|
|
1193
|
+
}
|
|
1194
|
+
)
|
|
1195
|
+
] });
|
|
1196
|
+
return wrapper ? wrapper(content) : content;
|
|
1197
|
+
};
|
|
1198
|
+
var InlineAd_default = InlineAd;
|
|
1199
|
+
export {
|
|
1200
|
+
AdsContext,
|
|
1201
|
+
AdsProvider,
|
|
1202
|
+
InlineAd_default as InlineAd,
|
|
1203
|
+
VISITOR_ID_KEY,
|
|
1204
|
+
useAd
|
|
1205
|
+
};
|