fingerprintjs

Форк
0
/
agent.ts 
206 строк · 6.3 Кб
1
import { version } from '../package.json'
2
import { requestIdleCallbackIfAvailable } from './utils/async'
3
import { UnknownComponents } from './utils/entropy_source'
4
import { x64hash128 } from './utils/hashing'
5
import { errorToObject } from './utils/misc'
6
import loadBuiltinSources, { BuiltinComponents } from './sources'
7
import getConfidence, { Confidence } from './confidence'
8

9
/**
10
 * Options for Fingerprint class loading
11
 */
12
export interface LoadOptions {
13
  /**
14
   * When browser doesn't support `requestIdleCallback` a `setTimeout` will be used. This number is only for Safari and
15
   * old Edge, because Chrome/Blink based browsers support `requestIdleCallback`. The value is in milliseconds.
16
   * @default 50
17
   */
18
  delayFallback?: number
19
  /**
20
   * Whether to print debug messages to the console.
21
   * Required to ease investigations of problems.
22
   */
23
  debug?: boolean
24
}
25

26
/**
27
 * Options for getting visitor identifier
28
 */
29
export interface GetOptions {
30
  /**
31
   * Whether to print debug messages to the console.
32
   *
33
   * @deprecated Use the `debug` option of `load()` instead
34
   */
35
  debug?: boolean
36
}
37

38
/**
39
 * Result of getting a visitor identifier
40
 */
41
export interface GetResult {
42
  /**
43
   * The visitor identifier
44
   */
45
  visitorId: string
46
  /**
47
   * A confidence score that tells how much the agent is sure about the visitor identifier
48
   */
49
  confidence: Confidence
50
  /**
51
   * List of components that has formed the visitor identifier.
52
   *
53
   * Warning! The type of this property is specific but out of Semantic Versioning, i.e. may have incompatible changes
54
   * within a major version. If you want to avoid breaking changes, treat the property as having type
55
   * `UnknownComponents` that is more generic but guarantees backward compatibility within a major version.
56
   */
57
  components: BuiltinComponents
58
  /**
59
   * The fingerprinting algorithm version
60
   *
61
   * @see https://github.com/fingerprintjs/fingerprintjs#version-policy For more details
62
   */
63
  version: string
64
}
65

66
/**
67
 * Agent object that can get visitor identifier
68
 */
69
export interface Agent {
70
  /**
71
   * Gets the visitor identifier
72
   */
73
  get(options?: Readonly<GetOptions>): Promise<GetResult>
74
}
75

76
function componentsToCanonicalString(components: UnknownComponents) {
77
  let result = ''
78
  for (const componentKey of Object.keys(components).sort()) {
79
    const component = components[componentKey]
80
    const value = 'error' in component ? 'error' : JSON.stringify(component.value)
81
    result += `${result ? '|' : ''}${componentKey.replace(/([:|\\])/g, '\\$1')}:${value}`
82
  }
83
  return result
84
}
85

86
export function componentsToDebugString(components: UnknownComponents): string {
87
  return JSON.stringify(
88
    components,
89
    (_key, value) => {
90
      if (value instanceof Error) {
91
        return errorToObject(value)
92
      }
93
      return value
94
    },
95
    2,
96
  )
97
}
98

99
export function hashComponents(components: UnknownComponents): string {
100
  return x64hash128(componentsToCanonicalString(components))
101
}
102

103
/**
104
 * Makes a GetResult implementation that calculates the visitor id hash on demand.
105
 * Designed for optimisation.
106
 */
107
function makeLazyGetResult(components: BuiltinComponents): GetResult {
108
  let visitorIdCache: string | undefined
109

110
  // This function runs very fast, so there is no need to make it lazy
111
  const confidence = getConfidence(components)
112

113
  // A plain class isn't used because its getters and setters aren't enumerable.
114
  return {
115
    get visitorId(): string {
116
      if (visitorIdCache === undefined) {
117
        visitorIdCache = hashComponents(this.components)
118
      }
119
      return visitorIdCache
120
    },
121
    set visitorId(visitorId: string) {
122
      visitorIdCache = visitorId
123
    },
124
    confidence,
125
    components,
126
    version,
127
  }
128
}
129

130
/**
131
 * A delay is required to ensure consistent entropy components.
132
 * See https://github.com/fingerprintjs/fingerprintjs/issues/254
133
 * and https://github.com/fingerprintjs/fingerprintjs/issues/307
134
 * and https://github.com/fingerprintjs/fingerprintjs/commit/945633e7c5f67ae38eb0fea37349712f0e669b18
135
 */
136
export function prepareForSources(delayFallback = 50): Promise<void> {
137
  // A proper deadline is unknown. Let it be twice the fallback timeout so that both cases have the same average time.
138
  return requestIdleCallbackIfAvailable(delayFallback, delayFallback * 2)
139
}
140

141
/**
142
 * The function isn't exported from the index file to not allow to call it without `load()`.
143
 * The hiding gives more freedom for future non-breaking updates.
144
 *
145
 * A factory function is used instead of a class to shorten the attribute names in the minified code.
146
 * Native private class fields could've been used, but TypeScript doesn't allow them with `"target": "es5"`.
147
 */
148
function makeAgent(getComponents: () => Promise<BuiltinComponents>, debug?: boolean): Agent {
149
  const creationTime = Date.now()
150

151
  return {
152
    async get(options) {
153
      const startTime = Date.now()
154
      const components = await getComponents()
155
      const result = makeLazyGetResult(components)
156

157
      if (debug || options?.debug) {
158
        // console.log is ok here because it's under a debug clause
159
        // eslint-disable-next-line no-console
160
        console.log(`Copy the text below to get the debug data:
161

162
\`\`\`
163
version: ${result.version}
164
userAgent: ${navigator.userAgent}
165
timeBetweenLoadAndGet: ${startTime - creationTime}
166
visitorId: ${result.visitorId}
167
components: ${componentsToDebugString(components)}
168
\`\`\``)
169
      }
170

171
      return result
172
    },
173
  }
174
}
175

176
/**
177
 * Sends an unpersonalized AJAX request to collect installation statistics
178
 */
179
function monitor() {
180
  // The FingerprintJS CDN (https://github.com/fingerprintjs/cdn) replaces `window.__fpjs_d_m` with `true`
181
  if (window.__fpjs_d_m || Math.random() >= 0.001) {
182
    return
183
  }
184
  try {
185
    const request = new XMLHttpRequest()
186
    request.open('get', `https://m1.openfpcdn.io/fingerprintjs/v${version}/npm-monitoring`, true)
187
    request.send()
188
  } catch (error) {
189
    // console.error is ok here because it's an unexpected error handler
190
    // eslint-disable-next-line no-console
191
    console.error(error)
192
  }
193
}
194

195
/**
196
 * Builds an instance of Agent and waits a delay required for a proper operation.
197
 */
198
export async function load(options: Readonly<LoadOptions> = {}): Promise<Agent> {
199
  if ((options as { monitoring?: boolean }).monitoring ?? true) {
200
    monitor()
201
  }
202
  const { delayFallback, debug } = options
203
  await prepareForSources(delayFallback)
204
  const getComponents = loadBuiltinSources({ cache: {}, debug })
205
  return makeAgent(getComponents, debug)
206
}
207

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.