@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,54 @@
1
+ import { options } from './modules/options';
2
+
3
+ // the component interface is the form of the JSON object the function's promise must return
4
+ export interface componentInterface {
5
+ [key: string]: string | string[] | number | boolean | componentInterface;
6
+ }
7
+
8
+
9
+ // The component function's interface is simply the promise of the above
10
+ export interface componentFunctionInterface {
11
+ (): Promise<componentInterface>;
12
+ }
13
+
14
+ // components include a dictionary of name: function.
15
+ export const components: {[name: string]: componentFunctionInterface} = {};
16
+
17
+
18
+ /**
19
+ * includeComponent is the function each component function needs to call in order for the component to be included
20
+ * in the fingerprint.
21
+ * @param {string} name - the name identifier of the component
22
+ * @param {componentFunctionInterface} creationFunction - the function that implements the component
23
+ * @returns
24
+ */
25
+ export const includeComponent = (name:string, creationFunction: componentFunctionInterface) => {
26
+ if (typeof window !== 'undefined')
27
+ components[name] = creationFunction;
28
+ }
29
+
30
+
31
+ /**
32
+ * The function turns the map of component functions to a map of Promises when called
33
+ * @returns {[name: string]: <Promise>componentInterface}
34
+ */
35
+ export const getComponentPromises = () => {
36
+ return Object.fromEntries(
37
+ Object.entries(components)
38
+ .filter(([key]) => {
39
+ return !options?.exclude?.includes(key)}
40
+ )
41
+ .filter(([key]) => {
42
+ return options?.include?.some(e => e.includes('.'))
43
+ ? options?.include?.some(e => e.startsWith(key))
44
+ : options?.include?.length === 0 || options?.include?.includes(key)
45
+ }
46
+ )
47
+ .map(([key, value]) => [key, value()])
48
+ );
49
+ }
50
+
51
+
52
+ export const timeoutInstance: componentInterface = {
53
+ 'timeout': "true"
54
+ }
@@ -0,0 +1,60 @@
1
+ import { getFingerprint } from './modules/fp'
2
+ import { detectTorBrowser } from './modules/tor'
3
+ import { detectIncognito } from './modules/incognito'
4
+ import { botDetection } from './modules/bot'
5
+ import { detectBrowser } from './components/system/browser'
6
+ import { getOs } from './modules/os'
7
+ import { getDeviceDetails } from './modules/device'
8
+ import { getBrowserDetails } from './modules/browserDetails'
9
+ import './components'
10
+
11
+ export { getFingerprint, detectTorBrowser, detectIncognito, botDetection, detectBrowser, getOs, getDeviceDetails, getBrowserDetails }
12
+
13
+ interface SdkInfo {
14
+ name: string;
15
+ version: string;
16
+ }
17
+
18
+ export const buildInitialPayload = async (request_id: string, sdkInfo: SdkInfo) => {
19
+ const [
20
+ fingerprint,
21
+ torResult,
22
+ botResult,
23
+ incognito,
24
+ osResult,
25
+ deviceDetails,
26
+ browserDetails,
27
+ ] = await fetchModules();
28
+
29
+ return {
30
+ request_id,
31
+ device: {
32
+ fingerprint_id: fingerprint.hash,
33
+ type: "web",
34
+ os: deviceDetails.deviceOs,
35
+ os_version: osResult.version,
36
+ raw_device_data: fingerprint.data,
37
+ browserDetails: browserDetails,
38
+ bot: botResult,
39
+ tampering: deviceDetails.tampering,
40
+ incognito: incognito,
41
+ tor: torResult,
42
+ },
43
+ sdk: {
44
+ name: sdkInfo?.name,
45
+ version: sdkInfo?.version
46
+ }
47
+ };
48
+ };
49
+
50
+ export async function fetchModules() {
51
+ return await Promise.all([
52
+ getFingerprint(),
53
+ detectTorBrowser(),
54
+ botDetection(),
55
+ detectIncognito(),
56
+ getOs(),
57
+ getDeviceDetails(),
58
+ getBrowserDetails(),
59
+ ]);
60
+ }
@@ -0,0 +1,25 @@
1
+ import { load } from "@fingerprintjs/botd";
2
+
3
+ async function botDetection() {
4
+ try {
5
+ const botd = await load();
6
+ const result = botd.detect();
7
+
8
+
9
+ if (result.bot) {
10
+ return {
11
+ status: true,
12
+ botKind: result.botKind,
13
+ };
14
+ } else {
15
+ return {
16
+ status: false,
17
+ };
18
+ }
19
+ } catch (error) {
20
+ console.error(error);
21
+ return null;
22
+ }
23
+ }
24
+
25
+ export { botDetection };
@@ -0,0 +1,11 @@
1
+ import { detectBrowser } from "../components/system/browser";
2
+
3
+ export async function getBrowserDetails() {
4
+ const { name, version } = detectBrowser();
5
+
6
+ return {
7
+ name,
8
+ version,
9
+ user_agent: navigator.userAgent,
10
+ };
11
+ }
@@ -0,0 +1,150 @@
1
+ import { DeviceResults, AllowedOs, DeviceType } from '../../../core';
2
+ import { normalizeOs, determineDeviceType, convertToTwoDP, isAllowedDeviceType, detectActualOs } from './helpers';
3
+
4
+ type ClaimedData = DeviceResults['claimed'];
5
+ type SignalData = DeviceResults['signals'];
6
+ type AnalysisData = DeviceResults['analysis'];
7
+
8
+ export const analyzeSpoofing = (claimed: ClaimedData, signals: SignalData): AnalysisData => {
9
+ const MAX_CONFIDENCE_SCORE = 1.0;
10
+ let score = MAX_CONFIDENCE_SCORE;
11
+ const conflicts: string[] = [];
12
+ const analysis: AnalysisData = {
13
+ confidence: MAX_CONFIDENCE_SCORE,
14
+ spoofingConfidence: 0.0,
15
+ isSpoofed: false,
16
+ detectionConflicts: [],
17
+ realPlatform: null,
18
+ realDeviceType: null,
19
+ };
20
+
21
+ const hasTouch = signals.touchPoints > 0;
22
+ const screenWidth = signals.screen.width;
23
+
24
+ const claimedOs = normalizeOs(claimed.platform);
25
+ const gpuOs = detectActualOs(signals.webGL.renderer?.toLowerCase() || '');
26
+ const platformOs = normalizeOs(signals.actualPlatform);
27
+ const actualOs = gpuOs || platformOs;
28
+ let suspectedOs: AllowedOs | null = claimedOs;
29
+
30
+ // --- Apply Rules ---
31
+
32
+ // Rule 1: Os Mismatch
33
+ if (claimedOs && actualOs && claimedOs !== actualOs) {
34
+ score -= 0.3;
35
+ conflicts.push(`Claimed OS "${claimedOs}" disagrees with actual OS signal "${actualOs}".`);
36
+ suspectedOs = actualOs; // Trust actual signal more initially
37
+ } else if (claimedOs && !actualOs && claimed.platform !== '') {
38
+ score -= 0.2;
39
+ conflicts.push(`Actual OS signal "${signals.actualPlatform}" is unrecognized.`);
40
+ suspectedOs = claimedOs;
41
+ } else if (!claimedOs && actualOs) {
42
+ score -= 0.2;
43
+ conflicts.push(`Claimed OS signal "${claimed.platform}" is unrecognized.`);
44
+ suspectedOs = actualOs;
45
+ }
46
+
47
+ // Rule 2: GPU Vendor vs Determined Preliminary OS
48
+ const gpuVendor = (signals.webGL.vendor || "").toLowerCase();
49
+ const gpuRenderer = (signals.webGL.renderer || "").toLowerCase();
50
+
51
+ if (gpuVendor && gpuVendor !== 'n/a' && gpuVendor !== 'n/a (masked)') {
52
+ let gpuConflict = false;
53
+ let gpuSuggestedOs: AllowedOs | null = null;
54
+
55
+ if (suspectedOs === 'android' && (gpuVendor.includes('apple') || gpuRenderer.includes('apple'))) { gpuConflict = true; gpuSuggestedOs = 'ios'; }
56
+ else if (suspectedOs === 'ios' && !gpuVendor.includes('apple') && !gpuRenderer.includes('apple')) {
57
+ gpuConflict = true;
58
+ if (gpuVendor.includes('qualcomm') || gpuVendor.includes('adreno') || gpuVendor.includes('mali') || gpuVendor.includes('powervr') || gpuVendor.includes('arm')) { gpuSuggestedOs = 'android'; }
59
+ else if (gpuVendor.includes('intel') || gpuRenderer.includes('intel') || gpuVendor.includes('nvidia') || gpuRenderer.includes('nvidia') || gpuVendor.includes('amd') || gpuRenderer.includes('amd') || gpuRenderer.includes('radeon')) { gpuSuggestedOs = 'windows'; }
60
+ else { gpuSuggestedOs = 'android'; }
61
+ }
62
+ else if (suspectedOs === 'windows' && (gpuVendor.includes('apple') || gpuRenderer.includes('apple'))) { gpuConflict = true; gpuSuggestedOs = 'macos'; }
63
+ else if (suspectedOs === 'macos' && !(gpuVendor.includes('apple') || gpuRenderer.includes('apple') || gpuVendor.includes('intel') || gpuRenderer.includes('intel') || gpuVendor.includes('amd') || gpuRenderer.includes('amd') || gpuRenderer.includes('radeon'))) {
64
+ gpuConflict = true;
65
+ if (gpuVendor.includes('qualcomm') || gpuVendor.includes('adreno') || gpuVendor.includes('mali') || gpuVendor.includes('powervr') || gpuVendor.includes('arm')) { gpuSuggestedOs = 'android'; }
66
+ else { gpuSuggestedOs = 'windows'; }
67
+ }
68
+
69
+ if (gpuConflict) {
70
+ score -= 0.5;
71
+ conflicts.push(`OS "${suspectedOs}" conflicts with GPU "${signals.webGL.vendor} / ${signals.webGL.renderer}". GPU suggests "${gpuSuggestedOs || 'unknown'}".`);
72
+ if (gpuSuggestedOs) { suspectedOs = gpuSuggestedOs; }
73
+ }
74
+ // Add logic here for suspicious but not conflicting GPUs if desired (e.g., Intel GPU on Android)
75
+ } else if (signals.webGL.error) {
76
+ score -= 0.1;
77
+ conflicts.push(`WebGL info unavailable (${signals.webGL.error}).`);
78
+ }
79
+
80
+
81
+ // Rule 3: Claimed Device Type vs Touch Support
82
+ let preliminaryRealDeviceType: DeviceType | null = null;
83
+ const normalizedClaimedType = isAllowedDeviceType(claimed.guessedDeviceType) ? claimed.guessedDeviceType : null;
84
+
85
+ if (claimed.mobile === true && !hasTouch) {
86
+ score -= 0.3;
87
+ conflicts.push(`Claimed mobile=true but no touch points detected.`);
88
+ preliminaryRealDeviceType = 'desktop';
89
+ } else if (claimed.mobile === false && hasTouch) {
90
+ score -= 0.15;
91
+ conflicts.push(`Claimed mobile=false but touch points detected.`);
92
+ preliminaryRealDeviceType = determineDeviceType(claimed.mobile, hasTouch, screenWidth, suspectedOs);
93
+ } else if (normalizedClaimedType) {
94
+ // If claimed type exists and doesn't conflict strongly with touch, use it as preliminary
95
+ preliminaryRealDeviceType = normalizedClaimedType;
96
+ }
97
+
98
+
99
+ // Rule 4: Screen Size vs Preliminary Device Type Sanity Check
100
+ const typicalMobileWidth = 768;
101
+ const typicalTabletWidth = 1024;
102
+
103
+ if (preliminaryRealDeviceType === 'mobile' && screenWidth > typicalTabletWidth) {
104
+ score -= 0.15;
105
+ conflicts.push(`Device type 'mobile', but screen width (${screenWidth}px) is large.`);
106
+ preliminaryRealDeviceType = 'tablet';
107
+ } else if (preliminaryRealDeviceType === 'desktop' && screenWidth < typicalMobileWidth) {
108
+ score -= 0.15;
109
+ conflicts.push(`Preliminary type 'desktop', but screen width (${screenWidth}px) is small.`);
110
+ preliminaryRealDeviceType = hasTouch ? 'mobile' : 'desktop';
111
+ }
112
+
113
+ // --- Finalize Analysis ---
114
+ // We use the suspected Os but fallback to the claimed os of the ua if null
115
+ analysis.realPlatform = suspectedOs || claimedOs;
116
+
117
+ // Determine final device type
118
+ const determinedType = determineDeviceType(claimed.mobile, hasTouch, screenWidth, analysis.realPlatform);
119
+
120
+ if (!preliminaryRealDeviceType) {
121
+ analysis.realDeviceType = determinedType;
122
+ } else {
123
+ // Check if the determined type based on final signals contradicts the preliminary one
124
+ if (determinedType && determinedType !== preliminaryRealDeviceType) {
125
+ score -= 0.12; // Small penalty for internal inconsistency
126
+ conflicts.push(`Initial device type guess (${preliminaryRealDeviceType}) adjusted to ${determinedType} based on final signals.`);
127
+ analysis.realDeviceType = determinedType;
128
+ } else {
129
+ analysis.realDeviceType = preliminaryRealDeviceType;
130
+ }
131
+ }
132
+
133
+ // Ensure device type is populated based on OS if still null
134
+ if (!analysis.realDeviceType) {
135
+ if (analysis.realPlatform === 'ios' || analysis.realPlatform === 'android') {
136
+ analysis.realDeviceType = determinedType || 'mobile';
137
+ } else if (analysis.realPlatform) { // windows, macos, linux
138
+ analysis.realDeviceType = determinedType || 'desktop';
139
+ } else { // OS unknown
140
+ analysis.realDeviceType = hasTouch ? (screenWidth > 768 ? 'tablet' : 'mobile') : 'desktop';
141
+ }
142
+ }
143
+
144
+ analysis.confidence = convertToTwoDP(Math.max(0, score));
145
+ analysis.spoofingConfidence = convertToTwoDP(MAX_CONFIDENCE_SCORE - analysis.confidence);
146
+ analysis.isSpoofed = analysis.spoofingConfidence > 0.1;
147
+ analysis.detectionConflicts = conflicts;
148
+
149
+ return analysis;
150
+ };
@@ -0,0 +1,92 @@
1
+ import { DeviceResults, ClaimedDevice } from '../../../core';
2
+ import { guessDeviceTypeFromUA } from './helpers';
3
+
4
+
5
+ type SignalData = DeviceResults['signals'];
6
+
7
+ // What the device claims to be is gotten from the useragent string
8
+ export const getClaimedData = async (userAgentString: string): Promise<ClaimedDevice> => {
9
+ const claimed = {} as ClaimedDevice;
10
+
11
+ try {
12
+ if ((navigator as any).userAgentData) {
13
+ const uaData = await (navigator as any).userAgentData.getHighEntropyValues([
14
+ "platform", "platformVersion", "architecture", "model", "uaFullVersion", "brands", "mobile",
15
+ ]);
16
+ claimed.platform = uaData.platform;
17
+ claimed.mobile = uaData.mobile ?? null;
18
+ claimed.brands = uaData.brands || [];
19
+
20
+ // Initial guess - will be refined later using signals if needed
21
+ if (claimed.mobile) {
22
+ claimed.guessedDeviceType = screen.width > 768 ? "tablet" : "mobile";
23
+ } else {
24
+ claimed.guessedDeviceType = "desktop";
25
+ }
26
+ } else {
27
+ const ua = userAgentString;
28
+ const lowerUa = ua.toLowerCase();
29
+
30
+ // Determine the device platform from user agent string
31
+ if (lowerUa.includes("android")) claimed.platform = "Android";
32
+ else if (lowerUa.includes("iphone") || lowerUa.includes("ipad")) claimed.platform = "iOS";
33
+ else if (lowerUa.includes("windows nt")) claimed.platform = "Windows";
34
+ else if (lowerUa.includes("mac os x") || lowerUa.includes("macintosh")) claimed.platform = "macOS";
35
+ else if (lowerUa.includes("linux")) claimed.platform = "Linux";
36
+
37
+ const guessedType = guessDeviceTypeFromUA(ua);
38
+ claimed.guessedDeviceType = guessedType;
39
+ claimed.mobile = guessedType === 'mobile' || guessedType === 'tablet';
40
+
41
+ const brandMatch = ua.match(/(Apple|Samsung|Google|Microsoft|Sony|LG|HTC|Nokia|Motorola|Huawei|Xiaomi|Pixel|Firefox|Safari|Edge|Chrome)/i);
42
+ if (brandMatch) {
43
+ const versionMatch = ua.match(new RegExp(brandMatch[0] + "[ /]([\\d._]+)"));
44
+ claimed.brands.push({ brand: brandMatch[0], version: versionMatch ? versionMatch[1] : "unknown" });
45
+ }
46
+ }
47
+ } catch (e) {
48
+ console.error("Error getting User Agent Data:", e);
49
+ }
50
+ return claimed;
51
+ };
52
+
53
+ // What we actually think the device could be achieved from some of this signals
54
+ export const getSignalData = (): SignalData => {
55
+ const signals: SignalData = {
56
+ actualPlatform: (navigator as any).userAgentData?.platform || navigator.platform ,
57
+ touchPoints: navigator.maxTouchPoints || 0,
58
+ screen: {
59
+ width: screen.width,
60
+ height: screen.height,
61
+ pixelRatio: window.devicePixelRatio || 1,
62
+ },
63
+ webGL: { vendor: undefined, renderer: undefined, error: "" },
64
+ userAgentString: navigator.userAgent,
65
+ };
66
+
67
+ try {
68
+ const canvas = document.createElement("canvas");
69
+ const gl = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
70
+
71
+ if (gl && 'getParameter' in gl) {
72
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
73
+ if (debugInfo) {
74
+ signals.webGL.vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "n/a";
75
+ signals.webGL.renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || "n/a";
76
+ } else {
77
+ signals.webGL.vendor = gl.getParameter(gl.VENDOR) || "n/a (masked)";
78
+ signals.webGL.renderer = gl.getParameter(gl.RENDERER) || "n/a (masked)";
79
+ if (!signals.webGL.vendor || signals.webGL.vendor === "n/a (masked)") {
80
+ signals.webGL.error = "Debug info extension unavailable or vendor masked";
81
+ }
82
+ }
83
+ } else {
84
+ signals.webGL.error = "WebGL context unavailable";
85
+ }
86
+ } catch (error) {
87
+ console.error("Error getting WebGL data:", error);
88
+ signals.webGL.error = error instanceof Error ? error.message : "Unknown WebGL error";
89
+ }
90
+
91
+ return signals;
92
+ };
@@ -0,0 +1,123 @@
1
+ import {
2
+ ALLOWED_DEVICE_TYPES,
3
+ ALLOWED_OS,
4
+ AllowedOs,
5
+ DeviceType,
6
+ } from '../../../core';
7
+
8
+ // Normalize the gueesed Os
9
+ export const normalizeOs = (
10
+ platform: string | undefined | null
11
+ ): AllowedOs | null => {
12
+ if (!platform) return null;
13
+ const lowerPlatform = platform.toLowerCase();
14
+
15
+ if (lowerPlatform.includes("android")) return "android";
16
+ if (
17
+ lowerPlatform.includes("iphone") ||
18
+ lowerPlatform.includes("ipad") ||
19
+ lowerPlatform === "ios"
20
+ )
21
+ return "ios";
22
+ if (lowerPlatform.includes("win")) return "windows";
23
+ if (lowerPlatform.includes("mac") || lowerPlatform.includes("macos"))
24
+ return "macos";
25
+ if (lowerPlatform.includes("linux")) return "linux";
26
+ // if (lowerPlatform.includes("cros")) return "linux"; // Example extension
27
+
28
+ return null;
29
+ };
30
+
31
+ const patterns = {
32
+ ios: [/apple a\d+ gpu/i],
33
+ macos: [/apple m\d+/i, /opengl engine/i],
34
+ windows: [/angle.*direct3d/i],
35
+ android: [/adreno|mali|powervr/i],
36
+ linux: [/mesa|x11/i],
37
+ };
38
+
39
+ // Detect the actual OS from the GPU vendor and renderer if available
40
+ export const detectActualOs = (renderer: string): AllowedOs | null => {
41
+ if (patterns.ios.some((pattern) => pattern.test(renderer))) {
42
+ return "ios";
43
+ } else if (patterns.macos.some((pattern) => pattern.test(renderer))) {
44
+ return "macos";
45
+ } else if (patterns.windows.some((pattern) => pattern.test(renderer))) {
46
+ return "windows";
47
+ } else if (patterns.android.some((pattern) => pattern.test(renderer))) {
48
+ return "android";
49
+ } else if (
50
+ patterns.linux.some((pattern) => pattern.test(renderer)) ||
51
+ /NVIDIA|AMD|Intel/i.test(renderer)
52
+ ) {
53
+ return "linux";
54
+ } else {
55
+ return null;
56
+ }
57
+ };
58
+
59
+ export const determineDeviceType = (
60
+ claimedMobile: boolean | null,
61
+ hasTouch: boolean,
62
+ screenWidth: number,
63
+ realOs: AllowedOs | null
64
+ ): DeviceType | null => {
65
+ // Determine the real device type from the os
66
+ if (realOs === "ios" || realOs === "android") {
67
+ return screenWidth > 768 ? "tablet" : "mobile";
68
+ }
69
+
70
+ if (realOs === "windows" || realOs === "macos" || realOs === "linux") {
71
+ return "desktop";
72
+ }
73
+
74
+ // check using touch attributes if the os couldn't determine device type
75
+ if (hasTouch) {
76
+ if (screenWidth < 600) return "mobile";
77
+ if (screenWidth <= 1024) return "tablet";
78
+ if (claimedMobile === false) return "desktop"; // Likely touch laptop
79
+ return "tablet"; // Default large touch to tablet
80
+ }
81
+
82
+ if (!hasTouch) {
83
+ return "desktop";
84
+ }
85
+
86
+ return null;
87
+ };
88
+
89
+ export const guessDeviceTypeFromUA = (ua: string): DeviceType | null => {
90
+ const lowerUa = ua.toLowerCase();
91
+ if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(lowerUa)) {
92
+ return "tablet";
93
+ }
94
+ if (
95
+ /Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|hpwos|Opera M(obi|ini)/i.test(
96
+ lowerUa
97
+ )
98
+ ) {
99
+ return "mobile";
100
+ }
101
+ if (
102
+ lowerUa.includes("windows nt") ||
103
+ lowerUa.includes("macintosh") ||
104
+ lowerUa.includes("x11")
105
+ ) {
106
+ return "desktop";
107
+ }
108
+ return null;
109
+ };
110
+
111
+ export const convertToTwoDP = (num: number): number => {
112
+ return parseFloat(num.toFixed(2));
113
+ };
114
+
115
+ export const isAllowedOs = (os: string): os is AllowedOs => {
116
+ return !!os && (ALLOWED_OS as readonly string[]).includes(os);
117
+ };
118
+
119
+ export const isAllowedDeviceType = (
120
+ type: DeviceType | null
121
+ ): type is DeviceType => {
122
+ return !!type && (ALLOWED_DEVICE_TYPES as readonly string[]).includes(type);
123
+ };
@@ -0,0 +1,64 @@
1
+ import {
2
+ RefinedDeviceResults,
3
+ deviceTampering,
4
+ DeviceResults,
5
+ } from "../../../core";
6
+ import { analyzeSpoofing } from "./analyze-data";
7
+ import { getClaimedData, getSignalData } from "./gather-data";
8
+
9
+ export const getDeviceDetails = async (): Promise<RefinedDeviceResults> => {
10
+ // 1. Initialize structure
11
+ const initialResults: DeviceResults = {
12
+ claimed: {
13
+ platform: "",
14
+ mobile: false,
15
+ brands: [],
16
+ guessedDeviceType: null,
17
+ },
18
+ signals: {
19
+ actualPlatform: "",
20
+ touchPoints: 0,
21
+ screen: { width: 0, height: 0, pixelRatio: 1 },
22
+ webGL: { vendor: undefined, renderer: undefined, error: "" },
23
+ userAgentString: "",
24
+ },
25
+ analysis: {
26
+ confidence: 1.0,
27
+ spoofingConfidence: 0.0,
28
+ isSpoofed: false,
29
+ detectionConflicts: [],
30
+ realPlatform: null,
31
+ realDeviceType: null,
32
+ },
33
+ };
34
+
35
+ // 2. Gather Signals (Sync first)
36
+ const signals = getSignalData();
37
+ initialResults.signals = signals;
38
+
39
+ // 3. Gather Claimed Data (Async) - Pass UA string from signals
40
+ const claimed = await getClaimedData(signals.userAgentString);
41
+ initialResults.claimed = claimed;
42
+
43
+ // 4. Analyze Data
44
+ const analysis = analyzeSpoofing(claimed, signals);
45
+ initialResults.analysis = analysis;
46
+
47
+ // 5. Format Final Output
48
+ const tamperingResult = (): deviceTampering => {
49
+ if (analysis.isSpoofed) {
50
+ return { status: true, confidence: analysis.spoofingConfidence };
51
+ } else {
52
+ return { status: false };
53
+ }
54
+ };
55
+
56
+ const finalResults: RefinedDeviceResults = {
57
+ ...initialResults,
58
+ deviceOs: analysis.realPlatform,
59
+ deviceType: analysis.realDeviceType,
60
+ tampering: tamperingResult(),
61
+ };
62
+
63
+ return finalResults;
64
+ };