@tracetail/js 2.3.9 → 2.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * TraceTail Core Fingerprinting Module
3
+ *
4
+ * Shared fingerprinting primitives used by both the script-tag SDK
5
+ * and the @tracetail/js NPM package, ensuring identical visitor IDs.
6
+ *
7
+ * Signal collection, hashing, and stable extraction logic here MUST
8
+ * match the script-tag SDK (client/src/sdk/tracetail.ts) exactly.
9
+ */
10
+ /**
11
+ * SHA-256 based hash, returning the first 32 hex chars (128 bits).
12
+ */
13
+ export declare function complexHash(str: string): Promise<string>;
14
+ /**
15
+ * Sort object keys recursively for deterministic JSON.stringify output.
16
+ */
17
+ export declare function sortObjectKeysRecursively(obj: unknown): unknown;
18
+ /**
19
+ * Extract stable components for hashing.
20
+ * Matches extractStableComponents() in the script-tag SDK.
21
+ */
22
+ export declare function extractStableComponents(components: Record<string, unknown>): Record<string, unknown>;
23
+ export interface CoreComponents {
24
+ basic?: Record<string, unknown>;
25
+ canvas?: Record<string, unknown>;
26
+ webgl?: Record<string, unknown>;
27
+ fonts?: Record<string, unknown>;
28
+ audio?: Record<string, unknown>;
29
+ [key: string]: unknown;
30
+ }
31
+ /**
32
+ * Collect core fingerprinting signals.
33
+ * Signal collection methods match the script-tag SDK's fast-path collectors.
34
+ */
35
+ export declare function collectCoreComponents(): Promise<CoreComponents>;
36
+ /**
37
+ * Generate a visitor ID from collected components.
38
+ * Uses the same extraction → sort → hash → prefix pipeline as the script-tag SDK.
39
+ */
40
+ export declare function generateCoreVisitorId(components: Record<string, unknown>, isFree: boolean): Promise<string>;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * @tracetail/js - Enterprise Browser Fingerprinting SDK
3
- * Version: 2.3.7
3
+ * Version: 2.3.10
4
4
  *
5
- * Over 99.5% accuracy browser fingerprinting with server-side processing.
6
- * Perfect for fraud detection, user analytics, and security applications.
5
+ * Thin wrapper around the core TraceTail fingerprinting engine.
6
+ * Produces the SAME visitor ID as the script-tag SDK.
7
7
  */
