@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/LICENSE +21 -0
- package/README.md +406 -0
- package/dist/InChatAdSlot.d.ts +4 -0
- package/dist/InChatAdSlot.d.ts.map +1 -0
- package/dist/SimulaProvider.d.ts +5 -0
- package/dist/SimulaProvider.d.ts.map +1 -0
- package/dist/hooks/useBotDetection.d.ts +3 -0
- package/dist/hooks/useBotDetection.d.ts.map +1 -0
- package/dist/hooks/useDebounce.d.ts +2 -0
- package/dist/hooks/useDebounce.d.ts.map +1 -0
- package/dist/hooks/useOMIDViewability.d.ts +24 -0
- package/dist/hooks/useOMIDViewability.d.ts.map +1 -0
- package/dist/hooks/useViewability.d.ts +6 -0
- package/dist/hooks/useViewability.d.ts.map +1 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2240 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2224 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/api.d.ts +18 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/colorThemes.d.ts +21 -0
- package/dist/utils/colorThemes.d.ts.map +1 -0
- package/dist/utils/styling.d.ts +4 -0
- package/dist/utils/styling.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/package.json +76 -0
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
|