fingerprintjs

Форк
0
254 строки · 8.6 Кб
1
import { isChromium, isGecko, isWebKit } from '../utils/browser'
2

3
// Types and constants are used instead of interfaces and enums to avoid this error in projects which use this library:
4
// Exported variable '...' has or is using name '...' from external module "..." but cannot be named.
5

6
/**
7
 * WebGL basic features
8
 */
9
type WebGlBasicsPayload = {
10
  version: string // WebGL 1.0 (OpenGL ES 2.0 Chromium)
11
  vendor: string // WebKit
12
  vendorUnmasked: string // Apple
13
  renderer: string // WebKit WebGL
14
  rendererUnmasked: string // Apple M1
15
  shadingLanguageVersion: string // WebGL GLSL ES 1.0 (OpenGL ES GLSL...
16
}
17

18
/**
19
 * WebGL extended features
20
 */
21
type WebGlExtensionsPayload = {
22
  contextAttributes: string[] // ['alpha=true', 'antialias=true...
23
  parameters: string[] // ['ACTIVE_TEXTURE(33984)', 'ALIASED_LINE_WID...
24
  shaderPrecisions: string[] // ['FRAGMENT_SHADER.LOW_FLOAT=127,127,23...
25
  extensions: string[] | null // ['ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_color...
26
  extensionParameters: string[] // ['COMPRESSED_RGB_S3TC_DXT1_EXT(33776)', 'COMPR...
27
}
28

29
type CanvasContext = WebGLRenderingContext & { readonly canvas: HTMLCanvasElement }
30

31
type Options = {
32
  cache: {
33
    webgl?: {
34
      context: CanvasContext | undefined
35
    }
36
  }
37
}
38

39
/** WebGl context is not available */
40
export const STATUS_NO_GL_CONTEXT = -1
41
/** WebGL context `getParameter` method is not a function */
42
export const STATUS_GET_PARAMETER_NOT_A_FUNCTION = -2
43

44
export type SpecialStatus = typeof STATUS_NO_GL_CONTEXT | typeof STATUS_GET_PARAMETER_NOT_A_FUNCTION
45

46
const validContextParameters = new Set([
47
  10752, 2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961, 2962, 2963, 2964, 2965, 2966, 2967, 2968,
48
  2978, 3024, 3042, 3088, 3089, 3106, 3107, 32773, 32777, 32777, 32823, 32824, 32936, 32937, 32938, 32939, 32968, 32969,
49
  32970, 32971, 3317, 33170, 3333, 3379, 3386, 33901, 33902, 34016, 34024, 34076, 3408, 3410, 3411, 3412, 3413, 3414,
50
  3415, 34467, 34816, 34817, 34818, 34819, 34877, 34921, 34930, 35660, 35661, 35724, 35738, 35739, 36003, 36004, 36005,
51
  36347, 36348, 36349, 37440, 37441, 37443, 7936, 7937, 7938,
52
  // SAMPLE_ALPHA_TO_COVERAGE (32926) and SAMPLE_COVERAGE (32928) are excluded because they trigger a console warning
53
  // in IE, Chrome ≤ 59 and Safari ≤ 13 and give no entropy.
54
])
55
const validExtensionParams = new Set([
56
  34047, // MAX_TEXTURE_MAX_ANISOTROPY_EXT
57
  35723, // FRAGMENT_SHADER_DERIVATIVE_HINT_OES
58
  36063, // MAX_COLOR_ATTACHMENTS_WEBGL
59
  34852, // MAX_DRAW_BUFFERS_WEBGL
60
  34853, // DRAW_BUFFER0_WEBGL
61
  34854, // DRAW_BUFFER1_WEBGL
62
  34229, // VERTEX_ARRAY_BINDING_OES
63
  36392, // TIMESTAMP_EXT
64
  36795, // GPU_DISJOINT_EXT
65
  38449, // MAX_VIEWS_OVR
66
])
67
const shaderTypes = ['FRAGMENT_SHADER', 'VERTEX_SHADER'] as const
68
const precisionTypes = ['LOW_FLOAT', 'MEDIUM_FLOAT', 'HIGH_FLOAT', 'LOW_INT', 'MEDIUM_INT', 'HIGH_INT'] as const
69
const rendererInfoExtensionName = 'WEBGL_debug_renderer_info'
70
const polygonModeExtensionName = 'WEBGL_polygon_mode'
71

72
/**
73
 * Gets the basic and simple WebGL parameters
74
 */
75
export function getWebGlBasics({ cache }: Options): WebGlBasicsPayload | SpecialStatus {
76
  const gl = getWebGLContext(cache)
77
  if (!gl) {
78
    return STATUS_NO_GL_CONTEXT
79
  }
80

81
  if (!isValidParameterGetter(gl)) {
82
    return STATUS_GET_PARAMETER_NOT_A_FUNCTION
83
  }
84

85
  const debugExtension = shouldAvoidDebugRendererInfo() ? null : gl.getExtension(rendererInfoExtensionName)
86

87
  return {
88
    version: gl.getParameter(gl.VERSION)?.toString() || '',
89
    vendor: gl.getParameter(gl.VENDOR)?.toString() || '',
90
    vendorUnmasked: debugExtension ? gl.getParameter(debugExtension.UNMASKED_VENDOR_WEBGL)?.toString() : '',
91
    renderer: gl.getParameter(gl.RENDERER)?.toString() || '',
92
    rendererUnmasked: debugExtension ? gl.getParameter(debugExtension.UNMASKED_RENDERER_WEBGL)?.toString() : '',
93
    shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION)?.toString() || '',
94
  }
