@seekora-ai/search-sdk 0.2.6 → 0.2.8
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/cdn/seekora-sdk.js +30 -19
- package/cdn/seekora-sdk.min.js +12 -12
- package/dist/client.d.ts +130 -0
- package/dist/client.js +199 -5
- package/dist/generated/api.d.ts +33029 -8906
- package/dist/generated/api.js +46824 -1681
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -1
- package/dist/ui-components.d.ts +74 -0
- package/dist/ui-components.js +165 -0
- package/package.json +1 -1
- package/dist/src/cdn.d.ts +0 -16
- package/dist/src/cdn.js +0 -26
- package/dist/src/client.d.ts +0 -709
- package/dist/src/client.js +0 -1548
- package/dist/src/config-loader.d.ts +0 -43
- package/dist/src/config-loader.js +0 -147
- package/dist/src/config.d.ts +0 -22
- package/dist/src/config.js +0 -58
- package/dist/src/context-collector.d.ts +0 -273
- package/dist/src/context-collector.js +0 -868
- package/dist/src/event-queue.d.ts +0 -195
- package/dist/src/event-queue.js +0 -424
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -48
- package/dist/src/logger.d.ts +0 -61
- package/dist/src/logger.js +0 -172
- package/dist/src/utils.d.ts +0 -20
- package/dist/src/utils.js +0 -73
|
@@ -1,868 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Context Collector for Seekora SDK
|
|
4
|
-
*
|
|
5
|
-
* Collects browser context, device information, and fingerprinting
|
|
6
|
-
* for enriching analytics events with comprehensive user environment data.
|
|
7
|
-
*
|
|
8
|
-
* Uses FingerprintJS Open Source for industry-standard device fingerprinting
|
|
9
|
-
* with 50+ browser signals for high accuracy cross-session identification.
|
|
10
|
-
*/
|
|
11
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
-
if (k2 === undefined) k2 = k;
|
|
13
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
-
}
|
|
17
|
-
Object.defineProperty(o, k2, desc);
|
|
18
|
-
}) : (function(o, m, k, k2) {
|
|
19
|
-
if (k2 === undefined) k2 = k;
|
|
20
|
-
o[k2] = m[k];
|
|
21
|
-
}));
|
|
22
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
-
}) : function(o, v) {
|
|
25
|
-
o["default"] = v;
|
|
26
|
-
});
|
|
27
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
-
var ownKeys = function(o) {
|
|
29
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
-
var ar = [];
|
|
31
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
-
return ar;
|
|
33
|
-
};
|
|
34
|
-
return ownKeys(o);
|
|
35
|
-
};
|
|
36
|
-
return function (mod) {
|
|
37
|
-
if (mod && mod.__esModule) return mod;
|
|
38
|
-
var result = {};
|
|
39
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
-
__setModuleDefault(result, mod);
|
|
41
|
-
return result;
|
|
42
|
-
};
|
|
43
|
-
})();
|
|
44
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.ContextCollector = void 0;
|
|
46
|
-
exports.getDefaultContextCollector = getDefaultContextCollector;
|
|
47
|
-
exports.collectBrowserContext = collectBrowserContext;
|
|
48
|
-
let FingerprintJS = null;
|
|
49
|
-
// Try to load FingerprintJS
|
|
50
|
-
async function loadFingerprintJS() {
|
|
51
|
-
if (FingerprintJS)
|
|
52
|
-
return FingerprintJS;
|
|
53
|
-
try {
|
|
54
|
-
FingerprintJS = await Promise.resolve().then(() => __importStar(require('@fingerprintjs/fingerprintjs')));
|
|
55
|
-
return FingerprintJS;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// FingerprintJS not available, will fall back to manual implementation
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Default configuration
|
|
64
|
-
* Fingerprinting is enabled by default with FingerprintJS for best accuracy
|
|
65
|
-
*/
|
|
66
|
-
const DEFAULT_CONFIG = {
|
|
67
|
-
enableFingerprinting: true,
|
|
68
|
-
useFingerprintJS: true,
|
|
69
|
-
enableCanvasFingerprint: true,
|
|
70
|
-
enableWebGLFingerprint: true,
|
|
71
|
-
enableAudioFingerprint: false,
|
|
72
|
-
autoCollect: true,
|
|
73
|
-
cacheDuration: 30000,
|
|
74
|
-
fingerprintStorageKey: 'seekora_device_fp',
|
|
75
|
-
enableExtendedComponents: true,
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* ContextCollector class for gathering browser and device context
|
|
79
|
-
*
|
|
80
|
-
* Uses FingerprintJS Open Source for industry-standard fingerprinting
|
|
81
|
-
* with automatic fallback to manual implementation if unavailable.
|
|
82
|
-
*/
|
|
83
|
-
class ContextCollector {
|
|
84
|
-
constructor(config = {}) {
|
|
85
|
-
this.cachedContext = null;
|
|
86
|
-
this.cacheTimestamp = 0;
|
|
87
|
-
this.cachedFingerprint = null;
|
|
88
|
-
this.fingerprintJSAgent = null;
|
|
89
|
-
this.fingerprintJSLoading = null;
|
|
90
|
-
this.usingFingerprintJS = false;
|
|
91
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
92
|
-
// Load cached fingerprint from storage
|
|
93
|
-
if (this.config.enableFingerprinting) {
|
|
94
|
-
this.loadCachedFingerprint();
|
|
95
|
-
// Pre-load FingerprintJS agent for faster first fingerprint
|
|
96
|
-
if (this.config.useFingerprintJS && this.isBrowser()) {
|
|
97
|
-
this.initFingerprintJS();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Initialize FingerprintJS agent (async, non-blocking)
|
|
103
|
-
*/
|
|
104
|
-
async initFingerprintJS() {
|
|
105
|
-
if (this.fingerprintJSAgent || this.fingerprintJSLoading)
|
|
106
|
-
return;
|
|
107
|
-
this.fingerprintJSLoading = (async () => {
|
|
108
|
-
try {
|
|
109
|
-
const fpjs = await loadFingerprintJS();
|
|
110
|
-
if (fpjs) {
|
|
111
|
-
this.fingerprintJSAgent = await fpjs.load();
|
|
112
|
-
this.usingFingerprintJS = true;
|
|
113
|
-
console.debug('[Seekora] FingerprintJS initialized successfully');
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.debug('[Seekora] FingerprintJS not available, using manual fingerprinting');
|
|
118
|
-
}
|
|
119
|
-
return this.fingerprintJSAgent;
|
|
120
|
-
})();
|
|
121
|
-
await this.fingerprintJSLoading;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Get FingerprintJS agent, waiting for initialization if needed
|
|
125
|
-
*/
|
|
126
|
-
async getFingerprintJSAgent() {
|
|
127
|
-
if (this.fingerprintJSAgent)
|
|
128
|
-
return this.fingerprintJSAgent;
|
|
129
|
-
if (this.fingerprintJSLoading) {
|
|
130
|
-
await this.fingerprintJSLoading;
|
|
131
|
-
return this.fingerprintJSAgent;
|
|
132
|
-
}
|
|
133
|
-
await this.initFingerprintJS();
|
|
134
|
-
return this.fingerprintJSAgent;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Check if running in browser environment
|
|
138
|
-
*/
|
|
139
|
-
isBrowser() {
|
|
140
|
-
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Collect complete browser context
|
|
144
|
-
*/
|
|
145
|
-
async collect() {
|
|
146
|
-
// Return cached context if still valid
|
|
147
|
-
if (this.cachedContext && this.config.cacheDuration > 0) {
|
|
148
|
-
const elapsed = Date.now() - this.cacheTimestamp;
|
|
149
|
-
if (elapsed < this.config.cacheDuration) {
|
|
150
|
-
// Update dynamic fields that may have changed
|
|
151
|
-
this.cachedContext.document_visibility = this.getDocumentVisibility();
|
|
152
|
-
this.cachedContext.viewport_width = this.getViewportWidth();
|
|
153
|
-
this.cachedContext.viewport_height = this.getViewportHeight();
|
|
154
|
-
return this.cachedContext;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const context = await this.collectFresh();
|
|
158
|
-
// Cache the context
|
|
159
|
-
this.cachedContext = context;
|
|
160
|
-
this.cacheTimestamp = Date.now();
|
|
161
|
-
return context;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Collect fresh context without caching
|
|
165
|
-
*/
|
|
166
|
-
async collectFresh() {
|
|
167
|
-
if (!this.isBrowser()) {
|
|
168
|
-
return this.getServerContext();
|
|
169
|
-
}
|
|
170
|
-
const utm = this.parseUTMParameters();
|
|
171
|
-
const connection = this.getConnectionInfo();
|
|
172
|
-
const browserInfo = this.parseBrowserInfo();
|
|
173
|
-
const context = {
|
|
174
|
-
// Screen dimensions
|
|
175
|
-
screen_width: screen.width || 0,
|
|
176
|
-
screen_height: screen.height || 0,
|
|
177
|
-
viewport_width: this.getViewportWidth(),
|
|
178
|
-
viewport_height: this.getViewportHeight(),
|
|
179
|
-
color_depth: screen.colorDepth || screen.pixelDepth || 24,
|
|
180
|
-
pixel_ratio: window.devicePixelRatio || 1,
|
|
181
|
-
// Browser information
|
|
182
|
-
browser_name: browserInfo.name,
|
|
183
|
-
browser_version: browserInfo.version,
|
|
184
|
-
browser_language: navigator.language || 'en',
|
|
185
|
-
browser_languages: Array.from(navigator.languages || [navigator.language || 'en']),
|
|
186
|
-
timezone: this.getTimezone(),
|
|
187
|
-
timezone_offset: new Date().getTimezoneOffset(),
|
|
188
|
-
// Page context
|
|
189
|
-
page_url: this.sanitizeUrl(window.location.href),
|
|
190
|
-
page_path: window.location.pathname,
|
|
191
|
-
page_title: document.title || '',
|
|
192
|
-
page_referrer: this.sanitizeUrl(document.referrer || ''),
|
|
193
|
-
document_visibility: this.getDocumentVisibility(),
|
|
194
|
-
// UTM parameters
|
|
195
|
-
...utm,
|
|
196
|
-
// Connection information
|
|
197
|
-
...connection,
|
|
198
|
-
// Platform detection
|
|
199
|
-
platform: this.detectPlatform(),
|
|
200
|
-
is_mobile: this.isMobile(),
|
|
201
|
-
is_tablet: this.isTablet(),
|
|
202
|
-
is_touch_device: this.isTouchDevice(),
|
|
203
|
-
hardware_concurrency: navigator.hardwareConcurrency,
|
|
204
|
-
max_touch_points: navigator.maxTouchPoints,
|
|
205
|
-
device_memory: navigator.deviceMemory,
|
|
206
|
-
};
|
|
207
|
-
// Generate fingerprint if enabled
|
|
208
|
-
if (this.config.enableFingerprinting) {
|
|
209
|
-
context.device_fingerprint = await this.getOrGenerateFingerprint();
|
|
210
|
-
}
|
|
211
|
-
return context;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Get server-side context (for Node.js environments)
|
|
215
|
-
*/
|
|
216
|
-
getServerContext() {
|
|
217
|
-
return {
|
|
218
|
-
screen_width: 0,
|
|
219
|
-
screen_height: 0,
|
|
220
|
-
viewport_width: 0,
|
|
221
|
-
viewport_height: 0,
|
|
222
|
-
color_depth: 0,
|
|
223
|
-
pixel_ratio: 1,
|
|
224
|
-
browser_name: 'node',
|
|
225
|
-
browser_version: process.version || '',
|
|
226
|
-
browser_language: 'en',
|
|
227
|
-
browser_languages: ['en'],
|
|
228
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
|
229
|
-
timezone_offset: new Date().getTimezoneOffset(),
|
|
230
|
-
page_url: '',
|
|
231
|
-
page_path: '',
|
|
232
|
-
page_title: '',
|
|
233
|
-
page_referrer: '',
|
|
234
|
-
document_visibility: 'visible',
|
|
235
|
-
platform: process.platform || 'unknown',
|
|
236
|
-
is_mobile: false,
|
|
237
|
-
is_tablet: false,
|
|
238
|
-
is_touch_device: false,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Get viewport width
|
|
243
|
-
*/
|
|
244
|
-
getViewportWidth() {
|
|
245
|
-
if (!this.isBrowser())
|
|
246
|
-
return 0;
|
|
247
|
-
return window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Get viewport height
|
|
251
|
-
*/
|
|
252
|
-
getViewportHeight() {
|
|
253
|
-
if (!this.isBrowser())
|
|
254
|
-
return 0;
|
|
255
|
-
return window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Get document visibility state
|
|
259
|
-
*/
|
|
260
|
-
getDocumentVisibility() {
|
|
261
|
-
if (!this.isBrowser())
|
|
262
|
-
return 'visible';
|
|
263
|
-
return document.visibilityState || 'visible';
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Get timezone name
|
|
267
|
-
*/
|
|
268
|
-
getTimezone() {
|
|
269
|
-
try {
|
|
270
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
return 'UTC';
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Parse UTM parameters from URL
|
|
278
|
-
*/
|
|
279
|
-
parseUTMParameters() {
|
|
280
|
-
if (!this.isBrowser())
|
|
281
|
-
return {};
|
|
282
|
-
try {
|
|
283
|
-
const params = new URLSearchParams(window.location.search);
|
|
284
|
-
const result = {};
|
|
285
|
-
const utmSource = params.get('utm_source');
|
|
286
|
-
const utmMedium = params.get('utm_medium');
|
|
287
|
-
const utmCampaign = params.get('utm_campaign');
|
|
288
|
-
const utmTerm = params.get('utm_term');
|
|
289
|
-
const utmContent = params.get('utm_content');
|
|
290
|
-
if (utmSource)
|
|
291
|
-
result.utm_source = utmSource;
|
|
292
|
-
if (utmMedium)
|
|
293
|
-
result.utm_medium = utmMedium;
|
|
294
|
-
if (utmCampaign)
|
|
295
|
-
result.utm_campaign = utmCampaign;
|
|
296
|
-
if (utmTerm)
|
|
297
|
-
result.utm_term = utmTerm;
|
|
298
|
-
if (utmContent)
|
|
299
|
-
result.utm_content = utmContent;
|
|
300
|
-
// Also check referrer for UTM params (for cross-domain tracking)
|
|
301
|
-
if (Object.keys(result).length === 0 && document.referrer) {
|
|
302
|
-
try {
|
|
303
|
-
const referrerUrl = new URL(document.referrer);
|
|
304
|
-
const referrerParams = new URLSearchParams(referrerUrl.search);
|
|
305
|
-
const refUtmSource = referrerParams.get('utm_source');
|
|
306
|
-
const refUtmMedium = referrerParams.get('utm_medium');
|
|
307
|
-
const refUtmCampaign = referrerParams.get('utm_campaign');
|
|
308
|
-
const refUtmTerm = referrerParams.get('utm_term');
|
|
309
|
-
const refUtmContent = referrerParams.get('utm_content');
|
|
310
|
-
if (refUtmSource)
|
|
311
|
-
result.utm_source = refUtmSource;
|
|
312
|
-
if (refUtmMedium)
|
|
313
|
-
result.utm_medium = refUtmMedium;
|
|
314
|
-
if (refUtmCampaign)
|
|
315
|
-
result.utm_campaign = refUtmCampaign;
|
|
316
|
-
if (refUtmTerm)
|
|
317
|
-
result.utm_term = refUtmTerm;
|
|
318
|
-
if (refUtmContent)
|
|
319
|
-
result.utm_content = refUtmContent;
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// Invalid referrer URL, ignore
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
return {};
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Get connection information from Network Information API
|
|
333
|
-
*/
|
|
334
|
-
getConnectionInfo() {
|
|
335
|
-
if (!this.isBrowser())
|
|
336
|
-
return {};
|
|
337
|
-
const connection = navigator.connection ||
|
|
338
|
-
navigator.mozConnection ||
|
|
339
|
-
navigator.webkitConnection;
|
|
340
|
-
if (!connection)
|
|
341
|
-
return {};
|
|
342
|
-
return {
|
|
343
|
-
connection_type: connection.type,
|
|
344
|
-
connection_effective_type: connection.effectiveType,
|
|
345
|
-
connection_downlink: connection.downlink,
|
|
346
|
-
connection_rtt: connection.rtt,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Parse browser name and version from user agent
|
|
351
|
-
*/
|
|
352
|
-
parseBrowserInfo() {
|
|
353
|
-
if (!this.isBrowser()) {
|
|
354
|
-
return { name: 'node', version: process.version || '' };
|
|
355
|
-
}
|
|
356
|
-
const ua = navigator.userAgent;
|
|
357
|
-
let name = 'unknown';
|
|
358
|
-
let version = '';
|
|
359
|
-
// Order matters - check more specific browsers first
|
|
360
|
-
if (ua.includes('Firefox/')) {
|
|
361
|
-
name = 'firefox';
|
|
362
|
-
const match = ua.match(/Firefox\/(\d+\.?\d*)/);
|
|
363
|
-
version = match ? match[1] : '';
|
|
364
|
-
}
|
|
365
|
-
else if (ua.includes('Edg/')) {
|
|
366
|
-
name = 'edge';
|
|
367
|
-
const match = ua.match(/Edg\/(\d+\.?\d*)/);
|
|
368
|
-
version = match ? match[1] : '';
|
|
369
|
-
}
|
|
370
|
-
else if (ua.includes('OPR/') || ua.includes('Opera/')) {
|
|
371
|
-
name = 'opera';
|
|
372
|
-
const match = ua.match(/(?:OPR|Opera)\/(\d+\.?\d*)/);
|
|
373
|
-
version = match ? match[1] : '';
|
|
374
|
-
}
|
|
375
|
-
else if (ua.includes('Chrome/')) {
|
|
376
|
-
name = 'chrome';
|
|
377
|
-
const match = ua.match(/Chrome\/(\d+\.?\d*)/);
|
|
378
|
-
version = match ? match[1] : '';
|
|
379
|
-
}
|
|
380
|
-
else if (ua.includes('Safari/') && !ua.includes('Chrome')) {
|
|
381
|
-
name = 'safari';
|
|
382
|
-
const match = ua.match(/Version\/(\d+\.?\d*)/);
|
|
383
|
-
version = match ? match[1] : '';
|
|
384
|
-
}
|
|
385
|
-
else if (ua.includes('MSIE') || ua.includes('Trident/')) {
|
|
386
|
-
name = 'ie';
|
|
387
|
-
const match = ua.match(/(?:MSIE |rv:)(\d+\.?\d*)/);
|
|
388
|
-
version = match ? match[1] : '';
|
|
389
|
-
}
|
|
390
|
-
return { name, version };
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Detect platform from user agent
|
|
394
|
-
*/
|
|
395
|
-
detectPlatform() {
|
|
396
|
-
if (!this.isBrowser()) {
|
|
397
|
-
return process.platform || 'unknown';
|
|
398
|
-
}
|
|
399
|
-
const ua = navigator.userAgent.toLowerCase();
|
|
400
|
-
const platform = (navigator.platform || '').toLowerCase();
|
|
401
|
-
if (ua.includes('android'))
|
|
402
|
-
return 'android';
|
|
403
|
-
if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod'))
|
|
404
|
-
return 'ios';
|
|
405
|
-
if (platform.includes('mac') || ua.includes('mac os'))
|
|
406
|
-
return 'macos';
|
|
407
|
-
if (platform.includes('win') || ua.includes('windows'))
|
|
408
|
-
return 'windows';
|
|
409
|
-
if (platform.includes('linux') || ua.includes('linux'))
|
|
410
|
-
return 'linux';
|
|
411
|
-
if (ua.includes('cros'))
|
|
412
|
-
return 'chromeos';
|
|
413
|
-
return 'unknown';
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Check if device is mobile
|
|
417
|
-
*/
|
|
418
|
-
isMobile() {
|
|
419
|
-
if (!this.isBrowser())
|
|
420
|
-
return false;
|
|
421
|
-
const ua = navigator.userAgent.toLowerCase();
|
|
422
|
-
return /android|webos|iphone|ipod|blackberry|iemobile|opera mini|mobile/i.test(ua);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Check if device is tablet
|
|
426
|
-
*/
|
|
427
|
-
isTablet() {
|
|
428
|
-
if (!this.isBrowser())
|
|
429
|
-
return false;
|
|
430
|
-
const ua = navigator.userAgent.toLowerCase();
|
|
431
|
-
return /ipad|tablet|playbook|silk/i.test(ua) ||
|
|
432
|
-
(ua.includes('android') && !ua.includes('mobile'));
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Check if device supports touch
|
|
436
|
-
*/
|
|
437
|
-
isTouchDevice() {
|
|
438
|
-
if (!this.isBrowser())
|
|
439
|
-
return false;
|
|
440
|
-
return 'ontouchstart' in window ||
|
|
441
|
-
navigator.maxTouchPoints > 0 ||
|
|
442
|
-
navigator.msMaxTouchPoints > 0;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Sanitize URL to remove sensitive query parameters
|
|
446
|
-
*/
|
|
447
|
-
sanitizeUrl(url) {
|
|
448
|
-
if (!url)
|
|
449
|
-
return '';
|
|
450
|
-
try {
|
|
451
|
-
const urlObj = new URL(url);
|
|
452
|
-
const sensitiveParams = ['password', 'token', 'api_key', 'apikey', 'secret', 'auth', 'key'];
|
|
453
|
-
sensitiveParams.forEach(param => {
|
|
454
|
-
if (urlObj.searchParams.has(param)) {
|
|
455
|
-
urlObj.searchParams.set(param, '[REDACTED]');
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
return urlObj.toString();
|
|
459
|
-
}
|
|
460
|
-
catch {
|
|
461
|
-
return url;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Load cached fingerprint from storage
|
|
466
|
-
*/
|
|
467
|
-
loadCachedFingerprint() {
|
|
468
|
-
if (!this.isBrowser())
|
|
469
|
-
return;
|
|
470
|
-
try {
|
|
471
|
-
const stored = localStorage.getItem(this.config.fingerprintStorageKey);
|
|
472
|
-
if (stored) {
|
|
473
|
-
this.cachedFingerprint = stored;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
// localStorage not available
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Get or generate device fingerprint
|
|
482
|
-
*/
|
|
483
|
-
async getOrGenerateFingerprint() {
|
|
484
|
-
// Return cached fingerprint if available
|
|
485
|
-
if (this.cachedFingerprint) {
|
|
486
|
-
return this.cachedFingerprint;
|
|
487
|
-
}
|
|
488
|
-
// Generate new fingerprint
|
|
489
|
-
const fingerprint = await this.generateFingerprint();
|
|
490
|
-
// Cache it
|
|
491
|
-
this.cachedFingerprint = fingerprint;
|
|
492
|
-
// Persist to localStorage
|
|
493
|
-
if (this.isBrowser()) {
|
|
494
|
-
try {
|
|
495
|
-
localStorage.setItem(this.config.fingerprintStorageKey, fingerprint);
|
|
496
|
-
}
|
|
497
|
-
catch {
|
|
498
|
-
// localStorage not available
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return fingerprint;
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Generate device fingerprint using FingerprintJS or manual fallback
|
|
505
|
-
*
|
|
506
|
-
* FingerprintJS provides ~99.5% accuracy with 50+ browser signals
|
|
507
|
-
* Manual fallback provides ~70% accuracy with 15+ signals
|
|
508
|
-
*/
|
|
509
|
-
async generateFingerprint() {
|
|
510
|
-
// Try FingerprintJS first (preferred)
|
|
511
|
-
if (this.config.useFingerprintJS && this.isBrowser()) {
|
|
512
|
-
try {
|
|
513
|
-
const agent = await this.getFingerprintJSAgent();
|
|
514
|
-
if (agent) {
|
|
515
|
-
const result = await agent.get();
|
|
516
|
-
// FingerprintJS provides a visitorId (stable fingerprint)
|
|
517
|
-
// and components (individual signals with confidence scores)
|
|
518
|
-
console.debug('[Seekora] FingerprintJS fingerprint generated:', {
|
|
519
|
-
visitorId: result.visitorId,
|
|
520
|
-
confidence: result.confidence?.score,
|
|
521
|
-
components: Object.keys(result.components || {}).length
|
|
522
|
-
});
|
|
523
|
-
return result.visitorId;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
catch (error) {
|
|
527
|
-
console.debug('[Seekora] FingerprintJS failed, falling back to manual:', error);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Fallback to manual fingerprinting
|
|
531
|
-
return this.generateManualFingerprint();
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Generate fingerprint manually using browser characteristics
|
|
535
|
-
* Used as fallback when FingerprintJS is not available
|
|
536
|
-
*/
|
|
537
|
-
async generateManualFingerprint() {
|
|
538
|
-
const components = [];
|
|
539
|
-
if (!this.isBrowser()) {
|
|
540
|
-
// Server-side fingerprint (limited)
|
|
541
|
-
components.push(process.platform || 'unknown', process.version || 'unknown', process.arch || 'unknown');
|
|
542
|
-
return this.hashComponents(components);
|
|
543
|
-
}
|
|
544
|
-
// Screen characteristics
|
|
545
|
-
components.push(screen.width, screen.height, screen.colorDepth, window.devicePixelRatio || 1, screen.availWidth || 0, screen.availHeight || 0);
|
|
546
|
-
// Browser characteristics
|
|
547
|
-
components.push(navigator.language, navigator.languages?.join(',') || '', navigator.hardwareConcurrency || 0, navigator.maxTouchPoints || 0, navigator.deviceMemory || 0, navigator.cookieEnabled ? 1 : 0, navigator.doNotTrack || '');
|
|
548
|
-
// Timezone
|
|
549
|
-
components.push(this.getTimezone());
|
|
550
|
-
// Platform
|
|
551
|
-
components.push(navigator.platform || '', navigator.vendor || '', navigator.userAgent || '');
|
|
552
|
-
// Plugins (limited info)
|
|
553
|
-
if (navigator.plugins) {
|
|
554
|
-
components.push(navigator.plugins.length);
|
|
555
|
-
// Add plugin names for extended fingerprinting
|
|
556
|
-
if (this.config.enableExtendedComponents) {
|
|
557
|
-
const pluginNames = Array.from(navigator.plugins)
|
|
558
|
-
.map(p => p.name)
|
|
559
|
-
.sort()
|
|
560
|
-
.join(',');
|
|
561
|
-
components.push(pluginNames);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
// Canvas fingerprint (if enabled)
|
|
565
|
-
if (this.config.enableCanvasFingerprint) {
|
|
566
|
-
const canvasHash = this.getCanvasFingerprint();
|
|
567
|
-
if (canvasHash) {
|
|
568
|
-
components.push(canvasHash);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
// WebGL fingerprint (if enabled)
|
|
572
|
-
if (this.config.enableWebGLFingerprint) {
|
|
573
|
-
const webglHash = this.getWebGLFingerprint();
|
|
574
|
-
if (webglHash) {
|
|
575
|
-
components.push(webglHash);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
// Audio fingerprint (if enabled)
|
|
579
|
-
if (this.config.enableAudioFingerprint) {
|
|
580
|
-
const audioHash = await this.getAudioFingerprint();
|
|
581
|
-
if (audioHash) {
|
|
582
|
-
components.push(audioHash);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Extended components for higher uniqueness
|
|
586
|
-
if (this.config.enableExtendedComponents) {
|
|
587
|
-
// Add available fonts detection (simplified)
|
|
588
|
-
const fontHash = this.getFontFingerprint();
|
|
589
|
-
if (fontHash) {
|
|
590
|
-
components.push(fontHash);
|
|
591
|
-
}
|
|
592
|
-
// Add WebGL parameters
|
|
593
|
-
const webglParams = this.getWebGLParameters();
|
|
594
|
-
if (webglParams) {
|
|
595
|
-
components.push(webglParams);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
return this.hashComponents(components);
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Get audio fingerprint using AudioContext
|
|
602
|
-
*/
|
|
603
|
-
async getAudioFingerprint() {
|
|
604
|
-
if (!this.isBrowser())
|
|
605
|
-
return null;
|
|
606
|
-
try {
|
|
607
|
-
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
608
|
-
if (!AudioContext)
|
|
609
|
-
return null;
|
|
610
|
-
const context = new AudioContext();
|
|
611
|
-
const oscillator = context.createOscillator();
|
|
612
|
-
const analyser = context.createAnalyser();
|
|
613
|
-
const gain = context.createGain();
|
|
614
|
-
const processor = context.createScriptProcessor(4096, 1, 1);
|
|
615
|
-
oscillator.type = 'triangle';
|
|
616
|
-
oscillator.frequency.setValueAtTime(10000, context.currentTime);
|
|
617
|
-
gain.gain.setValueAtTime(0, context.currentTime);
|
|
618
|
-
oscillator.connect(analyser);
|
|
619
|
-
analyser.connect(processor);
|
|
620
|
-
processor.connect(gain);
|
|
621
|
-
gain.connect(context.destination);
|
|
622
|
-
oscillator.start(0);
|
|
623
|
-
const dataArray = new Float32Array(analyser.frequencyBinCount);
|
|
624
|
-
analyser.getFloatFrequencyData(dataArray);
|
|
625
|
-
oscillator.stop();
|
|
626
|
-
await context.close();
|
|
627
|
-
// Hash the audio data
|
|
628
|
-
const audioSum = dataArray.slice(0, 30).reduce((a, b) => a + Math.abs(b), 0);
|
|
629
|
-
return this.simpleHash(audioSum.toString());
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Get font fingerprint by testing font availability
|
|
637
|
-
*/
|
|
638
|
-
getFontFingerprint() {
|
|
639
|
-
if (!this.isBrowser())
|
|
640
|
-
return null;
|
|
641
|
-
try {
|
|
642
|
-
const testFonts = [
|
|
643
|
-
'Arial', 'Courier New', 'Georgia', 'Helvetica', 'Times New Roman',
|
|
644
|
-
'Verdana', 'Comic Sans MS', 'Impact', 'Lucida Console', 'Tahoma',
|
|
645
|
-
'Trebuchet MS', 'Palatino Linotype', 'Century Gothic', 'Garamond'
|
|
646
|
-
];
|
|
647
|
-
const canvas = document.createElement('canvas');
|
|
648
|
-
const ctx = canvas.getContext('2d');
|
|
649
|
-
if (!ctx)
|
|
650
|
-
return null;
|
|
651
|
-
const testString = 'mmmmmmmmmmlli';
|
|
652
|
-
const baseFont = 'monospace';
|
|
653
|
-
ctx.font = `72px ${baseFont}`;
|
|
654
|
-
const baseWidth = ctx.measureText(testString).width;
|
|
655
|
-
const availableFonts = [];
|
|
656
|
-
for (const font of testFonts) {
|
|
657
|
-
ctx.font = `72px "${font}", ${baseFont}`;
|
|
658
|
-
const width = ctx.measureText(testString).width;
|
|
659
|
-
if (width !== baseWidth) {
|
|
660
|
-
availableFonts.push(font);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
return this.simpleHash(availableFonts.join(','));
|
|
664
|
-
}
|
|
665
|
-
catch {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
/**
|
|
670
|
-
* Get additional WebGL parameters for fingerprinting
|
|
671
|
-
*/
|
|
672
|
-
getWebGLParameters() {
|
|
673
|
-
if (!this.isBrowser())
|
|
674
|
-
return null;
|
|
675
|
-
try {
|
|
676
|
-
const canvas = document.createElement('canvas');
|
|
677
|
-
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
678
|
-
if (!gl)
|
|
679
|
-
return null;
|
|
680
|
-
const params = [
|
|
681
|
-
gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
|
|
682
|
-
gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
|
|
683
|
-
gl.getParameter(gl.MAX_VARYING_VECTORS),
|
|
684
|
-
gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
|
|
685
|
-
gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
|
|
686
|
-
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
|
|
687
|
-
gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
|
|
688
|
-
gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),
|
|
689
|
-
gl.getParameter(gl.MAX_RENDERBUFFER_SIZE),
|
|
690
|
-
gl.getParameter(gl.MAX_VIEWPORT_DIMS)?.toString() || ''
|
|
691
|
-
];
|
|
692
|
-
return this.simpleHash(params.join(','));
|
|
693
|
-
}
|
|
694
|
-
catch {
|
|
695
|
-
return null;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Get canvas fingerprint
|
|
700
|
-
*/
|
|
701
|
-
getCanvasFingerprint() {
|
|
702
|
-
if (!this.isBrowser())
|
|
703
|
-
return null;
|
|
704
|
-
try {
|
|
705
|
-
const canvas = document.createElement('canvas');
|
|
706
|
-
const ctx = canvas.getContext('2d');
|
|
707
|
-
if (!ctx)
|
|
708
|
-
return null;
|
|
709
|
-
canvas.width = 200;
|
|
710
|
-
canvas.height = 50;
|
|
711
|
-
// Draw text with various styles
|
|
712
|
-
ctx.textBaseline = 'top';
|
|
713
|
-
ctx.font = '14px Arial';
|
|
714
|
-
ctx.fillStyle = '#f60';
|
|
715
|
-
ctx.fillRect(125, 1, 62, 20);
|
|
716
|
-
ctx.fillStyle = '#069';
|
|
717
|
-
ctx.fillText('Seekora SDK', 2, 15);
|
|
718
|
-
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
|
|
719
|
-
ctx.fillText('Canvas FP', 4, 17);
|
|
720
|
-
// Get data URL and hash it
|
|
721
|
-
const dataUrl = canvas.toDataURL();
|
|
722
|
-
return this.simpleHash(dataUrl);
|
|
723
|
-
}
|
|
724
|
-
catch {
|
|
725
|
-
return null;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Get WebGL fingerprint
|
|
730
|
-
*/
|
|
731
|
-
getWebGLFingerprint() {
|
|
732
|
-
if (!this.isBrowser())
|
|
733
|
-
return null;
|
|
734
|
-
try {
|
|
735
|
-
const canvas = document.createElement('canvas');
|
|
736
|
-
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
737
|
-
if (!gl)
|
|
738
|
-
return null;
|
|
739
|
-
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
740
|
-
if (!debugInfo)
|
|
741
|
-
return null;
|
|
742
|
-
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
|
|
743
|
-
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
744
|
-
return this.simpleHash(`${vendor}~${renderer}`);
|
|
745
|
-
}
|
|
746
|
-
catch {
|
|
747
|
-
return null;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Hash array of components into a fingerprint string
|
|
752
|
-
*/
|
|
753
|
-
hashComponents(components) {
|
|
754
|
-
const str = components.map(c => String(c)).join('|');
|
|
755
|
-
return this.simpleHash(str);
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Simple hash function (djb2 variant)
|
|
759
|
-
*/
|
|
760
|
-
simpleHash(str) {
|
|
761
|
-
let hash = 5381;
|
|
762
|
-
for (let i = 0; i < str.length; i++) {
|
|
763
|
-
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
764
|
-
hash = hash & hash; // Convert to 32bit integer
|
|
765
|
-
}
|
|
766
|
-
// Convert to hex string and ensure positive
|
|
767
|
-
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Clear cached context
|
|
771
|
-
*/
|
|
772
|
-
clearCache() {
|
|
773
|
-
this.cachedContext = null;
|
|
774
|
-
this.cacheTimestamp = 0;
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Clear cached fingerprint (both in memory and storage)
|
|
778
|
-
*/
|
|
779
|
-
clearFingerprint() {
|
|
780
|
-
this.cachedFingerprint = null;
|
|
781
|
-
if (this.isBrowser()) {
|
|
782
|
-
try {
|
|
783
|
-
localStorage.removeItem(this.config.fingerprintStorageKey);
|
|
784
|
-
}
|
|
785
|
-
catch {
|
|
786
|
-
// localStorage not available
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Get current configuration
|
|
792
|
-
*/
|
|
793
|
-
getConfig() {
|
|
794
|
-
return { ...this.config };
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* Update configuration
|
|
798
|
-
*/
|
|
799
|
-
updateConfig(config) {
|
|
800
|
-
this.config = { ...this.config, ...config };
|
|
801
|
-
this.clearCache();
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Check if using FingerprintJS (vs manual implementation)
|
|
805
|
-
*/
|
|
806
|
-
isUsingFingerprintJS() {
|
|
807
|
-
return this.usingFingerprintJS;
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* Get fingerprint info including method used and confidence
|
|
811
|
-
*/
|
|
812
|
-
async getFingerprintInfo() {
|
|
813
|
-
if (!this.config.enableFingerprinting) {
|
|
814
|
-
return {
|
|
815
|
-
fingerprint: '',
|
|
816
|
-
method: 'manual',
|
|
817
|
-
confidence: 0
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
// Try FingerprintJS
|
|
821
|
-
if (this.config.useFingerprintJS && this.isBrowser()) {
|
|
822
|
-
try {
|
|
823
|
-
const agent = await this.getFingerprintJSAgent();
|
|
824
|
-
if (agent) {
|
|
825
|
-
const result = await agent.get();
|
|
826
|
-
return {
|
|
827
|
-
fingerprint: result.visitorId,
|
|
828
|
-
method: 'fingerprintjs',
|
|
829
|
-
confidence: result.confidence?.score,
|
|
830
|
-
componentsCount: Object.keys(result.components || {}).length
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
catch {
|
|
835
|
-
// Fall through to manual
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
// Manual fallback
|
|
839
|
-
const fingerprint = await this.generateManualFingerprint();
|
|
840
|
-
return {
|
|
841
|
-
fingerprint,
|
|
842
|
-
method: 'manual',
|
|
843
|
-
confidence: 0.7, // Estimated accuracy for manual method
|
|
844
|
-
componentsCount: 15
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
exports.ContextCollector = ContextCollector;
|
|
849
|
-
/**
|
|
850
|
-
* Default context collector instance
|
|
851
|
-
*/
|
|
852
|
-
let defaultCollector = null;
|
|
853
|
-
/**
|
|
854
|
-
* Get or create the default context collector
|
|
855
|
-
*/
|
|
856
|
-
function getDefaultContextCollector(config) {
|
|
857
|
-
if (!defaultCollector) {
|
|
858
|
-
defaultCollector = new ContextCollector(config);
|
|
859
|
-
}
|
|
860
|
-
return defaultCollector;
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Collect browser context using the default collector
|
|
864
|
-
*/
|
|
865
|
-
async function collectBrowserContext(config) {
|
|
866
|
-
const collector = getDefaultContextCollector(config);
|
|
867
|
-
return collector.collect();
|
|
868
|
-
}
|