const enum Platform { WINDOWS = 'Windows', MAC = 'Mac', LINUX = 'Linux', ANDROID = 'Android', CHROME_OS = 'Chrome OS', } const SYSTEM_FONTS = [ 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', ] function getEngine() { const x = [].constructor try { (-1).toFixed(-1) } catch (err: any) { return err.message.length + (x+'').split(x.name).join('').length } } const ENGINE_IDENTIFIER = getEngine() const IS_BLINK = ENGINE_IDENTIFIER == 80 const IS_GECKO = ENGINE_IDENTIFIER == 58 const IS_WEBKIT = ENGINE_IDENTIFIER == 77 function getPlatformEstimate(): [ scores: Record, highestScore: number, headlessEstimate: Record ] | [] { //if (!IS_BLINK) return [] const v80 = 'getVideoPlaybackQuality' in HTMLVideoElement.prototype const v81 = CSS.supports('color-scheme: initial') const v84 = CSS.supports('appearance: initial') const v86 = 'DisplayNames' in Intl const v88 = CSS.supports('aspect-ratio: initial') const v89 = CSS.supports('border-end-end-radius: initial') const v95 = 'randomUUID' in Crypto.prototype const hasBarcodeDetector = 'BarcodeDetector' in window // @ts-expect-error if not supported const hasDownlinkMax = 'downlinkMax' in (window.NetworkInformation?.prototype || {}) const hasContentIndex = 'ContentIndex' in window const hasContactsManager = 'ContactsManager' in window const hasEyeDropper = 'EyeDropper' in window const hasFileSystemWritableFileStream = 'FileSystemWritableFileStream' in window const hasHid = 'HID' in window && 'HIDDevice' in window const hasSerialPort = 'SerialPort' in window && 'Serial' in window const hasSharedWorker = 'SharedWorker' in window const hasTouch = 'ontouchstart' in Window && 'TouchEvent' in window const hasAppBadge = 'setAppBadge' in Navigator.prototype const hasFeature = (version: boolean, condition: boolean) => { return (version ? [condition] : []) } const estimate: Record = { [Platform.ANDROID]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, hasContentIndex), ...hasFeature(v80, hasContactsManager), hasDownlinkMax, ...hasFeature(v95, !hasEyeDropper), ...hasFeature(v86, !hasFileSystemWritableFileStream), ...hasFeature(v89, !hasHid), ...hasFeature(v89, !hasSerialPort), !hasSharedWorker, hasTouch, ...hasFeature(v81, !hasAppBadge), ], [Platform.CHROME_OS]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, hasTouch || !hasTouch, ...hasFeature(v81, !hasAppBadge), ], [Platform.WINDOWS]: [ ...hasFeature(v88, !hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, hasTouch || !hasTouch, ...hasFeature(v81, hasAppBadge), ], [Platform.MAC]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, !hasTouch, ...hasFeature(v81, hasAppBadge), ], [Platform.LINUX]: [ ...hasFeature(v88, !hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, !hasTouch || !hasTouch, ...hasFeature(v81, !hasAppBadge), ], } // Chrome only features const headlessEstimate: Record = { noContentIndex: v84 && !hasContentIndex, noContactsManager: v80 && !hasContactsManager, noDownlinkMax: !hasDownlinkMax, } const scores = Object.keys(estimate).reduce((acc, key) => { const list = estimate[key] const score = +((list.filter((x) => x).length / list.length).toFixed(2)) acc[key] = score return acc }, {} as Record) const platform = Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b) const highestScore = scores[platform] console.log([scores, highestScore, headlessEstimate]); return [scores, highestScore, headlessEstimate] } console.log(getPlatformEstimate());