@thumbmarkjs/thumbmarkjs 0.19.1 → 0.20.0
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 +1 -1
- package/dist/thumbmark.cjs.js +1 -1
- package/dist/thumbmark.cjs.js.map +1 -1
- 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 +3 -2
- package/src/components/audio/audio.ts +64 -0
- package/src/components/canvas/canvas.test.ts +38 -0
- package/src/components/canvas/canvas.ts +90 -0
- package/src/components/fonts/fonts.ts +145 -0
- package/src/components/hardware/hardware.ts +69 -0
- package/src/components/index.ts +17 -0
- package/src/components/locales/locales.ts +23 -0
- package/src/components/math/math.ts +39 -0
- package/src/components/permissions/permissions.ts +58 -0
- package/src/components/plugins/plugins.ts +22 -0
- package/src/components/screen/screen.ts +46 -0
- package/src/components/system/browser.ts +63 -0
- package/src/components/system/system.ts +40 -0
- package/src/components/webgl/webgl.ts +148 -0
- package/src/declarations.d.ts +15 -0
- package/src/factory.ts +58 -0
- package/src/fingerprint/functions.test.ts +34 -0
- package/src/fingerprint/functions.ts +118 -0
- package/src/fingerprint/options.ts +26 -0
- package/src/index.ts +6 -0
- package/src/utils/commonPixels.ts +38 -0
- package/src/utils/ephemeralIFrame.ts +35 -0
- package/src/utils/getMostFrequent.ts +39 -0
- package/src/utils/hash.ts +198 -0
- package/src/utils/imageDataToDataURL.ts +15 -0
- package/src/utils/raceAll.ts +45 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {componentInterface, getComponentPromises, timeoutInstance} from '../factory'
|
|
2
|
+
import {hash} from '../utils/hash'
|
|
3
|
+
import {raceAll, raceAllPerformance} from '../utils/raceAll'
|
|
4
|
+
import {options} from './options'
|
|
5
|
+
import * as packageJson from '../../package.json'
|
|
6
|
+
|
|
7
|
+
export function getVersion(): string {
|
|
8
|
+
return packageJson.version
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getFingerprintData(): Promise<componentInterface> {
|
|
12
|
+
try {
|
|
13
|
+
const promiseMap: Record<string, Promise<componentInterface>> = getComponentPromises()
|
|
14
|
+
const keys: string[] = Object.keys(promiseMap)
|
|
15
|
+
const promises: Promise<componentInterface>[] = Object.values(promiseMap)
|
|
16
|
+
const resolvedValues: (componentInterface | undefined)[] = await raceAll(promises, options?.timeout || 1000, timeoutInstance);
|
|
17
|
+
const validValues: componentInterface[] = resolvedValues.filter((value): value is componentInterface => value !== undefined);
|
|
18
|
+
const resolvedComponents: Record<string, componentInterface> = {};
|
|
19
|
+
validValues.forEach((value, index) => {
|
|
20
|
+
resolvedComponents[keys[index]] = value
|
|
21
|
+
})
|
|
22
|
+
return filterFingerprintData(resolvedComponents, options.exclude || [], options.include || [], "")
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This function filters the fingerprint data based on the exclude and include list
|
|
31
|
+
* @param {componentInterface} obj - components objects from main componentInterface
|
|
32
|
+
* @param {string[]} excludeList - elements to exclude from components objects (e.g : 'canvas', 'system.browser')
|
|
33
|
+
* @param {string[]} includeList - elements to only include from components objects (e.g : 'canvas', 'system.browser')
|
|
34
|
+
* @param {string} path - auto-increment path iterating on key objects from components objects
|
|
35
|
+
* @returns {componentInterface} result - returns the final object before hashing in order to get fingerprint
|
|
36
|
+
*/
|
|
37
|
+
export function filterFingerprintData(obj: componentInterface, excludeList: string[], includeList: string[], path: string = ""): componentInterface {
|
|
38
|
+
const result: componentInterface = {};
|
|
39
|
+
|
|
40
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
41
|
+
const currentPath = path + key + ".";
|
|
42
|
+
|
|
43
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
44
|
+
const filtered = filterFingerprintData(value, excludeList, includeList, currentPath);
|
|
45
|
+
if (Object.keys(filtered).length > 0) {
|
|
46
|
+
result[key] = filtered;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
const isExcluded = excludeList.some(exclusion => currentPath.startsWith(exclusion));
|
|
50
|
+
const isIncluded = includeList.some(inclusion => currentPath.startsWith(inclusion));
|
|
51
|
+
|
|
52
|
+
if (!isExcluded || isIncluded) {
|
|
53
|
+
result[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getFingerprint(includeData?: false): Promise<string>
|
|
62
|
+
export async function getFingerprint(includeData: true): Promise<{ hash: string, data: componentInterface }>
|
|
63
|
+
export async function getFingerprint(includeData?: boolean): Promise<string | { hash: string, data: componentInterface }> {
|
|
64
|
+
try {
|
|
65
|
+
const fingerprintData = await getFingerprintData()
|
|
66
|
+
const thisHash = hash(JSON.stringify(fingerprintData))
|
|
67
|
+
if (Math.random() < 0.001 && options.logging) logFingerprintData(thisHash, fingerprintData)
|
|
68
|
+
if (includeData) {
|
|
69
|
+
return { hash: thisHash.toString(), data: fingerprintData}
|
|
70
|
+
} else {
|
|
71
|
+
return thisHash.toString()
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function getFingerprintPerformance() {
|
|
79
|
+
try {
|
|
80
|
+
const promiseMap = getComponentPromises()
|
|
81
|
+
const keys = Object.keys(promiseMap)
|
|
82
|
+
const promises = Object.values(promiseMap)
|
|
83
|
+
const resolvedValues = await raceAllPerformance(promises, options?.timeout || 1000, timeoutInstance )
|
|
84
|
+
const resolvedComponents: { [key: string]: any } = {
|
|
85
|
+
elapsed: {}
|
|
86
|
+
}
|
|
87
|
+
resolvedValues.forEach((value, index) => {
|
|
88
|
+
resolvedComponents[keys[index]] = value.value
|
|
89
|
+
resolvedComponents["elapsed"][keys[index]] = value.elapsed
|
|
90
|
+
});
|
|
91
|
+
return resolvedComponents
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Function to log the fingerprint data
|
|
99
|
+
async function logFingerprintData(thisHash: string, fingerprintData: componentInterface) {
|
|
100
|
+
const url = 'https://logging.thumbmarkjs.com/v1/log'
|
|
101
|
+
const payload = {
|
|
102
|
+
thumbmark: thisHash,
|
|
103
|
+
components: fingerprintData,
|
|
104
|
+
version: getVersion()
|
|
105
|
+
};
|
|
106
|
+
if (!sessionStorage.getItem("_tmjs_l")) {
|
|
107
|
+
sessionStorage.setItem("_tmjs_l", "1")
|
|
108
|
+
try {
|
|
109
|
+
await fetch(url, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'application/json'
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify(payload)
|
|
115
|
+
});
|
|
116
|
+
} catch { } // do nothing
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface optionsInterface {
|
|
2
|
+
exclude: string[],
|
|
3
|
+
include: string[],
|
|
4
|
+
webgl_runs?: number,
|
|
5
|
+
canvas_runs?: number,
|
|
6
|
+
permissions_to_check?: PermissionName[], // new option
|
|
7
|
+
retries?: number, // new option
|
|
8
|
+
timeout?: number, // new option
|
|
9
|
+
logging: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export let options: optionsInterface = {
|
|
13
|
+
exclude: [],
|
|
14
|
+
include: [],
|
|
15
|
+
logging: true,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function setOption<K extends keyof optionsInterface>(key: K, value: optionsInterface[K]) {
|
|
19
|
+
if (!['include', 'exclude', 'permissions_to_check', 'retries', 'timeout', 'logging'].includes(key))
|
|
20
|
+
throw new Error('Unknown option ' + key)
|
|
21
|
+
if (['include', 'exclude', 'permissions_to_check'].includes(key) && !(Array.isArray(value) && value.every(item => typeof item === 'string')) )
|
|
22
|
+
throw new Error('The value of the include, exclude and permissions_to_check must be an array of strings');
|
|
23
|
+
if ([ 'retries', 'timeout'].includes(key) && typeof value !== 'number')
|
|
24
|
+
throw new Error('The value of retries must be a number');
|
|
25
|
+
options[key] = value;
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getFingerprint, getFingerprintData, getFingerprintPerformance, getVersion } from './fingerprint/functions'
|
|
2
|
+
import { setOption } from './fingerprint/options'
|
|
3
|
+
import { includeComponent } from './factory'
|
|
4
|
+
import './components'
|
|
5
|
+
|
|
6
|
+
export { setOption, getVersion, getFingerprint, getFingerprintData, getFingerprintPerformance, includeComponent }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function getCommonPixels(images: ImageData[], width: number, height: number ): ImageData {
|
|
2
|
+
let finalData: number[] = [];
|
|
3
|
+
for (let i = 0; i < images[0].data.length; i++) {
|
|
4
|
+
let indice: number[] = [];
|
|
5
|
+
for (let u = 0; u < images.length; u++) {
|
|
6
|
+
indice.push(images[u].data[i]);
|
|
7
|
+
}
|
|
8
|
+
finalData.push(getMostFrequent(indice));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const pixelData = finalData;
|
|
12
|
+
const pixelArray = new Uint8ClampedArray(pixelData);
|
|
13
|
+
return new ImageData(pixelArray, width, height);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getMostFrequent(arr: number[]): number {
|
|
17
|
+
if (arr.length === 0) {
|
|
18
|
+
return 0; // Handle empty array case
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const frequencyMap: { [key: number]: number } = {};
|
|
22
|
+
|
|
23
|
+
// Count occurrences of each number in the array
|
|
24
|
+
for (const num of arr) {
|
|
25
|
+
frequencyMap[num] = (frequencyMap[num] || 0) + 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let mostFrequent: number = arr[0];
|
|
29
|
+
|
|
30
|
+
// Find the number with the highest frequency
|
|
31
|
+
for (const num in frequencyMap) {
|
|
32
|
+
if (frequencyMap[num] > frequencyMap[mostFrequent]) {
|
|
33
|
+
mostFrequent = parseInt(num, 10);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return mostFrequent;
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export async function ephemeralIFrame(callback: ({ iframe }: { iframe: Document }) => void): Promise<any> {
|
|
2
|
+
|
|
3
|
+
while (!document.body) {
|
|
4
|
+
await wait(50)
|
|
5
|
+
}
|
|
6
|
+
const iframe = document.createElement('iframe')
|
|
7
|
+
iframe.setAttribute('frameBorder', '0')
|
|
8
|
+
|
|
9
|
+
const style = iframe.style
|
|
10
|
+
style.setProperty('position','fixed');
|
|
11
|
+
style.setProperty('display', 'block', 'important')
|
|
12
|
+
style.setProperty('visibility', 'visible')
|
|
13
|
+
style.setProperty('border', '0');
|
|
14
|
+
style.setProperty('opacity','0');
|
|
15
|
+
|
|
16
|
+
iframe.src = 'about:blank'
|
|
17
|
+
document.body.appendChild(iframe)
|
|
18
|
+
|
|
19
|
+
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
|
|
20
|
+
if (!iframeDocument) {
|
|
21
|
+
throw new Error('Iframe document is not accessible');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Execute the callback function with access to the iframe's document
|
|
25
|
+
callback({ iframe: iframeDocument });
|
|
26
|
+
|
|
27
|
+
// Clean up after running the callback
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
document.body.removeChild(iframe);
|
|
30
|
+
}, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function wait<T = void>(durationMs: number, resolveWith?: T): Promise<T> {
|
|
34
|
+
return new Promise((resolve) => setTimeout(resolve, durationMs, resolveWith))
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function mostFrequentValue(arr: any[]): any | null {
|
|
2
|
+
if (arr.length === 0) {
|
|
3
|
+
return null; // Return null for an empty array
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const frequencyMap: { [key: string]: number } = {};
|
|
7
|
+
|
|
8
|
+
// Count occurrences of each element in the array
|
|
9
|
+
arr.forEach((element) => {
|
|
10
|
+
const key = String(element);
|
|
11
|
+
frequencyMap[key] = (frequencyMap[key] || 0) + 1;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let mostFrequent: any = arr[0]; // Assume the first element is the most frequent
|
|
15
|
+
let highestFrequency = 1; // Frequency of the assumed most frequent element
|
|
16
|
+
|
|
17
|
+
// Find the element with the highest frequency
|
|
18
|
+
Object.keys(frequencyMap).forEach((key) => {
|
|
19
|
+
if (frequencyMap[key] > highestFrequency) {
|
|
20
|
+
mostFrequent = key;
|
|
21
|
+
highestFrequency = frequencyMap[key];
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return mostFrequent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function mostFrequentValuesInArrayOfDictionaries(arr: { [key: string]: any }[], keys: string[]): { [key: string]: any } {
|
|
29
|
+
const result: { [key: string]: any } = {};
|
|
30
|
+
|
|
31
|
+
keys.forEach((key) => {
|
|
32
|
+
const valuesForKey = arr.map((obj) => (key in obj ? obj[key] : undefined)).filter((val) => val !== undefined);
|
|
33
|
+
const mostFrequentValueForKey = mostFrequentValue(valuesForKey);
|
|
34
|
+
if (mostFrequentValueForKey)
|
|
35
|
+
result[key] = mostFrequentValueForKey;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code is taken from https://github.com/LinusU/murmur-128/blob/master/index.js
|
|
3
|
+
* But instead of dependencies to encode-utf8 and fmix, I've implemented them here.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function encodeUtf8(text: string): ArrayBuffer {
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
return encoder.encode(text).buffer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function fmix (input : number) : number {
|
|
12
|
+
input ^= (input >>> 16)
|
|
13
|
+
input = Math.imul(input, 0x85ebca6b)
|
|
14
|
+
input ^= (input >>> 13)
|
|
15
|
+
input = Math.imul(input, 0xc2b2ae35)
|
|
16
|
+
input ^= (input >>> 16)
|
|
17
|
+
|
|
18
|
+
return (input >>> 0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const C = new Uint32Array([
|
|
22
|
+
0x239b961b,
|
|
23
|
+
0xab0e9789,
|
|
24
|
+
0x38b34ae5,
|
|
25
|
+
0xa1e38b93
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
function rotl (m : number, n : number) : number {
|
|
29
|
+
return (m << n) | (m >>> (32 - n))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function body (key : ArrayBuffer, hash : Uint32Array) {
|
|
33
|
+
const blocks = (key.byteLength / 16) | 0
|
|
34
|
+
const view32 = new Uint32Array(key, 0, blocks * 4)
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < blocks; i++) {
|
|
37
|
+
const k = view32.subarray(i * 4, (i + 1) * 4)
|
|
38
|
+
|
|
39
|
+
k[0] = Math.imul(k[0], C[0])
|
|
40
|
+
k[0] = rotl(k[0], 15)
|
|
41
|
+
k[0] = Math.imul(k[0], C[1])
|
|
42
|
+
|
|
43
|
+
hash[0] = (hash[0] ^ k[0])
|
|
44
|
+
hash[0] = rotl(hash[0], 19)
|
|
45
|
+
hash[0] = (hash[0] + hash[1])
|
|
46
|
+
hash[0] = Math.imul(hash[0], 5) + 0x561ccd1b
|
|
47
|
+
|
|
48
|
+
k[1] = Math.imul(k[1], C[1])
|
|
49
|
+
k[1] = rotl(k[1], 16)
|
|
50
|
+
k[1] = Math.imul(k[1], C[2])
|
|
51
|
+
|
|
52
|
+
hash[1] = (hash[1] ^ k[1])
|
|
53
|
+
hash[1] = rotl(hash[1], 17)
|
|
54
|
+
hash[1] = (hash[1] + hash[2])
|
|
55
|
+
hash[1] = Math.imul(hash[1], 5) + 0x0bcaa747
|
|
56
|
+
|
|
57
|
+
k[2] = Math.imul(k[2], C[2])
|
|
58
|
+
k[2] = rotl(k[2], 17)
|
|
59
|
+
k[2] = Math.imul(k[2], C[3])
|
|
60
|
+
|
|
61
|
+
hash[2] = (hash[2] ^ k[2])
|
|
62
|
+
hash[2] = rotl(hash[2], 15)
|
|
63
|
+
hash[2] = (hash[2] + hash[3])
|
|
64
|
+
hash[2] = Math.imul(hash[2], 5) + 0x96cd1c35
|
|
65
|
+
|
|
66
|
+
k[3] = Math.imul(k[3], C[3])
|
|
67
|
+
k[3] = rotl(k[3], 18)
|
|
68
|
+
k[3] = Math.imul(k[3], C[0])
|
|
69
|
+
|
|
70
|
+
hash[3] = (hash[3] ^ k[3])
|
|
71
|
+
hash[3] = rotl(hash[3], 13)
|
|
72
|
+
hash[3] = (hash[3] + hash[0])
|
|
73
|
+
hash[3] = Math.imul(hash[3], 5) + 0x32ac3b17
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function tail (key : ArrayBuffer, hash : Uint32Array) {
|
|
78
|
+
const blocks = (key.byteLength / 16) | 0
|
|
79
|
+
const reminder = (key.byteLength % 16)
|
|
80
|
+
|
|
81
|
+
const k = new Uint32Array(4)
|
|
82
|
+
const tail = new Uint8Array(key, blocks * 16, reminder)
|
|
83
|
+
|
|
84
|
+
switch (reminder) {
|
|
85
|
+
case 15:
|
|
86
|
+
k[3] = (k[3] ^ (tail[14] << 16))
|
|
87
|
+
// fallthrough
|
|
88
|
+
case 14:
|
|
89
|
+
k[3] = (k[3] ^ (tail[13] << 8))
|
|
90
|
+
// fallthrough
|
|
91
|
+
case 13:
|
|
92
|
+
k[3] = (k[3] ^ (tail[12] << 0))
|
|
93
|
+
|
|
94
|
+
k[3] = Math.imul(k[3], C[3])
|
|
95
|
+
k[3] = rotl(k[3], 18)
|
|
96
|
+
k[3] = Math.imul(k[3], C[0])
|
|
97
|
+
hash[3] = (hash[3] ^ k[3])
|
|
98
|
+
// fallthrough
|
|
99
|
+
case 12:
|
|
100
|
+
k[2] = (k[2] ^ (tail[11] << 24))
|
|
101
|
+
// fallthrough
|
|
102
|
+
case 11:
|
|
103
|
+
k[2] = (k[2] ^ (tail[10] << 16))
|
|
104
|
+
// fallthrough
|
|
105
|
+
case 10:
|
|
106
|
+
k[2] = (k[2] ^ (tail[9] << 8))
|
|
107
|
+
// fallthrough
|
|
108
|
+
case 9:
|
|
109
|
+
k[2] = (k[2] ^ (tail[8] << 0))
|
|
110
|
+
|
|
111
|
+
k[2] = Math.imul(k[2], C[2])
|
|
112
|
+
k[2] = rotl(k[2], 17)
|
|
113
|
+
k[2] = Math.imul(k[2], C[3])
|
|
114
|
+
hash[2] = (hash[2] ^ k[2])
|
|
115
|
+
// fallthrough
|
|
116
|
+
case 8:
|
|
117
|
+
k[1] = (k[1] ^ (tail[7] << 24))
|
|
118
|
+
// fallthrough
|
|
119
|
+
case 7:
|
|
120
|
+
k[1] = (k[1] ^ (tail[6] << 16))
|
|
121
|
+
// fallthrough
|
|
122
|
+
case 6:
|
|
123
|
+
k[1] = (k[1] ^ (tail[5] << 8))
|
|
124
|
+
// fallthrough
|
|
125
|
+
case 5:
|
|
126
|
+
k[1] = (k[1] ^ (tail[4] << 0))
|
|
127
|
+
|
|
128
|
+
k[1] = Math.imul(k[1], C[1])
|
|
129
|
+
k[1] = rotl(k[1], 16)
|
|
130
|
+
k[1] = Math.imul(k[1], C[2])
|
|
131
|
+
hash[1] = (hash[1] ^ k[1])
|
|
132
|
+
// fallthrough
|
|
133
|
+
case 4:
|
|
134
|
+
k[0] = (k[0] ^ (tail[3] << 24))
|
|
135
|
+
// fallthrough
|
|
136
|
+
case 3:
|
|
137
|
+
k[0] = (k[0] ^ (tail[2] << 16))
|
|
138
|
+
// fallthrough
|
|
139
|
+
case 2:
|
|
140
|
+
k[0] = (k[0] ^ (tail[1] << 8))
|
|
141
|
+
// fallthrough
|
|
142
|
+
case 1:
|
|
143
|
+
k[0] = (k[0] ^ (tail[0] << 0))
|
|
144
|
+
|
|
145
|
+
k[0] = Math.imul(k[0], C[0])
|
|
146
|
+
k[0] = rotl(k[0], 15)
|
|
147
|
+
k[0] = Math.imul(k[0], C[1])
|
|
148
|
+
hash[0] = (hash[0] ^ k[0])
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function finalize (key : ArrayBuffer, hash : Uint32Array) {
|
|
153
|
+
hash[0] = (hash[0] ^ key.byteLength)
|
|
154
|
+
hash[1] = (hash[1] ^ key.byteLength)
|
|
155
|
+
hash[2] = (hash[2] ^ key.byteLength)
|
|
156
|
+
hash[3] = (hash[3] ^ key.byteLength)
|
|
157
|
+
|
|
158
|
+
hash[0] = (hash[0] + hash[1]) | 0
|
|
159
|
+
hash[0] = (hash[0] + hash[2]) | 0
|
|
160
|
+
hash[0] = (hash[0] + hash[3]) | 0
|
|
161
|
+
|
|
162
|
+
hash[1] = (hash[1] + hash[0]) | 0
|
|
163
|
+
hash[2] = (hash[2] + hash[0]) | 0
|
|
164
|
+
hash[3] = (hash[3] + hash[0]) | 0
|
|
165
|
+
|
|
166
|
+
hash[0] = fmix(hash[0])
|
|
167
|
+
hash[1] = fmix(hash[1])
|
|
168
|
+
hash[2] = fmix(hash[2])
|
|
169
|
+
hash[3] = fmix(hash[3])
|
|
170
|
+
|
|
171
|
+
hash[0] = (hash[0] + hash[1]) | 0
|
|
172
|
+
hash[0] = (hash[0] + hash[2]) | 0
|
|
173
|
+
hash[0] = (hash[0] + hash[3]) | 0
|
|
174
|
+
|
|
175
|
+
hash[1] = (hash[1] + hash[0]) | 0
|
|
176
|
+
hash[2] = (hash[2] + hash[0]) | 0
|
|
177
|
+
hash[3] = (hash[3] + hash[0]) | 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function hash (key : ArrayBuffer | string, seed = 0) : string {
|
|
181
|
+
seed = (seed ? (seed | 0) : 0)
|
|
182
|
+
|
|
183
|
+
if (typeof key === 'string') {
|
|
184
|
+
key = encodeUtf8(key)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!(key instanceof ArrayBuffer)) {
|
|
188
|
+
throw new TypeError('Expected key to be ArrayBuffer or string')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const hash = new Uint32Array([seed, seed, seed, seed])
|
|
192
|
+
|
|
193
|
+
body(key, hash)
|
|
194
|
+
tail(key, hash)
|
|
195
|
+
finalize(key, hash)
|
|
196
|
+
const byteArray = new Uint8Array(hash.buffer);
|
|
197
|
+
return Array.from(byteArray).map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
198
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function imageDataToDataURL(imageData: ImageData): string {
|
|
2
|
+
const canvas = document.createElement('canvas');
|
|
3
|
+
const context = canvas.getContext('2d');
|
|
4
|
+
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error('Canvas context not supported');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
canvas.width = imageData.width;
|
|
10
|
+
canvas.height = imageData.height;
|
|
11
|
+
|
|
12
|
+
context.putImageData(imageData, 0, 0);
|
|
13
|
+
|
|
14
|
+
return canvas.toDataURL();
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { componentInterface } from '../factory'
|
|
2
|
+
|
|
3
|
+
type DelayedPromise<T> = Promise<T>;
|
|
4
|
+
|
|
5
|
+
export function delay<T>(t: number, val: T): DelayedPromise<T> {
|
|
6
|
+
return new Promise<T>((resolve) => {
|
|
7
|
+
setTimeout(() => resolve(val), t);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export interface RaceResult<T> {
|
|
13
|
+
value: T;
|
|
14
|
+
elapsed?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function raceAllPerformance<T>(
|
|
18
|
+
promises: Promise<T>[],
|
|
19
|
+
timeoutTime: number,
|
|
20
|
+
timeoutVal: T
|
|
21
|
+
): Promise<RaceResult<T>[]> {
|
|
22
|
+
return Promise.all(
|
|
23
|
+
promises.map((p) => {
|
|
24
|
+
const startTime = performance.now();
|
|
25
|
+
return Promise.race([
|
|
26
|
+
p.then((value) => ({
|
|
27
|
+
value,
|
|
28
|
+
elapsed: performance.now() - startTime,
|
|
29
|
+
})),
|
|
30
|
+
delay(timeoutTime, timeoutVal).then((value) => ({
|
|
31
|
+
value,
|
|
32
|
+
elapsed: performance.now() - startTime,
|
|
33
|
+
})),
|
|
34
|
+
]);
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function raceAll<T>(promises: Promise<T>[], timeoutTime: number, timeoutVal: T): Promise<(T | undefined)[]> {
|
|
42
|
+
return Promise.all(promises.map((p) => {
|
|
43
|
+
return Promise.race([p, delay(timeoutTime, timeoutVal)]);
|
|
44
|
+
}));
|
|
45
|
+
}
|