stelz 0.2.0 → 0.2.1
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 +12 -6
- package/THIRD_PARTY_NOTICES.md +3 -1
- package/dist/cli.js +4295 -1291
- package/dist/plugin.js +92 -17
- package/dist/supervisor.js +137 -34
- package/dist/ui/assets/index-7xWId3LL.js +11 -0
- package/dist/ui/assets/index-By17r8AD.css +1 -0
- package/dist/ui/index.html +4 -3
- package/docs/README.md +24 -7
- package/docs/architecture.md +56 -5
- package/docs/bootstrap.md +195 -0
- package/docs/clients.md +5 -1
- package/docs/distribution.md +26 -17
- package/docs/onboarding-acceptance.md +79 -0
- package/docs/profile-transfer.md +89 -0
- package/docs/tests.md +76 -27
- package/docs/ui.md +31 -15
- package/examples/onboarding-fixture.html +22 -0
- package/package.json +62 -8
- package/skills/stelz-browser/SKILL.md +7 -3
- package/skills/stelz-browser/references/bootstrap.md +16 -0
- package/dist/ui/chunk-6y88c98t.css +0 -1
- package/dist/ui/chunk-jt6ajg4c.js +0 -11
- /package/dist/ui/{font-328d50b6ece9ca25.woff → assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff} +0 -0
- /package/dist/ui/{font-08949f728dc52d52.woff2 → assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2} +0 -0
- /package/dist/ui/{font-3b646991d30055a9.woff2 → assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2} +0 -0
- /package/dist/ui/{font-828907bfd14855c8.woff → assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff} +0 -0
- /package/dist/ui/{font-0717336fb31fcdcd.woff2 → assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2} +0 -0
- /package/dist/ui/{font-8d2c7b2eca91869d.woff → assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff} +0 -0
- /package/dist/ui/{font-7861a349af1e925a.woff → assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff} +0 -0
- /package/dist/ui/{font-8960851d691c054e.woff2 → assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2} +0 -0
- /package/dist/ui/{theme-j8bgvgsv.js → theme-fqsa3r76.js} +0 -0
package/dist/plugin.js
CHANGED
|
@@ -14,12 +14,21 @@ var ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
|
14
14
|
// package.json
|
|
15
15
|
var package_default = {
|
|
16
16
|
name: "stelz",
|
|
17
|
-
version: "0.2.
|
|
17
|
+
version: "0.2.1",
|
|
18
18
|
description: "Standalone persistent Camoufox profiles with exclusive MCP control",
|
|
19
19
|
license: "MIT",
|
|
20
20
|
homepage: "https://github.com/souljorje/stelz#readme",
|
|
21
|
-
bugs: {
|
|
22
|
-
|
|
21
|
+
bugs: {
|
|
22
|
+
url: "https://github.com/souljorje/stelz/issues"
|
|
23
|
+
},
|
|
24
|
+
keywords: [
|
|
25
|
+
"browser",
|
|
26
|
+
"automation",
|
|
27
|
+
"camoufox",
|
|
28
|
+
"playwright",
|
|
29
|
+
"mcp",
|
|
30
|
+
"persistent-profiles"
|
|
31
|
+
],
|
|
23
32
|
bin: {
|
|
24
33
|
stelz: "dist/cli.js"
|
|
25
34
|
},
|
|
@@ -41,15 +50,45 @@ var package_default = {
|
|
|
41
50
|
},
|
|
42
51
|
repository: {
|
|
43
52
|
type: "git",
|
|
44
|
-
url: "https://github.com/souljorje/stelz.git"
|
|
45
|
-
|
|
53
|
+
url: "https://github.com/souljorje/stelz.git"
|
|
54
|
+
},
|
|
55
|
+
publishConfig: {
|
|
56
|
+
access: "public",
|
|
57
|
+
registry: "https://registry.npmjs.org"
|
|
46
58
|
},
|
|
47
|
-
publishConfig: { access: "public", registry: "https://registry.npmjs.org" },
|
|
48
59
|
type: "module",
|
|
49
60
|
packageManager: "bun@1.4.4",
|
|
50
61
|
scripts: {
|
|
51
|
-
build: "bun
|
|
52
|
-
|
|
62
|
+
build: "bun scripts/build.ts",
|
|
63
|
+
pack: "bun scripts/pack.ts",
|
|
64
|
+
setup: "bun src/cli/cli.ts setup",
|
|
65
|
+
start: "bun src/cli/cli.ts start",
|
|
66
|
+
status: "bun src/cli/cli.ts status",
|
|
67
|
+
stop: "bun src/cli/cli.ts stop",
|
|
68
|
+
pause: "bun src/cli/cli.ts pause",
|
|
69
|
+
resume: "bun src/cli/cli.ts resume",
|
|
70
|
+
mcp: "bun src/cli/cli.ts mcp",
|
|
71
|
+
ui: "bun --no-env-file src/cli/cli.ts ui",
|
|
72
|
+
browser: "bun scripts/call.ts",
|
|
73
|
+
fixture: "bun tests/helpers/fixture.ts",
|
|
74
|
+
test: "bun test ./tests/unit ./tests/integration && bun run test:regression",
|
|
75
|
+
"test:ui": "bun test tests/integration/catalog-limits.test.ts tests/unit/profile-windows.test.ts tests/unit/ui-server.test.ts tests/integration/ui-model.test.ts tests/integration/profile-configuration.test.ts tests/integration/profile-rename.test.ts tests/unit/ui-api.test.ts tests/unit/ui-display.test.ts",
|
|
76
|
+
"test:ui:browser": "bun tests/browser/ui-browser.ts",
|
|
77
|
+
"test:regression": "node --experimental-transform-types --test tests/regression/*.test.ts",
|
|
78
|
+
"test:integration": "bun tests/browser/integration.ts",
|
|
79
|
+
"test:native": "bun tests/browser/native-integration.ts",
|
|
80
|
+
"test:window-close": "bun tests/browser/window-close.ts",
|
|
81
|
+
"test:windows": "bun --no-env-file tests/browser/profile-windows-browser.ts",
|
|
82
|
+
"test:proxy": "bun tests/browser/proxy-integration.ts",
|
|
83
|
+
"test:manual": "bun tests/qa/manual-integration.ts",
|
|
84
|
+
"test:typing": "bun --no-env-file tests/browser/typing-browser.ts",
|
|
85
|
+
"test:public": "bun --no-env-file tests/qa/public-sites.ts",
|
|
86
|
+
"test:package": "bun tests/qa/package.ts",
|
|
87
|
+
typecheck: "tsc --noEmit",
|
|
88
|
+
"verify:release": "bun scripts/verify-release.ts",
|
|
89
|
+
"test:concurrency": "bun --no-env-file tests/browser/concurrency.ts",
|
|
90
|
+
check: "bun run build && bun run typecheck && bun run test",
|
|
91
|
+
"test:transfer": "bun --no-env-file tests/browser/profile-transfer.ts"
|
|
53
92
|
},
|
|
54
93
|
dependencies: {
|
|
55
94
|
"@askjo/camofox-browser": "1.18.1",
|
|
@@ -58,12 +97,32 @@ var package_default = {
|
|
|
58
97
|
hono: "4.13.16",
|
|
59
98
|
"playwright-core": "1.62.1",
|
|
60
99
|
"better-sqlite3": "13.0.1",
|
|
61
|
-
impit: "0.14.8"
|
|
100
|
+
impit: "0.14.8",
|
|
101
|
+
"stream-json": "3.8.0",
|
|
102
|
+
yauzl: "3.4.0",
|
|
103
|
+
yazl: "3.3.1"
|
|
62
104
|
},
|
|
63
105
|
bundleDependencies: [
|
|
64
106
|
"@askjo/camofox-browser",
|
|
65
107
|
"camoufox-js"
|
|
66
|
-
]
|
|
108
|
+
],
|
|
109
|
+
devDependencies: {
|
|
110
|
+
"@fontsource/ibm-plex-mono": "5.3.0",
|
|
111
|
+
"@fontsource/ibm-plex-sans": "5.3.0",
|
|
112
|
+
"@types/bun": "1.4.4",
|
|
113
|
+
"@types/react": "19.3.0",
|
|
114
|
+
"@types/react-dom": "19.3.0",
|
|
115
|
+
react: "19.3.1",
|
|
116
|
+
"react-dom": "19.3.1",
|
|
117
|
+
typescript: "5.9.3",
|
|
118
|
+
vite: "8.3.6",
|
|
119
|
+
"@types/yauzl": "3.4.0",
|
|
120
|
+
"@types/yazl": "3.3.1"
|
|
121
|
+
},
|
|
122
|
+
patchedDependencies: {
|
|
123
|
+
"@askjo/camofox-browser@1.18.1": "patches/@askjo%2Fcamofox-browser@1.18.1.patch",
|
|
124
|
+
"camoufox-js@0.11.5": "patches/camoufox-js@0.11.5.patch"
|
|
125
|
+
}
|
|
67
126
|
};
|
|
68
127
|
|
|
69
128
|
// src/versions.ts
|
|
@@ -365,6 +424,23 @@ function replayFingerprint(saved, generated) {
|
|
|
365
424
|
}
|
|
366
425
|
return effective;
|
|
367
426
|
}
|
|
427
|
+
function applyProxyLocale(config, proxied) {
|
|
428
|
+
const region = config["locale:region"];
|
|
429
|
+
if (!proxied || typeof region !== "string" || !/^[A-Z]{2}$/.test(region) || config["locale:all"])
|
|
430
|
+
return config;
|
|
431
|
+
const current = new Intl.Locale(`und-${region}`), maximized = current.maximize();
|
|
432
|
+
if (maximized.region !== current.region)
|
|
433
|
+
return config;
|
|
434
|
+
const locale = maximized.minimize();
|
|
435
|
+
if (locale.language === "und")
|
|
436
|
+
return config;
|
|
437
|
+
const effective = { ...config, "locale:language": locale.language };
|
|
438
|
+
if (locale.script)
|
|
439
|
+
effective["locale:script"] = locale.script;
|
|
440
|
+
else
|
|
441
|
+
delete effective["locale:script"];
|
|
442
|
+
return effective;
|
|
443
|
+
}
|
|
368
444
|
function launchPreferences(current, proxied, device = {}) {
|
|
369
445
|
const capabilities = "webGl:parameters" in device ? { "webgl.enable-webgl2": "webGl2:parameters" in device } : {};
|
|
370
446
|
return { ...current, ...capabilities, ...proxied ? {
|
|
@@ -397,7 +473,7 @@ function applyFingerprint(options, stateDir, binary, proxied) {
|
|
|
397
473
|
pin = { version: 2, engineSha256, config: freezeDevice(generated) };
|
|
398
474
|
writeFileSync(path, JSON.stringify(pin, null, 2), { mode: 384, flag: "wx" });
|
|
399
475
|
}
|
|
400
|
-
const effective = replayFingerprint(pin.config, generated);
|
|
476
|
+
const effective = applyProxyLocale(replayFingerprint(pin.config, generated), proxied);
|
|
401
477
|
for (const [key] of chunks)
|
|
402
478
|
delete env[key];
|
|
403
479
|
const serialized = JSON.stringify(effective);
|
|
@@ -882,16 +958,15 @@ async function register(app, ctx, options = {}) {
|
|
|
882
958
|
if (!id || !stateDir)
|
|
883
959
|
throw Error("local-profile requires STELZ_PROFILE_ID and STELZ_PROFILE_DIR");
|
|
884
960
|
const file = join6(stateDir, "status.json");
|
|
885
|
-
const
|
|
961
|
+
const proxied = !!process.env.PROXY_HOST;
|
|
886
962
|
const interval = parseIpCheckInterval(options.ipCheckIntervalMs === undefined ? process.env.STELZ_IP_CHECK_INTERVAL_MS : String(options.ipCheckIntervalMs));
|
|
887
963
|
const now = options.now ?? (() => performance.now());
|
|
888
964
|
let lastVerifiedAt;
|
|
889
965
|
const expected = process.env.STELZ_EXPECTED_IP || "";
|
|
890
|
-
const requiresCheck =
|
|
966
|
+
const requiresCheck = proxied || !!expected;
|
|
891
967
|
let state = {
|
|
892
968
|
profileId: id,
|
|
893
969
|
instanceId: randomUUID2(),
|
|
894
|
-
mode,
|
|
895
970
|
phase: "starting",
|
|
896
971
|
exitIp: null,
|
|
897
972
|
verificationIntervalMs: interval,
|
|
@@ -1001,7 +1076,7 @@ async function register(app, ctx, options = {}) {
|
|
|
1001
1076
|
exitIp: ip,
|
|
1002
1077
|
expectedIp: expected || null,
|
|
1003
1078
|
checkedAt: new Date().toISOString(),
|
|
1004
|
-
fixedIpVerified:
|
|
1079
|
+
fixedIpVerified: proxied && !!expected,
|
|
1005
1080
|
verificationStatus: "verified",
|
|
1006
1081
|
error: null,
|
|
1007
1082
|
checkFailure: null
|
|
@@ -1017,7 +1092,7 @@ async function register(app, ctx, options = {}) {
|
|
|
1017
1092
|
verificationStatus: "verification_failed",
|
|
1018
1093
|
exitIp: null,
|
|
1019
1094
|
fixedIpVerified: false,
|
|
1020
|
-
error:
|
|
1095
|
+
error: proxied ? "Proxy exit IP verification failed. Correct the network and run stelz recheck. Direct fallback is disabled." : "Expected exit IP verification failed. Correct the network or expected IP and run stelz recheck.",
|
|
1021
1096
|
checkFailure: error.message.slice(0, 500)
|
|
1022
1097
|
});
|
|
1023
1098
|
}
|
|
@@ -1041,7 +1116,7 @@ async function register(app, ctx, options = {}) {
|
|
|
1041
1116
|
throw Error(error);
|
|
1042
1117
|
}
|
|
1043
1118
|
launchAdmitted = true;
|
|
1044
|
-
const hashes = applyFingerprint(launchOptions, stateDir, process.env.CAMOUFOX_EXECUTABLE,
|
|
1119
|
+
const hashes = applyFingerprint(launchOptions, stateDir, process.env.CAMOUFOX_EXECUTABLE, proxied);
|
|
1045
1120
|
addProfileExtension(launchOptions, windows.extensionPath);
|
|
1046
1121
|
persist({ phase: "starting", ...hashes });
|
|
1047
1122
|
});
|
package/dist/supervisor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/runtime/supervisor.ts
|
|
3
3
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
4
|
-
import { join as
|
|
4
|
+
import { join as join9 } from "path";
|
|
5
5
|
|
|
6
6
|
// src/profiles/config.ts
|
|
7
7
|
import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync } from "fs";
|
|
@@ -18,12 +18,21 @@ import { createHash } from "crypto";
|
|
|
18
18
|
// package.json
|
|
19
19
|
var package_default = {
|
|
20
20
|
name: "stelz",
|
|
21
|
-
version: "0.2.
|
|
21
|
+
version: "0.2.1",
|
|
22
22
|
description: "Standalone persistent Camoufox profiles with exclusive MCP control",
|
|
23
23
|
license: "MIT",
|
|
24
24
|
homepage: "https://github.com/souljorje/stelz#readme",
|
|
25
|
-
bugs: {
|
|
26
|
-
|
|
25
|
+
bugs: {
|
|
26
|
+
url: "https://github.com/souljorje/stelz/issues"
|
|
27
|
+
},
|
|
28
|
+
keywords: [
|
|
29
|
+
"browser",
|
|
30
|
+
"automation",
|
|
31
|
+
"camoufox",
|
|
32
|
+
"playwright",
|
|
33
|
+
"mcp",
|
|
34
|
+
"persistent-profiles"
|
|
35
|
+
],
|
|
27
36
|
bin: {
|
|
28
37
|
stelz: "dist/cli.js"
|
|
29
38
|
},
|
|
@@ -45,15 +54,45 @@ var package_default = {
|
|
|
45
54
|
},
|
|
46
55
|
repository: {
|
|
47
56
|
type: "git",
|
|
48
|
-
url: "https://github.com/souljorje/stelz.git"
|
|
49
|
-
|
|
57
|
+
url: "https://github.com/souljorje/stelz.git"
|
|
58
|
+
},
|
|
59
|
+
publishConfig: {
|
|
60
|
+
access: "public",
|
|
61
|
+
registry: "https://registry.npmjs.org"
|
|
50
62
|
},
|
|
51
|
-
publishConfig: { access: "public", registry: "https://registry.npmjs.org" },
|
|
52
63
|
type: "module",
|
|
53
64
|
packageManager: "bun@1.4.4",
|
|
54
65
|
scripts: {
|
|
55
|
-
build: "bun
|
|
56
|
-
|
|
66
|
+
build: "bun scripts/build.ts",
|
|
67
|
+
pack: "bun scripts/pack.ts",
|
|
68
|
+
setup: "bun src/cli/cli.ts setup",
|
|
69
|
+
start: "bun src/cli/cli.ts start",
|
|
70
|
+
status: "bun src/cli/cli.ts status",
|
|
71
|
+
stop: "bun src/cli/cli.ts stop",
|
|
72
|
+
pause: "bun src/cli/cli.ts pause",
|
|
73
|
+
resume: "bun src/cli/cli.ts resume",
|
|
74
|
+
mcp: "bun src/cli/cli.ts mcp",
|
|
75
|
+
ui: "bun --no-env-file src/cli/cli.ts ui",
|
|
76
|
+
browser: "bun scripts/call.ts",
|
|
77
|
+
fixture: "bun tests/helpers/fixture.ts",
|
|
78
|
+
test: "bun test ./tests/unit ./tests/integration && bun run test:regression",
|
|
79
|
+
"test:ui": "bun test tests/integration/catalog-limits.test.ts tests/unit/profile-windows.test.ts tests/unit/ui-server.test.ts tests/integration/ui-model.test.ts tests/integration/profile-configuration.test.ts tests/integration/profile-rename.test.ts tests/unit/ui-api.test.ts tests/unit/ui-display.test.ts",
|
|
80
|
+
"test:ui:browser": "bun tests/browser/ui-browser.ts",
|
|
81
|
+
"test:regression": "node --experimental-transform-types --test tests/regression/*.test.ts",
|
|
82
|
+
"test:integration": "bun tests/browser/integration.ts",
|
|
83
|
+
"test:native": "bun tests/browser/native-integration.ts",
|
|
84
|
+
"test:window-close": "bun tests/browser/window-close.ts",
|
|
85
|
+
"test:windows": "bun --no-env-file tests/browser/profile-windows-browser.ts",
|
|
86
|
+
"test:proxy": "bun tests/browser/proxy-integration.ts",
|
|
87
|
+
"test:manual": "bun tests/qa/manual-integration.ts",
|
|
88
|
+
"test:typing": "bun --no-env-file tests/browser/typing-browser.ts",
|
|
89
|
+
"test:public": "bun --no-env-file tests/qa/public-sites.ts",
|
|
90
|
+
"test:package": "bun tests/qa/package.ts",
|
|
91
|
+
typecheck: "tsc --noEmit",
|
|
92
|
+
"verify:release": "bun scripts/verify-release.ts",
|
|
93
|
+
"test:concurrency": "bun --no-env-file tests/browser/concurrency.ts",
|
|
94
|
+
check: "bun run build && bun run typecheck && bun run test",
|
|
95
|
+
"test:transfer": "bun --no-env-file tests/browser/profile-transfer.ts"
|
|
57
96
|
},
|
|
58
97
|
dependencies: {
|
|
59
98
|
"@askjo/camofox-browser": "1.18.1",
|
|
@@ -62,12 +101,32 @@ var package_default = {
|
|
|
62
101
|
hono: "4.13.16",
|
|
63
102
|
"playwright-core": "1.62.1",
|
|
64
103
|
"better-sqlite3": "13.0.1",
|
|
65
|
-
impit: "0.14.8"
|
|
104
|
+
impit: "0.14.8",
|
|
105
|
+
"stream-json": "3.8.0",
|
|
106
|
+
yauzl: "3.4.0",
|
|
107
|
+
yazl: "3.3.1"
|
|
66
108
|
},
|
|
67
109
|
bundleDependencies: [
|
|
68
110
|
"@askjo/camofox-browser",
|
|
69
111
|
"camoufox-js"
|
|
70
|
-
]
|
|
112
|
+
],
|
|
113
|
+
devDependencies: {
|
|
114
|
+
"@fontsource/ibm-plex-mono": "5.3.0",
|
|
115
|
+
"@fontsource/ibm-plex-sans": "5.3.0",
|
|
116
|
+
"@types/bun": "1.4.4",
|
|
117
|
+
"@types/react": "19.3.0",
|
|
118
|
+
"@types/react-dom": "19.3.0",
|
|
119
|
+
react: "19.3.1",
|
|
120
|
+
"react-dom": "19.3.1",
|
|
121
|
+
typescript: "5.9.3",
|
|
122
|
+
vite: "8.3.6",
|
|
123
|
+
"@types/yauzl": "3.4.0",
|
|
124
|
+
"@types/yazl": "3.3.1"
|
|
125
|
+
},
|
|
126
|
+
patchedDependencies: {
|
|
127
|
+
"@askjo/camofox-browser@1.18.1": "patches/@askjo%2Fcamofox-browser@1.18.1.patch",
|
|
128
|
+
"camoufox-js@0.11.5": "patches/camoufox-js@0.11.5.patch"
|
|
129
|
+
}
|
|
71
130
|
};
|
|
72
131
|
|
|
73
132
|
// src/versions.ts
|
|
@@ -129,11 +188,33 @@ function normalizeProfileId(value) {
|
|
|
129
188
|
return { ...record, profileId: identityId };
|
|
130
189
|
}
|
|
131
190
|
|
|
191
|
+
// src/profiles/network-compat.ts
|
|
192
|
+
function legacyConnectionMode(profile) {
|
|
193
|
+
if (!Object.hasOwn(profile, "mode"))
|
|
194
|
+
return;
|
|
195
|
+
const { mode } = profile;
|
|
196
|
+
if (mode !== "direct" && mode !== "proxy")
|
|
197
|
+
throw Error("Legacy connection mode must be direct or proxy.");
|
|
198
|
+
return mode;
|
|
199
|
+
}
|
|
200
|
+
function hasProxy(profile) {
|
|
201
|
+
const legacy = legacyConnectionMode(profile);
|
|
202
|
+
return legacy === "proxy" || legacy !== "direct" && profile.proxyEnv !== undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
132
205
|
// src/profiles/config.ts
|
|
133
206
|
var HOME_DIR = resolve(process.env.STELZ_HOME || join2(homedir(), ".local/share/stelz"));
|
|
134
207
|
var CONFIG_FILE = existingProfilePath(HOME_DIR, "profiles.json", "identities.json");
|
|
135
208
|
var STATE = existingProfilePath(HOME_DIR, "profiles", "identities");
|
|
136
209
|
var UPSTREAM = join2(HOME_DIR, "runtime", `camofox-${UPSTREAM_VERSION}`);
|
|
210
|
+
function parseProfileName(value) {
|
|
211
|
+
if (typeof value !== "string" || /[\p{Cc}\p{Bidi_Control}\u2028\u2029\u206a-\u206f]/u.test(value))
|
|
212
|
+
throw Error("Use a display name with 1\u201380 characters and no control or directional format characters.");
|
|
213
|
+
const name = value.trim();
|
|
214
|
+
if (!name || name.length > 80)
|
|
215
|
+
throw Error("Use a display name with 1\u201380 characters and no control or directional format characters.");
|
|
216
|
+
return name;
|
|
217
|
+
}
|
|
137
218
|
function settings() {
|
|
138
219
|
const value = json(CONFIG_FILE);
|
|
139
220
|
if (!value)
|
|
@@ -151,10 +232,13 @@ function validateSettings(input) {
|
|
|
151
232
|
throw Error("Configure a profiles array.");
|
|
152
233
|
const ids = new Set, ports = new Set;
|
|
153
234
|
for (const p of value.profiles) {
|
|
154
|
-
if (!p || typeof p !== "object" || typeof p.id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(p.id) || !Number.isInteger(p.port) || p.port < 1024 || p.port > 65535 ||
|
|
235
|
+
if (!p || typeof p !== "object" || typeof p.id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(p.id) || !Number.isInteger(p.port) || p.port < 1024 || p.port > 65535 || ids.has(p.id) || ports.has(p.port))
|
|
155
236
|
throw Error("Profile IDs and ports must be valid and unique.");
|
|
237
|
+
legacyConnectionMode(p);
|
|
156
238
|
if (p.storage !== undefined && !["native", "checkpoint"].includes(p.storage))
|
|
157
239
|
throw Error("Storage must be native or checkpoint.");
|
|
240
|
+
if (p.name !== undefined && parseProfileName(p.name) !== p.name)
|
|
241
|
+
throw Error("Profile display names must have no leading or trailing whitespace.");
|
|
158
242
|
if (p.typingMode !== undefined && !isTypingMode(p.typingMode))
|
|
159
243
|
throw Error("Typing mode must be auto, keyboard, or instant.");
|
|
160
244
|
for (const name of [p.proxyEnv, p.expectedIPEnv]) {
|
|
@@ -207,7 +291,7 @@ function alive(pid) {
|
|
|
207
291
|
}
|
|
208
292
|
}
|
|
209
293
|
function proxyEnvironment(p) {
|
|
210
|
-
if (p
|
|
294
|
+
if (!hasProxy(p))
|
|
211
295
|
return {};
|
|
212
296
|
const raw = p.proxyEnv && secret(p.proxyEnv);
|
|
213
297
|
if (!raw)
|
|
@@ -235,7 +319,7 @@ function validateProxy(raw, id = "Profile") {
|
|
|
235
319
|
return u;
|
|
236
320
|
}
|
|
237
321
|
function configurationHash(p) {
|
|
238
|
-
const { typingMode: _typingMode, ...launchProfile } = p;
|
|
322
|
+
const { name: _name, typingMode: _typingMode, ...launchProfile } = p;
|
|
239
323
|
const interval = ipCheckIntervalMs(), typing = typingMode();
|
|
240
324
|
return createHash("sha256").update(JSON.stringify({
|
|
241
325
|
...launchProfile,
|
|
@@ -253,13 +337,13 @@ import {
|
|
|
253
337
|
fstatSync as fstatSync2,
|
|
254
338
|
openSync as openSync2,
|
|
255
339
|
readdirSync,
|
|
256
|
-
readFileSync as
|
|
340
|
+
readFileSync as readFileSync4,
|
|
257
341
|
readSync as readSync2,
|
|
258
342
|
renameSync as renameSync2,
|
|
259
343
|
statSync,
|
|
260
344
|
writeFileSync as writeFileSync2
|
|
261
345
|
} from "fs";
|
|
262
|
-
import { join as
|
|
346
|
+
import { join as join8 } from "path";
|
|
263
347
|
|
|
264
348
|
// src/runtime/profile-process.ts
|
|
265
349
|
import { closeSync, constants, fstatSync, ftruncateSync, lstatSync as lstatSync2, openSync, readFileSync as readFileSync2, readSync, writeSync } from "fs";
|
|
@@ -363,7 +447,7 @@ function processStart(pid) {
|
|
|
363
447
|
return processInfo(pid)?.start;
|
|
364
448
|
}
|
|
365
449
|
// src/profiles/profile-configuration.ts
|
|
366
|
-
import { join as
|
|
450
|
+
import { join as join6 } from "path";
|
|
367
451
|
|
|
368
452
|
// src/profiles/profile-catalog.ts
|
|
369
453
|
import { join as join3 } from "path";
|
|
@@ -377,11 +461,30 @@ import { join as join4 } from "path";
|
|
|
377
461
|
var NETWORK_JOURNAL = join4(HOME_DIR, ".network-transaction.json");
|
|
378
462
|
var secretsFile2 = join4(HOME_DIR, "secrets.env");
|
|
379
463
|
var LIMIT2 = 1024 * 1024;
|
|
380
|
-
|
|
464
|
+
|
|
465
|
+
// src/profiles/profile-import-transaction.ts
|
|
466
|
+
import { dirname, join as join5 } from "path";
|
|
467
|
+
var PROFILE_IMPORT_JOURNAL = join5(HOME_DIR, ".profile-import-transaction.json");
|
|
381
468
|
var secretsFile3 = join5(HOME_DIR, "secrets.env");
|
|
469
|
+
var LIMIT3 = 1024 * 1024;
|
|
470
|
+
// src/profiles/profile-configuration.ts
|
|
471
|
+
var secretsFile4 = join6(HOME_DIR, "secrets.env");
|
|
472
|
+
|
|
473
|
+
// src/runtime/runtime-plugin.ts
|
|
474
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync3 } from "fs";
|
|
475
|
+
import { join as join7 } from "path";
|
|
476
|
+
function assertRuntimePlugin(root = ROOT, upstream = UPSTREAM) {
|
|
477
|
+
try {
|
|
478
|
+
const current = join7(root, "dist/plugin.js"), installed = join7(upstream, "plugins/local-profile/index.js");
|
|
479
|
+
if (![current, installed].every((file) => lstatSync3(file).isFile()) || !readFileSync3(current).equals(readFileSync3(installed)))
|
|
480
|
+
throw Error();
|
|
481
|
+
} catch {
|
|
482
|
+
throw Error("The installed browser plugin does not match this package. Stop all profiles and run stelz setup before starting a new browser.");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
382
485
|
|
|
383
486
|
// src/runtime/runtime.ts
|
|
384
|
-
var key = (id) =>
|
|
487
|
+
var key = (id) => readFileSync4(join8(dir(id), "access-key"), "utf8").trim();
|
|
385
488
|
var LOG_LIMIT = 1024 * 1024;
|
|
386
489
|
function rotateLog(file, incoming = 0) {
|
|
387
490
|
try {
|
|
@@ -402,7 +505,7 @@ var instanceId = process.argv[3];
|
|
|
402
505
|
if (!instanceId)
|
|
403
506
|
throw Error("A supervisor requires a reserved service instance.");
|
|
404
507
|
for (const name of ["storage", "cookies", "uploads", "traces", "tmp"])
|
|
405
|
-
mkdirSync2(
|
|
508
|
+
mkdirSync2(join9(runtimeDir, name), { recursive: true, mode: 448 });
|
|
406
509
|
var proxy = proxyEnvironment(p);
|
|
407
510
|
var secrets = [proxy.PROXY_USERNAME, proxy.PROXY_PASSWORD].filter(Boolean).flatMap((s) => [s, decodeURIComponent(s)]);
|
|
408
511
|
function redact(s) {
|
|
@@ -413,7 +516,7 @@ function redact(s) {
|
|
|
413
516
|
return value;
|
|
414
517
|
}
|
|
415
518
|
function log(line) {
|
|
416
|
-
const file =
|
|
519
|
+
const file = join9(runtimeDir, "server.log"), value = `${redact(line)}
|
|
417
520
|
`;
|
|
418
521
|
rotateLog(file, Buffer.byteLength(value));
|
|
419
522
|
appendFileSync(file, value, { mode: 384 });
|
|
@@ -431,10 +534,10 @@ var env = {
|
|
|
431
534
|
CAMOFOX_INTERACTIVE: "desktop",
|
|
432
535
|
CAMOFOX_ACCESS_KEY: key(p.id),
|
|
433
536
|
CAMOFOX_API_KEY: key(p.id),
|
|
434
|
-
CAMOFOX_PROFILE_DIR:
|
|
435
|
-
CAMOFOX_COOKIES_DIR:
|
|
436
|
-
CAMOFOX_UPLOADS_DIR:
|
|
437
|
-
CAMOFOX_TRACES_DIR:
|
|
537
|
+
CAMOFOX_PROFILE_DIR: join9(runtimeDir, "storage"),
|
|
538
|
+
CAMOFOX_COOKIES_DIR: join9(runtimeDir, "cookies"),
|
|
539
|
+
CAMOFOX_UPLOADS_DIR: join9(runtimeDir, "uploads"),
|
|
540
|
+
CAMOFOX_TRACES_DIR: join9(runtimeDir, "traces"),
|
|
438
541
|
CAMOFOX_DISABLE_DEFAULT_ADDONS: "1",
|
|
439
542
|
CAMOFOX_CRASH_REPORT_ENABLED: "false",
|
|
440
543
|
SESSION_TIMEOUT_MS: "0",
|
|
@@ -446,7 +549,6 @@ var env = {
|
|
|
446
549
|
STELZ_IP_CHECK_INTERVAL_MS: String(ipCheckIntervalMs()),
|
|
447
550
|
STELZ_PROFILE_ID: p.id,
|
|
448
551
|
STELZ_PROFILE_DIR: runtimeDir,
|
|
449
|
-
STELZ_MODE: p.mode,
|
|
450
552
|
STELZ_EXPECTED_IP: p.expectedIPEnv ? secret(p.expectedIPEnv) || "" : "",
|
|
451
553
|
STELZ_HOME: HOME_DIR,
|
|
452
554
|
STELZ_UPSTREAM: UPSTREAM
|
|
@@ -458,15 +560,16 @@ process.stdin.once('data', () => {
|
|
|
458
560
|
});
|
|
459
561
|
process.stdin.once('end', () => { if (!started) process.exit(1); });
|
|
460
562
|
process.stdin.resume();`;
|
|
461
|
-
var server = await lock(
|
|
462
|
-
const file =
|
|
563
|
+
var server = await lock(join9(STATE, "admission.lock"), async () => {
|
|
564
|
+
const file = join9(runtimeDir, "running.json"), record = json(file), supervisorStart = processStart(process.pid);
|
|
463
565
|
if (!record || record.instanceId !== instanceId || record.serverState !== "not-started" || !supervisorStart || record.pid && (record.pid !== process.pid || record.supervisorStart !== supervisorStart))
|
|
464
566
|
throw Error("The service reservation is no longer valid.");
|
|
465
567
|
if (record.configurationHash !== configurationHash(p))
|
|
466
568
|
throw Error("Profile configuration changed during startup.");
|
|
569
|
+
assertRuntimePlugin();
|
|
467
570
|
const reserved = { ...record, pid: process.pid, supervisorStart };
|
|
468
571
|
save(file, reserved);
|
|
469
|
-
const child = Bun.spawn(["node", "-e", bootstrap,
|
|
572
|
+
const child = Bun.spawn(["node", "-e", bootstrap, join9(UPSTREAM, "server.js")], {
|
|
470
573
|
cwd: UPSTREAM,
|
|
471
574
|
env,
|
|
472
575
|
stdin: "pipe",
|
|
@@ -524,7 +627,7 @@ function stop() {
|
|
|
524
627
|
if (stopping)
|
|
525
628
|
return;
|
|
526
629
|
stopping = true;
|
|
527
|
-
const record = json(
|
|
630
|
+
const record = json(join9(runtimeDir, "running.json"));
|
|
528
631
|
if (record?.instanceId === instanceId && record.serverPid === server.pid && record.serverStart && processStart(server.pid) === record.serverStart)
|
|
529
632
|
server.kill("SIGTERM");
|
|
530
633
|
else
|
|
@@ -549,7 +652,7 @@ var initialize = async () => {
|
|
|
549
652
|
});
|
|
550
653
|
if (!r.ok)
|
|
551
654
|
throw Error("Original browser context could not be initialized");
|
|
552
|
-
save(
|
|
655
|
+
save(join9(runtimeDir, "initialized.json"), { instanceId });
|
|
553
656
|
return;
|
|
554
657
|
}
|
|
555
658
|
} catch (e) {
|
|
@@ -564,14 +667,14 @@ var initialize = async () => {
|
|
|
564
667
|
initialize();
|
|
565
668
|
var exitCode = await server.exited;
|
|
566
669
|
await Promise.race([logs, Bun.sleep(1000)]);
|
|
567
|
-
var old = normalizeProfileId(json(
|
|
670
|
+
var old = normalizeProfileId(json(join9(runtimeDir, "status.json")) || {});
|
|
568
671
|
var clean = exitCode === 0 && old?.phase === "stopped";
|
|
569
|
-
save(
|
|
672
|
+
save(join9(runtimeDir, "status.json"), {
|
|
570
673
|
...old,
|
|
571
674
|
profileId: p.id,
|
|
572
675
|
phase: clean ? "stopped" : "unclean",
|
|
573
676
|
exitCode
|
|
574
677
|
});
|
|
575
678
|
if (clean)
|
|
576
|
-
save(
|
|
679
|
+
save(join9(runtimeDir, "stopped.json"), { instanceId });
|
|
577
680
|
process.exit(exitCode);
|