95
}
96

97
/**
98
 * Gets the advanced and massive WebGL parameters and extensions
99
 */
100
export function getWebGlExtensions({ cache }: Options): WebGlExtensionsPayload | SpecialStatus {
101
  const gl = getWebGLContext(cache)
102
  if (!gl) {
103
    return STATUS_NO_GL_CONTEXT
104
  }
105

106
  if (!isValidParameterGetter(gl)) {
107
    return STATUS_GET_PARAMETER_NOT_A_FUNCTION
108
  }
109

110
  const extensions = gl.getSupportedExtensions()
111
  const contextAttributes = gl.getContextAttributes()
112

113
  // Features
114
  const attributes: string[] = []
115
  const parameters: string[] = []
116
  const extensionParameters: string[] = []
117
  const shaderPrecisions: string[] = []
118

119
  // Context attributes
120
  if (contextAttributes) {
121
    for (const attributeName of Object.keys(contextAttributes) as (keyof WebGLContextAttributes)[]) {
122
      attributes.push(`${attributeName}=${contextAttributes[attributeName]}`)
123
    }
124
  }
125

126
  // Context parameters
127
  const constants = getConstantsFromPrototype(gl)
128
  for (const constant of constants) {
129
    const code = gl[constant] as number
130
    parameters.push(`${constant}=${code}${validContextParameters.has(code) ? `=${gl.getParameter(code)}` : ''}`)
131
  }
132

133
  // Extension parameters
134
  if (extensions) {
135
    for (const name of extensions) {
136
      if (
137
        (name === rendererInfoExtensionName && shouldAvoidDebugRendererInfo()) ||
138
        (name === polygonModeExtensionName && shouldAvoidPolygonModeExtensions())
139
      ) {
140
        continue
141
      }
142

143
      const extension = gl.getExtension(name)
144
      if (!extension) {
145
        continue
146
      }
147

148
      for (const constant of getConstantsFromPrototype(extension)) {
149
        const code = extension[constant]
150
        extensionParameters.push(
151
          `${constant}=${code}${validExtensionParams.has(code) ? `=${gl.getParameter(code)}` : ''}`,
152
        )
153
      }
154
    }
155
  }
156

157
  // Shader precision
158
  for (const shaderType of shaderTypes) {
159
    for (const precisionType of precisionTypes) {
160
      const shaderPrecision = getShaderPrecision(gl, shaderType, precisionType)
161
      shaderPrecisions.push(`${shaderType}.${precisionType}=${shaderPrecision.join(',')}`)
162
    }
163
  }
164

165
  // Postprocess
166
  extensionParameters.sort()
167
  parameters.sort()
168

169
  return {
170
    contextAttributes: attributes,
171
    parameters: parameters,
172
    shaderPrecisions: shaderPrecisions,
173
    extensions: extensions,
174
    extensionParameters: extensionParameters,
175
  }
176
}
177

178
/**
179
 * This function usually takes the most time to execute in all the sources, therefore we cache its result.
180
 *
181
 * Warning for package users:
182
 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk.
183
 */
184
export function getWebGLContext(cache: Options['cache']) {
185
  if (cache.webgl) {
186
    return cache.webgl.context
187
  }
188

189
  const canvas = document.createElement('canvas')
190
  let context: CanvasContext | undefined
191

192
  canvas.addEventListener('webglCreateContextError', () => (context = undefined))
193

194
  for (const type of ['webgl', 'experimental-webgl']) {
195
    try {
196
      context = canvas.getContext(type) as CanvasContext
197
    } catch {
198
      // Ok, continue
199
    }
200
    if (context) {
201
      break
202
    }
203
  }
204

205
  cache.webgl = { context }
206
  return context
207
}
208

209
/**
210
 * https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat
211
 * https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat
212
 * https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.12
213
 */
214
function getShaderPrecision(
215
  gl: WebGLRenderingContext,
216
  shaderType: typeof shaderTypes[number],
217
  precisionType: typeof precisionTypes[number],
218
) {
219
  const shaderPrecision = gl.getShaderPrecisionFormat(gl[shaderType], gl[precisionType])
220
  return shaderPrecision ? [shaderPrecision.rangeMin, shaderPrecision.rangeMax, shaderPrecision.precision] : []
221
}
222

223
function getConstantsFromPrototype<K>(obj: K): Array<Extract<keyof K, string>> {
224
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
  const keys = Object.keys((obj as any).__proto__) as Array<keyof K>
226
  return keys.filter(isConstantLike)
227
}
228

229
function isConstantLike<K>(key: K): key is Extract<K, string> {
230
  return typeof key === 'string' && !key.match(/[^A-Z0-9_x]/)
231
}
232

233
/**
234
 * Some browsers print a console warning when the WEBGL_debug_renderer_info extension is requested.
235
 * JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers.
236
 */
237
export function shouldAvoidDebugRendererInfo(): boolean {
238
  return isGecko()
239
}
240

241
/**
242
 * Some browsers print a console warning when the WEBGL_polygon_mode extension is requested.
243
 * JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers.
244
 */
245
export function shouldAvoidPolygonModeExtensions(): boolean {
246
  return isChromium() || isWebKit()
247
}
248

249
/**
250
 * Some unknown browsers have no `getParameter` method
251
 */
252
function isValidParameterGetter(gl: WebGLRenderingContext) {
253
  return typeof gl.getParameter === 'function'
254
}
255

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

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

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

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