@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,6 @@
1
+ /**
2
+ * This code is taken from https://github.com/LinusU/murmur-128/blob/master/index.js
3
+ * But instead of dependencies to encode-utf8 and fmix, I've implemented them here.
4
+ */
5
+ export declare function hash(key: ArrayBuffer | string, seed?: number): string;
6
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/device/utils/hash.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoLH,wBAAgB,IAAI,CAAE,GAAG,EAAG,WAAW,GAAG,MAAM,EAAE,IAAI,SAAI,GAAI,MAAM,CAkBnE"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts an error object to a plain object that can be used with `JSON.stringify`.
3
+ * If you just run `JSON.stringify(error)`, you'll get `'{}'`.
4
+ */
5
+ export declare function errorToObject(error: Readonly<Error>): Record<string, unknown>;
6
+ export declare function isFunctionNative(func: (...args: unknown[]) => unknown): boolean;
7
+ //# sourceMappingURL=misc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../../src/device/utils/misc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ3E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,OAAO,CAE/E"}
@@ -0,0 +1,9 @@
1
+ type DelayedPromise<T> = Promise<T>;
2
+ export declare function delay<T>(t: number, val: T): DelayedPromise<T>;
3
+ export interface RaceResult<T> {
4
+ value: T;
5
+ elapsed?: number;
6
+ }
7
+ export declare function raceAll<T>(promises: Promise<T>[], timeoutTime: number, timeoutVal: T): Promise<(T | undefined)[]>;
8
+ export {};
9
+ //# sourceMappingURL=raceAll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raceAll.d.ts","sourceRoot":"","sources":["../../../src/device/utils/raceAll.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AAEpC,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAI7D;AAGD,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAIjH"}
@@ -0,0 +1,4 @@
1
+ export { generateRequestId, GingerHttpClient, HttpClient, GingerClientError, GingerError, pageVisibility } from "./core";
2
+ export { fetchModules, buildInitialPayload } from "./device";
3
+ export { GingerJsClient } from "./client";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAC,MAAM,QAAQ,CAAC;AACxH,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@ginger-ai/ginger-js",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "GingerJs JavaScript agent for Single page application (SPA)",
6
+ "main": "dist/ginger.cjs.js",
7
+ "module": "dist/ginger.esm.js",
8
+ "types": "dist/ginger.esm.d.ts",
9
+ "unpkg": "dist/ginger.umd.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/ginger.esm.js",
13
+ "require": "./dist/ginger.cjs.js",
14
+ "types": "./dist/ginger.esm.d.ts"
15
+ }
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "fraud detection",
22
+ "fingerprinting",
23
+ "fingerprint",
24
+ "browser identification",
25
+ "bot detection",
26
+ "spoof detection"
27
+ ],
28
+ "files": [
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "scripts": {
33
+ "build": "rm -rf dist/* && rm -rf types/* && rollup -c"
34
+ },
35
+ "devDependencies": {
36
+ "@rollup/plugin-commonjs": "^25.0.8",
37
+ "@rollup/plugin-json": "^6.1.0",
38
+ "@rollup/plugin-node-resolve": "^15.3.1",
39
+ "@rollup/plugin-terser": "^0.4.4",
40
+ "@rollup/plugin-typescript": "^11.1.6",
41
+ "@types/jest": "^29.0.0",
42
+ "eslint": "^8.0.0",
43
+ "jest": "^29.0.0",
44
+ "rollup": "^4.0.0",
45
+ "rollup-plugin-dts": "^6.2.3",
46
+ "typescript": "^5.9.2"
47
+ },
48
+ "dependencies": {
49
+ "@fingerprintjs/botd": "^1.9.1",
50
+ "ua-parser-js": "^2.0.4"
51
+ }
52
+ }
@@ -0,0 +1,279 @@
1
+ import { GingerClientError, FieldMetrics, FillEnum, FillMethod, pageVisibility, Field } from "../core";
2
+
3
+ interface TrackedFieldMetrics extends FieldMetrics {
4
+ readonly element: HTMLInputElement | HTMLTextAreaElement;
5
+ }
6
+
7
+ interface EventConfig {
8
+ readonly pauseThresholdMs: number;
9
+ }
10
+
11
+ const { getCount, addListener, removeListener } = pageVisibility();
12
+
13
+ class EventTracker {
14
+ private static readonly DEFAULT_CONFIG: EventConfig = {
15
+ pauseThresholdMs: 1500,
16
+ };
17
+
18
+ private readonly config: EventConfig;
19
+ private isInitialized = false;
20
+ private readonly fields: TrackedFieldMetrics[] = [];
21
+ private readonly trackedFieldIds = new Set<string>();
22
+
23
+ constructor(config: Partial<EventConfig> = {}) {
24
+ this.config = { ...EventTracker.DEFAULT_CONFIG, ...config };
25
+ addListener();
26
+ }
27
+
28
+ /**
29
+ * Initialize tracking for multiple form fields
30
+ */
31
+ public initializeTracking(fields: readonly Field[]): void {
32
+ const newFields = fields.filter(
33
+ (field) => !this.trackedFieldIds.has(field.id)
34
+ );
35
+
36
+ newFields.forEach((field) => this.trackField(field));
37
+ this.isInitialized = true;
38
+ }
39
+
40
+ /**
41
+ * Get all tracked metrics data (excluding DOM elements)
42
+ */
43
+ get metrics(): FieldMetrics[] {
44
+ this.ensureInitialized();
45
+ return this.fields.map(({ element, ...field }) => field);
46
+ }
47
+
48
+ /**
49
+ * Remove tracking from specific field or all fields
50
+ */
51
+ public removeTracking(fieldId?: string): void {
52
+ if (fieldId) {
53
+ this.removeFieldTracking(fieldId);
54
+ } else {
55
+ this.removeAllTracking();
56
+ }
57
+ }
58
+
59
+ // Private implementation methods
60
+
61
+ private trackField(field: Field): void {
62
+ const { id, ltm } = field;
63
+ const element = this.getValidElement(id);
64
+ const fieldMetric = this.createFieldMetric(id, element, ltm);
65
+
66
+ this.fields.push(fieldMetric);
67
+ this.trackedFieldIds.add(id);
68
+
69
+ // Use bound method to maintain context
70
+ const boundHandler = this.createInputHandler(fieldMetric);
71
+ element.addEventListener("input", boundHandler);
72
+ }
73
+
74
+ private getValidElement(id: string): HTMLInputElement | HTMLTextAreaElement {
75
+ const element = document.getElementById(id);
76
+
77
+ if (!element) {
78
+ throw new GingerClientError(
79
+ `Element with ID "${id}" was not found in the DOM. Please ensure the ID is correctly assigned to an input element.`
80
+ );
81
+ }
82
+
83
+ if (!this.isValidInputElement(element)) {
84
+ throw new GingerClientError(
85
+ `Element with ID "${id}" must be an HTMLInputElement or HTMLTextAreaElement.`
86
+ );
87
+ }
88
+
89
+ return element;
90
+ }
91
+
92
+ private isValidInputElement(
93
+ element: Element
94
+ ): element is HTMLInputElement | HTMLTextAreaElement {
95
+ return (
96
+ element instanceof HTMLInputElement ||
97
+ element instanceof HTMLTextAreaElement
98
+ );
99
+ }
100
+
101
+ private createFieldMetric(
102
+ id: string,
103
+ element: HTMLInputElement | HTMLTextAreaElement,
104
+ ltm?: boolean
105
+ ): TrackedFieldMetrics {
106
+ return {
107
+ field_name: id,
108
+ started_at: 0,
109
+ ended_at: 0,
110
+ interaction_count: 0,
111
+ fill_method: null,
112
+ paste_count: 0,
113
+ ltm: ltm ?? false,
114
+ corrections_count: 0,
115
+ pauses: 0,
116
+ pauseDurations: [],
117
+ element,
118
+ };
119
+ }
120
+
121
+ private createInputHandler(field: TrackedFieldMetrics) {
122
+ return (event: Event): void => {
123
+ this.handleInput(field, event as InputEvent);
124
+ };
125
+ }
126
+
127
+ private handleInput(
128
+ currentField: TrackedFieldMetrics,
129
+ event: InputEvent
130
+ ): void {
131
+ const now = performance.now();
132
+ const lastInteractionTime = currentField.ended_at || 0;
133
+
134
+ const interactions = this.analyzeInteraction(
135
+ currentField,
136
+ event,
137
+ now,
138
+ lastInteractionTime
139
+ );
140
+
141
+ this.updateFieldMetrics(currentField, interactions, now);
142
+ }
143
+
144
+ private analyzeInteraction(
145
+ field: TrackedFieldMetrics,
146
+ event: InputEvent,
147
+ now: number,
148
+ lastInteractionTime: number
149
+ ) {
150
+ const fillMethod = this.determineFillMethod(field, event);
151
+
152
+ return {
153
+ fillMethod,
154
+ isCorrection: this.isCorrection(event),
155
+ isPaste: this.isPaste(fillMethod, event),
156
+ isPause: this.isPause(now, lastInteractionTime),
157
+ pauseDuration: now - lastInteractionTime,
158
+ };
159
+ }
160
+
161
+ private updateFieldMetrics(
162
+ field: TrackedFieldMetrics,
163
+ interactions: ReturnType<typeof this.analyzeInteraction>,
164
+ now: number
165
+ ): void {
166
+ // Initialize start time on first interaction
167
+ if (!field.started_at) {
168
+ field.started_at = now;
169
+ }
170
+
171
+ field.ended_at = now;
172
+ field.fill_method = interactions.fillMethod;
173
+ field.interaction_count += 1;
174
+
175
+ if (interactions.isCorrection) field.corrections_count += 1;
176
+ if (interactions.isPaste) field.paste_count += 1;
177
+ if (interactions.isPause) {
178
+ field.pauses += 1;
179
+ field.pauseDurations.push(interactions.pauseDuration);
180
+ }
181
+ }
182
+
183
+ private determineFillMethod(
184
+ field: TrackedFieldMetrics,
185
+ event: InputEvent
186
+ ): FillMethod {
187
+ const inputType = event.inputType;
188
+ if (!inputType) return FillEnum.paste;
189
+
190
+ const currentMethod: FillMethod =
191
+ inputType === "insertText"
192
+ ? FillEnum.typed
193
+ : inputType === "insertFromPaste"
194
+ ? FillEnum.paste
195
+ : FillEnum.mixed;
196
+
197
+ // If methods have been mixed, maintain "mixed" state
198
+ if (field.fill_method && field.fill_method !== currentMethod) {
199
+ return FillEnum.mixed;
200
+ }
201
+
202
+ return currentMethod;
203
+ }
204
+
205
+ private isCorrection(event: InputEvent): boolean {
206
+ return event.inputType === "deleteContentBackward";
207
+ }
208
+
209
+ private isPaste(fillMethod: FillMethod, event: InputEvent): boolean {
210
+ return (
211
+ fillMethod === FillEnum.paste || event.inputType === "insertFromPaste"
212
+ );
213
+ }
214
+
215
+ private isPause(now: number, lastInteractionTime: number): boolean {
216
+ if (lastInteractionTime === 0) return false;
217
+
218
+ const timeSinceLastKeystroke = now - lastInteractionTime;
219
+ return timeSinceLastKeystroke > this.config.pauseThresholdMs;
220
+ }
221
+
222
+ private removeFieldTracking(fieldId: string): void {
223
+ const index = this.fields.findIndex(
224
+ (field) => field.field_name === fieldId
225
+ );
226
+
227
+ if (index === -1) return;
228
+
229
+ const field = this.fields[index];
230
+ this.cleanupFieldTracking(field);
231
+
232
+ this.fields.splice(index, 1);
233
+ this.trackedFieldIds.delete(fieldId);
234
+ }
235
+
236
+ private removeAllTracking(): void {
237
+ this.fields.forEach((field) => this.cleanupFieldTracking(field));
238
+ this.fields.length = 0;
239
+ this.trackedFieldIds.clear();
240
+ }
241
+
242
+ private cleanupFieldTracking(field: TrackedFieldMetrics): void {
243
+ // Note: We can't remove the exact handler since we're using bound methods
244
+ // In a real implementation, you'd want to store the bound handlers
245
+ field.element.removeEventListener("input", this.createInputHandler(field));
246
+ }
247
+
248
+ private ensureInitialized(): void {
249
+ if (!this.isInitialized) {
250
+ throw new GingerClientError(
251
+ "Ginger.trackEvent must be initialized before data can be fetched."
252
+ );
253
+ }
254
+ }
255
+ }
256
+
257
+ // Singleton instance for the module
258
+ const eventTracker = new EventTracker();
259
+
260
+ // Public API exports
261
+ export const trackInputs = (fields: readonly Field[]): void => {
262
+ eventTracker.initializeTracking(fields);
263
+ };
264
+
265
+ export const getTrackedFields = (): {
266
+ distractions_count: number;
267
+ fields: FieldMetrics[];
268
+ } => {
269
+ return {
270
+ distractions_count: getCount(),
271
+ fields: eventTracker.metrics,
272
+ };
273
+ };
274
+
275
+ export const removeTracking = (fieldId?: string): void => {
276
+ eventTracker.removeTracking(fieldId);
277
+ };
278
+
279
+ export { EventTracker, removeListener };
@@ -0,0 +1,132 @@
1
+ import {
2
+ trackInputs,
3
+ getTrackedFields,
4
+ removeListener,
5
+ } from "../behaviour";
6
+
7
+ import { buildInitialPayload } from "../device";
8
+
9
+ import {
10
+ generateRequestId,
11
+ GingerClientError,
12
+ HttpClient,
13
+ GingerHttpClient,
14
+ BehaviourPayloadResponse,
15
+ Configurations,
16
+ EVENT_TYPES,
17
+ EventParams,
18
+ Payload,
19
+ PayloadResponse,
20
+ BehaviourPayload,
21
+ BehaviourParams,
22
+ PayloadResponseData,
23
+ BehaviourPayloadResponseData,
24
+ } from "../core";
25
+
26
+ interface GingerClient {
27
+ initialize(configs: Configurations): Promise<PayloadResponse | undefined>;
28
+ trackEvent(params: BehaviourParams): void;
29
+ getTrackedData(): BehaviourPayload;
30
+ submitEvent(): Promise<BehaviourPayloadResponseData>;
31
+ }
32
+
33
+ export class GingerJsClient implements GingerClient {
34
+ private readonly requestId = generateRequestId();
35
+ private deviceResponse: PayloadResponseData | undefined;
36
+ private isInitialized: boolean = false;
37
+ private trackDetails: EventParams | undefined;
38
+ private httpClient: HttpClient;
39
+
40
+ constructor({ apikey }: Configurations) {
41
+ this.httpClient = new GingerHttpClient(apikey);
42
+ }
43
+
44
+ async initialize() {
45
+ try {
46
+ const sdkInfo = { name: '@ginger-ai/ginger-js', version: '0.0.1' };
47
+ const payload = await buildInitialPayload(this.requestId, sdkInfo);
48
+ const response = await this.httpClient.post<Payload, PayloadResponse>({
49
+ url: `/api/v1/devices`,
50
+ payload,
51
+ });
52
+
53
+ this.isInitialized = true;
54
+ this.deviceResponse = response.data;
55
+
56
+ return response;
57
+ } catch (error) {
58
+ this.isInitialized = false;
59
+ throw new GingerClientError(`Initialization failed: ${error}`);
60
+ }
61
+ }
62
+
63
+ trackEvent({ event_type, track_fields, request_id }: BehaviourParams): void {
64
+ if (!this.isInitialized) {
65
+ throw new GingerClientError(
66
+ "Unsuccessful Initialization. Cannot track behaviour."
67
+ );
68
+ }
69
+
70
+ if (!EVENT_TYPES.includes(event_type)) {
71
+ throw new GingerClientError(
72
+ `Invalid event type: ${event_type}. Allowed types are: ${EVENT_TYPES.join(
73
+ ", "
74
+ )}`
75
+ );
76
+ }
77
+
78
+ trackInputs(track_fields);
79
+ this.trackDetails = {
80
+ event_type,
81
+ request_id,
82
+ fingerprint_id: this.deviceResponse?.fingerprint_id || "",
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Get tracked data
88
+ */
89
+ getTrackedData = () => {
90
+ if (!this.isInitialized) {
91
+ throw new GingerClientError(
92
+ "Unsuccessful Initialization. Cannot get tracked data."
93
+ );
94
+ }
95
+
96
+ if (!this.trackDetails) {
97
+ throw new GingerClientError(
98
+ "Tracking not initiated. Call `trackEvent` before getting tracked data."
99
+ );
100
+ }
101
+
102
+ const trackData = getTrackedFields();
103
+
104
+ return {
105
+ event_type: this.trackDetails.event_type,
106
+ request_id: this.trackDetails.request_id,
107
+ fingerprint_id: this.trackDetails.fingerprint_id,
108
+ data: { ...trackData },
109
+ };
110
+ };
111
+
112
+ /**
113
+ * Call submitEvent to submit tracked data
114
+ */
115
+ submitEvent = async () => {
116
+ const payload = this.getTrackedData();
117
+
118
+ try {
119
+ const response = await this.httpClient.post<
120
+ BehaviourPayload,
121
+ BehaviourPayloadResponse
122
+ >({ url: `/api/v1/events`, payload });
123
+
124
+ removeListener();
125
+ return response.data;
126
+ } catch (error) {
127
+ throw new GingerClientError(
128
+ `Tracked Data submission failed: ${String(error)}`
129
+ );
130
+ }
131
+ };
132
+ }
@@ -0,0 +1,4 @@
1
+ export const CONSTANTS = {
2
+ LIVE_URL: "https://app.getrayyan.com",
3
+ TEST_URL: "https://test.getrayyan.com"
4
+ }
@@ -0,0 +1,32 @@
1
+ declare const BotKind: {
2
+ readonly Awesomium: "awesomium";
3
+ readonly Cef: "cef";
4
+ readonly CefSharp: "cefsharp";
5
+ readonly CoachJS: "coachjs";
6
+ readonly Electron: "electron";
7
+ readonly FMiner: "fminer";
8
+ readonly Geb: "geb";
9
+ readonly NightmareJS: "nightmarejs";
10
+ readonly Phantomas: "phantomas";
11
+ readonly PhantomJS: "phantomjs";
12
+ readonly Rhino: "rhino";
13
+ readonly Selenium: "selenium";
14
+ readonly Sequentum: "sequentum";
15
+ readonly SlimerJS: "slimerjs";
16
+ readonly WebDriverIO: "webdriverio";
17
+ readonly WebDriver: "webdriver";
18
+ readonly HeadlessChrome: "headless_chrome";
19
+ readonly Unknown: "unknown";
20
+ None: "none";
21
+ };
22
+ type BotKind = (typeof BotKind)[keyof typeof BotKind];
23
+
24
+ export type BotDetectionResult =
25
+ | {
26
+ status: boolean;
27
+ botKind: BotKind;
28
+ }
29
+ | {
30
+ status: boolean;
31
+ }
32
+ | null;
@@ -0,0 +1,67 @@
1
+ export const ALLOWED_OS = [
2
+ "ios",
3
+ "android",
4
+ "windows",
5
+ "macos",
6
+ "linux",
7
+ ] as const;
8
+ export const ALLOWED_DEVICE_TYPES = ["mobile", "desktop", "tablet"] as const;
9
+
10
+ export type AllowedOs = (typeof ALLOWED_OS)[number];
11
+ export type DeviceType = (typeof ALLOWED_DEVICE_TYPES)[number] | null;
12
+
13
+ export interface BrandInfo {
14
+ brand: string;
15
+ version: string;
16
+ }
17
+
18
+ export interface ClaimedDevice {
19
+ platform: string;
20
+ mobile: boolean;
21
+ brands: BrandInfo[];
22
+ guessedDeviceType: DeviceType | null;
23
+ }
24
+
25
+ export interface ScreenInfo {
26
+ width: number;
27
+ height: number;
28
+ pixelRatio: number;
29
+ }
30
+
31
+ export interface WebGLInfo {
32
+ vendor?: string;
33
+ renderer?: string;
34
+ error: string;
35
+ }
36
+ export interface DeviceSignals {
37
+ actualPlatform: string;
38
+ touchPoints: number;
39
+ screen: ScreenInfo;
40
+ webGL: WebGLInfo;
41
+ readonly userAgentString: string;
42
+ }
43
+
44
+ export interface DeviceAnalysis {
45
+ confidence: number;
46
+ spoofingConfidence: number;
47
+ isSpoofed: boolean;
48
+ detectionConflicts: string[];
49
+ realPlatform: AllowedOs | null;
50
+ realDeviceType: DeviceType;
51
+ }
52
+ export interface DeviceResults {
53
+ claimed: ClaimedDevice;
54
+ signals: DeviceSignals;
55
+ analysis: DeviceAnalysis;
56
+ }
57
+
58
+ export interface RefinedDeviceResults extends DeviceResults {
59
+ deviceOs: AllowedOs | null;
60
+ deviceType: DeviceType;
61
+ tampering: deviceTampering;
62
+ }
63
+
64
+ export interface deviceTampering {
65
+ status: boolean;
66
+ confidence?: number;
67
+ }
@@ -0,0 +1,38 @@
1
+ export interface FingerprintComponentValue {
2
+ [key: string]:
3
+ | string
4
+ | string[]
5
+ | number
6
+ | boolean
7
+ | undefined
8
+ | null
9
+ | FingerprintComponentValue
10
+ | FingerprintComponentValue[];
11
+ }
12
+
13
+ export interface RawDeviceData extends FingerprintComponentValue {
14
+ audio?: FingerprintComponentValue;
15
+ fonts?: FingerprintComponentValue;
16
+ hardware?: FingerprintComponentValue;
17
+ locales?: FingerprintComponentValue;
18
+ permissions?: FingerprintComponentValue;
19
+ screen?: FingerprintComponentValue;
20
+ system?: FingerprintComponentValue;
21
+ emojiFingerprint?: FingerprintComponentValue;
22
+ math?: FingerprintComponentValue;
23
+ vendorFlavour?: FingerprintComponentValue;
24
+ canvas?: FingerprintComponentValue;
25
+ webgl?: FingerprintComponentValue;
26
+ plugins?: FingerprintComponentValue;
27
+ domBlockers?: FingerprintComponentValue;
28
+ forcedColors?: FingerprintComponentValue;
29
+ colorGamut?: FingerprintComponentValue;
30
+ osCpu?: FingerprintComponentValue;
31
+ audioLatency?: FingerprintComponentValue;
32
+ }
33
+
34
+ export interface GingerJsFp {
35
+ hash: string;
36
+ data: RawDeviceData;
37
+ componentsUsedForHash: Array<keyof RawDeviceData>;
38
+ }