@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,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
|
+
}
|