fingerprintjs
254 строки · 8.6 Кб
1import { 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*/
9type WebGlBasicsPayload = {10version: string // WebGL 1.0 (OpenGL ES 2.0 Chromium)11vendor: string // WebKit12vendorUnmasked: string // Apple13renderer: string // WebKit WebGL14rendererUnmasked: string // Apple M115shadingLanguageVersion: string // WebGL GLSL ES 1.0 (OpenGL ES GLSL...16}
17
18/**
19* WebGL extended features
20*/
21type WebGlExtensionsPayload = {22contextAttributes: string[] // ['alpha=true', 'antialias=true...23parameters: string[] // ['ACTIVE_TEXTURE(33984)', 'ALIASED_LINE_WID...24shaderPrecisions: string[] // ['FRAGMENT_SHADER.LOW_FLOAT=127,127,23...25extensions: string[] | null // ['ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_color...26extensionParameters: string[] // ['COMPRESSED_RGB_S3TC_DXT1_EXT(33776)', 'COMPR...27}
28
29type CanvasContext = WebGLRenderingContext & { readonly canvas: HTMLCanvasElement }30
31type Options = {32cache: {33webgl?: {34context: CanvasContext | undefined35}36}37}
38
39/** WebGl context is not available */
40export const STATUS_NO_GL_CONTEXT = -141/** WebGL context `getParameter` method is not a function */
42export const STATUS_GET_PARAMETER_NOT_A_FUNCTION = -243
44export type SpecialStatus = typeof STATUS_NO_GL_CONTEXT | typeof STATUS_GET_PARAMETER_NOT_A_FUNCTION45
46const validContextParameters = new Set([4710752, 2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961, 2962, 2963, 2964, 2965, 2966, 2967, 2968,482978, 3024, 3042, 3088, 3089, 3106, 3107, 32773, 32777, 32777, 32823, 32824, 32936, 32937, 32938, 32939, 32968, 32969,4932970, 32971, 3317, 33170, 3333, 3379, 3386, 33901, 33902, 34016, 34024, 34076, 3408, 3410, 3411, 3412, 3413, 3414,503415, 34467, 34816, 34817, 34818, 34819, 34877, 34921, 34930, 35660, 35661, 35724, 35738, 35739, 36003, 36004, 36005,5136347, 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 warning53// in IE, Chrome ≤ 59 and Safari ≤ 13 and give no entropy.54])55const validExtensionParams = new Set([5634047, // MAX_TEXTURE_MAX_ANISOTROPY_EXT5735723, // FRAGMENT_SHADER_DERIVATIVE_HINT_OES5836063, // MAX_COLOR_ATTACHMENTS_WEBGL5934852, // MAX_DRAW_BUFFERS_WEBGL6034853, // DRAW_BUFFER0_WEBGL6134854, // DRAW_BUFFER1_WEBGL6234229, // VERTEX_ARRAY_BINDING_OES6336392, // TIMESTAMP_EXT6436795, // GPU_DISJOINT_EXT6538449, // MAX_VIEWS_OVR66])67const shaderTypes = ['FRAGMENT_SHADER', 'VERTEX_SHADER'] as const68const precisionTypes = ['LOW_FLOAT', 'MEDIUM_FLOAT', 'HIGH_FLOAT', 'LOW_INT', 'MEDIUM_INT', 'HIGH_INT'] as const69const rendererInfoExtensionName = 'WEBGL_debug_renderer_info'70const polygonModeExtensionName = 'WEBGL_polygon_mode'71
72/**
73* Gets the basic and simple WebGL parameters
74*/
75export function getWebGlBasics({ cache }: Options): WebGlBasicsPayload | SpecialStatus {76const gl = getWebGLContext(cache)77if (!gl) {78return STATUS_NO_GL_CONTEXT79}80
81if (!isValidParameterGetter(gl)) {82return STATUS_GET_PARAMETER_NOT_A_FUNCTION83}84
85const debugExtension = shouldAvoidDebugRendererInfo() ? null : gl.getExtension(rendererInfoExtensionName)86
87return {88version: gl.getParameter(gl.VERSION)?.toString() || '',89vendor: gl.getParameter(gl.VENDOR)?.toString() || '',90vendorUnmasked: debugExtension ? gl.getParameter(debugExtension.UNMASKED_VENDOR_WEBGL)?.toString() : '',91renderer: gl.getParameter(gl.RENDERER)?.toString() || '',92rendererUnmasked: debugExtension ? gl.getParameter(debugExtension.UNMASKED_RENDERER_WEBGL)?.toString() : '',93shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION)?.toString() || '',94}95}
96
97/**
98* Gets the advanced and massive WebGL parameters and extensions
99*/
100export function getWebGlExtensions({ cache }: Options): WebGlExtensionsPayload | SpecialStatus {101const gl = getWebGLContext(cache)102if (!gl) {103return STATUS_NO_GL_CONTEXT104}105
106if (!isValidParameterGetter(gl)) {107return STATUS_GET_PARAMETER_NOT_A_FUNCTION108}109
110const extensions = gl.getSupportedExtensions()111const contextAttributes = gl.getContextAttributes()112
113// Features114const attributes: string[] = []115const parameters: string[] = []116const extensionParameters: string[] = []117const shaderPrecisions: string[] = []118
119// Context attributes120if (contextAttributes) {121for (const attributeName of Object.keys(contextAttributes) as (keyof WebGLContextAttributes)[]) {122attributes.push(`${attributeName}=${contextAttributes[attributeName]}`)123}124}125
126// Context parameters127const constants = getConstantsFromPrototype(gl)128for (const constant of constants) {129const code = gl[constant] as number130parameters.push(`${constant}=${code}${validContextParameters.has(code) ? `=${gl.getParameter(code)}` : ''}`)131}132
133// Extension parameters134if (extensions) {135for (const name of extensions) {136if (137(name === rendererInfoExtensionName && shouldAvoidDebugRendererInfo()) ||138(name === polygonModeExtensionName && shouldAvoidPolygonModeExtensions())139) {140continue141}142
143const extension = gl.getExtension(name)144if (!extension) {145continue146}147
148for (const constant of getConstantsFromPrototype(extension)) {149const code = extension[constant]150extensionParameters.push(151`${constant}=${code}${validExtensionParams.has(code) ? `=${gl.getParameter(code)}` : ''}`,152)153}154}155}156
157// Shader precision158for (const shaderType of shaderTypes) {159for (const precisionType of precisionTypes) {160const shaderPrecision = getShaderPrecision(gl, shaderType, precisionType)161shaderPrecisions.push(`${shaderType}.${precisionType}=${shaderPrecision.join(',')}`)162}163}164
165// Postprocess166extensionParameters.sort()167parameters.sort()168
169return {170contextAttributes: attributes,171parameters: parameters,172shaderPrecisions: shaderPrecisions,173extensions: extensions,174extensionParameters: 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*/
184export function getWebGLContext(cache: Options['cache']) {185if (cache.webgl) {186return cache.webgl.context187}188
189const canvas = document.createElement('canvas')190let context: CanvasContext | undefined191
192canvas.addEventListener('webglCreateContextError', () => (context = undefined))193
194for (const type of ['webgl', 'experimental-webgl']) {195try {196context = canvas.getContext(type) as CanvasContext197} catch {198// Ok, continue199}200if (context) {201break202}203}204
205cache.webgl = { context }206return context207}
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*/
214function getShaderPrecision(215gl: WebGLRenderingContext,216shaderType: typeof shaderTypes[number],217precisionType: typeof precisionTypes[number],218) {219const shaderPrecision = gl.getShaderPrecisionFormat(gl[shaderType], gl[precisionType])220return shaderPrecision ? [shaderPrecision.rangeMin, shaderPrecision.rangeMax, shaderPrecision.precision] : []221}
222
223function getConstantsFromPrototype<K>(obj: K): Array<Extract<keyof K, string>> {224// eslint-disable-next-line @typescript-eslint/no-explicit-any225const keys = Object.keys((obj as any).__proto__) as Array<keyof K>226return keys.filter(isConstantLike)227}
228
229function isConstantLike<K>(key: K): key is Extract<K, string> {230return 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*/
237export function shouldAvoidDebugRendererInfo(): boolean {238return 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*/
245export function shouldAvoidPolygonModeExtensions(): boolean {246return isChromium() || isWebKit()247}
248
249/**
250* Some unknown browsers have no `getParameter` method
251*/
252function isValidParameterGetter(gl: WebGLRenderingContext) {253return typeof gl.getParameter === 'function'254}
255