Sign up below to view device data and get your trial account.

We communicate via email to process your request in line with our privacy policy. Please check the box to give us your permission to do this.

Cancel

Capturing User-Agent Client-Hint data from the browser with JavaScript

As defined in the WICG User-Agent Client-Hints specification, User-Agent Client Hints (UA-CH) are available on the server side in the form of additional HTTP request headers and the client side via a JavaScript API. Confusingly, there are some slight differences in naming between the two different sources so some of our customers have asked us for guidance on obtaining UA-CH from the browser, but in a header-like format that can then be sent to a server as part of an existing payload for analysis by DeviceAtlas.

This code may be useful for customers with integrations with publishers, to share with their publisher partners to facilitate capture of client hints, or for publishers for their own use.

This library emits a JSON object containing the full set of HTTP headers to enable a DeviceAtlas API to resolve them into device properties. If the browser supports UA-CH then the the appropriate headers are included, if not only the User-Agent string is captured.

Usage

// Import module 
import { clientHintHeaders } from "./clientHintHeaders.js";

// Wait for client hints promise to resolve and log the header name and value to the console
clientHintHeaders.then(res => {
    for (const [header, value] of Object.entries(res)) {
        console.log(`${header}: ${value}`);
    }
});

Output

Chrome emulating a Pixel 5

User-Agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36
Sec-Ch-Ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
Sec-Ch-Ua-Arch: ""
Sec-Ch-Ua-Mobile: ?1
Sec-Ch-Ua-Model: "Pixel 5"
Sec-Ch-Ua-Platform: "Android"
Sec-Ch-Ua-Platform-Version: "11"
Sec-Ch-Ua-Full-Version-List: "Not?A_Brand";v="8.0.0.0", "Chromium";v="108.0.5359.124", "Google Chrome";v="108.0.5359.124"
Sec-Ch-Ua-Bitness: "64"
Sec-Ch-Ua-Wow64: ?0

Safari on macOS Big Sur

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15

Firefox on macOS Big Sur

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0

Code

clientHintHeaders.js

const getBrandsConcatenated = (brands) => {
    return brands.map(value => {
        let brandEscaped = escapeDoubleQuoteAndBackslash(value.brand);
        return `"${brandEscaped}";v="${value.version}"`;
    }).join(", ");
}

const doubleQuoteEscape = (value) => {
    return value.replace(/"/g, '\\"');
}

const backslashEscape = (value) => {
    return value.replace(/\\/, "\\\\");
}

const escapeDoubleQuoteAndBackslash = (value) => {
    if (typeof value === 'string' && value){
        value = backslashEscape(value);
        value = doubleQuoteEscape(value);
    }

    return value;
}

const isValidArray = (fullVersionList) => {
    return typeof fullVersionList !== 'undefined' && Array.isArray(fullVersionList);
}

const getBrowserList = (fullVersionList, brands) => {
    if (isValidArray(fullVersionList) ){
        return getBrandsConcatenated(fullVersionList);
    } else if (isValidArray(brands) ){
        return getBrandsConcatenated(brands);
    }
}

const getHintsValues = () => {
    return [
        'brands',
        'mobile',
        'platform',
        'architecture',
        'bitness',
        'fullVersionList',
        'model',
        'platformVersion',
        'wow64',
    ];
}

function getPropertyQuoted(property) {
    if (property !== undefined){
        let valueEscaped = escapeDoubleQuoteAndBackslash(property);
        return `"${valueEscaped}"`;
    }

    return property;
}

export const clientHintHeaders = (new Promise(resolve => {
    const navUAData = navigator && navigator.userAgentData
    if (navUAData) {
        navUAData.getHighEntropyValues(getHintsValues()).then(ua => {
            const headers = {
                'User-Agent': navigator.userAgent,
                'Sec-CH-UA': getBrowserList(ua.brands),
                'Sec-CH-UA-Arch': getPropertyQuoted(ua.architecture),
                'Sec-CH-UA-Mobile': '?' + (ua.mobile ? '1' : '0'),
                'Sec-CH-UA-Model': getPropertyQuoted(ua.model),
                'Sec-CH-UA-Platform': getPropertyQuoted(ua.platform),
                'Sec-CH-UA-Platform-Version': getPropertyQuoted(ua.platformVersion),
                'Sec-CH-UA-Full-Version-List': getBrowserList(ua.fullVersionList, ua.brands),
                'Sec-CH-UA-Bitness': getPropertyQuoted(ua.bitness),
                'Sec-CH-UA-Wow64': '?' + (ua.wow64 ? '1' : '0')
            };

            resolve(headers);
        })
    } else if (navigator) {
        resolve({
            'User-Agent': navigator.userAgent,
        });
    } else {
        resolve({});
    }
}));