@simula/ads 1.1.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 ADDED
@@ -0,0 +1,2240 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
5
+
6
+ // Production API URL
7
+ const API_BASE_URL = 'https://simula-api-701226639755.us-central1.run.app';
8
+ // Create a server session and return its id
9
+ async function createSession(apiKey, devMode, primaryUserID) {
10
+ try {
11
+ const headers = {
12
+ 'Content-Type': 'application/json',
13
+ 'Authorization': `Bearer ${apiKey}`,
14
+ };
15
+ // Build query parameters
16
+ const params = new URLSearchParams();
17
+ if (devMode !== undefined) {
18
+ params.append('devMode', String(devMode));
19
+ }
20
+ if (primaryUserID !== undefined && primaryUserID !== '') {
21
+ params.append('ppid', primaryUserID);
22
+ }
23
+ const queryString = params.toString();
24
+ const url = `${API_BASE_URL}/session/create${queryString ? `?${queryString}` : ''}`;
25
+ const response = await fetch(url, {
26
+ method: 'POST',
27
+ headers,
28
+ body: JSON.stringify({}),
29
+ });
30
+ if (!response.ok) {
31
+ if (response.status === 401) {
32
+ throw new Error('Invalid API key (please check dashboard or contact Simula team for a valid API key)');
33
+ }
34
+ return undefined;
35
+ }
36
+ const data = await response.json();
37
+ if (data && typeof data.sessionId === 'string' && data.sessionId) {
38
+ return data.sessionId;
39
+ }
40
+ return undefined;
41
+ }
42
+ catch (error) {
43
+ // Re-throw 401 errors with our custom message
44
+ if (error instanceof Error && error.message.includes('Invalid API key')) {
45
+ throw error;
46
+ }
47
+ return undefined;
48
+ }
49
+ }
50
+ const fetchAd = async (request) => {
51
+ var _a, _b, _c, _d;
52
+ try {
53
+ const conversationHistory = request.messages;
54
+ // Normalize theme accent and font to arrays for backend
55
+ const normalizedTheme = request.theme ? {
56
+ ...request.theme,
57
+ accent: request.theme.accent ? (Array.isArray(request.theme.accent) ? request.theme.accent : [request.theme.accent]) : undefined,
58
+ font: request.theme.font ? (Array.isArray(request.theme.font) ? request.theme.font : [request.theme.font]) : undefined,
59
+ } : undefined;
60
+ const requestBody = {
61
+ messages: conversationHistory,
62
+ types: request.formats,
63
+ slot_id: request.slotId,
64
+ theme: normalizedTheme,
65
+ session_id: request.sessionId,
66
+ char_desc: request.charDesc,
67
+ };
68
+ const headers = {
69
+ 'Content-Type': 'application/json',
70
+ 'Authorization': `Bearer ${request.apiKey}`,
71
+ };
72
+ const response = await fetch(`${API_BASE_URL}/render_ad/ssp`, {
73
+ method: 'POST',
74
+ headers,
75
+ body: JSON.stringify(requestBody),
76
+ });
77
+ if (!response.ok) {
78
+ throw new Error(`HTTP error! status: ${response.status}`);
79
+ }
80
+ const data = await response.json();
81
+ // Handle new API shape
82
+ if (data && typeof data === 'object') {
83
+ if (!data.adInserted) {
84
+ return { error: 'No fill' };
85
+ }
86
+ // New shape: { adType, adInserted, adResponse: { ad_id, iframe_url, ... } }
87
+ if (data.adResponse && typeof data.adResponse === 'object') {
88
+ const ar = data.adResponse;
89
+ const ad = {
90
+ id: (_a = ar.ad_id) !== null && _a !== void 0 ? _a : ar.id,
91
+ format: ((_c = (_b = data.adType) !== null && _b !== void 0 ? _b : ar.format) !== null && _c !== void 0 ? _c : 'iframe'),
92
+ iframeUrl: (_d = ar.iframe_url) !== null && _d !== void 0 ? _d : ar.iframeUrl,
93
+ };
94
+ if (ad.id && ad.iframeUrl) {
95
+ return { ad };
96
+ }
97
+ return { error: 'Invalid ad response' };
98
+ }
99
+ // Legacy shape: { ad: { ... } }
100
+ if (data.ad) {
101
+ return { ad: data.ad };
102
+ }
103
+ if (data.error) {
104
+ return { error: data.error };
105
+ }
106
+ }
107
+ return { error: 'Unexpected response from ad server' };
108
+ }
109
+ catch (error) {
110
+ console.error('❌ API Request failed:', error);
111
+ return {
112
+ error: error instanceof Error ? error.message : 'Failed to fetch ad'
113
+ };
114
+ }
115
+ };
116
+ const trackImpression = async (adId, apiKey) => {
117
+ try {
118
+ const headers = {
119
+ 'Content-Type': 'application/json',
120
+ 'Authorization': `Bearer ${apiKey}`,
121
+ };
122
+ await fetch(`${API_BASE_URL}/track/engagement/impression/${adId}`, {
123
+ method: 'POST',
124
+ headers,
125
+ body: JSON.stringify({}),
126
+ });
127
+ }
128
+ catch (error) {
129
+ console.error('Failed to track impression:', error);
130
+ }
131
+ };
132
+ /* Not used for now, used when we are also mediation layer
133
+ export const trackClick = async (adId: string, apiKey: string): Promise<void> => {
134
+ try {
135
+ await fetch(`${API_BASE_URL}/track_click`, {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json',
139
+ 'Authorization': `Bearer ${apiKey}`,
140
+ },
141
+ body: JSON.stringify({
142
+ ad_id: adId,
143
+ timestamp: new Date().toISOString(),
144
+ }),
145
+ });
146
+ } catch (error) {
147
+ console.error('Failed to track click:', error);
148
+ }
149
+ };
150
+ */
151
+
152
+ // Helper functions for width validation
153
+ const isAutoWidth$1 = (width) => width === 'auto';
154
+ const isPercentageWidth$1 = (width) => typeof width === 'string' && /^\d+(?:\.\d+)?%$/.test(width);
155
+ const isPixelWidth = (width) => typeof width === 'string' && /^\d+(?:\.\d+)?px$/.test(width);
156
+ /**
157
+ * Validates SimulaProvider props
158
+ * Throws descriptive errors for invalid props
159
+ */
160
+ const validateSimulaProviderProps = (props) => {
161
+ const validProps = ['apiKey', 'children', 'devMode', 'primaryUserID'];
162
+ const receivedProps = Object.keys(props);
163
+ // Check for unknown props
164
+ const unknownProps = receivedProps.filter(prop => !validProps.includes(prop));
165
+ if (unknownProps.length > 0) {
166
+ throw new Error(`Invalid prop${unknownProps.length > 1 ? 's' : ''} passed to SimulaProvider: ${unknownProps.map(p => `"${p}"`).join(', ')}. ` +
167
+ `Valid props are: ${validProps.join(', ')}`);
168
+ }
169
+ // Validate required props
170
+ if (!props.apiKey || typeof props.apiKey !== 'string') {
171
+ throw new Error('SimulaProvider requires a valid "apiKey" prop (string)');
172
+ }
173
+ if (!props.children) {
174
+ throw new Error('SimulaProvider requires a "children" prop');
175
+ }
176
+ // Validate optional props
177
+ if (props.devMode !== undefined && typeof props.devMode !== 'boolean') {
178
+ throw new Error(`Invalid "devMode" prop type: "${typeof props.devMode}". Must be a boolean`);
179
+ }
180
+ if (props.primaryUserID !== undefined && typeof props.primaryUserID !== 'string') {
181
+ throw new Error(`Invalid "primaryUserID" prop type: "${typeof props.primaryUserID}". Must be a string`);
182
+ }
183
+ };
184
+ /**
185
+ * Validates InChatAdSlot props
186
+ * Throws descriptive errors for invalid props
187
+ */
188
+ const validateInChatAdSlotProps = (props) => {
189
+ const validProps = ['messages', 'trigger', 'formats', 'theme', 'debounceMs', 'charDesc', 'onImpression', 'onClick', 'onError'];
190
+ const receivedProps = Object.keys(props);
191
+ // Check for unknown props
192
+ const unknownProps = receivedProps.filter(prop => !validProps.includes(prop));
193
+ if (unknownProps.length > 0) {
194
+ throw new Error(`Invalid prop${unknownProps.length > 1 ? 's' : ''} passed to InChatAdSlot: ${unknownProps.map(p => `"${p}"`).join(', ')}. ` +
195
+ `Valid props are: ${validProps.join(', ')}`);
196
+ }
197
+ // Validate messages (required)
198
+ if (!props.messages || !Array.isArray(props.messages)) {
199
+ throw new Error('InChatAdSlot requires a valid "messages" prop (array of Message objects)');
200
+ }
201
+ if (props.messages.length === 0) {
202
+ throw new Error('InChatAdSlot "messages" prop cannot be an empty array');
203
+ }
204
+ props.messages.forEach((msg, i) => {
205
+ if (!msg || typeof msg !== 'object') {
206
+ throw new Error(`Invalid message at index ${i}: must be an object with "role" and "content" properties`);
207
+ }
208
+ if (!msg.role || typeof msg.role !== 'string') {
209
+ throw new Error(`Invalid message at index ${i}: "role" must be a non-empty string`);
210
+ }
211
+ if (msg.content === undefined || typeof msg.content !== 'string') {
212
+ throw new Error(`Invalid message at index ${i}: "content" must be a string`);
213
+ }
214
+ });
215
+ // Validate formats (optional)
216
+ if (props.formats !== undefined) {
217
+ validateFormats(props.formats);
218
+ }
219
+ // Validate theme (optional)
220
+ if (props.theme !== undefined) {
221
+ validateTheme(props.theme);
222
+ }
223
+ // Validate debounceMs (optional)
224
+ if (props.debounceMs !== undefined) {
225
+ if (typeof props.debounceMs !== 'number') {
226
+ throw new Error(`Invalid "debounceMs" prop type: "${typeof props.debounceMs}". Must be a number`);
227
+ }
228
+ if (props.debounceMs < 0) {
229
+ throw new Error(`Invalid "debounceMs" prop value: "${props.debounceMs}". Must be a non-negative number`);
230
+ }
231
+ }
232
+ // Validate charDesc (optional)
233
+ if (props.charDesc !== undefined && typeof props.charDesc !== 'string') {
234
+ throw new Error(`Invalid "charDesc" prop type: "${typeof props.charDesc}". Must be a string`);
235
+ }
236
+ // Validate callbacks (optional)
237
+ if (props.onImpression !== undefined && typeof props.onImpression !== 'function') {
238
+ throw new Error(`Invalid "onImpression" prop type: "${typeof props.onImpression}". Must be a function`);
239
+ }
240
+ if (props.onClick !== undefined && typeof props.onClick !== 'function') {
241
+ throw new Error(`Invalid "onClick" prop type: "${typeof props.onClick}". Must be a function`);
242
+ }
243
+ if (props.onError !== undefined && typeof props.onError !== 'function') {
244
+ throw new Error(`Invalid "onError" prop type: "${typeof props.onError}". Must be a function`);
245
+ }
246
+ // Note: trigger validation (Promise check) is skipped because checking instanceof Promise
247
+ // is unreliable across different Promise implementations and contexts
248
+ };
249
+ /**
250
+ * Validates formats - accepts string or string[]
251
+ * Throws descriptive errors for invalid formats
252
+ */
253
+ const validateFormats = (formats) => {
254
+ if (!formats)
255
+ return;
256
+ const validFormatOptions = ['all', 'tips', 'interactive', 'suggestions', 'text', 'highlight', 'visual_banner', 'image_feature'];
257
+ // Normalize to array for validation
258
+ const formatsArray = Array.isArray(formats) ? formats : [formats];
259
+ formatsArray.forEach((format, i) => {
260
+ if (typeof format !== 'string') {
261
+ throw new Error(`Invalid format type at index ${i}: "${typeof format}". Must be a string. Valid values: ${validFormatOptions.join(', ')}`);
262
+ }
263
+ if (!validFormatOptions.includes(format)) {
264
+ throw new Error(`Invalid format value${Array.isArray(formats) ? ` at index ${i}` : ''}: "${format}". Valid values: ${validFormatOptions.join(', ')}`);
265
+ }
266
+ });
267
+ };
268
+ /**
269
+ * Validates theme object
270
+ * Throws descriptive errors for invalid theme properties
271
+ */
272
+ const validateTheme = (theme) => {
273
+ if (!theme || typeof theme !== 'object') {
274
+ throw new Error('Invalid "theme": must be an object');
275
+ }
276
+ const validThemeOptions = ['light', 'dark', 'auto'];
277
+ const validAccentOptions = ['blue', 'red', 'green', 'yellow', 'purple', 'pink', 'orange', 'neutral', 'gray', 'tan', 'transparent', 'image'];
278
+ const validFontOptions = ['san-serif', 'serif', 'monospace'];
279
+ const validKeys = ['theme', 'accent', 'font', 'width', 'cornerRadius'];
280
+ // Check for invalid top-level keys
281
+ Object.keys(theme).forEach(key => {
282
+ if (!validKeys.includes(key)) {
283
+ throw new Error(`Invalid theme parameter "${key}". Valid parameters: ${validKeys.join(', ')}`);
284
+ }
285
+ });
286
+ // Validate theme value
287
+ if (theme.theme !== undefined) {
288
+ if (typeof theme.theme !== 'string') {
289
+ throw new Error(`Invalid theme type "${typeof theme.theme}". Must be a string. Valid values: ${validThemeOptions.join(', ')}`);
290
+ }
291
+ if (!validThemeOptions.includes(theme.theme)) {
292
+ throw new Error(`Invalid theme value "${theme.theme}". Valid values: ${validThemeOptions.join(', ')}`);
293
+ }
294
+ }
295
+ // Validate accent value(s)
296
+ if (theme.accent !== undefined) {
297
+ const accents = Array.isArray(theme.accent) ? theme.accent : [theme.accent];
298
+ accents.forEach((accent, i) => {
299
+ if (typeof accent !== 'string') {
300
+ throw new Error(`Invalid accent type at index ${i}: "${typeof accent}". Must be a string. Valid values: ${validAccentOptions.join(', ')}`);
301
+ }
302
+ if (!validAccentOptions.includes(accent)) {
303
+ throw new Error(`Invalid accent value${Array.isArray(theme.accent) ? ` at index ${i}` : ''}: "${accent}". Valid values: ${validAccentOptions.join(', ')}`);
304
+ }
305
+ });
306
+ }
307
+ // Validate font value(s)
308
+ if (theme.font !== undefined) {
309
+ const fonts = Array.isArray(theme.font) ? theme.font : [theme.font];
310
+ fonts.forEach((font, i) => {
311
+ if (typeof font !== 'string') {
312
+ throw new Error(`Invalid font type at index ${i}: "${typeof font}". Must be a string. Valid values: ${validFontOptions.join(', ')}`);
313
+ }
314
+ if (!validFontOptions.includes(font)) {
315
+ throw new Error(`Invalid font value${Array.isArray(theme.font) ? ` at index ${i}` : ''}: "${font}". Valid values: ${validFontOptions.join(', ')}`);
316
+ }
317
+ });
318
+ }
319
+ // Validate width
320
+ if (theme.width !== undefined) {
321
+ if (typeof theme.width !== 'number' && typeof theme.width !== 'string') {
322
+ throw new Error(`Invalid width type "${typeof theme.width}". Must be number, "auto", "%", or "px"`);
323
+ }
324
+ if (typeof theme.width === 'string' && !isAutoWidth$1(theme.width) && !isPercentageWidth$1(theme.width) && !isPixelWidth(theme.width)) {
325
+ throw new Error(`Invalid width "${theme.width}". Must be an integer, "auto", or a string like "100%", "500px"`);
326
+ }
327
+ }
328
+ // Validate cornerRadius
329
+ if (theme.cornerRadius !== undefined && typeof theme.cornerRadius !== 'number') {
330
+ throw new Error(`Invalid cornerRadius type "${typeof theme.cornerRadius}". Must be a number`);
331
+ }
332
+ };
333
+
334
+ const SimulaContext = react.createContext(undefined);
335
+ const useSimula = () => {
336
+ const context = react.useContext(SimulaContext);
337
+ if (!context) {
338
+ throw new Error('useSimula must be used within a SimulaProvider');
339
+ }
340
+ return context;
341
+ };
342
+ const SimulaProvider = (props) => {
343
+ // Validate props early
344
+ validateSimulaProviderProps(props);
345
+ const { apiKey, children, devMode = false, primaryUserID } = props;
346
+ const [sessionId, setSessionId] = react.useState(undefined);
347
+ react.useEffect(() => {
348
+ let cancelled = false;
349
+ async function ensureSession() {
350
+ const id = await createSession(apiKey, devMode, primaryUserID);
351
+ if (!cancelled && id)
352
+ setSessionId(id);
353
+ }
354
+ ensureSession();
355
+ return () => { cancelled = true; };
356
+ }, [apiKey, devMode, primaryUserID]);
357
+ const value = {
358
+ apiKey,
359
+ devMode,
360
+ sessionId,
361
+ };
362
+ return (jsxRuntime.jsx(SimulaContext.Provider, { value: value, children: children }));
363
+ };
364
+
365
+ const byteToHex = [];
366
+ for (let i = 0; i < 256; ++i) {
367
+ byteToHex.push((i + 0x100).toString(16).slice(1));
368
+ }
369
+ function unsafeStringify(arr, offset = 0) {
370
+ return (byteToHex[arr[offset + 0]] +
371
+ byteToHex[arr[offset + 1]] +
372
+ byteToHex[arr[offset + 2]] +
373
+ byteToHex[arr[offset + 3]] +
374
+ '-' +
375
+ byteToHex[arr[offset + 4]] +
376
+ byteToHex[arr[offset + 5]] +
377
+ '-' +
378
+ byteToHex[arr[offset + 6]] +
379
+ byteToHex[arr[offset + 7]] +
380
+ '-' +
381
+ byteToHex[arr[offset + 8]] +
382
+ byteToHex[arr[offset + 9]] +
383
+ '-' +
384
+ byteToHex[arr[offset + 10]] +
385
+ byteToHex[arr[offset + 11]] +
386
+ byteToHex[arr[offset + 12]] +
387
+ byteToHex[arr[offset + 13]] +
388
+ byteToHex[arr[offset + 14]] +
389
+ byteToHex[arr[offset + 15]]).toLowerCase();
390
+ }
391
+
392
+ let getRandomValues;
393
+ const rnds8 = new Uint8Array(16);
394
+ function rng() {
395
+ if (!getRandomValues) {
396
+ if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
397
+ throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
398
+ }
399
+ getRandomValues = crypto.getRandomValues.bind(crypto);
400
+ }
401
+ return getRandomValues(rnds8);
402
+ }
403
+
404
+ const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);
405
+ var native = { randomUUID };
406
+
407
+ function _v4(options, buf, offset) {
408
+ options = options || {};
409
+ const rnds = options.random ?? options.rng?.() ?? rng();
410
+ if (rnds.length < 16) {
411
+ throw new Error('Random bytes length must be >= 16');
412
+ }
413
+ rnds[6] = (rnds[6] & 0x0f) | 0x40;
414
+ rnds[8] = (rnds[8] & 0x3f) | 0x80;
415
+ return unsafeStringify(rnds);
416
+ }
417
+ function v4(options, buf, offset) {
418
+ if (native.randomUUID && true && !options) {
419
+ return native.randomUUID();
420
+ }
421
+ return _v4(options);
422
+ }
423
+
424
+ const useDebounce = (callback, delay, deps) => {
425
+ const timeoutRef = react.useRef();
426
+ react.useEffect(() => {
427
+ if (timeoutRef.current) {
428
+ clearTimeout(timeoutRef.current);
429
+ }
430
+ timeoutRef.current = setTimeout(callback, delay);
431
+ return () => {
432
+ if (timeoutRef.current) {
433
+ clearTimeout(timeoutRef.current);
434
+ }
435
+ };
436
+ }, [...deps, delay]);
437
+ react.useEffect(() => {
438
+ return () => {
439
+ if (timeoutRef.current) {
440
+ clearTimeout(timeoutRef.current);
441
+ }
442
+ };
443
+ }, []);
444
+ };
445
+
446
+ /******************************************************************************
447
+ Copyright (c) Microsoft Corporation.
448
+
449
+ Permission to use, copy, modify, and/or distribute this software for any
450
+ purpose with or without fee is hereby granted.
451
+
452
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
453
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
454
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
455
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
456
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
457
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
458
+ PERFORMANCE OF THIS SOFTWARE.
459
+ ***************************************************************************** */
460
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
461
+
462
+ var extendStatics = function(d, b) {
463
+ extendStatics = Object.setPrototypeOf ||
464
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
465
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
466
+ return extendStatics(d, b);
467
+ };
468
+
469
+ function __extends(d, b) {
470
+ if (typeof b !== "function" && b !== null)
471
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
472
+ extendStatics(d, b);
473
+ function __() { this.constructor = d; }
474
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
475
+ }
476
+
477
+ function __awaiter(thisArg, _arguments, P, generator) {
478
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
479
+ return new (P || (P = Promise))(function (resolve, reject) {
480
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
481
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
482
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
483
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
484
+ });
485
+ }
486
+
487
+ function __generator(thisArg, body) {
488
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
489
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
490
+ function verb(n) { return function (v) { return step([n, v]); }; }
491
+ function step(op) {
492
+ if (f) throw new TypeError("Generator is already executing.");
493
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
494
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
495
+ if (y = 0, t) op = [op[0] & 2, t.value];
496
+ switch (op[0]) {
497
+ case 0: case 1: t = op; break;
498
+ case 4: _.label++; return { value: op[1], done: false };
499
+ case 5: _.label++; y = op[1]; op = [0]; continue;
500
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
501
+ default:
502
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
503
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
504
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
505
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
506
+ if (t[2]) _.ops.pop();
507
+ _.trys.pop(); continue;
508
+ }
509
+ op = body.call(thisArg, _);
510
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
511
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
512
+ }
513
+ }
514
+
515
+ function __spreadArray(to, from, pack) {
516
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
517
+ if (ar || !(i in from)) {
518
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
519
+ ar[i] = from[i];
520
+ }
521
+ }
522
+ return to.concat(ar || Array.prototype.slice.call(from));
523
+ }
524
+
525
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
526
+ var e = new Error(message);
527
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
528
+ };
529
+
530
+ /**
531
+ * Fingerprint BotD v1.9.1 - Copyright (c) FingerprintJS, Inc, 2024 (https://fingerprint.com)
532
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
533
+ */
534
+
535
+
536
+ var version = "1.9.1";
537
+
538
+ /**
539
+ * Enum for types of bots.
540
+ * Specific types of bots come first, followed by automation technologies.
541
+ *
542
+ * @readonly
543
+ * @enum {string}
544
+ */
545
+ var BotKind = {
546
+ // Object is used instead of Typescript enum to avoid emitting IIFE which might be affected by further tree-shaking.
547
+ // See example of compiled enums https://stackoverflow.com/q/47363996)
548
+ Awesomium: 'awesomium',
549
+ Cef: 'cef',
550
+ CefSharp: 'cefsharp',
551
+ CoachJS: 'coachjs',
552
+ Electron: 'electron',
553
+ FMiner: 'fminer',
554
+ Geb: 'geb',
555
+ NightmareJS: 'nightmarejs',
556
+ Phantomas: 'phantomas',
557
+ PhantomJS: 'phantomjs',
558
+ Rhino: 'rhino',
559
+ Selenium: 'selenium',
560
+ Sequentum: 'sequentum',
561
+ SlimerJS: 'slimerjs',
562
+ WebDriverIO: 'webdriverio',
563
+ WebDriver: 'webdriver',
564
+ HeadlessChrome: 'headless_chrome',
565
+ Unknown: 'unknown',
566
+ };
567
+ /**
568
+ * Bot detection error.
569
+ */
570
+ var BotdError = /** @class */ (function (_super) {
571
+ __extends(BotdError, _super);
572
+ /**
573
+ * Creates a new BotdError.
574
+ *
575
+ * @class
576
+ */
577
+ function BotdError(state, message) {
578
+ var _this = _super.call(this, message) || this;
579
+ _this.state = state;
580
+ _this.name = 'BotdError';
581
+ Object.setPrototypeOf(_this, BotdError.prototype);
582
+ return _this;
583
+ }
584
+ return BotdError;
585
+ }(Error));
586
+
587
+ function detect(components, detectors) {
588
+ var detections = {};
589
+ var finalDetection = {
590
+ bot: false,
591
+ };
592
+ for (var detectorName in detectors) {
593
+ var detector = detectors[detectorName];
594
+ var detectorRes = detector(components);
595
+ var detection = { bot: false };
596
+ if (typeof detectorRes === 'string') {
597
+ detection = { bot: true, botKind: detectorRes };
598
+ }
599
+ else if (detectorRes) {
600
+ detection = { bot: true, botKind: BotKind.Unknown };
601
+ }
602
+ detections[detectorName] = detection;
603
+ if (detection.bot) {
604
+ finalDetection = detection;
605
+ }
606
+ }
607
+ return [detections, finalDetection];
608
+ }
609
+ function collect(sources) {
610
+ return __awaiter(this, void 0, void 0, function () {
611
+ var components, sourcesKeys;
612
+ var _this = this;
613
+ return __generator(this, function (_a) {
614
+ switch (_a.label) {
615
+ case 0:
616
+ components = {};
617
+ sourcesKeys = Object.keys(sources);
618
+ return [4 /*yield*/, Promise.all(sourcesKeys.map(function (sourceKey) { return __awaiter(_this, void 0, void 0, function () {
619
+ var res, _a, _b, error_1;
620
+ var _c;
621
+ return __generator(this, function (_d) {
622
+ switch (_d.label) {
623
+ case 0:
624
+ res = sources[sourceKey];
625
+ _d.label = 1;
626
+ case 1:
627
+ _d.trys.push([1, 3, , 4]);
628
+ _a = components;
629
+ _b = sourceKey;
630
+ _c = {};
631
+ return [4 /*yield*/, res()];
632
+ case 2:
633
+ _a[_b] = (_c.value = _d.sent(),
634
+ _c.state = 0 /* State.Success */,
635
+ _c);
636
+ return [3 /*break*/, 4];
637
+ case 3:
638
+ error_1 = _d.sent();
639
+ if (error_1 instanceof BotdError) {
640
+ components[sourceKey] = {
641
+ state: error_1.state,
642
+ error: "".concat(error_1.name, ": ").concat(error_1.message),
643
+ };
644
+ }
645
+ else {
646
+ components[sourceKey] = {
647
+ state: -3 /* State.UnexpectedBehaviour */,
648
+ error: error_1 instanceof Error ? "".concat(error_1.name, ": ").concat(error_1.message) : String(error_1),
649
+ };
650
+ }
651
+ return [3 /*break*/, 4];
652
+ case 4: return [2 /*return*/];
653
+ }
654
+ });
655
+ }); }))];
656
+ case 1:
657
+ _a.sent();
658
+ return [2 /*return*/, components];
659
+ }
660
+ });
661
+ });
662
+ }
663
+
664
+ function detectAppVersion(_a) {
665
+ var appVersion = _a.appVersion;
666
+ if (appVersion.state !== 0 /* State.Success */)
667
+ return false;
668
+ if (/headless/i.test(appVersion.value))
669
+ return BotKind.HeadlessChrome;
670
+ if (/electron/i.test(appVersion.value))
671
+ return BotKind.Electron;
672
+ if (/slimerjs/i.test(appVersion.value))
673
+ return BotKind.SlimerJS;
674
+ }
675
+
676
+ function arrayIncludes(arr, value) {
677
+ return arr.indexOf(value) !== -1;
678
+ }
679
+ function strIncludes(str, value) {
680
+ return str.indexOf(value) !== -1;
681
+ }
682
+ function arrayFind(array, callback) {
683
+ if ('find' in array)
684
+ return array.find(callback);
685
+ for (var i = 0; i < array.length; i++) {
686
+ if (callback(array[i], i, array))
687
+ return array[i];
688
+ }
689
+ return undefined;
690
+ }
691
+
692
+ function getObjectProps(obj) {
693
+ return Object.getOwnPropertyNames(obj);
694
+ }
695
+ function includes(arr) {
696
+ var keys = [];
697
+ for (var _i = 1; _i < arguments.length; _i++) {
698
+ keys[_i - 1] = arguments[_i];
699
+ }
700
+ var _loop_1 = function (key) {
701
+ if (typeof key === 'string') {
702
+ if (arrayIncludes(arr, key))
703
+ return { value: true };
704
+ }
705
+ else {
706
+ var match = arrayFind(arr, function (value) { return key.test(value); });
707
+ if (match != null)
708
+ return { value: true };
709
+ }
710
+ };
711
+ for (var _a = 0, keys_1 = keys; _a < keys_1.length; _a++) {
712
+ var key = keys_1[_a];
713
+ var state_1 = _loop_1(key);
714
+ if (typeof state_1 === "object")
715
+ return state_1.value;
716
+ }
717
+ return false;
718
+ }
719
+ function countTruthy(values) {
720
+ return values.reduce(function (sum, value) { return sum + (value ? 1 : 0); }, 0);
721
+ }
722
+
723
+ function detectDocumentAttributes(_a) {
724
+ var documentElementKeys = _a.documentElementKeys;
725
+ if (documentElementKeys.state !== 0 /* State.Success */)
726
+ return false;
727
+ if (includes(documentElementKeys.value, 'selenium', 'webdriver', 'driver')) {
728
+ return BotKind.Selenium;
729
+ }
730
+ }
731
+
732
+ function detectErrorTrace(_a) {
733
+ var errorTrace = _a.errorTrace;
734
+ if (errorTrace.state !== 0 /* State.Success */)
735
+ return false;
736
+ if (/PhantomJS/i.test(errorTrace.value))
737
+ return BotKind.PhantomJS;
738
+ }
739
+
740
+ function detectEvalLengthInconsistency(_a) {
741
+ var evalLength = _a.evalLength, browserKind = _a.browserKind, browserEngineKind = _a.browserEngineKind;
742
+ if (evalLength.state !== 0 /* State.Success */ ||
743
+ browserKind.state !== 0 /* State.Success */ ||
744
+ browserEngineKind.state !== 0 /* State.Success */)
745
+ return;
746
+ var length = evalLength.value;
747
+ if (browserEngineKind.value === "unknown" /* BrowserEngineKind.Unknown */)
748
+ return false;
749
+ return ((length === 37 && !arrayIncludes(["webkit" /* BrowserEngineKind.Webkit */, "gecko" /* BrowserEngineKind.Gecko */], browserEngineKind.value)) ||
750
+ (length === 39 && !arrayIncludes(["internet_explorer" /* BrowserKind.IE */], browserKind.value)) ||
751
+ (length === 33 && !arrayIncludes(["chromium" /* BrowserEngineKind.Chromium */], browserEngineKind.value)));
752
+ }
753
+
754
+ function detectFunctionBind(_a) {
755
+ var functionBind = _a.functionBind;
756
+ if (functionBind.state === -2 /* State.NotFunction */)
757
+ return BotKind.PhantomJS;
758
+ }
759
+
760
+ function detectLanguagesLengthInconsistency(_a) {
761
+ var languages = _a.languages;
762
+ if (languages.state === 0 /* State.Success */ && languages.value.length === 0) {
763
+ return BotKind.HeadlessChrome;
764
+ }
765
+ }
766
+
767
+ function detectMimeTypesConsistent(_a) {
768
+ var mimeTypesConsistent = _a.mimeTypesConsistent;
769
+ if (mimeTypesConsistent.state === 0 /* State.Success */ && !mimeTypesConsistent.value) {
770
+ return BotKind.Unknown;
771
+ }
772
+ }
773
+
774
+ function detectNotificationPermissions(_a) {
775
+ var notificationPermissions = _a.notificationPermissions, browserKind = _a.browserKind;
776
+ if (browserKind.state !== 0 /* State.Success */ || browserKind.value !== "chrome" /* BrowserKind.Chrome */)
777
+ return false;
778
+ if (notificationPermissions.state === 0 /* State.Success */ && notificationPermissions.value) {
779
+ return BotKind.HeadlessChrome;
780
+ }
781
+ }
782
+
783
+ function detectPluginsArray(_a) {
784
+ var pluginsArray = _a.pluginsArray;
785
+ if (pluginsArray.state === 0 /* State.Success */ && !pluginsArray.value)
786
+ return BotKind.HeadlessChrome;
787
+ }
788
+
789
+ function detectPluginsLengthInconsistency(_a) {
790
+ var pluginsLength = _a.pluginsLength, android = _a.android, browserKind = _a.browserKind, browserEngineKind = _a.browserEngineKind;
791
+ if (pluginsLength.state !== 0 /* State.Success */ ||
792
+ android.state !== 0 /* State.Success */ ||
793
+ browserKind.state !== 0 /* State.Success */ ||
794
+ browserEngineKind.state !== 0 /* State.Success */)
795
+ return;
796
+ if (browserKind.value !== "chrome" /* BrowserKind.Chrome */ ||
797
+ android.value ||
798
+ browserEngineKind.value !== "chromium" /* BrowserEngineKind.Chromium */)
799
+ return;
800
+ if (pluginsLength.value === 0)
801
+ return BotKind.HeadlessChrome;
802
+ }
803
+
804
+ function detectProcess(_a) {
805
+ var _b;
806
+ var process = _a.process;
807
+ if (process.state !== 0 /* State.Success */)
808
+ return false;
809
+ if (process.value.type === 'renderer' || ((_b = process.value.versions) === null || _b === void 0 ? void 0 : _b.electron) != null)
810
+ return BotKind.Electron;
811
+ }
812
+
813
+ function detectProductSub(_a) {
814
+ var productSub = _a.productSub, browserKind = _a.browserKind;
815
+ if (productSub.state !== 0 /* State.Success */ || browserKind.state !== 0 /* State.Success */)
816
+ return false;
817
+ if ((browserKind.value === "chrome" /* BrowserKind.Chrome */ ||
818
+ browserKind.value === "safari" /* BrowserKind.Safari */ ||
819
+ browserKind.value === "opera" /* BrowserKind.Opera */ ||
820
+ browserKind.value === "wechat" /* BrowserKind.WeChat */) &&
821
+ productSub.value !== '20030107')
822
+ return BotKind.Unknown;
823
+ }
824
+
825
+ function detectUserAgent(_a) {
826
+ var userAgent = _a.userAgent;
827
+ if (userAgent.state !== 0 /* State.Success */)
828
+ return false;
829
+ if (/PhantomJS/i.test(userAgent.value))
830
+ return BotKind.PhantomJS;
831
+ if (/Headless/i.test(userAgent.value))
832
+ return BotKind.HeadlessChrome;
833
+ if (/Electron/i.test(userAgent.value))
834
+ return BotKind.Electron;
835
+ if (/slimerjs/i.test(userAgent.value))
836
+ return BotKind.SlimerJS;
837
+ }
838
+
839
+ function detectWebDriver(_a) {
840
+ var webDriver = _a.webDriver;
841
+ if (webDriver.state === 0 /* State.Success */ && webDriver.value)
842
+ return BotKind.HeadlessChrome;
843
+ }
844
+
845
+ function detectWebGL(_a) {
846
+ var webGL = _a.webGL;
847
+ if (webGL.state === 0 /* State.Success */) {
848
+ var _b = webGL.value, vendor = _b.vendor, renderer = _b.renderer;
849
+ if (vendor == 'Brian Paul' && renderer == 'Mesa OffScreen') {
850
+ return BotKind.HeadlessChrome;
851
+ }
852
+ }
853
+ }
854
+
855
+ function detectWindowExternal(_a) {
856
+ var windowExternal = _a.windowExternal;
857
+ if (windowExternal.state !== 0 /* State.Success */)
858
+ return false;
859
+ if (/Sequentum/i.test(windowExternal.value))
860
+ return BotKind.Sequentum;
861
+ }
862
+
863
+ function detectWindowSize(_a) {
864
+ var windowSize = _a.windowSize, documentFocus = _a.documentFocus;
865
+ if (windowSize.state !== 0 /* State.Success */ || documentFocus.state !== 0 /* State.Success */)
866
+ return false;
867
+ var _b = windowSize.value, outerWidth = _b.outerWidth, outerHeight = _b.outerHeight;
868
+ // When a page is opened in a new tab without focusing it right away, the window outer size is 0x0
869
+ if (!documentFocus.value)
870
+ return;
871
+ if (outerWidth === 0 && outerHeight === 0)
872
+ return BotKind.HeadlessChrome;
873
+ }
874
+
875
+ function detectDistinctiveProperties(_a) {
876
+ var distinctiveProps = _a.distinctiveProps;
877
+ if (distinctiveProps.state !== 0 /* State.Success */)
878
+ return false;
879
+ var value = distinctiveProps.value;
880
+ var bot;
881
+ for (bot in value)
882
+ if (value[bot])
883
+ return bot;
884
+ }
885
+
886
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
887
+ var detectors = {
888
+ detectAppVersion: detectAppVersion,
889
+ detectDocumentAttributes: detectDocumentAttributes,
890
+ detectErrorTrace: detectErrorTrace,
891
+ detectEvalLengthInconsistency: detectEvalLengthInconsistency,
892
+ detectFunctionBind: detectFunctionBind,
893
+ detectLanguagesLengthInconsistency: detectLanguagesLengthInconsistency,
894
+ detectNotificationPermissions: detectNotificationPermissions,
895
+ detectPluginsArray: detectPluginsArray,
896
+ detectPluginsLengthInconsistency: detectPluginsLengthInconsistency,
897
+ detectProcess: detectProcess,
898
+ detectUserAgent: detectUserAgent,
899
+ detectWebDriver: detectWebDriver,
900
+ detectWebGL: detectWebGL,
901
+ detectWindowExternal: detectWindowExternal,
902
+ detectWindowSize: detectWindowSize,
903
+ detectMimeTypesConsistent: detectMimeTypesConsistent,
904
+ detectProductSub: detectProductSub,
905
+ detectDistinctiveProperties: detectDistinctiveProperties,
906
+ };
907
+
908
+ function getAppVersion() {
909
+ var appVersion = navigator.appVersion;
910
+ if (appVersion == undefined) {
911
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.appVersion is undefined');
912
+ }
913
+ return appVersion;
914
+ }
915
+
916
+ function getDocumentElementKeys() {
917
+ if (document.documentElement === undefined) {
918
+ throw new BotdError(-1 /* State.Undefined */, 'document.documentElement is undefined');
919
+ }
920
+ var documentElement = document.documentElement;
921
+ if (typeof documentElement.getAttributeNames !== 'function') {
922
+ throw new BotdError(-2 /* State.NotFunction */, 'document.documentElement.getAttributeNames is not a function');
923
+ }
924
+ return documentElement.getAttributeNames();
925
+ }
926
+
927
+ function getErrorTrace() {
928
+ try {
929
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
930
+ // @ts-ignore
931
+ null[0]();
932
+ }
933
+ catch (error) {
934
+ if (error instanceof Error && error['stack'] != null) {
935
+ return error.stack.toString();
936
+ }
937
+ }
938
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'errorTrace signal unexpected behaviour');
939
+ }
940
+
941
+ function getEvalLength() {
942
+ return eval.toString().length;
943
+ }
944
+
945
+ function getFunctionBind() {
946
+ if (Function.prototype.bind === undefined) {
947
+ throw new BotdError(-2 /* State.NotFunction */, 'Function.prototype.bind is undefined');
948
+ }
949
+ return Function.prototype.bind.toString();
950
+ }
951
+
952
+ function getBrowserEngineKind() {
953
+ var _a, _b;
954
+ // Based on research in October 2020. Tested to detect Chromium 42-86.
955
+ var w = window;
956
+ var n = navigator;
957
+ if (countTruthy([
958
+ 'webkitPersistentStorage' in n,
959
+ 'webkitTemporaryStorage' in n,
960
+ n.vendor.indexOf('Google') === 0,
961
+ 'webkitResolveLocalFileSystemURL' in w,
962
+ 'BatteryManager' in w,
963
+ 'webkitMediaStream' in w,
964
+ 'webkitSpeechGrammar' in w,
965
+ ]) >= 5) {
966
+ return "chromium" /* BrowserEngineKind.Chromium */;
967
+ }
968
+ if (countTruthy([
969
+ 'ApplePayError' in w,
970
+ 'CSSPrimitiveValue' in w,
971
+ 'Counter' in w,
972
+ n.vendor.indexOf('Apple') === 0,
973
+ 'getStorageUpdates' in n,
974
+ 'WebKitMediaKeys' in w,
975
+ ]) >= 4) {
976
+ return "webkit" /* BrowserEngineKind.Webkit */;
977
+ }
978
+ if (countTruthy([
979
+ 'buildID' in navigator,
980
+ 'MozAppearance' in ((_b = (_a = document.documentElement) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : {}),
981
+ 'onmozfullscreenchange' in w,
982
+ 'mozInnerScreenX' in w,
983
+ 'CSSMozDocumentRule' in w,
984
+ 'CanvasCaptureMediaStream' in w,
985
+ ]) >= 4) {
986
+ return "gecko" /* BrowserEngineKind.Gecko */;
987
+ }
988
+ return "unknown" /* BrowserEngineKind.Unknown */;
989
+ }
990
+ function getBrowserKind() {
991
+ var _a;
992
+ var userAgent = (_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.toLowerCase();
993
+ if (strIncludes(userAgent, 'edg/')) {
994
+ return "edge" /* BrowserKind.Edge */;
995
+ }
996
+ else if (strIncludes(userAgent, 'trident') || strIncludes(userAgent, 'msie')) {
997
+ return "internet_explorer" /* BrowserKind.IE */;
998
+ }
999
+ else if (strIncludes(userAgent, 'wechat')) {
1000
+ return "wechat" /* BrowserKind.WeChat */;
1001
+ }
1002
+ else if (strIncludes(userAgent, 'firefox')) {
1003
+ return "firefox" /* BrowserKind.Firefox */;
1004
+ }
1005
+ else if (strIncludes(userAgent, 'opera') || strIncludes(userAgent, 'opr')) {
1006
+ return "opera" /* BrowserKind.Opera */;
1007
+ }
1008
+ else if (strIncludes(userAgent, 'chrome')) {
1009
+ return "chrome" /* BrowserKind.Chrome */;
1010
+ }
1011
+ else if (strIncludes(userAgent, 'safari')) {
1012
+ return "safari" /* BrowserKind.Safari */;
1013
+ }
1014
+ else {
1015
+ return "unknown" /* BrowserKind.Unknown */;
1016
+ }
1017
+ }
1018
+ // Source: https://github.com/fingerprintjs/fingerprintjs/blob/master/src/utils/browser.ts#L223
1019
+ function isAndroid() {
1020
+ var browserEngineKind = getBrowserEngineKind();
1021
+ var isItChromium = browserEngineKind === "chromium" /* BrowserEngineKind.Chromium */;
1022
+ var isItGecko = browserEngineKind === "gecko" /* BrowserEngineKind.Gecko */;
1023
+ // Only 2 browser engines are presented on Android.
1024
+ // Actually, there is also Android 4.1 browser, but it's not worth detecting it at the moment.
1025
+ if (!isItChromium && !isItGecko)
1026
+ return false;
1027
+ var w = window;
1028
+ // Chrome removes all words "Android" from `navigator` when desktop version is requested
1029
+ // Firefox keeps "Android" in `navigator.appVersion` when desktop version is requested
1030
+ return (countTruthy([
1031
+ 'onorientationchange' in w,
1032
+ 'orientation' in w,
1033
+ isItChromium && !('SharedWorker' in w),
1034
+ isItGecko && /android/i.test(navigator.appVersion),
1035
+ ]) >= 2);
1036
+ }
1037
+ function getDocumentFocus() {
1038
+ if (document.hasFocus === undefined) {
1039
+ return false;
1040
+ }
1041
+ return document.hasFocus();
1042
+ }
1043
+ function isChromium86OrNewer() {
1044
+ // Checked in Chrome 85 vs Chrome 86 both on desktop and Android
1045
+ var w = window;
1046
+ return (countTruthy([
1047
+ !('MediaSettingsRange' in w),
1048
+ 'RTCEncodedAudioFrame' in w,
1049
+ '' + w.Intl === '[object Intl]',
1050
+ '' + w.Reflect === '[object Reflect]',
1051
+ ]) >= 3);
1052
+ }
1053
+
1054
+ function getLanguages() {
1055
+ var n = navigator;
1056
+ var result = [];
1057
+ var language = n.language || n.userLanguage || n.browserLanguage || n.systemLanguage;
1058
+ if (language !== undefined) {
1059
+ result.push([language]);
1060
+ }
1061
+ if (Array.isArray(n.languages)) {
1062
+ var browserEngine = getBrowserEngineKind();
1063
+ // Starting from Chromium 86, there is only a single value in `navigator.language` in Incognito mode:
1064
+ // the value of `navigator.language`. Therefore, the value is ignored in this browser.
1065
+ if (!(browserEngine === "chromium" /* BrowserEngineKind.Chromium */ && isChromium86OrNewer())) {
1066
+ result.push(n.languages);
1067
+ }
1068
+ }
1069
+ else if (typeof n.languages === 'string') {
1070
+ var languages = n.languages;
1071
+ if (languages) {
1072
+ result.push(languages.split(','));
1073
+ }
1074
+ }
1075
+ return result;
1076
+ }
1077
+
1078
+ function areMimeTypesConsistent() {
1079
+ if (navigator.mimeTypes === undefined) {
1080
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.mimeTypes is undefined');
1081
+ }
1082
+ var mimeTypes = navigator.mimeTypes;
1083
+ var isConsistent = Object.getPrototypeOf(mimeTypes) === MimeTypeArray.prototype;
1084
+ for (var i = 0; i < mimeTypes.length; i++) {
1085
+ isConsistent && (isConsistent = Object.getPrototypeOf(mimeTypes[i]) === MimeType.prototype);
1086
+ }
1087
+ return isConsistent;
1088
+ }
1089
+
1090
+ function getNotificationPermissions() {
1091
+ return __awaiter(this, void 0, void 0, function () {
1092
+ var permissions, permissionStatus;
1093
+ return __generator(this, function (_a) {
1094
+ switch (_a.label) {
1095
+ case 0:
1096
+ if (window.Notification === undefined) {
1097
+ throw new BotdError(-1 /* State.Undefined */, 'window.Notification is undefined');
1098
+ }
1099
+ if (navigator.permissions === undefined) {
1100
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.permissions is undefined');
1101
+ }
1102
+ permissions = navigator.permissions;
1103
+ if (typeof permissions.query !== 'function') {
1104
+ throw new BotdError(-2 /* State.NotFunction */, 'navigator.permissions.query is not a function');
1105
+ }
1106
+ _a.label = 1;
1107
+ case 1:
1108
+ _a.trys.push([1, 3, , 4]);
1109
+ return [4 /*yield*/, permissions.query({ name: 'notifications' })];
1110
+ case 2:
1111
+ permissionStatus = _a.sent();
1112
+ return [2 /*return*/, window.Notification.permission === 'denied' && permissionStatus.state === 'prompt'];
1113
+ case 3:
1114
+ _a.sent();
1115
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'notificationPermissions signal unexpected behaviour');
1116
+ case 4: return [2 /*return*/];
1117
+ }
1118
+ });
1119
+ });
1120
+ }
1121
+
1122
+ function getPluginsArray() {
1123
+ if (navigator.plugins === undefined) {
1124
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.plugins is undefined');
1125
+ }
1126
+ if (window.PluginArray === undefined) {
1127
+ throw new BotdError(-1 /* State.Undefined */, 'window.PluginArray is undefined');
1128
+ }
1129
+ return navigator.plugins instanceof PluginArray;
1130
+ }
1131
+
1132
+ function getPluginsLength() {
1133
+ if (navigator.plugins === undefined) {
1134
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.plugins is undefined');
1135
+ }
1136
+ if (navigator.plugins.length === undefined) {
1137
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'navigator.plugins.length is undefined');
1138
+ }
1139
+ return navigator.plugins.length;
1140
+ }
1141
+
1142
+ function getProcess() {
1143
+ var process = window.process;
1144
+ var errorPrefix = 'window.process is';
1145
+ if (process === undefined) {
1146
+ throw new BotdError(-1 /* State.Undefined */, "".concat(errorPrefix, " undefined"));
1147
+ }
1148
+ if (process && typeof process !== 'object') {
1149
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, "".concat(errorPrefix, " not an object"));
1150
+ }
1151
+ return process;
1152
+ }
1153
+
1154
+ function getProductSub() {
1155
+ var productSub = navigator.productSub;
1156
+ if (productSub === undefined) {
1157
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.productSub is undefined');
1158
+ }
1159
+ return productSub;
1160
+ }
1161
+
1162
+ function getRTT() {
1163
+ if (navigator.connection === undefined) {
1164
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.connection is undefined');
1165
+ }
1166
+ if (navigator.connection.rtt === undefined) {
1167
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.connection.rtt is undefined');
1168
+ }
1169
+ return navigator.connection.rtt;
1170
+ }
1171
+
1172
+ function getUserAgent() {
1173
+ return navigator.userAgent;
1174
+ }
1175
+
1176
+ function getWebDriver() {
1177
+ if (navigator.webdriver == undefined) {
1178
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.webdriver is undefined');
1179
+ }
1180
+ return navigator.webdriver;
1181
+ }
1182
+
1183
+ function getWebGL() {
1184
+ var canvasElement = document.createElement('canvas');
1185
+ if (typeof canvasElement.getContext !== 'function') {
1186
+ throw new BotdError(-2 /* State.NotFunction */, 'HTMLCanvasElement.getContext is not a function');
1187
+ }
1188
+ var webGLContext = canvasElement.getContext('webgl');
1189
+ if (webGLContext === null) {
1190
+ throw new BotdError(-4 /* State.Null */, 'WebGLRenderingContext is null');
1191
+ }
1192
+ if (typeof webGLContext.getParameter !== 'function') {
1193
+ throw new BotdError(-2 /* State.NotFunction */, 'WebGLRenderingContext.getParameter is not a function');
1194
+ }
1195
+ var vendor = webGLContext.getParameter(webGLContext.VENDOR);
1196
+ var renderer = webGLContext.getParameter(webGLContext.RENDERER);
1197
+ return { vendor: vendor, renderer: renderer };
1198
+ }
1199
+
1200
+ function getWindowExternal() {
1201
+ if (window.external === undefined) {
1202
+ throw new BotdError(-1 /* State.Undefined */, 'window.external is undefined');
1203
+ }
1204
+ var external = window.external;
1205
+ if (typeof external.toString !== 'function') {
1206
+ throw new BotdError(-2 /* State.NotFunction */, 'window.external.toString is not a function');
1207
+ }
1208
+ return external.toString();
1209
+ }
1210
+
1211
+ function getWindowSize() {
1212
+ return {
1213
+ outerWidth: window.outerWidth,
1214
+ outerHeight: window.outerHeight,
1215
+ innerWidth: window.innerWidth,
1216
+ innerHeight: window.innerHeight,
1217
+ };
1218
+ }
1219
+
1220
+ function checkDistinctiveProperties() {
1221
+ var _a;
1222
+ // The order in the following list matters, because specific types of bots come first, followed by automation technologies.
1223
+ var distinctivePropsList = (_a = {},
1224
+ _a[BotKind.Awesomium] = {
1225
+ window: ['awesomium'],
1226
+ },
1227
+ _a[BotKind.Cef] = {
1228
+ window: ['RunPerfTest'],
1229
+ },
1230
+ _a[BotKind.CefSharp] = {
1231
+ window: ['CefSharp'],
1232
+ },
1233
+ _a[BotKind.CoachJS] = {
1234
+ window: ['emit'],
1235
+ },
1236
+ _a[BotKind.FMiner] = {
1237
+ window: ['fmget_targets'],
1238
+ },
1239
+ _a[BotKind.Geb] = {
1240
+ window: ['geb'],
1241
+ },
1242
+ _a[BotKind.NightmareJS] = {
1243
+ window: ['__nightmare', 'nightmare'],
1244
+ },
1245
+ _a[BotKind.Phantomas] = {
1246
+ window: ['__phantomas'],
1247
+ },
1248
+ _a[BotKind.PhantomJS] = {
1249
+ window: ['callPhantom', '_phantom'],
1250
+ },
1251
+ _a[BotKind.Rhino] = {
1252
+ window: ['spawn'],
1253
+ },
1254
+ _a[BotKind.Selenium] = {
1255
+ window: ['_Selenium_IDE_Recorder', '_selenium', 'calledSelenium', /^([a-z]){3}_.*_(Array|Promise|Symbol)$/],
1256
+ document: ['__selenium_evaluate', 'selenium-evaluate', '__selenium_unwrapped'],
1257
+ },
1258
+ _a[BotKind.WebDriverIO] = {
1259
+ window: ['wdioElectron'],
1260
+ },
1261
+ _a[BotKind.WebDriver] = {
1262
+ window: [
1263
+ 'webdriver',
1264
+ '__webdriverFunc',
1265
+ '__lastWatirAlert',
1266
+ '__lastWatirConfirm',
1267
+ '__lastWatirPrompt',
1268
+ '_WEBDRIVER_ELEM_CACHE',
1269
+ 'ChromeDriverw',
1270
+ ],
1271
+ document: [
1272
+ '__webdriver_script_fn',
1273
+ '__driver_evaluate',
1274
+ '__webdriver_evaluate',
1275
+ '__fxdriver_evaluate',
1276
+ '__driver_unwrapped',
1277
+ '__webdriver_unwrapped',
1278
+ '__fxdriver_unwrapped',
1279
+ '__webdriver_script_fn',
1280
+ '__webdriver_script_func',
1281
+ '__webdriver_script_function',
1282
+ '$cdc_asdjflasutopfhvcZLmcf',
1283
+ '$cdc_asdjflasutopfhvcZLmcfl_',
1284
+ '$chrome_asyncScriptInfo',
1285
+ '__$webdriverAsyncExecutor',
1286
+ ],
1287
+ },
1288
+ _a[BotKind.HeadlessChrome] = {
1289
+ window: ['domAutomation', 'domAutomationController'],
1290
+ },
1291
+ _a);
1292
+ var botName;
1293
+ var result = {};
1294
+ var windowProps = getObjectProps(window);
1295
+ var documentProps = [];
1296
+ if (window.document !== undefined)
1297
+ documentProps = getObjectProps(window.document);
1298
+ for (botName in distinctivePropsList) {
1299
+ var props = distinctivePropsList[botName];
1300
+ if (props !== undefined) {
1301
+ var windowContains = props.window === undefined ? false : includes.apply(void 0, __spreadArray([windowProps], props.window, false));
1302
+ var documentContains = props.document === undefined || !documentProps.length ? false : includes.apply(void 0, __spreadArray([documentProps], props.document, false));
1303
+ result[botName] = windowContains || documentContains;
1304
+ }
1305
+ }
1306
+ return result;
1307
+ }
1308
+
1309
+ var sources = {
1310
+ android: isAndroid,
1311
+ browserKind: getBrowserKind,
1312
+ browserEngineKind: getBrowserEngineKind,
1313
+ documentFocus: getDocumentFocus,
1314
+ userAgent: getUserAgent,
1315
+ appVersion: getAppVersion,
1316
+ rtt: getRTT,
1317
+ windowSize: getWindowSize,
1318
+ pluginsLength: getPluginsLength,
1319
+ pluginsArray: getPluginsArray,
1320
+ errorTrace: getErrorTrace,
1321
+ productSub: getProductSub,
1322
+ windowExternal: getWindowExternal,
1323
+ mimeTypesConsistent: areMimeTypesConsistent,
1324
+ evalLength: getEvalLength,
1325
+ webGL: getWebGL,
1326
+ webDriver: getWebDriver,
1327
+ languages: getLanguages,
1328
+ notificationPermissions: getNotificationPermissions,
1329
+ documentElementKeys: getDocumentElementKeys,
1330
+ functionBind: getFunctionBind,
1331
+ process: getProcess,
1332
+ distinctiveProps: checkDistinctiveProperties,
1333
+ };
1334
+
1335
+ /**
1336
+ * Class representing a bot detector.
1337
+ *
1338
+ * @class
1339
+ * @implements {BotDetectorInterface}
1340
+ */
1341
+ var BotDetector = /** @class */ (function () {
1342
+ function BotDetector() {
1343
+ this.components = undefined;
1344
+ this.detections = undefined;
1345
+ }
1346
+ BotDetector.prototype.getComponents = function () {
1347
+ return this.components;
1348
+ };
1349
+ BotDetector.prototype.getDetections = function () {
1350
+ return this.detections;
1351
+ };
1352
+ /**
1353
+ * @inheritdoc
1354
+ */
1355
+ BotDetector.prototype.detect = function () {
1356
+ if (this.components === undefined) {
1357
+ throw new Error("BotDetector.detect can't be called before BotDetector.collect");
1358
+ }
1359
+ var _a = detect(this.components, detectors), detections = _a[0], finalDetection = _a[1];
1360
+ this.detections = detections;
1361
+ return finalDetection;
1362
+ };
1363
+ /**
1364
+ * @inheritdoc
1365
+ */
1366
+ BotDetector.prototype.collect = function () {
1367
+ return __awaiter(this, void 0, void 0, function () {
1368
+ var _a;
1369
+ return __generator(this, function (_b) {
1370
+ switch (_b.label) {
1371
+ case 0:
1372
+ _a = this;
1373
+ return [4 /*yield*/, collect(sources)];
1374
+ case 1:
1375
+ _a.components = _b.sent();
1376
+ return [2 /*return*/, this.components];
1377
+ }
1378
+ });
1379
+ });
1380
+ };
1381
+ return BotDetector;
1382
+ }());
1383
+
1384
+ /**
1385
+ * Sends an unpersonalized AJAX request to collect installation statistics
1386
+ */
1387
+ function monitor() {
1388
+ // The FingerprintJS CDN (https://github.com/fingerprintjs/cdn) replaces `window.__fpjs_d_m` with `true`
1389
+ if (window.__fpjs_d_m || Math.random() >= 0.001) {
1390
+ return;
1391
+ }
1392
+ try {
1393
+ var request = new XMLHttpRequest();
1394
+ request.open('get', "https://m1.openfpcdn.io/botd/v".concat(version, "/npm-monitoring"), true);
1395
+ request.send();
1396
+ }
1397
+ catch (error) {
1398
+ // console.error is ok here because it's an unexpected error handler
1399
+ // eslint-disable-next-line no-console
1400
+ console.error(error);
1401
+ }
1402
+ }
1403
+ function load(_a) {
1404
+ var _b = _a === void 0 ? {} : _a, _c = _b.monitoring, monitoring = _c === void 0 ? true : _c;
1405
+ return __awaiter(this, void 0, void 0, function () {
1406
+ var detector;
1407
+ return __generator(this, function (_d) {
1408
+ switch (_d.label) {
1409
+ case 0:
1410
+ if (monitoring) {
1411
+ monitor();
1412
+ }
1413
+ detector = new BotDetector();
1414
+ return [4 /*yield*/, detector.collect()];
1415
+ case 1:
1416
+ _d.sent();
1417
+ return [2 /*return*/, detector];
1418
+ }
1419
+ });
1420
+ });
1421
+ }
1422
+
1423
+ const useBotDetection = () => {
1424
+ const [result, setResult] = react.useState({
1425
+ isBot: false,
1426
+ reasons: []
1427
+ });
1428
+ react.useEffect(() => {
1429
+ const detectBot = async () => {
1430
+ try {
1431
+ // Load and initialize BotD
1432
+ const botd = await load();
1433
+ const detectionResult = await botd.detect();
1434
+ const isBot = detectionResult.bot;
1435
+ const reasons = isBot ? ['FingerprintJS BotD detected automation'] : [];
1436
+ setResult({
1437
+ isBot,
1438
+ reasons
1439
+ });
1440
+ }
1441
+ catch (error) {
1442
+ // If BotD fails to load, assume human user (fail open for better UX)
1443
+ console.warn('BotD detection failed, assuming human user:', error);
1444
+ setResult({
1445
+ isBot: false,
1446
+ reasons: ['BotD failed to load - assuming human']
1447
+ });
1448
+ }
1449
+ };
1450
+ detectBot();
1451
+ }, []);
1452
+ return result;
1453
+ };
1454
+
1455
+ const useViewability = (options = {}) => {
1456
+ const { threshold = 0.5, durationMs = 1000, onImpressionTracked } = options;
1457
+ const elementRef = react.useRef(null);
1458
+ const [isViewable, setIsViewable] = react.useState(false);
1459
+ const [hasBeenViewed, setHasBeenViewed] = react.useState(false);
1460
+ const [impressionTracked, setImpressionTracked] = react.useState(false);
1461
+ const [viewableStartTime, setViewableStartTime] = react.useState(null);
1462
+ const [hasMetDuration, setHasMetDuration] = react.useState(false);
1463
+ react.useEffect(() => {
1464
+ if (!elementRef.current)
1465
+ return;
1466
+ const observer = new IntersectionObserver(([entry]) => {
1467
+ const viewable = entry.intersectionRatio >= threshold;
1468
+ const now = Date.now();
1469
+ if (viewable) {
1470
+ if (viewableStartTime === null) {
1471
+ setViewableStartTime(now);
1472
+ }
1473
+ setIsViewable(true);
1474
+ if (!hasBeenViewed) {
1475
+ setHasBeenViewed(true);
1476
+ }
1477
+ }
1478
+ else {
1479
+ setViewableStartTime(null);
1480
+ setIsViewable(false);
1481
+ setHasMetDuration(false);
1482
+ }
1483
+ }, {
1484
+ threshold,
1485
+ rootMargin: '0px'
1486
+ });
1487
+ observer.observe(elementRef.current);
1488
+ return () => observer.disconnect();
1489
+ }, [threshold, hasBeenViewed, viewableStartTime]);
1490
+ // Duration tracking effect
1491
+ react.useEffect(() => {
1492
+ if (!isViewable || viewableStartTime === null || hasMetDuration)
1493
+ return;
1494
+ const timeoutId = setTimeout(() => {
1495
+ if (isViewable && viewableStartTime !== null) {
1496
+ const elapsed = Date.now() - viewableStartTime;
1497
+ if (elapsed >= durationMs) {
1498
+ setHasMetDuration(true);
1499
+ }
1500
+ }
1501
+ }, durationMs);
1502
+ return () => clearTimeout(timeoutId);
1503
+ }, [isViewable, viewableStartTime, durationMs, hasMetDuration]);
1504
+ const trackImpression = (adId) => {
1505
+ if (impressionTracked)
1506
+ return;
1507
+ setImpressionTracked(true);
1508
+ if (onImpressionTracked && adId) {
1509
+ onImpressionTracked(adId);
1510
+ }
1511
+ };
1512
+ return {
1513
+ elementRef,
1514
+ isViewable: isViewable && hasMetDuration, // MRC-compliant viewability (with duration)
1515
+ isInstantViewable: isViewable, // Instant viewability (no duration requirement)
1516
+ hasBeenViewed,
1517
+ impressionTracked,
1518
+ trackImpression
1519
+ };
1520
+ };
1521
+
1522
+ // Font definitions
1523
+ const fonts = {
1524
+ 'san-serif': {
1525
+ primary: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1526
+ secondary: 'system-ui, -apple-system, sans-serif'
1527
+ },
1528
+ 'serif': {
1529
+ primary: 'Georgia, "Times New Roman", Times, serif',
1530
+ secondary: '"Hoefler Text", "Baskerville Old Face", Garamond, serif'
1531
+ },
1532
+ 'monospace': {
1533
+ primary: '"SF Mono", Monaco, "Inconsolata", "Roboto Mono", "Source Code Pro", Consolas, "Courier New", monospace',
1534
+ secondary: 'ui-monospace, Menlo, monospace'
1535
+ }
1536
+ };
1537
+ // Modal color themes - only two options based on theme mode
1538
+ const colorCombinations = {
1539
+ 'light': {
1540
+ background: '#ffffff',
1541
+ text: '#1e293b',
1542
+ primary: '#3b82f6',
1543
+ primaryHover: '#2563eb',
1544
+ border: '#e2e8f0'
1545
+ },
1546
+ 'dark': {
1547
+ background: '#0f172a',
1548
+ text: '#f1f5f9',
1549
+ primary: '#60a5fa',
1550
+ primaryHover: '#3b82f6',
1551
+ border: '#475569'
1552
+ }
1553
+ };
1554
+ // Function to get the appropriate theme based on theme mode
1555
+ // Accent is ignored - modal always uses consistent colors based on light/dark theme
1556
+ function getColorTheme(themeMode, _accent) {
1557
+ // Handle auto theme by detecting system preference
1558
+ let resolvedTheme = themeMode;
1559
+ if (themeMode === 'auto') {
1560
+ resolvedTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1561
+ }
1562
+ // Return the theme color, fallback to light
1563
+ return colorCombinations[resolvedTheme] || colorCombinations['light'];
1564
+ }
1565
+ // Helper functions for styling
1566
+ const getSolidBackground = (colors) => {
1567
+ return colors.background;
1568
+ };
1569
+ const getBackgroundGradient = (colors) => {
1570
+ // No longer using gradients, just return solid background
1571
+ return colors.background;
1572
+ };
1573
+ const getTextMuted = (colors) => {
1574
+ // Use a slightly muted version of the text color
1575
+ return colors.text + 'aa'; // Add alpha for slight transparency
1576
+ };
1577
+ const getTextSecondary = (colors) => {
1578
+ // Not used anymore, but keeping for compatibility
1579
+ return colors.text;
1580
+ };
1581
+ const getBorderLight = (colors) => {
1582
+ return colors.border;
1583
+ };
1584
+ const getShadow = (colors) => {
1585
+ return 'rgba(0, 0, 0, 0.1)';
1586
+ };
1587
+ const getFontStyles = (font = 'san-serif') => {
1588
+ return fonts[font] || fonts['san-serif'];
1589
+ };
1590
+
1591
+ const createInChatAdSlotCSS = (theme = {}) => {
1592
+ const { theme: themeMode = 'light', accent = 'blue', font = 'san-serif', width = 'auto', } = theme;
1593
+ // If accent or font is an array, use the first value for styling
1594
+ Array.isArray(accent) ? accent[0] : accent;
1595
+ const effectiveFont = Array.isArray(font) ? font[0] : font;
1596
+ const colors = getColorTheme(themeMode);
1597
+ const fonts = getFontStyles(effectiveFont);
1598
+ // Fixed dimensions
1599
+ const minWidth = 320;
1600
+ const fixedHeight = 265;
1601
+ // Handle width calculation
1602
+ const getWidthCSS = () => {
1603
+ if (width === 'auto') {
1604
+ return '100%';
1605
+ }
1606
+ if (typeof width === 'string') {
1607
+ // For percentage values and other CSS units, use as-is
1608
+ return width;
1609
+ }
1610
+ return `${width}px`;
1611
+ };
1612
+ return `
1613
+ .simula-content-slot {
1614
+ width: ${getWidthCSS()};
1615
+ min-width: ${minWidth}px;
1616
+ height: ${fixedHeight}px;
1617
+ margin: 0px;
1618
+ line-height: 1.5;
1619
+ position: relative;
1620
+ overflow: hidden;
1621
+ }
1622
+
1623
+ .simula-content-slot * {
1624
+ margin: 0;
1625
+ padding: 0;
1626
+ }
1627
+
1628
+ .simula-content-frame {
1629
+ width: 100%;
1630
+ height: 100%;
1631
+ border: none;
1632
+ outline: none;
1633
+ box-shadow: none;
1634
+ border-radius: 0;
1635
+ background: transparent;
1636
+ display: block;
1637
+ margin: 0;
1638
+ padding: 0;
1639
+ position: relative;
1640
+ vertical-align: top;
1641
+ }
1642
+
1643
+ .simula-info-icon {
1644
+ position: absolute;
1645
+ top: 12;
1646
+ right: 12px;
1647
+ background: none;
1648
+ border: none;
1649
+ color: ${colors.text};
1650
+ opacity: 0.5;
1651
+ cursor: pointer;
1652
+ padding: 6px;
1653
+ border-radius: 50%;
1654
+ transition: all 0.2s ease;
1655
+ z-index: 10;
1656
+ display: flex;
1657
+ align-items: center;
1658
+ justify-content: center;
1659
+ }
1660
+
1661
+ .simula-info-icon:hover {
1662
+ opacity: 1;
1663
+ transform: scale(1.1);
1664
+ }
1665
+
1666
+ .simula-modal-overlay {
1667
+ position: absolute;
1668
+ top: 0;
1669
+ left: 0;
1670
+ right: 0;
1671
+ bottom: 0;
1672
+ background: rgba(0, 0, 0, 0.5);
1673
+ display: flex;
1674
+ align-items: center;
1675
+ justify-content: center;
1676
+ z-index: 1000;
1677
+ }
1678
+
1679
+ .simula-modal-content {
1680
+ background: ${getSolidBackground(colors)};
1681
+ border-radius: 8px;
1682
+ padding: 16px;
1683
+ width: 75%;
1684
+ min-width: 200px;
1685
+ max-width: 431px;
1686
+ margin: 16px;
1687
+ position: relative;
1688
+ box-shadow: 0 4px 12px ${getShadow()};
1689
+ font-family: ${fonts.primary};
1690
+ line-height: 1.5;
1691
+ color: ${colors.text};
1692
+ font-size: 14px;
1693
+ border: 1px solid ${colors.border};
1694
+ }
1695
+
1696
+ .simula-modal-close {
1697
+ position: absolute;
1698
+ top: 8px;
1699
+ right: 12px;
1700
+ background: none;
1701
+ border: none;
1702
+ font-size: 24px;
1703
+ cursor: pointer;
1704
+ color: ${getTextMuted(colors)};
1705
+ padding: 4px;
1706
+ line-height: 1;
1707
+ }
1708
+
1709
+ .simula-modal-close:hover {
1710
+ color: ${colors.text};
1711
+ }
1712
+
1713
+ .simula-modal-link {
1714
+ color: ${colors.primary};
1715
+ text-decoration: underline;
1716
+ }
1717
+
1718
+ .simula-modal-link:hover {
1719
+ color: ${colors.primaryHover};
1720
+ text-decoration: underline;
1721
+ }
1722
+
1723
+ .simula-content-label {
1724
+ font-size: 10px;
1725
+ color: ${getTextMuted(colors)};
1726
+ text-transform: uppercase;
1727
+ letter-spacing: 0.05em;
1728
+ margin-bottom: 8px;
1729
+ display: block;
1730
+ }
1731
+ `;
1732
+ };
1733
+
1734
+ // Internal constant to prevent API abuse
1735
+ const MIN_FETCH_INTERVAL_MS = 1000; // 1 second minimum between fetches
1736
+ // Helper functions for width validation
1737
+ const isAutoWidth = (width) => width === 'auto';
1738
+ const isPercentageWidth = (width) => typeof width === 'string' && /^\d+(?:\.\d+)?%$/.test(width);
1739
+ const needsWidthMeasurement = (width) => isAutoWidth(width) || isPercentageWidth(width);
1740
+ // Validate messages array has actual content
1741
+ const hasValidMessages = (messages) => {
1742
+ return messages.length > 0 && messages.some(msg => msg && msg.content && msg.content.trim().length > 0);
1743
+ };
1744
+ const InChatAdSlot = (props) => {
1745
+ // Validate props early
1746
+ validateInChatAdSlotProps(props);
1747
+ const { messages, trigger, formats: formatsRaw = ['all'], theme = {}, debounceMs = 0, charDesc, onImpression, onClick, onError, } = props;
1748
+ // Normalize formats to array (similar to theme.accent and theme.font)
1749
+ const formats = Array.isArray(formatsRaw) ? formatsRaw : [formatsRaw];
1750
+ const { apiKey, sessionId } = useSimula();
1751
+ // Generate a stable slotId for this component instance
1752
+ const slotId = react.useMemo(() => `slot-${v4()}`, []);
1753
+ const [ad, setAd] = react.useState(null);
1754
+ const [loading, setLoading] = react.useState(false);
1755
+ const [error, setError] = react.useState(null);
1756
+ const [shouldFetch, setShouldFetch] = react.useState(false);
1757
+ const [showInfoModal, setShowInfoModal] = react.useState(false);
1758
+ const [iframeLoaded, setIframeLoaded] = react.useState(false);
1759
+ const [measuredWidth, setMeasuredWidth] = react.useState(null);
1760
+ // Track if this AdSlot has already been triggered
1761
+ const [hasTriggered, setHasTriggered] = react.useState(false);
1762
+ const [triggerUsed, setTriggerUsed] = react.useState(null);
1763
+ const lastFetchTimeRef = react.useRef(0);
1764
+ const styleElementRef = react.useRef(null);
1765
+ const { elementRef, hasBeenViewed, isViewable, // MRC-compliant (1 second duration)
1766
+ trackImpression: viewabilityTrackImpression } = useViewability({
1767
+ threshold: 0.5,
1768
+ durationMs: 1000, // 1 second for MRC compliance
1769
+ onImpressionTracked: (adId) => {
1770
+ // This gets called after impression tracking
1771
+ trackImpression(adId, apiKey);
1772
+ if (ad) {
1773
+ onImpression === null || onImpression === void 0 ? void 0 : onImpression(ad);
1774
+ }
1775
+ }
1776
+ });
1777
+ const { isBot, reasons } = useBotDetection();
1778
+ // Measure actual width once when element is ready
1779
+ react.useEffect(() => {
1780
+ const currentWidth = theme.width;
1781
+ // If no width is configured at all, skip measurement
1782
+ if (currentWidth === undefined)
1783
+ return;
1784
+ // Only measure if it's 'auto' or a percentage value
1785
+ const needsMeasurement = needsWidthMeasurement(currentWidth);
1786
+ if (!needsMeasurement)
1787
+ return;
1788
+ if (!elementRef.current)
1789
+ return;
1790
+ // Track if already measured to prevent re-observation
1791
+ let hasMeasured = false;
1792
+ // Use ResizeObserver to get the initial size
1793
+ const resizeObserver = new ResizeObserver(entries => {
1794
+ const entry = entries[0];
1795
+ if (entry && !hasMeasured) {
1796
+ hasMeasured = true;
1797
+ const width = Math.round(entry.contentRect.width);
1798
+ setMeasuredWidth(width);
1799
+ // Disconnect after first measurement
1800
+ resizeObserver.disconnect();
1801
+ }
1802
+ });
1803
+ resizeObserver.observe(elementRef.current);
1804
+ return () => {
1805
+ resizeObserver.disconnect();
1806
+ };
1807
+ }, [theme.width, elementRef]);
1808
+ const fetchAdData = react.useCallback(async () => {
1809
+ if (!hasBeenViewed || loading || hasTriggered || error) {
1810
+ return;
1811
+ }
1812
+ // Block if sessionId is missing or invalid
1813
+ if (!sessionId) {
1814
+ setError('Session invalid, fetch ad request blocked');
1815
+ console.error('Session invalid, fetch ad request blocked');
1816
+ return;
1817
+ }
1818
+ // Wait for width measurement when using auto or percentage values
1819
+ const currentWidth = theme.width;
1820
+ const needsMeasurement = needsWidthMeasurement(currentWidth);
1821
+ if (needsMeasurement && measuredWidth === null) {
1822
+ return;
1823
+ }
1824
+ // Block ad requests from detected bots
1825
+ if (isBot) {
1826
+ console.warn('Bot detected, blocking ad request:', { reasons });
1827
+ return;
1828
+ }
1829
+ const now = Date.now();
1830
+ if (now - lastFetchTimeRef.current < MIN_FETCH_INTERVAL_MS) {
1831
+ return;
1832
+ }
1833
+ setLoading(true);
1834
+ setError(null);
1835
+ lastFetchTimeRef.current = now;
1836
+ try {
1837
+ // Validate width before making API call
1838
+ const currentConfiguredWidth = theme.width;
1839
+ const minWidth = 320;
1840
+ // Check width bounds (numeric or px string)
1841
+ let widthValue = null;
1842
+ if (typeof currentConfiguredWidth === 'number') {
1843
+ widthValue = currentConfiguredWidth;
1844
+ }
1845
+ else if (typeof currentConfiguredWidth === 'string') {
1846
+ const pxMatch = currentConfiguredWidth.match(/^(\d+(?:\.\d+)?)px$/);
1847
+ if (pxMatch) {
1848
+ widthValue = parseFloat(pxMatch[1]);
1849
+ }
1850
+ }
1851
+ // Validate minimum width if we have a concrete value
1852
+ if (widthValue !== null && widthValue < minWidth) {
1853
+ console.error(`AdSlot width ${widthValue}px is below minimum ${minWidth}px. Skipping ad request.`);
1854
+ setError(`Invalid width: ${widthValue}px (minimum: ${minWidth}px)`);
1855
+ setLoading(false);
1856
+ return;
1857
+ }
1858
+ // Create theme with measured width for backend
1859
+ const themeForBackend = { ...theme };
1860
+ // Convert the current width to pixels for backend - always send as 'width'
1861
+ if (typeof currentConfiguredWidth === "string") {
1862
+ // Check if it's a px string like "400px"
1863
+ const pxMatch = currentConfiguredWidth.match(/^(\d+(?:\.\d+)?)px$/);
1864
+ if (pxMatch) {
1865
+ // Convert "400px" to 400
1866
+ themeForBackend.width = parseFloat(pxMatch[1]);
1867
+ }
1868
+ else if (measuredWidth !== null) {
1869
+ // For 'auto' and percentages, use measured width
1870
+ // If measured width is below minimum, use minimum
1871
+ const finalWidth = measuredWidth < minWidth ? minWidth : measuredWidth;
1872
+ themeForBackend.width = finalWidth;
1873
+ }
1874
+ }
1875
+ else if (typeof currentConfiguredWidth === "number") {
1876
+ // Already a number, just use it
1877
+ themeForBackend.width = currentConfiguredWidth;
1878
+ }
1879
+ const result = await fetchAd({
1880
+ messages,
1881
+ formats,
1882
+ apiKey,
1883
+ slotId,
1884
+ theme: themeForBackend,
1885
+ sessionId,
1886
+ charDesc,
1887
+ });
1888
+ if (result.error) {
1889
+ console.warn('🚫 Ad fetch failed:', result.error);
1890
+ setError(result.error);
1891
+ onError === null || onError === void 0 ? void 0 : onError(new Error(result.error));
1892
+ }
1893
+ else if (result.ad) {
1894
+ setAd(result.ad);
1895
+ // Mark as triggered - this AdSlot will never fetch again
1896
+ setHasTriggered(true);
1897
+ }
1898
+ else {
1899
+ console.warn('🚫 No ad returned from API - no fill or invalid response');
1900
+ setError('No ad available');
1901
+ onError === null || onError === void 0 ? void 0 : onError(new Error('No ad available'));
1902
+ }
1903
+ }
1904
+ catch (err) {
1905
+ const errorMessage = err instanceof Error ? err.message : 'Failed to fetch ad';
1906
+ setError(errorMessage);
1907
+ onError === null || onError === void 0 ? void 0 : onError(new Error(errorMessage));
1908
+ }
1909
+ finally {
1910
+ setLoading(false);
1911
+ }
1912
+ }, [hasBeenViewed, loading, hasTriggered, error, messages, formats, apiKey, slotId, theme, onError, isBot, reasons, sessionId, measuredWidth]);
1913
+ useDebounce(() => {
1914
+ if (shouldFetch) {
1915
+ fetchAdData();
1916
+ setShouldFetch(false);
1917
+ }
1918
+ }, debounceMs, [shouldFetch, fetchAdData]);
1919
+ // Handle trigger promise resolution/rejection
1920
+ react.useEffect(() => {
1921
+ if (hasTriggered)
1922
+ return;
1923
+ if (!trigger)
1924
+ return;
1925
+ if (trigger === triggerUsed)
1926
+ return;
1927
+ setTriggerUsed(trigger);
1928
+ trigger
1929
+ .then(() => {
1930
+ if (!hasTriggered) {
1931
+ setShouldFetch(true);
1932
+ }
1933
+ })
1934
+ .catch(() => {
1935
+ if (!hasTriggered) {
1936
+ setShouldFetch(true);
1937
+ }
1938
+ });
1939
+ }, [trigger, triggerUsed, hasTriggered]);
1940
+ // Consolidated trigger logic - checks all conditions before fetching
1941
+ react.useEffect(() => {
1942
+ // Don't trigger if already triggered
1943
+ if (hasTriggered)
1944
+ return;
1945
+ // Don't trigger if no valid messages
1946
+ if (!hasValidMessages(messages))
1947
+ return;
1948
+ // Don't trigger if waiting for trigger promise
1949
+ if (trigger && !triggerUsed)
1950
+ return;
1951
+ // Don't trigger if not viewed yet
1952
+ if (!hasBeenViewed)
1953
+ return;
1954
+ // Don't trigger if waiting for width measurement
1955
+ const needsMeasurement = typeof theme.width === "string" && needsWidthMeasurement(theme.width);
1956
+ if (needsMeasurement && measuredWidth === null)
1957
+ return;
1958
+ // All conditions met, trigger fetch
1959
+ setShouldFetch(true);
1960
+ }, [hasBeenViewed, hasTriggered, triggerUsed, trigger, measuredWidth, theme.width, messages]);
1961
+ react.useEffect(() => {
1962
+ if (ad && isViewable && !isBot && iframeLoaded) {
1963
+ viewabilityTrackImpression(ad.id); // Track impression when viewable AND iframe loaded
1964
+ }
1965
+ }, [ad, isViewable, isBot, iframeLoaded, viewabilityTrackImpression]);
1966
+ react.useEffect(() => {
1967
+ // Create style element once on mount
1968
+ if (!styleElementRef.current) {
1969
+ styleElementRef.current = document.createElement('style');
1970
+ styleElementRef.current.setAttribute('data-simula-styles', 'true');
1971
+ document.head.appendChild(styleElementRef.current);
1972
+ }
1973
+ // Update CSS content whenever theme changes
1974
+ const css = createInChatAdSlotCSS(theme);
1975
+ styleElementRef.current.textContent = css;
1976
+ // Cleanup only on unmount
1977
+ return () => {
1978
+ if (styleElementRef.current && document.head.contains(styleElementRef.current)) {
1979
+ document.head.removeChild(styleElementRef.current);
1980
+ styleElementRef.current = null;
1981
+ }
1982
+ };
1983
+ }, [theme]);
1984
+ const renderContent = () => {
1985
+ if (loading) {
1986
+ return null;
1987
+ }
1988
+ if (error) {
1989
+ return null;
1990
+ }
1991
+ if (!ad || !ad.iframeUrl) {
1992
+ return null;
1993
+ }
1994
+ return (jsxRuntime.jsxs("div", { className: "simula-content-slot", onClick: () => onClick === null || onClick === void 0 ? void 0 : onClick(ad), style: { cursor: onClick ? 'pointer' : 'default' }, children: [jsxRuntime.jsx("iframe", { src: ad.iframeUrl, className: "simula-content-frame", style: { display: 'block', verticalAlign: 'top', border: 0, margin: 0, padding: 0, width: '100%' }, frameBorder: "0", scrolling: "no", allowTransparency: true, sandbox: "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox", title: `Content: ${ad.id}`, onLoad: () => {
1995
+ // Iframe loaded - now impressions can be tracked
1996
+ setIframeLoaded(true);
1997
+ } }), jsxRuntime.jsx("button", { className: "simula-info-icon", onClick: (e) => {
1998
+ e.stopPropagation();
1999
+ setShowInfoModal(true);
2000
+ }, "aria-label": "Content information", children: jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: [jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "7", stroke: "currentColor", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("text", { x: "8", y: "12", textAnchor: "middle", fontSize: "10", fontFamily: "serif", children: "i" })] }) }), showInfoModal && (jsxRuntime.jsx("div", { className: "simula-modal-overlay", onClick: () => setShowInfoModal(false), children: jsxRuntime.jsxs("div", { className: "simula-modal-content", onClick: (e) => e.stopPropagation(), children: [jsxRuntime.jsx("button", { className: "simula-modal-close", onClick: () => setShowInfoModal(false), "aria-label": "Close", children: "\u00D7" }), jsxRuntime.jsxs("p", { children: ["Powered by", ' ', jsxRuntime.jsx("a", { href: "https://simula.ad", target: "_blank", rel: "noopener noreferrer", className: "simula-modal-link", children: "Simula" })] })] }) }))] }));
2001
+ };
2002
+ return (jsxRuntime.jsx("div", { ref: elementRef, style: {
2003
+ display: error ? 'none' : 'block',
2004
+ minWidth: ad && ad.id ? '320px' : '0px',
2005
+ width: !theme.width || theme.width === 'auto' ? '100%' : theme.width,
2006
+ height: ad && ad.id ? '265px' : '0px',
2007
+ overflow: 'hidden'
2008
+ }, children: renderContent() }));
2009
+ };
2010
+
2011
+ const useOMIDViewability = (options = {}) => {
2012
+ const { threshold = 0.5, durationMs = 1000, partnerName = 'Simula-Ad-SDK', partnerVersion = '1.0.0', onImpressionTracked } = options;
2013
+ const elementRef = react.useRef(null);
2014
+ const [isViewable, setIsViewable] = react.useState(false);
2015
+ const [hasBeenViewed, setHasBeenViewed] = react.useState(false);
2016
+ const [impressionTracked, setImpressionTracked] = react.useState(false);
2017
+ const [viewableStartTime, setViewableStartTime] = react.useState(null);
2018
+ const [hasMetDuration, setHasMetDuration] = react.useState(false);
2019
+ const omidSessionRef = react.useRef(null);
2020
+ const adEventsRef = react.useRef(null);
2021
+ react.useEffect(() => {
2022
+ if (!elementRef.current)
2023
+ return;
2024
+ // Initialize OMID Session
2025
+ const initializeOMID = async () => {
2026
+ var _a;
2027
+ try {
2028
+ // Load OMID Service Script dynamically if not already loaded
2029
+ if (!window.omidSessionClient) {
2030
+ await loadOMIDServiceScript();
2031
+ }
2032
+ const sessionClient = (_a = window.omidSessionClient) === null || _a === void 0 ? void 0 : _a['1.0.0'];
2033
+ if (!sessionClient) {
2034
+ console.warn('OMID Session Client not available, falling back to basic viewability');
2035
+ initializeFallbackViewability();
2036
+ return;
2037
+ }
2038
+ // Create OMID partner and context
2039
+ const Partner = sessionClient.Partner;
2040
+ const Context = sessionClient.Context;
2041
+ const AdSession = sessionClient.AdSession;
2042
+ const AdEvents = sessionClient.AdEvents;
2043
+ const partner = new Partner(partnerName, partnerVersion);
2044
+ const context = new Context(partner, [], null); // No verification scripts for now
2045
+ // Create ad session
2046
+ const adSession = new AdSession(context);
2047
+ adSession.setCreativeType('display');
2048
+ adSession.setImpressionType('beginToRender');
2049
+ omidSessionRef.current = adSession;
2050
+ // Set the ad view
2051
+ context.setSlotElement(elementRef.current);
2052
+ // Start session
2053
+ adSession.start();
2054
+ // Create ad events handler
2055
+ adEventsRef.current = new AdEvents(adSession);
2056
+ // Register session observer for viewability events
2057
+ adSession.registerSessionObserver((event) => {
2058
+ var _a;
2059
+ switch (event.type) {
2060
+ case 'sessionStart':
2061
+ // Signal ad loaded
2062
+ (_a = adEventsRef.current) === null || _a === void 0 ? void 0 : _a.loaded();
2063
+ break;
2064
+ case 'sessionError':
2065
+ console.error('OMID session error:', event.data);
2066
+ initializeFallbackViewability();
2067
+ break;
2068
+ case 'sessionFinish':
2069
+ break;
2070
+ case 'geometryChange':
2071
+ handleGeometryChange(event.data);
2072
+ break;
2073
+ }
2074
+ });
2075
+ }
2076
+ catch (error) {
2077
+ console.warn('OMID initialization failed, using fallback:', error);
2078
+ initializeFallbackViewability();
2079
+ }
2080
+ };
2081
+ const loadOMIDServiceScript = async () => {
2082
+ return new Promise((resolve, reject) => {
2083
+ // Check if script is already loaded
2084
+ if (document.querySelector('script[src*="omweb-v1.js"]')) {
2085
+ resolve();
2086
+ return;
2087
+ }
2088
+ const script = document.createElement('script');
2089
+ script.src = 'https://s0.2mdn.net/instream/omweb/omweb-v1.js'; // Official OMID Service Script
2090
+ script.async = true;
2091
+ script.onload = () => resolve();
2092
+ script.onerror = () => reject(new Error('Failed to load OMID script'));
2093
+ document.head.appendChild(script);
2094
+ });
2095
+ };
2096
+ const handleGeometryChange = (geometryData) => {
2097
+ if (!(geometryData === null || geometryData === void 0 ? void 0 : geometryData.viewport) || !(geometryData === null || geometryData === void 0 ? void 0 : geometryData.adView))
2098
+ return;
2099
+ const { viewport, adView } = geometryData;
2100
+ // Calculate viewable area
2101
+ const viewableArea = calculateViewableArea(viewport, adView);
2102
+ const totalArea = adView.width * adView.height;
2103
+ const viewablePercentage = totalArea > 0 ? viewableArea / totalArea : 0;
2104
+ const newIsViewable = viewablePercentage >= threshold;
2105
+ const now = Date.now();
2106
+ if (newIsViewable) {
2107
+ if (viewableStartTime === null) {
2108
+ setViewableStartTime(now);
2109
+ }
2110
+ setIsViewable(true);
2111
+ if (!hasBeenViewed) {
2112
+ setHasBeenViewed(true);
2113
+ }
2114
+ }
2115
+ else {
2116
+ setViewableStartTime(null);
2117
+ setIsViewable(false);
2118
+ setHasMetDuration(false);
2119
+ }
2120
+ };
2121
+ const calculateViewableArea = (viewport, adView) => {
2122
+ const intersectionLeft = Math.max(viewport.x, adView.x);
2123
+ const intersectionTop = Math.max(viewport.y, adView.y);
2124
+ const intersectionRight = Math.min(viewport.x + viewport.width, adView.x + adView.width);
2125
+ const intersectionBottom = Math.min(viewport.y + viewport.height, adView.y + adView.height);
2126
+ const width = Math.max(0, intersectionRight - intersectionLeft);
2127
+ const height = Math.max(0, intersectionBottom - intersectionTop);
2128
+ return width * height;
2129
+ };
2130
+ // Fallback implementation using Intersection Observer
2131
+ const initializeFallbackViewability = () => {
2132
+ const observer = new IntersectionObserver((entries) => {
2133
+ entries.forEach((entry) => {
2134
+ const newIsViewable = entry.intersectionRatio >= threshold;
2135
+ const now = Date.now();
2136
+ if (newIsViewable) {
2137
+ if (viewableStartTime === null) {
2138
+ setViewableStartTime(now);
2139
+ }
2140
+ setIsViewable(true);
2141
+ if (!hasBeenViewed) {
2142
+ setHasBeenViewed(true);
2143
+ }
2144
+ }
2145
+ else {
2146
+ setViewableStartTime(null);
2147
+ setIsViewable(false);
2148
+ setHasMetDuration(false);
2149
+ }
2150
+ });
2151
+ }, {
2152
+ threshold,
2153
+ rootMargin: '0px'
2154
+ });
2155
+ if (elementRef.current) {
2156
+ observer.observe(elementRef.current);
2157
+ }
2158
+ return () => {
2159
+ observer.disconnect();
2160
+ };
2161
+ };
2162
+ initializeOMID();
2163
+ // Cleanup
2164
+ return () => {
2165
+ if (omidSessionRef.current) {
2166
+ try {
2167
+ omidSessionRef.current.finish();
2168
+ }
2169
+ catch (error) {
2170
+ console.warn('Error finishing OMID session:', error);
2171
+ }
2172
+ }
2173
+ };
2174
+ }, [threshold, partnerName, partnerVersion, hasBeenViewed, viewableStartTime]);
2175
+ // Duration tracking effect
2176
+ react.useEffect(() => {
2177
+ if (!isViewable || viewableStartTime === null || hasMetDuration)
2178
+ return;
2179
+ const timeoutId = setTimeout(() => {
2180
+ if (isViewable && viewableStartTime !== null) {
2181
+ const elapsed = Date.now() - viewableStartTime;
2182
+ if (elapsed >= durationMs) {
2183
+ setHasMetDuration(true);
2184
+ }
2185
+ }
2186
+ }, durationMs);
2187
+ return () => clearTimeout(timeoutId);
2188
+ }, [isViewable, viewableStartTime, durationMs, hasMetDuration]);
2189
+ const trackImpression = (adId) => {
2190
+ if (impressionTracked)
2191
+ return;
2192
+ if (adEventsRef.current) {
2193
+ // Use OMID impression tracking
2194
+ try {
2195
+ adEventsRef.current.impressionOccurred();
2196
+ setImpressionTracked(true);
2197
+ // After OMID tracks successfully, ping our backend
2198
+ if (onImpressionTracked && adId) {
2199
+ onImpressionTracked(adId);
2200
+ }
2201
+ }
2202
+ catch (error) {
2203
+ console.warn('OMID impression tracking failed:', error);
2204
+ setImpressionTracked(true); // Mark as tracked to prevent retries
2205
+ }
2206
+ }
2207
+ else {
2208
+ // Fallback impression tracking
2209
+ setImpressionTracked(true);
2210
+ // Still ping our backend even in fallback mode
2211
+ if (onImpressionTracked && adId) {
2212
+ onImpressionTracked(adId);
2213
+ }
2214
+ }
2215
+ };
2216
+ return {
2217
+ elementRef,
2218
+ isViewable: isViewable && hasMetDuration, // Only considered viewable after meeting duration
2219
+ hasBeenViewed,
2220
+ impressionTracked,
2221
+ trackImpression
2222
+ };
2223
+ };
2224
+
2225
+ exports.InChatAdSlot = InChatAdSlot;
2226
+ exports.SimulaProvider = SimulaProvider;
2227
+ exports.fonts = fonts;
2228
+ exports.getBackgroundGradient = getBackgroundGradient;
2229
+ exports.getBorderLight = getBorderLight;
2230
+ exports.getColorTheme = getColorTheme;
2231
+ exports.getFontStyles = getFontStyles;
2232
+ exports.getShadow = getShadow;
2233
+ exports.getSolidBackground = getSolidBackground;
2234
+ exports.getTextMuted = getTextMuted;
2235
+ exports.getTextSecondary = getTextSecondary;
2236
+ exports.useBotDetection = useBotDetection;
2237
+ exports.useOMIDViewability = useOMIDViewability;
2238
+ exports.useSimula = useSimula;
2239
+ exports.useViewability = useViewability;
2240
+ //# sourceMappingURL=index.js.map