Skip to content

Commit 5f5bb40

Browse files
committed
v0.8.0
* Sync detection logic with WhichBrowser/Parser-PHP up to 581d614 * Hide the macOS version when reported as the frozen fingerprinting-mitigation value (10.15.7/10.15) * Fix OS X version detection to also match macOS 20-29 (e.g. Tahoe 26), previously capped at 10-19 * Recover the real iOS version from Safari's Version/ token when iOS 26+ freezes the UA at 18.x/19.x * Add macOS nicknames Ventura, Sonoma, Sequoia and Tahoe, and Darwin mapping up to macOS/iOS 26 * Routine refresh of the applications, models, indices, profiles and regexes databases
1 parent 16f8439 commit 5f5bb40

99 files changed

Lines changed: 4161 additions & 5659 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

.github/workflows/main.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ jobs:
1212
strategy:
1313
matrix:
1414
node-version:
15-
- "16.x"
16-
- "17.x"
17-
- "18.x"
18-
- "19.x"
1915
- "20.x"
16+
- "22.x"
17+
- "24.x"
2018
steps:
19+
- uses: actions/checkout@v4
2120
- name: "Set up Node.js ${{ matrix.node-version }}"
22-
uses: actions/setup-node@v1
21+
uses: actions/setup-node@v4
2322
with:
2423
node-version: "${{ matrix.node-version }}"
25-
- uses: actions/checkout@v2
2624
- run: npm install
2725
- run: npm run lint
2826
- run: npm run test-and-coverage

.husky/pre-commit

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
#!/usr/bin/env sh
2-
. "$(dirname -- "$0")/_/husky.sh"
3-
41
npx --no-install lint-staged

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
#### 0.8.0 (2026-08-27)
2+
3+
- Sync WhichBrowser/Parser-JavaScript to [WhichBrowser/Parser-PHP@581d614](https://github.com/WhichBrowser/Parser-PHP/commit/581d614d686bfbec3529ad60562a5213ac5d8d72) [[diff](https://github.com/WhichBrowser/Parser-PHP/compare/1044880bc792dbce5948fbff22ae731c43c280d9...581d614d686bfbec3529ad60562a5213ac5d8d72)]
4+
5+
- **Fixes:**
6+
* Hide the macOS version when it is reported as the fingerprinting-mitigation frozen value (`10.15.7` for Chrome/Safari, `10.15` for Firefox)
7+
* Recognize macOS versions starting with `2x` (e.g. macOS 26 Tahoe) — the OS X version regex previously only matched `1x` (10 to 19)
8+
* Recover the real iOS version from Safari's `Version/` token when iOS 26+ freezes the reported OS version at `18.x`/`19.x`
9+
* `update-chrome` pulled its data from `omahaproxy.appspot.com`, which has been shut down for years and returned 404, so it silently updated nothing. Chrome versions released after 118 (Nov 2023) had no matching entry in the stable-channel database and were consequently misidentified as `Chrome Dev`. Switched to the Chromium Dash release API
10+
11+
- **Database:**
12+
* Adds macOS nicknames `Ventura` (13), `Sonoma` (14), `Sequoia` (15) and `Tahoe` (26)
13+
* Adds Darwin-to-macOS/iOS version mapping up to Darwin 25 (macOS 26 Tahoe / iOS 26)
14+
* Added Chrome stable versions 119 to 153 (desktop) and 119 to 152 (mobile). Firefox and Edge were checked too — both parse the browser version directly from the UA string with no version lookup table, so current releases (Firefox 141, Edge 140) were already detected correctly
15+
* Routine refresh of the applications, models, indices, profiles and regexes databases
16+
17+
- **Operations:**
18+
* Removed the deprecated `request` package (unmaintained, several vulnerabilities with no fix available) from the `bin/update-*` scripts in favor of the native `fetch`. This also resolved every other outstanding Dependabot alert (`js-yaml`, `handlebars`, `minimatch`, `picomatch`, `path-to-regexp`, `uuid`) by bumping `nyc`, and picking up newer transitive dependencies for `eslint`, `lint-staged` and `sinon` — none of this affects the published package, which has zero runtime dependencies
19+
* The `bin/update-*` scripts used to swallow fetch failures silently (this is exactly how the dead `omahaproxy` endpoint above went unnoticed for years); they now log the error and exit with a non-zero status code
20+
* Every devDependency bumped to latest: `eslint` 8→10, `husky` 8→9, `lint-staged` 15→17, `sinon` 17→22, `@hapi/lab` 25→26, `prettier`, `eslint-plugin-prettier`, `eslint-config-prettier`
21+
* Migrated `.eslintrc.json` to flat config (`eslint.config.js`), required by ESLint 9+. Replaced `eslint-config-google` — archived by Google in 2025, last released in 2019, and no longer loadable at all under ESLint 10 since it still references core rules (`valid-jsdoc`, `require-jsdoc`) that were removed — with the ~20 rules it contributed that weren't already superseded by Prettier, declared directly so the project no longer depends on an abandoned package
22+
* `husky` 9 dropped the `.husky/_/husky.sh` boilerplate hooks used to source; updated `.husky/pre-commit` accordingly
23+
* `nyc` 18 and `lint-staged` 17 require Node 20+/22+ to run; updated the CI matrix from Node 16-20 (all EOL except the tail of 20.x) to the currently maintained LTS lines (20.x, 22.x, 24.x), and bumped `actions/checkout`/`actions/setup-node` to v4
24+
* `update-all` now runs `prettier`/`eslint --fix` on `data/` right after regenerating it (new `format-data` script), so the generated files are committed in their final formatted form instead of relying on the pre-commit hook to reformat them after the fact
25+
126
#### 0.7.1 (2023-11-01)
227

328
- **Database:** Added Chrome 101 to 118.

bin/update-applications.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,23 @@ const types = [
1616
needsBrowserConstants: true,
1717
},
1818
];
19-
const request = require('request');
2019
const fs = require('fs');
2120
const path = require('path');
2221

