|
2 | 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | 4 |
|
| 5 | +/** |
| 6 | + * Brand information for User-Agent Client Hints |
| 7 | + */ |
| 8 | +interface UADataValues { |
| 9 | + brand: string; |
| 10 | + version: string; |
| 11 | +} |
| 12 | + |
| 13 | +/** |
| 14 | + * High entropy values for User-Agent Client Hints |
| 15 | + */ |
| 16 | +interface UAHighEntropyValues { |
| 17 | + architecture?: string; |
| 18 | + bitness?: string; |
| 19 | + brands?: UADataValues[]; |
| 20 | + fullVersionList?: UADataValues[]; |
| 21 | + mobile?: boolean; |
| 22 | + model?: string; |
| 23 | + platform?: string; |
| 24 | + platformVersion?: string; |
| 25 | + wow64?: boolean; |
| 26 | + formFactor?: string; |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * NavigatorUAData interface implementation according to User-Agent Client Hints specification |
| 31 | + * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData |
| 32 | + */ |
| 33 | +class NavigatorUAData { |
| 34 | + /** |
| 35 | + * Returns an array of brand information containing the browser name and version |
| 36 | + */ |
| 37 | + get brands(): UADataValues[] { |
| 38 | + // Return realistic brand data based on the user agent |
| 39 | + const ua = internal_navigator_user_agent(); |
| 40 | + |
| 41 | + // Parse version from user agent (simplified approach without regex) |
| 42 | + let version = "119.0.0.0"; |
| 43 | + const chromeIndex = ua.indexOf("Chrome/"); |
| 44 | + if (chromeIndex !== -1) { |
| 45 | + const versionStart = chromeIndex + 7; // Length of "Chrome/" |
| 46 | + const versionEnd = ua.indexOf(" ", versionStart); |
| 47 | + if (versionEnd !== -1) { |
| 48 | + version = ua.substring(versionStart, versionEnd); |
| 49 | + } else { |
| 50 | + version = ua.substring(versionStart); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + return [ |
| 55 | + { brand: "Not A(Brand", version: "99.0.0.0" }, |
| 56 | + { brand: "Google Chrome", version: version }, |
| 57 | + { brand: "Chromium", version: version }, |
| 58 | + ]; |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * Returns true if the user-agent is running on a mobile device |
| 63 | + */ |
| 64 | + get mobile(): boolean { |
| 65 | + const ua = internal_navigator_user_agent(); |
| 66 | + return ua.includes("Mobile") || ua.includes("Android") || ua.includes("iPhone") || |
| 67 | + ua.includes("iPad"); |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * Returns the platform brand the user-agent is running on |
| 72 | + */ |
| 73 | + get platform(): string { |
| 74 | + const ua = internal_navigator_user_agent(); |
| 75 | + |
| 76 | + if (ua.includes("Windows NT")) { |
| 77 | + return "Windows"; |
| 78 | + } |
| 79 | + if (ua.includes("Macintosh")) { |
| 80 | + return "macOS"; |
| 81 | + } |
| 82 | + if (ua.includes("Linux")) { |
| 83 | + return "Linux"; |
| 84 | + } |
| 85 | + if (ua.includes("Android")) { |
| 86 | + return "Android"; |
| 87 | + } |
| 88 | + if (ua.includes("iPhone") || ua.includes("iPad")) { |
| 89 | + return "iOS"; |
| 90 | + } |
| 91 | + return "Unknown"; |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * Returns a Promise that resolves with high entropy values |
| 96 | + */ |
| 97 | + getHighEntropyValues(hints: string[]): Promise<UAHighEntropyValues> { |
| 98 | + const ua = internal_navigator_user_agent(); |
| 99 | + const result: UAHighEntropyValues = {}; |
| 100 | + |
| 101 | + for (const hint of hints) { |
| 102 | + switch (hint) { |
| 103 | + case "architecture": |
| 104 | + if (ua.includes("x86_64") || ua.includes("Win64; x64")) { |
| 105 | + result.architecture = "x86"; |
| 106 | + } else if (ua.includes("aarch64") || ua.includes("ARM64")) { |
| 107 | + result.architecture = "arm"; |
| 108 | + } else { |
| 109 | + result.architecture = "x86"; |
| 110 | + } |
| 111 | + break; |
| 112 | + |
| 113 | + case "bitness": |
| 114 | + result.bitness = ua.includes("Win64") || ua.includes("x86_64") || ua.includes("aarch64") ? |
| 115 | + "64" : |
| 116 | + "32"; |
| 117 | + break; |
| 118 | + |
| 119 | + case "brands": |
| 120 | + result.brands = this.brands; |
| 121 | + break; |
| 122 | + |
| 123 | + case "fullVersionList": |
| 124 | + result.fullVersionList = this.brands; |
| 125 | + break; |
| 126 | + |
| 127 | + case "mobile": |
| 128 | + result.mobile = this.mobile; |
| 129 | + break; |
| 130 | + |
| 131 | + case "model": |
| 132 | + result.model = ""; |
| 133 | + break; |
| 134 | + |
| 135 | + case "platform": |
| 136 | + result.platform = this.platform; |
| 137 | + break; |
| 138 | + |
| 139 | + case "platformVersion": |
| 140 | + if (ua.includes("Windows NT 10.0")) { |
| 141 | + result.platformVersion = "10.0.0"; |
| 142 | + } else if (ua.includes("Windows NT 11.0")) { |
| 143 | + result.platformVersion = "11.0.0"; |
| 144 | + } else if (ua.includes("Mac OS X")) { |
| 145 | + // Find Mac OS X version without regex |
| 146 | + const macIndex = ua.indexOf("Mac OS X "); |
| 147 | + if (macIndex !== -1) { |
| 148 | + const versionStart = macIndex + 9; // Length of "Mac OS X " |
| 149 | + const versionEnd = ua.indexOf(")", versionStart); |
| 150 | + if (versionEnd !== -1) { |
| 151 | + const macVersion = ua.substring(versionStart, versionEnd); |
| 152 | + result.platformVersion = macVersion.replace(/_/g, "."); |
| 153 | + } else { |
| 154 | + result.platformVersion = "10.15.7"; |
| 155 | + } |
| 156 | + } else { |
| 157 | + result.platformVersion = "10.15.7"; |
| 158 | + } |
| 159 | + } else { |
| 160 | + result.platformVersion = "0.0.0"; |
| 161 | + } |
| 162 | + break; |
| 163 | + |
| 164 | + case "wow64": |
| 165 | + result.wow64 = false; |
| 166 | + break; |
| 167 | + |
| 168 | + case "formFactor": |
| 169 | + result.formFactor = this.mobile ? "Mobile" : "Desktop"; |
| 170 | + break; |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + return Promise.resolve(result); |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Returns a JSON representation of the low entropy properties |
| 179 | + */ |
| 180 | + toJSON(): { brands: UADataValues[]; mobile: boolean; platform: string; } { |
| 181 | + return { |
| 182 | + brands: this.brands, |
| 183 | + mobile: this.mobile, |
| 184 | + platform: this.platform, |
| 185 | + }; |
| 186 | + } |
| 187 | +} |
| 188 | + |
5 | 189 | /** |
6 | 190 | * Navigator interface implementation according to HTML specification |
7 | 191 | * Provides information about the user agent and browser environment |
@@ -102,6 +286,13 @@ class AndromedaNavigator { |
102 | 286 | get vendorSub(): string { |
103 | 287 | return ""; |
104 | 288 | } |
| 289 | + |
| 290 | + /** |
| 291 | + * Returns a NavigatorUAData object for User-Agent Client Hints |
| 292 | + */ |
| 293 | + get userAgentData(): NavigatorUAData { |
| 294 | + return new NavigatorUAData(); |
| 295 | + } |
105 | 296 | } |
106 | 297 |
|
107 | 298 | // Attach the navigator object to globalThis |
|
0 commit comments