@thumbmarkjs/thumbmarkjs 0.20.6 → 1.0.0-rc.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 +43 -45
- package/dist/thumbmark.cjs.js +1 -1
- package/dist/thumbmark.cjs.js.map +1 -1
- package/dist/thumbmark.esm.d.ts +107 -16
- 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/package.json +7 -2
- package/src/components/audio/{audio.ts → index.ts} +11 -8
- package/src/components/canvas/{canvas.ts → index.ts} +12 -8
- package/src/components/fonts/{fonts.ts → index.ts} +10 -5
- package/src/components/hardware/{hardware.ts → index.ts} +3 -5
- package/src/components/locales/{locales.ts → index.ts} +2 -4
- package/src/components/math/index.ts +28 -0
- package/src/components/permissions/{permissions.ts → index.ts} +29 -37
- package/src/components/plugins/{plugins.ts → index.ts} +2 -4
- package/src/components/screen/{screen.ts → index.ts} +2 -4
- package/src/components/system/{system.ts → index.ts} +2 -5
- package/src/components/webgl/{webgl.ts → index.ts} +3 -5
- package/src/factory.ts +40 -29
- package/src/functions/filterComponents.ts +42 -0
- package/src/functions/functions.test.ts +39 -0
- package/src/functions/index.ts +262 -0
- package/src/functions/legacy_functions.ts +54 -0
- package/src/index.ts +13 -4
- package/src/{fingerprint/options.ts → options.ts} +20 -12
- package/src/thumbmark.ts +41 -0
- package/src/{declarations.d.ts → types/global.d.ts} +1 -1
- package/src/utils/raceAll.ts +0 -2
- package/src/components/index.ts +0 -17
- package/src/components/math/math.ts +0 -39
- package/src/fingerprint/functions.test.ts +0 -34
- package/src/fingerprint/functions.ts +0 -118
- /package/src/components/canvas/{canvas.test.ts → index.test.ts} +0 -0
package/src/factory.ts
CHANGED
|
@@ -4,21 +4,53 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { optionsInterface } from './options';
|
|
8
|
+
//import { getComponentPromises } from './fingerprint/tm_functions';
|
|
9
|
+
|
|
10
|
+
// Import all built-in component functions
|
|
11
|
+
import getAudio from "./components/audio";
|
|
12
|
+
import getCanvas from "./components/canvas";
|
|
13
|
+
import getFonts from "./components/fonts";
|
|
14
|
+
import getHardware from "./components/hardware";
|
|
15
|
+
import getLocales from "./components/locales";
|
|
16
|
+
import getMath from "./components/math";
|
|
17
|
+
import getPermissions from "./components/permissions";
|
|
18
|
+
import getPlugins from "./components/plugins";
|
|
19
|
+
import getScreen from "./components/screen";
|
|
20
|
+
import getSystem from "./components/system";
|
|
21
|
+
import getWebGL from "./components/webgl";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description key->function map of built-in components. Do not call the function here.
|
|
25
|
+
*/
|
|
26
|
+
export const tm_component_promises = {
|
|
27
|
+
'audio': getAudio,
|
|
28
|
+
'canvas': getCanvas,
|
|
29
|
+
'fonts': getFonts,
|
|
30
|
+
'hardware': getHardware,
|
|
31
|
+
'locales': getLocales,
|
|
32
|
+
'math': getMath,
|
|
33
|
+
'permissions': getPermissions,
|
|
34
|
+
'plugins': getPlugins,
|
|
35
|
+
'screen': getScreen,
|
|
36
|
+
'system': getSystem,
|
|
37
|
+
'webgl': getWebGL
|
|
38
|
+
};
|
|
8
39
|
|
|
9
40
|
// the component interface is the form of the JSON object the function's promise must return
|
|
10
41
|
export interface componentInterface {
|
|
11
42
|
[key: string]: string | string[] | number | boolean | componentInterface;
|
|
12
|
-
}
|
|
43
|
+
};
|
|
13
44
|
|
|
14
45
|
|
|
15
46
|
// The component function's interface is simply the promise of the above
|
|
16
47
|
export interface componentFunctionInterface {
|
|
17
|
-
(): Promise<componentInterface>;
|
|
48
|
+
(options?: optionsInterface): Promise<componentInterface | null>;
|
|
18
49
|
}
|
|
19
50
|
|
|
20
51
|
// components include a dictionary of name: function.
|
|
21
|
-
|
|
52
|
+
// Renamed to customComponents for clarity; this is for user-registered components.
|
|
53
|
+
export const customComponents: {[name: string]: componentFunctionInterface | null} = {};
|
|
22
54
|
|
|
23
55
|
//In case a promise time-outs, this is what we use as the value in place
|
|
24
56
|
export const timeoutInstance: componentInterface = {
|
|
@@ -30,29 +62,8 @@ export const timeoutInstance: componentInterface = {
|
|
|
30
62
|
* in the fingerprint.
|
|
31
63
|
* @param {string} name - the name identifier of the component
|
|
32
64
|
* @param {componentFunctionInterface} creationFunction - the function that implements the component
|
|
33
|
-
* @returns
|
|
65
|
+
* @returns nothing
|
|
34
66
|
*/
|
|
35
|
-
export const includeComponent = (name:string, creationFunction: componentFunctionInterface) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The function turns the map of component functions to a map of Promises when called
|
|
42
|
-
* @returns {[name: string]: <Promise>componentInterface}
|
|
43
|
-
*/
|
|
44
|
-
export const getComponentPromises = () => {
|
|
45
|
-
return Object.fromEntries(
|
|
46
|
-
Object.entries(components)
|
|
47
|
-
.filter(([key]) => {
|
|
48
|
-
return !options?.exclude?.includes(key)}
|
|
49
|
-
)
|
|
50
|
-
.filter(([key]) => {
|
|
51
|
-
return options?.include?.some(e => e.includes('.'))
|
|
52
|
-
? options?.include?.some(e => e.startsWith(key))
|
|
53
|
-
: options?.include?.length === 0 || options?.include?.includes(key)
|
|
54
|
-
}
|
|
55
|
-
)
|
|
56
|
-
.map(([key, value]) => [key, value()])
|
|
57
|
-
);
|
|
58
|
-
}
|
|
67
|
+
export const includeComponent = (name:string, creationFunction: componentFunctionInterface, options?: optionsInterface) => {
|
|
68
|
+
customComponents[name] = creationFunction;
|
|
69
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { componentInterface } from "../factory";
|
|
2
|
+
import { optionsInterface } from "../options";
|
|
3
|
+
|
|
4
|
+
// ===================== Data Filtering =====================
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively filters a componentInterface object by include/exclude options.
|
|
8
|
+
*
|
|
9
|
+
* @param obj - The object to filter
|
|
10
|
+
* @param options - Filtering options
|
|
11
|
+
* @param path - Current path (for recursion)
|
|
12
|
+
* @returns Filtered object
|
|
13
|
+
*/
|
|
14
|
+
export function filterThumbmarkData(
|
|
15
|
+
obj: componentInterface,
|
|
16
|
+
options?: optionsInterface,
|
|
17
|
+
path: string = ""
|
|
18
|
+
): componentInterface {
|
|
19
|
+
const result: componentInterface = {};
|
|
20
|
+
const excludeList = options?.exclude || [];
|
|
21
|
+
const includeList = options?.include || [];
|
|
22
|
+
|
|
23
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
24
|
+
const currentPath = path + key + ".";
|
|
25
|
+
|
|
26
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
27
|
+
const filtered = filterThumbmarkData(value, options, currentPath);
|
|
28
|
+
if (Object.keys(filtered).length > 0) {
|
|
29
|
+
result[key] = filtered;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
const isExcluded = excludeList.some(exclusion => currentPath.startsWith(exclusion));
|
|
33
|
+
const isIncluded = includeList.some(inclusion => currentPath.startsWith(inclusion));
|
|
34
|
+
|
|
35
|
+
if (!isExcluded || isIncluded) {
|
|
36
|
+
result[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { componentInterface } from '../factory'
|
|
2
|
+
import { filterThumbmarkData } from '../utils/filterComponents'
|
|
3
|
+
import { defaultOptions } from '../options';
|
|
4
|
+
|
|
5
|
+
const test_components: componentInterface = {
|
|
6
|
+
'one': '1',
|
|
7
|
+
'two': 2,
|
|
8
|
+
'three': {'a': true, 'b': false}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('component filtering tests', () => {
|
|
12
|
+
test("excluding top level works", () => {
|
|
13
|
+
expect(filterThumbmarkData(test_components,
|
|
14
|
+
{ ...defaultOptions, ...{ exclude: ['one']} }
|
|
15
|
+
)).toMatchObject({
|
|
16
|
+
'two': 2, 'three': {'a': true, 'b': false}
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
test("including top level works", () => {
|
|
20
|
+
expect(filterThumbmarkData(test_components, { ...defaultOptions, ...{ include: ['one', 'two']} })).toMatchObject({
|
|
21
|
+
'one': '1', 'two': 2
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
test("excluding low-level works", () => {
|
|
25
|
+
expect(filterThumbmarkData(test_components,
|
|
26
|
+
{ ...defaultOptions, ...{ exclude: ['two', 'three.a']} }
|
|
27
|
+
)).toMatchObject({
|
|
28
|
+
'one': '1',
|
|
29
|
+
'three': {'b': false}
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
test("including low-level works", () => {
|
|
33
|
+
expect(filterThumbmarkData(test_components,
|
|
34
|
+
{ ...defaultOptions, ...{ include: ['one', 'three.b']} })).toMatchObject({
|
|
35
|
+
'one': '1',
|
|
36
|
+
'three': {'b': false}
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThumbmarkJS: Main fingerprinting and API logic
|
|
3
|
+
*
|
|
4
|
+
* This module handles component collection, API calls, uniqueness scoring, and data filtering
|
|
5
|
+
* for the ThumbmarkJS browser fingerprinting library.
|
|
6
|
+
*
|
|
7
|
+
* Exports:
|
|
8
|
+
* - getThumbmark
|
|
9
|
+
* - getThumbmarkDataFromPromiseMap
|
|
10
|
+
* - resolveClientComponents
|
|
11
|
+
* - getVersion
|
|
12
|
+
* - filterThumbmarkData
|
|
13
|
+
*
|
|
14
|
+
* Internal helpers and types are also defined here.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ===================== Imports =====================
|
|
18
|
+
import { defaultOptions, optionsInterface } from "../options";
|
|
19
|
+
import {
|
|
20
|
+
timeoutInstance,
|
|
21
|
+
componentInterface,
|
|
22
|
+
tm_component_promises,
|
|
23
|
+
customComponents,
|
|
24
|
+
includeComponent as globalIncludeComponent
|
|
25
|
+
} from "../factory";
|
|
26
|
+
import { hash } from "../utils/hash";
|
|
27
|
+
import { raceAllPerformance } from "../utils/raceAll";
|
|
28
|
+
import * as packageJson from '../../package.json';
|
|
29
|
+
import { filterThumbmarkData } from './filterComponents'
|
|
30
|
+
|
|
31
|
+
// ===================== Types & Interfaces =====================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Info returned from the API (IP, classification, uniqueness, etc)
|
|
35
|
+
*/
|
|
36
|
+
interface infoInterface {
|
|
37
|
+
ip_address?: {
|
|
38
|
+
ip_address: string,
|
|
39
|
+
ip_identifier: string,
|
|
40
|
+
autonomous_system_number: number,
|
|
41
|
+
ip_version: 'v6' | 'v4',
|
|
42
|
+
},
|
|
43
|
+
classification?: {
|
|
44
|
+
tor: boolean,
|
|
45
|
+
proxy: boolean, // i.e. vpn and
|
|
46
|
+
datacenter: boolean,
|
|
47
|
+
danger_level: number, // 5 is highest and should be blocked. 0 is no danger.
|
|
48
|
+
},
|
|
49
|
+
uniqueness?: {
|
|
50
|
+
score: number | string
|
|
51
|
+
},
|
|
52
|
+
timed_out?: boolean; // added for timeout handling
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* API response structure
|
|
57
|
+
*/
|
|
58
|
+
interface apiResponse {
|
|
59
|
+
thumbmark?: string;
|
|
60
|
+
info?: infoInterface;
|
|
61
|
+
version?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Final thumbmark response structure
|
|
66
|
+
*/
|
|
67
|
+
interface thumbmarkResponse {
|
|
68
|
+
components: componentInterface,
|
|
69
|
+
info: { [key: string]: any },
|
|
70
|
+
version: string,
|
|
71
|
+
thumbmark: string,
|
|
72
|
+
/**
|
|
73
|
+
* Only present if options.performance is true.
|
|
74
|
+
*/
|
|
75
|
+
elapsed?: any;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===================== Version =====================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns the current package version
|
|
82
|
+
*/
|
|
83
|
+
export function getVersion(): string {
|
|
84
|
+
return packageJson.version;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ===================== API Call Logic =====================
|
|
88
|
+
|
|
89
|
+
let currentApiPromise: Promise<apiResponse> | null = null;
|
|
90
|
+
let apiPromiseResult: apiResponse | null = null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Calls the Thumbmark API with the given components, using caching and deduplication.
|
|
94
|
+
* Returns a promise for the API response or null on error.
|
|
95
|
+
*/
|
|
96
|
+
export const getApiPromise = (
|
|
97
|
+
options: optionsInterface,
|
|
98
|
+
components: componentInterface
|
|
99
|
+
): Promise<apiResponse | null> => {
|
|
100
|
+
// 1. If a result is already cached and caching is enabled, return it.
|
|
101
|
+
if (options.cache_api_call && apiPromiseResult) {
|
|
102
|
+
return Promise.resolve(apiPromiseResult);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 2. If a request is already in flight, return that promise to prevent duplicate calls.
|
|
106
|
+
if (currentApiPromise) {
|
|
107
|
+
return currentApiPromise;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. Otherwise, initiate a new API call with timeout.
|
|
111
|
+
const endpoint = 'https://api-dev.thumbmarkjs.com/thumbmark';
|
|
112
|
+
const fetchPromise = fetch(endpoint, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'x-api-key': options.api_key!,
|
|
116
|
+
'Authorization': 'custom-authorized',
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({ components: components, clientHash: hash(JSON.stringify(components)) }),
|
|
119
|
+
})
|
|
120
|
+
.then(response => {
|
|
121
|
+
// Handle HTTP errors that aren't network errors
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
124
|
+
}
|
|
125
|
+
return response.json();
|
|
126
|
+
})
|
|
127
|
+
.then(data => {
|
|
128
|
+
apiPromiseResult = data; // Cache the successful result
|
|
129
|
+
currentApiPromise = null; // Clear the in-flight promise
|
|
130
|
+
return data;
|
|
131
|
+
})
|
|
132
|
+
.catch(error => {
|
|
133
|
+
console.error('Error fetching pro data', error);
|
|
134
|
+
currentApiPromise = null; // Also clear the in-flight promise on error
|
|
135
|
+
// Return null instead of a string to prevent downstream crashes
|
|
136
|
+
return null;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Timeout logic
|
|
140
|
+
const timeoutMs = options.timeout || 5000;
|
|
141
|
+
const timeoutPromise = new Promise<apiResponse>((resolve) => {
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
resolve({
|
|
144
|
+
thumbmark: hash(JSON.stringify(components)),
|
|
145
|
+
info: { timed_out: true },
|
|
146
|
+
version: packageJson.version,
|
|
147
|
+
});
|
|
148
|
+
}, timeoutMs);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
currentApiPromise = Promise.race([fetchPromise, timeoutPromise]);
|
|
152
|
+
return currentApiPromise;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ===================== Main Thumbmark Logic =====================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Main entry point: collects all components, optionally calls API, and returns thumbmark data.
|
|
159
|
+
*
|
|
160
|
+
* @param options - Options for fingerprinting and API
|
|
161
|
+
* @returns thumbmarkResponse (elapsed is present only if options.performance is true)
|
|
162
|
+
*/
|
|
163
|
+
export async function getThumbmark(options?: optionsInterface): Promise<thumbmarkResponse> {
|
|
164
|
+
const _options = { ...defaultOptions, ...options };
|
|
165
|
+
// Merge built-in and user-registered components
|
|
166
|
+
const allComponents = { ...tm_component_promises, ...customComponents };
|
|
167
|
+
const { elapsed, resolvedComponents: clientComponentsResult } = await resolveClientComponents(allComponents, _options);
|
|
168
|
+
|
|
169
|
+
const apiPromise = _options.api_key ? getApiPromise(_options, clientComponentsResult) : null;
|
|
170
|
+
const apiResult = apiPromise ? await apiPromise : null;
|
|
171
|
+
|
|
172
|
+
// Only add 'elapsed' if performance is true
|
|
173
|
+
const maybeElapsed = _options.performance ? { elapsed } : {};
|
|
174
|
+
|
|
175
|
+
if (apiResult) {
|
|
176
|
+
const info: infoInterface = apiResult.info || {};
|
|
177
|
+
return {
|
|
178
|
+
components: clientComponentsResult,
|
|
179
|
+
info,
|
|
180
|
+
version: getVersion(),
|
|
181
|
+
thumbmark: apiResult.thumbmark || 'undefined',
|
|
182
|
+
...maybeElapsed,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
thumbmark: hash(JSON.stringify(clientComponentsResult)),
|
|
187
|
+
components: clientComponentsResult,
|
|
188
|
+
info: {
|
|
189
|
+
uniqueness: 'api only'
|
|
190
|
+
},
|
|
191
|
+
version: getVersion(),
|
|
192
|
+
...maybeElapsed,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ===================== Component Resolution & Performance =====================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolves and times all filtered component promises from a component function map.
|
|
200
|
+
*
|
|
201
|
+
* @param comps - Map of component functions
|
|
202
|
+
* @param options - Options for filtering and timing
|
|
203
|
+
* @returns Object with elapsed times and filtered resolved components
|
|
204
|
+
*/
|
|
205
|
+
export async function resolveClientComponents(
|
|
206
|
+
comps: { [key: string]: (options?: optionsInterface) => Promise<componentInterface | null> },
|
|
207
|
+
options?: optionsInterface
|
|
208
|
+
): Promise<{ elapsed: Record<string, number>, resolvedComponents: componentInterface }> {
|
|
209
|
+
const opts = { ...defaultOptions, ...options };
|
|
210
|
+
const filtered = Object.entries(comps)
|
|
211
|
+
.filter(([key]) => !opts?.exclude?.includes(key))
|
|
212
|
+
.filter(([key]) =>
|
|
213
|
+
opts?.include?.some(e => e.includes('.'))
|
|
214
|
+
? opts?.include?.some(e => e.startsWith(key))
|
|
215
|
+
: opts?.include?.length === 0 || opts?.include?.includes(key)
|
|
216
|
+
);
|
|
217
|
+
const keys = filtered.map(([key]) => key);
|
|
218
|
+
const promises = filtered.map(([_, fn]) => fn(options));
|
|
219
|
+
const resolvedValues = await raceAllPerformance(promises, opts?.timeout || 5000, timeoutInstance);
|
|
220
|
+
|
|
221
|
+
const elapsed: Record<string, number> = {};
|
|
222
|
+
const resolvedComponentsRaw: Record<string, componentInterface> = {};
|
|
223
|
+
|
|
224
|
+
resolvedValues.forEach((value, index) => {
|
|
225
|
+
if (value.value != null) {
|
|
226
|
+
resolvedComponentsRaw[keys[index]] = value.value;
|
|
227
|
+
elapsed[keys[index]] = value.elapsed ?? 0;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const resolvedComponents = filterThumbmarkData(resolvedComponentsRaw, opts, "");
|
|
232
|
+
return { elapsed, resolvedComponents };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ===================== Logging (Internal) =====================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Logs thumbmark data to remote logging endpoint (only once per session)
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
async function logThumbmarkData(thisHash: string, thumbmarkData: componentInterface) {
|
|
242
|
+
const url = 'https://logging.thumbmarkjs.com/v1/log';
|
|
243
|
+
const payload = {
|
|
244
|
+
thumbmark: thisHash,
|
|
245
|
+
components: thumbmarkData,
|
|
246
|
+
version: getVersion()
|
|
247
|
+
};
|
|
248
|
+
if (!sessionStorage.getItem("_tmjs_l")) {
|
|
249
|
+
sessionStorage.setItem("_tmjs_l", "1");
|
|
250
|
+
try {
|
|
251
|
+
await fetch(url, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json'
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify(payload)
|
|
257
|
+
});
|
|
258
|
+
} catch { /* do nothing */ }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { globalIncludeComponent as includeComponent };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is here to support legacy implementations.
|
|
3
|
+
* Eventually, these functions will be removed to keep the library small.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { componentInterface } from '../factory'
|
|
7
|
+
import { options} from '../options'
|
|
8
|
+
import { resolveClientComponents, getThumbmark } from '.';
|
|
9
|
+
import { tm_component_promises } from "../factory";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @deprecated
|
|
14
|
+
*/
|
|
15
|
+
export async function getFingerprintData() {
|
|
16
|
+
const thumbmarkData = await getThumbmark(options);
|
|
17
|
+
return thumbmarkData.components;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param includeData boolean
|
|
23
|
+
* @deprecated this function is going to be removed. use getThumbmark or Thumbmark class instead.
|
|
24
|
+
*/
|
|
25
|
+
export async function getFingerprint(includeData?: false): Promise<string>
|
|
26
|
+
export async function getFingerprint(includeData: true): Promise<{ hash: string, data: componentInterface }>
|
|
27
|
+
export async function getFingerprint(includeData?: boolean): Promise<string | { hash: string, data: componentInterface }> {
|
|
28
|
+
try {
|
|
29
|
+
const thumbmarkData = await getThumbmark(options);
|
|
30
|
+
if (includeData) {
|
|
31
|
+
return { hash: thumbmarkData.thumbmark.toString(), data: thumbmarkData.components}
|
|
32
|
+
} else {
|
|
33
|
+
return thumbmarkData.thumbmark.toString()
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @deprecated use Thumbmark or getThumbmark instead with options
|
|
42
|
+
*/
|
|
43
|
+
export async function getFingerprintPerformance() {
|
|
44
|
+
try {
|
|
45
|
+
const { elapsed, resolvedComponents } = await resolveClientComponents(tm_component_promises, options);
|
|
46
|
+
// Legacy format: merge resolvedComponents and elapsed into one object
|
|
47
|
+
return {
|
|
48
|
+
...resolvedComponents,
|
|
49
|
+
elapsed
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
getFingerprint,
|
|
3
|
+
getFingerprintData,
|
|
4
|
+
getFingerprintPerformance
|
|
5
|
+
} from './functions/legacy_functions'
|
|
6
|
+
import { getThumbmark, getVersion } from './functions'
|
|
7
|
+
import { setOption } from './options'
|
|
3
8
|
import { includeComponent } from './factory'
|
|
4
|
-
import './
|
|
9
|
+
import { Thumbmark } from './thumbmark'
|
|
5
10
|
|
|
6
|
-
export {
|
|
11
|
+
export { Thumbmark, getThumbmark, getVersion,
|
|
12
|
+
|
|
13
|
+
// deprecated functions. Don't use anymore.
|
|
14
|
+
setOption, getFingerprint, getFingerprintData, getFingerprintPerformance, includeComponent
|
|
15
|
+
}
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
export interface optionsInterface {
|
|
2
|
-
exclude
|
|
3
|
-
include
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
exclude?: string[],
|
|
3
|
+
include?: string[],
|
|
4
|
+
permissions_to_check?: PermissionName[],
|
|
5
|
+
timeout?: number,
|
|
6
|
+
logging?: boolean,
|
|
7
|
+
api_key?: string,
|
|
8
|
+
cache_api_call?: boolean,
|
|
9
|
+
performance?: boolean, // re-added
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export const defaultOptions: optionsInterface = {
|
|
13
13
|
exclude: [],
|
|
14
14
|
include: [],
|
|
15
15
|
logging: true,
|
|
16
|
-
timeout:
|
|
17
|
-
|
|
16
|
+
timeout: 5000,
|
|
17
|
+
cache_api_call: true,
|
|
18
|
+
performance: false // re-added
|
|
19
|
+
};
|
|
18
20
|
|
|
21
|
+
export let options = {...defaultOptions};
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param key @deprecated this function will be removed
|
|
25
|
+
* @param value
|
|
26
|
+
*/
|
|
19
27
|
export function setOption<K extends keyof optionsInterface>(key: K, value: optionsInterface[K]) {
|
|
20
|
-
if (!['include', 'exclude', 'permissions_to_check', 'retries', 'timeout', 'logging'].includes(key))
|
|
28
|
+
if (!['include', 'exclude', 'permissions_to_check', 'retries', 'timeout', 'logging', 'api_key', 'cache_api_call'].includes(key))
|
|
21
29
|
throw new Error('Unknown option ' + key)
|
|
22
30
|
if (['include', 'exclude', 'permissions_to_check'].includes(key) && !(Array.isArray(value) && value.every(item => typeof item === 'string')) )
|
|
23
31
|
throw new Error('The value of the include, exclude and permissions_to_check must be an array of strings');
|
package/src/thumbmark.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { optionsInterface } from "./options";
|
|
2
|
+
import { getThumbmark, getVersion, includeComponent as globalIncludeComponent } from './functions';
|
|
3
|
+
import { defaultOptions } from "./options";
|
|
4
|
+
import { componentInterface } from "./factory";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A client for generating thumbmarks with a persistent configuration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class Thumbmark {
|
|
11
|
+
private options: optionsInterface;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Thumbmarker client instance.
|
|
15
|
+
* @param options - Default configuration options for this instance.
|
|
16
|
+
*/
|
|
17
|
+
constructor(options?: optionsInterface) {
|
|
18
|
+
this.options = { ...defaultOptions, ...options };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generates a thumbmark using the instance's configuration.
|
|
23
|
+
* @param overrideOptions - Options to override for this specific call.
|
|
24
|
+
* @returns The thumbmark result.
|
|
25
|
+
*/
|
|
26
|
+
public async get(overrideOptions?: optionsInterface): Promise<any> {
|
|
27
|
+
const finalOptions = { ...this.options, ...overrideOptions };
|
|
28
|
+
return getThumbmark(finalOptions);
|
|
29
|
+
}
|
|
30
|
+
public getVersion(): string {
|
|
31
|
+
return getVersion()
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register a custom component to be included in the fingerprint.
|
|
35
|
+
* @param key - The component name
|
|
36
|
+
* @param fn - The component function
|
|
37
|
+
*/
|
|
38
|
+
public includeComponent(key: string, fn: (options?: optionsInterface) => Promise<componentInterface | null>) {
|
|
39
|
+
globalIncludeComponent(key, fn);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/utils/raceAll.ts
CHANGED
package/src/components/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Does anyone have a cleaner way of doing this?
|
|
3
|
-
* I want to import all the components in this folder
|
|
4
|
-
* Feels a little dumb I'm doing this manually.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import './audio/audio'
|
|
8
|
-
import './canvas/canvas'
|
|
9
|
-
import './fonts/fonts'
|
|
10
|
-
import './hardware/hardware'
|
|
11
|
-
import './locales/locales'
|
|
12
|
-
import './permissions/permissions'
|
|
13
|
-
import './plugins/plugins'
|
|
14
|
-
import './screen/screen'
|
|
15
|
-
import './system/system'
|
|
16
|
-
import './webgl/webgl'
|
|
17
|
-
import './math/math'
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { componentInterface, includeComponent } from '../../factory'
|
|
2
|
-
|
|
3
|
-
const getMathInfo = async (): Promise<componentInterface> => {
|
|
4
|
-
return {
|
|
5
|
-
acos: Math.acos(0.5),
|
|
6
|
-
asin: integrate(Math.asin, -1, 1, 97),
|
|
7
|
-
atan: integrate(Math.atan, -1, 1, 97),
|
|
8
|
-
cos: integrate(Math.cos, 0, Math.PI, 97),
|
|
9
|
-
cosh: Math.cosh(9/7),
|
|
10
|
-
e: Math.E,
|
|
11
|
-
largeCos: Math.cos(1e20),
|
|
12
|
-
largeSin: Math.sin(1e20),
|
|
13
|
-
largeTan: Math.tan(1e20),
|
|
14
|
-
log: Math.log(1000),
|
|
15
|
-
pi: Math.PI,
|
|
16
|
-
sin: integrate(Math.sin, -Math.PI, Math.PI, 97),
|
|
17
|
-
sinh: integrate(Math.sinh, -9/7, 7/9, 97),
|
|
18
|
-
sqrt: Math.sqrt(2),
|
|
19
|
-
tan: integrate(Math.tan, 0, 2 * Math.PI, 97),
|
|
20
|
-
tanh: integrate(Math.tanh, -9/7, 7/9, 97),
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** This might be a little excessive, but I wasn't sure what number to pick for some of the
|
|
25
|
-
* trigonometric functions. Using an integral here, so a few numbers are calculated. However,
|
|
26
|
-
* I do this mainly for those integrals that sum up to a small value, otherwise there's no point.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
const integrate = (f: (x: number) => number, a: number, b: number, n: number): number => {
|
|
30
|
-
const h = (b - a) / n;
|
|
31
|
-
let sum = 0;
|
|
32
|
-
for (let i = 0; i < n; i++) {
|
|
33
|
-
const x = a + (i + 0.5) * h;
|
|
34
|
-
sum += f(x);
|
|
35
|
-
}
|
|
36
|
-
return sum * h;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
includeComponent('math', getMathInfo);
|