@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,347 @@
1
+ import { countTruthy } from './data'
2
+ import { isFunctionNative } from './misc'
3
+
4
+ /*
5
+ * Functions to help with features that vary through browsers
6
+ */
7
+
8
+ /**
9
+ * Checks whether the browser is based on Trident (the Internet Explorer engine) without using user-agent.
10
+ *
11
+ * Warning for package users:
12
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
13
+ */
14
+ export function isTrident(): boolean {
15
+ const w = window
16
+ const n = navigator
17
+
18
+ // The properties are checked to be in IE 10, IE 11 and not to be in other browsers in October 2020
19
+ return (
20
+ countTruthy([
21
+ 'MSCSSMatrix' in w,
22
+ 'msSetImmediate' in w,
23
+ 'msIndexedDB' in w,
24
+ 'msMaxTouchPoints' in n,
25
+ 'msPointerEnabled' in n,
26
+ ]) >= 4
27
+ )
28
+ }
29
+
30
+ /**
31
+ * Checks whether the browser is based on EdgeHTML (the pre-Chromium Edge engine) without using user-agent.
32
+ *
33
+ * Warning for package users:
34
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
35
+ */
36
+ export function isEdgeHTML(): boolean {
37
+ // Based on research in October 2020
38
+ const w = window
39
+ const n = navigator
40
+
41
+ return (
42
+ countTruthy(['msWriteProfilerMark' in w, 'MSStream' in w, 'msLaunchUri' in n, 'msSaveBlob' in n]) >= 3 &&
43
+ !isTrident()
44
+ )
45
+ }
46
+
47
+ /**
48
+ * Checks whether the browser is based on Chromium without using user-agent.
49
+ *
50
+ * Warning for package users:
51
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
52
+ */
53
+ export function isChromium(): boolean {
54
+ // Based on research in October 2020. Tested to detect Chromium 42-86.
55
+ const w = window
56
+ const n = navigator
57
+
58
+ return (
59
+ countTruthy([
60
+ 'webkitPersistentStorage' in n,
61
+ 'webkitTemporaryStorage' in n,
62
+ (n.vendor || '').indexOf('Google') === 0,
63
+ 'webkitResolveLocalFileSystemURL' in w,
64
+ 'BatteryManager' in w,
65
+ 'webkitMediaStream' in w,
66
+ 'webkitSpeechGrammar' in w,
67
+ ]) >= 5
68
+ )
69
+ }
70
+
71
+ /**
72
+ * Checks whether the browser is based on mobile or desktop Safari without using user-agent.
73
+ * All iOS browsers use WebKit (the Safari engine).
74
+ *
75
+ * Warning for package users:
76
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
77
+ */
78
+ export function isWebKit(): boolean {
79
+ // Based on research in August 2024
80
+ const w = window
81
+ const n = navigator
82
+
83
+ return (
84
+ countTruthy([
85
+ 'ApplePayError' in w,
86
+ 'CSSPrimitiveValue' in w,
87
+ 'Counter' in w,
88
+ n.vendor.indexOf('Apple') === 0,
89
+ 'RGBColor' in w,
90
+ 'WebKitMediaKeys' in w,
91
+ ]) >= 4
92
+ )
93
+ }
94
+
95
+ /**
96
+ * Checks whether this WebKit browser is a desktop browser.
97
+ * It doesn't check that the browser is based on WebKit, there is a separate function for this.
98
+ *
99
+ * Warning for package users:
100
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
101
+ */
102
+ export function isDesktopWebKit(): boolean {
103
+ // Checked in Safari and DuckDuckGo
104
+
105
+ const w = window
106
+ const { HTMLElement, Document } = w
107
+
108
+ return (
109
+ countTruthy([
110
+ 'safari' in w, // Always false in Karma and BrowserStack Automate
111
+ !('ongestureend' in w),
112
+ !('TouchEvent' in w),
113
+ !('orientation' in w),
114
+ HTMLElement && !('autocapitalize' in HTMLElement.prototype),
115
+ Document && 'pointerLockElement' in Document.prototype,
116
+ ]) >= 4
117
+ )
118
+ }
119
+
120
+ /**
121
+ * Checks whether this WebKit browser is Safari.
122
+ * It doesn't check that the browser is based on WebKit, there is a separate function for this.
123
+ *
124
+ * Warning! The function works properly only for Safari version 15.4 and newer.
125
+ */
126
+ export function isSafariWebKit(): boolean {
127
+ // Checked in Safari, Chrome, Firefox, Yandex, UC Browser, Opera, Edge and DuckDuckGo.
128
+ // iOS Safari and Chrome were checked on iOS 11-18. DuckDuckGo was checked on iOS 17-18 and macOS 14-15.
129
+ // Desktop Safari versions 12-18 were checked.
130
+ // The other browsers were checked on iOS 17 and 18; there was no chance to check them on the other OS versions.
131
+
132
+ const w = window
133
+
134
+ return (
135
+ // Filters-out Chrome, Yandex, DuckDuckGo (macOS and iOS), Edge
136
+ isFunctionNative(w.print) &&
137
+ // Doesn't work in Safari < 15.4
138
+ String((w as unknown as Record<string, unknown>).browser) === '[object WebPageNamespace]'
139
+ )
140
+ }
141
+
142
+ /**
143
+ * Checks whether the browser is based on Gecko (Firefox engine) without using user-agent.
144
+ *
145
+ * Warning for package users:
146
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
147
+ */
148
+ export function isGecko(): boolean {
149
+ const w = window
150
+
151
+ // Based on research in September 2020
152
+ return (
153
+ countTruthy([
154
+ 'buildID' in navigator,
155
+ 'MozAppearance' in (document.documentElement?.style ?? {}),
156
+ 'onmozfullscreenchange' in w,
157
+ 'mozInnerScreenX' in w,
158
+ 'CSSMozDocumentRule' in w,
159
+ 'CanvasCaptureMediaStream' in w,
160
+ ]) >= 4
161
+ )
162
+ }
163
+
164
+ /**
165
+ * Checks whether the browser is based on Chromium version ≥86 without using user-agent.
166
+ * It doesn't check that the browser is based on Chromium, there is a separate function for this.
167
+ */
168
+ export function isChromium86OrNewer(): boolean {
169
+ // Checked in Chrome 85 vs Chrome 86 both on desktop and Android. Checked in macOS Chrome 128, Android Chrome 127.
170
+ const w = window
171
+
172
+ return (
173
+ countTruthy([
174
+ !('MediaSettingsRange' in w),
175
+ 'RTCEncodedAudioFrame' in w,
176
+ '' + w.Intl === '[object Intl]',
177
+ '' + w.Reflect === '[object Reflect]',
178
+ ]) >= 3
179
+ )
180
+ }
181
+
182
+ /**
183
+ * Checks whether the browser is based on Chromium version ≥122 without using user-agent.
184
+ * It doesn't check that the browser is based on Chromium, there is a separate function for this.
185
+ */
186
+ export function isChromium122OrNewer(): boolean {
187
+ // Checked in Chrome 121 vs Chrome 122 and 129 both on desktop and Android
188
+ const w: any = window
189
+ const { URLPattern } = w
190
+
191
+ return (
192
+ countTruthy([
193
+ 'union' in Set.prototype,
194
+ 'Iterator' in w,
195
+ URLPattern && 'hasRegExpGroups' in URLPattern.prototype,
196
+ 'RGB8' in WebGLRenderingContext.prototype,
197
+ ]) >= 3
198
+ )
199
+ }
200
+
201
+ /**
202
+ * Checks whether the browser is based on WebKit version ≥606 (Safari ≥12) without using user-agent.
203
+ * It doesn't check that the browser is based on WebKit, there is a separate function for this.
204
+ *
205
+ * @see https://en.wikipedia.org/wiki/Safari_version_history#Release_history Safari-WebKit versions map
206
+ */
207
+ export function isWebKit606OrNewer(): boolean {
208
+ // Checked in Safari 9–18
209
+ const w = window
210
+
211
+ return (
212
+ countTruthy([
213
+ 'DOMRectList' in w,
214
+ 'RTCPeerConnectionIceEvent' in w,
215
+ 'SVGGeometryElement' in w,
216
+ 'ontransitioncancel' in w,
217
+ ]) >= 3
218
+ )
219
+ }
220
+
221
+ /**
222
+ * Checks whether the browser is based on WebKit version ≥616 (Safari ≥17) without using user-agent.
223
+ * It doesn't check that the browser is based on WebKit, there is a separate function for this.
224
+ *
225
+ * @see https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes Safari 17 release notes
226
+ * @see https://tauri.app/v1/references/webview-versions/#webkit-versions-in-safari Safari-WebKit versions map
227
+ */
228
+ export function isWebKit616OrNewer(): boolean {
229
+ const w = window
230
+ const n = navigator
231
+ const { CSS, HTMLButtonElement } = w
232
+
233
+ return (
234
+ countTruthy([
235
+ !('getStorageUpdates' in n),
236
+ HTMLButtonElement && 'popover' in HTMLButtonElement.prototype,
237
+ 'CSSCounterStyleRule' in w,
238
+ CSS.supports('font-size-adjust: ex-height 0.5'),
239
+ CSS.supports('text-transform: full-width'),
240
+ ]) >= 4
241
+ )
242
+ }
243
+
244
+ /**
245
+ * Checks whether the device is an iPad.
246
+ * It doesn't check that the engine is WebKit and that the WebKit isn't desktop.
247
+ */
248
+ export function isIPad(): boolean {
249
+ // Checked on:
250
+ // Safari on iPadOS (both mobile and desktop modes): 8, 11-18
251
+ // Chrome on iPadOS (both mobile and desktop modes): 11-18
252
+ // Safari on iOS (both mobile and desktop modes): 9-18
253
+ // Chrome on iOS (both mobile and desktop modes): 9-18
254
+
255
+ // Before iOS 13. Safari tampers the value in "request desktop site" mode since iOS 13.
256
+ if (navigator.platform === 'iPad') {
257
+ return true
258
+ }
259
+
260
+ const s = screen
261
+ const screenRatio = s.width / s.height
262
+
263
+ return (
264
+ countTruthy([
265
+ // Since iOS 13. Doesn't work in Chrome on iPadOS <15, but works in desktop mode.
266
+ 'MediaSource' in window,
267
+ // Since iOS 12. Doesn't work in Chrome on iPadOS.
268
+ !!(Element as any).prototype.webkitRequestFullscreen,
269
+ // iPhone 4S that runs iOS 9 matches this, but it is not supported
270
+ // Doesn't work in incognito mode of Safari ≥17 with split screen because of tracking prevention
271
+ screenRatio > 0.65 && screenRatio < 1.53,
272
+ ]) >= 2
273
+ )
274
+ }
275
+
276
+ /**
277
+ * Warning for package users:
278
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
279
+ */
280
+ export function getFullscreenElement(): Element | null {
281
+ const d: any = document
282
+ return d.fullscreenElement || d.msFullscreenElement || d.mozFullScreenElement || d.webkitFullscreenElement || null
283
+ }
284
+
285
+ export function exitFullscreen(): Promise<void> {
286
+ const d: any = document
287
+ // `call` is required because the function throws an error without a proper "this" context
288
+ return (d.exitFullscreen || d.msExitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen).call(d)
289
+ }
290
+
291
+ /**
292
+ * Checks whether the device runs on Android without using user-agent.
293
+ *
294
+ * Warning for package users:
295
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
296
+ */
297
+ export function isAndroid(): boolean {
298
+ const isItChromium = isChromium()
299
+ const isItGecko = isGecko()
300
+ const w = window
301
+ const n = navigator
302
+ // Chrome removes all words "Android" from `navigator` when desktop version is requested
303
+ // Firefox keeps "Android" in `navigator.appVersion` when desktop version is requested
304
+ if (isItChromium) {
305
+ return (
306
+ countTruthy([
307
+ !('SharedWorker' in w),
308
+ // `typechange` is deprecated, but it's still present on Android (tested on Chrome Mobile 117)
309
+ // Removal proposal https://bugs.chromium.org/p/chromium/issues/detail?id=699892
310
+ // Note: this expression returns true on ChromeOS, so additional detectors are required to avoid false-positives
311
+ // n[c] && 'ontypechange' in n[c],
312
+ !('sinkId' in new Audio()),
313
+ ]) >= 2
314
+ )
315
+ } else if (isItGecko) {
316
+ return countTruthy(['onorientationchange' in w, 'orientation' in w, /android/i.test(n.appVersion)]) >= 2
317
+ } else {
318
+ // Only 2 browser engines are presented on Android.
319
+ // Actually, there is also Android 4.1 browser, but it's not worth detecting it at the moment.
320
+ return false
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Checks whether the browser is Samsung Internet without using user-agent.
326
+ * It doesn't check that the browser is based on Chromium, please use `isChromium` before using this function.
327
+ *
328
+ * Warning for package users:
329
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
330
+ */
331
+ export function isSamsungInternet(): boolean {
332
+ // Checked in Samsung Internet 21, 25 and 27
333
+ const n = navigator
334
+ const w = window
335
+ const audioPrototype = Audio.prototype
336
+ const { visualViewport } = w
337
+
338
+ return (
339
+ countTruthy([
340
+ 'srLatency' in audioPrototype,
341
+ 'srChannelCount' in audioPrototype,
342
+ 'devicePosture' in n, // Not available in HTTP
343
+ visualViewport && 'segments' in visualViewport,
344
+ 'getTextInformation' in Image.prototype, // Not available in Samsung Internet 21
345
+ ]) >= 3
346
+ )
347
+ }
@@ -0,0 +1,38 @@
1
+ export function getCommonPixels(images: ImageData[], width: number, height: number ): ImageData {
2
+ let finalData: number[] = [];
3
+ for (let i = 0; i < images[0].data.length; i++) {
4
+ let indice: number[] = [];
5
+ for (let u = 0; u < images.length; u++) {
6
+ indice.push(images[u].data[i]);
7
+ }
8
+ finalData.push(getMostFrequent(indice));
9
+ }
10
+
11
+ const pixelData = finalData;
12
+ const pixelArray = new Uint8ClampedArray(pixelData);
13
+ return new ImageData(pixelArray, width, height);
14
+ }
15
+
16
+ function getMostFrequent(arr: number[]): number {
17
+ if (arr.length === 0) {
18
+ return 0; // Handle empty array case
19
+ }
20
+
21
+ const frequencyMap: { [key: number]: number } = {};
22
+
23
+ // Count occurrences of each number in the array
24
+ for (const num of arr) {
25
+ frequencyMap[num] = (frequencyMap[num] || 0) + 1;
26
+ }
27
+
28
+ let mostFrequent: number = arr[0];
29
+
30
+ // Find the number with the highest frequency
31
+ for (const num in frequencyMap) {
32
+ if (frequencyMap[num] > frequencyMap[mostFrequent]) {
33
+ mostFrequent = parseInt(num, 10);
34
+ }
35
+ }
36
+
37
+ return mostFrequent;
38
+ }
@@ -0,0 +1,161 @@
1
+ /*
2
+ * This file contains functions to work with pure data only (no browser features, DOM, side effects, etc).
3
+ */
4
+
5
+ /**
6
+ * Does the same as Array.prototype.includes but has better typing
7
+ */
8
+ export function includes<THaystack>(haystack: ArrayLike<THaystack>, needle: unknown): needle is THaystack {
9
+ for (let i = 0, l = haystack.length; i < l; ++i) {
10
+ if (haystack[i] === needle) {
11
+ return true
12
+ }
13
+ }
14
+ return false
15
+ }
16
+
17
+ /**
18
+ * Like `!includes()` but with proper typing
19
+ */
20
+ export function excludes<THaystack, TNeedle>(
21
+ haystack: ArrayLike<THaystack>,
22
+ needle: TNeedle,
23
+ ): needle is Exclude<TNeedle, THaystack> {
24
+ return !includes(haystack, needle)
25
+ }
26
+
27
+ /**
28
+ * Be careful, NaN can return
29
+ */
30
+ export function toInt(value: unknown): number {
31
+ return parseInt(value as string)
32
+ }
33
+
34
+ /**
35
+ * Be careful, NaN can return
36
+ */
37
+ export function toFloat(value: unknown): number {
38
+ return parseFloat(value as string)
39
+ }
40
+
41
+ export function replaceNaN<T, U>(value: T, replacement: U): T | U {
42
+ return typeof value === 'number' && isNaN(value) ? replacement : value
43
+ }
44
+
45
+ export function countTruthy(values: unknown[]): number {
46
+ return values.reduce<number>((sum, value) => sum + (value ? 1 : 0), 0)
47
+ }
48
+
49
+ export function round(value: number, base = 1): number {
50
+ if (Math.abs(base) >= 1) {
51
+ return Math.round(value / base) * base
52
+ } else {
53
+ // Sometimes when a number is multiplied by a small number, precision is lost,
54
+ // for example 1234 * 0.0001 === 0.12340000000000001, and it's more precise divide: 1234 / (1 / 0.0001) === 0.1234.
55
+ const counterBase = 1 / base
56
+ return Math.round(value * counterBase) / counterBase
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Parses a CSS selector into tag name with HTML attributes.
62
+ * Only single element selector are supported (without operators like space, +, >, etc).
63
+ *
64
+ * Multiple values can be returned for each attribute. You decide how to handle them.
65
+ */
66
+ export function parseSimpleCssSelector(
67
+ selector: string,
68
+ ): [tag: string | undefined, attributes: Record<string, string[]>] {
69
+ const errorMessage = `Unexpected syntax '${selector}'`
70
+ const tagMatch = /^\s*([a-z-]*)(.*)$/i.exec(selector) as RegExpExecArray
71
+ const tag = tagMatch[1] || undefined
72
+ const attributes: Record<string, string[]> = {}
73
+ const partsRegex = /([.:#][\w-]+|\[.+?\])/gi
74
+
75
+ const addAttribute = (name: string, value: string) => {
76
+ attributes[name] = attributes[name] || []
77
+ attributes[name].push(value)
78
+ }
79
+
80
+ for (;;) {
81
+ const match = partsRegex.exec(tagMatch[2])
82
+ if (!match) {
83
+ break
84
+ }
85
+ const part = match[0]
86
+ switch (part[0]) {
87
+ case '.':
88
+ addAttribute('class', part.slice(1))
89
+ break
90
+ case '#':
91
+ addAttribute('id', part.slice(1))
92
+ break
93
+ case '[': {
94
+ const attributeMatch = /^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(part)
95
+ if (attributeMatch) {
96
+ addAttribute(attributeMatch[1], attributeMatch[4] ?? attributeMatch[5] ?? '')
97
+ } else {
98
+ throw new Error(errorMessage)
99
+ }
100
+ break
101
+ }
102
+ default:
103
+ throw new Error(errorMessage)
104
+ }
105
+ }
106
+
107
+ return [tag, attributes]
108
+ }
109
+
110
+ export function areSetsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean {
111
+ if (set1 === set2) {
112
+ return true
113
+ }
114
+ if (set1.size !== set2.size) {
115
+ return false
116
+ }
117
+
118
+ for (let iter = set1.values(), step = iter.next(); !step.done; step = iter.next()) {
119
+ if (!set2.has(step.value)) {
120
+ return false
121
+ }
122
+ }
123
+ return true
124
+ }
125
+
126
+ export function maxInIterator<T>(iterator: Iterator<T>, getItemScore: (item: T) => number): T | undefined {
127
+ let maxItem: T | undefined
128
+ let maxItemScore: number | undefined
129
+
130
+ for (let step = iterator.next(); !step.done; step = iterator.next()) {
131
+ const item = step.value
132
+ const score = getItemScore(item)
133
+ if (maxItemScore === undefined || score > maxItemScore) {
134
+ maxItem = item
135
+ maxItemScore = score
136
+ }
137
+ }
138
+
139
+ return maxItem
140
+ }
141
+
142
+ /**
143
+ * Converts a string to UTF8 bytes
144
+ */
145
+ export function getUTF8Bytes(input: string): Uint8Array {
146
+ // Benchmark: https://jsbench.me/b6klaaxgwq/1
147
+ // If you want to just count bytes, see solutions at https://jsbench.me/ehklab415e/1
148
+ const result = new Uint8Array(input.length)
149
+ for (let i = 0; i < input.length; i++) {
150
+ // `charCode` is faster than encoding, so we prefer that when it's possible
151
+ const charCode = input.charCodeAt(i)
152
+
153
+ // In case of non-ASCII symbols we use proper encoding
154
+ if (charCode > 127) {
155
+ return new TextEncoder().encode(input)
156
+ }
157
+ result[i] = charCode
158
+ }
159
+ return result
160
+ }
161
+
@@ -0,0 +1,148 @@
1
+ import { MaybePromise, wait } from './async'
2
+ import { parseSimpleCssSelector } from './data'
3
+
4
+ /**
5
+ * Creates and keeps an invisible iframe while the given function runs.
6
+ * The given function is called when the iframe is loaded and has a body.
7
+ * The iframe allows to measure DOM sizes inside itself.
8
+ *
9
+ * Notice: passing an initial HTML code doesn't work in IE.
10
+ *
11
+ * Warning for package users:
12
+ * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
13
+ */
14
+ export async function withIframe<T>(
15
+ action: (iframe: HTMLIFrameElement, iWindow: typeof window) => MaybePromise<T>,
16
+ initialHtml?: string,
17
+ domPollInterval = 50,
18
+ ): Promise<T> {
19
+ const d = document
20
+
21
+ // document.body can be null while the page is loading
22
+ while (!d.body) {
23
+ await wait(domPollInterval)
24
+ }
25
+
26
+ const iframe = d.createElement('iframe')
27
+
28
+ try {
29
+ await new Promise<void>((_resolve, _reject) => {
30
+ let isComplete = false
31
+ const resolve = () => {
32
+ isComplete = true
33
+ _resolve()
34
+ }
35
+ const reject = (error: unknown) => {
36
+ isComplete = true
37
+ _reject(error)
38
+ }
39
+
40
+ iframe.onload = resolve
41
+ iframe.onerror = reject
42
+ const { style } = iframe
43
+ style.setProperty('display', 'block', 'important') // Required for browsers to calculate the layout
44
+ style.position = 'absolute'
45
+ style.top = '0'
46
+ style.left = '0'
47
+ style.visibility = 'hidden'
48
+ if (initialHtml && 'srcdoc' in iframe) {
49
+ iframe.srcdoc = initialHtml
50
+ } else {
51
+ iframe.src = 'about:blank'
52
+ }
53
+ d.body.appendChild(iframe)
54
+
55
+ // WebKit in WeChat doesn't fire the iframe's `onload` for some reason.
56
+ // This code checks for the loading state manually.
57
+ // See https://github.com/fingerprintjs/fingerprintjs/issues/645
58
+ const checkReadyState = () => {
59
+ // The ready state may never become 'complete' in Firefox despite the 'load' event being fired.
60
+ // So an infinite setTimeout loop can happen without this check.
61
+ // See https://github.com/fingerprintjs/fingerprintjs/pull/716#issuecomment-986898796
62
+ if (isComplete) {
63
+ return
64
+ }
65
+
66
+ // Make sure iframe.contentWindow and iframe.contentWindow.document are both loaded
67
+ // The contentWindow.document can miss in JSDOM (https://github.com/jsdom/jsdom).
68
+ if (iframe.contentWindow?.document?.readyState === 'complete') {
69
+ resolve()
70
+ } else {
71
+ setTimeout(checkReadyState, 10)
72
+ }
73
+ }
74
+ checkReadyState()
75
+ })
76
+
77
+ while (!iframe.contentWindow?.document?.body) {
78
+ await wait(domPollInterval)
79
+ }
80
+
81
+ return await action(iframe, iframe.contentWindow as typeof window)
82
+ } finally {
83
+ iframe.parentNode?.removeChild(iframe)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Creates a DOM element that matches the given selector.
89
+ * Only single element selector are supported (without operators like space, +, >, etc).
90
+ */
91
+ export function selectorToElement(selector: string): HTMLElement {
92
+ const [tag, attributes] = parseSimpleCssSelector(selector)
93
+ const element = document.createElement(tag ?? 'div')
94
+ for (const name of Object.keys(attributes)) {
95
+ const value = attributes[name].join(' ')
96
+ // Changing the `style` attribute can cause a CSP error, therefore we change the `style.cssText` property.
97
+ // https://github.com/fingerprintjs/fingerprintjs/issues/733
98
+ if (name === 'style') {
99
+ addStyleString(element.style, value)
100
+ } else {
101
+ element.setAttribute(name, value)
102
+ }
103
+ }
104
+ return element
105
+ }
106
+
107
+ /**
108
+ * Adds CSS styles from a string in such a way that doesn't trigger a CSP warning (unsafe-inline or unsafe-eval)
109
+ */
110
+ export function addStyleString(style: CSSStyleDeclaration, source: string): void {
111
+ // We don't use `style.cssText` because browsers must block it when no `unsafe-eval` CSP is presented: https://csplite.com/csp145/#w3c_note
112
+ // Even though the browsers ignore this standard, we don't use `cssText` just in case.
113
+ for (const property of source.split(';')) {
114
+ const match = /^\s*([\w-]+)\s*:\s*(.+?)(\s*!([\w-]+))?\s*$/.exec(property)
115
+ if (match) {
116
+ const [, name, value, , priority] = match
117
+ style.setProperty(name, value, priority || '') // The last argument can't be undefined in IE11
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Returns true if the code runs in an iframe, and any parent page's origin doesn't match the current origin
124
+ */
125
+ export function isAnyParentCrossOrigin(): boolean {
126
+ let currentWindow: Window = window
127
+
128
+ for (;;) {
129
+ const parentWindow = currentWindow.parent
130
+ if (!parentWindow || parentWindow === currentWindow) {
131
+ return false // The top page is reached
132
+ }
133
+
134
+ try {
135
+ if (parentWindow.location.origin !== currentWindow.location.origin) {
136
+ return true
137
+ }
138
+ } catch (error) {
139
+ // The error is thrown when `origin` is accessed on `parentWindow.location` when the parent is cross-origin
140
+ if (error instanceof Error && error.name === 'SecurityError') {
141
+ return true
142
+ }
143
+ throw error
144
+ }
145
+
146
+ currentWindow = parentWindow
147
+ }
148
+ }