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