8
+ import { type CoreComponents } from './core';
8
9
  export interface FingerprintOptions {
9
10
  apiKey: string;
10
11
  endpoint?: string;
@@ -22,29 +23,9 @@ export interface FingerprintResult {
22
23
  visitorId: string;
23
24
  confidence: number;
24
25
  processingTime: number;
25
- components?: ComponentData;
26
- }
27
- export interface ComponentData {
28
- canvas?: string;
29
- webgl?: string;
30
- audio?: string;
31
- fonts?: string[];
32
- screen?: {
33
- width: number;
34
- height: number;
35
- colorDepth: number;
36
- pixelRatio: number;
37
- };
38
- timezone?: string;
39
- language?: string;
40
- platform?: string;
41
- userAgent?: string;
42
- cookiesEnabled?: boolean;
43
- localStorage?: boolean;
44
- sessionStorage?: boolean;
45
- indexedDB?: boolean;
46
- webRTC?: string;
26
+ components?: CoreComponents;
47
27
  }
28
+ export { CoreComponents as ComponentData };
48
29
  /**
49
30
  * TraceTail SDK - Enterprise Browser Fingerprinting
50
31
  *
@@ -62,10 +43,7 @@ export declare class TraceTail {
62
43
  private cache;
63
44
  constructor(options: FingerprintOptions);
64
45
  /**
65
- * Generate a browser fingerprint with server-side processing
66
- *
67
- * @param options Optional generation parameters
68
- * @returns Promise resolving to fingerprint result
46
+ * Generate a browser fingerprint with optional server-side processing
69
47
  */
70
48
  generateFingerprint(options?: {
71
49
  timeout?: number;
@@ -73,26 +51,11 @@ export declare class TraceTail {
73
51
  }): Promise<FingerprintResult>;
74
52
  /**
75
53
  * Send fingerprint components to server for enhanced processing
76
- * @private
77
54
  */
78
55
  private sendToServer;
79
- /**
80
- * Get the current SDK version
81
- */
82
56
  static getVersion(): string;
83
- /**
84
- * Validate an API key format
85
- */
86
57
  static validateApiKey(apiKey: string): boolean;
87
- private collectComponents;
88
- private generateVisitorId;
89
58
  private calculateConfidence;
90
- private generateCanvasFingerprint;
91
- private generateWebGLFingerprint;
92
- private generateAudioFingerprint;
93
- private detectFonts;
94
- private generateWebRTCFingerprint;
95
- private testStorage;
96
59
  private isDNTEnabled;
97
60
  private log;
98
61
  }
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||"/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.7",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=!0===(null==e?void 0:e.verbose),r=n?"fingerprint_verbose":"fingerprint_basic";if(this.options.caching&&this.cache.has(r)){const e=this.cache.get(r);if(e)return this.log("Using cached fingerprint",e),e}const i=await this.collectComponents(),o=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint){const e={...await this.sendToServer(i,n),processingTime:Math.round(performance.now()-t)};return this.options.caching&&this.cache.set(r,e),this.log("Server-enhanced fingerprint generated",e),e}const a=await this.generateVisitorId(i),s={visitorId:a,confidence:this.calculateConfidence(i),processingTime:o,...n&&{components:i}};return this.options.caching&&this.cache.set(r,s),this.log("Client-side fingerprint generated",s),s}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){const n=new AbortController,r=setTimeout(()=>n.abort(),this.options.timeout);try{const 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.7"},body:JSON.stringify({components:e,verbose:t||!1,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.7"}}),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(),a={visitorId:o.visitorId,confidence:o.confidence,processingTime:o.processingTime||0};if(t){if(!o.components)throw new Error("Server did not return components in verbose mode");a.components=o.components}return a}catch(e){throw clearTimeout(r),this.options.debug&&console.error("[TraceTail] Server error:",e),e}}static getVersion(){return"2.3.7"}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(),i=t.createDynamicsCompressor();n.type="triangle",n.frequency.setValueAtTime(1e4,t.currentTime),n.connect(i),i.connect(r),n.start(0),await new Promise(e=>setTimeout(e,100));const o=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(o),n.stop(),await t.close(),Array.from(o.slice(0,30)).map(e=>Math.abs(e)).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(){try{if(!window.RTCPeerConnection)return"unsupported";const e=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});e.createDataChannel("tracetail");const t=new Promise(t=>{const n=setTimeout(()=>t(),1e3);e.onicegatheringstatechange=()=>{"complete"===e.iceGatheringState&&(clearTimeout(n),t())}});await e.createOffer(),await e.setLocalDescription(),await t;const n=e.localDescription;e.close();return((null==n?void 0:n.sdp)||"error").split("\n").filter(e=>!e.includes("a=ice-pwd")&&!e.includes("a=ice-ufrag")).join("").substring(0,100)}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";function e(e){let t=0;const r=e.length;if(0===r)return"0";for(let n=0;n<r;n++)t=(t<<5)-t+e.charCodeAt(n),t|=0;return Math.abs(t).toString(36)}function t(e){if(null===e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(t);const r={},n=Object.keys(e).sort();for(const o of n)r[o]=t(e[o]);return r}Object.defineProperty(exports,"__esModule",{value:!0});const r=new Set(["network","processingTime","timestamp","realUserDetection","storage","battery","webrtc","performance","timing","memoryProfile"]);function n(e){if(!e||"object"!=typeof e)return!1;if(!(e.error||e.unavailable||e.disabled))return!1;const t=Object.keys(e),r=new Set(["error","unavailable","disabled","id","timestamp"]);return 0===t.filter(e=>!r.has(e)).length}const o=["Arial","Arial Black","Arial Narrow","Arial Unicode MS","Calibri","Cambria","Century Gothic","Comic Sans MS","Consolas","Courier","Courier New","Georgia","Helvetica","Impact","Lucida Console","Lucida Grande","Palatino","Tahoma","Times","Times New Roman","Trebuchet MS","Verdana","Monaco","Menlo"];async function i(){const t={};try{t.basic={userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,cookieEnabled:navigator.cookieEnabled,screen:{width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timezoneOffset:(new Date).getTimezoneOffset(),hardwareConcurrency:navigator.hardwareConcurrency,maxTouchPoints:navigator.maxTouchPoints,deviceMemory:navigator.deviceMemory}}catch(e){}try{t.canvas=await async function(){try{const t=document.createElement("canvas"),r=t.getContext("2d");if(!r)return{unavailable:!0};t.width=256,t.height=256,r.fillStyle="#f60",r.fillRect(0,0,256,256);const n=r.createLinearGradient(0,0,256,256);n.addColorStop(0,"#ff6b6b"),n.addColorStop(.5,"#4ecdc4"),n.addColorStop(1,"#1a1a2e"),r.fillStyle=n,r.fillRect(0,0,128,128);const o=r.createRadialGradient(192,64,10,192,64,60);o.addColorStop(0,"#e74c3c"),o.addColorStop(1,"rgba(46,204,113,0.4)"),r.fillStyle=o,r.fillRect(128,0,128,128),r.fillStyle="#069",r.font="14px Arial",r.fillText("TraceTail",4,150),r.font="italic 12px Georgia",r.fillText("Canvas FP",4,170),r.font="20px serif",r.fillText("😀🌍🚀❤️",4,200),r.beginPath(),r.moveTo(130,140),r.bezierCurveTo(150,200,200,160,240,210),r.strokeStyle="#9b59b6",r.lineWidth=2,r.stroke(),r.beginPath(),r.arc(200,200,30,0,2*Math.PI),r.fillStyle="rgba(52,152,219,0.6)",r.fill(),r.shadowColor="rgba(0,0,0,0.5)",r.shadowBlur=6,r.shadowOffsetX=3,r.shadowOffsetY=3,r.fillStyle="#2ecc71",r.fillRect(60,200,50,40),r.shadowColor="transparent",r.globalCompositeOperation="multiply",r.fillStyle="#e67e22",r.fillRect(80,180,60,60),r.globalCompositeOperation="screen",r.fillStyle="#3498db",r.fillRect(100,190,60,60),r.globalCompositeOperation="source-over",r.font="11px Arial",r.fillStyle="#000",r.fillText("Subpx",10.5,245.5);const i=[r.getImageData(0,0,32,32),r.getImageData(128,0,32,32),r.getImageData(0,128,32,32),r.getImageData(180,180,32,32)].map(e=>Array.from(e.data.slice(0,64)).reduce((e,t)=>e+t,0));return{hash:e(i.join(",")),samples:i}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.canvas={error:"collection failed"}}try{t.webgl=function(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return{unavailable:!0};const r=t.getExtension("WEBGL_debug_renderer_info"),n=r?t.getParameter(r.UNMASKED_VENDOR_WEBGL):t.getParameter(t.VENDOR),o=r?t.getParameter(r.UNMASKED_RENDERER_WEBGL):t.getParameter(t.RENDERER);return{vendor:t.getParameter(t.VENDOR),renderer:t.getParameter(t.RENDERER),version:t.getParameter(t.VERSION),unmaskedVendor:n,unmaskedRenderer:o}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.webgl={error:"collection failed"}}try{t.fonts=function(){try{const t=document.createElement("canvas"),r=t.getContext("2d");if(!r)return{unavailable:!0};t.width=200,t.height=50;const n=[],i=["mmmmmmmmmmlli","ABCDEFGHIJKLMNOPQRSTUVWXYZ","1234567890"];r.font="14px monospace";const a=i.map(e=>r.measureText(e).width);for(const e of o){r.font=`14px "${e}", monospace`;const t=i.map(e=>r.measureText(e).width);t.some((e,t)=>{var r;return Math.abs(e-(null!==(r=a[t])&&void 0!==r?r:0))>.1})&&n.push(e)}return{detected:n,count:n.length,hash:e(n.join(","))}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.fonts={error:"collection failed"}}try{t.audio=function(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return{unavailable:!0};const t=new e,r={sampleRate:t.sampleRate,maxChannelCount:t.destination.maxChannelCount,baseLatency:t.baseLatency};return t.close(),r}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.audio={error:"collection failed"}}return t}async function a(e,o){const i=function(e){const t={};for(const[o,i]of Object.entries(e))if(!r.has(o))if(i&&"object"==typeof i&&!Array.isArray(i)){const e=i;if(n(e))continue;if(0===Object.keys(e).length)continue;t[o]=i}else null!=i&&(t[o]=i);return t}(e),a=t(i),s=JSON.stringify(a);return"fp2_"+(await async function(e){if(!e.length)return"0000000000000000";const t=(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}(s)).substring(0,16)}class s{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||"/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.7",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 r=!0===(null==e?void 0:e.verbose),n=r?"fingerprint_verbose":"fingerprint_basic";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 o=await i(),s=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint)try{const e={...await this.sendToServer(o,r),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 c=await a(o),l={visitorId:c,confidence:this.calculateConfidence(o),processingTime:s,...r&&{components:o}};return this.options.caching&&this.cache.set(n,l),this.log("Client-side fingerprint generated",l),l}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){const r=new AbortController,n=setTimeout(()=>r.abort(),this.options.timeout);try{const o=await fetch(`${this.options.endpoint}/id`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`,"X-TraceTail-SDK":"js/2.3.7"},body:JSON.stringify({components:e,verbose:t||!1,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.7"}}),signal:r.signal});if(clearTimeout(n),!o.ok){if(401===o.status)throw new Error("Invalid API key");throw new Error(`Server returned ${o.status}`)}const i=await o.json(),a={visitorId:i.visitorId,confidence:i.confidence,processingTime:i.processingTime||0};if(t){if(!i.components)throw new Error("Server did not return components in verbose mode");a.components=i.components}return a}catch(e){throw clearTimeout(n),this.options.debug&&console.error("[TraceTail] Server error:",e),e}}static getVersion(){return"2.3.7"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}calculateConfidence(e){let t=1;e.canvas||(t-=.03),e.webgl||(t-=.03),e.audio||(t-=.02),e.fonts||(t-=.02),e.webRTC||(t-=.01);const r=Object.values(e).filter(e=>null!=e).length;return r<3?t-=.1:r<5&&(t-=.03),Math.min(.999,Math.max(.5,t))}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}exports.TraceTail=s,exports.default=s;
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||"/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.7",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=!0===(null==e?void 0:e.verbose),r=n?"fingerprint_verbose":"fingerprint_basic";if(this.options.caching&&this.cache.has(r)){const e=this.cache.get(r);if(e)return this.log("Using cached fingerprint",e),e}const i=await this.collectComponents(),o=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint){const e={...await this.sendToServer(i,n),processingTime:Math.round(performance.now()-t)};return this.options.caching&&this.cache.set(r,e),this.log("Server-enhanced fingerprint generated",e),e}const a=await this.generateVisitorId(i),s={visitorId:a,confidence:this.calculateConfidence(i),processingTime:o,...n&&{components:i}};return this.options.caching&&this.cache.set(r,s),this.log("Client-side fingerprint generated",s),s}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){const n=new AbortController,r=setTimeout(()=>n.abort(),this.options.timeout);try{const 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.7"},body:JSON.stringify({components:e,verbose:t||!1,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.7"}}),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(),a={visitorId:o.visitorId,confidence:o.confidence,processingTime:o.processingTime||0};if(t){if(!o.components)throw new Error("Server did not return components in verbose mode");a.components=o.components}return a}catch(e){throw clearTimeout(r),this.options.debug&&console.error("[TraceTail] Server error:",e),e}}static getVersion(){return"2.3.7"}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(),i=t.createDynamicsCompressor();n.type="triangle",n.frequency.setValueAtTime(1e4,t.currentTime),n.connect(i),i.connect(r),n.start(0),await new Promise(e=>setTimeout(e,100));const o=new Float32Array(r.frequencyBinCount);return r.getFloatFrequencyData(o),n.stop(),await t.close(),Array.from(o.slice(0,30)).map(e=>Math.abs(e)).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(){try{if(!window.RTCPeerConnection)return"unsupported";const e=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});e.createDataChannel("tracetail");const t=new Promise(t=>{const n=setTimeout(()=>t(),1e3);e.onicegatheringstatechange=()=>{"complete"===e.iceGatheringState&&(clearTimeout(n),t())}});await e.createOffer(),await e.setLocalDescription(),await t;const n=e.localDescription;e.close();return((null==n?void 0:n.sdp)||"error").split("\n").filter(e=>!e.includes("a=ice-pwd")&&!e.includes("a=ice-ufrag")).join("").substring(0,100)}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
+ function e(e){let t=0;const r=e.length;if(0===r)return"0";for(let n=0;n<r;n++)t=(t<<5)-t+e.charCodeAt(n),t|=0;return Math.abs(t).toString(36)}function t(e){if(null===e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(t);const r={},n=Object.keys(e).sort();for(const o of n)r[o]=t(e[o]);return r}const r=new Set(["network","processingTime","timestamp","realUserDetection","storage","battery","webrtc","performance","timing","memoryProfile"]);function n(e){if(!e||"object"!=typeof e)return!1;if(!(e.error||e.unavailable||e.disabled))return!1;const t=Object.keys(e),r=new Set(["error","unavailable","disabled","id","timestamp"]);return 0===t.filter(e=>!r.has(e)).length}const o=["Arial","Arial Black","Arial Narrow","Arial Unicode MS","Calibri","Cambria","Century Gothic","Comic Sans MS","Consolas","Courier","Courier New","Georgia","Helvetica","Impact","Lucida Console","Lucida Grande","Palatino","Tahoma","Times","Times New Roman","Trebuchet MS","Verdana","Monaco","Menlo"];async function a(){const t={};try{t.basic={userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,cookieEnabled:navigator.cookieEnabled,screen:{width:screen.width,height:screen.height,colorDepth:screen.colorDepth,pixelRatio:window.devicePixelRatio||1},timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timezoneOffset:(new Date).getTimezoneOffset(),hardwareConcurrency:navigator.hardwareConcurrency,maxTouchPoints:navigator.maxTouchPoints,deviceMemory:navigator.deviceMemory}}catch(e){}try{t.canvas=await async function(){try{const t=document.createElement("canvas"),r=t.getContext("2d");if(!r)return{unavailable:!0};t.width=256,t.height=256,r.fillStyle="#f60",r.fillRect(0,0,256,256);const n=r.createLinearGradient(0,0,256,256);n.addColorStop(0,"#ff6b6b"),n.addColorStop(.5,"#4ecdc4"),n.addColorStop(1,"#1a1a2e"),r.fillStyle=n,r.fillRect(0,0,128,128);const o=r.createRadialGradient(192,64,10,192,64,60);o.addColorStop(0,"#e74c3c"),o.addColorStop(1,"rgba(46,204,113,0.4)"),r.fillStyle=o,r.fillRect(128,0,128,128),r.fillStyle="#069",r.font="14px Arial",r.fillText("TraceTail",4,150),r.font="italic 12px Georgia",r.fillText("Canvas FP",4,170),r.font="20px serif",r.fillText("😀🌍🚀❤️",4,200),r.beginPath(),r.moveTo(130,140),r.bezierCurveTo(150,200,200,160,240,210),r.strokeStyle="#9b59b6",r.lineWidth=2,r.stroke(),r.beginPath(),r.arc(200,200,30,0,2*Math.PI),r.fillStyle="rgba(52,152,219,0.6)",r.fill(),r.shadowColor="rgba(0,0,0,0.5)",r.shadowBlur=6,r.shadowOffsetX=3,r.shadowOffsetY=3,r.fillStyle="#2ecc71",r.fillRect(60,200,50,40),r.shadowColor="transparent",r.globalCompositeOperation="multiply",r.fillStyle="#e67e22",r.fillRect(80,180,60,60),r.globalCompositeOperation="screen",r.fillStyle="#3498db",r.fillRect(100,190,60,60),r.globalCompositeOperation="source-over",r.font="11px Arial",r.fillStyle="#000",r.fillText("Subpx",10.5,245.5);const a=[r.getImageData(0,0,32,32),r.getImageData(128,0,32,32),r.getImageData(0,128,32,32),r.getImageData(180,180,32,32)].map(e=>Array.from(e.data.slice(0,64)).reduce((e,t)=>e+t,0));return{hash:e(a.join(",")),samples:a}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.canvas={error:"collection failed"}}try{t.webgl=function(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return{unavailable:!0};const r=t.getExtension("WEBGL_debug_renderer_info"),n=r?t.getParameter(r.UNMASKED_VENDOR_WEBGL):t.getParameter(t.VENDOR),o=r?t.getParameter(r.UNMASKED_RENDERER_WEBGL):t.getParameter(t.RENDERER);return{vendor:t.getParameter(t.VENDOR),renderer:t.getParameter(t.RENDERER),version:t.getParameter(t.VERSION),unmaskedVendor:n,unmaskedRenderer:o}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.webgl={error:"collection failed"}}try{t.fonts=function(){try{const t=document.createElement("canvas"),r=t.getContext("2d");if(!r)return{unavailable:!0};t.width=200,t.height=50;const n=[],a=["mmmmmmmmmmlli","ABCDEFGHIJKLMNOPQRSTUVWXYZ","1234567890"];r.font="14px monospace";const i=a.map(e=>r.measureText(e).width);for(const e of o){r.font=`14px "${e}", monospace`;const t=a.map(e=>r.measureText(e).width);t.some((e,t)=>{var r;return Math.abs(e-(null!==(r=i[t])&&void 0!==r?r:0))>.1})&&n.push(e)}return{detected:n,count:n.length,hash:e(n.join(","))}}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.fonts={error:"collection failed"}}try{t.audio=function(){try{const e=window.AudioContext||window.webkitAudioContext;if(!e)return{unavailable:!0};const t=new e,r={sampleRate:t.sampleRate,maxChannelCount:t.destination.maxChannelCount,baseLatency:t.baseLatency};return t.close(),r}catch(e){return{error:e instanceof Error?e.message:String(e)}}}()}catch(e){t.audio={error:"collection failed"}}return t}async function i(e,o){const a=function(e){const t={};for(const[o,a]of Object.entries(e))if(!r.has(o))if(a&&"object"==typeof a&&!Array.isArray(a)){const e=a;if(n(e))continue;if(0===Object.keys(e).length)continue;t[o]=a}else null!=a&&(t[o]=a);return t}(e),i=t(a),s=JSON.stringify(i);return"fp2_"+(await async function(e){if(!e.length)return"0000000000000000";const t=(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").substring(0,32)}(s)).substring(0,16)}class s{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||"/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.7",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 r=!0===(null==e?void 0:e.verbose),n=r?"fingerprint_verbose":"fingerprint_basic";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 o=await a(),s=Math.round(performance.now()-t);if(this.options.apiKey&&this.options.endpoint)try{const e={...await this.sendToServer(o,r),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 c=await i(o),l={visitorId:c,confidence:this.calculateConfidence(o),processingTime:s,...r&&{components:o}};return this.options.caching&&this.cache.set(n,l),this.log("Client-side fingerprint generated",l),l}catch(e){throw this.log("Fingerprint generation failed",e),e}}async sendToServer(e,t){const r=new AbortController,n=setTimeout(()=>r.abort(),this.options.timeout);try{const o=await fetch(`${this.options.endpoint}/id`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`,"X-TraceTail-SDK":"js/2.3.7"},body:JSON.stringify({components:e,verbose:t||!1,clientCapabilities:{timestamp:Date.now(),sdkVersion:"2.3.7"}}),signal:r.signal});if(clearTimeout(n),!o.ok){if(401===o.status)throw new Error("Invalid API key");throw new Error(`Server returned ${o.status}`)}const a=await o.json(),i={visitorId:a.visitorId,confidence:a.confidence,processingTime:a.processingTime||0};if(t){if(!a.components)throw new Error("Server did not return components in verbose mode");i.components=a.components}return i}catch(e){throw clearTimeout(n),this.options.debug&&console.error("[TraceTail] Server error:",e),e}}static getVersion(){return"2.3.7"}static validateApiKey(e){return/^tt_(test|prod)_[a-zA-Z0-9]{32,}$/.test(e)}calculateConfidence(e){let t=1;e.canvas||(t-=.03),e.webgl||(t-=.03),e.audio||(t-=.02),e.fonts||(t-=.02),e.webRTC||(t-=.01);const r=Object.values(e).filter(e=>null!=e).length;return r<3?t-=.1:r<5&&(t-=.03),Math.min(.999,Math.max(.5,t))}isDNTEnabled(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack}log(e,t){this.options.debug&&console.log(`[TraceTail] ${e}`,t)}}export{s as TraceTail,s as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tracetail/js",
3
- "version": "2.3.9",
3
+ "version": "2.3.11",
4
4
  "description": "TraceTail JavaScript SDK for browser fingerprinting",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",