headfox-js 0.1.1 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -2
- package/dist/pkgman.d.ts +24 -3
- package/dist/pkgman.js +91 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,27 +79,48 @@ const browser = await firefox.connect(server.wsEndpoint());
|
|
|
79
79
|
|
|
80
80
|
## Browser source
|
|
81
81
|
|
|
82
|
-
By default, `headfox-js` downloads the
|
|
82
|
+
By default, `headfox-js` downloads the official Camoufox browser bundles from `daijro/camoufox`.
|
|
83
83
|
|
|
84
84
|
You can override the release source when you need to test a different fork or future Headfox-native releases:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
HEADFOX_JS_RELEASE_REPO=owner/repo
|
|
88
|
-
|
|
88
|
+
HEADFOX_JS_RELEASE_TAG=v135.0.1-beta.24
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
Useful env vars:
|
|
92
92
|
|
|
93
93
|
- `HEADFOX_JS_RELEASE_REPO`
|
|
94
|
+
- `HEADFOX_JS_RELEASE_TAG`
|
|
95
|
+
- `HEADFOX_JS_ASSET_NAME`
|
|
94
96
|
- `HEADFOX_JS_ASSET_PREFIXES`
|
|
95
97
|
- `HEADFOX_JS_CACHE_DIR`
|
|
96
98
|
|
|
97
99
|
Legacy compatibility env vars are still accepted:
|
|
98
100
|
|
|
99
101
|
- `CAMOUFOX_JS_RELEASE_REPO`
|
|
102
|
+
- `CAMOUFOX_JS_RELEASE_TAG`
|
|
103
|
+
- `CAMOUFOX_JS_ASSET_NAME`
|
|
100
104
|
- `CAMOUFOX_JS_ASSET_PREFIXES`
|
|
101
105
|
- `CAMOUFOX_JS_CACHE_DIR`
|
|
102
106
|
|
|
107
|
+
For deterministic installs in Docker or CI, pin a known tag and let `headfox-js` pick the matching platform asset:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
HEADFOX_JS_RELEASE_REPO=daijro/camoufox
|
|
111
|
+
HEADFOX_JS_RELEASE_TAG=v135.0.1-beta.24
|
|
112
|
+
npx headfox-js fetch
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If you need a custom fork with irregular asset names, set both the tag and exact asset name:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
HEADFOX_JS_RELEASE_REPO=CloverLabsAI/camoufox
|
|
119
|
+
HEADFOX_JS_RELEASE_TAG=webrtc-ip-by-context
|
|
120
|
+
HEADFOX_JS_ASSET_NAME=roverfox-webrtc-fix.zip
|
|
121
|
+
npx headfox-js fetch
|
|
122
|
+
```
|
|
123
|
+
|
|
103
124
|
## Optional WebGL data support
|
|
104
125
|
|
|
105
126
|
For best fingerprint fidelity, `headfox-js` will use the bundled WebGL sampling database when the optional SQLite dependency is available.
|
package/dist/pkgman.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import type { PathLike } from "node:fs";
|
|
2
2
|
import type { Writable } from "node:stream";
|
|
3
3
|
export declare const OS_NAME: "mac" | "win" | "lin";
|
|
4
|
+
export declare const DEFAULT_RELEASE_REPO = "daijro/camoufox";
|
|
5
|
+
export declare function resolveReleaseRepo(): string;
|
|
6
|
+
export declare function resolveReleaseTag(): string | undefined;
|
|
7
|
+
export declare function resolveAssetName(): string | undefined;
|
|
8
|
+
export declare function resolveAssetPrefixes(): string[];
|
|
4
9
|
export declare const INSTALL_DIR: string;
|
|
5
10
|
export declare const LOCAL_DATA: PathLike;
|
|
6
11
|
export declare const OS_ARCH_MATRIX: {
|
|
7
12
|
[key: string]: string[];
|
|
8
13
|
};
|
|
14
|
+
export declare function buildAssetPattern(prefixes: string[], osName: "mac" | "win" | "lin", arch: string): RegExp;
|
|
15
|
+
export declare function parseVersionParts(...sources: Array<string | undefined | null>): {
|
|
16
|
+
version: string;
|
|
17
|
+
release: string;
|
|
18
|
+
} | null;
|
|
9
19
|
declare class Version {
|
|
10
20
|
release: string;
|
|
11
21
|
version?: string;
|
|
@@ -20,11 +30,21 @@ declare class Version {
|
|
|
20
30
|
static isSupportedPath(path: PathLike): boolean;
|
|
21
31
|
static buildMinMax(): [Version, Version];
|
|
22
32
|
}
|
|
33
|
+
type GitHubAsset = {
|
|
34
|
+
name: string;
|
|
35
|
+
browser_download_url: string;
|
|
36
|
+
};
|
|
37
|
+
type GitHubRelease = {
|
|
38
|
+
name?: string;
|
|
39
|
+
tag_name?: string;
|
|
40
|
+
assets: GitHubAsset[];
|
|
41
|
+
};
|
|
23
42
|
export declare class GitHubDownloader {
|
|
24
43
|
githubRepo: string;
|
|
25
44
|
apiUrl: string;
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
releaseTag?: string;
|
|
46
|
+
constructor(githubRepo: string, releaseTag?: string);
|
|
47
|
+
checkAsset(asset: GitHubAsset, _release: GitHubRelease): any;
|
|
28
48
|
missingAssetError(): void;
|
|
29
49
|
getAsset({ retries }?: {
|
|
30
50
|
retries: number;
|
|
@@ -35,9 +55,10 @@ export declare class HeadfoxFetcher extends GitHubDownloader {
|
|
|
35
55
|
_version_obj?: Version;
|
|
36
56
|
pattern: RegExp;
|
|
37
57
|
_url?: string;
|
|
58
|
+
assetNameOverride?: string;
|
|
38
59
|
constructor();
|
|
39
60
|
init(): Promise<void>;
|
|
40
|
-
checkAsset(asset:
|
|
61
|
+
checkAsset(asset: GitHubAsset, release: GitHubRelease): [Version, string] | null;
|
|
41
62
|
missingAssetError(): void;
|
|
42
63
|
static getPlatformArch(): string;
|
|
43
64
|
fetchLatest(): Promise<void>;
|
package/dist/pkgman.js
CHANGED
|
@@ -24,18 +24,33 @@ if (!(process.platform in OS_MAP)) {
|
|
|
24
24
|
throw new UnsupportedOS(`OS ${process.platform} is not supported`);
|
|
25
25
|
}
|
|
26
26
|
export const OS_NAME = OS_MAP[process.platform];
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
export const DEFAULT_RELEASE_REPO = "daijro/camoufox";
|
|
28
|
+
function readEnvValue(...names) {
|
|
29
|
+
for (const name of names) {
|
|
30
|
+
const value = process.env[name]?.trim();
|
|
31
|
+
if (value) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
export function resolveReleaseRepo() {
|
|
38
|
+
return (readEnvValue("HEADFOX_JS_RELEASE_REPO", "CAMOUFOX_JS_RELEASE_REPO") ??
|
|
39
|
+
DEFAULT_RELEASE_REPO);
|
|
40
|
+
}
|
|
41
|
+
export function resolveReleaseTag() {
|
|
42
|
+
return readEnvValue("HEADFOX_JS_RELEASE_TAG", "CAMOUFOX_JS_RELEASE_TAG");
|
|
43
|
+
}
|
|
44
|
+
export function resolveAssetName() {
|
|
45
|
+
return readEnvValue("HEADFOX_JS_ASSET_NAME", "CAMOUFOX_JS_ASSET_NAME");
|
|
46
|
+
}
|
|
47
|
+
export function resolveAssetPrefixes() {
|
|
48
|
+
return Array.from(new Set((readEnvValue("HEADFOX_JS_ASSET_PREFIXES", "CAMOUFOX_JS_ASSET_PREFIXES") ?? "camoufox,headfox")
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((value) => value.trim())
|
|
51
|
+
.filter(Boolean)));
|
|
52
|
+
}
|
|
53
|
+
const CACHE_DIR_NAME = readEnvValue("HEADFOX_JS_CACHE_DIR", "CAMOUFOX_JS_CACHE_DIR") ?? "headfox";
|
|
39
54
|
const APP_BUNDLE_CANDIDATES = ["Headfox.app", "Camoufox.app"];
|
|
40
55
|
const currentDir = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
|
|
41
56
|
export const INSTALL_DIR = userCacheDir(CACHE_DIR_NAME);
|
|
@@ -51,6 +66,28 @@ const LAUNCH_FILE_CANDIDATES = {
|
|
|
51
66
|
mac: ["../MacOS/headfox", "../MacOS/camoufox"],
|
|
52
67
|
lin: ["headfox-bin", "camoufox-bin"],
|
|
53
68
|
};
|
|
69
|
+
function escapeRegex(value) {
|
|
70
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
71
|
+
}
|
|
72
|
+
export function buildAssetPattern(prefixes, osName, arch) {
|
|
73
|
+
return new RegExp(`(?:${prefixes.map(escapeRegex).join("|")})-(.+)-(.+)-${escapeRegex(osName)}\\.${escapeRegex(arch)}\\.zip$`, "i");
|
|
74
|
+
}
|
|
75
|
+
export function parseVersionParts(...sources) {
|
|
76
|
+
const pattern = /(\d+(?:\.\d+){0,2})-((?:alpha|beta|rc)\.\d+)/i;
|
|
77
|
+
for (const source of sources) {
|
|
78
|
+
if (!source) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const match = source.match(pattern);
|
|
82
|
+
if (match) {
|
|
83
|
+
return {
|
|
84
|
+
version: match[1],
|
|
85
|
+
release: match[2].toLowerCase(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
54
91
|
function installMissingMessage(product = "Headfox") {
|
|
55
92
|
return `${product} is not installed at ${INSTALL_DIR}. Please run \`headfox-js fetch\` to install.`;
|
|
56
93
|
}
|
|
@@ -139,11 +176,15 @@ const [VERSION_MIN, VERSION_MAX] = Version.buildMinMax();
|
|
|
139
176
|
export class GitHubDownloader {
|
|
140
177
|
githubRepo;
|
|
141
178
|
apiUrl;
|
|
142
|
-
|
|
179
|
+
releaseTag;
|
|
180
|
+
constructor(githubRepo, releaseTag) {
|
|
143
181
|
this.githubRepo = githubRepo;
|
|
144
|
-
this.
|
|
182
|
+
this.releaseTag = releaseTag;
|
|
183
|
+
this.apiUrl = releaseTag
|
|
184
|
+
? `https://api.github.com/repos/${githubRepo}/releases/tags/${encodeURIComponent(releaseTag)}`
|
|
185
|
+
: `https://api.github.com/repos/${githubRepo}/releases`;
|
|
145
186
|
}
|
|
146
|
-
checkAsset(asset) {
|
|
187
|
+
checkAsset(asset, _release) {
|
|
147
188
|
return asset.browser_download_url;
|
|
148
189
|
}
|
|
149
190
|
missingAssetError() {
|
|
@@ -167,10 +208,13 @@ export class GitHubDownloader {
|
|
|
167
208
|
if (!response || !response.ok) {
|
|
168
209
|
throw new Error(`Failed to fetch releases from ${this.apiUrl} after ${retries} attempts`);
|
|
169
210
|
}
|
|
170
|
-
const
|
|
211
|
+
const releaseData = await response.json();
|
|
212
|
+
const releases = Array.isArray(releaseData)
|
|
213
|
+
? releaseData
|
|
214
|
+
: [releaseData];
|
|
171
215
|
for (const release of releases) {
|
|
172
216
|
for (const asset of release.assets) {
|
|
173
|
-
const data = this.checkAsset(asset);
|
|
217
|
+
const data = this.checkAsset(asset, release);
|
|
174
218
|
if (data) {
|
|
175
219
|
return data;
|
|
176
220
|
}
|
|
@@ -184,25 +228,47 @@ export class HeadfoxFetcher extends GitHubDownloader {
|
|
|
184
228
|
_version_obj;
|
|
185
229
|
pattern;
|
|
186
230
|
_url;
|
|
231
|
+
assetNameOverride;
|
|
187
232
|
constructor() {
|
|
188
|
-
super(
|
|
233
|
+
super(resolveReleaseRepo(), resolveReleaseTag());
|
|
189
234
|
this.arch = HeadfoxFetcher.getPlatformArch();
|
|
190
|
-
this.
|
|
235
|
+
this.assetNameOverride = resolveAssetName();
|
|
236
|
+
this.pattern = buildAssetPattern(resolveAssetPrefixes(), OS_NAME, this.arch);
|
|
191
237
|
}
|
|
192
238
|
async init() {
|
|
193
239
|
await this.fetchLatest();
|
|
194
240
|
}
|
|
195
|
-
checkAsset(asset) {
|
|
196
|
-
|
|
197
|
-
|
|
241
|
+
checkAsset(asset, release) {
|
|
242
|
+
if (this.assetNameOverride && asset.name !== this.assetNameOverride) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
if (!this.assetNameOverride && !asset.name.match(this.pattern)) {
|
|
198
246
|
return null;
|
|
199
|
-
|
|
200
|
-
|
|
247
|
+
}
|
|
248
|
+
const versionData = parseVersionParts(asset.name, release.name, release.tag_name);
|
|
249
|
+
if (!versionData) {
|
|
250
|
+
if (this.assetNameOverride) {
|
|
251
|
+
throw new MissingRelease(`Configured asset "${asset.name}" was found in ${this.githubRepo}, but headfox-js could not infer its browser version from the asset or release metadata.`);
|
|
252
|
+
}
|
|
201
253
|
return null;
|
|
254
|
+
}
|
|
255
|
+
const version = new Version(versionData.release, versionData.version);
|
|
256
|
+
if (!version.isSupported()) {
|
|
257
|
+
if (this.assetNameOverride) {
|
|
258
|
+
throw new UnsupportedVersion(`Configured asset "${asset.name}" resolved to unsupported browser release v${version.fullString}. Supported range: (${CONSTRAINTS.asRange()}).`);
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
202
262
|
return [version, asset.browser_download_url];
|
|
203
263
|
}
|
|
204
264
|
missingAssetError() {
|
|
205
|
-
|
|
265
|
+
const location = this.releaseTag
|
|
266
|
+
? `${this.githubRepo} release tag "${this.releaseTag}"`
|
|
267
|
+
: this.githubRepo;
|
|
268
|
+
if (this.assetNameOverride) {
|
|
269
|
+
throw new MissingRelease(`Could not find configured asset "${this.assetNameOverride}" in ${location}.`);
|
|
270
|
+
}
|
|
271
|
+
throw new MissingRelease(`No matching release asset found in ${location} for ${OS_NAME} ${this.arch} in the supported range: (${CONSTRAINTS.asRange()}). Set HEADFOX_JS_RELEASE_TAG to pin a known working release when using custom forks.`);
|
|
206
272
|
}
|
|
207
273
|
static getPlatformArch() {
|
|
208
274
|
const platArch = os.arch().toLowerCase();
|
package/package.json
CHANGED