UNPKG

node-device-detector

Version:

Nodejs device detector (port matomo-org/device-detector)

1,011 lines (895 loc) 28.5 kB
const helper = require('./parser/helper'); const { attr } = helper; // device parsers const MobileParser = require('./parser/device/mobile'); const NotebookParser = require('./parser/device/notebook'); const HbbTvParser = require('./parser/device/hbb-tv'); const ShellTvParser = require('./parser/device/shell-tv'); const ConsoleParser = require('./parser/device/console'); const CarBrowserParser = require('./parser/device/car-browser'); const CameraParser = require('./parser/device/camera'); const PortableMediaPlayerParser = require( './parser/device/portable-media-player'); // client parsers const MobileAppParser = require('./parser/client/mobile-app'); const MediaPlayerParser = require('./parser/client/media-player'); const BrowserParser = require('./parser/client/browser'); const LibraryParser = require('./parser/client/library'); const FeedReaderParser = require('./parser/client/feed-reader'); const PIMParser = require('./parser/client/pim'); // os parsers const OsParser = require('./parser/os-abstract-parser'); // bot parsers const BotParser = require('./parser/bot-abstract-parser'); // vendor fragment parsers const VendorFragmentParser = require( './parser/vendor-fragment-abstract-parser'); // other parsers const AliasDevice = require('./parser/device/alias-device'); const IndexerClient = require('./parser/client/indexer-client'); const IndexerDevice = require('./parser/device/indexer-device'); const InfoDevice = require('./parser/device/info-device'); // checks const DeviceTrusted = require('./parser/device/device-trusted'); // const, lists, parser names const DEVICE_TYPE = require('./parser/const/device-type'); const CLIENT_TYPE = require('./parser/const/client-type'); const APPLE_OS_LIST = require('./parser/const/apple-os'); const DESKTOP_OS_LIST = require('./parser/const/desktop-os'); const DEVICE_PARSER_LIST = require('./parser/const/device-parser'); const CLIENT_PARSER_LIST = require('./parser/const/client-parser'); const FORM_FACTORS_MAPPING = require('./parser/const/form-factor-mapping'); const MOBILE_BROWSER_LIST = require('./parser/client/browser-short-mobile'); const VENDOR_FRAGMENT_PARSER = 'VendorFragment'; const OS_PARSER = 'Os'; const BOT_PARSER = 'Bot'; // =========================== // static private parser init // =========================== const aliasDevice = new AliasDevice(); aliasDevice.setReplaceBrand(false); const infoDevice = new InfoDevice(); class DeviceDetector { vendorParserList = {}; osParserList = {}; botParserList = {}; deviceParserList = {}; clientParserList = {}; #skipBotDetection = false; #deviceIndexes = false; #osIndexes = false; #clientIndexes = false; #deviceAliasCode = false; #deviceTrusted = false; #deviceInfo = false; #clientVersionTruncate = null; #osVersionTruncate = null; #maxUserAgentSize = null; /** * @param {DeviceDetectorOptions} options **/ constructor(options = {}) { this.init(); this.skipBotDetection = attr(options, 'skipBotDetection', false); this.osVersionTruncate = attr(options, 'osVersionTruncate', null); this.clientVersionTruncate = attr(options, 'clientVersionTruncate', null); this.deviceIndexes = attr(options, 'deviceIndexes', false); this.clientIndexes = attr(options, 'clientIndexes', false); this.osIndexes = attr(options, 'osIndexes', false); this.deviceAliasCode = attr(options, 'deviceAliasCode', false); this.maxUserAgentSize = attr(options, 'maxUserAgentSize', null); this.deviceTrusted = attr(options, 'deviceTrusted', false); this.deviceInfo = attr(options, 'deviceInfo', false); } init() { this.addParseOs(OS_PARSER, new OsParser()); this.addParseClient(CLIENT_PARSER_LIST.FEED_READER, new FeedReaderParser()); this.addParseClient(CLIENT_PARSER_LIST.MOBILE_APP, new MobileAppParser()); this.addParseClient(CLIENT_PARSER_LIST.MEDIA_PLAYER, new MediaPlayerParser()); this.addParseClient(CLIENT_PARSER_LIST.PIM, new PIMParser()); this.addParseClient(CLIENT_PARSER_LIST.BROWSER, new BrowserParser()); this.addParseClient(CLIENT_PARSER_LIST.LIBRARY, new LibraryParser()); this.addParseDevice(DEVICE_PARSER_LIST.HBBTV, new HbbTvParser()); this.addParseDevice(DEVICE_PARSER_LIST.SHELLTV, new ShellTvParser()); this.addParseDevice(DEVICE_PARSER_LIST.NOTEBOOK, new NotebookParser()); this.addParseDevice(DEVICE_PARSER_LIST.CONSOLE, new ConsoleParser()); this.addParseDevice(DEVICE_PARSER_LIST.CAR_BROWSER, new CarBrowserParser()); this.addParseDevice(DEVICE_PARSER_LIST.CAMERA, new CameraParser()); this.addParseDevice(DEVICE_PARSER_LIST.PORTABLE_MEDIA_PLAYER, new PortableMediaPlayerParser()); this.addParseDevice(DEVICE_PARSER_LIST.MOBILE, new MobileParser()); this.addParseVendor(VENDOR_FRAGMENT_PARSER, new VendorFragmentParser()); this.addParseBot(BOT_PARSER, new BotParser()); } set deviceTrusted(stage) { this.#deviceTrusted = stage; } get deviceTrusted() { return this.#deviceTrusted; } set deviceInfo(stage) { this.#deviceInfo = stage; } get deviceInfo() { return this.#deviceInfo; } /** * Set string size limit for the useragent * @param {number} size */ set maxUserAgentSize(size) { this.#maxUserAgentSize = size; for (let name in this.clientParserList) { this.clientParserList[name].setMaxUserAgentSize(size); } for (let name in this.osParserList) { this.osParserList[name].setMaxUserAgentSize(size); } for (let name in this.deviceParserList) { this.deviceParserList[name].setMaxUserAgentSize(size); } } /** * Get string size limit for the useragent * @returns {null|number} */ get maxUserAgentSize() { return this.#maxUserAgentSize; } get skipBotDetection() { return this.#skipBotDetection; } set skipBotDetection(discard) { this.#skipBotDetection = discard; } /** * @param {boolean} stage - true use indexes, false not use indexes */ set deviceIndexes(stage) { this.#deviceIndexes = stage; } /** * @return {boolean} - true use indexes, false not use indexes */ get deviceIndexes() { return this.#deviceIndexes; } /** * true use indexes, false not use indexes * @param {boolean} status */ set clientIndexes(status) { this.#clientIndexes = status; for (let name in this.clientParserList) { this.clientParserList[name].clientIndexes = status; } } /** * @return {boolean} */ get clientIndexes() { return this.#clientIndexes; } /** * true use indexes, false not use indexes * @param status */ set osIndexes(status) { this.#osIndexes = status; for (let name in this.osParserList) { this.osParserList[name].osIndexes = status; } } /** * true use indexes, false not use indexes * @return {boolean} */ get osIndexes() { return this.#osIndexes; } /** * true use deviceAliasCode, false not use deviceAliasCode * @param {boolean} status */ set deviceAliasCode(status) { this.#deviceAliasCode = status; } /** * true use deviceAliasCode, false not use deviceAliasCode * @return {boolean} */ get deviceAliasCode() { return this.#deviceAliasCode; } /** * set truncate client version (default null - all) * @param value */ set clientVersionTruncate(value) { this.#clientVersionTruncate = value; for (let name in this.clientParserList) { this.clientParserList[name].setVersionTruncation(value); } } /** * get truncate client version * @return int|null */ get clientVersionTruncate() { return this.#clientVersionTruncate; } /** * set truncate os version (default null - all) * @param value */ set osVersionTruncate(value) { this.#osVersionTruncate = value; for (let name in this.osParserList) { this.osParserList[name].setVersionTruncation(value); } } /** * get truncate os version * @return {null|number} */ get osVersionTruncate() { return this.#osVersionTruncate; } /** * set truncate os version (default null - all) * @deprecated the method will be removed in v2.0 (use detector.osVersionTruncate) * @param value */ setOsVersionTruncate(value) { this.osVersionTruncate = value; } /** * set truncate client version (default null - all) * @deprecated the method will be removed in v2.0 (use detector.clientVersionTruncate) * @param value */ setClientVersionTruncate(value) { this.clientVersionTruncate = value; } /** * get all device types * @return {string[]} */ getAvailableDeviceTypes() { return Object.values(DEVICE_TYPE); } /** * get all brands * @return {string[]} */ getAvailableBrands() { return this.getParseDevice(DEVICE_PARSER_LIST.MOBILE).getAvailableBrands(); } /** * has device brand * @param brand * @return {boolean} */ hasBrand(brand) { return this.getParseDevice(DEVICE_PARSER_LIST.MOBILE).getCollectionBrands()[brand] !== void 0; } /** * get all browsers * @returns {string[]} */ getAvailableBrowsers() { return this.getParseClient(CLIENT_PARSER_LIST.BROWSER).getAvailableBrowsers(); } /** * get device parser by name * @param {string} name * @return {DeviceParserAbstract|null} */ getParseDevice(name) { return this.deviceParserList[name] ? this.deviceParserList[name] : null; } /** * get client parser by name * @param {string} name * @return {*} */ getParseClient(name) { return this.clientParserList[name] ? this.clientParserList[name] : null; } /** * get bot parser by name * @param name * @return {*} */ getParseBot(name) { return this.botParserList[name] ? this.botParserList[name] : null; } /** * get os parser by name * @param name * @return {*} */ getParseOs(name) { return this.osParserList[name] ? this.osParserList[name] : null; } /** * get vendor parser by name (specific parsers) * @param {string} name * @return {*} */ getParseVendor(name) { return this.vendorParserList[name] ? this.vendorParserList[name] : null; } /** * get alias device parser * @returns {AliasDevice} */ getParseAliasDevice() { return aliasDevice; } /** * get info device parser * @returns {InfoDevice} */ getParseInfoDevice() { return infoDevice; } getDeviceParserNames() { return DEVICE_PARSER_LIST; } getClientParserNames() { return CLIENT_PARSER_LIST; } getBotParserNames() { return { 'DEFAULT': BOT_PARSER }; } getOsParserNames() { return { 'DEFAULT': OS_PARSER }; } /** * add device type parser * @param {string} name * @param parser */ addParseDevice(name, parser) { this.deviceParserList[name] = parser; } /** * add os type parser * @param {string} name * @param {OsAbstractParser} parser */ addParseOs(name, parser) { this.osParserList[name] = parser; } /** * add bot type parser * @param {string} name * @param {BotAbstractParser} parser */ addParseBot(name, parser) { this.botParserList[name] = parser; } /** * add client type parser * @param {string} name * @param {ClientAbstractParser} parser */ addParseClient(name, parser) { this.clientParserList[name] = parser; } /** * add vendor type parser * @param {string} name * @param {VendorFragmentAbstractParser} parser */ addParseVendor(name, parser) { this.vendorParserList[name] = parser; } /** * parse OS * @param {string} userAgent * @param {ResultClientHints|{}} clientHints * @return {ResultOs} */ parseOs(userAgent, clientHints = {}) { let result = {}; for (let name in this.osParserList) { let parser = this.osParserList[name]; let resultMerge = parser.parse(userAgent, clientHints); if (resultMerge) { result = Object.assign(result, resultMerge); break; } } return result; } /** * prepare user agent for restrict rules * @param {string|*} userAgent * @returns {string|*} */ prepareUserAgent(userAgent) { if (userAgent && this.maxUserAgentSize && this.maxUserAgentSize < userAgent.length) { return String(userAgent).substring(0, this.maxUserAgentSize); } return userAgent; } /** * parse device type * @param {string} userAgent * @param {ResultOs} osData * @param {ResultClient} clientData * @param {ResultDevice} deviceData * @param {ResultClientHints} clientHints * @return {DeviceType} */ parseDeviceType( userAgent, osData, clientData, deviceData, clientHints ) { userAgent = this.prepareUserAgent(userAgent); let osName = attr(osData, 'name', ''); let osFamily = attr(osData, 'family', ''); let osVersion = attr(osData, 'version', ''); let clientType = attr(clientData, 'type', ''); let clientShortName = attr(clientData, 'short_name', ''); let clientName = attr(clientData, 'name', ''); let clientFamily = attr(clientData, 'family', ''); let deviceType = attr(deviceData, 'type', ''); // client hint detect device type if ( deviceType === '' && clientHints && clientHints.device && clientHints.device.model && clientHints.formFactors.length ) { for(const [type, deviceTypeName] of Object.entries(FORM_FACTORS_MAPPING)) { if (clientHints.formFactors.includes(type)) { deviceType = '' + deviceTypeName; break; } } } /** * All devices containing VR fragment are assumed to be a wearable */ if (deviceType === '' && helper.hasVRFragment(userAgent)) { deviceType = DEVICE_TYPE.WEARABLE; } /** * Chrome on Android passes the device type based on the keyword 'Mobile' * If it is present the device should be a smartphone, otherwise it's a tablet * See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent * Note: We do not check for browser (family) here, as there might be mobile apps using Chrome, that won't have * a detected browser, but can still be detected. So we check the useragent for Chrome instead. */ if (deviceType === '' && osFamily === 'Android' && helper.matchUserAgent('Chrome/[.0-9]*', userAgent)) { deviceType = helper.matchUserAgent('(Mobile|eliboM)', userAgent) ? DEVICE_TYPE.SMARTPHONE : DEVICE_TYPE.TABLET; } /** * Some UA contain the fragment 'Pad/APad', so we assume those devices as tablets */ if (deviceType === DEVICE_TYPE.SMARTPHONE && helper.matchUserAgent('Pad/APad', userAgent)) { deviceType = DEVICE_TYPE.TABLET; } /** * Some UA contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices as tablets */ if (deviceType === '' && (helper.hasAndroidTableFragment(userAgent) || helper.hasOperaTableFragment(userAgent))) { deviceType = DEVICE_TYPE.TABLET; } /** * Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices as smartphones */ if (deviceType === '' && helper.hasAndroidMobileFragment(userAgent)) { deviceType = DEVICE_TYPE.SMARTPHONE; } /** * Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published * too late, there were a bunch of tablets running with 2.x * With 4.0 the two trees were merged and it is for smartphones and tablets * * So were are expecting that all devices running Android < 2 are smartphones * Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown */ if (deviceType === '' && osName === 'Android' && osVersion !== '') { if (helper.versionCompare(osVersion, '2.0') === -1) { deviceType = DEVICE_TYPE.SMARTPHONE; } else if ( helper.versionCompare(osVersion, '3.0') >= 0 && helper.versionCompare(osVersion, '4.0') === -1 ) { deviceType = DEVICE_TYPE.TABLET; } } /** * All detected feature phones running android are more likely a smartphone */ if (deviceType === DEVICE_TYPE.FEATURE_PHONE && osFamily === 'Android') { deviceType = DEVICE_TYPE.SMARTPHONE; } /** * All unknown devices under running Java ME * are more likely features phones */ if (deviceType === '' && osName === 'Java ME') { deviceType = DEVICE_TYPE.FEATURE_PHONE; } /** * All devices running KaiOS are more likely features phones */ if ('KaiOS' === osName) { deviceType = DEVICE_TYPE.FEATURE_PHONE; } /** * According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx * Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the * UA string, the computer has touch capability, and is running Windows 8 (or later). * This UA string will be transmitted on a touch-enabled system running Windows 8 (RT) * * As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that * all Windows 8 touch devices are tablets. */ const hasWindowTable = '' === deviceType && ('Windows RT' === osName || ('Windows' === osName && helper.versionCompare(osVersion, '8') >= 0)) && helper.hasTouchFragment(userAgent); if (hasWindowTable) { deviceType = DEVICE_TYPE.TABLET; } if ('' === deviceType && /Puffin\/\d/i.test(userAgent)) { /** * All devices running Puffin Secure Browser that contain letter 'D' are assumed to be desktops */ if (helper.hasPuffinDesktopFragment(userAgent)) { deviceType = DEVICE_TYPE.DESKTOP; } /** * All devices running Puffin Web Browser that contain letter 'P' are assumed to be smartphones */ if (helper.hasPuffinSmartphoneFragment(userAgent)) { deviceType = DEVICE_TYPE.SMARTPHONE; } /** * All devices running Puffin Web Browser that contain letter 'T' are assumed to be tablets */ if (helper.hasPuffinTabletFragment(userAgent)) { deviceType = DEVICE_TYPE.TABLET; } } /** * All devices running Opera TV Store are assumed to be a tv */ if (helper.hasOperaTVStoreFragment(userAgent)) { deviceType = DEVICE_TYPE.TV; } /** * All devices running Coolita OS are assumed to be a tv */ if ('Coolita OS' === osName) { deviceType = DEVICE_TYPE.TV; } /** * All devices that contain Andr0id in string are assumed to be a tv */ const hasDeviceTvType = [ DEVICE_TYPE.TV, DEVICE_TYPE.PERIPHERAL ].indexOf(deviceType) === -1 && helper.hasAndroidTVFragment(userAgent) if (hasDeviceTvType) { deviceType = DEVICE_TYPE.TV; } /** * All devices running Tizen TV or SmartTV are assumed to be a tv */ if ('' === deviceType && helper.hasTVFragment(userAgent)) { deviceType = DEVICE_TYPE.TV; } /** * Devices running those clients are assumed to be a TV */ if (helper.hasTVClient(clientName)) { deviceType = DEVICE_TYPE.TV; } /** * All devices containing TV fragment are assumed to be a tv */ if ('' === deviceType && helper.matchUserAgent('\\(TV;', userAgent)) { deviceType = DEVICE_TYPE.TV; } /** * Set device type desktop if string ua contains desktop */ if (DEVICE_TYPE.DESKTOP !== deviceType && userAgent.indexOf('Desktop') !== -1) { if (helper.matchUserAgent('Desktop(?: (x(?:32|64)|WOW64))?;', userAgent)) { deviceType = DEVICE_TYPE.DESKTOP; } } // check os desktop and not mobile browser if (deviceType === '') { let hasMobileBrowser = ( clientType === CLIENT_TYPE.BROWSER && MOBILE_BROWSER_LIST.indexOf(clientShortName) !== -1 ); let hasDesktopOs = osName !== '' && ( DESKTOP_OS_LIST.indexOf(osName) !== -1 || DESKTOP_OS_LIST.indexOf(osFamily) !== -1 ); if (!hasMobileBrowser && hasDesktopOs) { deviceType = DEVICE_TYPE.DESKTOP; } } return { type: deviceType }; } /** * get brand by device code (used in indexing) * @param {string} deviceCode * @returns {string[]} */ getBrandsByDeviceCode(deviceCode) { if ('' === deviceCode) { return []; } return IndexerDevice.findDeviceBrandsForDeviceCode(deviceCode); } /** * parse device model code from useragent string * @param {string} userAgent * @returns {ResultDeviceCode} */ parseDeviceCode(userAgent) { return aliasDevice.parse(userAgent); } /** * restore original userAgent from clientHints object * @param {string} userAgent * @param {ResultClientHints} clientHints * @return {string} */ restoreUserAgentFromClientHints(userAgent, clientHints) { return helper.restoreUserAgentFromClientHints(userAgent, clientHints) } /** * parse device * @param {string} userAgent * @param {ResultClientHints} clientHints * @return {ResultDevice} */ parseDevice(userAgent, clientHints) { let deviceModel = ''; let brandIndexes = []; let result = { id: '', type: '', brand: '', model: '', code: '', info: {}, trusted: null }; const ua = this.restoreUserAgentFromClientHints(userAgent, clientHints); // skip all parse is client-hints useragent and model not exist if (!helper.hasDeviceModelByClientHints(clientHints) && helper.hasUserAgentClientHintsFragment(userAgent)) { return Object.assign({}, result); } // add device.code to result if (this.deviceIndexes || this.deviceAliasCode || this.deviceInfo || this.deviceTrusted) { if (helper.hasDeviceModelByClientHints(clientHints)) { result.code = clientHints.device.model; } else { const alias = this.parseDeviceCode(ua); result.code = alias.name ? alias.name : ''; } deviceModel = '' + result.code; } else if(helper.hasDeviceModelByClientHints(clientHints)) { deviceModel = '' + clientHints.device.model; } // get indexes brands to scan only if (this.deviceIndexes) { brandIndexes = this.getBrandsByDeviceCode(result.code); } // is desktop fragment then parsing device skip if (deviceModel === '' && helper.hasDesktopFragment(ua)) { return result; } if (result && result.brand === '') { for (let name in this.deviceParserList) { let parser = this.deviceParserList[name]; let resultMerge = parser.parse(ua, brandIndexes); if (resultMerge) { result = Object.assign({}, result, resultMerge); break; } } } if (result && result.brand === '') { let resultVendor = this.parseVendor(ua); if (resultVendor) { result.brand = resultVendor.name; result.id = resultVendor.id; } } // client hints get model raw if (result.model === '' && helper.hasDeviceModelByClientHints(clientHints)) { result.model = clientHints.device.model; } // device info or deviceTrusted if (this.deviceInfo || this.deviceTrusted) { let deviceModel = result.code ? result.code : result.model; result.info = this.getParseInfoDevice().info(result.brand, deviceModel); } return result; } /** * parse vendor * @param {string} userAgent * @return {ResultVendor|null} */ parseVendor(userAgent) { return this.getParseVendor(VENDOR_FRAGMENT_PARSER).parse(userAgent); } /** * parse bot * @param {string} userAgent * @param {ResultClientHints} clientHints * @return {ResultBot|{}} */ parseBot(userAgent, clientHints) { let result = {}; if (this.skipBotDetection) { return result; } for (let name in this.botParserList) { let resultMerge = this.botParserList[name].parse(userAgent); if (resultMerge) { result = Object.assign(result, resultMerge); break; } } return result; } /** * parse client * @param {string} userAgent * @param {ResultClientHints} clientHints * @return {ResultClient|{}} */ parseClient(userAgent, clientHints) { const extendParsers = [CLIENT_PARSER_LIST.MOBILE_APP, CLIENT_PARSER_LIST.BROWSER]; // scan for indexes if (this.clientIndexes) { for (let name in this.clientParserList) { const hasExtend = extendParsers.indexOf(name) !== -1; const parser = this.clientParserList[name]; const hash = hasExtend ? parser.parseFromHashHintsApp(clientHints) : {}; const hint = hasExtend ? parser.parseFromClientHints(clientHints) : {}; const data = parser.parseUserAgentByPositions(userAgent); const result = hasExtend ? parser.prepareParseResult(userAgent, data, hint, hash): data; if (result && result.name) { return Object.assign({}, result); } } } // scan full for (let name in this.clientParserList) { const parser = this.clientParserList[name]; const result = parser.parse(userAgent, clientHints); if (result && result.name) { return Object.assign({}, result); } } return {}; } prepareDetectResult( userAgent, osData, clientData, deviceData, clientHints ) { /** * if it's fake UA then it's best not to identify it as Apple running Android OS or GNU/Linux */ if (deviceData.brand === 'Apple' && APPLE_OS_LIST.indexOf(osData.name) === -1) { deviceData.id = ''; deviceData.brand = ''; deviceData.model = ''; deviceData.type = ''; } /** * Assume all devices running iOS / Mac OS are from Apple */ if (deviceData.brand === '' && APPLE_OS_LIST.indexOf(osData.name) !== -1) { deviceData.brand = 'Apple'; } let deviceDataType = this.parseDeviceType( userAgent, osData, clientData, deviceData, clientHints ); deviceData = Object.assign(deviceData, deviceDataType); if (this.deviceTrusted) { deviceData.trusted = DeviceTrusted.check(osData, clientData, deviceData, clientHints); } else { delete deviceData.trusted; } if (!this.deviceAliasCode) { delete deviceData.code; } if (!this.deviceInfo) { delete deviceData.info; } /** * All devices running Coolita OS are assumed to be a tv */ if ('Coolita OS' === osData.name && '' === deviceData.brand) { deviceData.brand = 'coocaa'; } return { os: osData, client: clientData, device: deviceData }; } /** * detect os, client and device for async * @param {string} userAgent - string from request header['user-agent'] * @param {ResultClientHints|{}} clientHints * @return {DetectResult} */ async detectAsync(userAgent, clientHints = {}) { userAgent = this.prepareUserAgent(userAgent); let devicePromise = new Promise((resolve) => { return resolve(this.parseDevice(userAgent, clientHints)); }); let osPromise = new Promise((resolve) => { return resolve(this.parseOs(userAgent, clientHints)); }); let clientPromise = new Promise((resolve) => { return resolve(this.parseClient(userAgent, clientHints)); }); let [deviceData, osData, clientData] = await Promise.all([ devicePromise, osPromise, clientPromise ]); return this.prepareDetectResult( userAgent, osData, clientData, deviceData, clientHints ); } /** * detect os, client and device for sync * @param {string} userAgent - string from request header['user-agent'] * @param {ResultClientHints|{}} clientHints * @return {DetectResult} */ detect(userAgent, clientHints = {}) { userAgent = this.prepareUserAgent(userAgent); let deviceData = this.parseDevice(userAgent, clientHints); let osData = this.parseOs(userAgent, clientHints); let clientData = this.parseClient(userAgent, clientHints); return this.prepareDetectResult( userAgent, osData, clientData, deviceData, clientHints ); } } module.exports = DeviceDetector;