@ginger-ai/ginger-js 0.0.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/README.md +88 -0
- package/dist/ginger.cjs.js +2 -0
- package/dist/ginger.cjs.js.map +1 -0
- package/dist/ginger.esm.d.ts +294 -0
- package/dist/ginger.esm.js +2 -0
- package/dist/ginger.esm.js.map +1 -0
- package/dist/ginger.umd.js +2 -0
- package/dist/ginger.umd.js.map +1 -0
- package/dist/types/behaviour/index.d.ts +49 -0
- package/dist/types/behaviour/index.d.ts.map +1 -0
- package/dist/types/client/index.d.ts +35 -0
- package/dist/types/client/index.d.ts.map +1 -0
- package/dist/types/core/constants.d.ts +5 -0
- package/dist/types/core/constants.d.ts.map +1 -0
- package/dist/types/core/dto/bot-detector.dto.d.ts +30 -0
- package/dist/types/core/dto/bot-detector.dto.d.ts.map +1 -0
- package/dist/types/core/dto/device-detector.dto.d.ts +54 -0
- package/dist/types/core/dto/device-detector.dto.d.ts.map +1 -0
- package/dist/types/core/dto/fingerprint.dto.d.ts +29 -0
- package/dist/types/core/dto/fingerprint.dto.d.ts.map +1 -0
- package/dist/types/core/dto/ginger.dto.d.ts +73 -0
- package/dist/types/core/dto/ginger.dto.d.ts.map +1 -0
- package/dist/types/core/dto/incognito-detector.dto.d.ts +4 -0
- package/dist/types/core/dto/incognito-detector.dto.d.ts.map +1 -0
- package/dist/types/core/dto/index.d.ts +10 -0
- package/dist/types/core/dto/index.d.ts.map +1 -0
- package/dist/types/core/dto/metrics.dto.d.ts +19 -0
- package/dist/types/core/dto/metrics.dto.d.ts.map +1 -0
- package/dist/types/core/dto/os-detector.dto.d.ts +6 -0
- package/dist/types/core/dto/os-detector.dto.d.ts.map +1 -0
- package/dist/types/core/dto/tor-detector.dto.d.ts +5 -0
- package/dist/types/core/dto/tor-detector.dto.d.ts.map +1 -0
- package/dist/types/core/helpers.d.ts +8 -0
- package/dist/types/core/helpers.d.ts.map +1 -0
- package/dist/types/core/http/httpClient.d.ts +28 -0
- package/dist/types/core/http/httpClient.d.ts.map +1 -0
- package/dist/types/core/http/request.d.ts +4 -0
- package/dist/types/core/http/request.d.ts.map +1 -0
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/core/index.d.ts.map +1 -0
- package/dist/types/core/util/error.d.ts +25 -0
- package/dist/types/core/util/error.d.ts.map +1 -0
- package/dist/types/core/util/generate-requestid.d.ts +2 -0
- package/dist/types/core/util/generate-requestid.d.ts.map +1 -0
- package/dist/types/device/components/audio/audio.d.ts +2 -0
- package/dist/types/device/components/audio/audio.d.ts.map +1 -0
- package/dist/types/device/components/canvas/canvas.d.ts +3 -0
- package/dist/types/device/components/canvas/canvas.d.ts.map +1 -0
- package/dist/types/device/components/extra/extra.d.ts +19 -0
- package/dist/types/device/components/extra/extra.d.ts.map +1 -0
- package/dist/types/device/components/fonts/fonts.d.ts +3 -0
- package/dist/types/device/components/fonts/fonts.d.ts.map +1 -0
- package/dist/types/device/components/hardware/hardware.d.ts +2 -0
- package/dist/types/device/components/hardware/hardware.d.ts.map +1 -0
- package/dist/types/device/components/index.d.ts +15 -0
- package/dist/types/device/components/index.d.ts.map +1 -0
- package/dist/types/device/components/locales/locales.d.ts +2 -0
- package/dist/types/device/components/locales/locales.d.ts.map +1 -0
- package/dist/types/device/components/math/math.d.ts +2 -0
- package/dist/types/device/components/math/math.d.ts.map +1 -0
- package/dist/types/device/components/permissions/permissions.d.ts +3 -0
- package/dist/types/device/components/permissions/permissions.d.ts.map +1 -0
- package/dist/types/device/components/plugins/plugins.d.ts +3 -0
- package/dist/types/device/components/plugins/plugins.d.ts.map +1 -0
- package/dist/types/device/components/screen/screen.d.ts +2 -0
- package/dist/types/device/components/screen/screen.d.ts.map +1 -0
- package/dist/types/device/components/screen/screenResolution.d.ts +16 -0
- package/dist/types/device/components/screen/screenResolution.d.ts.map +1 -0
- package/dist/types/device/components/system/browser.d.ts +22 -0
- package/dist/types/device/components/system/browser.d.ts.map +1 -0
- package/dist/types/device/components/system/emoji.d.ts +2 -0
- package/dist/types/device/components/system/emoji.d.ts.map +1 -0
- package/dist/types/device/components/system/system.d.ts +2 -0
- package/dist/types/device/components/system/system.d.ts.map +1 -0
- package/dist/types/device/components/webgl/imageHash.d.ts +3 -0
- package/dist/types/device/components/webgl/imageHash.d.ts.map +1 -0
- package/dist/types/device/components/webgl/webgl.d.ts +54 -0
- package/dist/types/device/components/webgl/webgl.d.ts.map +1 -0
- package/dist/types/device/factory.d.ts +26 -0
- package/dist/types/device/factory.d.ts.map +1 -0
- package/dist/types/device/index.d.ts +61 -0
- package/dist/types/device/index.d.ts.map +1 -0
- package/dist/types/device/modules/bot.d.ts +9 -0
- package/dist/types/device/modules/bot.d.ts.map +1 -0
- package/dist/types/device/modules/browserDetails.d.ts +6 -0
- package/dist/types/device/modules/browserDetails.d.ts.map +1 -0
- package/dist/types/device/modules/device/analyze-data.d.ts +7 -0
- package/dist/types/device/modules/device/analyze-data.d.ts.map +1 -0
- package/dist/types/device/modules/device/gather-data.d.ts +6 -0
- package/dist/types/device/modules/device/gather-data.d.ts.map +1 -0
- package/dist/types/device/modules/device/helpers.d.ts +9 -0
- package/dist/types/device/modules/device/helpers.d.ts.map +1 -0
- package/dist/types/device/modules/device/index.d.ts +3 -0
- package/dist/types/device/modules/device/index.d.ts.map +1 -0
- package/dist/types/device/modules/fp.d.ts +14 -0
- package/dist/types/device/modules/fp.d.ts.map +1 -0
- package/dist/types/device/modules/incognito.d.ts +7 -0
- package/dist/types/device/modules/incognito.d.ts.map +1 -0
- package/dist/types/device/modules/options.d.ts +12 -0
- package/dist/types/device/modules/options.d.ts.map +1 -0
- package/dist/types/device/modules/os.d.ts +3 -0
- package/dist/types/device/modules/os.d.ts.map +1 -0
- package/dist/types/device/modules/tor.d.ts +4 -0
- package/dist/types/device/modules/tor.d.ts.map +1 -0
- package/dist/types/device/utils/async.d.ts +33 -0
- package/dist/types/device/utils/async.d.ts.map +1 -0
- package/dist/types/device/utils/browser_.d.ts +103 -0
- package/dist/types/device/utils/browser_.d.ts.map +1 -0
- package/dist/types/device/utils/commonPixels.d.ts +2 -0
- package/dist/types/device/utils/commonPixels.d.ts.map +1 -0
- package/dist/types/device/utils/data.d.ts +33 -0
- package/dist/types/device/utils/data.d.ts.map +1 -0
- package/dist/types/device/utils/dom.d.ts +26 -0
- package/dist/types/device/utils/dom.d.ts.map +1 -0
- package/dist/types/device/utils/ephemeralIFrame.d.ts +5 -0
- package/dist/types/device/utils/ephemeralIFrame.d.ts.map +1 -0
- package/dist/types/device/utils/getMostFrequent.d.ts +6 -0
- package/dist/types/device/utils/getMostFrequent.d.ts.map +1 -0
- package/dist/types/device/utils/hash.d.ts +6 -0
- package/dist/types/device/utils/hash.d.ts.map +1 -0
- package/dist/types/device/utils/misc.d.ts +7 -0
- package/dist/types/device/utils/misc.d.ts.map +1 -0
- package/dist/types/device/utils/raceAll.d.ts +9 -0
- package/dist/types/device/utils/raceAll.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +52 -0
- package/src/behaviour/index.ts +279 -0
- package/src/client/index.ts +132 -0
- package/src/core/constants.ts +4 -0
- package/src/core/dto/bot-detector.dto.ts +32 -0
- package/src/core/dto/device-detector.dto.ts +67 -0
- package/src/core/dto/fingerprint.dto.ts +38 -0
- package/src/core/dto/ginger.dto.ts +89 -0
- package/src/core/dto/incognito-detector.dto.ts +2 -0
- package/src/core/dto/index.ts +18 -0
- package/src/core/dto/metrics.dto.ts +20 -0
- package/src/core/dto/os-detector.dto.ts +5 -0
- package/src/core/dto/tor-detector.dto.ts +4 -0
- package/src/core/helpers.ts +33 -0
- package/src/core/http/httpClient.ts +52 -0
- package/src/core/http/request.ts +32 -0
- package/src/core/index.ts +5 -0
- package/src/core/util/error.ts +40 -0
- package/src/core/util/generate-requestid.ts +63 -0
- package/src/device/components/audio/audio.ts +58 -0
- package/src/device/components/canvas/canvas.ts +88 -0
- package/src/device/components/extra/extra.ts +581 -0
- package/src/device/components/fonts/fonts.ts +143 -0
- package/src/device/components/hardware/hardware.ts +66 -0
- package/src/device/components/index.ts +14 -0
- package/src/device/components/locales/locales.ts +21 -0
- package/src/device/components/math/math.ts +39 -0
- package/src/device/components/permissions/permissions.ts +60 -0
- package/src/device/components/plugins/plugins.ts +22 -0
- package/src/device/components/screen/screen.ts +13 -0
- package/src/device/components/screen/screenResolution.ts +45 -0
- package/src/device/components/system/browser.ts +838 -0
- package/src/device/components/system/emoji.ts +134 -0
- package/src/device/components/system/system.ts +76 -0
- package/src/device/components/webgl/imageHash.ts +144 -0
- package/src/device/components/webgl/webgl.ts +302 -0
- package/src/device/factory.ts +54 -0
- package/src/device/index.ts +60 -0
- package/src/device/modules/bot.ts +25 -0
- package/src/device/modules/browserDetails.ts +11 -0
- package/src/device/modules/device/analyze-data.ts +150 -0
- package/src/device/modules/device/gather-data.ts +92 -0
- package/src/device/modules/device/helpers.ts +123 -0
- package/src/device/modules/device/index.ts +64 -0
- package/src/device/modules/fp.ts +138 -0
- package/src/device/modules/incognito.ts +253 -0
- package/src/device/modules/options.ts +17 -0
- package/src/device/modules/os.ts +15 -0
- package/src/device/modules/tor.ts +41 -0
- package/src/device/utils/async.ts +106 -0
- package/src/device/utils/browser_.ts +347 -0
- package/src/device/utils/commonPixels.ts +38 -0
- package/src/device/utils/data.ts +161 -0
- package/src/device/utils/dom.ts +148 -0
- package/src/device/utils/ephemeralIFrame.ts +35 -0
- package/src/device/utils/getMostFrequent.ts +39 -0
- package/src/device/utils/hash.ts +202 -0
- package/src/device/utils/misc.ts +18 -0
- package/src/device/utils/raceAll.ts +19 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { componentInterface, includeComponent } from '../../factory';
|
|
2
|
+
import { hash } from '../../utils/hash';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Set of emojis to sample for fingerprinting
|
|
6
|
+
* These emojis are chosen because they have significant rendering differences across platforms
|
|
7
|
+
*/
|
|
8
|
+
const TEST_EMOJIS = [
|
|
9
|
+
'😀', // Basic smiling face - varies significantly
|
|
10
|
+
'👨👩👧👦', // Family emoji - complex with multiple characters
|
|
11
|
+
'🇺🇸', // Flag - rendered differently across platforms
|
|
12
|
+
'🍎', // Apple - varies in color and style
|
|
13
|
+
'🐼', // Panda - good variation in details
|
|
14
|
+
'🚀', // Rocket - complex shape with details
|
|
15
|
+
'🏳️🌈', // Rainbow flag - combination character
|
|
16
|
+
'👍🏽', // Thumbs up with skin tone - tests skin tone rendering
|
|
17
|
+
'❤️', // Heart with variation selector
|
|
18
|
+
'🤦♂️', // Facepalm with gender - complex combination
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Font families to test for emoji rendering variations
|
|
23
|
+
*/
|
|
24
|
+
const TEST_FONTS = [
|
|
25
|
+
'Apple Color Emoji',
|
|
26
|
+
'Segoe UI Emoji',
|
|
27
|
+
'Segoe UI Symbol',
|
|
28
|
+
'Noto Color Emoji',
|
|
29
|
+
'Android Emoji',
|
|
30
|
+
'EmojiOne',
|
|
31
|
+
'Twemoji Mozilla',
|
|
32
|
+
'sans-serif' // Fallback
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Renders an emoji to a canvas and returns its pixel data
|
|
37
|
+
* @param emoji The emoji to render
|
|
38
|
+
* @param fontFamily Font family to use
|
|
39
|
+
* @returns Uint8ClampedArray of pixels or null if rendering fails
|
|
40
|
+
*/
|
|
41
|
+
function renderEmojiToCanvas(emoji: string, fontFamily: string): Uint8ClampedArray | null {
|
|
42
|
+
try {
|
|
43
|
+
// Create canvas and context
|
|
44
|
+
const canvas = document.createElement('canvas');
|
|
45
|
+
const size = 20; // Small size is enough for fingerprinting
|
|
46
|
+
canvas.width = size;
|
|
47
|
+
canvas.height = size;
|
|
48
|
+
|
|
49
|
+
const ctx = canvas.getContext('2d');
|
|
50
|
+
if (!ctx) return null;
|
|
51
|
+
|
|
52
|
+
// Clear background
|
|
53
|
+
ctx.fillStyle = 'white';
|
|
54
|
+
ctx.fillRect(0, 0, size, size);
|
|
55
|
+
|
|
56
|
+
// Draw emoji
|
|
57
|
+
ctx.textBaseline = 'middle';
|
|
58
|
+
ctx.textAlign = 'center';
|
|
59
|
+
ctx.fillStyle = 'black';
|
|
60
|
+
ctx.font = `16px "${fontFamily}"`;
|
|
61
|
+
ctx.fillText(emoji, size / 2, size / 2);
|
|
62
|
+
|
|
63
|
+
// Get pixel data
|
|
64
|
+
return ctx.getImageData(0, 0, size, size).data;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a simplified fingerprint from pixel data by sampling
|
|
72
|
+
* @param pixels Canvas pixel data
|
|
73
|
+
* @returns Simplified representation for fingerprinting
|
|
74
|
+
*/
|
|
75
|
+
function simplifyPixelData(pixels: Uint8ClampedArray): number[] {
|
|
76
|
+
// Sample every 8th pixel for efficiency
|
|
77
|
+
const simplified: number[] = [];
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < pixels.length; i += 32) {
|
|
80
|
+
// Use only RGB values (skip alpha)
|
|
81
|
+
simplified.push(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return simplified;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generates a fingerprint based on emoji rendering
|
|
89
|
+
* @returns Promise that resolves to emoji fingerprint data
|
|
90
|
+
*/
|
|
91
|
+
async function getEmojiFingerprint(): Promise<componentInterface> {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
try {
|
|
94
|
+
const results: Record<string, string> = {};
|
|
95
|
+
let combinedData: number[] = [];
|
|
96
|
+
|
|
97
|
+
// Test each emoji with the first available font
|
|
98
|
+
TEST_EMOJIS.forEach((emoji, index) => {
|
|
99
|
+
// Try each font until one works
|
|
100
|
+
for (const font of TEST_FONTS) {
|
|
101
|
+
const pixelData = renderEmojiToCanvas(emoji, font);
|
|
102
|
+
|
|
103
|
+
if (pixelData) {
|
|
104
|
+
const simplified = simplifyPixelData(pixelData);
|
|
105
|
+
combinedData = [...combinedData, ...simplified];
|
|
106
|
+
|
|
107
|
+
// Store individual emoji hash for detailed fingerprinting
|
|
108
|
+
results[`emoji_${index}`] = hash(new Uint8Array(simplified).buffer).slice(0, 16);
|
|
109
|
+
break; // Use first successful font
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Generate a combined hash for all emoji data
|
|
115
|
+
const combinedHash = hash(new Uint8Array(combinedData).buffer);
|
|
116
|
+
|
|
117
|
+
resolve({
|
|
118
|
+
emojiFingerprintHash: combinedHash,
|
|
119
|
+
emojiDetails: results,
|
|
120
|
+
uniqueEmojisRendered: Object.keys(results).length
|
|
121
|
+
});
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// Fallback in case of any errors
|
|
124
|
+
resolve({
|
|
125
|
+
emojiFingerprintHash: 'unsupported',
|
|
126
|
+
emojiDetails: {},
|
|
127
|
+
uniqueEmojisRendered: 0
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Register the component
|
|
134
|
+
includeComponent('emojiFingerprint', getEmojiFingerprint);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { componentInterface, includeComponent } from '../../factory';
|
|
2
|
+
import { detectBrowser } from './browser'
|
|
3
|
+
|
|
4
|
+
async function getSystemDetails(): Promise<componentInterface> {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
// Get detailed browser information
|
|
7
|
+
const browserDetails = detectBrowser();
|
|
8
|
+
|
|
9
|
+
resolve({
|
|
10
|
+
'platform': window.navigator.platform,
|
|
11
|
+
'cookieEnabled': window.navigator.cookieEnabled,
|
|
12
|
+
'productSub': navigator.productSub,
|
|
13
|
+
'product': navigator.product,
|
|
14
|
+
'browser': {
|
|
15
|
+
'name': browserDetails.name,
|
|
16
|
+
},
|
|
17
|
+
'applePayVersion': getApplePayVersion(),
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generates a unique browser hash based on various browser characteristics
|
|
24
|
+
* that can be used for fingerprinting
|
|
25
|
+
*/
|
|
26
|
+
// function generateBrowserHash(): string {
|
|
27
|
+
// const characteristics = [
|
|
28
|
+
// navigator.userAgent,
|
|
29
|
+
// navigator.language,
|
|
30
|
+
// navigator.hardwareConcurrency,
|
|
31
|
+
// navigator.deviceMemory,
|
|
32
|
+
// navigator.platform,
|
|
33
|
+
// screen.colorDepth,
|
|
34
|
+
// navigator.maxTouchPoints,
|
|
35
|
+
// 'chrome' in window ? 'chrome' : 'no-chrome',
|
|
36
|
+
// 'opr' in window ? 'opera' : 'no-opera',
|
|
37
|
+
// 'safari' in window ? 'safari' : 'no-safari',
|
|
38
|
+
// new Date().getTimezoneOffset(),
|
|
39
|
+
// screen.width + 'x' + screen.height
|
|
40
|
+
// ];
|
|
41
|
+
|
|
42
|
+
// // Use simple hashing algorithm
|
|
43
|
+
// let hash = 0;
|
|
44
|
+
// const str = characteristics.join('||');
|
|
45
|
+
|
|
46
|
+
// for (let i = 0; i < str.length; i++) {
|
|
47
|
+
// const char = str.charCodeAt(i);
|
|
48
|
+
// hash = ((hash << 5) - hash) + char;
|
|
49
|
+
// hash = hash & hash; // Convert to 32bit integer
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// // Convert to hex string
|
|
53
|
+
// return (hash >>> 0).toString(16).padStart(8, '0');
|
|
54
|
+
// }
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns applePayCanMakePayments: boolean, applePayMaxSupportedVersion: number
|
|
58
|
+
*/
|
|
59
|
+
function getApplePayVersion(): number {
|
|
60
|
+
if (window.location.protocol === 'https:' && typeof (window as any).ApplePaySession === 'function') {
|
|
61
|
+
try {
|
|
62
|
+
const versionCheck = (window as any).ApplePaySession.supportsVersion;
|
|
63
|
+
for (let i = 15; i > 0; i--) {
|
|
64
|
+
if (versionCheck(i)) {
|
|
65
|
+
return i;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
includeComponent('system', getSystemDetails);
|
|
76
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { componentInterface, } from '../../factory'
|
|
2
|
+
import { hash } from '../../utils/hash'
|
|
3
|
+
import { getCommonPixels } from '../../utils/commonPixels';
|
|
4
|
+
import { getBrowser } from '../system/browser';
|
|
5
|
+
|
|
6
|
+
const _RUNS = (getBrowser().name !== 'SamsungBrowser') ? 1 : 3;
|
|
7
|
+
let canvas: HTMLCanvasElement
|
|
8
|
+
let gl: WebGLRenderingContext | null = null;
|
|
9
|
+
|
|
10
|
+
function initializeCanvasAndWebGL() {
|
|
11
|
+
if (typeof document !== 'undefined') {
|
|
12
|
+
canvas = document.createElement('canvas');
|
|
13
|
+
canvas.width = 200;
|
|
14
|
+
canvas.height = 100;
|
|
15
|
+
gl = canvas.getContext('webgl');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function createWebGLFingerprint(): Promise<componentInterface | undefined> {
|
|
20
|
+
initializeCanvasAndWebGL();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
|
|
24
|
+
if (!gl) {
|
|
25
|
+
throw new Error('WebGL not supported');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const imageDatas: ImageData[] = Array.from({length: _RUNS}, () => createWebGLImageData() );
|
|
30
|
+
// and then checking the most common bytes for each channel of each pixel
|
|
31
|
+
const commonImageData = getCommonPixels(imageDatas, canvas.width, canvas.height);
|
|
32
|
+
//const imageData = createWebGLImageData()
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
'commonImageHash': hash(commonImageData.data.toString()).toString(),
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createWebGLImageData(): ImageData {
|
|
43
|
+
try {
|
|
44
|
+
|
|
45
|
+
if (!gl) {
|
|
46
|
+
throw new Error('WebGL not supported');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const vertexShaderSource = `
|
|
50
|
+
attribute vec2 position;
|
|
51
|
+
void main() {
|
|
52
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const fragmentShaderSource = `
|
|
57
|
+
precision mediump float;
|
|
58
|
+
void main() {
|
|
59
|
+
gl_FragColor = vec4(0.812, 0.195, 0.553, 0.921); // Set line color
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
64
|
+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
65
|
+
|
|
66
|
+
if (!vertexShader || !fragmentShader) {
|
|
67
|
+
throw new Error('Failed to create shaders');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
gl.shaderSource(vertexShader, vertexShaderSource);
|
|
71
|
+
gl.shaderSource(fragmentShader, fragmentShaderSource);
|
|
72
|
+
|
|
73
|
+
gl.compileShader(vertexShader);
|
|
74
|
+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
75
|
+
throw new Error('Vertex shader compilation failed: ' + gl.getShaderInfoLog(vertexShader));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
gl.compileShader(fragmentShader);
|
|
79
|
+
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
80
|
+
throw new Error('Fragment shader compilation failed: ' + gl.getShaderInfoLog(fragmentShader));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const shaderProgram = gl.createProgram();
|
|
84
|
+
|
|
85
|
+
if (!shaderProgram) {
|
|
86
|
+
throw new Error('Failed to create shader program');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
gl.attachShader(shaderProgram, vertexShader);
|
|
90
|
+
gl.attachShader(shaderProgram, fragmentShader);
|
|
91
|
+
gl.linkProgram(shaderProgram);
|
|
92
|
+
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
93
|
+
throw new Error('Shader program linking failed: ' + gl.getProgramInfoLog(shaderProgram));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
gl.useProgram(shaderProgram);
|
|
97
|
+
|
|
98
|
+
// Set up vertices to form lines
|
|
99
|
+
const numSpokes: number = 137;
|
|
100
|
+
const vertices = new Float32Array(numSpokes * 4);
|
|
101
|
+
const angleIncrement = (2 * Math.PI) / numSpokes;
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < numSpokes; i++) {
|
|
104
|
+
const angle = i * angleIncrement;
|
|
105
|
+
|
|
106
|
+
// Define two points for each line (spoke)
|
|
107
|
+
vertices[i * 4] = 0; // Center X
|
|
108
|
+
vertices[i * 4 + 1] = 0; // Center Y
|
|
109
|
+
vertices[i * 4 + 2] = Math.cos(angle) * (canvas.width / 2); // Endpoint X
|
|
110
|
+
vertices[i * 4 + 3] = Math.sin(angle) * (canvas.height / 2); // Endpoint Y
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const vertexBuffer = gl.createBuffer();
|
|
114
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
115
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
116
|
+
|
|
117
|
+
const positionAttribute = gl.getAttribLocation(shaderProgram, 'position');
|
|
118
|
+
gl.enableVertexAttribArray(positionAttribute);
|
|
119
|
+
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
|
|
120
|
+
|
|
121
|
+
// Render
|
|
122
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
123
|
+
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
124
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
125
|
+
gl.drawArrays(gl.LINES, 0, numSpokes * 2);
|
|
126
|
+
|
|
127
|
+
const pixelData = new Uint8ClampedArray(canvas.width * canvas.height * 4);
|
|
128
|
+
gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);
|
|
129
|
+
const imageData = new ImageData(pixelData, canvas.width, canvas.height);
|
|
130
|
+
|
|
131
|
+
return imageData;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
//console.error(error);
|
|
134
|
+
return new ImageData(1, 1);
|
|
135
|
+
} finally {
|
|
136
|
+
if (gl) {
|
|
137
|
+
// Reset WebGL state
|
|
138
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
139
|
+
gl.useProgram(null);
|
|
140
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
141
|
+
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { componentInterface, includeComponent } from "../../factory";
|
|
2
|
+
import { isChromium, isGecko, isWebKit } from "../../utils/browser_";
|
|
3
|
+
import { createWebGLFingerprint } from "./imageHash";
|
|
4
|
+
|
|
5
|
+
// Types and constants are used instead of interfaces and enums to avoid this error in projects which use this library:
|
|
6
|
+
// Exported variable '...' has or is using name '...' from external module "..." but cannot be named.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* WebGL extended features
|
|
11
|
+
*/
|
|
12
|
+
type WebGlExtensionsPayload = {
|
|
13
|
+
contextAttributes: string[]; // ['alpha=true', 'antialias=true...
|
|
14
|
+
parameters: string[]; // ['ACTIVE_TEXTURE(33984)', 'ALIASED_LINE_WID...
|
|
15
|
+
shaderPrecisions: string[]; // ['FRAGMENT_SHADER.LOW_FLOAT=127,127,23...
|
|
16
|
+
extensions: string[] | null; // ['ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_color...
|
|
17
|
+
extensionParameters: string[]; // ['COMPRESSED_RGB_S3TC_DXT1_EXT(33776)', 'COMPR...
|
|
18
|
+
unsupportedExtensions: string[]; // ['EXT_blend_minmax', 'EXT_color...
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type CanvasContext = WebGLRenderingContext & {
|
|
22
|
+
readonly canvas: HTMLCanvasElement;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Options = {
|
|
26
|
+
cache: {
|
|
27
|
+
webgl?: {
|
|
28
|
+
context: CanvasContext | undefined;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** WebGl context is not available */
|
|
34
|
+
export const STATUS_NO_GL_CONTEXT = -1;
|
|
35
|
+
/** WebGL context `getParameter` method is not a function */
|
|
36
|
+
export const STATUS_GET_PARAMETER_NOT_A_FUNCTION = -2;
|
|
37
|
+
|
|
38
|
+
export type SpecialStatus =
|
|
39
|
+
| typeof STATUS_NO_GL_CONTEXT
|
|
40
|
+
| typeof STATUS_GET_PARAMETER_NOT_A_FUNCTION;
|
|
41
|
+
|
|
42
|
+
const validContextParameters = new Set([
|
|
43
|
+
10752, 2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961, 2962,
|
|
44
|
+
2963, 2964, 2965, 2966, 2967, 2968, 2978, 3024, 3042, 3088, 3089, 3106, 3107,
|
|
45
|
+
32773, 32777, 32777, 32823, 32824, 32936, 32937, 32938, 32939, 32968, 32969,
|
|
46
|
+
32970, 32971, 3317, 33170, 3333, 3379, 3386, 33901, 33902, 34016, 34024,
|
|
47
|
+
34076, 3408, 3410, 3411, 3412, 3413, 3414, 3415, 34467, 34816, 34817, 34818,
|
|
48
|
+
34819, 34877, 34921, 34930, 35660, 35661, 35724, 35738, 35739, 36003, 36004,
|
|
49
|
+
36005, 36347, 36348, 36349, 37440, 37441, 37443, 7936, 7937, 7938,
|
|
50
|
+
// SAMPLE_ALPHA_TO_COVERAGE (32926) and SAMPLE_COVERAGE (32928) are excluded because they trigger a console warning
|
|
51
|
+
// in IE, Chrome ≤ 59 and Safari ≤ 13 and give no entropy.
|
|
52
|
+
]);
|
|
53
|
+
const validExtensionParams = new Set([
|
|
54
|
+
34047, // MAX_TEXTURE_MAX_ANISOTROPY_EXT
|
|
55
|
+
35723, // FRAGMENT_SHADER_DERIVATIVE_HINT_OES
|
|
56
|
+
36063, // MAX_COLOR_ATTACHMENTS_WEBGL
|
|
57
|
+
34852, // MAX_DRAW_BUFFERS_WEBGL
|
|
58
|
+
34853, // DRAW_BUFFER0_WEBGL
|
|
59
|
+
34854, // DRAW_BUFFER1_WEBGL
|
|
60
|
+
34229, // VERTEX_ARRAY_BINDING_OES
|
|
61
|
+
36392, // TIMESTAMP_EXT
|
|
62
|
+
36795, // GPU_DISJOINT_EXT
|
|
63
|
+
38449, // MAX_VIEWS_OVR
|
|
64
|
+
]);
|
|
65
|
+
const shaderTypes = ["FRAGMENT_SHADER", "VERTEX_SHADER"] as const;
|
|
66
|
+
const precisionTypes = [
|
|
67
|
+
"LOW_FLOAT",
|
|
68
|
+
"MEDIUM_FLOAT",
|
|
69
|
+
"HIGH_FLOAT",
|
|
70
|
+
"LOW_INT",
|
|
71
|
+
"MEDIUM_INT",
|
|
72
|
+
"HIGH_INT",
|
|
73
|
+
] as const;
|
|
74
|
+
const rendererInfoExtensionName = "WEBGL_debug_renderer_info";
|
|
75
|
+
const polygonModeExtensionName = "WEBGL_polygon_mode";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets the basic and simple WebGL parameters
|
|
79
|
+
*/
|
|
80
|
+
export async function getWebGlBasics(
|
|
81
|
+
{ cache = {} }: Options = { cache: {} }
|
|
82
|
+
): Promise<componentInterface> {
|
|
83
|
+
const gl = getWebGLContext(cache);
|
|
84
|
+
if (!gl) {
|
|
85
|
+
return { status: STATUS_NO_GL_CONTEXT };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!isValidParameterGetter(gl)) {
|
|
89
|
+
return { status: STATUS_GET_PARAMETER_NOT_A_FUNCTION };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const debugExtension = shouldAvoidDebugRendererInfo()
|
|
93
|
+
? null
|
|
94
|
+
: gl.getExtension(rendererInfoExtensionName);
|
|
95
|
+
|
|
96
|
+
const imageHash = await createWebGLFingerprint();
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
version: gl.getParameter(gl.VERSION)?.toString() || "",
|
|
100
|
+
vendor: gl.getParameter(gl.VENDOR)?.toString() || "",
|
|
101
|
+
vendorUnmasked: debugExtension
|
|
102
|
+
? gl.getParameter(debugExtension.UNMASKED_VENDOR_WEBGL)?.toString()
|
|
103
|
+
: "",
|
|
104
|
+
renderer: gl.getParameter(gl.RENDERER)?.toString() || "",
|
|
105
|
+
rendererUnmasked: debugExtension
|
|
106
|
+
? gl.getParameter(debugExtension.UNMASKED_RENDERER_WEBGL)?.toString()
|
|
107
|
+
: "",
|
|
108
|
+
shadingLanguageVersion:
|
|
109
|
+
gl.getParameter(gl.SHADING_LANGUAGE_VERSION)?.toString() || "",
|
|
110
|
+
...imageHash,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets the advanced and massive WebGL parameters and extensions
|
|
116
|
+
*/
|
|
117
|
+
export async function getWebGlExtensions(
|
|
118
|
+
{ cache = {} }: Options = { cache: {} }
|
|
119
|
+
): Promise<WebGlExtensionsPayload | SpecialStatus> {
|
|
120
|
+
const gl = getWebGLContext(cache);
|
|
121
|
+
if (!gl) {
|
|
122
|
+
return STATUS_NO_GL_CONTEXT;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!isValidParameterGetter(gl)) {
|
|
126
|
+
return STATUS_GET_PARAMETER_NOT_A_FUNCTION;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const extensions = gl.getSupportedExtensions();
|
|
130
|
+
const contextAttributes = gl.getContextAttributes();
|
|
131
|
+
const unsupportedExtensions: string[] = [];
|
|
132
|
+
|
|
133
|
+
// Features
|
|
134
|
+
const attributes: string[] = [];
|
|
135
|
+
const parameters: string[] = [];
|
|
136
|
+
const extensionParameters: string[] = [];
|
|
137
|
+
const shaderPrecisions: string[] = [];
|
|
138
|
+
|
|
139
|
+
// Context attributes
|
|
140
|
+
if (contextAttributes) {
|
|
141
|
+
for (const attributeName of Object.keys(
|
|
142
|
+
contextAttributes
|
|
143
|
+
) as (keyof WebGLContextAttributes)[]) {
|
|
144
|
+
attributes.push(`${attributeName}=${contextAttributes[attributeName]}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Context parameters
|
|
149
|
+
const constants = getConstantsFromPrototype(gl);
|
|
150
|
+
for (const constant of constants) {
|
|
151
|
+
const code = gl[constant] as number;
|
|
152
|
+
parameters.push(
|
|
153
|
+
`${constant}=${code}${
|
|
154
|
+
validContextParameters.has(code) ? `=${gl.getParameter(code)}` : ""
|
|
155
|
+
}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Extension parameters
|
|
160
|
+
if (extensions) {
|
|
161
|
+
for (const name of extensions) {
|
|
162
|
+
if (
|
|
163
|
+
(name === rendererInfoExtensionName &&
|
|
164
|
+
shouldAvoidDebugRendererInfo()) ||
|
|
165
|
+
(name === polygonModeExtensionName &&
|
|
166
|
+
shouldAvoidPolygonModeExtensions())
|
|
167
|
+
) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const extension = gl.getExtension(name);
|
|
172
|
+
if (!extension) {
|
|
173
|
+
unsupportedExtensions.push(name);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const constant of getConstantsFromPrototype(extension)) {
|
|
178
|
+
const code = extension[constant];
|
|
179
|
+
extensionParameters.push(
|
|
180
|
+
`${constant}=${code}${
|
|
181
|
+
validExtensionParams.has(code) ? `=${gl.getParameter(code)}` : ""
|
|
182
|
+
}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Shader precision
|
|
189
|
+
for (const shaderType of shaderTypes) {
|
|
190
|
+
for (const precisionType of precisionTypes) {
|
|
191
|
+
const shaderPrecision = getShaderPrecision(gl, shaderType, precisionType);
|
|
192
|
+
shaderPrecisions.push(
|
|
193
|
+
`${shaderType}.${precisionType}=${shaderPrecision.join(",")}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Postprocess
|
|
199
|
+
extensionParameters.sort();
|
|
200
|
+
parameters.sort();
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
contextAttributes: attributes,
|
|
204
|
+
parameters: parameters,
|
|
205
|
+
shaderPrecisions: shaderPrecisions,
|
|
206
|
+
extensions: extensions,
|
|
207
|
+
extensionParameters: extensionParameters,
|
|
208
|
+
unsupportedExtensions,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* This function usually takes the most time to execute in all the sources, therefore we cache its result.
|
|
214
|
+
*
|
|
215
|
+
* Warning for package users:
|
|
216
|
+
* This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
|
|
217
|
+
*/
|
|
218
|
+
export function getWebGLContext(cache: Options["cache"]) {
|
|
219
|
+
if (cache.webgl) {
|
|
220
|
+
return cache.webgl.context;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const canvas = document.createElement("canvas");
|
|
224
|
+
let context: CanvasContext | undefined;
|
|
225
|
+
|
|
226
|
+
canvas.addEventListener(
|
|
227
|
+
"webglCreateContextError",
|
|
228
|
+
() => (context = undefined)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
for (const type of ["webgl", "experimental-webgl"]) {
|
|
232
|
+
try {
|
|
233
|
+
context = canvas.getContext(type) as CanvasContext;
|
|
234
|
+
} catch {
|
|
235
|
+
// Ok, continue
|
|
236
|
+
}
|
|
237
|
+
if (context) {
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
cache.webgl = { context };
|
|
243
|
+
return context;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat
|
|
248
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat
|
|
249
|
+
* https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.12
|
|
250
|
+
*/
|
|
251
|
+
function getShaderPrecision(
|
|
252
|
+
gl: WebGLRenderingContext,
|
|
253
|
+
shaderType: (typeof shaderTypes)[number],
|
|
254
|
+
precisionType: (typeof precisionTypes)[number]
|
|
255
|
+
) {
|
|
256
|
+
const shaderPrecision = gl.getShaderPrecisionFormat(
|
|
257
|
+
gl[shaderType],
|
|
258
|
+
gl[precisionType]
|
|
259
|
+
);
|
|
260
|
+
return shaderPrecision
|
|
261
|
+
? [
|
|
262
|
+
shaderPrecision.rangeMin,
|
|
263
|
+
shaderPrecision.rangeMax,
|
|
264
|
+
shaderPrecision.precision,
|
|
265
|
+
]
|
|
266
|
+
: [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getConstantsFromPrototype<K>(obj: K): Array<Extract<keyof K, string>> {
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
const keys = Object.keys((obj as any).__proto__) as Array<keyof K>;
|
|
272
|
+
return keys.filter(isConstantLike);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function isConstantLike<K>(key: K): key is Extract<K, string> {
|
|
276
|
+
return typeof key === "string" && !key.match(/[^A-Z0-9_x]/);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Some browsers print a console warning when the WEBGL_debug_renderer_info extension is requested.
|
|
281
|
+
* JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers.
|
|
282
|
+
*/
|
|
283
|
+
export function shouldAvoidDebugRendererInfo(): boolean {
|
|
284
|
+
return isGecko();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Some browsers print a console warning when the WEBGL_polygon_mode extension is requested.
|
|
289
|
+
* JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers.
|
|
290
|
+
*/
|
|
291
|
+
export function shouldAvoidPolygonModeExtensions(): boolean {
|
|
292
|
+
return isChromium() || isWebKit();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Some unknown browsers have no `getParameter` method
|
|
297
|
+
*/
|
|
298
|
+
function isValidParameterGetter(gl: WebGLRenderingContext) {
|
|
299
|
+
return typeof gl.getParameter === "function";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
includeComponent("webgl", getWebGlBasics);
|