Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
提交
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .eslintrc.json

This file was deleted.

10 changes: 4 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@ jobs:
strategy:
matrix:
node-version:
- "16.x"
- "17.x"
- "18.x"
- "19.x"
- "20.x"
- "22.x"
- "24.x"
steps:
- uses: actions/checkout@v4
- name: "Set up Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: "${{ matrix.node-version }}"
- uses: actions/checkout@v2
- run: npm install
- run: npm run lint
- run: npm run test-and-coverage
3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no-install lint-staged
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
#### 0.8.0 (2026-08-27)

- 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)]

- **Fixes:**
* Hide the macOS version when it is reported as the fingerprinting-mitigation frozen value (`10.15.7` for Chrome/Safari, `10.15` for Firefox)
* Recognize macOS versions starting with `2x` (e.g. macOS 26 Tahoe) — the OS X version regex previously only matched `1x` (10 to 19)
* Recover the real iOS version from Safari's `Version/` token when iOS 26+ freezes the reported OS version at `18.x`/`19.x`
* `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

- **Database:**
* Adds macOS nicknames `Ventura` (13), `Sonoma` (14), `Sequoia` (15) and `Tahoe` (26)
* Adds Darwin-to-macOS/iOS version mapping up to Darwin 25 (macOS 26 Tahoe / iOS 26)
* 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
* Routine refresh of the applications, models, indices, profiles and regexes databases

- **Operations:**
* 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
* 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
* 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`
* 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
* `husky` 9 dropped the `.husky/_/husky.sh` boilerplate hooks used to source; updated `.husky/pre-commit` accordingly
* `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
* `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

#### 0.7.1 (2023-11-01)

- **Database:** Added Chrome 101 to 118.
Expand Down
24 changes: 14 additions & 10 deletions bin/update-applications.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ const types = [
needsBrowserConstants: true,
},
];
const request = require('request');
const fs = require('fs');
const path = require('path');

types.forEach((type) => {
console.log(`Updating application-${type.name.toLowerCase()}...`);
const filepath = path.join(__dirname, `../data/applications-${type.name.toLowerCase()}.js`);
const fileStream = fs.createWriteStream(filepath);
request(
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/applications-${type.name.toLowerCase()}.php`,
(err, response = {}) => {
if (err) {
return;
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/applications-${type.name.toLowerCase()}.php`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`${url} responded with HTTP ${response.status}`);
}

let result = response.body.match(/\[[\s\S]*\]/)[0];
return response.text();
})
.then((body) => {
let result = body.match(/\[[\s\S]*\]/)[0];

// Remove => and add :
result = result
Expand Down Expand Up @@ -65,6 +66,9 @@ types.forEach((type) => {
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
fileStream.end();
console.log(`Downloaded new application-${type.name.toLowerCase()}...`);
}
);
})
.catch((err) => {
console.error(`Failed to update application-${type.name.toLowerCase()}: ${err.message}`);
process.exitCode = 1;
});
});
36 changes: 22 additions & 14 deletions bin/update-browser-ids.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
/* eslint-disable no-console */
const request = require('request');
const fs = require('fs');
const path = require('path');
console.log('Updating browser ids...');
const filepath = path.join(__dirname, '../data/id-android.js');
const fileStream = fs.createWriteStream(filepath);

request('https://api.whichbrowser.net/resources/browser-ids.json', (err, response = {}) => {
if (err) {
return;
}
const result = JSON.parse(response.body);
fileStream.write('/* This file is automatically generated, do not edit manually! */\n\n');
fileStream.write('exports.ANDROID_BROWSERS = {\n');
result.forEach((val) => fileStream.write(` '${val.browserId}': ${deviceString(val.browserName)},\n`), null);
fileStream.write('};\n\n');
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
fileStream.end();
console.log(`Browser ids update complete with ${result.length} entries...`);
});
const URL = 'https://api.whichbrowser.net/resources/browser-ids.json';

