@thumbmarkjs/thumbmarkjs 1.2.0 → 1.3.1
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 +5 -13
- package/dist/thumbmark.cjs.js +1 -1
- package/dist/thumbmark.cjs.js.map +1 -1
- package/dist/thumbmark.esm.d.ts +3 -10
- package/dist/thumbmark.esm.js +1 -1
- package/dist/thumbmark.esm.js.map +1 -1
- package/dist/thumbmark.umd.js +1 -1
- package/dist/thumbmark.umd.js.map +1 -1
- package/dist/types/components/mathml/index.d.ts +2 -0
- package/dist/types/components/webrtc/index.d.ts +2 -0
- package/dist/types/factory.d.ts +9 -0
- package/dist/types/functions/api.d.ts +39 -0
- package/dist/types/functions/index.d.ts +2 -46
- package/dist/types/options.d.ts +2 -0
- package/dist/types/utils/log.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/mathml/index.ts +149 -0
- package/src/components/webrtc/index.ts +126 -0
- package/src/factory.ts +12 -0
- package/src/functions/api.ts +130 -0
- package/src/functions/filterComponents.ts +0 -1
- package/src/functions/index.ts +39 -140
- package/src/options.ts +3 -1
- package/src/utils/log.ts +14 -13
package/src/functions/index.ts
CHANGED
|
@@ -4,22 +4,15 @@
|
|
|
4
4
|
* This module handles component collection, API calls, uniqueness scoring, and data filtering
|
|
5
5
|
* for the ThumbmarkJS browser fingerprinting library.
|
|
6
6
|
*
|
|
7
|
-
* Exports:
|
|
8
|
-
* - getThumbmark
|
|
9
|
-
* - getThumbmarkDataFromPromiseMap
|
|
10
|
-
* - resolveClientComponents
|
|
11
|
-
* - filterThumbmarkData
|
|
12
|
-
*
|
|
13
|
-
* Internal helpers and types are also defined here.
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
|
-
// ===================== Imports =====================
|
|
17
9
|
import { defaultOptions, optionsInterface } from "../options";
|
|
18
10
|
import {
|
|
19
11
|
timeoutInstance,
|
|
20
12
|
componentInterface,
|
|
21
13
|
tm_component_promises,
|
|
22
14
|
customComponents,
|
|
15
|
+
tm_experimental_component_promises,
|
|
23
16
|
includeComponent as globalIncludeComponent
|
|
24
17
|
} from "../factory";
|
|
25
18
|
import { hash } from "../utils/hash";
|
|
@@ -27,43 +20,8 @@ import { raceAllPerformance } from "../utils/raceAll";
|
|
|
27
20
|
import { getVersion } from "../utils/version";
|
|
28
21
|
import { filterThumbmarkData } from './filterComponents'
|
|
29
22
|
import { logThumbmarkData } from '../utils/log';
|
|
30
|
-
import {
|
|
31
|
-
import { getVisitorId, setVisitorId } from "../utils/visitorId";
|
|
32
|
-
|
|
33
|
-
// ===================== Types & Interfaces =====================
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Info returned from the API (IP, classification, uniqueness, etc)
|
|
37
|
-
*/
|
|
38
|
-
interface infoInterface {
|
|
39
|
-
ip_address?: {
|
|
40
|
-
ip_address: string,
|
|
41
|
-
ip_identifier: string,
|
|
42
|
-
autonomous_system_number: number,
|
|
43
|
-
ip_version: 'v6' | 'v4',
|
|
44
|
-
},
|
|
45
|
-
classification?: {
|
|
46
|
-
tor: boolean,
|
|
47
|
-
vpn: boolean,
|
|
48
|
-
bot: boolean,
|
|
49
|
-
datacenter: boolean,
|
|
50
|
-
danger_level: number, // 5 is highest and should be blocked. 0 is no danger.
|
|
51
|
-
},
|
|
52
|
-
uniqueness?: {
|
|
53
|
-
score: number | string
|
|
54
|
-
},
|
|
55
|
-
timed_out?: boolean; // added for timeout handling
|
|
56
|
-
}
|
|
23
|
+
import { getApiPromise, infoInterface } from "./api";
|
|
57
24
|
|
|
58
|
-
/**
|
|
59
|
-
* API response structure
|
|
60
|
-
*/
|
|
61
|
-
interface apiResponse {
|
|
62
|
-
info?: infoInterface;
|
|
63
|
-
version?: string;
|
|
64
|
-
components?: componentInterface;
|
|
65
|
-
visitorId?: string;
|
|
66
|
-
}
|
|
67
25
|
|
|
68
26
|
/**
|
|
69
27
|
* Final thumbmark response structure
|
|
@@ -74,101 +32,11 @@ interface thumbmarkResponse {
|
|
|
74
32
|
version: string,
|
|
75
33
|
thumbmark: string,
|
|
76
34
|
visitorId?: string,
|
|
77
|
-
/**
|
|
78
|
-
* Only present if options.performance is true.
|
|
79
|
-
*/
|
|
80
35
|
elapsed?: any;
|
|
36
|
+
error?: string;
|
|
37
|
+
experimental?: componentInterface;
|
|
81
38
|
}
|
|
82
39
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// ===================== API Call Logic =====================
|
|
86
|
-
|
|
87
|
-
let currentApiPromise: Promise<apiResponse> | null = null;
|
|
88
|
-
let apiPromiseResult: apiResponse | null = null;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Calls the Thumbmark API with the given components, using caching and deduplication.
|
|
92
|
-
* Returns a promise for the API response or null on error.
|
|
93
|
-
*/
|
|
94
|
-
export const getApiPromise = (
|
|
95
|
-
options: optionsInterface,
|
|
96
|
-
components: componentInterface
|
|
97
|
-
): Promise<apiResponse | null> => {
|
|
98
|
-
// 1. If a result is already cached and caching is enabled, return it.
|
|
99
|
-
if (options.cache_api_call && apiPromiseResult) {
|
|
100
|
-
return Promise.resolve(apiPromiseResult);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 2. If a request is already in flight, return that promise to prevent duplicate calls.
|
|
104
|
-
if (currentApiPromise) {
|
|
105
|
-
return currentApiPromise;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 3. Otherwise, initiate a new API call with timeout.
|
|
109
|
-
const endpoint = `${API_ENDPOINT}/thumbmark`;
|
|
110
|
-
const visitorId = getVisitorId();
|
|
111
|
-
const requestBody: any = {
|
|
112
|
-
components,
|
|
113
|
-
options,
|
|
114
|
-
clientHash: hash(JSON.stringify(components)),
|
|
115
|
-
version: getVersion()
|
|
116
|
-
};
|
|
117
|
-
if (visitorId) {
|
|
118
|
-
requestBody.visitorId = visitorId;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const fetchPromise = fetch(endpoint, {
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: {
|
|
124
|
-
'x-api-key': options.api_key!,
|
|
125
|
-
'Authorization': 'custom-authorized',
|
|
126
|
-
'Content-Type': 'application/json',
|
|
127
|
-
},
|
|
128
|
-
body: JSON.stringify(requestBody),
|
|
129
|
-
})
|
|
130
|
-
.then(response => {
|
|
131
|
-
// Handle HTTP errors that aren't network errors
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
134
|
-
}
|
|
135
|
-
return response.json();
|
|
136
|
-
})
|
|
137
|
-
.then(data => {
|
|
138
|
-
// Handle visitor ID from server response
|
|
139
|
-
if (data.visitorId && data.visitorId !== visitorId) {
|
|
140
|
-
setVisitorId(data.visitorId);
|
|
141
|
-
}
|
|
142
|
-
apiPromiseResult = data; // Cache the successful result
|
|
143
|
-
currentApiPromise = null; // Clear the in-flight promise
|
|
144
|
-
return data;
|
|
145
|
-
})
|
|
146
|
-
.catch(error => {
|
|
147
|
-
console.error('Error fetching pro data', error);
|
|
148
|
-
currentApiPromise = null; // Also clear the in-flight promise on error
|
|
149
|
-
// Return null instead of a string to prevent downstream crashes
|
|
150
|
-
return null;
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Timeout logic
|
|
154
|
-
const timeoutMs = options.timeout || 5000;
|
|
155
|
-
const timeoutPromise = new Promise<apiResponse>((resolve) => {
|
|
156
|
-
setTimeout(() => {
|
|
157
|
-
resolve({
|
|
158
|
-
info: { timed_out: true },
|
|
159
|
-
version: getVersion(),
|
|
160
|
-
});
|
|
161
|
-
}, timeoutMs);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
currentApiPromise = Promise.race([fetchPromise, timeoutPromise]);
|
|
165
|
-
return currentApiPromise;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// ===================== Main Thumbmark Logic =====================
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
40
|
/**
|
|
173
41
|
* Main entry point: collects all components, optionally calls API, and returns thumbmark data.
|
|
174
42
|
*
|
|
@@ -177,12 +45,41 @@ export const getApiPromise = (
|
|
|
177
45
|
*/
|
|
178
46
|
export async function getThumbmark(options?: optionsInterface): Promise<thumbmarkResponse> {
|
|
179
47
|
const _options = { ...defaultOptions, ...options };
|
|
48
|
+
|
|
49
|
+
// Early logging decision
|
|
50
|
+
const shouldLog = (!sessionStorage.getItem("_tmjs_l") && Math.random() < 0.0001);
|
|
51
|
+
|
|
180
52
|
// Merge built-in and user-registered components
|
|
181
53
|
const allComponents = { ...tm_component_promises, ...customComponents };
|
|
182
54
|
const { elapsed, resolvedComponents: clientComponentsResult } = await resolveClientComponents(allComponents, _options);
|
|
183
55
|
|
|
56
|
+
// Resolve experimental components only when logging
|
|
57
|
+
let experimentalComponents = {};
|
|
58
|
+
if (shouldLog || _options.experimental) {
|
|
59
|
+
const { resolvedComponents } = await resolveClientComponents(tm_experimental_component_promises, _options);
|
|
60
|
+
experimentalComponents = resolvedComponents;
|
|
61
|
+
}
|
|
62
|
+
|
|
184
63
|
const apiPromise = _options.api_key ? getApiPromise(_options, clientComponentsResult) : null;
|
|
185
|
-
|
|
64
|
+
let apiResult = null;
|
|
65
|
+
|
|
66
|
+
if (apiPromise) {
|
|
67
|
+
try {
|
|
68
|
+
apiResult = await apiPromise;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Handle API key/quota errors
|
|
71
|
+
if (error instanceof Error && error.message === 'INVALID_API_KEY') {
|
|
72
|
+
return {
|
|
73
|
+
error: 'Invalid API key or quota exceeded',
|
|
74
|
+
components: {},
|
|
75
|
+
info: {},
|
|
76
|
+
version: getVersion(),
|
|
77
|
+
thumbmark: ''
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
throw error; // Re-throw other errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
186
83
|
|
|
187
84
|
// Only add 'elapsed' if performance is true
|
|
188
85
|
const maybeElapsed = _options.performance ? { elapsed } : {};
|
|
@@ -191,7 +88,10 @@ export async function getThumbmark(options?: optionsInterface): Promise<thumbmar
|
|
|
191
88
|
const info: infoInterface = apiResult?.info || { uniqueness: { score: 'api only' } };
|
|
192
89
|
const thumbmark = hash(JSON.stringify(components));
|
|
193
90
|
const version = getVersion();
|
|
194
|
-
|
|
91
|
+
// Only log to server when not in debug mode
|
|
92
|
+
if (shouldLog) {
|
|
93
|
+
logThumbmarkData(thumbmark, components, _options, experimentalComponents).catch(() => { /* do nothing */ });
|
|
94
|
+
}
|
|
195
95
|
|
|
196
96
|
const result: thumbmarkResponse = {
|
|
197
97
|
...(apiResult?.visitorId && { visitorId: apiResult.visitorId }),
|
|
@@ -200,6 +100,7 @@ export async function getThumbmark(options?: optionsInterface): Promise<thumbmar
|
|
|
200
100
|
info,
|
|
201
101
|
version,
|
|
202
102
|
...maybeElapsed,
|
|
103
|
+
...(Object.keys(experimentalComponents).length > 0 && _options.experimental && { experimental: experimentalComponents }),
|
|
203
104
|
};
|
|
204
105
|
|
|
205
106
|
return result;
|
|
@@ -244,6 +145,4 @@ export async function resolveClientComponents(
|
|
|
244
145
|
return { elapsed, resolvedComponents };
|
|
245
146
|
}
|
|
246
147
|
|
|
247
|
-
|
|
248
|
-
|
|
249
148
|
export { globalIncludeComponent as includeComponent };
|
package/src/options.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface optionsInterface {
|
|
|
8
8
|
cache_api_call?: boolean,
|
|
9
9
|
performance?: boolean,
|
|
10
10
|
stabilize?: string[],
|
|
11
|
+
experimental?: boolean,
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const API_ENDPOINT = 'https://api.thumbmarkjs.com';
|
|
@@ -19,7 +20,8 @@ export const defaultOptions: optionsInterface = {
|
|
|
19
20
|
logging: true,
|
|
20
21
|
timeout: 5000,
|
|
21
22
|
cache_api_call: true,
|
|
22
|
-
performance: false
|
|
23
|
+
performance: false,
|
|
24
|
+
experimental: false,
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export let options = {...defaultOptions};
|
package/src/utils/log.ts
CHANGED
|
@@ -10,25 +10,26 @@ import { API_ENDPOINT } from '../options';
|
|
|
10
10
|
* You can disable this by setting options.logging to false.
|
|
11
11
|
* @internal
|
|
12
12
|
*/
|
|
13
|
-
export async function logThumbmarkData(thisHash: string, thumbmarkData: componentInterface, options: optionsInterface): Promise<void> {
|
|
13
|
+
export async function logThumbmarkData(thisHash: string, thumbmarkData: componentInterface, options: optionsInterface, experimentalData: componentInterface = {}): Promise<void> {
|
|
14
14
|
const url = `${API_ENDPOINT}/log`;
|
|
15
15
|
const payload = {
|
|
16
16
|
thumbmark: thisHash,
|
|
17
17
|
components: thumbmarkData,
|
|
18
|
+
experimental: experimentalData,
|
|
18
19
|
version: getVersion(),
|
|
19
20
|
options,
|
|
20
21
|
path: window?.location?.pathname,
|
|
21
22
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
|
|
24
|
+
sessionStorage.setItem("_tmjs_l", "1");
|
|
25
|
+
try {
|
|
26
|
+
await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(payload)
|
|
32
|
+
});
|
|
33
|
+
} catch { /* do nothing */ }
|
|
34
|
+
|
|
34
35
|
}
|