UNPKG

fingerprint-generator

Version:

NodeJS package for generating realistic browser fingerprints.

176 lines 7.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FingerprintGenerator = void 0; const generative_bayesian_network_1 = require("generative-bayesian-network"); const header_generator_1 = require("header-generator"); const constants_1 = require("./constants"); /** * Fingerprint generator - Class for realistic browser fingerprint generation. */ class FingerprintGenerator extends header_generator_1.HeaderGenerator { /** * @param options Default header generation options used - unless overridden. */ constructor(options = {}) { super(options); Object.defineProperty(this, "fingerprintGeneratorNetwork", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "fingerprintGlobalOptions", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.fingerprintGlobalOptions = { screen: options.screen, mockWebRTC: options.mockWebRTC, slim: options.slim, }; this.fingerprintGeneratorNetwork = new generative_bayesian_network_1.BayesianNetwork({ path: `${__dirname}/data_files/fingerprint-network-definition.zip`, }); } /** * Generates a fingerprint and a matching set of ordered headers using a combination of the default options specified in the constructor * and their possible overrides provided here. * @param options Overrides default `FingerprintGenerator` options. * @param requestDependentHeaders Specifies known values of headers dependent on the particular request. */ getFingerprint(options = {}, requestDependentHeaders = {}) { const filteredValues = {}; for (const [key, value] of Object.entries(options)) { if (!value) { delete options[key]; } } options = { ...this.fingerprintGlobalOptions, ...options, }; const partialCSP = (() => { const extensiveScreen = options.screen && Object.keys(options.screen).length !== 0; const shouldUseExtensiveConstraints = extensiveScreen; if (!shouldUseExtensiveConstraints) return undefined; filteredValues.screen = extensiveScreen ? this.fingerprintGeneratorNetwork.nodesByName.screen.possibleValues.filter((screenString) => { const screen = JSON.parse(screenString.split(constants_1.STRINGIFIED_PREFIX)[1]); return (screen.width >= (options.screen?.minWidth ?? 0) && screen.width <= (options.screen?.maxWidth ?? 1e5) && screen.height >= (options.screen?.minHeight ?? 0) && screen.height <= (options.screen?.maxHeight ?? 1e5)); }) : undefined; try { return generative_bayesian_network_1.utils.getConstraintClosure(this.fingerprintGeneratorNetwork, filteredValues); } catch (e) { if (options?.strict) throw e; delete filteredValues.screen; return undefined; } })(); for (let generateRetries = 0; generateRetries < 10; generateRetries++) { // Generate headers consistent with the inputs to get input-compatible user-agent and accept-language headers needed later const headers = super.getHeaders(options, requestDependentHeaders, partialCSP?.userAgent); const userAgent = 'User-Agent' in headers ? headers['User-Agent'] : headers['user-agent']; // Generate fingerprint consistent with the generated user agent const fingerprint = this.fingerprintGeneratorNetwork.generateConsistentSampleWhenPossible({ ...filteredValues, userAgent: [userAgent], }); /* Delete any missing attributes and unpack any object/array-like attributes * that have been packed together to make the underlying network simpler */ for (const attribute of Object.keys(fingerprint)) { if (fingerprint[attribute] === constants_1.MISSING_VALUE_DATASET_TOKEN) { fingerprint[attribute] = null; } else if (fingerprint[attribute].startsWith(constants_1.STRINGIFIED_PREFIX)) { fingerprint[attribute] = JSON.parse(fingerprint[attribute].slice(constants_1.STRINGIFIED_PREFIX.length)); } } if (!fingerprint.screen) continue; // fix? sometimes, fingerprints are generated 90% empty/null. This is just a workaround. // Manually add the set of accepted languages required by the input const acceptLanguageHeaderValue = 'Accept-Language' in headers ? headers['Accept-Language'] : headers['accept-language']; const acceptedLanguages = []; for (const locale of acceptLanguageHeaderValue.split(',')) { acceptedLanguages.push(locale.split(';')[0]); } fingerprint.languages = acceptedLanguages; return { fingerprint: { ...this.transformFingerprint(fingerprint), mockWebRTC: options.mockWebRTC ?? this.fingerprintGlobalOptions.mockWebRTC ?? false, slim: options.slim ?? this.fingerprintGlobalOptions.slim ?? false, }, headers, }; } throw new Error('Failed to generate a consistent fingerprint after 10 attempts'); } /** * Transforms fingerprint to the final scheme, more suitable for fingerprint manipulation and injection. * This schema is used in the `fingerprint-injector`. * @param fingerprint Fingerprint to be transformed. * @returns Transformed fingerprint. */ transformFingerprint(fingerprint) { const { userAgent, userAgentData, doNotTrack, appCodeName, appName, appVersion, oscpu, webdriver, languages, platform, deviceMemory, hardwareConcurrency, product, productSub, vendor, vendorSub, maxTouchPoints, extraProperties, screen, pluginsData, audioCodecs, videoCodecs, battery, videoCard, multimediaDevices, fonts, } = fingerprint; const parsedMemory = parseInt(deviceMemory, 10); const parsedTouchPoints = parseInt(maxTouchPoints, 10); const navigator = { userAgent, userAgentData, language: languages[0], languages, platform, deviceMemory: Number.isNaN(parsedMemory) ? null : parsedMemory, // Firefox does not have deviceMemory available hardwareConcurrency: parseInt(hardwareConcurrency, 10), maxTouchPoints: Number.isNaN(parsedTouchPoints) ? 0 : parsedTouchPoints, product, productSub, vendor, vendorSub, doNotTrack, appCodeName, appName, appVersion, oscpu, extraProperties, webdriver, }; return { screen, navigator, audioCodecs, videoCodecs, pluginsData, battery, videoCard, multimediaDevices, fonts, }; } } exports.FingerprintGenerator = FingerprintGenerator; //# sourceMappingURL=fingerprint-generator.js.map