@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.
Files changed (186) hide show
  1. package/README.md +88 -0
  2. package/dist/ginger.cjs.js +2 -0
  3. package/dist/ginger.cjs.js.map +1 -0
  4. package/dist/ginger.esm.d.ts +294 -0
  5. package/dist/ginger.esm.js +2 -0
  6. package/dist/ginger.esm.js.map +1 -0
  7. package/dist/ginger.umd.js +2 -0
  8. package/dist/ginger.umd.js.map +1 -0
  9. package/dist/types/behaviour/index.d.ts +49 -0
  10. package/dist/types/behaviour/index.d.ts.map +1 -0
  11. package/dist/types/client/index.d.ts +35 -0
  12. package/dist/types/client/index.d.ts.map +1 -0
  13. package/dist/types/core/constants.d.ts +5 -0
  14. package/dist/types/core/constants.d.ts.map +1 -0
  15. package/dist/types/core/dto/bot-detector.dto.d.ts +30 -0
  16. package/dist/types/core/dto/bot-detector.dto.d.ts.map +1 -0
  17. package/dist/types/core/dto/device-detector.dto.d.ts +54 -0
  18. package/dist/types/core/dto/device-detector.dto.d.ts.map +1 -0
  19. package/dist/types/core/dto/fingerprint.dto.d.ts +29 -0
  20. package/dist/types/core/dto/fingerprint.dto.d.ts.map +1 -0
  21. package/dist/types/core/dto/ginger.dto.d.ts +73 -0
  22. package/dist/types/core/dto/ginger.dto.d.ts.map +1 -0
  23. package/dist/types/core/dto/incognito-detector.dto.d.ts +4 -0
  24. package/dist/types/core/dto/incognito-detector.dto.d.ts.map +1 -0
  25. package/dist/types/core/dto/index.d.ts +10 -0
  26. package/dist/types/core/dto/index.d.ts.map +1 -0
  27. package/dist/types/core/dto/metrics.dto.d.ts +19 -0
  28. package/dist/types/core/dto/metrics.dto.d.ts.map +1 -0
  29. package/dist/types/core/dto/os-detector.dto.d.ts +6 -0
  30. package/dist/types/core/dto/os-detector.dto.d.ts.map +1 -0
  31. package/dist/types/core/dto/tor-detector.dto.d.ts +5 -0
  32. package/dist/types/core/dto/tor-detector.dto.d.ts.map +1 -0
  33. package/dist/types/core/helpers.d.ts +8 -0
  34. package/dist/types/core/helpers.d.ts.map +1 -0
  35. package/dist/types/core/http/httpClient.d.ts +28 -0
  36. package/dist/types/core/http/httpClient.d.ts.map +1 -0
  37. package/dist/types/core/http/request.d.ts +4 -0
  38. package/dist/types/core/http/request.d.ts.map +1 -0
  39. package/dist/types/core/index.d.ts +6 -0
  40. package/dist/types/core/index.d.ts.map +1 -0
  41. package/dist/types/core/util/error.d.ts +25 -0
  42. package/dist/types/core/util/error.d.ts.map +1 -0
  43. package/dist/types/core/util/generate-requestid.d.ts +2 -0
  44. package/dist/types/core/util/generate-requestid.d.ts.map +1 -0
  45. package/dist/types/device/components/audio/audio.d.ts +2 -0
  46. package/dist/types/device/components/audio/audio.d.ts.map +1 -0
  47. package/dist/types/device/components/canvas/canvas.d.ts +3 -0
  48. package/dist/types/device/components/canvas/canvas.d.ts.map +1 -0
  49. package/dist/types/device/components/extra/extra.d.ts +19 -0
  50. package/dist/types/device/components/extra/extra.d.ts.map +1 -0
  51. package/dist/types/device/components/fonts/fonts.d.ts +3 -0
  52. package/dist/types/device/components/fonts/fonts.d.ts.map +1 -0
  53. package/dist/types/device/components/hardware/hardware.d.ts +2 -0
  54. package/dist/types/device/components/hardware/hardware.d.ts.map +1 -0
  55. package/dist/types/device/components/index.d.ts +15 -0
  56. package/dist/types/device/components/index.d.ts.map +1 -0
  57. package/dist/types/device/components/locales/locales.d.ts +2 -0
  58. package/dist/types/device/components/locales/locales.d.ts.map +1 -0
  59. package/dist/types/device/components/math/math.d.ts +2 -0
  60. package/dist/types/device/components/math/math.d.ts.map +1 -0
  61. package/dist/types/device/components/permissions/permissions.d.ts +3 -0
  62. package/dist/types/device/components/permissions/permissions.d.ts.map +1 -0
  63. package/dist/types/device/components/plugins/plugins.d.ts +3 -0
  64. package/dist/types/device/components/plugins/plugins.d.ts.map +1 -0
  65. package/dist/types/device/components/screen/screen.d.ts +2 -0
  66. package/dist/types/device/components/screen/screen.d.ts.map +1 -0
  67. package/dist/types/device/components/screen/screenResolution.d.ts +16 -0
  68. package/dist/types/device/components/screen/screenResolution.d.ts.map +1 -0
  69. package/dist/types/device/components/system/browser.d.ts +22 -0
  70. package/dist/types/device/components/system/browser.d.ts.map +1 -0
  71. package/dist/types/device/components/system/emoji.d.ts +2 -0
  72. package/dist/types/device/components/system/emoji.d.ts.map +1 -0
  73. package/dist/types/device/components/system/system.d.ts +2 -0
  74. package/dist/types/device/components/system/system.d.ts.map +1 -0
  75. package/dist/types/device/components/webgl/imageHash.d.ts +3 -0
  76. package/dist/types/device/components/webgl/imageHash.d.ts.map +1 -0
  77. package/dist/types/device/components/webgl/webgl.d.ts +54 -0
  78. package/dist/types/device/components/webgl/webgl.d.ts.map +1 -0
  79. package/dist/types/device/factory.d.ts +26 -0
  80. package/dist/types/device/factory.d.ts.map +1 -0
  81. package/dist/types/device/index.d.ts +61 -0
  82. package/dist/types/device/index.d.ts.map +1 -0
  83. package/dist/types/device/modules/bot.d.ts +9 -0
  84. package/dist/types/device/modules/bot.d.ts.map +1 -0
  85. package/dist/types/device/modules/browserDetails.d.ts +6 -0
  86. package/dist/types/device/modules/browserDetails.d.ts.map +1 -0
  87. package/dist/types/device/modules/device/analyze-data.d.ts +7 -0
  88. package/dist/types/device/modules/device/analyze-data.d.ts.map +1 -0
  89. package/dist/types/device/modules/device/gather-data.d.ts +6 -0
  90. package/dist/types/device/modules/device/gather-data.d.ts.map +1 -0
  91. package/dist/types/device/modules/device/helpers.d.ts +9 -0
  92. package/dist/types/device/modules/device/helpers.d.ts.map +1 -0
  93. package/dist/types/device/modules/device/index.d.ts +3 -0
  94. package/dist/types/device/modules/device/index.d.ts.map +1 -0
  95. package/dist/types/device/modules/fp.d.ts +14 -0
  96. package/dist/types/device/modules/fp.d.ts.map +1 -0
  97. package/dist/types/device/modules/incognito.d.ts +7 -0
  98. package/dist/types/device/modules/incognito.d.ts.map +1 -0
  99. package/dist/types/device/modules/options.d.ts +12 -0
  100. package/dist/types/device/modules/options.d.ts.map +1 -0
  101. package/dist/types/device/modules/os.d.ts +3 -0
  102. package/dist/types/device/modules/os.d.ts.map +1 -0
  103. package/dist/types/device/modules/tor.d.ts +4 -0
  104. package/dist/types/device/modules/tor.d.ts.map +1 -0
  105. package/dist/types/device/utils/async.d.ts +33 -0
  106. package/dist/types/device/utils/async.d.ts.map +1 -0
  107. package/dist/types/device/utils/browser_.d.ts +103 -0
  108. package/dist/types/device/utils/browser_.d.ts.map +1 -0
  109. package/dist/types/device/utils/commonPixels.d.ts +2 -0
  110. package/dist/types/device/utils/commonPixels.d.ts.map +1 -0
  111. package/dist/types/device/utils/data.d.ts +33 -0
  112. package/dist/types/device/utils/data.d.ts.map +1 -0
  113. package/dist/types/device/utils/dom.d.ts +26 -0
  114. package/dist/types/device/utils/dom.d.ts.map +1 -0
  115. package/dist/types/device/utils/ephemeralIFrame.d.ts +5 -0
  116. package/dist/types/device/utils/ephemeralIFrame.d.ts.map +1 -0
  117. package/dist/types/device/utils/getMostFrequent.d.ts +6 -0
  118. package/dist/types/device/utils/getMostFrequent.d.ts.map +1 -0
  119. package/dist/types/device/utils/hash.d.ts +6 -0
  120. package/dist/types/device/utils/hash.d.ts.map +1 -0
  121. package/dist/types/device/utils/misc.d.ts +7 -0
  122. package/dist/types/device/utils/misc.d.ts.map +1 -0
  123. package/dist/types/device/utils/raceAll.d.ts +9 -0
  124. package/dist/types/device/utils/raceAll.d.ts.map +1 -0
  125. package/dist/types/index.d.ts +4 -0
  126. package/dist/types/index.d.ts.map +1 -0
  127. package/package.json +52 -0
  128. package/src/behaviour/index.ts +279 -0
  129. package/src/client/index.ts +132 -0
  130. package/src/core/constants.ts +4 -0
  131. package/src/core/dto/bot-detector.dto.ts +32 -0
  132. package/src/core/dto/device-detector.dto.ts +67 -0
  133. package/src/core/dto/fingerprint.dto.ts +38 -0
  134. package/src/core/dto/ginger.dto.ts +89 -0
  135. package/src/core/dto/incognito-detector.dto.ts +2 -0
  136. package/src/core/dto/index.ts +18 -0
  137. package/src/core/dto/metrics.dto.ts +20 -0
  138. package/src/core/dto/os-detector.dto.ts +5 -0
  139. package/src/core/dto/tor-detector.dto.ts +4 -0
  140. package/src/core/helpers.ts +33 -0
  141. package/src/core/http/httpClient.ts +52 -0
  142. package/src/core/http/request.ts +32 -0
  143. package/src/core/index.ts +5 -0
  144. package/src/core/util/error.ts +40 -0
  145. package/src/core/util/generate-requestid.ts +63 -0
  146. package/src/device/components/audio/audio.ts +58 -0
  147. package/src/device/components/canvas/canvas.ts +88 -0
  148. package/src/device/components/extra/extra.ts +581 -0
  149. package/src/device/components/fonts/fonts.ts +143 -0
  150. package/src/device/components/hardware/hardware.ts +66 -0
  151. package/src/device/components/index.ts +14 -0
  152. package/src/device/components/locales/locales.ts +21 -0
  153. package/src/device/components/math/math.ts +39 -0
  154. package/src/device/components/permissions/permissions.ts +60 -0
  155. package/src/device/components/plugins/plugins.ts +22 -0
  156. package/src/device/components/screen/screen.ts +13 -0
  157. package/src/device/components/screen/screenResolution.ts +45 -0
  158. package/src/device/components/system/browser.ts +838 -0
  159. package/src/device/components/system/emoji.ts +134 -0
  160. package/src/device/components/system/system.ts +76 -0
  161. package/src/device/components/webgl/imageHash.ts +144 -0
  162. package/src/device/components/webgl/webgl.ts +302 -0
  163. package/src/device/factory.ts +54 -0
  164. package/src/device/index.ts +60 -0
  165. package/src/device/modules/bot.ts +25 -0
  166. package/src/device/modules/browserDetails.ts +11 -0
  167. package/src/device/modules/device/analyze-data.ts +150 -0
  168. package/src/device/modules/device/gather-data.ts +92 -0
  169. package/src/device/modules/device/helpers.ts +123 -0
  170. package/src/device/modules/device/index.ts +64 -0
  171. package/src/device/modules/fp.ts +138 -0
  172. package/src/device/modules/incognito.ts +253 -0
  173. package/src/device/modules/options.ts +17 -0
  174. package/src/device/modules/os.ts +15 -0
  175. package/src/device/modules/tor.ts +41 -0
  176. package/src/device/utils/async.ts +106 -0
  177. package/src/device/utils/browser_.ts +347 -0
  178. package/src/device/utils/commonPixels.ts +38 -0
  179. package/src/device/utils/data.ts +161 -0
  180. package/src/device/utils/dom.ts +148 -0
  181. package/src/device/utils/ephemeralIFrame.ts +35 -0
  182. package/src/device/utils/getMostFrequent.ts +39 -0
  183. package/src/device/utils/hash.ts +202 -0
  184. package/src/device/utils/misc.ts +18 -0
  185. package/src/device/utils/raceAll.ts +19 -0
  186. 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);