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 CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
- "parserOptions": {
3
- "ecmaVersion": 2020,
4
- "sourceType": "module"
5
- },
6
- "env": {
7
- "es6": true,
8
- "browser": true,
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
- v14.5
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
- ```javascript
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
- - `enableWebgl`: enable webgl renderer, 5x times slower but deadly powerful (default `false`)
16
- - `debug`: log data used to generate fingerprint to console (default `false`)
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
- ## Disclaimer
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
- Be careful:
21
- - strongest discriminating factor is canvas token which can't be computed on old devices (eg: iPhone 6)
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
@@ -0,0 +1,7 @@
1
+ export default {
2
+ testEnvironment: 'node',
3
+ clearMocks: true,
4
+ collectCoverage: true,
5
+ coverageDirectory: 'coverage',
6
+ coverageProvider: 'v8',
7
+ };
package/package.json CHANGED
@@ -1,24 +1,19 @@
1
1
  {
2
- "name": "get-browser-fingerprint",
3
- "version": "2.0.1",
4
- "author": "Damiano Barbati <damiano.barbati@gmail.com> (http://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",
11
- "prettier": "prettier --ignore-unknown",
12
- "test": "node src/index.spec.js || echo 'test failed'"
13
- },
14
- "devDependencies": {
15
- "@babel/eslint-parser": "^7.13.14",
16
- "eslint": "^7.26.0",
17
- "eslint-config-prettier": "^8.1.0",
18
- "eslint-plugin-editorconfig": "^3.0.2",
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
- <body>
4
- <h1></h1>
5
- <!-- yarn http-server src -o -c-1 -->
6
- <script type="module">
7
- import getFingerprint from './index.js';
8
-
9
- const t0 = performance.now();
10
- const fingerprint = getFingerprint({ debug: true });
11
- const t1 = performance.now();
12
-
13
- const result = `Fingerprint: ${fingerprint} (in ${(t1 - t0).toFixed(0)} ms)`;
14
- console.log(result);
15
- document.querySelector('h1').innerText = result;
16
- </script>
17
- </body>
18
- </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,237 +1,248 @@
1
- export default ({ enableWebgl = false, debug = false } = {}) => {
2
- let { devicePixelRatio } = window;
3
- // weird behaviour when getting value from localhost vs ip!!!
4
- devicePixelRatio = +parseInt(devicePixelRatio);
5
-
6
- const {
7
- appName,
8
- appCodeName,
9
- appVersion,
10
- cookieEnabled,
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
- doNotTrack,
37
+ devicePixelRatio,
13
38
  hardwareConcurrency,
14
- language,
15
- languages,
39
+ height,
16
40
  maxTouchPoints,
41
+ pixelDepth,
17
42
  platform,
18
- product,
19
- productSub,
20
- userAgent,
21
- vendor,
22
- vendorSub,
23
- webdriver,
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
- mimeTypes,
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
- const datastring = JSON.stringify(data, null, 4);
78
+ const datastring = JSON.stringify(data, null, 4);
72
79
 
73
- if (debug) console.log('fingerprint data', datastring);
80
+ if (debug) console.log('fingerprint data', datastring);
74
81
 
75
- const result = murmurhash3_32_gc(datastring);
76
- return result;
82
+ const result = murmurhash3_32_gc(datastring);
83
+ return result;
77
84
  };
78
85
 
79
86
  export const getCanvasID = (debug) => {
80
- try {
81
- const canvas = document.createElement('canvas');
82
- const ctx = canvas.getContext('2d');
83
- const text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}|;:',<.>/?";
84
- ctx.textBaseline = 'top';
85
- ctx.font = "14px 'Arial'";
86
- ctx.textBaseline = 'alphabetic';
87
- ctx.fillStyle = '#f60';
88
- ctx.fillRect(125, 1, 62, 20);
89
- ctx.fillStyle = '#069';
90
- ctx.fillText(text, 2, 15);
91
- ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
92
- ctx.fillText(text, 4, 17);
93
-
94
- const result = canvas.toDataURL();
95
-
96
- if (debug) {
97
- document.body.appendChild(canvas);
98
- } else {
99
- ctx.clearRect(0, 0, canvas.width, canvas.height);
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
- try {
110
- const canvas = document.createElement('canvas');
111
- const ctx = canvas.getContext('webgl');
112
- canvas.width = 256;
113
- canvas.height = 128;
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
- ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
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
- const i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
127
+ ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
122
128
 
123
- ctx.bufferData(ctx.ARRAY_BUFFER, i, ctx.STATIC_DRAW), (h.itemSize = 3), (h.numItems = 3);
129
+ const i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
124
130
 
125
- const j = ctx.createProgram();
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
- ctx.shaderSource(k, f);
129
- ctx.compileShader(k);
133
+ const j = ctx.createProgram();
134
+ const k = ctx.createShader(ctx.VERTEX_SHADER);
130
135
 
131
- const l = ctx.createShader(ctx.FRAGMENT_SHADER);
136
+ ctx.shaderSource(k, f);
137
+ ctx.compileShader(k);
132
138
 
133
- ctx.shaderSource(l, g);
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
- j.vertexPosAttrib = ctx.getAttribLocation(j, 'attrVertex');
141
- j.offsetUniform = ctx.getUniformLocation(j, 'uniformOffset');
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
- ctx.enableVertexAttribArray(j.vertexPosArray);
144
- ctx.vertexAttribPointer(j.vertexPosAttrib, h.itemSize, ctx.FLOAT, !1, 0, 0);
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
- const n = new Uint8Array(canvas.width * canvas.height * 4);
149
- ctx.readPixels(0, 0, canvas.width, canvas.height, ctx.RGBA, ctx.UNSIGNED_BYTE, n);
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
- const result = JSON.stringify(n).replace(/,?"[0-9]+":/g, '');
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
- if (debug) {
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
- return murmurhash3_32_gc(result);
160
- } catch {
161
- return null;
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
- try {
167
- const ctx = document.createElement('canvas').getContext('webgl');
168
-
169
- const result = {
170
- VERSION: ctx.getParameter(ctx.VERSION),
171
- SHADING_LANGUAGE_VERSION: ctx.getParameter(ctx.SHADING_LANGUAGE_VERSION),
172
- VENDOR: ctx.getParameter(ctx.VENDOR),
173
- SUPORTED_EXTENSIONS: ctx.getSupportedExtensions(),
174
- };
175
-
176
- return result;
177
- } catch {
178
- return null;
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
- const remainder = key.length & 3; // key.length % 4
184
- const bytes = key.length - remainder;
185
- const c1 = 0xcc9e2d51;
186
- const c2 = 0x1b873593;
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
- let h1, h1b, k1;
196
+ let h1, h1b, k1;
189
197
 
190
- for (let i = 0; i < bytes; i++) {
191
- k1 = (key.charCodeAt(i) & 0xff) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24);
192
- ++i;
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
- h1 ^= key.length;
212
+ const i = bytes - 1;
229
213
 
230
- h1 ^= h1 >>> 16;
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
- return h1 >>> 0;
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
- import getBrowserFingerprint from './index.js';
4
-
5
- (async () => {
6
- await (async () => {
7
- const browser = await puppeteer.launch();
8
- const page = await browser.newPage();
9
- const result = await page.evaluate(getBrowserFingerprint);
10
- await browser.close();
11
-
12
- assert.deepStrictEqual(Number.isInteger(result), true, 'fingerprint is not an integer');
13
- assert.deepStrictEqual(String(result).length > 7, true, 'fingerprint is not long enough');
14
- })();
15
-
16
- await (async () => {
17
- const browser = await puppeteer.launch();
18
- const page = await browser.newPage();
19
- const result = await page.evaluate(getBrowserFingerprint, true);
20
- await browser.close();
21
-
22
- assert.deepStrictEqual(Number.isInteger(result), true, 'fingerprint is not an integer');
23
- assert.deepStrictEqual(String(result).length > 7, true, 'fingerprint is not long enough');
24
- })();
25
- })()
26
- .then(console.log)
27
- .catch(console.error)
28
- .finally(process.exit);
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
@@ -1,6 +0,0 @@
1
- *.md
2
- .idea/
3
- node_modules/
4
- coverage/
5
- .cache/
6
- .DS_Store
package/.prettierrc DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "printWidth": 200,
3
- "semi": true,
4
- "singleQuote": true
5
- }