mirror of
https://github.com/lancedikson/bowser
synced 2026-03-02 03:40:27 +00:00
feat: add clientHints support + DuckDuckGo detection (#595)
This commit is contained in:
committed by
GitHub
parent
0c6fdd7248
commit
aaa67db25e
@@ -28,33 +28,49 @@ class Bowser {
|
||||
* Creates a {@link Parser} instance
|
||||
*
|
||||
* @param {String} UA UserAgent string
|
||||
* @param {Boolean} [skipParsing=false] Will make the Parser postpone parsing until you ask it
|
||||
* explicitly. Same as `skipParsing` for {@link Parser}.
|
||||
* @param {Boolean|Object} [skipParsingOrHints=false] Either a boolean to skip parsing,
|
||||
* or a ClientHints object (navigator.userAgentData)
|
||||
* @param {Object} [clientHints] User-Agent Client Hints data (navigator.userAgentData)
|
||||
* @returns {Parser}
|
||||
* @throws {Error} when UA is not a String
|
||||
*
|
||||
* @example
|
||||
* const parser = Bowser.getParser(window.navigator.userAgent);
|
||||
* const result = parser.getResult();
|
||||
*
|
||||
* @example
|
||||
* // With User-Agent Client Hints
|
||||
* const parser = Bowser.getParser(
|
||||
* window.navigator.userAgent,
|
||||
* window.navigator.userAgentData
|
||||
* );
|
||||
*/
|
||||
static getParser(UA, skipParsing = false) {
|
||||
static getParser(UA, skipParsingOrHints = false, clientHints = null) {
|
||||
if (typeof UA !== 'string') {
|
||||
throw new Error('UserAgent should be a string');
|
||||
}
|
||||
return new Parser(UA, skipParsing);
|
||||
return new Parser(UA, skipParsingOrHints, clientHints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately
|
||||
*
|
||||
* @param UA
|
||||
* @param {String} UA UserAgent string
|
||||
* @param {Object} [clientHints] User-Agent Client Hints data (navigator.userAgentData)
|
||||
* @return {ParsedResult}
|
||||
*
|
||||
* @example
|
||||
* const result = Bowser.parse(window.navigator.userAgent);
|
||||
*
|
||||
* @example
|
||||
* // With User-Agent Client Hints
|
||||
* const result = Bowser.parse(
|
||||
* window.navigator.userAgent,
|
||||
* window.navigator.userAgentData
|
||||
* );
|
||||
*/
|
||||
static parse(UA) {
|
||||
return (new Parser(UA)).getResult();
|
||||
static parse(UA, clientHints = null) {
|
||||
return (new Parser(UA, clientHints)).getResult();
|
||||
}
|
||||
|
||||
static get BROWSER_MAP() {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const BROWSER_ALIASES_MAP = {
|
||||
Chromium: 'chromium',
|
||||
Diffbot: 'diffbot',
|
||||
DuckDuckBot: 'duckduckbot',
|
||||
DuckDuckGo: 'duckduckgo',
|
||||
Electron: 'electron',
|
||||
Epiphany: 'epiphany',
|
||||
FacebookExternalHit: 'facebookexternalhit',
|
||||
@@ -82,6 +83,7 @@ export const BROWSER_MAP = {
|
||||
chromium: 'Chromium',
|
||||
diffbot: 'Diffbot',
|
||||
duckduckbot: 'DuckDuckBot',
|
||||
duckduckgo: 'DuckDuckGo',
|
||||
edge: 'Microsoft Edge',
|
||||
electron: 'Electron',
|
||||
epiphany: 'Epiphany',
|
||||
|
||||
@@ -987,6 +987,39 @@ const browsersList = [
|
||||
return browser;
|
||||
},
|
||||
},
|
||||
/* DuckDuckGo Browser */
|
||||
{
|
||||
test(parser) {
|
||||
// Chromium platforms (Android, Windows): check Client Hints brands first
|
||||
if (parser.hasBrand('DuckDuckGo')) {
|
||||
return true;
|
||||
}
|
||||
// WebKit platforms (iOS, macOS): check UA string for Ddg/version suffix
|
||||
return parser.test(/\sDdg\/[\d.]+$/i);
|
||||
},
|
||||
describe(ua, parser) {
|
||||
const browser = {
|
||||
name: 'DuckDuckGo',
|
||||
};
|
||||
|
||||
// Try Client Hints brand version first
|
||||
if (parser) {
|
||||
const hintsVersion = parser.getBrandVersion('DuckDuckGo');
|
||||
if (hintsVersion) {
|
||||
browser.version = hintsVersion;
|
||||
return browser;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to WebKit UA pattern
|
||||
const uaVersion = Utils.getFirstMatch(/\sDdg\/([\d.]+)$/i, ua);
|
||||
if (uaVersion) {
|
||||
browser.version = uaVersion;
|
||||
}
|
||||
|
||||
return browser;
|
||||
},
|
||||
},
|
||||
{
|
||||
test: [/chromium/i],
|
||||
describe(ua) {
|
||||
|
||||
@@ -4,6 +4,17 @@ import platformParsersList from './parser-platforms.js';
|
||||
import enginesParsersList from './parser-engines.js';
|
||||
import Utils from './utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClientHints
|
||||
* @property {Array<{brand: string, version: string}>} [brands] Array of brand objects
|
||||
* @property {boolean} [mobile] Whether the device is mobile
|
||||
* @property {string} [platform] Platform name (e.g., "Windows", "macOS")
|
||||
* @property {string} [platformVersion] Platform version
|
||||
* @property {string} [architecture] CPU architecture
|
||||
* @property {string} [model] Device model
|
||||
* @property {boolean} [wow64] Whether running under WoW64
|
||||
*/
|
||||
|
||||
/**
|
||||
* The main class that arranges the whole parsing process.
|
||||
*/
|
||||
@@ -12,21 +23,32 @@ class Parser {
|
||||
* Create instance of Parser
|
||||
*
|
||||
* @param {String} UA User-Agent string
|
||||
* @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance
|
||||
* improvements if you need to make a more particular parsing
|
||||
* like {@link Parser#parseBrowser} or {@link Parser#parsePlatform}
|
||||
* @param {Boolean|ClientHints} [skipParsingOrHints=false] Either a boolean to skip parsing,
|
||||
* or a ClientHints object containing User-Agent Client Hints data
|
||||
* @param {ClientHints} [clientHints] User-Agent Client Hints data (navigator.userAgentData)
|
||||
*
|
||||
* @throw {Error} in case of empty UA String
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(UA, skipParsing = false) {
|
||||
constructor(UA, skipParsingOrHints = false, clientHints = null) {
|
||||
if (UA === void (0) || UA === null || UA === '') {
|
||||
throw new Error("UserAgent parameter can't be empty");
|
||||
}
|
||||
|
||||
this._ua = UA;
|
||||
|
||||
// Handle overloaded constructor: (UA, clientHints) or (UA, skipParsing, clientHints)
|
||||
let skipParsing = false;
|
||||
if (typeof skipParsingOrHints === 'boolean') {
|
||||
skipParsing = skipParsingOrHints;
|
||||
this._hints = clientHints;
|
||||
} else if (skipParsingOrHints != null && typeof skipParsingOrHints === 'object') {
|
||||
this._hints = skipParsingOrHints;
|
||||
} else {
|
||||
this._hints = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef ParsedResult
|
||||
* @property {Object} browser
|
||||
@@ -56,6 +78,51 @@ class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Client Hints data
|
||||
* @return {ClientHints|null}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getHints() {
|
||||
return this._hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a brand exists in Client Hints brands array
|
||||
* @param {string} brandName The brand name to check for
|
||||
* @return {boolean}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
hasBrand(brandName) {
|
||||
if (!this._hints || !Array.isArray(this._hints.brands)) {
|
||||
return false;
|
||||
}
|
||||
const brandLower = brandName.toLowerCase();
|
||||
return this._hints.brands.some(
|
||||
b => b.brand && b.brand.toLowerCase() === brandLower,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brand version from Client Hints
|
||||
* @param {string} brandName The brand name to get version for
|
||||
* @return {string|undefined}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getBrandVersion(brandName) {
|
||||
if (!this._hints || !Array.isArray(this._hints.brands)) {
|
||||
return undefined;
|
||||
}
|
||||
const brandLower = brandName.toLowerCase();
|
||||
const brand = this._hints.brands.find(
|
||||
b => b.brand && b.brand.toLowerCase() === brandLower,
|
||||
);
|
||||
return brand ? brand.version : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UserAgent string of current Parser instance
|
||||
* @return {String} User-Agent String of the current <Parser> object
|
||||
@@ -95,7 +162,7 @@ class Parser {
|
||||
});
|
||||
|
||||
if (browserDescriptor) {
|
||||
this.parsedResult.browser = browserDescriptor.describe(this.getUA());
|
||||
this.parsedResult.browser = browserDescriptor.describe(this.getUA(), this);
|
||||
}
|
||||
|
||||
return this.parsedResult.browser;
|
||||
|
||||
Reference in New Issue
Block a user