get-browser-fingerprint 2.0.1 → 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/.eslintrc +7 -11
- 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 +19 -7
- package/jest.config.js +7 -0
- package/package.json +17 -22
- package/src/index.html +45 -16
- package/src/index.js +186 -175
- package/src/index.spec.js +51 -27
- package/src/index.spec.js.bak +34 -0
- package/.editorconfig +0 -11
- package/.prettierignore +0 -6
- package/.prettierrc +0 -5
package/.eslintrc
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"node": true
|
|
10
|
-
},
|
|
11
|
-
"extends": ["eslint:recommended"]
|
|
12
|
-
}
|
|
2
|
+
"extends": [
|
|
3
|
+
"eslint-config-xs/react"
|
|
4
|
+
],
|
|
5
|
+
"rules": {
|
|
6
|
+
"jest/no-done-callback": "off"
|
|
7
|
+
}
|
|
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,21 +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
14
|
Options available:
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
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`)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
⚠️ Be careful: the strongest discriminating factor is canvas token which can't be computed on old devices (eg: iPhone 6), deal accordingly ⚠️
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
## Development
|
|
22
|
+
|
|
23
|
+
To test locally:
|
|
24
|
+
```sh
|
|
25
|
+
nvm install
|
|
26
|
+
yarn install
|
|
27
|
+
yarn test
|
|
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,24 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"eslint-plugin-prettier": "^3.3.1",
|
|
20
|
-
"http-server": "^0.12.3",
|
|
21
|
-
"prettier": "^2.2.1",
|
|
22
|
-
"puppeteer": "^9.1.1"
|
|
23
|
-
}
|
|
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
|
+
}
|
|
24
19
|
}
|
package/src/index.html
CHANGED
|
@@ -1,18 +1,47 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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,237 +1,248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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,
|
|
11
36
|
deviceMemory,
|
|
12
|
-
|
|
37
|
+
devicePixelRatio,
|
|
13
38
|
hardwareConcurrency,
|
|
14
|
-
|
|
15
|
-
languages,
|
|
39
|
+
height,
|
|
16
40
|
maxTouchPoints,
|
|
41
|
+
pixelDepth,
|
|
17
42
|
platform,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} = window.navigator;
|
|
25
|
-
|
|
26
|
-
const plugins = Object.entries(window.navigator.plugins).map(([, plugin]) => plugin.name);
|
|
27
|
-
const mimeTypes = Object.entries(window.navigator.mimeTypes).map(([, mimeType]) => mimeType.type);
|
|
28
|
-
|
|
29
|
-
const { width, height, colorDepth, pixelDepth } = window.screen;
|
|
30
|
-
const timezoneOffset = new Date().getTimezoneOffset();
|
|
31
|
-
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
32
|
-
const touchSupport = 'ontouchstart' in window;
|
|
33
|
-
|
|
34
|
-
const canvas = getCanvasID(debug);
|
|
35
|
-
const webgl = enableWebgl ? getWebglID(debug) : null;
|
|
36
|
-
const webglInfo = getWebglInfo(debug);
|
|
37
|
-
|
|
38
|
-
const data = {
|
|
39
|
-
devicePixelRatio,
|
|
40
|
-
appName,
|
|
43
|
+
touchSupport,
|
|
44
|
+
webgl,
|
|
45
|
+
webglInfo,
|
|
46
|
+
width,
|
|
47
|
+
})
|
|
48
|
+
: JSON.stringify({
|
|
41
49
|
appCodeName,
|
|
50
|
+
appName,
|
|
42
51
|
appVersion,
|
|
52
|
+
canvas,
|
|
53
|
+
colorDepth,
|
|
43
54
|
cookieEnabled,
|
|
44
55
|
deviceMemory,
|
|
56
|
+
devicePixelRatio,
|
|
45
57
|
doNotTrack,
|
|
46
58
|
hardwareConcurrency,
|
|
59
|
+
height,
|
|
47
60
|
language,
|
|
48
61
|
languages,
|
|
49
62
|
maxTouchPoints,
|
|
50
|
-
|
|
63
|
+
pixelDepth,
|
|
51
64
|
platform,
|
|
52
|
-
plugins,
|
|
53
65
|
product,
|
|
54
66
|
productSub,
|
|
67
|
+
timezone,
|
|
68
|
+
timezoneOffset,
|
|
69
|
+
touchSupport,
|
|
55
70
|
userAgent,
|
|
56
71
|
vendor,
|
|
57
72
|
vendorSub,
|
|
58
|
-
webdriver,
|
|
59
|
-
width,
|
|
60
|
-
height,
|
|
61
|
-
colorDepth,
|
|
62
|
-
pixelDepth,
|
|
63
|
-
timezoneOffset,
|
|
64
|
-
timezone,
|
|
65
|
-
touchSupport,
|
|
66
|
-
canvas,
|
|
67
73
|
webgl,
|
|
68
74
|
webglInfo,
|
|
69
|
-
|
|
75
|
+
width,
|
|
76
|
+
});
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
const datastring = JSON.stringify(data, null, 4);
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
if (debug) console.log('fingerprint data', datastring);
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
const result = murmurhash3_32_gc(datastring);
|
|
83
|
+
return result;
|
|
77
84
|
};
|
|
78
85
|
|
|
79
86
|
export const getCanvasID = (debug) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return murmurhash3_32_gc(result);
|
|
103
|
-
} catch {
|
|
104
|
-
return null;
|
|
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);
|
|
105
107
|
}
|
|
108
|
+
|
|
109
|
+
return murmurhash3_32_gc(result);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
106
113
|
};
|
|
107
114
|
|
|
108
115
|
export const getWebglID = (debug) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const f = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}';
|
|
116
|
-
const g = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}';
|
|
117
|
-
const h = ctx.createBuffer();
|
|
116
|
+
try {
|
|
117
|
+
const canvas = document.createElement('canvas');
|
|
118
|
+
const ctx = canvas.getContext('webgl');
|
|
119
|
+
canvas.width = 256;
|
|
120
|
+
canvas.height = 128;
|
|
118
121
|
|
|
119
|
-
|
|
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();
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
|
|
122
128
|
|
|
123
|
-
|
|
129
|
+
const i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
const k = ctx.createShader(ctx.VERTEX_SHADER);
|
|
131
|
+
ctx.bufferData(ctx.ARRAY_BUFFER, i, ctx.STATIC_DRAW), (h.itemSize = 3), (h.numItems = 3);
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
const j = ctx.createProgram();
|
|
134
|
+
const k = ctx.createShader(ctx.VERTEX_SHADER);
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
ctx.shaderSource(k, f);
|
|
137
|
+
ctx.compileShader(k);
|
|
132
138
|
|
|
133
|
-
|
|
134
|
-
ctx.compileShader(l);
|
|
135
|
-
ctx.attachShader(j, k);
|
|
136
|
-
ctx.attachShader(j, l);
|
|
137
|
-
ctx.linkProgram(j);
|
|
138
|
-
ctx.useProgram(j);
|
|
139
|
+
const l = ctx.createShader(ctx.FRAGMENT_SHADER);
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
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);
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
ctx.uniform2f(j.offsetUniform, 1, 1);
|
|
146
|
-
ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, h.numItems);
|
|
148
|
+
j.vertexPosAttrib = ctx.getAttribLocation(j, 'attrVertex');
|
|
149
|
+
j.offsetUniform = ctx.getUniformLocation(j, 'uniformOffset');
|
|
147
150
|
|
|
148
|
-
|
|
149
|
-
|
|
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);
|
|
150
155
|
|
|
151
|
-
|
|
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);
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
document.body.appendChild(canvas);
|
|
155
|
-
} else {
|
|
156
|
-
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
|
|
157
|
-
}
|
|
159
|
+
const result = JSON.stringify(n).replace(/,?"[0-9]+":/g, '');
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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);
|
|
162
165
|
}
|
|
166
|
+
|
|
167
|
+
return murmurhash3_32_gc(result);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
163
171
|
};
|
|
164
172
|
|
|
165
173
|
export const getWebglInfo = () => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
|
|
184
|
+
return result;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
export const murmurhash3_32_gc = (key) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
const remainder = key.length & 3; // key.length % 4
|
|
192
|
+
const bytes = key.length - remainder;
|
|
193
|
+
const c1 = 0xcc9e2d51;
|
|
194
|
+
const c2 = 0x1b873593;
|
|
187
195
|
|
|
188
|
-
|
|
196
|
+
let h1, h1b, k1;
|
|
189
197
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
195
|
-
k1 = (k1 << 15) | (k1 >>> 17);
|
|
196
|
-
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
197
|
-
|
|
198
|
-
h1 ^= k1;
|
|
199
|
-
h1 = (h1 << 13) | (h1 >>> 19);
|
|
200
|
-
h1b = ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
|
|
201
|
-
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const i = bytes - 1;
|
|
205
|
-
|
|
206
|
-
k1 = 0;
|
|
207
|
-
|
|
208
|
-
switch (remainder) {
|
|
209
|
-
case 3: {
|
|
210
|
-
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case 2: {
|
|
214
|
-
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
case 1: {
|
|
218
|
-
k1 ^= key.charCodeAt(i) & 0xff;
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
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;
|
|
222
201
|
|
|
223
202
|
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
224
203
|
k1 = (k1 << 15) | (k1 >>> 17);
|
|
225
204
|
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
205
|
+
|
|
226
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
|
+
}
|
|
227
211
|
|
|
228
|
-
|
|
212
|
+
const i = bytes - 1;
|
|
229
213
|
|
|
230
|
-
|
|
231
|
-
h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
|
232
|
-
h1 ^= h1 >>> 13;
|
|
233
|
-
h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff;
|
|
234
|
-
h1 ^= h1 >>> 16;
|
|
214
|
+
k1 = 0;
|
|
235
215
|
|
|
236
|
-
|
|
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;
|
|
237
245
|
};
|
|
246
|
+
|
|
247
|
+
window.getBrowserFingerprint = getBrowserFingerprint;
|
|
248
|
+
export default getBrowserFingerprint;
|
package/src/index.spec.js
CHANGED
|
@@ -1,28 +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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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();
|
package/.editorconfig
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
[*]
|
|
2
|
-
charset = utf-8
|
|
3
|
-
end_of_line = lf
|
|
4
|
-
indent_style = space
|
|
5
|
-
indent_size = 4
|
|
6
|
-
insert_final_newline = true
|
|
7
|
-
trim_trailing_whitespace = true
|
|
8
|
-
|
|
9
|
-
# don't trim spaces in markdown files because 2 spaces = new line
|
|
10
|
-
[*.md]
|
|
11
|
-
trim_trailing_whitespace = false
|
package/.prettierignore
DELETED