2322
types.forEach((type) => {
2423
console.log(`Updating application-${type.name.toLowerCase()}...`);
2524
const filepath = path.join(__dirname, `../data/applications-${type.name.toLowerCase()}.js`);
2625
const fileStream = fs.createWriteStream(filepath);
27-
request(
28-
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/applications-${type.name.toLowerCase()}.php`,
29-
(err, response = {}) => {
30-
if (err) {
31-
return;
26+
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/applications-${type.name.toLowerCase()}.php`;
27+
fetch(url)
28+
.then((response) => {
29+
if (!response.ok) {
30+
throw new Error(`${url} responded with HTTP ${response.status}`);
3231
}
33-
34-
let result = response.body.match(/\[[\s\S]*\]/)[0];
32+
return response.text();
33+
})
34+
.then((body) => {
35+
let result = body.match(/\[[\s\S]*\]/)[0];
3536

3637
// Remove => and add :
3738
result = result
@@ -65,6 +66,9 @@ types.forEach((type) => {
6566
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
6667
fileStream.end();
6768
console.log(`Downloaded new application-${type.name.toLowerCase()}...`);
68-
}
69-
);
69+
})
70+
.catch((err) => {
71+
console.error(`Failed to update application-${type.name.toLowerCase()}: ${err.message}`);
72+
process.exitCode = 1;
73+
});
7074
});

bin/update-browser-ids.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
/* eslint-disable no-console */
2-
const request = require('request');
32
const fs = require('fs');
43
const path = require('path');
54
console.log('Updating browser ids...');
65
const filepath = path.join(__dirname, '../data/id-android.js');
76
const fileStream = fs.createWriteStream(filepath);
87

9-
request('https://api.whichbrowser.net/resources/browser-ids.json', (err, response = {}) => {
10-
if (err) {
11-
return;
12-
}
13-
const result = JSON.parse(response.body);
14-
fileStream.write('/* This file is automatically generated, do not edit manually! */\n\n');
15-
fileStream.write('exports.ANDROID_BROWSERS = {\n');
16-
result.forEach((val) => fileStream.write(` '${val.browserId}': ${deviceString(val.browserName)},\n`), null);
17-
fileStream.write('};\n\n');
18-
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
19-
fileStream.end();
20-
console.log(`Browser ids update complete with ${result.length} entries...`);
21-
});
8+
const URL = 'https://api.whichbrowser.net/resources/browser-ids.json';
9+
10+
fetch(URL)
11+
.then((response) => {
12+
if (!response.ok) {
13+
throw new Error(`${URL} responded with HTTP ${response.status}`);
14+
}
15+
return response.json();
16+
})
17+
.then((result) => {
18+
fileStream.write('/* This file is automatically generated, do not edit manually! */\n\n');
19+
fileStream.write('exports.ANDROID_BROWSERS = {\n');
20+
result.forEach((val) => fileStream.write(` '${val.browserId}': ${deviceString(val.browserName)},\n`), null);
21+
fileStream.write('};\n\n');
22+
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
23+
fileStream.end();
24+
console.log(`Browser ids update complete with ${result.length} entries...`);
25+
})
26+
.catch((err) => {
27+
console.error(`Failed to update browser ids: ${err.message}`);
28+
process.exitCode = 1;
29+
});
2230

2331
/**
2432
* Escapes \ and ' inside string

bin/update-chrome.js

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,83 @@
11
/* eslint-disable no-console */
2-
const request = require('request');
32
const fs = require('fs');
43
const path = require('path');
54
const Chrome = require('../src/data/Chrome');
65
console.log('Updating chrome versions...');
76
const filepath = path.join(__dirname, '../data/browsers-chrome.js');
8-
const fileStream = fs.createWriteStream(filepath);
9-
const stable = {
10-
desktop: [],
11-
mobile: [],
12-
};
13-
function comparator(a, b) {
14-
return parseInt(a, 10) - parseInt(b, 10);
15-
}
167

17-
request('http://omahaproxy.appspot.com/history', (err, response = {}) => {
18-
if (err) {
19-
return;
20-
}
21-
const omaha = response.body.split('\n');
22-
omaha.forEach((line) => {
23-
const [os, stability, version] = line.split(',');
24-
if (os === 'mac' && stability === 'stable') {
25-
stable.desktop.push(version.split('.').slice(0, 3).join('.'));
26-
}
27-
if (os === 'android' && stability === 'stable') {
28-
stable.mobile.push(version.split('.').slice(0, 3).join('.'));
29-
}
30-
});
31-
32-
stable.desktop.sort(comparator);
33-
stable.mobile.sort(comparator);
8+
/* OmahaProxy (the previous source for this data) has been shut down; the Chromium Dash
9+
* release API is the maintained replacement. `num` is capped server-side at 1000, which
10+
* is enough to cover the full Stable release history for both platforms. */
11+
const ENDPOINT = 'https://chromiumdash.appspot.com/fetch_releases?channel=Stable&num=1000&platform=';
3412

35-
let countNewDesktop = 0;
36-
stable.desktop.forEach((version) => {
37-
if (!Chrome.DESKTOP[version]) {
38-
Chrome.DESKTOP[version] = 'stable';
39-
countNewDesktop++;
13+
function compareVersions(a, b) {
14+
const partsA = a.split('.').map(Number);
15+
const partsB = b.split('.').map(Number);
16+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
17+
const diff = (partsA[i] || 0) - (partsB[i] || 0);
18+
if (diff !== 0) {
19+
return diff;
4020
}
41-
});
21+
}
22+
return 0;
23+
}
4224

43-
let countNewMobile = 0;
44-
stable.mobile.forEach((version) => {
45-
if (!Chrome.MOBILE[version]) {
46-
Chrome.MOBILE[version] = 'stable';
47-
countNewMobile++;
48-
}
49-
});
25+
function fetchPlatform(platform, callback) {
26+
const url = ENDPOINT + platform;
27+
fetch(url)
28+
.then((response) => {
29+
if (!response.ok) {
30+
throw new Error(`${url} responded with HTTP ${response.status}`);
31+
}
32+
return response.json();
33+
})
34+
.then((body) => {
35+
if (!Array.isArray(body)) {
36+
throw new Error(`${url} returned an unexpected payload`);
37+
}
38+
callback(body.map((release) => release.version.split('.').slice(0, 3).join('.')));
39+
})
40+
.catch((err) => {
41+
console.error(`Failed to fetch ${platform} releases: ${err.message}`);
42+
process.exitCode = 1;
43+
callback([]);
44+
});
45+
}
46+
47+
fetchPlatform('Mac', (desktopVersions) => {
48+
fetchPlatform('Android', (mobileVersions) => {
49+
let countNewDesktop = 0;
50+
desktopVersions.forEach((version) => {
51+
if (!Chrome.DESKTOP[version]) {
52+
Chrome.DESKTOP[version] = 'stable';
53+
countNewDesktop++;
54+
}
55+
});
5056

51-
fileStream.write('module.exports = {\n');
52-
fileStream.write(' DESKTOP: {\n');
53-
Object.keys(Chrome.DESKTOP).forEach((version) =>
54-
fileStream.write(` '${version}': '${Chrome.DESKTOP[version]}',\n`)
55-
);
56-
fileStream.write(' },\n');
57-
fileStream.write(' MOBILE: {\n');
58-
Object.keys(Chrome.MOBILE).forEach((version) =>
59-
fileStream.write(` '${version}': '${Chrome.MOBILE[version]}',\n`)
60-
);
61-
fileStream.write(' },\n');
62-
fileStream.write('};\n');
57+
let countNewMobile = 0;
58+
mobileVersions.forEach((version) => {
59+
if (!Chrome.MOBILE[version]) {
60+
Chrome.MOBILE[version] = 'stable';
61+
countNewMobile++;
62+
}
63+
});
6364

64-
fileStream.end();
65+
const fileStream = fs.createWriteStream(filepath);
66+
fileStream.write('module.exports = {\n');
67+
fileStream.write(' DESKTOP: {\n');
68+
Object.keys(Chrome.DESKTOP)
69+
.sort(compareVersions)
70+
.forEach((version) => fileStream.write(` '${version}': '${Chrome.DESKTOP[version]}',\n`));
71+
fileStream.write(' },\n');
72+
fileStream.write(' MOBILE: {\n');
73+
Object.keys(Chrome.MOBILE)
74+
.sort(compareVersions)
75+
.forEach((version) => fileStream.write(` '${version}': '${Chrome.MOBILE[version]}',\n`));
76+
fileStream.write(' },\n');
77+
fileStream.write('};\n');
78+
fileStream.end();
6579

66-
console.log(`Updated chrome with ${countNewDesktop} new stable version for DEKSTOP`);
67-
console.log(`Updated chrome with ${countNewMobile} new stable version for MOBILE`);
80+
console.log(`Updated chrome with ${countNewDesktop} new stable version for DEKSTOP`);
81+
console.log(`Updated chrome with ${countNewMobile} new stable version for MOBILE`);
82+
});
6883
});

bin/update-indices.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,22 @@ function updateIndeces(type) {
6666
* @param {string} type
6767
*/
6868
function updateIndecesFromWeb(type) {
69-
const request = require('request');
7069
const fs = require('fs');
7170
const path = require('path');
7271

7372
console.log(`Updating indices models-${type}...`);
7473
const filepath = path.join(__dirname, `../data/indices/models-${type}.js`);
7574
const fileStream = fs.createWriteStream(filepath);
76-
request(
77-
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/indices/models-${type}.php`,
78-
(err, response = {}) => {
79-
if (err) {
80-
return;
75+
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/indices/models-${type}.php`;
76+
fetch(url)
77+
.then((response) => {
78+
if (!response.ok) {
79+
throw new Error(`${url} responded with HTTP ${response.status}`);
8180
}
82-
83-
let result = response.body.match(/array \(([\s\S]*)\);/)[1];
81+
return response.text();
82+
})
83+
.then((body) => {
84+
let result = body.match(/array \(([\s\S]*)\);/)[1];
8485

8586
// Replace '@50' => with '@50':
8687
result = result.replace(/('@.*') =>/g, '$1:');
@@ -97,8 +98,11 @@ function updateIndecesFromWeb(type) {
9798
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
9899
fileStream.end();
99100
console.log(`Downloaded new application-${type}...`);
100-
}
101-
);
101+
})
102+
.catch((err) => {
103+
console.error(`Failed to update indices/models-${type}: ${err.message}`);
104+
process.exitCode = 1;
105+
});
102106
}
103107
/**
104108
* Sort object by keys

bin/update-models.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,23 @@ const models = [
5050
},
5151
];
5252

53-
const request = require('request');
5453
const fs = require('fs');
5554
const path = require('path');
5655

5756
models.forEach((type) => {
5857
console.log(`Updating application-${type.name.toLowerCase()}...`);
5958
const filepath = path.join(__dirname, `../data/models-${type.name.toLowerCase()}.js`);
6059
const fileStream = fs.createWriteStream(filepath);
61-
request(
62-
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/models-${type.name.toLowerCase()}.php`,
63-
(err, response = {}) => {
64-
if (err) {
65-
return;
60+
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/models-${type.name.toLowerCase()}.php`;
61+
fetch(url)
62+
.then((response) => {
63+
if (!response.ok) {
64+
throw new Error(`${url} responded with HTTP ${response.status}`);
6665
}
67-
68-
let result = response.body.match(/\[[\s\S]*\]/)[0];
66+
return response.text();
67+
})
68+
.then((body) => {
69+
let result = body.match(/\[[\s\S]*\]/)[0];
6970

7071
// Remove => and add :
7172
result = result.replace(/=>/g, ':');
@@ -116,6 +117,9 @@ models.forEach((type) => {
116117
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
117118
fileStream.end();
118119
console.log(`Downloaded new application-${type.name.toLowerCase()}...`);
119-
}
120-
);
120+
})
121+
.catch((err) => {
122+
console.error(`Failed to update models-${type.name.toLowerCase()}: ${err.message}`);
123+
process.exitCode = 1;
124+
});
121125
});

0 commit comments

Comments
 (0)