@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,838 @@
1
+ // Enhanced browser detection with multiple techniques
2
+ type BrowserDetails = {
3
+ name: string;
4
+ version: string;
5
+ confidence: number; // 0-100 score of detection confidence
6
+ engine: string;
7
+ };
8
+
9
+ /**
10
+ * Detect browser using multiple techniques to be resilient against spoofing and user agent changes
11
+ */
12
+ export function getBrowserName(): string {
13
+ const details = detectBrowser();
14
+ return details.name;
15
+ }
16
+
17
+ /**
18
+ * Enhanced browser detection that uses multiple techniques to identify the browser
19
+ * with high confidence even when user agent is spoofed or in mobile simulation mode
20
+ */
21
+ export function detectBrowser(): BrowserDetails {
22
+ // Default result with low confidence
23
+ const result: BrowserDetails = {
24
+ name: "Unknown",
25
+ version: "Unknown",
26
+ confidence: 0,
27
+ engine: "Unknown",
28
+ };
29
+
30
+ // Collection of detection results with confidence scores
31
+ const detections: { name: string; confidence: number }[] = [];
32
+
33
+ // First check for Edge in user agent (this is a fast check before other more intensive checks)
34
+ const ua = navigator.userAgent.toLowerCase();
35
+ if (ua.indexOf("edg/") !== -1 || ua.indexOf("edge/") !== -1) {
36
+ detections.push({ name: "Edge", confidence: 75 });
37
+ }
38
+
39
+ // 1. Engine detection based on error messages (more reliable than UA)
40
+ const engineSignatures = getEngineSignatures();
41
+ detectByEngineSignatures(detections, engineSignatures);
42
+
43
+ // 2. Feature detection for specific browsers
44
+ detectByFeatures(detections);
45
+
46
+ // 3. Protocol handler detection
47
+ detectByProtocolHandlers(detections);
48
+
49
+ // 4. CSS property detection
50
+ detectByCssProperties(detections);
51
+
52
+ // 5. Performance timing
53
+ detectByPerformance(detections);
54
+
55
+ // 6. JavaScript behavior patterns
56
+ detectByJsBehavior(detections);
57
+
58
+ // 7. Edge-specific detection - separate function to better differentiate Edge from Chrome
59
+ detectEdgeBrowser(detections);
60
+
61
+ // 8. Chrome-specific detection - enhanced Chrome detection
62
+ detectChromeBrowser(detections);
63
+
64
+ // 9. Brave-specific detection - get specific Brave signals
65
+ detectBraveBrowser(detections);
66
+
67
+ // Combine all detection results and calculate final browser with confidence
68
+ if (detections.length > 0) {
69
+ // Group by browser name and sum confidence
70
+ const grouped = detections.reduce((acc, curr) => {
71
+ acc[curr.name] = (acc[curr.name] || 0) + curr.confidence;
72
+ return acc;
73
+ }, {} as Record<string, number>);
74
+
75
+ // Find the browser with highest confidence
76
+ let maxConfidence = 0;
77
+ let detectedBrowser = "Unknown";
78
+
79
+ for (const [browser, confidence] of Object.entries(grouped)) {
80
+ if (confidence > maxConfidence) {
81
+ maxConfidence = confidence;
82
+ detectedBrowser = browser;
83
+ }
84
+ }
85
+
86
+ // Extra Edge check - check if incognito detection can confirm this is Edge
87
+ if (detectedBrowser === "Chrome" && isActuallyEdge()) {
88
+ detectedBrowser = "Edge";
89
+ }
90
+
91
+ result.name = detectedBrowser;
92
+ result.confidence = Math.min(100, maxConfidence);
93
+
94
+ // Set engine based on browser
95
+ if (["Chrome", "Edge", "Opera", "Brave"].includes(detectedBrowser)) {
96
+ result.engine = "Blink";
97
+ } else if (detectedBrowser === "Firefox") {
98
+ result.engine = "Gecko";
99
+ } else if (detectedBrowser === "Safari") {
100
+ result.engine = "WebKit";
101
+ }
102
+
103
+ // Try to extract version
104
+ result.version = getBrowserVersion(detectedBrowser);
105
+ }
106
+
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * Special detection for Brave browser
112
+ */
113
+ function detectBraveBrowser(
114
+ detections: { name: string; confidence: number }[]
115
+ ): void {
116
+ try {
117
+ // Check for Brave API directly
118
+ if ((navigator as any).brave) {
119
+ detections.push({ name: "Brave", confidence: 95 });
120
+ return;
121
+ }
122
+
123
+ // Check for specific Brave patterns in the user agent
124
+ const ua = navigator.userAgent;
125
+ if (ua.includes("Brave") || ua.includes("brave")) {
126
+ detections.push({ name: "Brave", confidence: 90 });
127
+ return;
128
+ }
129
+
130
+ // Brave specific behavior checks
131
+ // 1. Brave removes certain tracking headers
132
+ // 2. Brave blocks fingerprinting by default
133
+ // 3. Brave has specific privacy features
134
+
135
+ // Check for Chrome without Google Chrome-specific features
136
+ if (
137
+ typeof (window as any).chrome !== "undefined" &&
138
+ (window as any).chrome.runtime &&
139
+ !(window as any).google
140
+ ) {
141
+ // Additional check for absence of Chrome-specific features
142
+ const hasNoChromeFeatures =
143
+ !("google" in window) &&
144
+ !(window as any).chrome.webstore &&
145
+ typeof (navigator as any).brave !== "undefined";
146
+
147
+ if (hasNoChromeFeatures) {
148
+ detections.push({ name: "Brave", confidence: 60 });
149
+ }
150
+ }
151
+ } catch (e) {
152
+ // Ignore detection errors
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Specific check to determine if a browser is Edge by using the same techniques
158
+ * that incognito detection uses, which seems to report Edge correctly
159
+ */
160
+ function isActuallyEdge(): boolean {
161
+ try {
162
+ // Check user agent for Edge keywords
163
+ const ua = navigator.userAgent.toLowerCase();
164
+ if (ua.indexOf("edg/") !== -1 || ua.indexOf("edge/") !== -1) {
165
+ return true;
166
+ }
167
+
168
+ // Check if any MS-specific APIs or DOM elements are available
169
+ if (
170
+ (window as any).msCredentials ||
171
+ (document as any).documentMode ||
172
+ typeof (window as any).MSInputMethodContext !== "undefined" ||
173
+ typeof (navigator as any).msLaunchUri === "function"
174
+ ) {
175
+ return true;
176
+ }
177
+
178
+ // Check Edge-specific combination of features
179
+ if (
180
+ typeof (window as any).chrome !== "undefined" &&
181
+ (window as any).chrome.runtime &&
182
+ !(window as any).chrome.webstore &&
183
+ !(window as any).opr &&
184
+ !(window as any).opera
185
+ ) {
186
+ // Additional Edge-specific check
187
+ try {
188
+ // Edge typically does not have the Chrome PDF viewer plugin
189
+ const hasChromePdfViewer = Array.from(navigator.plugins).some(
190
+ (plugin) => plugin.name === "Chrome PDF Viewer"
191
+ );
192
+
193
+ // Check for unique Edge plugin patterns
194
+ const hasEdgePluginPattern = Array.from(navigator.plugins).some(
195
+ (plugin) => plugin.name.indexOf("Edge") !== -1
196
+ );
197
+
198
+ if (!hasChromePdfViewer || hasEdgePluginPattern) {
199
+ return true;
200
+ }
201
+ } catch (e) {
202
+ // Plugin check failed
203
+ }
204
+ }
205
+
206
+ return false;
207
+ } catch (e) {
208
+ return false;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Gets browser error behavior signature from various engine-specific behaviors
214
+ */
215
+ function getEngineSignatures(): {
216
+ toFixedErrorLength: number;
217
+ functionToStringLength: number;
218
+ } {
219
+ // Error message length from toFixed with negative values
220
+ let toFixedErrorLength = 0;
221
+ try {
222
+ const neg = parseInt("-1");
223
+ neg.toFixed(neg);
224
+ } catch (e: any) {
225
+ toFixedErrorLength = e.message.length;
226
+ }
227
+
228
+ // Function.toString() behavior - varies by browser
229
+ let functionToStringLength = 0;
230
+ try {
231
+ functionToStringLength = Function.prototype.toString.call(Function).length;
232
+ } catch (e) {
233
+ // Ignore error
234
+ }
235
+
236
+ return {
237
+ toFixedErrorLength,
238
+ functionToStringLength,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Detects browsers based on engine-specific error message patterns
244
+ */
245
+ function detectByEngineSignatures(
246
+ detections: { name: string; confidence: number }[],
247
+ signatures: { toFixedErrorLength: number; functionToStringLength: number }
248
+ ): void {
249
+ // Safari typically has error message length of 44
250
+ if (signatures.toFixedErrorLength === 44) {
251
+ detections.push({ name: "Safari", confidence: 40 });
252
+ }
253
+
254
+ // Chrome typically has error message length of 51
255
+ else if (signatures.toFixedErrorLength === 51) {
256
+ detections.push({ name: "Chrome", confidence: 30 }); // Increased from 15
257
+ }
258
+
259
+ // Firefox typically has error message length of 25
260
+ else if (signatures.toFixedErrorLength === 25) {
261
+ detections.push({ name: "Firefox", confidence: 30 });
262
+ }
263
+
264
+ // Function.toString length can help differentiate browsers too
265
+ if (
266
+ signatures.functionToStringLength > 30 &&
267
+ signatures.functionToStringLength < 40
268
+ ) {
269
+ detections.push({ name: "Firefox", confidence: 10 });
270
+ } else if (signatures.functionToStringLength > 40) {
271
+ detections.push({ name: "Chrome", confidence: 15 }); // Increased from 5
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Detects browser by checking for browser-specific features and APIs
277
+ */
278
+ function detectByFeatures(
279
+ detections: { name: string; confidence: number }[]
280
+ ): void {
281
+ // Brave detection
282
+ if (
283
+ (navigator as any).brave &&
284
+ typeof (navigator as any).brave.isBrave === "function"
285
+ ) {
286
+ detections.push({ name: "Brave", confidence: 90 });
287
+ }
288
+
289
+ // Check for Chrome-specific APIs
290
+ if (
291
+ typeof (window as any).chrome !== "undefined" &&
292
+ (window as any).chrome.app &&
293
+ (window as any).chrome.runtime
294
+ ) {
295
+ // First check if it's not actually Edge
296
+ const ua = navigator.userAgent.toLowerCase();
297
+ if (ua.indexOf("edg/") === -1 && ua.indexOf("edge/") === -1) {
298
+ if ((navigator as any).brave === undefined) {
299
+ detections.push({ name: "Chrome", confidence: 40 }); // Increased from 25
300
+ }
301
+ } else {
302
+ // This is more likely Edge than Chrome
303
+ detections.push({ name: "Edge", confidence: 40 });
304
+ }
305
+ }
306
+
307
+ // Additional Chrome-specific feature detection
308
+ if (
309
+ (window as any).chrome &&
310
+ (window as any).chrome.csi &&
311
+ (window as any).chrome.loadTimes &&
312
+ !(window as any).opr &&
313
+ !(window as any).opera &&
314
+ !(
315
+ (navigator as any).brave &&
316
+ typeof (navigator as any).brave.isBrave === "function"
317
+ )
318
+ ) {
319
+ // These features are very specific to Chrome and not present in most other Chromium browsers
320
+ detections.push({ name: "Chrome", confidence: 60 });
321
+ }
322
+
323
+ // Firefox-specific objects
324
+ if (
325
+ typeof (window as any).InstallTrigger !== "undefined" ||
326
+ typeof (window as any).sidebar !== "undefined"
327
+ ) {
328
+ detections.push({ name: "Firefox", confidence: 70 });
329
+ }
330
+
331
+ // Safari detection
332
+ if (
333
+ /constructor/i.test((window as any).HTMLElement as any) ||
334
+ (function (p) {
335
+ return p.toString() === "[object SafariRemoteNotification]";
336
+ })(
337
+ !(typeof (window as any).safari === "undefined") &&
338
+ (window as any).safari.pushNotification
339
+ )
340
+ ) {
341
+ detections.push({ name: "Safari", confidence: 70 });
342
+ }
343
+
344
+ // Basic Edge detection - more detailed in detectEdgeBrowser function
345
+ if ((window as any).msCredentials || (document as any).documentMode) {
346
+ detections.push({ name: "Edge", confidence: 60 }); // Increased confidence
347
+ }
348
+
349
+ // Opera detection
350
+ if ((window as any).opr || (window as any).opera) {
351
+ detections.push({ name: "Opera", confidence: 60 });
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Detects browser by checking for browser-specific protocol handlers
357
+ */
358
+ function detectByProtocolHandlers(
359
+ detections: { name: string; confidence: number }[]
360
+ ): void {
361
+ // Chrome/Chromium protocols
362
+ if (typeof navigator.registerProtocolHandler === "function") {
363
+ try {
364
+ if ("chrome" in (window as any)) {
365
+ // Only consider chrome protocol if not Edge
366
+ const ua = navigator.userAgent.toLowerCase();
367
+ if (ua.indexOf("edg/") === -1 && ua.indexOf("edge/") === -1) {
368
+ detections.push({ name: "Chrome", confidence: 25 }); // Increased from 15
369
+ }
370
+ }
371
+
372
+ // Edge has ms-* protocols available
373
+ if (
374
+ "ms-access" in (navigator as any) ||
375
+ "ms-browser-extension" in (navigator as any) ||
376
+ "ms-calculator" in (navigator as any) ||
377
+ "ms-drive-to" in (navigator as any) ||
378
+ "ms-excel" in (navigator as any) ||
379
+ "ms-gamebarservices" in (navigator as any) ||
380
+ "ms-search" in (navigator as any) ||
381
+ "ms-word" in (navigator as any)
382
+ ) {
383
+ detections.push({ name: "Edge", confidence: 80 }); // Increased confidence
384
+ }
385
+
386
+ // Opera has specific protocol handlers
387
+ if ("opr:" in navigator.plugins || "opera:" in navigator.plugins) {
388
+ detections.push({ name: "Opera", confidence: 25 });
389
+ }
390
+ } catch (e) {
391
+ // Protocol detection failed
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Detects browser by checking for browser-specific CSS properties
398
+ */
399
+ function detectByCssProperties(
400
+ detections: { name: string; confidence: number }[]
401
+ ): void {
402
+ const docStyle = window.getComputedStyle(document.documentElement);
403
+
404
+ // Webkit-specific properties
405
+ if (
406
+ docStyle.getPropertyValue("--apple-trailing-word") !== "" ||
407
+ docStyle.getPropertyValue("-webkit-app-region") !== ""
408
+ ) {
409
+ detections.push({ name: "Safari", confidence: 20 });
410
+ }
411
+
412
+ // Firefox-specific properties
413
+ if (
414
+ docStyle.getPropertyValue("-moz-context-properties") !== "" ||
415
+ docStyle.getPropertyValue("-moz-user-focus") !== ""
416
+ ) {
417
+ detections.push({ name: "Firefox", confidence: 20 });
418
+ }
419
+
420
+ // Edge-specific CSS properties
421
+ if (
422
+ docStyle.getPropertyValue("-ms-ime-align") !== "" ||
423
+ docStyle.getPropertyValue("-ms-flow-from") !== ""
424
+ ) {
425
+ detections.push({ name: "Edge", confidence: 60 }); // Increased confidence
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Detects browser by analyzing performance characteristics
431
+ */
432
+ function detectByPerformance(
433
+ detections: { name: string; confidence: number }[]
434
+ ): void {
435
+ // Chrome/Chromium-based browsers expose memory info
436
+ if (
437
+ (performance as any).memory &&
438
+ (performance as any).memory.jsHeapSizeLimit
439
+ ) {
440
+ // Different Chromium browsers have different heap size limits
441
+ const heapLimit = (performance as any).memory.jsHeapSizeLimit;
442
+
443
+ if (heapLimit > 2000000000) {
444
+ // Check if it's not actually Edge before assuming Chrome
445
+ const ua = navigator.userAgent.toLowerCase();
446
+ if (ua.indexOf("edg/") === -1 && ua.indexOf("edge/") === -1) {
447
+ detections.push({ name: "Chrome", confidence: 20 }); // Increased from 10
448
+ }
449
+ }
450
+ }
451
+
452
+ // Chrome-specific performance timing features
453
+ if (
454
+ typeof (window as any).chrome !== "undefined" &&
455
+ typeof (window as any).chrome.loadTimes === "function"
456
+ ) {
457
+ try {
458
+ const chromeLoad = (window as any).chrome.loadTimes();
459
+ if (
460
+ chromeLoad &&
461
+ typeof chromeLoad.firstPaintTime === "number" &&
462
+ typeof chromeLoad.requestTime === "number"
463
+ ) {
464
+ detections.push({ name: "Chrome", confidence: 30 });
465
+ }
466
+ } catch (e) {
467
+ // Performance timing check failed
468
+ }
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Detects browser by JavaScript behavior patterns
474
+ */
475
+ function detectByJsBehavior(
476
+ detections: { name: string; confidence: number }[]
477
+ ): void {
478
+ // Firefox specific behavior with error stack
479
+ try {
480
+ throw new Error();
481
+ } catch (err: any) {
482
+ if (err.stack && err.stack.indexOf("()@") >= 0) {
483
+ detections.push({ name: "Firefox", confidence: 15 });
484
+ }
485
+
486
+ // Chrome-specific stack trace format
487
+ if (err.stack && err.stack.indexOf("at new") >= 0) {
488
+ // Check if it's not actually Edge
489
+ const ua = navigator.userAgent.toLowerCase();
490
+ if (ua.indexOf("edg/") === -1 && ua.indexOf("edge/") === -1) {
491
+ detections.push({ name: "Chrome", confidence: 20 }); // Increased from 10
492
+ }
493
+ }
494
+
495
+ // Safari-specific stack trace format
496
+ if (
497
+ err.stack &&
498
+ err.stack.indexOf("@") === -1 &&
499
+ err.stack.indexOf("at ") === -1
500
+ ) {
501
+ detections.push({ name: "Safari", confidence: 15 });
502
+ }
503
+ }
504
+
505
+ // Brave shields detection
506
+ if (
507
+ document.createElement("canvas").toDataURL().length > 15 &&
508
+ (navigator as any).brave === undefined &&
509
+ typeof (window as any).chrome !== "undefined"
510
+ ) {
511
+ // Try to detect if Brave shields are on
512
+ const img = new Image();
513
+ let loadCount = 0;
514
+
515
+ img.onload = () => {
516
+ loadCount++;
517
+ if (loadCount === 0) {
518
+ detections.push({ name: "Brave", confidence: 20 });
519
+ }
520
+ };
521
+
522
+ img.onerror = () => {
523
+ detections.push({ name: "Brave", confidence: 10 });
524
+ };
525
+
526
+ // Try loading a tracking pixel
527
+ img.src = "https://www.facebook.com/tr?id=1234567890&ev=PageView";
528
+ }
529
+
530
+ // Additional Chrome-specific behavior check (Chrome DevTools Protocol)
531
+ try {
532
+ // Check if __JQUERY_OBJECT__ is defined (used in Chrome Developer Tools)
533
+ // This will throw an error in most other browsers
534
+ const hasDevTools = !!(window as any).__JQUERY_OBJECT__;
535
+ if (hasDevTools) {
536
+ detections.push({ name: "Chrome", confidence: 15 });
537
+ }
538
+ } catch (e) {
539
+ // DevTools check failed
540
+ }
541
+
542
+ // Check Chrome user agent pattern more directly when combined with other Chrome features
543
+ if (
544
+ navigator.userAgent.indexOf("Chrome") !== -1 &&
545
+ navigator.userAgent.indexOf("Edg") === -1 &&
546
+ navigator.userAgent.indexOf("OPR") === -1 &&
547
+ navigator.userAgent.indexOf("Brave") === -1 &&
548
+ typeof (window as any).chrome !== "undefined" &&
549
+ (window as any).chrome.runtime
550
+ ) {
551
+ detections.push({ name: "Chrome", confidence: 35 });
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Specialized Edge browser detection to differentiate it from Chrome
557
+ */
558
+ function detectEdgeBrowser(
559
+ detections: { name: string; confidence: number }[]
560
+ ): void {
561
+ // Check for Edge in User Agent as a supplementary signal (still useful)
562
+ const ua = navigator.userAgent.toLowerCase();
563
+ if (ua.indexOf("edg/") !== -1 || ua.indexOf("edge/") !== -1) {
564
+ detections.push({ name: "Edge", confidence: 80 }); // Increased from 30 to 80
565
+ }
566
+
567
+ // Check for Edge-specific objects
568
+ try {
569
+ // Check for CSS.supports method with -ms vendor prefix
570
+ if (CSS.supports("-ms-ime-align", "auto")) {
571
+ detections.push({ name: "Edge", confidence: 70 }); // Increased confidence
572
+ }
573
+ } catch (e) {
574
+ // CSS.supports might not be available
575
+ }
576
+
577
+ // Check for Edge-specific behaviors
578
+ try {
579
+ // Test if msLaunchUri is available (Edge-specific)
580
+ if (typeof (navigator as any).msLaunchUri === "function") {
581
+ detections.push({ name: "Edge", confidence: 85 }); // Increased confidence
582
+ }
583
+
584
+ // Test if MS-specific APIs are available
585
+ if (typeof (window as any).MSInputMethodContext !== "undefined") {
586
+ detections.push({ name: "Edge", confidence: 80 }); // Increased confidence
587
+ }
588
+
589
+ // Check for Edge-specific storage behavior
590
+ if ((document as any).documentMode || /edge/i.test(navigator.userAgent)) {
591
+ detections.push({ name: "Edge", confidence: 75 }); // Increased confidence
592
+ }
593
+
594
+ // Check window.chrome properties patterns specific to Edge
595
+ if (
596
+ typeof (window as any).chrome !== "undefined" &&
597
+ (window as any).chrome.runtime &&
598
+ !(window as any).chrome.webstore
599
+ ) {
600
+ // This combination is more common in Edge than Chrome
601
+ detections.push({ name: "Edge", confidence: 60 }); // Increased confidence
602
+ }
603
+ } catch (e) {
604
+ // Ignore errors in detection
605
+ }
606
+
607
+ // Get text rendering metrics - Edge and Chrome render text differently
608
+ try {
609
+ const canvas = document.createElement("canvas");
610
+ const ctx = canvas.getContext("2d");
611
+ if (ctx) {
612
+ canvas.width = 200;
613
+ canvas.height = 50;
614
+ ctx.font = "20px Arial";
615
+ ctx.textBaseline = "top";
616
+ ctx.fillText("EdgeBrowserTest", 0, 0);
617
+
618
+ // Get image data to analyze text rendering
619
+ const imageData = ctx.getImageData(
620
+ 0,
621
+ 0,
622
+ canvas.width,
623
+ canvas.height
624
+ ).data;
625
+
626
+ // Create a simple hash of the first few pixels
627
+ let hash = 0;
628
+ for (let i = 0; i < 400; i += 4) {
629
+ hash = (hash << 5) - hash + imageData[i];
630
+ }
631
+
632
+ // Edge typically has different text rendering than Chrome
633
+ if (hash < 0) {
634
+ // Just an example threshold, would need calibration
635
+ detections.push({ name: "Edge", confidence: 50 }); // Increased confidence
636
+ }
637
+ }
638
+ } catch (e) {
639
+ // Canvas access might be restricted
640
+ }
641
+ }
642
+
643
+ /**
644
+ * Enhanced detection for Chrome browser
645
+ */
646
+ function detectChromeBrowser(
647
+ detections: { name: string; confidence: number }[]
648
+ ): void {
649
+ // Complex feature combination check specific to Chrome but not other Chromium browsers
650
+ if (
651
+ // Must have Chrome in user agent but not be Edge or Opera
652
+ navigator.userAgent.indexOf("Chrome") !== -1 &&
653
+ navigator.userAgent.indexOf("Edg") === -1 &&
654
+ navigator.userAgent.indexOf("OPR") === -1 &&
655
+ // Must have chrome object with specific properties
656
+ typeof (window as any).chrome !== "undefined" &&
657
+ (window as any).chrome.runtime &&
658
+ // Check for Chrome-exclusive features
659
+ typeof (window as any).chrome.loadTimes === "function" &&
660
+ typeof (window as any).chrome.csi === "function" &&
661
+ // Ensure not Brave
662
+ typeof (navigator as any).brave === "undefined"
663
+ ) {
664
+ detections.push({ name: "Chrome", confidence: 75 });
665
+ }
666
+
667
+ // Check for Chrome PDF viewer plugin (generally not present in other Chromium browsers)
668
+ try {
669
+ const hasPdfViewer = Array.from(navigator.plugins).some(
670
+ (plugin) => plugin.name === "Chrome PDF Viewer"
671
+ );
672
+
673
+ if (
674
+ hasPdfViewer &&
675
+ navigator.userAgent.indexOf("Chrome") !== -1 &&
676
+ navigator.userAgent.indexOf("Edg") === -1
677
+ ) {
678
+ detections.push({ name: "Chrome", confidence: 60 });
679
+ }
680
+ } catch (e) {
681
+ // Plugin check failed
682
+ }
683
+
684
+ // Check Chrome version consistency in User Agent
685
+ // This helps differentiate from browsers that modify the Chrome version string
686
+ try {
687
+ const chromeMatch = navigator.userAgent.match(/Chrome\/([0-9.]+)/);
688
+ if (chromeMatch) {
689
+ const chromeVersion = chromeMatch[1];
690
+
691
+ // If app version contains the Chrome version and doesn't have Edg
692
+ if (
693
+ navigator.appVersion.indexOf(chromeVersion) !== -1 &&
694
+ navigator.userAgent.indexOf("Edg") === -1 &&
695
+ navigator.userAgent.indexOf("OPR") === -1
696
+ ) {
697
+ detections.push({ name: "Chrome", confidence: 30 });
698
+ }
699
+ }
700
+ } catch (e) {
701
+ // Version check failed
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Attempts to get the browser version
707
+ */
708
+ function getBrowserVersion(browserName: string): string {
709
+ const userAgent = navigator.userAgent;
710
+ let version = "Unknown";
711
+
712
+ try {
713
+ if (browserName === "Chrome") {
714
+ const match = userAgent.match(/Chrome\/([0-9.]+)/);
715
+ if (match) {
716
+ version = match[1];
717
+ }
718
+ } else if (browserName === "Firefox") {
719
+ const match = userAgent.match(/Firefox\/([0-9.]+)/);
720
+ if (match) {
721
+ version = match[1];
722
+ }
723
+ } else if (browserName === "Safari") {
724
+ const match = userAgent.match(/Version\/([0-9.]+)/);
725
+ if (match) {
726
+ version = match[1];
727
+ }
728
+ } else if (browserName === "Edge") {
729
+ // Modern Edge (Chromium-based)
730
+ const edgeMatch = userAgent.match(/Edg(?:e)?\/([0-9.]+)/);
731
+ if (edgeMatch) {
732
+ version = edgeMatch[1];
733
+ }
734
+ } else if (browserName === "Opera") {
735
+ const match = userAgent.match(/OPR\/([0-9.]+)/);
736
+ if (match) {
737
+ version = match[1];
738
+ }
739
+ } else if (browserName === "Brave") {
740
+ // First try to get version through Brave API if available
741
+ if ((navigator as any).brave && (navigator as any).brave.version) {
742
+ version = (navigator as any).brave.version;
743
+ } else {
744
+ // Otherwise extract from user agent like Chrome
745
+ // Brave uses the same Chrome version string but with Brave/[version]
746
+ const braveMatch = userAgent.match(/Brave\/([0-9.]+)/);
747
+ if (braveMatch) {
748
+ version = braveMatch[1];
749
+ } else {
750
+ // Fall back to Chrome version
751
+ const chromeMatch = userAgent.match(/Chrome\/([0-9.]+)/);
752
+ if (chromeMatch) {
753
+ version = chromeMatch[1];
754
+ }
755
+ }
756
+
757
+ // Try to access the Brave browser version via JavaScript API if available
758
+ if (typeof (navigator as any).brave !== "undefined") {
759
+ try {
760
+ // Request brave version info
761
+ (navigator as any).brave.isBrave().then((isBrave: boolean) => {
762
+ if (isBrave) {
763
+ // Remove console log
764
+ }
765
+ });
766
+ } catch (e) {
767
+ // API access failed
768
+ }
769
+ }
770
+ }
771
+ }
772
+ } catch (e) {
773
+ // Keep default 'Unknown' value
774
+ }
775
+
776
+ return version;
777
+ }
778
+ interface BrowserResult {
779
+ name: string;
780
+ version: string;
781
+ }
782
+
783
+ export function getBrowser(): BrowserResult {
784
+ if (typeof navigator === "undefined") {
785
+ return {
786
+ name: "unknown",
787
+ version: "unknown",
788
+ };
789
+ }
790
+ const ua = navigator.userAgent;
791
+ // Define some regular expressions to match different browsers and their versions
792
+ const regexes = [
793
+ // Edge
794
+ /(?<name>Edge|Edg)\/(?<version>\d+(?:\.\d+)?)/,
795
+ // Chrome, Chromium, Opera, Vivaldi, Brave, etc.
796
+ /(?<name>(?:Chrome|Chromium|OPR|Opera|Vivaldi|Brave))\/(?<version>\d+(?:\.\d+)?)/,
797
+ // Firefox, Waterfox, etc.
798
+ /(?<name>(?:Firefox|Waterfox|Iceweasel|IceCat))\/(?<version>\d+(?:\.\d+)?)/,
799
+ // Safari, Mobile Safari, etc.
800
+ /(?<name>Safari)\/(?<version>\d+(?:\.\d+)?)/,
801
+ // Internet Explorer, IE Mobile, etc.
802
+ /(?<name>MSIE|Trident|IEMobile).+?(?<version>\d+(?:\.\d+)?)/,
803
+ // Other browsers that use the format "BrowserName/version"
804
+ /(?<name>[A-Za-z]+)\/(?<version>\d+(?:\.\d+)?)/,
805
+ // Samsung internet browser
806
+ /(?<name>SamsungBrowser)\/(?<version>\d+(?:\.\d+)?)/,
807
+ // Samsung browser (Tizen format)
808
+ /(?<name>samsung).*Version\/(?<version>\d+(?:\.\d+)?)/i,
809
+ ];
810
+
811
+ // Define a map for browser name translations
812
+ const browserNameMap: { [key: string]: string } = {
813
+ edg: "Edge",
814
+ opr: "Opera",
815
+ samsung: "SamsungBrowser",
816
+ };
817
+
818
+ // Loop through the regexes and try to find a match
819
+ for (const regex of regexes) {
820
+ const match = ua.match(regex);
821
+ if (match && match.groups) {
822
+ // Translate the browser name if necessary
823
+ const name =
824
+ browserNameMap[match.groups.name.toLowerCase()] || match.groups.name;
825
+ // Return the browser name and version
826
+ return {
827
+ name: name,
828
+ version: match.groups.version,
829
+ };
830
+ }
831
+ }
832
+
833
+ // If no match is found, return unknown
834
+ return {
835
+ name: "unknown",
836
+ version: "unknown",
837
+ };
838
+ }