2017-05-18 22:57:18 +03:00
|
|
|
/**
|
|
|
|
|
* Browsers' descriptors
|
|
|
|
|
*
|
|
|
|
|
* The idea of descriptors is simple. You should know about them two simple things:
|
|
|
|
|
* 1. Every descriptor has a method or property called `test` and a `describe` method.
|
|
|
|
|
* 2. Order of descriptors is important.
|
|
|
|
|
*
|
|
|
|
|
* More details:
|
|
|
|
|
* 1. Method or property `test` serves as a way to detect whether the UA string
|
|
|
|
|
* matches some certain browser or not. The `describe` method helps to make a result
|
|
|
|
|
* object with params that show some browser-specific things: name, version, etc.
|
|
|
|
|
* 2. Order of descriptors is important because a Parser goes through them one by one
|
|
|
|
|
* in course. For example, if you insert Chrome's descriptor as the first one,
|
|
|
|
|
* more then a half of browsers will be described as Chrome, because they will pass
|
|
|
|
|
* the Chrome descriptor's test.
|
|
|
|
|
*
|
|
|
|
|
* Descriptor's `test` could be a property with an array of RegExps, where every RegExp
|
|
|
|
|
* will be applied to a UA string to test it whether it matches or not.
|
|
|
|
|
* If a descriptor has two or more regexps in the `test` array it tests them one by one
|
|
|
|
|
* with a logical sum operation. Parser stops if it has found any RegExp that matches the UA.
|
|
|
|
|
*
|
|
|
|
|
* Or `test` could be a method. In that case it gets a Parser instance and should
|
|
|
|
|
* return true/false to get the Parser know if this browser descriptor matches the UA or not.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-02-14 16:06:14 +00:00
|
|
|
import Utils from './utils.js';
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-06-08 23:01:16 +03:00
|
|
|
const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
|
2017-04-04 23:03:47 +03:00
|
|
|
|
|
|
|
|
const browsersList = [
|
2025-11-22 18:05:08 +02:00
|
|
|
/* GPTBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/gptbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'GPTBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/gptbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* ChatGPT-User */
|
|
|
|
|
{
|
|
|
|
|
test: [/chatgpt-user/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'ChatGPT-User',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/chatgpt-user\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* OAI-SearchBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/oai-searchbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'OAI-SearchBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/oai-searchbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* ClaudeBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/claudebot/i, /claude-web/i, /claude-user/i, /claude-searchbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'ClaudeBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:claudebot|claude-web|claude-user|claude-searchbot)\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Omgilibot */
|
|
|
|
|
{
|
|
|
|
|
test: [/omgilibot/i, /webzio-extended/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Omgilibot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:omgilibot|webzio-extended)\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Diffbot */
|
|
|
|
|
{
|
|
|
|
|
test: [/diffbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Diffbot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/diffbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* PerplexityBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/perplexitybot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'PerplexityBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/perplexitybot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Perplexity-User */
|
|
|
|
|
{
|
|
|
|
|
test: [/perplexity-user/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Perplexity-User',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/perplexity-user\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* YouBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/youbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'YouBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/youbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Meta-WebIndexer */
|
|
|
|
|
{
|
|
|
|
|
test: [/meta-webindexer/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Meta-WebIndexer',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/meta-webindexer\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Meta-ExternalAds */
|
|
|
|
|
{
|
|
|
|
|
test: [/meta-externalads/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Meta-ExternalAds',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/meta-externalads\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Meta-ExternalAgent */
|
|
|
|
|
{
|
|
|
|
|
test: [/meta-externalagent/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Meta-ExternalAgent',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/meta-externalagent\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Meta-ExternalFetcher */
|
|
|
|
|
{
|
|
|
|
|
test: [/meta-externalfetcher/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Meta-ExternalFetcher',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/meta-externalfetcher\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2019-01-16 13:30:38 +02:00
|
|
|
/* Googlebot */
|
|
|
|
|
{
|
|
|
|
|
test: [/googlebot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Googlebot',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2019-01-16 13:30:38 +02:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-07 05:08:55 +08:00
|
|
|
/* Linespider */
|
|
|
|
|
{
|
|
|
|
|
test: [/linespider/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Linespider',
|
|
|
|
|
};
|
fix: resolve open GitHub security findings
Code scanning (js/polynomial-redos, alerts #16 and #17)
-------------------------------------------------------
bowser applies these regexps to attacker-controlled User-Agent strings, and
four of them ran in quadratic time:
* The "Something else" fallback used /^(.*)\/(.*) / and
/^(.*)\/(.*)[ \t]\((.*)/. Two unbounded `.*` before a required literal
make the split ambiguous, so a UA of "/a" repeated backtracks O(n^2).
Greedy `(.*)` always picks the last `/` that still has the delimiter
after it, so the second group can never span a `/` -- narrowing it to
`[^/]*` is exactly equivalent and removes the ambiguity. Verified
identical on 1,000,000 fuzzed inputs and on the full acceptance corpus.
* The Linespider and SlackBot version regexps used `(?:-[-\w]+)?` before a
required `[\s/]`. Since `[-\w]` and `[\s/]` are disjoint the backtracking
is pure waste, but the engine still walks it once per start position, so
"linespider-" repeated is quadratic. Bounding the run to `{1,64}` makes
it linear; no real bot-name suffix approaches 64 characters.
A sweep of all 253 regexp literals in src/ (fuzzed for superlinear scaling,
with the four pre-fix patterns used to confirm the detector works) reports no
remaining superlinear regexps. test/unit/redos.js locks this in.
Actions (actions/missing-workflow-permissions, alerts #5 and #15)
-----------------------------------------------------------------
merge-to-master.yml and draft-or-update-next-release.yml had no `permissions`
block and so inherited the default token. Both now declare least privilege,
matching publish.yml and pull-request.yml.
Dependabot (22 open alerts, all development scope)
--------------------------------------------------
bowser ships no runtime dependencies, so none of these reached consumers, but
they were live in CI. `pnpm audit` goes from 29 advisories to 0:
* jsdoc 3 -> 4 (with docdash 1 -> 2) drops taffydb, which has no patched
release, and picks up current markdown-it/linkify-it.
* coveralls -> coveralls-next 6 drops `request`, which is deprecated with no
patched release, along with form-data, qs 6.5.x, uuid 3 and tough-cookie 2.
Same `coveralls` bin and same stdin contract; lcov conversion verified.
* gh-pages 3 -> 6 clears the critical prototype pollution advisory.
* pnpm overrides pin the remaining transitive-only advisories to the lowest
patched release on each existing major.
Verified: pnpm audit clean, lint clean, 346 tests pass (the acceptance corpus
runs against both src/ and the built es5.js), build, package smoke test, and
doc generation.
2026-08-30 20:37:19 +03:00
|
|
|
// `{1,64}` bounds the backtracking: unbounded, a repeated-token UA is quadratic.
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:linespider)(?:-[-\w]{1,64})?[\s/](\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2026-02-07 05:08:55 +08:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-22 15:44:08 +01:00
|
|
|
/* AmazonBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/amazonbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'AmazonBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/amazonbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* BingCrawler */
|
|
|
|
|
{
|
|
|
|
|
test: [/bingbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'BingCrawler',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/bingbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* BaiduSpider */
|
|
|
|
|
{
|
|
|
|
|
test: [/baiduspider/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'BaiduSpider',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/baiduspider\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* DuckDuckBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/duckduckbot/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'DuckDuckBot',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/duckduckbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* InternetArchiveCrawler */
|
|
|
|
|
{
|
|
|
|
|
test: [/ia_archiver/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'InternetArchiveCrawler',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/ia_archiver\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-22 18:05:08 +02:00
|
|
|
/* FacebookExternalHit */
|
2025-11-22 15:44:08 +01:00
|
|
|
{
|
|
|
|
|
test: [/facebookexternalhit/i, /facebookcatalog/i],
|
|
|
|
|
describe() {
|
|
|
|
|
return {
|
2025-11-22 18:05:08 +02:00
|
|
|
name: 'FacebookExternalHit',
|
2025-11-22 15:44:08 +01:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-01 00:54:09 +08:00
|
|
|
/* SlackBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/slackbot/i, /slack-imgProxy/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'SlackBot',
|
|
|
|
|
};
|
fix: resolve open GitHub security findings
Code scanning (js/polynomial-redos, alerts #16 and #17)
-------------------------------------------------------
bowser applies these regexps to attacker-controlled User-Agent strings, and
four of them ran in quadratic time:
* The "Something else" fallback used /^(.*)\/(.*) / and
/^(.*)\/(.*)[ \t]\((.*)/. Two unbounded `.*` before a required literal
make the split ambiguous, so a UA of "/a" repeated backtracks O(n^2).
Greedy `(.*)` always picks the last `/` that still has the delimiter
after it, so the second group can never span a `/` -- narrowing it to
`[^/]*` is exactly equivalent and removes the ambiguity. Verified
identical on 1,000,000 fuzzed inputs and on the full acceptance corpus.
* The Linespider and SlackBot version regexps used `(?:-[-\w]+)?` before a
required `[\s/]`. Since `[-\w]` and `[\s/]` are disjoint the backtracking
is pure waste, but the engine still walks it once per start position, so
"linespider-" repeated is quadratic. Bounding the run to `{1,64}` makes
it linear; no real bot-name suffix approaches 64 characters.
A sweep of all 253 regexp literals in src/ (fuzzed for superlinear scaling,
with the four pre-fix patterns used to confirm the detector works) reports no
remaining superlinear regexps. test/unit/redos.js locks this in.
Actions (actions/missing-workflow-permissions, alerts #5 and #15)
-----------------------------------------------------------------
merge-to-master.yml and draft-or-update-next-release.yml had no `permissions`
block and so inherited the default token. Both now declare least privilege,
matching publish.yml and pull-request.yml.
Dependabot (22 open alerts, all development scope)
--------------------------------------------------
bowser ships no runtime dependencies, so none of these reached consumers, but
they were live in CI. `pnpm audit` goes from 29 advisories to 0:
* jsdoc 3 -> 4 (with docdash 1 -> 2) drops taffydb, which has no patched
release, and picks up current markdown-it/linkify-it.
* coveralls -> coveralls-next 6 drops `request`, which is deprecated with no
patched release, along with form-data, qs 6.5.x, uuid 3 and tough-cookie 2.
Same `coveralls` bin and same stdin contract; lcov conversion verified.
* gh-pages 3 -> 6 clears the critical prototype pollution advisory.
* pnpm overrides pin the remaining transitive-only advisories to the lowest
patched release on each existing major.
Verified: pnpm audit clean, lint clean, 346 tests pass (the acceptance corpus
runs against both src/ and the built es5.js), build, package smoke test, and
doc generation.
2026-08-30 20:37:19 +03:00
|
|
|
// `{1,64}` bounds the backtracking: unbounded, a repeated-token UA is quadratic.
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:slackbot|slack-imgproxy)(?:-[-\w]{1,64})?[\s/](\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2026-02-01 00:54:09 +08:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-22 15:44:08 +01:00
|
|
|
/* YahooSlurp */
|
|
|
|
|
{
|
|
|
|
|
test: [/yahoo!?[\s/]*slurp/i],
|
|
|
|
|
describe() {
|
|
|
|
|
return {
|
|
|
|
|
name: 'YahooSlurp',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* YandexBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/yandexbot/i, /yandexmobilebot/i],
|
|
|
|
|
describe() {
|
|
|
|
|
return {
|
|
|
|
|
name: 'YandexBot',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* PingdomBot */
|
|
|
|
|
{
|
|
|
|
|
test: [/pingdom/i],
|
|
|
|
|
describe() {
|
|
|
|
|
return {
|
|
|
|
|
name: 'PingdomBot',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2017-12-20 22:34:29 +02:00
|
|
|
/* Opera < 13.0 */
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
|
|
|
|
test: [/opera/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Opera',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2017-12-20 22:34:29 +02:00
|
|
|
|
|
|
|
|
/* Opera > 13.0 */
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
2018-09-14 17:44:17 +03:00
|
|
|
test: [/opr\/|opios/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Opera',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/SamsungBrowser/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Samsung Internet for Android',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2018-07-09 21:26:43 +03:00
|
|
|
{
|
|
|
|
|
test: [/Whale/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'NAVER Whale Browser',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, ua);
|
2018-07-09 21:26:43 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-11-17 21:47:16 +03:00
|
|
|
{
|
|
|
|
|
test: [/PaleMoon/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Pale Moon',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:PaleMoon)[\s/](\d+(?:\.\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-04-10 21:48:44 +05:30
|
|
|
{
|
|
|
|
|
test: [/HeyTapBrowser/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'HeyTap Browser',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:HeyTapBrowser)[\s/](\d+(?:\.\d+)*)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/VivoBrowser/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Vivo Browser',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:VivoBrowser)[\s/](\d+(?:\.\d+)*)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2018-07-09 21:26:43 +03:00
|
|
|
{
|
|
|
|
|
test: [/MZBrowser/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'MZ Browser',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2018-07-09 21:26:43 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/focus/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Focus',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2018-07-09 21:26:43 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2018-09-09 15:07:40 +03:00
|
|
|
{
|
|
|
|
|
test: [/swing/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Swing',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2018-09-09 15:07:40 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
|
|
|
|
test: [/coast/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Opera Coast',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2020-06-02 00:00:04 +03:00
|
|
|
{
|
|
|
|
|
test: [/opt\/\d+(?:.?_?\d+)+/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Opera Touch',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
|
|
|
|
test: [/yabrowser/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Yandex Browser',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-04-07 11:28:03 +03:00
|
|
|
const version = Utils.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/ucbrowser/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'UC Browser',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
2018-08-29 22:44:50 +01:00
|
|
|
test: [/Maxthon|mxios/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Maxthon',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/epiphany/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Epiphany',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/puffin/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Puffin',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/sleipnir/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Sleipnir',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/k-meleon/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'K-Meleon',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2018-10-14 23:25:45 +08:00
|
|
|
{
|
|
|
|
|
test: [/micromessenger/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'WeChat',
|
|
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2019-09-18 10:34:14 +02:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/qqbrowser/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: (/qqbrowserlite/i).test(ua) ? 'QQ Browser Lite' : 'QQ Browser',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2018-10-14 23:25:45 +08:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
|
|
|
|
test: [/msie|trident/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Internet Explorer',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2019-04-14 13:39:35 +03:00
|
|
|
{
|
|
|
|
|
test: [/\sedg\//i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Microsoft Edge',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const version = Utils.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
2026-03-14 16:17:53 +02:00
|
|
|
test: [/edg([ea]|ios)\//i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Microsoft Edge',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2017-12-20 22:34:29 +02:00
|
|
|
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
2026-02-13 23:09:48 +02:00
|
|
|
test(parser) {
|
|
|
|
|
// Check Client Hints brands for Vivaldi
|
|
|
|
|
if (parser.hasBrand('Vivaldi')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Fall back to UA string detection
|
|
|
|
|
return parser.test(/vivaldi/i);
|
|
|
|
|
},
|
|
|
|
|
describe(ua, parser) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Vivaldi',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2026-02-13 23:09:48 +02:00
|
|
|
|
|
|
|
|
// Try Client Hints brand version first
|
|
|
|
|
if (parser) {
|
|
|
|
|
const hintsVersion = parser.getBrandVersion('Vivaldi');
|
|
|
|
|
if (hintsVersion) {
|
|
|
|
|
browser.version = hintsVersion;
|
|
|
|
|
return browser;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall back to UA string version
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/seamonkey/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'SeaMonkey',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
2017-08-20 18:22:28 +03:00
|
|
|
test: [/sailfish/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Sailfish',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2017-08-20 18:22:28 +03:00
|
|
|
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, ua);
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/silk/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Amazon Silk',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/phantom/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'PhantomJS',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/slimerjs/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'SlimerJS',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'BlackBerry',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
2018-07-09 21:26:43 +03:00
|
|
|
test: [/(web|hpw)[o0]s/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'WebOS Browser',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
|
|
|
|
{
|
2017-04-09 17:13:00 +03:00
|
|
|
test: [/bada/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Bada',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-09 17:13:00 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 17:13:00 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/tizen/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Tizen',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-09 17:13:00 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 17:13:00 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/qupzilla/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'QupZilla',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-09 17:13:00 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 17:13:00 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
2025-11-22 19:01:03 +02:00
|
|
|
{
|
|
|
|
|
test: [/librewolf/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'LibreWolf',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:librewolf)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-08-20 18:22:28 +03:00
|
|
|
{
|
2020-07-09 21:23:10 +03:00
|
|
|
test: [/firefox|iceweasel|fxios/i],
|
2017-08-20 18:22:28 +03:00
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Firefox',
|
2017-08-20 18:22:28 +03:00
|
|
|
};
|
2020-07-09 21:23:10 +03:00
|
|
|
const version = Utils.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
2017-08-20 18:22:28 +03:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-08-20 18:22:28 +03:00
|
|
|
},
|
2019-12-04 15:55:36 +03:00
|
|
|
{
|
|
|
|
|
test: [/electron/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Electron',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
2025-11-22 23:31:31 +02:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
test: [/sogoumobilebrowser/i, /metasr/i, /se 2\.[x]/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Sogou Browser',
|
|
|
|
|
};
|
|
|
|
|
const sogouMobileVersion = Utils.getFirstMatch(/(?:sogoumobilebrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
const chromiumVersion = Utils.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
const seVersion = Utils.getFirstMatch(/se ([\d.]+)x/i, ua);
|
|
|
|
|
const version = sogouMobileVersion || chromiumVersion || seVersion;
|
|
|
|
|
|
2019-12-04 15:55:36 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2020-08-23 13:58:07 -03:00
|
|
|
{
|
|
|
|
|
test: [/MiuiBrowser/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Miui',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-01 21:50:33 +00:00
|
|
|
/* 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;
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-07 18:29:20 +02:00
|
|
|
/* Brave Browser */
|
|
|
|
|
{
|
|
|
|
|
test(parser) {
|
|
|
|
|
// Check Client Hints brands for Brave
|
|
|
|
|
return parser.hasBrand('Brave');
|
|
|
|
|
},
|
|
|
|
|
describe(ua, parser) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Brave',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (parser) {
|
|
|
|
|
const hintsVersion = parser.getBrandVersion('Brave');
|
|
|
|
|
if (hintsVersion) {
|
|
|
|
|
browser.version = hintsVersion;
|
|
|
|
|
return browser;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
{
|
|
|
|
|
test: [/chromium/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Chromium',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-09 17:13:00 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 17:13:00 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
2026-07-31 11:10:10 +02:00
|
|
|
/* Ladybird */
|
|
|
|
|
{
|
|
|
|
|
test: [/ladybird/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Ladybird',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/ladybird\/(\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
{
|
|
|
|
|
test: [/chrome|crios|crmo/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Chrome',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua);
|
2017-04-09 17:13:00 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 17:13:00 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
2019-09-25 08:44:58 -03:00
|
|
|
{
|
|
|
|
|
test: [/GSA/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'Google Search',
|
|
|
|
|
};
|
|
|
|
|
const version = Utils.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, ua);
|
|
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
2017-04-09 22:09:47 +03:00
|
|
|
|
|
|
|
|
/* Android Browser */
|
2017-04-09 17:13:00 +03:00
|
|
|
{
|
|
|
|
|
test(parser) {
|
2017-08-20 18:22:28 +03:00
|
|
|
const notLikeAndroid = !parser.test(/like android/i);
|
2017-04-09 22:09:47 +03:00
|
|
|
const butAndroid = parser.test(/android/i);
|
|
|
|
|
return notLikeAndroid && butAndroid;
|
2017-04-09 17:13:00 +03:00
|
|
|
},
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Android Browser',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-04 23:03:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
},
|
2017-04-09 22:09:47 +03:00
|
|
|
|
2019-02-06 17:47:28 +09:00
|
|
|
/* PlayStation 4 */
|
|
|
|
|
{
|
|
|
|
|
test: [/playstation 4/i],
|
|
|
|
|
describe(ua) {
|
|
|
|
|
const browser = {
|
|
|
|
|
name: 'PlayStation 4',
|
|
|
|
|
};
|
2019-03-06 14:27:35 +02:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2019-02-06 17:47:28 +09:00
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
2017-04-09 22:09:47 +03:00
|
|
|
/* Safari */
|
2017-04-04 23:03:47 +03:00
|
|
|
{
|
2017-04-09 22:09:47 +03:00
|
|
|
test: [/safari|applewebkit/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2017-08-17 23:33:46 +03:00
|
|
|
const browser = {
|
2017-12-20 23:29:06 +02:00
|
|
|
name: 'Safari',
|
2017-08-17 23:33:46 +03:00
|
|
|
};
|
2019-02-14 16:06:14 +00:00
|
|
|
const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
|
2017-04-04 23:03:47 +03:00
|
|
|
|
2017-08-17 23:33:46 +03:00
|
|
|
if (version) {
|
|
|
|
|
browser.version = version;
|
2017-04-09 22:09:47 +03:00
|
|
|
}
|
2017-08-17 23:33:46 +03:00
|
|
|
|
|
|
|
|
return browser;
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
2017-04-09 22:09:47 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* Something else */
|
|
|
|
|
{
|
|
|
|
|
test: [/.*/i],
|
2017-05-18 22:57:18 +03:00
|
|
|
describe(ua) {
|
2019-04-23 10:28:11 +01:00
|
|
|
/* Here we try to make sure that there are explicit details about the device
|
|
|
|
|
* in order to decide what regexp exactly we want to apply
|
|
|
|
|
* (as there is a specific decision based on that conclusion)
|
|
|
|
|
*/
|
fix: resolve open GitHub security findings
Code scanning (js/polynomial-redos, alerts #16 and #17)
-------------------------------------------------------
bowser applies these regexps to attacker-controlled User-Agent strings, and
four of them ran in quadratic time:
* The "Something else" fallback used /^(.*)\/(.*) / and
/^(.*)\/(.*)[ \t]\((.*)/. Two unbounded `.*` before a required literal
make the split ambiguous, so a UA of "/a" repeated backtracks O(n^2).
Greedy `(.*)` always picks the last `/` that still has the delimiter
after it, so the second group can never span a `/` -- narrowing it to
`[^/]*` is exactly equivalent and removes the ambiguity. Verified
identical on 1,000,000 fuzzed inputs and on the full acceptance corpus.
* The Linespider and SlackBot version regexps used `(?:-[-\w]+)?` before a
required `[\s/]`. Since `[-\w]` and `[\s/]` are disjoint the backtracking
is pure waste, but the engine still walks it once per start position, so
"linespider-" repeated is quadratic. Bounding the run to `{1,64}` makes
it linear; no real bot-name suffix approaches 64 characters.
A sweep of all 253 regexp literals in src/ (fuzzed for superlinear scaling,
with the four pre-fix patterns used to confirm the detector works) reports no
remaining superlinear regexps. test/unit/redos.js locks this in.
Actions (actions/missing-workflow-permissions, alerts #5 and #15)
-----------------------------------------------------------------
merge-to-master.yml and draft-or-update-next-release.yml had no `permissions`
block and so inherited the default token. Both now declare least privilege,
matching publish.yml and pull-request.yml.
Dependabot (22 open alerts, all development scope)
--------------------------------------------------
bowser ships no runtime dependencies, so none of these reached consumers, but
they were live in CI. `pnpm audit` goes from 29 advisories to 0:
* jsdoc 3 -> 4 (with docdash 1 -> 2) drops taffydb, which has no patched
release, and picks up current markdown-it/linkify-it.
* coveralls -> coveralls-next 6 drops `request`, which is deprecated with no
patched release, along with form-data, qs 6.5.x, uuid 3 and tough-cookie 2.
Same `coveralls` bin and same stdin contract; lcov conversion verified.
* gh-pages 3 -> 6 clears the critical prototype pollution advisory.
* pnpm overrides pin the remaining transitive-only advisories to the lowest
patched release on each existing major.
Verified: pnpm audit clean, lint clean, 346 tests pass (the acceptance corpus
runs against both src/ and the built es5.js), build, package smoke test, and
doc generation.
2026-08-30 20:37:19 +03:00
|
|
|
const regexpWithoutDeviceSpec = /^(.*)\/([^/]*) /;
|
|
|
|
|
const regexpWithDeviceSpec = /^(.*)\/([^/]*)[ \t]\((.*)/;
|
2019-04-23 10:28:11 +01:00
|
|
|
const hasDeviceSpec = ua.search('\\(') !== -1;
|
2019-04-23 10:40:04 +01:00
|
|
|
const regexp = hasDeviceSpec ? regexpWithDeviceSpec : regexpWithoutDeviceSpec;
|
2017-04-09 22:09:47 +03:00
|
|
|
return {
|
2019-04-05 18:18:39 +01:00
|
|
|
name: Utils.getFirstMatch(regexp, ua),
|
|
|
|
|
version: Utils.getSecondMatch(regexp, ua),
|
2017-04-09 22:09:47 +03:00
|
|
|
};
|
2017-12-20 23:29:06 +02:00
|
|
|
},
|
|
|
|
|
},
|
2017-04-04 23:03:47 +03:00
|
|
|
];
|
|
|
|
|
|
2017-04-09 17:13:00 +03:00
|
|
|
export default browsersList;
|