mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Add periodic user agent string updates
This commit is contained in:
9
docs/ua.json
Normal file
9
docs/ua.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "./ua.schema.json",
|
||||
"version": 1,
|
||||
"platforms": {
|
||||
"win": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
"mac": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
"linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
|
||||
}
|
||||
}
|
||||
22
docs/ua.schema.json
Normal file
22
docs/ua.schema.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"ua_string": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"properties": {
|
||||
"$schema": { "type": "string" },
|
||||
"version": { "type": "integer", "const": 1 },
|
||||
"platforms": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"win": { "$ref": "#/$defs/ua_string" },
|
||||
"mac": { "$ref": "#/$defs/ua_string" },
|
||||
"linux": { "$ref": "#/$defs/ua_string" }
|
||||
},
|
||||
"required": ["win", "mac", "linux"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["version", "platforms"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import deviceManager from "./deviceManager";
|
||||
import { initAction } from "./action";
|
||||
import { initMenus } from "./menus";
|
||||
import { initWhitelist } from "./whitelist";
|
||||
import { cacheUaInfo, getChromeUserAgentString } from "../lib/userAgents";
|
||||
|
||||
const _ = browser.i18n.getMessage;
|
||||
|
||||
@@ -143,5 +144,6 @@ async function init() {
|
||||
});
|
||||
}
|
||||
|
||||
cacheUaInfo();
|
||||
cacheBaseConfig();
|
||||
init();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logger from "../lib/logger";
|
||||
import options from "../lib/options";
|
||||
|
||||
import { getChromeUserAgent } from "../lib/userAgents";
|
||||
import { cacheUaInfo, getChromeUserAgentString } from "../lib/userAgents";
|
||||
import { RemoteMatchPattern } from "../lib/matchPattern";
|
||||
|
||||
import {
|
||||
@@ -41,11 +41,17 @@ let customUserAgent: string | undefined;
|
||||
export async function initWhitelist() {
|
||||
logger.info("init (whitelist)");
|
||||
|
||||
await cacheUaInfo();
|
||||
|
||||
if (!platform) {
|
||||
const browserInfo = await browser.runtime.getBrowserInfo();
|
||||
|
||||
// TODO: Allow hybrid UA to be configurable
|
||||
platform = (await browser.runtime.getPlatformInfo()).os;
|
||||
chromeUserAgent = getChromeUserAgent(platform);
|
||||
chromeUserAgentHybrid = getChromeUserAgent(platform, true);
|
||||
chromeUserAgent = await getChromeUserAgentString(platform);
|
||||
chromeUserAgentHybrid = await getChromeUserAgentString(platform, {
|
||||
hybridFirefoxVersion: browserInfo.version
|
||||
});
|
||||
if (!chromeUserAgent) {
|
||||
throw logger.error("Failed to get Chrome UA string");
|
||||
}
|
||||
|
||||
@@ -106,4 +106,4 @@ export default {
|
||||
siteWhitelistCustomUserAgent: "",
|
||||
|
||||
showAdvancedOptions: false
|
||||
} as Options;
|
||||
} satisfies Options as Options;
|
||||
|
||||
@@ -1,25 +1,90 @@
|
||||
const PLATFORM_MAC = "Macintosh; Intel Mac OS X 12_5";
|
||||
const PLATFORM_MAC_HYBRID = "Macintosh; Intel Mac OS X 12_5; rv:103.0";
|
||||
const PLATFORM_WIN = "Windows NT 10.0; Win64; x64";
|
||||
const PLATFORM_LINUX = "X11; Linux x86_64";
|
||||
import logger from "./logger";
|
||||
import { TypedStorageArea } from "./TypedStorageArea";
|
||||
|
||||
const UA_CHROME =
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36";
|
||||
const UA_HYBRID = "Chrome/104.0.0.0 Gecko/20100101 Firefox/103.0";
|
||||
// Bundle UA info at time of build (as a fallback)
|
||||
import bundledUaInfo from "../../../docs/ua.json";
|
||||
|
||||
export function getChromeUserAgent(platform: string, hybrid = false) {
|
||||
let platformComponent: string;
|
||||
if (platform === "mac") {
|
||||
platformComponent = hybrid ? PLATFORM_MAC_HYBRID : PLATFORM_MAC;
|
||||
} else if (platform === "win") {
|
||||
platformComponent = PLATFORM_WIN;
|
||||
} else if (platform === "linux") {
|
||||
platformComponent = PLATFORM_LINUX;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const UA_INFO_ENDPOINT = "https://hensm.github.io/fx_cast/ua.json";
|
||||
|
||||
const browserComponent = hybrid ? UA_HYBRID : UA_CHROME;
|
||||
|
||||
return `Mozilla/5.0 (${platformComponent}) ${browserComponent}`;
|
||||
interface UaInfo {
|
||||
version: 1;
|
||||
platforms: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
const uaInfoStorage = new TypedStorageArea<{
|
||||
uaInfo: UaInfo | undefined;
|
||||
uaInfoUpdated: number | undefined;
|
||||
}>(browser.storage.local);
|
||||
|
||||
async function fetchUaInfo(): Promise<UaInfo | null> {
|
||||
try {
|
||||
const res: UaInfo = await fetch(UA_INFO_ENDPOINT).then(r => r.json());
|
||||
if (typeof res.version !== "number" || res.version !== 1) {
|
||||
throw logger.error(
|
||||
`UA info version mismatch (got ${res.version}, expected 1)`
|
||||
);
|
||||
} else if (
|
||||
typeof res.platforms !== "object" ||
|
||||
res.platforms === null
|
||||
) {
|
||||
throw logger.error("UA info platforms missing or invalid");
|
||||
}
|
||||
return res;
|
||||
} catch (err) {
|
||||
logger.error("Failed to fetch UA info:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function cacheUaInfo() {
|
||||
const { uaInfoUpdated } = await uaInfoStorage.get("uaInfoUpdated");
|
||||
|
||||
// If never updated or updated more than 24 hours ago
|
||||
if (!uaInfoUpdated || (Date.now() - uaInfoUpdated) / 1000 >= 86400) {
|
||||
logger.info("Fetching updated Chrome User-Agent strings...");
|
||||
const uaInfo = await fetchUaInfo();
|
||||
if (uaInfo) {
|
||||
await uaInfoStorage.set({ uaInfo, uaInfoUpdated: Date.now() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a chrome user */
|
||||
export async function getChromeUserAgentString(
|
||||
platform: string,
|
||||
opts?: { hybridFirefoxVersion?: string }
|
||||
) {
|
||||
const { uaInfo } = await uaInfoStorage.get("uaInfo");
|
||||
if (!uaInfo) {
|
||||
logger.warn("UA info not cached (using bundled version)");
|
||||
}
|
||||
const maybeBundledUaInfo = uaInfo ?? (bundledUaInfo as UaInfo);
|
||||
const userAgentString = maybeBundledUaInfo.platforms[platform];
|
||||
if (userAgentString) {
|
||||
// Return hybrid UA (mostly Firefox UA) if requested
|
||||
if (opts?.hybridFirefoxVersion) {
|
||||
// Sans-patch (e.g. "145.0.1" -> "145.0")
|
||||
const firefoxMajorMinorVersionMatch =
|
||||
opts.hybridFirefoxVersion.match(/^\d+\.\d+/);
|
||||
const componentMatch = userAgentString.match(
|
||||
/Mozilla\/5.0 \(([^\(]+)\) (.+)/
|
||||
);
|
||||
if (firefoxMajorMinorVersionMatch && componentMatch) {
|
||||
const [rv] = firefoxMajorMinorVersionMatch;
|
||||
const [, platformComponent, browserComponent] = componentMatch;
|
||||
// Extract Chrome/xxx part
|
||||
const chromeVersionMatch = browserComponent.match(
|
||||
/(?<= )Chrome\/[^ ]+(?= )/
|
||||
);
|
||||
if (chromeVersionMatch) {
|
||||
const [chromeVersion] = chromeVersionMatch;
|
||||
return `Mozilla/5.0 (${platformComponent}; rv:${rv}) ${chromeVersion} Gecko/20100101 Firefox/${rv}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return userAgentString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import options, { Options } from "../../lib/options";
|
||||
import defaultOptions from "../../defaultOptions";
|
||||
|
||||
import { getChromeUserAgent } from "../../lib/userAgents";
|
||||
import { getChromeUserAgentString } from "../../lib/userAgents";
|
||||
|
||||
const _ = browser.i18n.getMessage;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
let opts: Options | undefined;
|
||||
onMount(async () => {
|
||||
const platform = (await browser.runtime.getPlatformInfo()).os;
|
||||
defaultUserAgent = getChromeUserAgent(platform);
|
||||
defaultUserAgent = await getChromeUserAgentString(platform);
|
||||
|
||||
opts = await options.getAll();
|
||||
options.addEventListener("changed", async () => {
|
||||
|
||||
Reference in New Issue
Block a user