fetch(URL)
.then((response) => {
if (!response.ok) {
throw new Error(`${URL} responded with HTTP ${response.status}`);
}
return response.json();
})
.then((result) => {
fileStream.write('/* This file is automatically generated, do not edit manually! */\n\n');
fileStream.write('exports.ANDROID_BROWSERS = {\n');
result.forEach((val) => fileStream.write(` '${val.browserId}': ${deviceString(val.browserName)},\n`), null);
fileStream.write('};\n\n');
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
fileStream.end();
console.log(`Browser ids update complete with ${result.length} entries...`);
})
.catch((err) => {
console.error(`Failed to update browser ids: ${err.message}`);
process.exitCode = 1;
});

/**
* Escapes \ and ' inside string
Expand Down
123 changes: 69 additions & 54 deletions bin/update-chrome.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,83 @@
/* eslint-disable no-console */
const request = require('request');
const fs = require('fs');
const path = require('path');
const Chrome = require('../src/data/Chrome');
console.log('Updating chrome versions...');
const filepath = path.join(__dirname, '../data/browsers-chrome.js');
const fileStream = fs.createWriteStream(filepath);
const stable = {
desktop: [],
mobile: [],
};
function comparator(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
}

request('http://omahaproxy.appspot.com/history', (err, response = {}) => {
if (err) {
return;
}
const omaha = response.body.split('\n');
omaha.forEach((line) => {
const [os, stability, version] = line.split(',');
if (os === 'mac' && stability === 'stable') {
stable.desktop.push(version.split('.').slice(0, 3).join('.'));
}
if (os === 'android' && stability === 'stable') {
stable.mobile.push(version.split('.').slice(0, 3).join('.'));
}
});

stable.desktop.sort(comparator);
stable.mobile.sort(comparator);
/* OmahaProxy (the previous source for this data) has been shut down; the Chromium Dash
* release API is the maintained replacement. `num` is capped server-side at 1000, which
* is enough to cover the full Stable release history for both platforms. */
const ENDPOINT = 'https://chromiumdash.appspot.com/fetch_releases?channel=Stable&num=1000&platform=';

let count新建Desktop = 0;
stable.desktop.forEach((version) => {
if (!Chrome.DESKTOP[version]) {
Chrome.DESKTOP[version] = 'stable';
count新建Desktop++;
function compareVersions(a, b) {
const partsA = a.split('.').map(Number);
const partsB = b.split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const diff = (partsA[i] || 0) - (partsB[i] || 0);
if (diff !== 0) {
return diff;
}
});
}
return 0;
}

let count新建Mobile = 0;
stable.mobile.forEach((version) => {
if (!Chrome.MOBILE[version]) {
Chrome.MOBILE[version] = 'stable';
count新建Mobile++;
}
});
function fetchPlatform(platform, callback) {
const url = ENDPOINT + platform;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`${url} responded with HTTP ${response.status}`);
}
return response.json();
})
.then((body) => {
if (!Array.isArray(body)) {
throw new Error(`${url} returned an unexpected payload`);
}
callback(body.map((release) => release.version.split('.').slice(0, 3).join('.')));
})
.catch((err) => {
console.error(`Failed to fetch ${platform} releases: ${err.message}`);
process.exitCode = 1;
callback([]);
});
}

