@versini/ui-fingerprint 1.1.4 → 1.3.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +25 -77
  2. package/dist/index.js +625 -336
  3. package/package.json +9 -9
package/dist/index.d.ts CHANGED
@@ -1,77 +1,25 @@
1
- type BrowserFP = { browser: string };
2
-
3
- type VideoCardFP = {
4
- vendor: string;
5
- vendorUnmasked: string;
6
- renderer: string;
7
- rendererUnmasked: string;
8
- version: string;
9
- shadingLanguageVersion: string;
10
- };
11
-
12
- type HardwareFP = {
13
- hardware: {
14
- videocard: VideoCardFP;
15
- architecture: number;
16
- deviceMemory: string;
17
- jsHeapSizeLimit: number;
18
- };
19
- };
20
-
21
- type CanvasFP = {
22
- canvas: {
23
- data: string;
24
- };
25
- };
26
-
27
- type AudioFP = {
28
- audio: {
29
- sampleHash: string;
30
- oscillator: string;
31
- maxChannels: number;
32
- channelCountMode: string;
33
- };
34
- };
35
-
36
- type SystemFP = {
37
- system: {
38
- platform: string;
39
- cookieEnabled: boolean;
40
- productSub: string;
41
- product: string;
42
- };
43
- };
44
-
45
- type LocalesFP = {
46
- locales: {
47
- languages: string;
48
- timezone: string;
49
- };
50
- };
51
-
52
- type ScreenFP = {
53
- screen: {
54
- colorDepth: number;
55
- pixelDepth: number;
56
- isTouchScreen: boolean;
57
- maxTouchPoints: number;
58
- mediaMatches: string[];
59
- };
60
- };
61
-
62
- type FontsFP = string[];
63
-
64
- type FingerprintData = [
65
- AudioFP,
66
- BrowserFP,
67
- CanvasFP,
68
- FontsFP,
69
- HardwareFP,
70
- LocalesFP,
71
- ScreenFP,
72
- SystemFP
73
- ];
74
- declare const getFingerprintData: (debug?: boolean) => Promise<FingerprintData>;
75
- declare const getFingerprintHash: (debug?: boolean) => Promise<string>;
76
-
77
- export { getFingerprintData, getFingerprintHash };
1
+ import type { AudioFP } from '../common/types';
2
+ import type { BrowserFP } from '../common/types';
3
+ import type { CanvasFP } from '../common/types';
4
+ import type { FontsFP } from '../common/types';
5
+ import type { HardwareFP } from '../common/types';
6
+ import type { LocalesFP } from '../common/types';
7
+ import type { ScreenFP } from '../common/types';
8
+ import type { SystemFP } from '../common/types';
9
+
10
+ declare type FingerprintData = [
11
+ AudioFP,
12
+ BrowserFP,
13
+ CanvasFP,
14
+ FontsFP,
15
+ HardwareFP,
16
+ LocalesFP,
17
+ ScreenFP,
18
+ SystemFP
19
+ ];
20
+
21
+ export declare const getFingerprintData: (debug?: boolean) => Promise<FingerprintData>;
22
+
23
+ export declare const getFingerprintHash: (debug?: boolean) => Promise<string>;
24
+
25
+ export { }
package/dist/index.js CHANGED
@@ -1,359 +1,648 @@
1
1
  /*!
2
- @versini/ui-fingerprint v1.1.4
2
+ @versini/ui-fingerprint v1.3.0
3
3
  © 2025 gizmette.com
4
4
  */
