@tracetail/js 2.3.3 → 2.3.5

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Enterprise browser fingerprinting with **over 99.5% accuracy**. Zero dependencies, TypeScript support, and rock-solid stability.
4
4
 
5
- [![npm version](https://badge.fury.io/js/@tracetail/js.svg)](https://www.npmjs.com/package/@tracetail/js)
5
+ [![npm version](https://img.shields.io/npm/v/@tracetail/js.svg)](https://www.npmjs.com/package/@tracetail/js)
6
6
  [![Downloads](https://img.shields.io/npm/dm/@tracetail/js)](https://www.npmjs.com/package/@tracetail/js)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
8
  [![Size](https://img.shields.io/bundlephobia/minzip/@tracetail/js)](https://bundlephobia.com/package/@tracetail/js)
@@ -14,243 +14,334 @@ npm install @tracetail/js
14
14
  ```
15
15
 
16
16
  ```javascript
17
- import { TraceTail } from '@tracetail/js';
17
+ import TraceTail from '@tracetail/js';
18
18
 
19
- // Initialize
19
+ // Initialize with API key
20
20
  const tracetail = new TraceTail({
21
- apiKey: 'your-api-key'
21
+ apiKey: 'your-api-key',
22
+ debug: false
22
23
  });
23
24
 
24
25
  // Generate fingerprint
25
- const fingerprint = await tracetail.generateFingerprint();
26
- console.log(fingerprint.visitorId); // Unique visitor ID
26
+ const result = await tracetail.generateFingerprint();
27
+ console.log('Visitor ID:', result.visitorId);
28
+ console.log('Confidence:', (result.confidence * 100).toFixed(1) + '%');
27
29
  ```
28
30
 
29
31
  ## ✨ Features
30
32
 
31
33
  - **🎯 Over 99.5% accuracy** - Industry-leading device identification
32
- - **⚡ Zero dependencies** - Lightweight and fast (< 15KB gzipped)
34
+ - **⚡ Zero dependencies** - Lightweight and fast (27KB bundle)
33
35
  - **🔒 Enterprise security** - Bank-grade fraud detection
34
36
  - **📱 Cross-platform** - Works on all browsers and devices
35
37
  - **🔧 TypeScript** - Full type safety and IntelliSense
36
38
  - **🌐 Incognito consistent** - Same ID across normal and private browsing
37
- - **🚀 Performance optimized** - < 25ms fingerprint generation
39
+ - **🚀 Performance optimized** - <25ms fingerprint generation
38
40
 
39
- ## 📖 Documentation
41
+ ## 📖 API Reference
40
42
 
41
- ### Basic Usage
43
+ ### new TraceTail(options)
42
44
 
43
- ```javascript
44
- import { TraceTail } from '@tracetail/js';
45
+ Create a new TraceTail instance.
45
46
 
47
+ ```javascript
46
48
  const tracetail = new TraceTail({
47
- apiKey: 'tt_prod_...', // Get your API key at https://tracetail.io
48
- endpoint: 'https://api.tracetail.io' // Optional: custom endpoint
49
+ apiKey: 'tt_live_abc123xyz789', // Required: Your API key
50
+ endpoint: 'https://tracetail.io/api', // Optional: Custom endpoint
51
+ debug: true // Optional: Enable debug logging
49
52
  });
53
+ ```
54
+
55
+ **Parameters:**
56
+ - `apiKey` (string): Your TraceTail API key (required)
57
+ - `endpoint` (string): Custom API endpoint (optional)
58
+ - `debug` (boolean): Enable debug logging (optional)
59
+
60
+ ### tracetail.generateFingerprint(options?)
61
+
62
+ Generate a browser fingerprint with visitor identification.
50
63
 
51
- // Generate basic fingerprint
64
+ ```javascript
65
+ // Basic usage
52
66
  const result = await tracetail.generateFingerprint();
53
67
 
54
- console.log({
55
- visitorId: result.visitorId, // Unique visitor identifier
56
- confidence: result.confidence, // Accuracy confidence score
57
- components: result.components // Raw fingerprint components
68
+ // With detailed components
69
+ const detailedResult = await tracetail.generateFingerprint({
70
+ verbose: true // Include raw component data
58
71
  });
59
72
  ```
60
73
 
61
- ### Advanced Usage
74
+ **Parameters:**
75
+ - `options.verbose` (boolean): Include detailed component data
76
+
77
+ **Returns:** `Promise<FingerprintResult>`
78
+
79
+ ```typescript
80
+ interface FingerprintResult {
81
+ visitorId: string; // Unique visitor identifier
82
+ confidence: number; // Accuracy confidence (0-1)
83
+ processingTime: number; // Generation time in milliseconds
84
+ timestamp: string; // ISO timestamp
85
+ version: string; // SDK version (2.3.3)
86
+ signalCount: number; // Number of signals collected
87
+ components?: ComponentData; // Raw signal data (verbose mode)
88
+ }
89
+ ```
90
+
91
+ ### TraceTail.version
92
+
93
+ Get the SDK version (static property).
94
+
95
+ ```javascript
96
+ console.log('SDK Version:', TraceTail.version); // "2.3.3"
97
+ ```
98
+
99
+ ## 🎯 Usage Examples
100
+
101
+ ### Basic Visitor Identification
62
102
 
63
103
  ```javascript
64
- // Enhanced fingerprint with fraud detection
65
- const enhanced = await tracetail.generateEnhancedFingerprint({
66
- timeout: 5000, // Max generation time
67
- includeComponents: true, // Include raw components
68
- fraudDetection: true // Enable fraud analysis
104
+ import TraceTail from '@tracetail/js';
105
+
106
+ // Initialize once
107
+ const tracetail = new TraceTail({
108
+ apiKey: process.env.TRACETAIL_API_KEY
69
109
  });
70
110
 
111
+ // Use throughout your app
112
+ async function identifyVisitor() {
113
+ try {
114
+ const result = await tracetail.generateFingerprint();
115
+
116
+ console.log({
117
+ visitorId: result.visitorId, // "fp_abc123def456"
118
+ confidence: result.confidence, // 0.995 (99.5% accurate)
119
+ processingTime: result.processingTime, // 18ms
120
+ signalCount: result.signalCount // 45 signals collected
121
+ });
122
+
123
+ return result.visitorId;
124
+ } catch (error) {
125
+ console.error('Fingerprinting failed:', error);
126
+ return null;
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Advanced Usage with Components
132
+
133
+ ```javascript
134
+ // Get detailed fingerprint data
135
+ const detailed = await tracetail.generateFingerprint({ verbose: true });
136
+
71
137
  console.log({
72
- visitorId: enhanced.visitorId,
73
- riskScore: enhanced.riskScore, // 0-1 fraud risk score
74
- threatLevel: enhanced.threatLevel, // 'low', 'medium', 'high'
75
- geolocation: enhanced.geolocation, // Detected location
76
- botProbability: enhanced.botProbability // Bot detection score
138
+ visitorId: detailed.visitorId,
139
+ confidence: detailed.confidence,
140
+ components: {
141
+ canvas: detailed.components.canvas, // Canvas fingerprint
142
+ webgl: detailed.components.webgl, // WebGL parameters
143
+ audio: detailed.components.audio, // Audio context
144
+ fonts: detailed.components.fonts, // Font detection
145
+ hardware: detailed.components.hardware // Hardware info
146
+ }
77
147
  });
78
148
  ```
79
149
 
80
- ### Configuration Options
150
+ ### Error Handling
81
151
 
82
152
  ```javascript
83
- const tracetail = new TraceTail({
84
- apiKey: 'your-api-key',
153
+ async function safeFingerprinting() {
154
+ try {
155
+ const result = await tracetail.generateFingerprint();
156
+
157
+ if (result.confidence > 0.9) {
158
+ // High confidence - reliable identification
159
+ return result.visitorId;
160
+ } else {
161
+ // Lower confidence - may want to retry or use fallback
162
+ console.warn('Lower confidence fingerprint');
163
+ return result.visitorId;
164
+ }
165
+ } catch (error) {
166
+ console.error('Fingerprinting error:', error);
167
+
168
+ // Implement fallback identification
169
+ return generateFallbackId();
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### Caching Results
175
+
176
+ ```javascript
177
+ const CACHE_KEY = 'tracetail_visitor_id';
178
+ const CACHE_TTL = 3600000; // 1 hour
179
+
180
+ async function getCachedFingerprint() {
181
+ // Check cache first
182
+ const cached = localStorage.getItem(CACHE_KEY);
183
+ if (cached) {
184
+ const data = JSON.parse(cached);
185
+ if (Date.now() - data.timestamp < CACHE_TTL) {
186
+ return data.visitorId;
187
+ }
188
+ }
85
189
 
86
- // Optional settings
87
- endpoint: 'https://api.tracetail.io',
88
- timeout: 10000,
89
- debug: false,
90
- caching: true,
190
+ // Generate new fingerprint
191
+ const result = await tracetail.generateFingerprint();
91
192
 
92
- // Privacy settings
93
- respectDNT: false, // Honor Do Not Track
94
- includeIP: true, // Include IP in analysis
95
- storeFingerprints: true, // Cache fingerprints locally
193
+ // Cache result
194
+ localStorage.setItem(CACHE_KEY, JSON.stringify({
195
+ visitorId: result.visitorId,
196
+ timestamp: Date.now()
197
+ }));
96
198
 
97
- // Performance settings
98
- enableWorkers: true, // Use web workers
99
- batchRequests: true, // Batch multiple calls
100
- fallbackMode: 'basic' // Fallback when enhanced fails
101
- });
199
+ return result.visitorId;
200
+ }
102
201
  ```
103
202
 
104
- ## 🔧 API Reference
203
+ ## 🌐 CDN Usage
105
204
 
106
- ### `TraceTail` Class
205
+ For quick integration without NPM:
107
206
 
108
- #### `new TraceTail(options)`
207
+ ```html
208
+ <script src="https://tracetail.io/sdk.js"
209
+ data-api-key="your-api-key"></script>
210
+ <script>
211
+ TraceTail.generateFingerprint().then(result => {
212
+ console.log('Visitor ID:', result.visitorId);
213
+ console.log('Confidence:', (result.confidence * 100).toFixed(1) + '%');
214
+ });
215
+ </script>
216
+ ```
109
217
 
110
- Create a new TraceTail instance.
218
+ ## 🔧 TypeScript Support
111
219
 
112
- **Parameters:**
113
- - `options.apiKey` (string, required) - Your TraceTail API key
114
- - `options.endpoint` (string, optional) - Custom API endpoint
115
- - `options.timeout` (number, optional) - Request timeout in milliseconds
116
- - `options.debug` (boolean, optional) - Enable debug logging
220
+ Full TypeScript support with comprehensive type definitions:
117
221
 
118
- #### `generateFingerprint(options?)`
222
+ ```typescript
223
+ import TraceTail, { FingerprintResult, ComponentData } from '@tracetail/js';
119
224
 
120
- Generate a basic browser fingerprint.
225
+ // Initialize with types
226
+ const tracetail = new TraceTail({
227
+ apiKey: process.env.TRACETAIL_API_KEY!,
228
+ debug: process.env.NODE_ENV !== 'production'
229
+ });
121
230
 
122
- **Returns:** `Promise<FingerprintResult>`
231
+ // Typed results
232
+ const result: FingerprintResult = await tracetail.generateFingerprint();
123
233
 
124
- ```typescript
125
- interface FingerprintResult {
126
- visitorId: string;
127
- confidence: number;
128
- processingTime: number;
129
- components?: Record<string, any>;
234
+ // Type-safe component access
235
+ if (result.components) {
236
+ const canvas: ComponentData = result.components.canvas;
237
+ const webgl: ComponentData = result.components.webgl;
130
238
  }
131
239
  ```
132
240
 
133
- #### `generateEnhancedFingerprint(options?)`
241
+ ## 🎭 Framework Integrations
134
242
 
135
- Generate an enhanced fingerprint with fraud detection.
243
+ ### React
136
244
 
137
- **Returns:** `Promise<EnhancedFingerprintResult>`
138
-
139
- ```typescript
140
- interface EnhancedFingerprintResult extends FingerprintResult {
141
- riskScore: number;
142
- threatLevel: 'low' | 'medium' | 'high';
143
- geolocation?: GeolocationData;
144
- botProbability: number;
145
- fraudSignals: string[];
146
- }
245
+ ```bash
246
+ npm install @tracetail/react
147
247
  ```
148
248
 
149
- ## 🌟 Use Cases
150
-
151
- ### Fraud Detection
249
+ ```tsx
250
+ import { useTraceTail } from '@tracetail/react';
152
251
 
153
- ```javascript
154
- const result = await tracetail.generateEnhancedFingerprint();
155
-
156
- if (result.riskScore > 0.7) {
157
- // High risk - require additional verification
158
- showAdditionalVerification();
159
- } else if (result.riskScore > 0.3) {
160
- // Medium risk - monitor closely
161
- enableExtendedMonitoring();
252
+ function App() {
253
+ const { fingerprint, loading } = useTraceTail({
254
+ apiKey: 'your-api-key'
255
+ });
256
+
257
+ return <div>Visitor: {fingerprint?.visitorId}</div>;
162
258
  }
163
259
  ```
164
260
 
165
- ### User Analytics
261
+ ### Vue 3
166
262
 
167
- ```javascript
168
- const fingerprint = await tracetail.generateFingerprint();
169
-
170
- // Track unique visitors without cookies
171
- analytics.track('page_view', {
172
- visitorId: fingerprint.visitorId,
173
- page: window.location.pathname
174
- });
263
+ ```bash
264
+ npm install @tracetail/vue
175
265
  ```
176
266
 
177
- ### Account Security
267
+ ```vue
268
+ <script setup>
269
+ import { useFingerprint } from '@tracetail/vue';
178
270
 
179
- ```javascript
180
- // Detect account sharing or takeover
181
- const currentFingerprint = await tracetail.generateFingerprint();
182
- const storedFingerprint = localStorage.getItem('userFingerprint');
271
+ const { visitorId, loading } = useFingerprint();
272
+ </script>
183
273
 
184
- if (currentFingerprint.visitorId !== storedFingerprint) {
185
- // Different device - verify identity
186
- requireAdditionalAuth();
187
- }
274
+ <template>
275
+ <div>Visitor: {{ visitorId }}</div>
276
+ </template>
188
277
  ```
189
278
 
190
- ## 🔒 Privacy & Compliance
191
-
192
- TraceTail is designed with privacy in mind:
279
+ ### Angular
193
280
 
194
- - **GDPR compliant** - No personal data collection
195
- - **Respectful** - Honors Do Not Track when configured
196
- - **Transparent** - Clear data usage policies
197
- - **Secure** - All data encrypted in transit and at rest
281
+ ```bash
282
+ npm install @tracetail/angular
283
+ ```
198
284
 
199
- ## 🚀 Performance
285
+ ```typescript
286
+ import { TraceTailService } from '@tracetail/angular';
200
287
 
201
- - **< 25ms** fingerprint generation
202
- - **< 15KB** gzipped bundle size
203
- - **Zero dependencies** for maximum compatibility
204
- - **Web Worker support** for non-blocking execution
205
- - **Caching** for repeat visitors
288
+ @Component({
289
+ template: `<div>Visitor: {{ fingerprint$ | async | json }}</div>`
290
+ })
291
+ export class MyComponent {
292
+ fingerprint$ = this.traceTail.fingerprint$;
293
+
294
+ constructor(private traceTail: TraceTailService) {}
295
+ }
296
+ ```
206
297
 
207
- ## 📊 Browser Support
298
+ ## 🛠️ Development
208
299
 
209
- - Chrome 80+
210
- - Firefox 75+
211
- - Safari 13+
212
- - Edge 80+
213
- - Mobile browsers (iOS Safari 13+, Chrome Mobile 80+)
300
+ ```bash
301
+ # Clone repository
302
+ git clone https://github.com/tracetail/tracetail.git
214
303
 
215
- ## 🛠️ Migration Guide
304
+ # Install dependencies
305
+ npm install
216
306
 
217
- ### From FingerprintJS
307
+ # Run tests
308
+ npm test
218
309
 
219
- ```javascript
220
- // Before (FingerprintJS)
221
- import FingerprintJS from '@fingerprintjs/fingerprintjs';
222
- const fp = await FingerprintJS.load();
223
- const result = await fp.get();
224
-
225
- // After (TraceTail)
226
- import { TraceTail } from '@tracetail/js';
227
- const tracetail = new TraceTail({ apiKey: 'your-key' });
228
- const result = await tracetail.generateFingerprint();
310
+ # Build package
311
+ npm run build
229
312
  ```
230
313
 
231
- ### From Custom Solution
314
+ ## 📊 Performance
232
315
 
233
- ```javascript
234
- // Replace your custom fingerprinting
235
- const tracetail = new TraceTail({ apiKey: 'your-key' });
236
- const fingerprint = await tracetail.generateFingerprint();
316
+ - **Bundle Size**: 27KB minified + gzipped
317
+ - **Generation Time**: <25ms average
318
+ - **Accuracy**: >99.5% unique identification
319
+ - **Browser Support**: All modern browsers + IE11
320
+ - **Caching**: Automatic 1-hour local caching
237
321
 
238
- // Same visitor ID across sessions and devices
239
- const visitorId = fingerprint.visitorId;
240
- ```
322
+ ## 🔒 Privacy & Security
323
+
324
+ - **GDPR Compliant**: No PII collected by default
325
+ - **Secure**: Client-side processing with optional server enhancement
326
+ - **Transparent**: Open source and auditable
327
+ - **Configurable**: Respect user privacy preferences
241
328
 
242
- ## 📞 Support
329
+ ## 📈 Use Cases
243
330
 
244
- - **Documentation:** https://tracetail.io/docs
245
- - **API Reference:** https://tracetail.io/api-docs
246
- - **Support:** https://tracetail.io/support
247
- - **Discord:** https://discord.gg/tracetail
248
- - **Email:** support@tracetail.io
331
+ - **Fraud Prevention**: Detect suspicious users and prevent account takeovers
332
+ - **Analytics**: Track unique visitors without cookies
333
+ - **Personalization**: Provide consistent experience across sessions
334
+ - **Security**: Implement device-based authentication
335
+ - **A/B Testing**: Ensure consistent test groups
249
336
 
250
- ## 📝 License
337
+ ## 🆘 Support
251
338
 
252
- MIT License - see [LICENSE](LICENSE) file for details.
339
+ - **Documentation**: [docs.tracetail.io](https://docs.tracetail.io)
340
+ - **API Reference**: [tracetail.io/api/docs](https://tracetail.io/api/docs)
341
+ - **GitHub Issues**: [github.com/tracetail/tracetail/issues](https://github.com/tracetail/tracetail/issues)
342
+ - **Email**: support@tracetail.com
343
+ - **Discord**: [discord.gg/tracetail](https://discord.gg/tracetail)
253
344
 
254
- ---
345
+ ## 📄 License
255
346
 
256
- **[Get your API key](https://tracetail.io/auth) • [View Documentation](https://tracetail.io/docs) [Try Live Demo](https://tracetail.io/live-demo)**
347
+ MIT - see [LICENSE](LICENSE) for details.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @tracetail/js - Enterprise Browser Fingerprinting SDK
3
- * Version: 2.3.0
3
+ * Version: 2.3.4
4
4
  *
5
- * Over 99.5% accuracy browser fingerprinting with zero dependencies.
5
+ * Over 99.5% accuracy browser fingerprinting with server-side processing.
6
6
  * Perfect for fraud detection, user analytics, and security applications.
7
7
  */
8
8
  export interface FingerprintOptions {
@@ -24,18 +24,6 @@ export interface FingerprintResult {
24
24
  processingTime: number;
25
25
  components?: ComponentData;
26
26
  }
27
- export interface EnhancedFingerprintResult extends FingerprintResult {
28
- riskScore: number;
29
- threatLevel: 'low' | 'medium' | 'high';
30
- geolocation?: {
31
- country?: string;
32
- region?: string;
33
- city?: string;
34
- timezone?: string;
35
- };
36
- botProbability: number;
37
- fraudSignals: string[];
38
- }
39
27
  export interface ComponentData {
40
28
  canvas?: string;
41
29
  webgl?: string;
@@ -74,26 +62,20 @@ export declare class TraceTail {
74
62
  private cache;
75
63
  constructor(options: FingerprintOptions);
76
64
  /**
77
- * Generate a basic browser fingerprint
65
+ * Generate a browser fingerprint with server-side processing
78
66
  *
79
67
  * @param options Optional generation parameters
80
68
  * @returns Promise resolving to fingerprint result
81
69
  */
82
70
  generateFingerprint(options?: {
83
71
  timeout?: number;
84
- includeComponents?: boolean;
72
+ verbose?: boolean;
85
73
  }): Promise<FingerprintResult>;
86
74
  /**
87
- * Generate an enhanced fingerprint with fraud detection
88
- *
89
- * @param options Optional generation parameters
90
- * @returns Promise resolving to enhanced fingerprint result
75
+ * Send fingerprint components to server for enhanced processing
76
+ * @private
91
77
  */
92
- generateEnhancedFingerprint(options?: {
93
- timeout?: number;
94
- includeComponents?: boolean;
95
- fraudDetection?: boolean;
96
- }): Promise<EnhancedFingerprintResult>;
78
+ private sendToServer;
97
79
  /**
98
80
  * Get the current SDK version
99
81
  */
@@ -105,7 +87,6 @@ export declare class TraceTail {
105
87
  private collectComponents;
106
88
  private generateVisitorId;
107
89
  private calculateConfidence;
108
- private performServerAnalysis;
109
90
  private generateCanvasFingerprint;
110
91
  private generateWebGLFingerprint;
111
92
  private generateAudioFingerprint;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class e{constructor(e){if(this.cache=new Map,!e.apiKey)throw new Error("TraceTail: API key is required. Get yours at https://tracetail.io");this.options={apiKey:e.apiKey,endpoint:e.endpoint||"https://api.tracetail.io",timeout:e.timeout||1e4,debug:e.debug||!1,caching:!1!==e.caching,respectDNT:e.respectDNT||!1,includeIP:!1!==e.includeIP,storeFingerprints:!1!==e.storeFingerprints,enableWorkers:!1!==e.enableWorkers,batchRequests:!1!==e.batchRequests,fallbackMode:e.fallbackMode||"basic"},this.log("TraceTail SDK initialized",{version:"2.3.0",options:this.options})}async generateFingerprint(e){const t=performance.now();try{if(this.options.respectDNT&&this.isDNTEnabled())throw new Error("TraceTail: Do Not Track is enabled");const n="basic_fingerprint";if(this.options.caching&&this.cache.has(n)){const e=this.cache.get(n);return this.log("Using cached fingerprint",e),e}const r=await this.collectComponents(),i=await this.generateVisitorId(r),o={visitorId:i,confidence:this.calculateConfidence(r),processingTime:Math.round(performance.now()-t),...(null==e?void 0:e.includeComponents)&&{components:r}};return this.options.caching&&this.cache.set(n,o),this.log("Fingerprint generated",o),o}catch(e){throw this.log("Fingerprint generation failed",e),e}}async generateEnhancedFingerprint(e){const t=await this.generateFingerprint(e);try{const n=await this.performServerAnalysis(t,e);return{...t,...n}}catch(e){return this.log("Enhanced analysis failed, falling back to basic",e),{...t,riskScore:.1,threatLevel:"low",botProbability:.05,fraudSignals:[]}}}static getVersion(){return"2.3.0"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}async collectComponents(){const e={};try{e.screen={width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},e.timezone=Intl.DateTimeFormat().resolvedOptions().timeZone,e.language=navigator.language,e.platform=navigator.platform,e.userAgent=navigator.userAgent,e.cookiesEnabled=navigator.cookieEnabled,e.localStorage=this.testStorage("localStorage"),e.sessionStorage=this.testStorage("sessionStorage"),e.indexedDB="indexedDB"in window,e.canvas=await this.generateCanvasFingerprint(),e.webgl=this.generateWebGLFingerprint(),e.audio=await this.generateAudioFingerprint(),e.fonts=this.detectFonts(),e.webRTC=await this.generateWebRTCFingerprint()}catch(e){this.log("Component collection error",e)}return e}async generateVisitorId(e){const t=JSON.stringify(e,Object.keys(e).sort()),n=(new TextEncoder).encode(t),r=await crypto.subtle.digest("SHA-256",n);return`fp_${Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}`}calculateConfidence(e){let t=0,n=0;const r={canvas:.25,webgl:.2,audio:.15,fonts:.15,screen:.1,webRTC:.15};for(const[i,o]of Object.entries(r))n+=o,e[i]&&(t+=o);return Math.min(.995,Math.max(.7,t/n))}async performServerAnalysis(e,t){const n=await fetch(`${this.options.endpoint}/analyze`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:JSON.stringify({visitorId:e.visitorId,components:e.components,fraudDetection:!1!==(null==t?void 0:t.fraudDetection)})});if(!n.ok)throw new Error(`Server analysis failed: ${n.status}`);return await n.json()}async generateCanvasFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("2d");return t?(e.width=200,e.height=50,t.textBaseline="top",t.font="14px Arial",t.fillStyle="#f60",t.fillRect(125,1,62,20),t.fillStyle="#069",t.fillText("TraceTail 🔒",2,15),t.fillStyle="rgba(102, 204, 0, 0.2)",t.fillText("TraceTail 🔒",4,17),e.toDataURL().substring(0,100)):"unsupported"}catch(e){return"error"}}generateWebGLFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return"unsupported";const n=t.getParameter(t.RENDERER);return`${t.getParameter(t.VENDOR)}-${n}`.substring(0,100)}catch(e){return"error"}}async generateAudioFingerprint(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return"unsupported";const t=new e,n=t.createOscillator(),r=t.createAnalyser();n.connect(r),r.connect(t.destination);const i=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(i),await t.close(),Array.from(i.slice(0,10)).join(",")}catch(e){return"error"}}detectFonts(){const e=["Arial","Helvetica","Times","Courier","Verdana","Georgia","Palatino","Garamond","Bookman","Comic Sans MS","Trebuchet MS","Arial Black","Impact"],t=[],n=document.createElement("div");n.style.position="absolute",n.style.left="-9999px",document.body.appendChild(n);try{for(const r of e){const e=document.createElement("span");e.style.fontSize="72px",e.style.fontFamily=r,e.textContent="mmmmmmmmmmlli",n.appendChild(e),e.offsetWidth>0&&t.push(r)}}finally{document.body.removeChild(n)}return t}async generateWebRTCFingerprint(){var e;try{if(!window.RTCPeerConnection)return"unsupported";const t=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});t.createDataChannel("test");await t.createOffer();const n=t.localDescription;return t.close(),(null===(e=null==n?void 0:n.sdp)||void 0===e?void 0:e.substring(0,100))||"error"}catch(e){return"error"}}testStorage(e){try{const t=window[e],n="__tt_test__";return t.setItem(n,"test"),t.removeItem(n),!0}catch(e){return!1}}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}exports.TraceTail=e,exports.default=e;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});class e{constructor(e){if(this.cache=new Map,!e.apiKey)throw new Error("TraceTail: API key is required. Get yours at https://tracetail.io");this.options={apiKey:e.apiKey,endpoint:e.endpoint||"https://tracetail.io/api",timeout:e.timeout||1e4,debug:e.debug||!1,caching:!1!==e.caching,respectDNT:e.respectDNT||!1,includeIP:!1!==e.includeIP,storeFingerprints:!1!==e.storeFingerprints,enableWorkers:!1!==e.enableWorkers,batchRequests:!1!==e.batchRequests,fallbackMode:e.fallbackMode||"basic"},this.log("TraceTail SDK initialized",{version:"2.3.4",options:this.options})}async generateFingerprint(e){const t=performance.now();try{if(this.options.respectDNT&&this.isDNTEnabled())throw new Error("TraceTail: Do Not Track is enabled");const n="basic_fingerprint";if(this.options.caching&&this.cache.has(n)){const e=this.cache.get(n);if(e)return this.log("Using cached fingerprint",e),e}const r=await this.collectComponents(),i=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint)try{const i=await this.sendToServer(r,null==e?void 0:e.verbose);if(i){const e={...i,processingTime:Math.round(performance.now()-t)};return this.options.caching&&this.cache.set(n,e),this.log("Server-enhanced fingerprint generated",e),e}}catch(e){this.log("Server processing failed, falling back to client-side",e)}const o=await this.generateVisitorId(r),a={visitorId:o,confidence:this.calculateConfidence(r),processingTime:i,...(null==e?void 0:e.verbose)&&{components:r}};return this.options.caching&&this.cache.set(n,a),this.log("Client-side fingerprint generated",a),a}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){try{const n=new AbortController,r=setTimeout(()=>n.abort(),this.options.timeout),i=await fetch(`${this.options.endpoint}/id`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`,"X-TraceTail-SDK":"js/2.3.4"},body:JSON.stringify({components:e,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.4"}}),signal:n.signal});if(clearTimeout(r),!i.ok){if(401===i.status)throw new Error("Invalid API key");throw new Error(`Server returned ${i.status}`)}const o=await i.json();return{visitorId:o.visitorId,confidence:o.confidence,processingTime:o.processingTime||0,...t&&o.components&&{components:o.components}}}catch(e){return this.options.debug&&console.error("[TraceTail] Server error:",e),null}}static getVersion(){return"2.3.4"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}async collectComponents(){const e={};try{e.screen={width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},e.timezone=Intl.DateTimeFormat().resolvedOptions().timeZone,e.language=navigator.language,e.platform=navigator.platform,e.userAgent=navigator.userAgent,e.cookiesEnabled=navigator.cookieEnabled,e.localStorage=this.testStorage("localStorage"),e.sessionStorage=this.testStorage("sessionStorage"),e.indexedDB="indexedDB"in window,e.canvas=await this.generateCanvasFingerprint(),e.webgl=this.generateWebGLFingerprint(),e.audio=await this.generateAudioFingerprint(),e.fonts=this.detectFonts(),e.webRTC=await this.generateWebRTCFingerprint()}catch(e){this.log("Component collection error",e)}return e}async generateVisitorId(e){const t=JSON.stringify(e,Object.keys(e).sort()),n=(new TextEncoder).encode(t),r=await crypto.subtle.digest("SHA-256",n);return`fp_${Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}`}calculateConfidence(e){let t=0,n=0;const r={canvas:.25,webgl:.2,audio:.15,fonts:.15,screen:.1,webRTC:.15};for(const[i,o]of Object.entries(r))n+=o,e[i]&&(t+=o);return Math.min(.995,Math.max(.7,t/n))}async generateCanvasFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("2d");return t?(e.width=200,e.height=50,t.textBaseline="top",t.font="14px Arial",t.fillStyle="#f60",t.fillRect(125,1,62,20),t.fillStyle="#069",t.fillText("TraceTail 🔒",2,15),t.fillStyle="rgba(102, 204, 0, 0.2)",t.fillText("TraceTail 🔒",4,17),e.toDataURL().substring(0,100)):"unsupported"}catch(e){return"error"}}generateWebGLFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return"unsupported";const n=t.getParameter(t.RENDERER);return`${t.getParameter(t.VENDOR)}-${n}`.substring(0,100)}catch(e){return"error"}}async generateAudioFingerprint(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return"unsupported";const t=new e,n=t.createOscillator(),r=t.createAnalyser();n.connect(r),r.connect(t.destination);const i=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(i),await t.close(),Array.from(i.slice(0,10)).join(",")}catch(e){return"error"}}detectFonts(){const e=["Arial","Helvetica","Times","Courier","Verdana","Georgia","Palatino","Garamond","Bookman","Comic Sans MS","Trebuchet MS","Arial Black","Impact"],t=[],n=document.createElement("div");n.style.position="absolute",n.style.left="-9999px",document.body.appendChild(n);try{for(const r of e){const e=document.createElement("span");e.style.fontSize="72px",e.style.fontFamily=r,e.textContent="mmmmmmmmmmlli",n.appendChild(e),e.offsetWidth>0&&t.push(r)}}finally{document.body.removeChild(n)}return t}async generateWebRTCFingerprint(){var e;try{if(!window.RTCPeerConnection)return"unsupported";const t=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});t.createDataChannel("test");await t.createOffer();const n=t.localDescription;return t.close(),(null===(e=null==n?void 0:n.sdp)||void 0===e?void 0:e.substring(0,100))||"error"}catch(e){return"error"}}testStorage(e){try{const t=window[e],n="__tt_test__";return t.setItem(n,"test"),t.removeItem(n),!0}catch(e){return!1}}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}exports.TraceTail=e,exports.default=e;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- class e{constructor(e){if(this.cache=new Map,!e.apiKey)throw new Error("TraceTail: API key is required. Get yours at https://tracetail.io");this.options={apiKey:e.apiKey,endpoint:e.endpoint||"https://api.tracetail.io",timeout:e.timeout||1e4,debug:e.debug||!1,caching:!1!==e.caching,respectDNT:e.respectDNT||!1,includeIP:!1!==e.includeIP,storeFingerprints:!1!==e.storeFingerprints,enableWorkers:!1!==e.enableWorkers,batchRequests:!1!==e.batchRequests,fallbackMode:e.fallbackMode||"basic"},this.log("TraceTail SDK initialized",{version:"2.3.0",options:this.options})}async generateFingerprint(e){const t=performance.now();try{if(this.options.respectDNT&&this.isDNTEnabled())throw new Error("TraceTail: Do Not Track is enabled");const n="basic_fingerprint";if(this.options.caching&&this.cache.has(n)){const e=this.cache.get(n);return this.log("Using cached fingerprint",e),e}const r=await this.collectComponents(),i=await this.generateVisitorId(r),o={visitorId:i,confidence:this.calculateConfidence(r),processingTime:Math.round(performance.now()-t),...(null==e?void 0:e.includeComponents)&&{components:r}};return this.options.caching&&this.cache.set(n,o),this.log("Fingerprint generated",o),o}catch(e){throw this.log("Fingerprint generation failed",e),e}}async generateEnhancedFingerprint(e){const t=await this.generateFingerprint(e);try{const n=await this.performServerAnalysis(t,e);return{...t,...n}}catch(e){return this.log("Enhanced analysis failed, falling back to basic",e),{...t,riskScore:.1,threatLevel:"low",botProbability:.05,fraudSignals:[]}}}static getVersion(){return"2.3.0"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}async collectComponents(){const e={};try{e.screen={width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},e.timezone=Intl.DateTimeFormat().resolvedOptions().timeZone,e.language=navigator.language,e.platform=navigator.platform,e.userAgent=navigator.userAgent,e.cookiesEnabled=navigator.cookieEnabled,e.localStorage=this.testStorage("localStorage"),e.sessionStorage=this.testStorage("sessionStorage"),e.indexedDB="indexedDB"in window,e.canvas=await this.generateCanvasFingerprint(),e.webgl=this.generateWebGLFingerprint(),e.audio=await this.generateAudioFingerprint(),e.fonts=this.detectFonts(),e.webRTC=await this.generateWebRTCFingerprint()}catch(e){this.log("Component collection error",e)}return e}async generateVisitorId(e){const t=JSON.stringify(e,Object.keys(e).sort()),n=(new TextEncoder).encode(t),r=await crypto.subtle.digest("SHA-256",n);return`fp_${Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}`}calculateConfidence(e){let t=0,n=0;const r={canvas:.25,webgl:.2,audio:.15,fonts:.15,screen:.1,webRTC:.15};for(const[i,o]of Object.entries(r))n+=o,e[i]&&(t+=o);return Math.min(.995,Math.max(.7,t/n))}async performServerAnalysis(e,t){const n=await fetch(`${this.options.endpoint}/analyze`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:JSON.stringify({visitorId:e.visitorId,components:e.components,fraudDetection:!1!==(null==t?void 0:t.fraudDetection)})});if(!n.ok)throw new Error(`Server analysis failed: ${n.status}`);return await n.json()}async generateCanvasFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("2d");return t?(e.width=200,e.height=50,t.textBaseline="top",t.font="14px Arial",t.fillStyle="#f60",t.fillRect(125,1,62,20),t.fillStyle="#069",t.fillText("TraceTail 🔒",2,15),t.fillStyle="rgba(102, 204, 0, 0.2)",t.fillText("TraceTail 🔒",4,17),e.toDataURL().substring(0,100)):"unsupported"}catch(e){return"error"}}generateWebGLFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return"unsupported";const n=t.getParameter(t.RENDERER);return`${t.getParameter(t.VENDOR)}-${n}`.substring(0,100)}catch(e){return"error"}}async generateAudioFingerprint(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return"unsupported";const t=new e,n=t.createOscillator(),r=t.createAnalyser();n.connect(r),r.connect(t.destination);const i=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(i),await t.close(),Array.from(i.slice(0,10)).join(",")}catch(e){return"error"}}detectFonts(){const e=["Arial","Helvetica","Times","Courier","Verdana","Georgia","Palatino","Garamond","Bookman","Comic Sans MS","Trebuchet MS","Arial Black","Impact"],t=[],n=document.createElement("div");n.style.position="absolute",n.style.left="-9999px",document.body.appendChild(n);try{for(const r of e){const e=document.createElement("span");e.style.fontSize="72px",e.style.fontFamily=r,e.textContent="mmmmmmmmmmlli",n.appendChild(e),e.offsetWidth>0&&t.push(r)}}finally{document.body.removeChild(n)}return t}async generateWebRTCFingerprint(){var e;try{if(!window.RTCPeerConnection)return"unsupported";const t=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});t.createDataChannel("test");await t.createOffer();const n=t.localDescription;return t.close(),(null===(e=null==n?void 0:n.sdp)||void 0===e?void 0:e.substring(0,100))||"error"}catch(e){return"error"}}testStorage(e){try{const t=window[e],n="__tt_test__";return t.setItem(n,"test"),t.removeItem(n),!0}catch(e){return!1}}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}export{e as TraceTail,e as default};
1
+ class e{constructor(e){if(this.cache=new Map,!e.apiKey)throw new Error("TraceTail: API key is required. Get yours at https://tracetail.io");this.options={apiKey:e.apiKey,endpoint:e.endpoint||"https://tracetail.io/api",timeout:e.timeout||1e4,debug:e.debug||!1,caching:!1!==e.caching,respectDNT:e.respectDNT||!1,includeIP:!1!==e.includeIP,storeFingerprints:!1!==e.storeFingerprints,enableWorkers:!1!==e.enableWorkers,batchRequests:!1!==e.batchRequests,fallbackMode:e.fallbackMode||"basic"},this.log("TraceTail SDK initialized",{version:"2.3.4",options:this.options})}async generateFingerprint(e){const t=performance.now();try{if(this.options.respectDNT&&this.isDNTEnabled())throw new Error("TraceTail: Do Not Track is enabled");const n="basic_fingerprint";if(this.options.caching&&this.cache.has(n)){const e=this.cache.get(n);if(e)return this.log("Using cached fingerprint",e),e}const r=await this.collectComponents(),i=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint)try{const i=await this.sendToServer(r,null==e?void 0:e.verbose);if(i){const e={...i,processingTime:Math.round(performance.now()-t)};return this.options.caching&&this.cache.set(n,e),this.log("Server-enhanced fingerprint generated",e),e}}catch(e){this.log("Server processing failed, falling back to client-side",e)}const o=await this.generateVisitorId(r),a={visitorId:o,confidence:this.calculateConfidence(r),processingTime:i,...(null==e?void 0:e.verbose)&&{components:r}};return this.options.caching&&this.cache.set(n,a),this.log("Client-side fingerprint generated",a),a}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){try{const n=new AbortController,r=setTimeout(()=>n.abort(),this.options.timeout),i=await fetch(`${this.options.endpoint}/id`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`,"X-TraceTail-SDK":"js/2.3.4"},body:JSON.stringify({components:e,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.4"}}),signal:n.signal});if(clearTimeout(r),!i.ok){if(401===i.status)throw new Error("Invalid API key");throw new Error(`Server returned ${i.status}`)}const o=await i.json();return{visitorId:o.visitorId,confidence:o.confidence,processingTime:o.processingTime||0,...t&&o.components&&{components:o.components}}}catch(e){return this.options.debug&&console.error("[TraceTail] Server error:",e),null}}static getVersion(){return"2.3.4"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}async collectComponents(){const e={};try{e.screen={width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},e.timezone=Intl.DateTimeFormat().resolvedOptions().timeZone,e.language=navigator.language,e.platform=navigator.platform,e.userAgent=navigator.userAgent,e.cookiesEnabled=navigator.cookieEnabled,e.localStorage=this.testStorage("localStorage"),e.sessionStorage=this.testStorage("sessionStorage"),e.indexedDB="indexedDB"in window,e.canvas=await this.generateCanvasFingerprint(),e.webgl=this.generateWebGLFingerprint(),e.audio=await this.generateAudioFingerprint(),e.fonts=this.detectFonts(),e.webRTC=await this.generateWebRTCFingerprint()}catch(e){this.log("Component collection error",e)}return e}async generateVisitorId(e){const t=JSON.stringify(e,Object.keys(e).sort()),n=(new TextEncoder).encode(t),r=await crypto.subtle.digest("SHA-256",n);return`fp_${Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}`}calculateConfidence(e){let t=0,n=0;const r={canvas:.25,webgl:.2,audio:.15,fonts:.15,screen:.1,webRTC:.15};for(const[i,o]of Object.entries(r))n+=o,e[i]&&(t+=o);return Math.min(.995,Math.max(.7,t/n))}async generateCanvasFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("2d");return t?(e.width=200,e.height=50,t.textBaseline="top",t.font="14px Arial",t.fillStyle="#f60",t.fillRect(125,1,62,20),t.fillStyle="#069",t.fillText("TraceTail 🔒",2,15),t.fillStyle="rgba(102, 204, 0, 0.2)",t.fillText("TraceTail 🔒",4,17),e.toDataURL().substring(0,100)):"unsupported"}catch(e){return"error"}}generateWebGLFingerprint(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return"unsupported";const n=t.getParameter(t.RENDERER);return`${t.getParameter(t.VENDOR)}-${n}`.substring(0,100)}catch(e){return"error"}}async generateAudioFingerprint(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return"unsupported";const t=new e,n=t.createOscillator(),r=t.createAnalyser();n.connect(r),r.connect(t.destination);const i=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(i),await t.close(),Array.from(i.slice(0,10)).join(",")}catch(e){return"error"}}detectFonts(){const e=["Arial","Helvetica","Times","Courier","Verdana","Georgia","Palatino","Garamond","Bookman","Comic Sans MS","Trebuchet MS","Arial Black","Impact"],t=[],n=document.createElement("div");n.style.position="absolute",n.style.left="-9999px",document.body.appendChild(n);try{for(const r of e){const e=document.createElement("span");e.style.fontSize="72px",e.style.fontFamily=r,e.textContent="mmmmmmmmmmlli",n.appendChild(e),e.offsetWidth>0&&t.push(r)}}finally{document.body.removeChild(n)}return t}async generateWebRTCFingerprint(){var e;try{if(!window.RTCPeerConnection)return"unsupported";const t=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});t.createDataChannel("test");await t.createOffer();const n=t.localDescription;return t.close(),(null===(e=null==n?void 0:n.sdp)||void 0===e?void 0:e.substring(0,100))||"error"}catch(e){return"error"}}testStorage(e){try{const t=window[e],n="__tt_test__";return t.setItem(n,"test"),t.removeItem(n),!0}catch(e){return!1}}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}export{e as TraceTail,e as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tracetail/js",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "TraceTail JavaScript SDK for browser fingerprinting",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,25 @@
13
13
  "build": "rollup -c",
14
14
  "test": "echo \"Error: no test specified\" && exit 1"
15
15
  },
16
- "homepage": "https://tracetail.io",
16
+ "homepage": "https://demo-js.tracetail.io",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/tracetail/tracetail-js.git",
20
+ "directory": "packages/js"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/tracetail/tracetail-js/issues"
24
+ },
25
+ "keywords": [
26
+ "browser-fingerprinting",
27
+ "fraud-detection",
28
+ "device-identification",
29
+ "tracetail",
30
+ "javascript",
31
+ "fingerprinting",
32
+ "security",
33
+ "live-demo"
34
+ ],
17
35
  "author": "TraceTail",
18
36
  "license": "MIT",
19
37
  "devDependencies": {
@@ -22,4 +40,4 @@
22
40
  "@rollup/plugin-terser": "^0.4.4",
23
41
  "typescript": "^5.3.3"
24
42
  }
25
- }
43
+ }