fetchPlatform('Mac', (desktopVersions) => {
fetchPlatform('Android', (mobileVersions) => {
let count新建Desktop = 0;
desktopVersions.forEach((version) => {
if (!Chrome.DESKTOP[version]) {
Chrome.DESKTOP[version] = 'stable';
count新建Desktop++;
}
});

fileStream.write('module.exports = {\n');
fileStream.write(' DESKTOP: {\n');
Object.keys(Chrome.DESKTOP).forEach((version) =>
fileStream.write(` '${version}': '${Chrome.DESKTOP[version]}',\n`)
);
fileStream.write(' },\n');
fileStream.write(' MOBILE: {\n');
Object.keys(Chrome.MOBILE).forEach((version) =>
fileStream.write(` '${version}': '${Chrome.MOBILE[version]}',\n`)
);
fileStream.write(' },\n');
fileStream.write('};\n');
let count新建Mobile = 0;
mobileVersions.forEach((version) => {
if (!Chrome.MOBILE[version]) {
Chrome.MOBILE[version] = 'stable';
count新建Mobile++;
}
});

fileStream.end();
const fileStream = fs.createWriteStream(filepath);
fileStream.write('module.exports = {\n');
fileStream.write(' DESKTOP: {\n');
Object.keys(Chrome.DESKTOP)
.sort(compareVersions)
.forEach((version) => fileStream.write(` '${version}': '${Chrome.DESKTOP[version]}',\n`));
fileStream.write(' },\n');
fileStream.write(' MOBILE: {\n');
Object.keys(Chrome.MOBILE)
.sort(compareVersions)
.forEach((version) => fileStream.write(` '${version}': '${Chrome.MOBILE[version]}',\n`));
fileStream.write(' },\n');
fileStream.write('};\n');
fileStream.end();

console.log(`Updated chrome with ${count新建Desktop} new stable version for DEKSTOP`);
console.log(`Updated chrome with ${count新建Mobile} new stable version for MOBILE`);
console.log(`Updated chrome with ${count新建Desktop} new stable version for DEKSTOP`);
console.log(`Updated chrome with ${count新建Mobile} new stable version for MOBILE`);
});
});
24 changes: 14 additions & 10 deletions bin/update-indices.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,22 @@ function updateIndeces(type) {
* @param {string} type
*/
function updateIndecesFromWeb(type) {
const request = require('request');
const fs = require('fs');
const path = require('path');

console.log(`Updating indices models-${type}...`);
const filepath = path.join(__dirname, `../data/indices/models-${type}.js`);
const fileStream = fs.createWriteStream(filepath);
request(
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/indices/models-${type}.php`,
(err, response = {}) => {
if (err) {
return;
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/indices/models-${type}.php`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`${url} responded with HTTP ${response.status}`);
}

let result = response.body.match(/array \(([\s\S]*)\);/)[1];
return response.text();
})
.then((body) => {
let result = body.match(/array \(([\s\S]*)\);/)[1];

// Replace '@50' => with '@50':
result = result.replace(/('@.*') =>/g, '$1:');
Expand All @@ -97,8 +98,11 @@ function updateIndecesFromWeb(type) {
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
fileStream.end();
console.log(`Downloaded new application-${type}...`);
}
);
})
.catch((err) => {
console.error(`Failed to update indices/models-${type}: ${err.message}`);
process.exitCode = 1;
});
}
/**
* Sort object by keys
Expand Down
24 changes: 14 additions & 10 deletions bin/update-models.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,23 @@ const models = [
},
];

const request = require('request');
const fs = require('fs');
const path = require('path');

models.forEach((type) => {
console.log(`Updating application-${type.name.toLowerCase()}...`);
const filepath = path.join(__dirname, `../data/models-${type.name.toLowerCase()}.js`);
const fileStream = fs.createWriteStream(filepath);
request(
`https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/models-${type.name.toLowerCase()}.php`,
(err, response = {}) => {
if (err) {
return;
const url = `https://raw.githubusercontent.com/WhichBrowser/Parser/master/data/models-${type.name.toLowerCase()}.php`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`${url} responded with HTTP ${response.status}`);
}

let result = response.body.match(/\[[\s\S]*\]/)[0];
return response.text();
})
.then((body) => {
let result = body.match(/\[[\s\S]*\]/)[0];

// Remove => and add :
result = result.replace(/=>/g, ':');
Expand Down Expand Up @@ -116,6 +117,9 @@ models.forEach((type) => {
fileStream.write('/* This file is automatically generated, do not edit manually! */\n');
fileStream.end();
console.log(`Downloaded new application-${type.name.toLowerCase()}...`);
}
);
})
.catch((err) => {
console.error(`Failed to update models-${type.name.toLowerCase()}: ${err.message}`);
process.exitCode = 1;
});
});
Loading
Loading