<h3>native navigator.userAgentData</h3>
<pre><code id="naviveUserAgentData">...</code></pre>
<h3>polyfilled navigator.userAgentData</h3>
<pre><code id="customUserAgentData">...</code></pre>
<script type="module">
import {ponyfill, polyfill} from './user-agent-data.js';
const $ = (s, c = document) => c.querySelector(s);
function main() {
if (location.protocol !== 'https:') {
$('#naviveUserAgentData').textContent = 'navigator.userAgentData is not available in insecure context';
$('#customUserAgentData').textContent = 'navigator.userAgentData polyfill is designed to work in secure context';
return;
}
let keys = ['platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList'];
if (!navigator.userAgentData) {
$('#naviveUserAgentData').textContent = 'Your browser doesn\'t support client hints';
} else {
navigator.userAgentData.getHighEntropyValues(keys).then((result) => {
$('#naviveUserAgentData').textContent = JSON.stringify(result, null, 2);
});
}
let userAgentData = ponyfill();
userAgentData.getHighEntropyValues(keys).then((result) => {
$('#customUserAgentData').textContent = JSON.stringify(result, null, 2);
});
}
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', main) : main();
</script>
view raw index.html hosted with ❤ by GitHub
function getClientHints(navigator) {
let {userAgent} = navigator;
let mobile, platform = '', platformVersion = '', architecture = '', bitness = '', model = '', uaFullVersion = '', fullVersionList = [];
let platformInfo = userAgent;
let found = false;
let versionInfo = userAgent.replace(/\(([^)]+)\)?/g, ($0, $1) => {
if (!found) {
platformInfo = $1;
found = true;
}
return '';
});
let items = versionInfo.match(/(\S+)\/(\S+)/g);
let webview = false;
// detect mobile
mobile = userAgent.indexOf('Mobile') !== -1;
let m;
let m2;
// detect platform
if ((m = /Windows NT (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
platform = 'Windows';
// see https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
let nt2win = {
'6.1': '0.1', // win-7
'6.2': '0.2', // win-8
'6.3': '0.3', // win-8.1
'10.0': '10.0', // win-10
'11.0': '13.0', // win-11
};
let ver = nt2win[m[1]];
if (ver)
platformVersion = padVersion(ver, 3);
if ((m2 = /\b(WOW64|Win64|x64)\b/.exec(platformInfo)) !== null) {
architecture = 'x86';
bitness = '64';
}
} else if ((m = /Android (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
platform = 'Android';
platformVersion = padVersion(m[1]);
if ((m2 = /Linux (\w+)/.exec(navigator.platform)) !== null) {
if (m2[1]) {
m2 = parseArch(m2[1]);
architecture = m2[0];
bitness = m2[1];
}
}
} else if ((m = /(iPhone|iPod touch); CPU iPhone OS (\d+(_\d+)*)/.exec(platformInfo)) !== null) {
// see special notes at https://www.whatismybrowser.com/guides/the-latest-user-agent/safari
platform = 'iOS';
platformVersion = padVersion(m[2].replace(/_/g, '.'));
} else if ((m = /(iPad); CPU OS (\d+(_\d+)*)/.exec(platformInfo)) !== null) {
platform = 'iOS';
platformVersion = padVersion(m[2].replace(/_/g, '.'));
} else if ((m = /Macintosh; (Intel|\w+) Mac OS X (\d+([_.]\d+)*)/.exec(platformInfo)) !== null) {
platform = 'macOS';
platformVersion = padVersion(m[2].replace(/_/g, '.'));
} else if ((m = /Linux/.exec(platformInfo)) !== null) {
platform = 'Linux';
platformVersion = '';
// TODO
} else if ((m = /CrOS (\w+) (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
platform = 'Chrome OS';
platformVersion = padVersion(m[2]);
m2 = parseArch(m[1]);
architecture = m2[0];
bitness = m2[1];
}
if (!platform) {
platform = 'Unknown';
}
// detect fullVersionList / brands
let notABrand = {brand: ' Not;A Brand', version: '99.0.0.0'};
if ((m = /Chrome\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null && navigator.vendor === 'Google Inc.') {
fullVersionList.push({brand: 'Chromium', version: padVersion(m[1], 4)});
if ((m2 = /(Edge?)\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null) {
let identBrandMap = {
'Edge': 'Microsoft Edge',
'Edg': 'Microsoft Edge',
};
let brand = identBrandMap[m[1]];
fullVersionList.push({brand: brand, version: padVersion(m2[2], 4)});
} else {
fullVersionList.push({brand: 'Google Chrome', version: padVersion(m[1], 4)});
}
if (/\bwv\b/.exec(platformInfo)) {
webview = true;
}
} else if ((m = /AppleWebKit\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null && navigator.vendor === 'Apple Computer, Inc.') {
fullVersionList.push({brand: 'WebKit', version: padVersion(m[1])});
if (platform === 'iOS' && (m2 = /(CriOS|EdgiOS|FxiOS|Version)\/(\d+(\.\d+)*)/.exec(versionInfo)) != null) {
let identBrandMap = { // no
'CriOS': 'Google Chrome',
'EdgiOS': 'Microsoft Edge',
'FxiOS': 'Mozilla Firefox',
'Version': 'Apple Safari',
};
let brand = identBrandMap[m2[1]];
fullVersionList.push({brand, version: padVersion(m2[2])});
if (items.findIndex((s) => s.startsWith('Safari/')) === -1) {
webview = true;
}
}
} else if ((m = /Firefox\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null) {
fullVersionList.push({brand: 'Firefox', version: padVersion(m[1])});
} else {
fullVersionList.push(notABrand);
}
uaFullVersion = fullVersionList.length > 0 ? fullVersionList[fullVersionList.length - 1] : '';
let brands = fullVersionList.map((b) => {
let pos = b.version.indexOf('.');
let version = pos === -1 ? b.version : b.version.slice(0, pos);
return {brand: b.brand, version};
});
// TODO detect architecture, bitness and model
return {
mobile,
platform,
brands,
platformVersion,
architecture,
bitness,
model,
uaFullVersion,
fullVersionList,
webview
};
}
function parseArch(arch) {
switch (arch) {
case 'x86_64':
case 'x64':
return ['x86', '64'];
case 'x86_32':
case 'x86':
return ['x86', ''];
case 'armv6l':
case 'armv7l':
case 'armv8l':
return [arch, ''];
case 'aarch64':
return ['arm', '64'];
default:
return ['', ''];
}
}
function padVersion(ver, minSegs = 3) {
let parts = ver.split('.');
let len = parts.length;
if (len < minSegs) {
for (let i = 0, lenToPad = minSegs - len; i < lenToPad; i += 1) {
parts.push('0');
}
return parts.join('.');
}
return ver;
}
class NavigatorUAData {
constructor() {
this._ch = getClientHints(navigator);
Object.defineProperties(this, {
_ch: {enumerable: false},
});
}
get mobile() {
return this._ch.mobile;
}
get platform() {
return this._ch.platform;
}
get brands() {
return this._ch.brands;
}
getHighEntropyValues(hints) {
return new Promise((resolve, reject) => {
if (!Array.isArray(hints)) {
throw new TypeError('argument hints is not an array');
}
let hintSet = new Set(hints);
let data = this._ch;
let obj = {
mobile: data.mobile,
platform: data.platform,
brands: data.brands,
};
if (hintSet.has('architecture'))
obj.architecture = data.architecture;
if (hintSet.has('bitness'))
obj.bitness = data.bitness;
if (hintSet.has('model'))
obj.model = data.model;
if (hintSet.has('platformVersion'))
obj.platformVersion = data.platformVersion;
if (hintSet.has('uaFullVersion'))
obj.uaFullVersion = data.uaFullVersion;
if (hintSet.has('fullVersionList'))
obj.fullVersionList = data.fullVersionList;
resolve(obj);
});
}
toJSON() {
let data = this._ch;
return {
mobile: data.mobile,
brands: data.brands,
};
}
}
Object.defineProperty(NavigatorUAData.prototype, Symbol.toStringTag, {
enumerable: false,
configurable: true,
writable: false,
value: 'NavigatorUAData'
});
function ponyfill() {
return new NavigatorUAData(navigator);
}
function polyfill() {
if (location.protocol === 'https:' && !navigator.userAgentData) {
let userAgentData = new NavigatorUAData(navigator);
Object.defineProperty(Navigator.prototype, 'userAgentData', {
enumerable: true,
configurable: true,
get: function getUseAgentData() {
return userAgentData;
}
});
Object.defineProperty(window, 'NavigatorUAData', {
enumerable: false,
configurable: true,
writable: true,
value: NavigatorUAData
});
return true;
}
return false;
}
export {ponyfill, polyfill};

Codespaces

but...
Open Source
No vendor lock-in. 100% free and open source built by developers for developers.
Client Only
No server side setup needed. Download the desktop app or the CLI to get started.
Unopinionated
Repeatable dev environment for any infra, any IDE, and any programming language.
Download DevPod

See it in action

DevPod is infrastructure-independent and client-only, which makes it incredibly easy to get started with.

Now try the real thing

Download DevPod and get started with your preferred cloud provider, a Kubernetes cluster, or just your local laptop. DevPod works with any stack.
Download DevPod Now

DevPod - Core Features

DevPod allows you to spin up dev environments based on the devcontainer.json but it can also set up a best-estimate dev environment by analyzing your project.
Based on devcontainer.json
Open source standard for defining dev environments in a declarative file within your repository.
Lightweight
Desktop UI & CLI
DevPod ships as a powerful desktop application but also provides a progammable CLI.
Works with Public & Private Repos
If you can git clone, you can devpod it because DevPod just uses git to checkout your repos.
Join the conversation with other DevPod users and maintainers
Join us on Slack

Clone Dev’s DevPod

Incidunt consectetur necessitatibus excepturi. Rerum molestias sit quibusdam maxime eum qui ex et et et quia dolorum.

Why Teams Love DevPod

DevPod is the first and only tool for creating and managing dev environments that does not require a heavyweight server-side setup. Developers can write code in any language, and run it anywhere.
Instant Onboarding
Let new team members spin up a dev environment in seconds rather than days.
Consistent DevEx
Create a superior developer experience and save it as code in your project.
Cloud Power (when needed)
Switch back and forth between local and cloud-powered environments as needed.

Start coding in seconds with DevPods

DevPod enables Dev-Environments-as-Code for you and anyone you are collaborating on a project with. Download DevPod and get started now.
Download DevPod