get-browser-fingerprint 1.1.0 → 2.1.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/.babelrc +1 -0
- package/.eslintrc +6 -15
- package/.github/workflows/commit.yml +24 -0
- package/.github/workflows/tag.yml +28 -0
- package/.jest-run/Template Jest.run.xml +9 -0
- package/.nvmrc +1 -1
- package/README.md +21 -7
- package/jest.config.js +7 -0
- package/package.json +17 -14
- package/src/index.html +47 -0
- package/src/index.js +244 -117
- package/src/index.spec.js +51 -24
- package/src/index.spec.js.bak +34 -0
package/.babelrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/.eslintrc
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"browser": true,
|
|
9
|
-
"node": true
|
|
10
|
-
},
|
|
11
|
-
"extends": [
|
|
12
|
-
"eslint:recommended"
|
|
13
|
-
],
|
|
14
|
-
"rules": {
|
|
15
|
-
"no-console": "off"
|
|
16
|
-
}
|
|
2
|
+
"extends": [
|
|
3
|
+
"eslint-config-xs/react"
|
|
4
|
+
],
|
|
5
|
+
"rules": {
|
|
6
|
+
"jest/no-done-callback": "off"
|
|
7
|
+
}
|
|
17
8
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: tag
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-22.04
|
|
11
|
+
timeout-minutes: 3
|
|
12
|
+
steps:
|
|
13
|
+
- name: checkout
|
|
14
|
+
uses: actions/checkout@v2
|
|
15
|
+
|
|
16
|
+
- name: setup node
|
|
17
|
+
uses: actions/setup-node@v2
|
|
18
|
+
with:
|
|
19
|
+
node-version-file: '.nvmrc'
|
|
20
|
+
|
|
21
|
+
- name: test
|
|
22
|
+
run: |
|
|
23
|
+
yarn install
|
|
24
|
+
yarn test
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: tag
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- v*
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-22.04
|
|
11
|
+
timeout-minutes: 3
|
|
12
|
+
steps:
|
|
13
|
+
- name: checkout
|
|
14
|
+
uses: actions/checkout@v2
|
|
15
|
+
|
|
16
|
+
- name: setup node
|
|
17
|
+
uses: actions/setup-node@v2
|
|
18
|
+
with:
|
|
19
|
+
node-version-file: '.nvmrc'
|
|
20
|
+
registry-url: https://registry.npmjs.org/
|
|
21
|
+
|
|
22
|
+
- name: release
|
|
23
|
+
env:
|
|
24
|
+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
25
|
+
run: |
|
|
26
|
+
yarn install
|
|
27
|
+
yarn test
|
|
28
|
+
npm publish
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<component name="ProjectRunConfigurationManager">
|
|
2
|
+
<configuration default="true" type="JavaScriptTestRunnerJest">
|
|
3
|
+
<node-interpreter value="project" />
|
|
4
|
+
<node-options value="--experimental-vm-modules" />
|
|
5
|
+
<working-dir value="" />
|
|
6
|
+
<scope-kind value="ALL" />
|
|
7
|
+
<method v="2" />
|
|
8
|
+
</configuration>
|
|
9
|
+
</component>
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
v16.16
|
package/README.md
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
# get-browser-fingerprint
|
|
2
2
|
|
|
3
|
-
Zero dependencies package exporting a single function which computes a browser fingerprint.
|
|
3
|
+
Zero dependencies package exporting a single, fast (<15ms) and synchronous function which computes a browser fingerprint, without requiring any permission to the user.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
Get browser fingerprint:
|
|
8
|
-
```
|
|
8
|
+
```js
|
|
9
9
|
import getBrowserFingerprint from 'get-browser-fingerprint';
|
|
10
10
|
const fingerprint = getBrowserFingerprint();
|
|
11
11
|
console.log(fingerprint);
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Options available:
|
|
15
|
+
- `hardwareOnly` (default `false`): leverage only hardware info about device
|
|
16
|
+
- `enableWebgl` (default `false`): enable webgl renderer, ~4x times slower but adds another deadly powerful hardware detection layer on top of canvas
|
|
17
|
+
- `debug`: log data used to generate fingerprint to console and add canvas/webgl canvas to body to see rendered image (default `false`)
|
|
18
|
+
|
|
19
|
+
⚠️ Be careful: the strongest discriminating factor is canvas token which can't be computed on old devices (eg: iPhone 6), deal accordingly ⚠️
|
|
20
|
+
|
|
21
|
+
## Development
|
|
22
|
+
|
|
23
|
+
To test locally:
|
|
24
|
+
```sh
|
|
25
|
+
nvm install
|
|
26
|
+
yarn install
|
|
27
|
+
yarn test
|
|
19
28
|
```
|
|
29
|
+
|
|
30
|
+
To run example locally:
|
|
31
|
+
```sh
|
|
32
|
+
yarn http-server src -o -c-1 -p 80
|
|
33
|
+
```
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
"name": "get-browser-fingerprint",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"author": "Damiano Barbati <damiano.barbati@gmail.com> (https://github.com/damianobarbati)",
|
|
5
|
+
"repository": "https://github.com/damianobarbati/get-browser-fingerprint",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "src/index.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"eslint": "eslint --ignore-path .gitignore --fix",
|
|
11
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest --runInBand --no-cache"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"eslint-config-xs": "^1.3.0",
|
|
15
|
+
"http-server": "^14.1.1",
|
|
16
|
+
"jest": "^28.1.3",
|
|
17
|
+
"puppeteer": "^16.1.1"
|
|
18
|
+
}
|
|
16
19
|
}
|
package/src/index.html
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>get-browser-fingerprint demo</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>get-browser-fingerprint</h1>
|
|
8
|
+
|
|
9
|
+
<h2 id="default"></h2>
|
|
10
|
+
<h2 id="hardwareOnly"></h2>
|
|
11
|
+
<h2 id="enableWebgl"></h2>
|
|
12
|
+
|
|
13
|
+
<script type="module">
|
|
14
|
+
import getFingerprint from './index.js';
|
|
15
|
+
|
|
16
|
+
args_default: {
|
|
17
|
+
const t0 = performance.now();
|
|
18
|
+
const fingerprint = getFingerprint({ debug: true });
|
|
19
|
+
const t1 = performance.now();
|
|
20
|
+
|
|
21
|
+
const result = `Fingerprint: ${fingerprint} (computed in ${(t1 - t0).toFixed(0)} ms)`;
|
|
22
|
+
console.log(result);
|
|
23
|
+
document.getElementById('default').innerText = result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
args_hardwareOnly: {
|
|
27
|
+
const t0 = performance.now();
|
|
28
|
+
const fingerprint = getFingerprint({ hardwareOnly: true, debug: true });
|
|
29
|
+
const t1 = performance.now();
|
|
30
|
+
|
|
31
|
+
const result = `Fingerprint with hardwareOnly=true: ${fingerprint} (computed in ${(t1 - t0).toFixed(0)} ms)`;
|
|
32
|
+
console.log(result);
|
|
33
|
+
document.getElementById('hardwareOnly').innerText = result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
args_enableWebgl: {
|
|
37
|
+
const t0 = performance.now();
|
|
38
|
+
const fingerprint = getFingerprint({ enableWebgl: true, debug: true });
|
|
39
|
+
const t1 = performance.now();
|
|
40
|
+
|
|
41
|
+
const result = `Fingerprint with enableWebgl=true: ${fingerprint} (computed in ${(t1 - t0).toFixed(0)} ms)`;
|
|
42
|
+
console.log(result);
|
|
43
|
+
document.getElementById('enableWebgl').innerText = result;
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
package/src/index.js
CHANGED
|
@@ -1,121 +1,248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
h1 ^= key.length;
|
|
109
|
-
|
|
110
|
-
h1 ^= h1 >>> 16;
|
|
111
|
-
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
|
112
|
-
h1 ^= h1 >>> 13;
|
|
113
|
-
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
|
|
114
|
-
h1 ^= h1 >>> 16;
|
|
115
|
-
|
|
116
|
-
return h1 >>> 0;
|
|
1
|
+
const getBrowserFingerprint = ({ hardwareOnly = false, enableWebgl = false, debug = false } = {}) => {
|
|
2
|
+
const devicePixelRatio = +parseInt(window.devicePixelRatio);
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
appName,
|
|
6
|
+
appCodeName,
|
|
7
|
+
appVersion,
|
|
8
|
+
cookieEnabled,
|
|
9
|
+
deviceMemory,
|
|
10
|
+
doNotTrack,
|
|
11
|
+
hardwareConcurrency,
|
|
12
|
+
language,
|
|
13
|
+
languages,
|
|
14
|
+
maxTouchPoints,
|
|
15
|
+
platform,
|
|
16
|
+
product,
|
|
17
|
+
productSub,
|
|
18
|
+
userAgent,
|
|
19
|
+
vendor,
|
|
20
|
+
vendorSub,
|
|
21
|
+
} = window.navigator;
|
|
22
|
+
|
|
23
|
+
const { width, height, colorDepth, pixelDepth } = window.screen;
|
|
24
|
+
const timezoneOffset = new Date().getTimezoneOffset();
|
|
25
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
26
|
+
const touchSupport = 'ontouchstart' in window;
|
|
27
|
+
|
|
28
|
+
const canvas = getCanvasID(debug);
|
|
29
|
+
const webgl = enableWebgl ? getWebglID(debug) : undefined; // undefined will remove this from the stringify down here
|
|
30
|
+
const webglInfo = enableWebgl ? getWebglInfo(debug) : undefined; // undefined will remove this from the stringify down here
|
|
31
|
+
|
|
32
|
+
const data = hardwareOnly
|
|
33
|
+
? JSON.stringify({
|
|
34
|
+
canvas,
|
|
35
|
+
colorDepth,
|
|
36
|
+
deviceMemory,
|
|
37
|
+
devicePixelRatio,
|
|
38
|
+
hardwareConcurrency,
|
|
39
|
+
height,
|
|
40
|
+
maxTouchPoints,
|
|
41
|
+
pixelDepth,
|
|
42
|
+
platform,
|
|
43
|
+
touchSupport,
|
|
44
|
+
webgl,
|
|
45
|
+
webglInfo,
|
|
46
|
+
width,
|
|
47
|
+
})
|
|
48
|
+
: JSON.stringify({
|
|
49
|
+
appCodeName,
|
|
50
|
+
appName,
|
|
51
|
+
appVersion,
|
|
52
|
+
canvas,
|
|
53
|
+
colorDepth,
|
|
54
|
+
cookieEnabled,
|
|
55
|
+
deviceMemory,
|
|
56
|
+
devicePixelRatio,
|
|
57
|
+
doNotTrack,
|
|
58
|
+
hardwareConcurrency,
|
|
59
|
+
height,
|
|
60
|
+
language,
|
|
61
|
+
languages,
|
|
62
|
+
maxTouchPoints,
|
|
63
|
+
pixelDepth,
|
|
64
|
+
platform,
|
|
65
|
+
product,
|
|
66
|
+
productSub,
|
|
67
|
+
timezone,
|
|
68
|
+
timezoneOffset,
|
|
69
|
+
touchSupport,
|
|
70
|
+
userAgent,
|
|
71
|
+
vendor,
|
|
72
|
+
vendorSub,
|
|
73
|
+
webgl,
|
|
74
|
+
webglInfo,
|
|
75
|
+
width,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const datastring = JSON.stringify(data, null, 4);
|
|
79
|
+
|
|
80
|
+
if (debug) console.log('fingerprint data', datastring);
|
|
81
|
+
|
|
82
|
+
const result = murmurhash3_32_gc(datastring);
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const getCanvasID = (debug) => {
|
|
87
|
+
try {
|
|
88
|
+
const canvas = document.createElement('canvas');
|
|
89
|
+
const ctx = canvas.getContext('2d');
|
|
90
|
+
const text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}|;:',<.>/?";
|
|
91
|
+
ctx.textBaseline = 'top';
|
|
92
|
+
ctx.font = "14px 'Arial'";
|
|
93
|
+
ctx.textBaseline = 'alphabetic';
|
|
94
|
+
ctx.fillStyle = '#f60';
|
|
95
|
+
ctx.fillRect(125, 1, 62, 20);
|
|
96
|
+
ctx.fillStyle = '#069';
|
|
97
|
+
ctx.fillText(text, 2, 15);
|
|
98
|
+
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
|
|
99
|
+
ctx.fillText(text, 4, 17);
|
|
100
|
+
|
|
101
|
+
const result = canvas.toDataURL();
|
|
102
|
+
|
|
103
|
+
if (debug) {
|
|
104
|
+
document.body.appendChild(canvas);
|
|
105
|
+
} else {
|
|
106
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
117
107
|
}
|
|
118
108
|
|
|
119
|
-
|
|
109
|
+
return murmurhash3_32_gc(result);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const getWebglID = (debug) => {
|
|
116
|
+
try {
|
|
117
|
+
const canvas = document.createElement('canvas');
|
|
118
|
+
const ctx = canvas.getContext('webgl');
|
|
119
|
+
canvas.width = 256;
|
|
120
|
+
canvas.height = 128;
|
|
121
|
+
|
|
122
|
+
const f =
|
|
123
|
+
'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}';
|
|
124
|
+
const g = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}';
|
|
125
|
+
const h = ctx.createBuffer();
|
|
126
|
+
|
|
127
|
+
ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
|
|
128
|
+
|
|
129
|
+
const i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
|
|
130
|
+
|
|
131
|
+
ctx.bufferData(ctx.ARRAY_BUFFER, i, ctx.STATIC_DRAW), (h.itemSize = 3), (h.numItems = 3);
|
|
132
|
+
|
|
133
|
+
const j = ctx.createProgram();
|
|
134
|
+
const k = ctx.createShader(ctx.VERTEX_SHADER);
|
|
135
|
+
|
|
136
|
+
ctx.shaderSource(k, f);
|
|
137
|
+
ctx.compileShader(k);
|
|
138
|
+
|
|
139
|
+
const l = ctx.createShader(ctx.FRAGMENT_SHADER);
|
|
140
|
+
|
|
141
|
+
ctx.shaderSource(l, g);
|
|
142
|
+
ctx.compileShader(l);
|
|
143
|
+
ctx.attachShader(j, k);
|
|
144
|
+
ctx.attachShader(j, l);
|
|
145
|
+
ctx.linkProgram(j);
|
|
146
|
+
ctx.useProgram(j);
|
|
147
|
+
|
|
148
|
+
j.vertexPosAttrib = ctx.getAttribLocation(j, 'attrVertex');
|
|
149
|
+
j.offsetUniform = ctx.getUniformLocation(j, 'uniformOffset');
|
|
150
|
+
|
|
151
|
+
ctx.enableVertexAttribArray(j.vertexPosArray);
|
|
152
|
+
ctx.vertexAttribPointer(j.vertexPosAttrib, h.itemSize, ctx.FLOAT, !1, 0, 0);
|
|
153
|
+
ctx.uniform2f(j.offsetUniform, 1, 1);
|
|
154
|
+
ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, h.numItems);
|
|
155
|
+
|
|
156
|
+
const n = new Uint8Array(canvas.width * canvas.height * 4);
|
|
157
|
+
ctx.readPixels(0, 0, canvas.width, canvas.height, ctx.RGBA, ctx.UNSIGNED_BYTE, n);
|
|
158
|
+
|
|
159
|
+
const result = JSON.stringify(n).replace(/,?"[0-9]+":/g, '');
|
|
160
|
+
|
|
161
|
+
if (debug) {
|
|
162
|
+
document.body.appendChild(canvas);
|
|
163
|
+
} else {
|
|
164
|
+
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return murmurhash3_32_gc(result);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const getWebglInfo = () => {
|
|
174
|
+
try {
|
|
175
|
+
const ctx = document.createElement('canvas').getContext('webgl');
|
|
176
|
+
|
|
177
|
+
const result = {
|
|
178
|
+
VERSION: ctx.getParameter(ctx.VERSION),
|
|
179
|
+
SHADING_LANGUAGE_VERSION: ctx.getParameter(ctx.SHADING_LANGUAGE_VERSION),
|
|
180
|
+
VENDOR: ctx.getParameter(ctx.VENDOR),
|
|
181
|
+
SUPORTED_EXTENSIONS: ctx.getSupportedExtensions(),
|
|
182
|
+
};
|
|
183
|
+
|
|
120
184
|
return result;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const murmurhash3_32_gc = (key) => {
|
|
191
|
+
const remainder = key.length & 3; // key.length % 4
|
|
192
|
+
const bytes = key.length - remainder;
|
|
193
|
+
const c1 = 0xcc9e2d51;
|
|
194
|
+
const c2 = 0x1b873593;
|
|
195
|
+
|
|
196
|
+
let h1, h1b, k1;
|
|
197
|
+
|
|
198
|
+
for (let i = 0; i < bytes; i++) {
|
|
199
|
+
k1 = (key.charCodeAt(i) & 0xff) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24);
|
|
200
|
+
++i;
|
|
201
|
+
|
|
202
|
+
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
203
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
204
|
+
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
205
|
+
|
|
206
|
+
h1 ^= k1;
|
|
207
|
+
h1 = (h1 << 13) | (h1 >>> 19);
|
|
208
|
+
h1b = ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
|
|
209
|
+
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const i = bytes - 1;
|
|
213
|
+
|
|
214
|
+
k1 = 0;
|
|
215
|
+
|
|
216
|
+
switch (remainder) {
|
|
217
|
+
case 3: {
|
|
218
|
+
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 2: {
|
|
222
|
+
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case 1: {
|
|
226
|
+
k1 ^= key.charCodeAt(i) & 0xff;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
232
|
+
k1 = (k1 << 15) | (k1 >>> 17);
|
|
233
|
+
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
234
|
+
h1 ^= k1;
|
|
235
|
+
|
|
236
|
+
h1 ^= key.length;
|
|
237
|
+
|
|
238
|
+
h1 ^= h1 >>> 16;
|
|
239
|
+
h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
|
240
|
+
h1 ^= h1 >>> 13;
|
|
241
|
+
h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff;
|
|
242
|
+
h1 ^= h1 >>> 16;
|
|
243
|
+
|
|
244
|
+
return h1 >>> 0;
|
|
121
245
|
};
|
|
246
|
+
|
|
247
|
+
window.getBrowserFingerprint = getBrowserFingerprint;
|
|
248
|
+
export default getBrowserFingerprint;
|
package/src/index.spec.js
CHANGED
|
@@ -1,25 +1,52 @@
|
|
|
1
|
-
import { strict as assert } from 'assert';
|
|
2
1
|
import puppeteer from 'puppeteer';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
2
|
+
|
|
3
|
+
describe('getBrowserFingerprint', () => {
|
|
4
|
+
let browser, page;
|
|
5
|
+
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
browser = await puppeteer.launch({
|
|
8
|
+
// headless: false,
|
|
9
|
+
// devtools: true,
|
|
10
|
+
});
|
|
11
|
+
page = await browser.newPage();
|
|
12
|
+
|
|
13
|
+
await page.addScriptTag({
|
|
14
|
+
type: 'module',
|
|
15
|
+
path: './src/index.js',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(async () => {
|
|
20
|
+
await browser.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('works without args', async () => {
|
|
24
|
+
const result = await page.evaluate(() => {
|
|
25
|
+
const result = window.getBrowserFingerprint();
|
|
26
|
+
return result;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(typeof result).toBe('number');
|
|
30
|
+
expect(String(result).length).toBeGreaterThanOrEqual(7);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('works without hardwareOnly=true', async () => {
|
|
34
|
+
const result = await page.evaluate(() => {
|
|
35
|
+
const result = window.getBrowserFingerprint();
|
|
36
|
+
return result;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(typeof result).toBe('number');
|
|
40
|
+
expect(String(result).length).toBeGreaterThanOrEqual(7);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('works with enableWebgl=true', async () => {
|
|
44
|
+
const result = await page.evaluate(() => {
|
|
45
|
+
const result = window.getBrowserFingerprint({ enableWebgl: true });
|
|
46
|
+
return result;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(typeof result).toBe('number');
|
|
50
|
+
expect(String(result).length).toBeGreaterThanOrEqual(7);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { strict as assert } from 'assert';
|
|
2
|
+
import puppeteer from 'puppeteer';
|
|
3
|
+
|
|
4
|
+
const browser = await puppeteer.launch();
|
|
5
|
+
const page = await browser.newPage();
|
|
6
|
+
|
|
7
|
+
await page.addScriptTag({
|
|
8
|
+
type: 'module',
|
|
9
|
+
path: './src/index.js',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
await (async () => {
|
|
13
|
+
const result = await page.evaluate(() => {
|
|
14
|
+
const result = getBrowserFingerprint();
|
|
15
|
+
return result;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
assert.deepStrictEqual(Number.isInteger(result), true, 'fingerprint is not an integer');
|
|
19
|
+
assert.deepStrictEqual(String(result).length > 7, true, 'fingerprint is not long enough');
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
await (async () => {
|
|
23
|
+
const browser = await puppeteer.launch();
|
|
24
|
+
const page = await browser.newPage();
|
|
25
|
+
const result = await page.evaluate(() => {
|
|
26
|
+
const result = getBrowserFingerprint({ enableWebgl: true });
|
|
27
|
+
return result;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
assert.deepStrictEqual(Number.isInteger(result), true, 'fingerprint is not an integer');
|
|
31
|
+
assert.deepStrictEqual(String(result).length > 7, true, 'fingerprint is not long enough');
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
await browser.close();
|