5
- try {
6
- window.__VERSINI_UI_FINGERPRINT__ || (window.__VERSINI_UI_FINGERPRINT__ = {
7
- version: "1.1.4",
8
- buildTime: "08/23/2025 12:24 PM EDT",
9
- homepage: "https://github.com/aversini/ui-components",
10
- license: "MIT"
11
- });
12
- } catch {
13
- }
14
- const D = (t) => Array.from(t).map((e) => e.toString(16).padStart(2, "0")).join(""), w = async (t) => {
15
- if (t === "")
16
- return "";
17
- const e = new TextEncoder().encode(t), o = await crypto.subtle.digest("SHA-256", e);
18
- return Array.from(new Uint8Array(o)).map((s) => s.toString(16).padStart(2, "0")).join("");
5
+
6
+
7
+ ;// CONCATENATED MODULE: ./src/common/utilities.ts
8
+ const hashFromFloat32Array = (array)=>{
9
+ const hashArray = Array.from(array).map((b)=>b.toString(16).padStart(2, "0")).join("");
10
+ return hashArray;
19
11
  };
20
- function y(t, n) {
21
- return new Promise((e) => setTimeout(e, t, n));
22
- }
23
- async function T(t, n, e = 50) {
24
- const o = document;
25
- for (; !o.body; )
26
- await y(e);
27
- const r = o.createElement("iframe");
28
- try {
29
- for (await new Promise((a, s) => {
30
- let l = !1;
31
- const m = () => {
32
- l = !0, a();
33
- }, p = (f) => {
34
- l = !0, s(f);
35
- };
36
- r.onload = m, r.onerror = p;
37
- const { style: u } = r;
38
- u.setProperty("display", "block", "important"), u.position = "absolute", u.top = "0", u.left = "0", u.visibility = "hidden", r.src = "about:blank", o.body.appendChild(r);
39
- const g = () => {
40
- l || (r.contentWindow?.document?.readyState === "complete" ? m() : setTimeout(g, 10));
41
- };
42
- g();
43
- }); !r.contentWindow?.document?.body; )
44
- await y(e);
45
- return await t(r, r.contentWindow);
46
- } finally {
47
- r.parentNode?.removeChild(r);
48
- }
12
+ const hashFromString = async (input)=>{
13
+ if (input === "") {
14
+ return "";
15
+ }
16
+ const encoder = new TextEncoder();
17
+ const data = encoder.encode(input);
18
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
19
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
20
+ const hashHex = hashArray.map((b)=>b.toString(16).padStart(2, "0")).join("");
21
+ return hashHex;
22
+ };
23
+ function wait(durationMs, resolveWith) {
24
+ return new Promise((resolve)=>setTimeout(resolve, durationMs, resolveWith));
49
25
  }
50
- const x = {
51
- audio: {
52
- sampleHash: "",
53
- oscillator: "",
54
- maxChannels: 0,
55
- channelCountMode: ""
56
- }
57
- }, P = async (t) => new Promise((n) => {
58
- try {
59
- const e = new window.OfflineAudioContext(1, 5e3, 44100), o = e.createBufferSource(), r = e.createOscillator();
60
- r.frequency.value = 1e3;
61
- const a = e.createDynamicsCompressor();
62
- a.threshold.value = -50, a.knee.value = 40, a.ratio.value = 12, a.attack.value = 0, a.release.value = 0.2, r.connect(a), a.connect(e.destination), r.start(), e.startRendering(), e.oncomplete = (s) => {
63
- const l = s.renderedBuffer.getChannelData(0);
64
- r.disconnect(), a.disconnect(), n({
65
- audio: {
66
- sampleHash: D(l),
67
- oscillator: r.type,
68
- maxChannels: e.destination.maxChannelCount,
69
- channelCountMode: o.channelCountMode
26
+ /**
27
+ * Creates and keeps an invisible iframe while the given function runs.
28
+ * The given function is called when the iframe is loaded and has a body.
29
+ * The iframe allows to measure DOM sizes inside itself.
30
+ *
31
+ * Notice: passing an initial HTML code doesn't work in IE.
32
+ *
33
+ * Warning for package users:
34
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
35
+ */ async function withIframe(action, initialHtml, domPollInterval = 50) {
36
+ const d = document;
37
+ // document.body can be null while the page is loading
38
+ while(!d.body){
39
+ await wait(domPollInterval);
40
+ }
41
+ const iframe = d.createElement("iframe");
42
+ try {
43
+ await new Promise((_resolve, _reject)=>{
44
+ let isComplete = false;
45
+ const resolve = ()=>{
46
+ isComplete = true;
47
+ _resolve();
48
+ };
49
+ const reject = (error)=>{
50
+ isComplete = true;
51
+ _reject(error);
52
+ };
53
+ iframe.onload = resolve;
54
+ iframe.onerror = reject;
55
+ const { style } = iframe;
56
+ // Required for browsers to calculate the layout
57
+ style.setProperty("display", "block", "important");
58
+ style.position = "absolute";
59
+ style.top = "0";
60
+ style.left = "0";
61
+ style.visibility = "hidden";
62
+ if (initialHtml && "srcdoc" in iframe) {
63
+ iframe.srcdoc = initialHtml;
64
+ } else {
65
+ iframe.src = "about:blank";
66
+ }
67
+ d.body.appendChild(iframe);
68
+ // WebKit in WeChat doesn't fire the iframe's `onload` for some reason.
69
+ // This code checks for the loading state manually.
70
+ // See https://github.com/fingerprintjs/fingerprintjs/issues/645
71
+ const checkReadyState = ()=>{
72
+ // The ready state may never become 'complete' in Firefox despite the 'load' event being fired.
73
+ // So an infinite setTimeout loop can happen without this check.
74
+ // See https://github.com/fingerprintjs/fingerprintjs/pull/716#issuecomment-986898796
75
+ if (isComplete) {
76
+ return;
77
+ }
78
+ // Make sure iframe.contentWindow and iframe.contentWindow.document are both loaded
79
+ // The contentWindow.document can miss in JSDOM (https://github.com/jsdom/jsdom).
80
+ if (iframe.contentWindow?.document?.readyState === "complete") {
81
+ resolve();
82
+ } else {
83
+ setTimeout(checkReadyState, 10);
84
+ }
85
+ };
86
+ checkReadyState();
87
+ });
88
+ while(!iframe.contentWindow?.document?.body){
89
+ await wait(domPollInterval);
70
90
  }
71
- });
72
- };
73
- } catch (e) {
74
- t && console.error("Error creating audio fingerprint:", e), n({
75
- audio: {
91
+ return await action(iframe, iframe.contentWindow);
92
+ } finally{
93
+ iframe.parentNode?.removeChild(iframe);
94
+ }
95
+ }
96
+
97
+ ;// CONCATENATED MODULE: ./src/components/audio.ts
98
+
99
+ const emptyAudio = {
100
+ audio: {
76
101
  sampleHash: "",
77
102
  oscillator: "",
78
103
  maxChannels: 0,
79
104
  channelCountMode: ""
80
- }
105
+ }
106
+ };
107
+ const getAudio = async (debug)=>{
108
+ return new Promise((resolve)=>{
109
+ try {
110
+ const audioContext = new window.OfflineAudioContext(1, 5000, 44100);
111
+ const audioBuffer = audioContext.createBufferSource();
112
+ const oscillator = audioContext.createOscillator();
113
+ oscillator.frequency.value = 1000;
114
+ const compressor = audioContext.createDynamicsCompressor();
115
+ compressor.threshold.value = -50;
116
+ compressor.knee.value = 40;
117
+ compressor.ratio.value = 12;
118
+ compressor.attack.value = 0;
119
+ compressor.release.value = 0.2;
120
+ oscillator.connect(compressor);
121
+ compressor.connect(audioContext.destination);
122
+ oscillator.start();
123
+ audioContext.startRendering();
124
+ audioContext.oncomplete = (event)=>{
125
+ const samples = event.renderedBuffer.getChannelData(0);
126
+ oscillator.disconnect();
127
+ compressor.disconnect();
128
+ resolve({
129
+ audio: {
130
+ sampleHash: hashFromFloat32Array(samples),
131
+ oscillator: oscillator.type,
132
+ maxChannels: audioContext.destination.maxChannelCount,
133
+ channelCountMode: audioBuffer.channelCountMode
134
+ }
135
+ });
136
+ };
137
+ } catch (error) {
138
+ if (debug) {
139
+ console.error("Error creating audio fingerprint:", error);
140
+ }
141
+ resolve({
142
+ audio: {
143
+ sampleHash: "",
144
+ oscillator: "",
145
+ maxChannels: 0,
146
+ channelCountMode: ""
147
+ }
148
+ });
149
+ }
81
150
  });
82
- }
83
- }), v = { browser: "" }, H = async (t) => typeof navigator > "u" ? v : { browser: navigator.userAgent }, E = {
84
- canvas: {
85
- data: ""
86
- }
87
- }, R = async (t) => {
88
- try {
89
- const o = Array.from(
90
- { length: 3 },
91
- () => _(300, 30)
92
- ), r = N(o, 300, 30);
93
- return {
94
- canvas: {
95
- data: (await w(r.data.toString())).toString()
96
- }
97
- };
98
- } catch (o) {
99
- return t && (console.error("Error creating canvas fingerprint"), console.info(o)), E;
100
- }
101
- }, _ = (t, n) => {
102
- const e = document.createElement("canvas"), o = e.getContext("2d");
103
- if (!o)
104
- return new ImageData(1, 1);
105
- e.width = t, e.height = n;
106
- const r = o.createLinearGradient(0, 0, e.width, e.height);
107
- r.addColorStop(0, "red"), r.addColorStop(1 / 6, "orange"), r.addColorStop(2 / 6, "yellow"), r.addColorStop(3 / 6, "green"), r.addColorStop(4 / 6, "blue"), r.addColorStop(5 / 6, "indigo"), r.addColorStop(1, "violet"), o.fillStyle = r, o.fillRect(0, 0, e.width, e.height);
108
- const a = "mmMwWLliI0O&1 - Les sanglots longs des violons de l'automne blessent mon coeur d'une langueur monotone";
109
- return o.font = "26.321px Arial", o.fillStyle = "black", o.fillText(a, -5, 15), o.fillStyle = "rgba(0, 0, 255, 0.5)", o.fillText(a, -3.3, 17.7), o.beginPath(), o.moveTo(0, 0), o.lineTo(e.width * 2 / 7, e.height), o.strokeStyle = "white", o.lineWidth = 2, o.stroke(), o.getImageData(0, 0, e.width, e.height);
110
- }, F = (t) => {
111
- if (t.length === 0)
112
- return 0;
113
- const n = {};
114
- for (const o of t)
115
- n[o] = (n[o] || 0) + 1;
116
- let e = t[0];
117
- for (const o in n)
118
- n[o] > n[e] && (e = parseInt(o, 10));
119
- return e;
120
- }, N = (t, n, e) => {
121
- const o = [];
122
- for (let s = 0; s < t[0].data.length; s++) {
123
- const l = [];
124
- for (let m = 0; m < t.length; m++)
125
- l.push(t[m].data[s]);
126
- o.push(F(l));
127
- }
128
- const r = o, a = new Uint8ClampedArray(r);
129
- return new ImageData(a, n, e);
130
- }, G = [], L = "mmMwWLliI0O&1", k = "48px", h = ["monospace", "sans-serif", "serif"], S = [
131
- "sans-serif-thin",
132
- "ARNO PRO",
133
- "Agency FB",
134
- "Arabic Typesetting",
135
- "Arial Unicode MS",
136
- "AvantGarde Bk BT",
137
- "BankGothic Md BT",
138
- "Bitstream Vera Sans Mono",
139
- "Calibri",
140
- "Century",
141
- "Century Gothic",
142
- "Clarendon",
143
- "EUROSTILE",
144
- "Franklin Gothic",
145
- "GOTHAM",
146
- "Gill Sans",
147
- "Helvetica Neue",
148
- "Letter Gothic",
149
- "Menlo",
150
- "MS Outlook",
151
- "MS Reference Specialty",
152
- "MS UI Gothic",
153
- "MT Extra",
154
- "MYRIAD PRO",
155
- "Marlett",
156
- "Microsoft Uighur",
157
- "Minion Pro",
158
- "Monotype Corsiva",
159
- "PMingLiU",
160
- "Pristina",
161
- "SCRIPTINA",
162
- "SimHei",
163
- "Small Fonts",
164
- "Staccato222 BT",
165
- "TRAJAN PRO",
166
- "Univers CE 55 Medium",
167
- "ZWAdobeF"
168
- ], O = async (t) => T(async (n, { document: e }) => {
169
- const o = e.body;
170
- o.style.fontSize = k;
171
- const r = e.createElement("div");
172
- r.style.setProperty("visibility", "hidden", "important");
173
- const a = {}, s = {}, l = (i) => {
174
- const c = e.createElement("span"), { style: d } = c;
175
- return d.position = "absolute", d.top = "0", d.left = "0", d.fontFamily = i, c.textContent = L, r.appendChild(c), c;
176
- }, m = (i, c) => l(`'${i}',${c}`), p = () => h.map(l), u = () => {
177
- const i = {};
178
- for (const c of S)
179
- i[c] = h.map(
180
- (d) => m(c, d)
181
- );
182
- return i;
183
- }, g = (i) => h.some(
184
- (c, d) => i[d].offsetWidth !== a[c] || i[d].offsetHeight !== s[c]
185
- ), f = p(), I = u();
186
- o.appendChild(r);
187
- for (let i = 0; i < h.length; i++)
188
- a[h[i]] = f[i].offsetWidth, s[h[i]] = f[i].offsetHeight;
189
- return S.filter((i) => g(I[i]));
190
- }), C = {
191
- vendor: "",
192
- vendorUnmasked: "",
193
- renderer: "",
194
- rendererUnmasked: "",
195
- version: "",
196
- shadingLanguageVersion: ""
197
- }, M = {
198
- hardware: {
199
- videocard: C,
200
- architecture: 0,
201
- deviceMemory: "undefined",
202
- jsHeapSizeLimit: 0
203
- }
204
151
  };
205
- function U() {
206
- const t = document.createElement("canvas"), n = t.getContext("webgl") ?? t.getContext("experimental-webgl");
207
- if (n && "getParameter" in n) {
208
- const e = n.getExtension("WEBGL_debug_renderer_info");
209
- return {
210
- vendor: (n.getParameter(n.VENDOR) || "").toString(),
211
- vendorUnmasked: e ? (n.getParameter(e.UNMASKED_VENDOR_WEBGL) || "").toString() : "",
212
- renderer: (n.getParameter(n.RENDERER) || "").toString(),
213
- rendererUnmasked: e ? (n.getParameter(e.UNMASKED_RENDERER_WEBGL) || "").toString() : "",
214
- version: (n.getParameter(n.VERSION) || "").toString(),
215
- shadingLanguageVersion: (n.getParameter(n.SHADING_LANGUAGE_VERSION) || "").toString()
152
+
153
+ ;// CONCATENATED MODULE: ./src/components/browser.ts
154
+ const emptyBrowser = {
155
+ browser: ""
156
+ };
157
+ const getBrowser = async (_debug)=>{
158
+ return typeof navigator === "undefined" ? emptyBrowser : {
159
+ browser: navigator.userAgent
216
160
  };
217
- }
218
- return C;
161
+ };
162
+
163
+ ;// CONCATENATED MODULE: ./src/components/canvas.ts
164
+
165
+ const emptyCanvas = {
166
+ canvas: {
167
+ data: ""
168
+ }
169
+ };
170
+ const getCanvas = async (debug)=>{
171
+ const WIDTH = 300;
172
+ const HEIGHT = 30;
173
+ try {
174
+ /**
175
+ * Creating 3 identical image data with same text and same colors,
176
+ * and extracting the common pixels from them. This is a fingerprint
177
+ * busting technique to defeat canvas counter-fingerprinting.
178
+ */ const imageData = Array.from({
179
+ length: 3
180
+ }, ()=>generateImageData(WIDTH, HEIGHT));
181
+ const commonImageData = getCommonPixels(imageData, WIDTH, HEIGHT);
182
+ const hashedData = (await hashFromString(commonImageData.data.toString())).toString();
183
+ return {
184
+ canvas: {
185
+ data: hashedData
186
+ }
187
+ };
188
+ } catch (error) {
189
+ if (debug) {
190
+ console.error("Error creating canvas fingerprint");
191
+ console.info(error);
192
+ }
193
+ return emptyCanvas;
194
+ }
195
+ };
196
+ const generateImageData = (width, height)=>{
197
+ const canvas = document.createElement("canvas");
198
+ const ctx = canvas.getContext("2d");
199
+ if (!ctx) {
200
+ return new ImageData(1, 1);
201
+ }
202
+ canvas.width = width;
203
+ canvas.height = height;
204
+ // Create rainbow gradient for the background rectangle
205
+ const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
206
+ gradient.addColorStop(0, "red");
207
+ gradient.addColorStop(1 / 6, "orange");
208
+ gradient.addColorStop(2 / 6, "yellow");
209
+ gradient.addColorStop(3 / 6, "green");
210
+ gradient.addColorStop(4 / 6, "blue");
211
+ gradient.addColorStop(5 / 6, "indigo");
212
+ gradient.addColorStop(1, "violet");
213
+ // Draw background rectangle with the rainbow gradient
214
+ ctx.fillStyle = gradient;
215
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
216
+ // Draw some random text
217
+ // We use m or w because these two characters take up the maximum width.
218
+ // And we use a LLi so that the same matching fonts can get separated.
219
+ const randomText = "mmMwWLliI0O&1 - Les sanglots longs des violons de l'automne blessent mon coeur d'une langueur monotone";
220
+ ctx.font = "26.321px Arial";
221
+ ctx.fillStyle = "black";
222
+ ctx.fillText(randomText, -5, 15);
223
+ // Draw the same text with an offset, different color, and slight transparency
224
+ ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
225
+ ctx.fillText(randomText, -3.3, 17.7);
226
+ // Draw a line crossing the image at an arbitrary angle
227
+ ctx.beginPath();
228
+ ctx.moveTo(0, 0);
229
+ ctx.lineTo(canvas.width * 2 / 7, canvas.height);
230
+ ctx.strokeStyle = "white";
231
+ ctx.lineWidth = 2;
232
+ ctx.stroke();
233
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
234
+ return imageData;
235
+ };
236
+ const getMostFrequent = (arr)=>{
237
+ if (arr.length === 0) {
238
+ return 0;
239
+ }
240
+ const frequencyMap = {};
241
+ for (const num of arr){
242
+ frequencyMap[num] = (frequencyMap[num] || 0) + 1;
243
+ }
244
+ let mostFrequent = arr[0];
245
+ // Find the number with the highest frequency
246
+ for(const num in frequencyMap){
247
+ if (frequencyMap[num] > frequencyMap[mostFrequent]) {
248
+ mostFrequent = parseInt(num, 10);
249
+ }
250
+ }
251
+ return mostFrequent;
252
+ };
253
+ const getCommonPixels = (images, width, height)=>{
254
+ const finalData = [];
255
+ for(let i = 0; i < images[0].data.length; i++){
256
+ const indice = [];
257
+ for(let u = 0; u < images.length; u++){
258
+ indice.push(images[u].data[i]);
259
+ }
260
+ finalData.push(getMostFrequent(indice));
261
+ }
262
+ const pixelData = finalData;
263
+ const pixelArray = new Uint8ClampedArray(pixelData);
264
+ return new ImageData(pixelArray, width, height);
265
+ };
266
+
267
+ ;// CONCATENATED MODULE: ./src/components/fonts.ts
268
+
269
+ const emptyFonts = [];
270
+ // We use m or w because these two characters take up the maximum width.
271
+ // And we use a LLi so that the same matching fonts can get separated.
272
+ const testString = "mmMwWLliI0O&1";
273
+ // We test using 48px font size, we may use any size. I guess larger the better.
274
+ const textSize = "48px";
275
+ // A font will be compared against all the three default fonts.
276
+ // And if for any default fonts it doesn't match, then that font is available.
277
+ const baseFonts = [
278
+ "monospace",
279
+ "sans-serif",
280
+ "serif"
281
+ ];
282
+ const fontList = [
283
+ "sans-serif-thin",
284
+ "ARNO PRO",
285
+ "Agency FB",
286
+ "Arabic Typesetting",
287
+ "Arial Unicode MS",
288
+ "AvantGarde Bk BT",
289
+ "BankGothic Md BT",
290
+ "Bitstream Vera Sans Mono",
291
+ "Calibri",
292
+ "Century",
293
+ "Century Gothic",
294
+ "Clarendon",
295
+ "EUROSTILE",
296
+ "Franklin Gothic",
297
+ "GOTHAM",
298
+ "Gill Sans",
299
+ "Helvetica Neue",
300
+ "Letter Gothic",
301
+ "Menlo",
302
+ "MS Outlook",
303
+ "MS Reference Specialty",
304
+ "MS UI Gothic",
305
+ "MT Extra",
306
+ "MYRIAD PRO",
307
+ "Marlett",
308
+ "Microsoft Uighur",
309
+ "Minion Pro",
310
+ "Monotype Corsiva",
311
+ "PMingLiU",
312
+ "Pristina",
313
+ "SCRIPTINA",
314
+ "SimHei",
315
+ "Small Fonts",
316
+ "Staccato222 BT",
317
+ "TRAJAN PRO",
318
+ "Univers CE 55 Medium",
319
+ "ZWAdobeF"
320
+ ];
321
+ const getFonts = async (_debug)=>{
322
+ return withIframe(async (_, { document })=>{
323
+ const holder = document.body;
324
+ holder.style.fontSize = textSize;
325
+ // div to load spans for the default fonts and the fonts to detect
326
+ const spansContainer = document.createElement("div");
327
+ spansContainer.style.setProperty("visibility", "hidden", "important");
328
+ const defaultWidth = {};
329
+ const defaultHeight = {};
330
+ // creates a span where the fonts will be loaded
331
+ const createSpan = (fontFamily)=>{
332
+ const span = document.createElement("span");
333
+ const { style } = span;
334
+ style.position = "absolute";
335
+ style.top = "0";
336
+ style.left = "0";
337
+ style.fontFamily = fontFamily;
338
+ span.textContent = testString;
339
+ spansContainer.appendChild(span);
340
+ return span;
341
+ };
342
+ // creates a span and load the font to detect and a base font for fallback
343
+ const createSpanWithFonts = (fontToDetect, baseFont)=>{
344
+ return createSpan(`'${fontToDetect}',${baseFont}`);
345
+ };
346
+ // creates spans for the base fonts and adds them to baseFontsDiv
347
+ const initializeBaseFontsSpans = ()=>{
348
+ return baseFonts.map(createSpan);
349
+ };
350
+ // creates spans for the fonts to detect and adds them to fontsDiv
351
+ const initializeFontsSpans = ()=>{
352
+ // Stores {fontName : [spans for that font]}
353
+ const spans = {};
354
+ for (const font of fontList){
355
+ spans[font] = baseFonts.map((baseFont)=>createSpanWithFonts(font, baseFont));
356
+ }
357
+ return spans;
358
+ };
359
+ // checks if a font is available
360
+ const isFontAvailable = (fontSpans)=>{
361
+ return baseFonts.some((baseFont, baseFontIndex)=>fontSpans[baseFontIndex].offsetWidth !== defaultWidth[baseFont] || fontSpans[baseFontIndex].offsetHeight !== defaultHeight[baseFont]);
362
+ };
363
+ // create spans for base fonts
364
+ const baseFontsSpans = initializeBaseFontsSpans();
365
+ // create spans for fonts to detect
366
+ const fontsSpans = initializeFontsSpans();
367
+ // add all the spans to the DOM
368
+ holder.appendChild(spansContainer);
369
+ // get the default width for the three base fonts
370
+ for(let index = 0; index < baseFonts.length; index++){
371
+ defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth; // width for the default font
372
+ defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight; // height for the default font
373
+ }
374
+ // check available fonts
375
+ return fontList.filter((font)=>isFontAvailable(fontsSpans[font]));
376
+ });
377
+ };
378
+
379
+ ;// CONCATENATED MODULE: ./src/components/hardware.ts
380
+ const emptyVideoCard = {
381
+ vendor: "",
382
+ vendorUnmasked: "",
383
+ renderer: "",
384
+ rendererUnmasked: "",
385
+ version: "",
386
+ shadingLanguageVersion: ""
387
+ };
388
+ const emptyHardware = {
389
+ hardware: {
390
+ videocard: emptyVideoCard,
391
+ architecture: 0,
392
+ deviceMemory: "undefined",
393
+ jsHeapSizeLimit: 0
394
+ }
395
+ };
396
+ function getVideoCard() {
397
+ const canvas = document.createElement("canvas");
398
+ const gl = canvas.getContext("webgl") ?? canvas.getContext("experimental-webgl");
399
+ if (gl && "getParameter" in gl) {
400
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
401
+ return {
402
+ vendor: (gl.getParameter(gl.VENDOR) || "").toString(),
403
+ vendorUnmasked: debugInfo ? (gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "").toString() : "",
404
+ renderer: (gl.getParameter(gl.RENDERER) || "").toString(),
405
+ rendererUnmasked: debugInfo ? (gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || "").toString() : "",
406
+ version: (gl.getParameter(gl.VERSION) || "").toString(),
407
+ shadingLanguageVersion: (gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || "").toString()
408
+ };
409
+ }
410
+ return emptyVideoCard;
219
411
  }
220
- function B() {
221
- const t = new Float32Array(1), n = new Uint8Array(t.buffer);
222
- return t[0] = 1 / 0, t[0] = t[0] - t[0], n[3];
412
+ function getArchitecture() {
413
+ const f = new Float32Array(1);
414
+ const u8 = new Uint8Array(f.buffer);
415
+ f[0] = Infinity;
416
+ f[0] = f[0] - f[0];
417
+ return u8[3];
223
418
  }
224
- const W = () => navigator.deviceMemory || 0, V = () => window.performance && window.performance.memory || {
225
- jsHeapSizeLimit: 0
226
- }, z = async (t) => new Promise((n) => {
227
- try {
228
- const e = W(), o = V();
229
- n({
230
- hardware: {
231
- videocard: U(),
232
- architecture: B(),
233
- deviceMemory: e.toString() || "undefined",
234
- jsHeapSizeLimit: o.jsHeapSizeLimit || 0
235
- }
419
+ // @ts-ignore
420
+ const getDeviceMemory = ()=>navigator.deviceMemory || 0;
421
+ const getMemoryInfo = ()=>window.performance && window.performance.memory || {
422
+ jsHeapSizeLimit: 0
423
+ };
424
+ const getHardware = async (debug)=>{
425
+ return new Promise((resolve)=>{
426
+ try {
427
+ const deviceMemory = getDeviceMemory();
428
+ const memoryInfo = getMemoryInfo();
429
+ resolve({
430
+ hardware: {
431
+ videocard: getVideoCard(),
432
+ architecture: getArchitecture(),
433
+ deviceMemory: deviceMemory.toString() || "undefined",
434
+ jsHeapSizeLimit: memoryInfo.jsHeapSizeLimit || 0
435
+ }
436
+ });
437
+ } catch (error) {
438
+ if (debug) {
439
+ console.error("Error getting hardware data");
440
+ console.info(error);
441
+ }
442
+ resolve(emptyHardware);
443
+ }
236
444
  });
237
- } catch (e) {
238
- t && (console.error("Error getting hardware data"), console.info(e)), n(M);
239
- }
240
- }), j = {
241
- locales: {
242
- languages: "",
243
- timezone: ""
244
- }
245
- }, $ = async (t) => new Promise((n) => {
246
- n({
445
+ };
446
+
447
+ ;// CONCATENATED MODULE: ./src/components/locale.ts
448
+ const emptyLocales = {
247
449
  locales: {
248
- languages: navigator.language,
249
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
450
+ languages: "",
451
+ timezone: ""
452
+ }
453
+ };
454
+ const getLocales = async (_debug)=>{
455
+ return new Promise((resolve)=>{
456
+ resolve({
457
+ locales: {
458
+ languages: navigator.language,
459
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
460
+ }
461
+ });
462
+ });
463
+ };
464
+
465
+ ;// CONCATENATED MODULE: ./src/components/screen.ts
466
+ const emptyScreen = {
467
+ screen: {
468
+ colorDepth: 0,
469
+ pixelDepth: 0,
470
+ isTouchScreen: false,
471
+ maxTouchPoints: 0,
472
+ mediaMatches: []
250
473
  }
251
- });
252
- }), b = {
253
- screen: {
254
- colorDepth: 0,
255
- pixelDepth: 0,
256
- isTouchScreen: !1,
257
- maxTouchPoints: 0,
258
- mediaMatches: []
259
- }
260
- }, q = async (t) => new Promise((n) => {
261
- try {
262
- const e = window.screen, o = {
263
- screen: {
264
- colorDepth: e.colorDepth,
265
- pixelDepth: e.pixelDepth,
266
- isTouchScreen: navigator.maxTouchPoints > 0,
267
- maxTouchPoints: navigator.maxTouchPoints,
268
- mediaMatches: J()
269
- }
474
+ };
475
+ const getScreen = async (debug)=>{
476
+ return new Promise((resolve)=>{
477
+ try {
478
+ const screen = window.screen;
479
+ const screenData = {
480
+ screen: {
481
+ colorDepth: screen.colorDepth,
482
+ pixelDepth: screen.pixelDepth,
483
+ isTouchScreen: navigator.maxTouchPoints > 0,
484
+ maxTouchPoints: navigator.maxTouchPoints,
485
+ mediaMatches: mediaMatches()
486
+ }
487
+ };
488
+ resolve(screenData);
489
+ } catch (error) {
490
+ if (debug) {
491
+ console.error("Error creating screen fingerprint");
492
+ console.info(error);
493
+ }
494
+ resolve(emptyScreen);
495
+ }
496
+ });
497
+ };
498
+ function mediaMatches() {
499
+ const results = [];
500
+ const mediaQueries = {
501
+ "prefers-contrast": [
502
+ "high",
503
+ "more",
504
+ "low",
505
+ "less",
506
+ "forced",
507
+ "no-preference"
508
+ ],
509
+ "any-hover": [
510
+ "hover",
511
+ "none"
512
+ ],
513
+ "any-pointer": [
514
+ "none",
515
+ "coarse",
516
+ "fine"
517
+ ],
518
+ pointer: [
519
+ "none",
520
+ "coarse",
521
+ "fine"
522
+ ],
523
+ hover: [
524
+ "hover",
525
+ "none"
526
+ ],
527
+ update: [
528
+ "fast",
529
+ "slow"
530
+ ],
531
+ "inverted-colors": [
532
+ "inverted",
533
+ "none"
534
+ ],
535
+ "prefers-reduced-motion": [
536
+ "reduce",
537
+ "no-preference"
538
+ ],
539
+ "prefers-reduced-transparency": [
540
+ "reduce",
541
+ "no-preference"
542
+ ],
543
+ scripting: [
544
+ "none",
545
+ "initial-only",
546
+ "enabled"
547
+ ],
548
+ "forced-colors": [
549
+ "active",
550
+ "none"
551
+ ],
552
+ "color-gamut": [
553
+ "srgb",
554
+ "p3",
555
+ "rec2020"
556
+ ]
270
557
  };
271
- n(o);
272
- } catch (e) {
273
- t && (console.error("Error creating screen fingerprint"), console.info(e)), n(b);
274
- }
275
- });
276
- function J() {
277
- const t = [], n = {
278
- "prefers-contrast": [
279
- "high",
280
- "more",
281
- "low",
282
- "less",
283
- "forced",
284
- "no-preference"
285
- ],
286
- "any-hover": ["hover", "none"],
287
- "any-pointer": ["none", "coarse", "fine"],
288
- pointer: ["none", "coarse", "fine"],
289
- hover: ["hover", "none"],
290
- update: ["fast", "slow"],
291
- "inverted-colors": ["inverted", "none"],
292
- "prefers-reduced-motion": ["reduce", "no-preference"],
293
- "prefers-reduced-transparency": ["reduce", "no-preference"],
294
- scripting: ["none", "initial-only", "enabled"],
295
- "forced-colors": ["active", "none"],
296
- "color-gamut": ["srgb", "p3", "rec2020"]
297
- };
298
- return Object.keys(n).forEach((e) => {
299
- n[e].forEach((o) => {
300
- matchMedia(`(${e}: ${o})`).matches && t.push(`${e}: ${o}`);
558
+ Object.keys(mediaQueries).forEach((key)=>{
559
+ mediaQueries[key].forEach((value)=>{
560
+ if (matchMedia(`(${key}: ${value})`).matches) {
561
+ results.push(`${key}: ${value}`);
562
+ }
563
+ });
301
564
  });
302
- }), t;
565
+ return results;
303
566
  }
304
- const K = async (t) => {
305
- try {
306
- return {
307
- system: {
308
- platform: navigator.platform,
309
- cookieEnabled: navigator.cookieEnabled,
310
- productSub: navigator.productSub,
311
- product: navigator.product
312
- }
313
- };
314
- } catch (n) {
315
- return t && (console.error("Error getting system data"), console.info(n)), A;
316
- }
317
- }, A = {
318
- system: {
319
- platform: "",
320
- cookieEnabled: !1,
321
- productSub: "",
322
- product: ""
323
- }
324
- }, Z = async (t) => {
325
- try {
326
- return Promise.all([
327
- P(t),
328
- H(),
329
- R(t),
330
- O(),
331
- z(t),
332
- $(),
333
- q(t),
334
- K(t)
335
- ]);
336
- } catch {
337
- return [
338
- x,
339
- v,
340
- E,
341
- G,
342
- M,
343
- j,
344
- b,
345
- A
346
- ];
347
- }
348
- }, Q = async (t) => {
349
- try {
350
- const n = await Z(t);
351
- return await w(JSON.stringify(n));
352
- } catch (n) {
353
- return t && (console.error("Error getting fingerprint hash"), console.info(n)), "";
354
- }
567
+
568
+ ;// CONCATENATED MODULE: ./src/components/system.ts
569
+ const getSystem = async (debug)=>{
570
+ try {
571
+ return {
572
+ system: {
573
+ platform: navigator.platform,
574
+ cookieEnabled: navigator.cookieEnabled,
575
+ productSub: navigator.productSub,
576
+ product: navigator.product
577
+ }
578
+ };
579
+ } catch (error) {
580
+ if (debug) {
581
+ console.error("Error getting system data");
582
+ console.info(error);
583
+ }
584
+ return emptySystem;
585
+ }
586
+ };
587
+ const emptySystem = {
588
+ system: {
589
+ platform: "",
590
+ cookieEnabled: false,
591
+ productSub: "",
592
+ product: ""
593
+ }
355
594
  };
356
- export {
357
- Z as getFingerprintData,
358
- Q as getFingerprintHash
595
+
596
+ ;// CONCATENATED MODULE: ./src/common/fingerprint.ts
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+ const getFingerprintData = async (debug)=>{
607
+ try {
608
+ return Promise.all([
609
+ getAudio(debug),
610
+ getBrowser(debug),
611
+ getCanvas(debug),
612
+ getFonts(debug),
613
+ getHardware(debug),
614
+ getLocales(debug),
615
+ getScreen(debug),
616
+ getSystem(debug)
617
+ ]);
618
+ } catch (_error) {
619
+ return [
620
+ emptyAudio,
621
+ emptyBrowser,
622
+ emptyCanvas,
623
+ emptyFonts,
624
+ emptyHardware,
625
+ emptyLocales,
626
+ emptyScreen,
627
+ emptySystem
628
+ ];
629
+ }
630
+ };
631
+ const getFingerprintHash = async (debug)=>{
632
+ try {
633
+ const data = await getFingerprintData(debug);
634
+ return await hashFromString(JSON.stringify(data));
635
+ } catch (_error) {
636
+ if (debug) {
637
+ console.error("Error getting fingerprint hash");
638
+ console.info(_error);
639
+ }
640
+ return "";
641
+ }
359
642
  };
643
+
644
+ ;// CONCATENATED MODULE: ./src/components/index.ts
645
+ // force new release
646
+
647
+
648
+ export { getFingerprintData, getFingerprintHash };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@versini/ui-fingerprint",
3
- "version": "1.1.4",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
- "homepage": "https://github.com/aversini/ui-components",
9
+ "homepage": "https://www.npmjs.com/package/@versini/ui-fingerprint",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git@github.com:aversini/ui-components.git"
@@ -20,13 +20,13 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build:check": "tsc",
23
- "build:js": "vite build",
24
- "build:types": "tsup",
25
- "build": "npm-run-all --serial clean build:check build:js build:types",
23
+ "build:js": "rslib build",
24
+ "build:types": "echo 'Types now built with rslib'",
25
+ "build": "npm-run-all --serial clean build:check build:js",
26
26
  "clean": "rimraf dist tmp",
27
- "dev:js": "vite build --watch --mode development",
28
- "dev:types": "tsup --watch src",
29
- "dev": "npm-run-all clean --parallel dev:js dev:types",
27
+ "dev:js": "rslib build --watch",
28
+ "dev:types": "echo 'Types now watched with rslib'",
29
+ "dev": "rslib build --watch",
30
30
  "lint": "biome lint src",
31
31
  "lint:fix": "biome check src --write --no-errors-on-unmatched",
32
32
  "prettier": "biome check --write --no-errors-on-unmatched",
@@ -34,5 +34,5 @@
34
34
  "test:watch": "vitest",
35
35
  "test": "vitest run"
36
36
  },
37
- "gitHead": "d568e20474c6c87f836c4cb6548f2cc0143a353c"
37
+ "gitHead": "b2ee2e328ecadfbedcb26d14afa896a30f0ab54b"
38
38
  }