sootsim 0.1.36 → 0.1.38
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 +20 -5
- package/dist-cli/bin.js +15 -20
- package/dist-cli/chunks/{agent-YZB6D3DR.js → agent-CGQWOOOL.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-VHCVS22I.js → agent-wrapper-M6P53GJ2.js} +10 -10
- package/dist-cli/chunks/{assert-AIVCKKLG.js → assert-O7N2SYJZ.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-DF4KYGPS.js +2 -0
- package/dist-cli/chunks/beta-AK25X3AU.js +2 -0
- package/dist-cli/chunks/chunk-23YMXBQ2.js +1 -0
- package/dist-cli/chunks/chunk-2ABGQIW7.js +4 -0
- package/dist-cli/chunks/chunk-5HNZO5AI.js +7 -0
- package/dist-cli/chunks/chunk-5XI3AB4I.js +3 -0
- package/dist-cli/chunks/{chunk-IJMYFYDZ.js → chunk-67K3WAEZ.js} +2 -2
- package/dist-cli/chunks/chunk-6UJXRT7F.js +5 -0
- package/dist-cli/chunks/chunk-ARH3T5NK.js +66 -0
- package/dist-cli/chunks/{chunk-VFDRZNPN.js → chunk-B4VH67D3.js} +1 -1
- package/dist-cli/chunks/chunk-BEY2QVU5.js +3 -0
- package/dist-cli/chunks/chunk-CB4PUMI2.js +1 -0
- package/dist-cli/chunks/chunk-CHMHTTI7.js +11 -0
- package/dist-cli/chunks/chunk-E473YTRQ.js +2 -0
- package/dist-cli/chunks/chunk-FCOCFEBU.js +27 -0
- package/dist-cli/chunks/chunk-FRE5TY3C.js +1 -0
- package/dist-cli/chunks/chunk-FX3PPKSJ.js +2 -0
- package/dist-cli/chunks/{chunk-27P763IZ.js → chunk-FXY5FWVY.js} +2 -2
- package/dist-cli/chunks/{chunk-A5BRCXYE.js → chunk-G23GIRBM.js} +1 -1
- package/dist-cli/chunks/chunk-G62Q2MQI.js +1 -0
- package/dist-cli/chunks/chunk-GWQUPWVO.js +2 -0
- package/dist-cli/chunks/{chunk-EWSQSALM.js → chunk-H6CG42HE.js} +4 -4
- package/dist-cli/chunks/chunk-HNAGYNWN.js +16 -0
- package/dist-cli/chunks/{chunk-KAXZHEKM.js → chunk-IE2WYVJF.js} +1 -1
- package/dist-cli/chunks/chunk-IKVIHHWE.js +2 -0
- package/dist-cli/chunks/chunk-JJVZMGRM.js +2 -0
- package/dist-cli/chunks/{chunk-HWCKZXNJ.js → chunk-LNRBXCUI.js} +2 -2
- package/dist-cli/chunks/{chunk-CYCXOAVZ.js → chunk-MTPWS4JK.js} +4 -4
- package/dist-cli/chunks/{chunk-G7XQD4KC.js → chunk-NKYTISAN.js} +2 -2
- package/dist-cli/chunks/chunk-NSZBULGG.js +348 -0
- package/dist-cli/chunks/chunk-P32FCOS3.js +4 -0
- package/dist-cli/chunks/chunk-P722XCFT.js +3 -0
- package/dist-cli/chunks/chunk-PFRPXFSL.js +117 -0
- package/dist-cli/chunks/chunk-QD22CQLH.js +5 -0
- package/dist-cli/chunks/chunk-QI4VLQ3A.js +5 -0
- package/dist-cli/chunks/{chunk-OXN2PEB7.js → chunk-SKNHJDYO.js} +3 -3
- package/dist-cli/chunks/{chunk-YIO6S3R5.js → chunk-T442FYM5.js} +1 -1
- package/dist-cli/chunks/chunk-TR554AIH.js +17 -0
- package/dist-cli/chunks/chunk-UD5ILFN5.js +3 -0
- package/dist-cli/chunks/{chunk-LHDWH7VS.js → chunk-VIEK76DX.js} +1 -1
- package/dist-cli/chunks/chunk-VVUEWU2P.js +64 -0
- package/dist-cli/chunks/{chunk-RMW5BO3S.js → chunk-Z2PBRNJP.js} +2 -2
- package/dist-cli/chunks/chunk-ZEVZN3S4.js +2 -0
- package/dist-cli/chunks/{compat-Y2O2U7FL.js → compat-DNQWSPFQ.js} +2 -2
- package/dist-cli/chunks/{config-SRBOFUCI.js → config-ABR5BGUO.js} +2 -2
- package/dist-cli/chunks/control-DEHRU4XZ.js +2 -0
- package/dist-cli/chunks/cpu-profile-Y5YDH6X2.js +22 -0
- package/dist-cli/chunks/daemon-2Z4DAJT6.js +83 -0
- package/dist-cli/chunks/{debug-BIDMW2PE.js → debug-R36UPOJP.js} +4 -4
- package/dist-cli/chunks/demo-app-registry-DMMWYL7G.js +2 -0
- package/dist-cli/chunks/detox-FQJWEWLT.js +49 -0
- package/dist-cli/chunks/device-SXKLDZEC.js +16 -0
- package/dist-cli/chunks/diagnose-M7RKNI2H.js +41 -0
- package/dist-cli/chunks/drivers-NSCX5CRA.js +2 -0
- package/dist-cli/chunks/electron-YUAKGT4H.js +18 -0
- package/dist-cli/chunks/flow-UQSRNEZD.js +2 -0
- package/dist-cli/chunks/hints-SDD7L3VS.js +2 -0
- package/dist-cli/chunks/home-paths-QMCX2227.js +2 -0
- package/dist-cli/chunks/inspect-6FPPW7GS.js +1101 -0
- package/dist-cli/chunks/install-TTH3PM3B.js +2 -0
- package/dist-cli/chunks/{install-desktop-2MYEI4FM.js → install-desktop-EKMYRDQH.js} +3 -3
- package/dist-cli/chunks/{keys-7PNASIQR.js → keys-ODG3TDUP.js} +2 -2
- package/dist-cli/chunks/{launch-JNS47LAQ.js → launch-7HUL745I.js} +3 -3
- package/dist-cli/chunks/{login-YWZWUHBS.js → login-BJKQMJYT.js} +4 -4
- package/dist-cli/chunks/{logout-O6SXMSBP.js → logout-7NG3KGJD.js} +2 -2
- package/dist-cli/chunks/maestro-VFEUWV3Q.js +80 -0
- package/dist-cli/chunks/{preview-WGKJO5FS.js → preview-WZ6XNOBC.js} +2 -2
- package/dist-cli/chunks/profile-DXFEZOHB.js +22 -0
- package/dist-cli/chunks/react-QP7PL5CZ.js +30 -0
- package/dist-cli/chunks/{record-QPWLYH5R.js → record-AFE25EMH.js} +5 -5
- package/dist-cli/chunks/{runtime-KEMO2MSB.js → runtime-GFWS3QLZ.js} +3 -3
- package/dist-cli/chunks/screenshot-U6VFOVFW.js +28 -0
- package/dist-cli/chunks/screenshot-mode-HNGV3AFC.js +17 -0
- package/dist-cli/chunks/screenshots-GYMRDUJR.js +70 -0
- package/dist-cli/chunks/server-3Q22YYM6.js +35 -0
- package/dist-cli/chunks/setup-repo-NNDWIGZR.js +2 -0
- package/dist-cli/chunks/{skills-MO7BFNVM.js → skills-FT76ZVAV.js} +2 -2
- package/dist-cli/chunks/start-N573LFCF.js +23 -0
- package/dist-cli/chunks/store-6NTDGLPH.js +2 -0
- package/dist-cli/chunks/telemetry-SNZBIRGL.js +2 -0
- package/dist-cli/chunks/{test-XUI3KNNQ.js → test-AJPY2Q6W.js} +3 -3
- package/dist-cli/chunks/three-mode-JZZVOMTG.js +39 -0
- package/dist-cli/chunks/timeline-GTSCF5P6.js +22 -0
- package/dist-cli/chunks/upload-UHTVCGGR.js +2 -0
- package/dist-cli/chunks/what-happened-N3AQ6I7O.js +15 -0
- package/dist-cli/chunks/whoami-FOIMN6UC.js +2 -0
- package/dist-lib/agent-daemon-client.cjs +4 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +41 -39
- package/dist-lib/attached-projects.cjs +30 -28
- package/dist-lib/auth/shared-session.cjs +35 -27
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +6 -2
- package/dist-lib/dev-bundle-resolution.cjs +5 -50
- package/dist-lib/home-paths.cjs +94 -38
- package/dist-lib/host/bridge-host.cjs +2144 -1330
- package/dist-lib/host/fetch-proxy-handler.cjs +248 -0
- package/dist-lib/index.cjs +21 -21
- package/dist-lib/metro.cjs +21 -21
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/vite-base.cjs +3410 -1632
- package/dist-lib/vite.cjs +1 -1
- package/package.json +7 -1
- package/dist-cli/chunks/auto-bootstrap-MLNTX23H.js +0 -2
- package/dist-cli/chunks/chunk-3UIWOHC2.js +0 -62
- package/dist-cli/chunks/chunk-5KGFHWVR.js +0 -1
- package/dist-cli/chunks/chunk-5QIUJNT3.js +0 -5
- package/dist-cli/chunks/chunk-6GGMKFWJ.js +0 -4
- package/dist-cli/chunks/chunk-6Z275LCY.js +0 -2
- package/dist-cli/chunks/chunk-75LBYBKW.js +0 -11
- package/dist-cli/chunks/chunk-DFN3GGH7.js +0 -5
- package/dist-cli/chunks/chunk-EBEHZJRG.js +0 -117
- package/dist-cli/chunks/chunk-EJLNUMMP.js +0 -3
- package/dist-cli/chunks/chunk-FE7UI3MT.js +0 -4
- package/dist-cli/chunks/chunk-G663654J.js +0 -1
- package/dist-cli/chunks/chunk-GW7XY5KC.js +0 -2
- package/dist-cli/chunks/chunk-H2QO4TDV.js +0 -22
- package/dist-cli/chunks/chunk-HWFHBMAQ.js +0 -27
- package/dist-cli/chunks/chunk-J7CTD37P.js +0 -1
- package/dist-cli/chunks/chunk-N32NCVL2.js +0 -3
- package/dist-cli/chunks/chunk-NIZBR7EK.js +0 -2
- package/dist-cli/chunks/chunk-NYY36OKU.js +0 -308
- package/dist-cli/chunks/chunk-PJL25JQV.js +0 -5
- package/dist-cli/chunks/chunk-SHO54NET.js +0 -2
- package/dist-cli/chunks/chunk-SMVJOWSV.js +0 -16
- package/dist-cli/chunks/chunk-TC6V7YFC.js +0 -3
- package/dist-cli/chunks/chunk-YLIIVTTQ.js +0 -3
- package/dist-cli/chunks/chunk-YR7BGGYE.js +0 -2
- package/dist-cli/chunks/chunk-ZEW3RF5Q.js +0 -1
- package/dist-cli/chunks/control-PL2V2O6S.js +0 -2
- package/dist-cli/chunks/daemon-IZC32PZW.js +0 -50
- package/dist-cli/chunks/demo-app-registry-5JFOUU3D.js +0 -2
- package/dist-cli/chunks/detox-B3FDOIS3.js +0 -49
- package/dist-cli/chunks/device-ZZSI363W.js +0 -16
- package/dist-cli/chunks/drivers-S4NGK4DB.js +0 -2
- package/dist-cli/chunks/electron-5YFHXEOI.js +0 -15
- package/dist-cli/chunks/flow-JJBO6TFY.js +0 -2
- package/dist-cli/chunks/hints-G5HBBV2O.js +0 -2
- package/dist-cli/chunks/home-paths-VWC3FWA3.js +0 -2
- package/dist-cli/chunks/inspect-POOPWUQI.js +0 -1034
- package/dist-cli/chunks/install-MP6FHXNZ.js +0 -2
- package/dist-cli/chunks/install-dev-desktop-SKH3KEHY.js +0 -100
- package/dist-cli/chunks/maestro-CW6XVUKV.js +0 -75
- package/dist-cli/chunks/profile-SUOBRPIC.js +0 -22
- package/dist-cli/chunks/screenshot-JTY46V7G.js +0 -26
- package/dist-cli/chunks/screenshot-mode-7OYBBX6D.js +0 -17
- package/dist-cli/chunks/screenshots-QISKC4GD.js +0 -70
- package/dist-cli/chunks/server-YSFJAKAV.js +0 -34
- package/dist-cli/chunks/setup-repo-LFB3HBEO.js +0 -2
- package/dist-cli/chunks/store-6MFL53I4.js +0 -2
- package/dist-cli/chunks/telemetry-CN42GMVC.js +0 -2
- package/dist-cli/chunks/upload-6FUT7AX5.js +0 -2
- package/dist-cli/chunks/whoami-TQFHY42N.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.1.
|
|
1
|
+
/*! sootsim v0.1.38 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -36,700 +36,565 @@ __export(bridge_host_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(bridge_host_exports);
|
|
38
38
|
var import_child_process4 = require("child_process");
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
39
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
40
|
+
var import_http3 = require("http");
|
|
41
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
42
42
|
var import_ws = require("ws");
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
var
|
|
44
|
+
// scripts/dev-server-scanner.ts
|
|
45
|
+
var import_child_process = require("child_process");
|
|
46
|
+
var import_http = __toESM(require("http"), 1);
|
|
47
|
+
var import_net = __toESM(require("net"), 1);
|
|
48
|
+
var import_util = require("util");
|
|
46
49
|
|
|
47
|
-
// src/
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
|
|
52
|
-
var ACTIVE_RUNTIME_FILE = "active";
|
|
53
|
-
var DAEMON_LOCKFILE = "daemon.json";
|
|
54
|
-
var CONFIG_FILE = "config.json";
|
|
55
|
-
var DAEMON_HEARTBEAT_STALE_MS = 3e4;
|
|
56
|
-
function sootsimHomeDir() {
|
|
57
|
-
const override = process.env[SOOTSIM_HOME_ENV];
|
|
58
|
-
if (override && override.length > 0) return import_path.default.resolve(override);
|
|
59
|
-
return import_path.default.join((0, import_os.homedir)(), ".sootsim");
|
|
60
|
-
}
|
|
61
|
-
function runtimesDir() {
|
|
62
|
-
return import_path.default.join(sootsimHomeDir(), "runtimes");
|
|
63
|
-
}
|
|
64
|
-
function runtimeDir(version) {
|
|
65
|
-
return import_path.default.join(runtimesDir(), version);
|
|
66
|
-
}
|
|
67
|
-
function activeRuntimeFile() {
|
|
68
|
-
return import_path.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
|
|
69
|
-
}
|
|
70
|
-
function cacheDir() {
|
|
71
|
-
return import_path.default.join(sootsimHomeDir(), "cache");
|
|
72
|
-
}
|
|
73
|
-
function daemonLockfilePath() {
|
|
74
|
-
return import_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
|
|
75
|
-
}
|
|
76
|
-
function configFilePath() {
|
|
77
|
-
return import_path.default.join(sootsimHomeDir(), CONFIG_FILE);
|
|
50
|
+
// src/config.ts
|
|
51
|
+
var SOOTSIM_CONFIG_QUERY_PARAM = "sootsimConfig";
|
|
52
|
+
function hasOwnKeys(value) {
|
|
53
|
+
return !!value && Object.keys(value).length > 0;
|
|
78
54
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
import_fs.default.mkdirSync(cacheDir(), { recursive: true });
|
|
55
|
+
function hasSootSimConfig(config) {
|
|
56
|
+
if (!config) return false;
|
|
57
|
+
return hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.env) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
|
|
83
58
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
59
|
+
function applySootSimConfigToUrl(url, config) {
|
|
60
|
+
const parsed = new URL(url);
|
|
61
|
+
if (hasSootSimConfig(config)) {
|
|
62
|
+
parsed.searchParams.set(SOOTSIM_CONFIG_QUERY_PARAM, JSON.stringify(config));
|
|
63
|
+
} else {
|
|
64
|
+
parsed.searchParams.delete(SOOTSIM_CONFIG_QUERY_PARAM);
|
|
90
65
|
}
|
|
66
|
+
return parsed.toString();
|
|
91
67
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
function listInstalledRuntimes() {
|
|
98
|
-
try {
|
|
99
|
-
return import_fs.default.readdirSync(runtimesDir(), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort(compareSemver);
|
|
100
|
-
} catch {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
68
|
+
|
|
69
|
+
// src/native-dev-bundle-url.ts
|
|
70
|
+
function isAbsoluteHttpUrl(url) {
|
|
71
|
+
return /^https?:\/\//i.test(url);
|
|
103
72
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
const hyphen = v.indexOf("-");
|
|
107
|
-
const core = hyphen >= 0 ? v.slice(0, hyphen) : v;
|
|
108
|
-
const pre = hyphen >= 0 ? v.slice(hyphen + 1) : "";
|
|
109
|
-
const parts = core.split(".").map((n) => Number.parseInt(n, 10));
|
|
110
|
-
if (parts.some((n) => !Number.isFinite(n))) return [[Number.POSITIVE_INFINITY], v];
|
|
111
|
-
return [parts, pre];
|
|
112
|
-
};
|
|
113
|
-
const [an, ap] = parse(a);
|
|
114
|
-
const [bn, bp] = parse(b);
|
|
115
|
-
for (let i = 0; i < Math.max(an.length, bn.length); i++) {
|
|
116
|
-
const av = an[i] ?? 0;
|
|
117
|
-
const bv = bn[i] ?? 0;
|
|
118
|
-
if (av !== bv) return av - bv;
|
|
119
|
-
}
|
|
120
|
-
if (ap === bp) return 0;
|
|
121
|
-
if (!ap) return 1;
|
|
122
|
-
if (!bp) return -1;
|
|
123
|
-
return ap < bp ? -1 : 1;
|
|
73
|
+
function isNativeDevBundlePath(pathname) {
|
|
74
|
+
return pathname.endsWith(".bundle");
|
|
124
75
|
}
|
|
125
|
-
function
|
|
126
|
-
const version = readActiveRuntime();
|
|
127
|
-
if (!version) return null;
|
|
128
|
-
const dir = runtimeDir(version);
|
|
76
|
+
function normalizeNativeDevBundleUrl(bundleUrl) {
|
|
129
77
|
try {
|
|
130
|
-
|
|
78
|
+
const isAbsolute = isAbsoluteHttpUrl(bundleUrl);
|
|
79
|
+
const parsed = new URL(bundleUrl, "http://soot.local");
|
|
80
|
+
parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, ".bundle");
|
|
81
|
+
if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl;
|
|
82
|
+
parsed.searchParams.delete("transform.bytecode");
|
|
83
|
+
if (isAbsolute) return parsed.toString();
|
|
84
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
131
85
|
} catch {
|
|
86
|
+
return bundleUrl;
|
|
132
87
|
}
|
|
133
|
-
return null;
|
|
134
88
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
89
|
+
|
|
90
|
+
// scripts/demo-app-registry.ts
|
|
91
|
+
var import_node_fs = require("node:fs");
|
|
92
|
+
var import_node_os = require("node:os");
|
|
93
|
+
var import_node_path = require("node:path");
|
|
94
|
+
var HOME = (0, import_node_os.homedir)();
|
|
95
|
+
function findWorkspaceRoot(startDir) {
|
|
96
|
+
let dir = startDir;
|
|
97
|
+
while (true) {
|
|
98
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "pnpm-workspace.yaml")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "turbo.json")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "nx.json")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "lerna.json"))) {
|
|
99
|
+
return dir;
|
|
100
|
+
}
|
|
101
|
+
const packageJsonPath = (0, import_node_path.join)(dir, "package.json");
|
|
102
|
+
if ((0, import_node_fs.existsSync)(packageJsonPath)) {
|
|
103
|
+
try {
|
|
104
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(packageJsonPath, "utf8"));
|
|
105
|
+
if (pkg.workspaces) return dir;
|
|
106
|
+
} catch {
|
|
146
107
|
}
|
|
147
|
-
return null;
|
|
148
|
-
} finally {
|
|
149
|
-
import_fs.default.closeSync(fd);
|
|
150
108
|
}
|
|
151
|
-
|
|
152
|
-
return null;
|
|
109
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
110
|
+
if (parent === dir) return null;
|
|
111
|
+
dir = parent;
|
|
153
112
|
}
|
|
154
113
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
process.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
114
|
+
function resolveWorkspaceScriptPath(workspaceRelativePath, packageRelativePath) {
|
|
115
|
+
const workspaceRoot = findWorkspaceRoot(process.cwd());
|
|
116
|
+
const candidates = [
|
|
117
|
+
workspaceRoot ? (0, import_node_path.resolve)(workspaceRoot, workspaceRelativePath) : null,
|
|
118
|
+
(0, import_node_path.resolve)(process.cwd(), workspaceRelativePath),
|
|
119
|
+
(0, import_node_path.resolve)(process.cwd(), packageRelativePath)
|
|
120
|
+
].filter((candidate) => Boolean(candidate));
|
|
121
|
+
for (const candidate of candidates) {
|
|
122
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
163
123
|
}
|
|
124
|
+
return candidates[0] ?? (0, import_node_path.resolve)(process.cwd(), workspaceRelativePath);
|
|
164
125
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
126
|
+
var getExpensifyProxyScript = () => resolveWorkspaceScriptPath(
|
|
127
|
+
"packages/sootsim-engine/scripts/expensify-web-proxy.ts",
|
|
128
|
+
"scripts/expensify-web-proxy.ts"
|
|
129
|
+
);
|
|
130
|
+
var getRainbowMetadataProxyScript = () => resolveWorkspaceScriptPath(
|
|
131
|
+
"packages/sootsim-engine/scripts/rainbow-metadata-proxy.ts",
|
|
132
|
+
"scripts/rainbow-metadata-proxy.ts"
|
|
133
|
+
);
|
|
134
|
+
var getMattermostRNUtilsNativeModule = () => resolveWorkspaceScriptPath(
|
|
135
|
+
"packages/compat/src/stubs/mattermost-rnutils-native.ts",
|
|
136
|
+
"../compat/src/stubs/mattermost-rnutils-native.ts"
|
|
137
|
+
);
|
|
138
|
+
var getMattermostKeychainNativeModule = () => resolveWorkspaceScriptPath(
|
|
139
|
+
"packages/compat/src/stubs/native-seams/react-native-keychain-manager.ts",
|
|
140
|
+
"../compat/src/stubs/native-seams/react-native-keychain-manager.ts"
|
|
141
|
+
);
|
|
142
|
+
var getMattermostNetworkClientNativeModule = () => resolveWorkspaceScriptPath(
|
|
143
|
+
"packages/compat/src/stubs/mattermost-network-client-native.ts",
|
|
144
|
+
"../compat/src/stubs/mattermost-network-client-native.ts"
|
|
145
|
+
);
|
|
146
|
+
var EXPENSIFY_NATIVE_PROXY_ENV = {
|
|
147
|
+
USE_NGROK: "true",
|
|
148
|
+
NGROK_URL: "http://localhost:9000/",
|
|
149
|
+
SECURE_NGROK_URL: "http://localhost:9000/"
|
|
150
|
+
};
|
|
151
|
+
var MATTERMOST_DIR = (0, import_node_path.join)(HOME, "github/mattermost-mobile");
|
|
152
|
+
var UNISWAP_REPO_DIR = (0, import_node_path.join)(HOME, "github/uniswap-interface");
|
|
153
|
+
var UNISWAP_APP_DIR = (0, import_node_path.join)(UNISWAP_REPO_DIR, "apps/mobile");
|
|
154
|
+
var UNISWAP_ENV_LOCAL_FILE = (0, import_node_path.join)(UNISWAP_REPO_DIR, ".env.defaults.local");
|
|
155
|
+
var UNISWAP_PLACEHOLDER = "stored-in-.env.local";
|
|
156
|
+
var UNISWAP_DEMO_ENV_MARKER = "# sootsim demo env overrides";
|
|
157
|
+
var UNISWAP_FORCE_UPGRADE_HOOK_FILE = (0, import_node_path.join)(
|
|
158
|
+
UNISWAP_REPO_DIR,
|
|
159
|
+
"packages/uniswap/src/features/forceUpgrade/hooks/useForceUpgradeStatus.ts"
|
|
160
|
+
);
|
|
161
|
+
var UNISWAP_FORCE_UPGRADE_NOTIFICATION_FILE = (0, import_node_path.join)(
|
|
162
|
+
UNISWAP_REPO_DIR,
|
|
163
|
+
"apps/mobile/src/notification-service/data-sources/createForceUpgradeNotificationDataSource.ts"
|
|
164
|
+
);
|
|
165
|
+
var UNISWAP_FORCE_UPGRADE_PATCH_MARKER = "SOOTSIM_DEMO_DISABLE_FORCE_UPGRADE";
|
|
166
|
+
function parseEnvFile(filePath) {
|
|
167
|
+
if (!(0, import_node_fs.existsSync)(filePath)) return {};
|
|
168
|
+
const env = {};
|
|
169
|
+
const source = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
170
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
171
|
+
const line = rawLine.trim();
|
|
172
|
+
if (!line || line.startsWith("#")) continue;
|
|
173
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
174
|
+
if (!match) continue;
|
|
175
|
+
let value = match[2].trim();
|
|
176
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
177
|
+
value = value.slice(1, -1);
|
|
178
|
+
}
|
|
179
|
+
env[match[1]] = value;
|
|
177
180
|
}
|
|
178
|
-
|
|
179
|
-
return true;
|
|
181
|
+
return env;
|
|
180
182
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
function isUsableUniswapEnvValue(value) {
|
|
184
|
+
if (!value) return false;
|
|
185
|
+
const trimmed = value.trim();
|
|
186
|
+
if (!trimmed) return false;
|
|
187
|
+
if (trimmed.includes(UNISWAP_PLACEHOLDER)) return false;
|
|
188
|
+
if (trimmed === "TRADING_API_KEY" || trimmed === "UNISWAP_API_KEY") return false;
|
|
189
|
+
return true;
|
|
186
190
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
var import_stream = require("stream");
|
|
194
|
-
var import_promises = require("stream/promises");
|
|
195
|
-
var DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
|
|
196
|
-
var RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
|
|
197
|
-
var RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
|
|
198
|
-
function readConfig() {
|
|
199
|
-
try {
|
|
200
|
-
const parsed = JSON.parse(import_fs2.default.readFileSync(configFilePath(), "utf8"));
|
|
201
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
202
|
-
} catch {
|
|
203
|
-
return {};
|
|
191
|
+
function pickEnvValue(sources, keys) {
|
|
192
|
+
for (const source of sources) {
|
|
193
|
+
for (const key of keys) {
|
|
194
|
+
const value = source[key];
|
|
195
|
+
if (isUsableUniswapEnvValue(value)) return value.trim();
|
|
196
|
+
}
|
|
204
197
|
}
|
|
198
|
+
return void 0;
|
|
205
199
|
}
|
|
206
|
-
function
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
200
|
+
function resolveUniswapDemoEnv() {
|
|
201
|
+
const localEnv = parseEnvFile(UNISWAP_ENV_LOCAL_FILE);
|
|
202
|
+
const webEnv = parseEnvFile((0, import_node_path.join)(UNISWAP_REPO_DIR, "apps/web/.env"));
|
|
203
|
+
const sources = [
|
|
204
|
+
process.env,
|
|
205
|
+
localEnv,
|
|
206
|
+
webEnv
|
|
207
|
+
];
|
|
208
|
+
const env = {};
|
|
209
|
+
const bindings = [
|
|
210
|
+
["AMPLITUDE_PROXY_URL_OVERRIDE", ["REACT_APP_AMPLITUDE_PROXY_URL"]],
|
|
211
|
+
["QUICKNODE_ENDPOINT_NAME", ["REACT_APP_QUICKNODE_ENDPOINT_NAME"]],
|
|
212
|
+
["QUICKNODE_ENDPOINT_TOKEN", ["REACT_APP_QUICKNODE_ENDPOINT_TOKEN"]],
|
|
213
|
+
["INFURA_KEY", ["REACT_APP_INFURA_KEY"]],
|
|
214
|
+
["STATSIG_API_KEY", ["REACT_APP_STATSIG_API_KEY"]],
|
|
215
|
+
["STATSIG_PROXY_URL_OVERRIDE", ["REACT_APP_STATSIG_PROXY_URL"]],
|
|
216
|
+
["WALLETCONNECT_PROJECT_ID", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
217
|
+
["WALLETCONNECT_PROJECT_ID_BETA", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
218
|
+
["WALLETCONNECT_PROJECT_ID_DEV", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
219
|
+
["TRADING_API_KEY", ["REACT_APP_TRADING_API_KEY"]],
|
|
220
|
+
["UNISWAP_API_KEY", []]
|
|
221
|
+
];
|
|
222
|
+
for (const [target, aliases] of bindings) {
|
|
223
|
+
const value = pickEnvValue(sources, [target, ...aliases]);
|
|
224
|
+
if (!value) continue;
|
|
225
|
+
env[target] = value;
|
|
226
|
+
for (const alias of aliases) {
|
|
227
|
+
env[alias] = value;
|
|
228
|
+
}
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
+
const hasPrivateGatewayKeys = isUsableUniswapEnvValue(env.TRADING_API_KEY) && isUsableUniswapEnvValue(env.UNISWAP_API_KEY);
|
|
231
|
+
if (!hasPrivateGatewayKeys) {
|
|
232
|
+
const publicGraphqlUrl = pickEnvValue(sources, ["GRAPHQL_URL_OVERRIDE", "REACT_APP_AWS_API_ENDPOINT"]) || "https://interface.gateway.uniswap.org/v1/graphql";
|
|
233
|
+
env.API_BASE_URL_OVERRIDE = "https://interface.gateway.uniswap.org";
|
|
234
|
+
env.API_BASE_URL_V2_OVERRIDE = "https://interface.gateway.uniswap.org/v2";
|
|
235
|
+
env.GRAPHQL_URL_OVERRIDE = publicGraphqlUrl;
|
|
236
|
+
env.TRADING_API_URL_OVERRIDE = "https://trading-api-labs.interface.gateway.uniswap.org";
|
|
237
|
+
env.FOR_API_URL_OVERRIDE = "https://for.interface.gateway.uniswap.org/v2/FOR.v1.FORService";
|
|
238
|
+
}
|
|
239
|
+
return env;
|
|
230
240
|
}
|
|
231
|
-
function
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
throw new Error(
|
|
236
|
-
`no version specified and channel '${channel}' has no latest entry in the manifest`
|
|
237
|
-
);
|
|
241
|
+
function ensureUniswapDemoEnvLocal() {
|
|
242
|
+
const existingSource = (0, import_node_fs.existsSync)(UNISWAP_ENV_LOCAL_FILE) ? (0, import_node_fs.readFileSync)(UNISWAP_ENV_LOCAL_FILE, "utf8") : "";
|
|
243
|
+
if (existingSource && !existingSource.includes(UNISWAP_DEMO_ENV_MARKER)) {
|
|
244
|
+
return;
|
|
238
245
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
246
|
+
const env = resolveUniswapDemoEnv();
|
|
247
|
+
const lines = [UNISWAP_DEMO_ENV_MARKER];
|
|
248
|
+
for (const [key, value] of Object.entries(env).sort(([a], [b]) => a.localeCompare(b))) {
|
|
249
|
+
lines.push(`${key}=${JSON.stringify(value)}`);
|
|
244
250
|
}
|
|
245
|
-
|
|
251
|
+
lines.push("");
|
|
252
|
+
(0, import_node_fs.writeFileSync)(UNISWAP_ENV_LOCAL_FILE, `${lines.join("\n")}
|
|
253
|
+
`);
|
|
246
254
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return
|
|
257
|
-
version,
|
|
258
|
-
channel,
|
|
259
|
-
cdnOrigin,
|
|
260
|
-
runtimeDir: destDir,
|
|
261
|
-
installed: false,
|
|
262
|
-
activated: setActive,
|
|
263
|
-
manifest
|
|
264
|
-
};
|
|
255
|
+
function ensureUniswapForceUpgradePatched() {
|
|
256
|
+
const hookNeedle = `export function useForceUpgradeStatus(): ForceUpgradeStatus {
|
|
257
|
+
`;
|
|
258
|
+
const hookPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
259
|
+
return 'not-required'
|
|
260
|
+
|
|
261
|
+
`;
|
|
262
|
+
const hookLegacyPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
263
|
+
if (process.env.${UNISWAP_FORCE_UPGRADE_PATCH_MARKER} === 'true') {
|
|
264
|
+
return 'not-required'
|
|
265
265
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
266
|
+
|
|
267
|
+
`;
|
|
268
|
+
const notificationNeedle = ` const getForceUpgradeStatus = (): ForceUpgradeStatus => {
|
|
269
|
+
`;
|
|
270
|
+
const notificationPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
271
|
+
return 'not-required'
|
|
272
|
+
|
|
273
|
+
`;
|
|
274
|
+
const notificationLegacyPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
275
|
+
if (process.env.${UNISWAP_FORCE_UPGRADE_PATCH_MARKER} === 'true') {
|
|
276
|
+
return 'not-required'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
`;
|
|
280
|
+
const patchWithMigration = (filePath, needle, patch, legacyPatch) => {
|
|
281
|
+
const source = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
282
|
+
if (source.includes(patch)) return;
|
|
283
|
+
if (source.includes(legacyPatch)) {
|
|
284
|
+
(0, import_node_fs.writeFileSync)(filePath, source.replace(legacyPatch, patch));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!source.includes(needle)) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`uniswap demo patch failed: expected snippet not found in ${filePath}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
(0, import_node_fs.writeFileSync)(filePath, source.replace(needle, `${needle}${patch}`));
|
|
293
|
+
};
|
|
294
|
+
patchWithMigration(
|
|
295
|
+
UNISWAP_FORCE_UPGRADE_HOOK_FILE,
|
|
296
|
+
hookNeedle,
|
|
297
|
+
hookPatch,
|
|
298
|
+
hookLegacyPatch
|
|
299
|
+
);
|
|
300
|
+
patchWithMigration(
|
|
301
|
+
UNISWAP_FORCE_UPGRADE_NOTIFICATION_FILE,
|
|
302
|
+
notificationNeedle,
|
|
303
|
+
notificationPatch,
|
|
304
|
+
notificationLegacyPatch
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
var JOPLIN_DIR = (0, import_node_path.join)(HOME, "github/joplin");
|
|
308
|
+
var JOPLIN_APP_DIR = (0, import_node_path.join)(JOPLIN_DIR, "packages/app-mobile");
|
|
309
|
+
var JOPLIN_WATCH_ROOTS = [
|
|
310
|
+
"packages/lib",
|
|
311
|
+
"packages/renderer",
|
|
312
|
+
"packages/turndown",
|
|
313
|
+
"packages/turndown-plugin-gfm",
|
|
314
|
+
"packages/editor",
|
|
315
|
+
"packages/tools",
|
|
316
|
+
"packages/utils",
|
|
317
|
+
"packages/fork-htmlparser2",
|
|
318
|
+
"packages/fork-uslug",
|
|
319
|
+
"packages/fork-sax",
|
|
320
|
+
"packages/htmlpack",
|
|
321
|
+
"packages/react-native-saf-x",
|
|
322
|
+
"packages/react-native-alarm-notification"
|
|
323
|
+
];
|
|
324
|
+
function ensureJoplinWatchmanConfigs() {
|
|
325
|
+
if (!(0, import_node_fs.existsSync)(JOPLIN_DIR)) return;
|
|
326
|
+
for (const rel of JOPLIN_WATCH_ROOTS) {
|
|
327
|
+
const dir = (0, import_node_path.join)(JOPLIN_DIR, rel);
|
|
328
|
+
if (!(0, import_node_fs.existsSync)(dir)) continue;
|
|
329
|
+
const cfg = (0, import_node_path.join)(dir, ".watchmanconfig");
|
|
330
|
+
if (!(0, import_node_fs.existsSync)(cfg)) (0, import_node_fs.writeFileSync)(cfg, "{}\n");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
var JOPLIN_BUILD_SENTINELS = [
|
|
334
|
+
"packages/lib/models/Setting.js",
|
|
335
|
+
"packages/turndown/lib/turndown.cjs.js",
|
|
336
|
+
"packages/app-mobile/pluginAssets/index.js"
|
|
337
|
+
];
|
|
338
|
+
function ensureJoplinBuilt() {
|
|
339
|
+
if (!(0, import_node_fs.existsSync)(JOPLIN_DIR)) return;
|
|
340
|
+
const missing = JOPLIN_BUILD_SENTINELS.filter(
|
|
341
|
+
(rel) => !(0, import_node_fs.existsSync)((0, import_node_path.join)(JOPLIN_DIR, rel))
|
|
342
|
+
);
|
|
343
|
+
if (missing.length === 0) return;
|
|
344
|
+
const { execSync } = require("node:child_process");
|
|
345
|
+
execSync("yarn buildParallel", {
|
|
346
|
+
cwd: JOPLIN_DIR,
|
|
347
|
+
stdio: "inherit",
|
|
348
|
+
env: { ...process.env, NO_FLIPPER: "1", CI: "" }
|
|
349
|
+
});
|
|
350
|
+
const stillMissing = JOPLIN_BUILD_SENTINELS.filter(
|
|
351
|
+
(rel) => !(0, import_node_fs.existsSync)((0, import_node_path.join)(JOPLIN_DIR, rel))
|
|
352
|
+
);
|
|
353
|
+
if (stillMissing.length > 0) {
|
|
276
354
|
throw new Error(
|
|
277
|
-
`
|
|
355
|
+
`joplin demo: yarn buildParallel did not produce: ${stillMissing.join(", ")}`
|
|
278
356
|
);
|
|
279
357
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
358
|
+
}
|
|
359
|
+
var RAINBOW_DIR = (0, import_node_path.join)(HOME, "github/rainbow");
|
|
360
|
+
var RAINBOW_GRAPHQL_DIR = (0, import_node_path.join)(RAINBOW_DIR, "src/graphql");
|
|
361
|
+
var RAINBOW_GRAPHQL_CONFIG_FILE = (0, import_node_path.join)(RAINBOW_GRAPHQL_DIR, "config.js");
|
|
362
|
+
var RAINBOW_NETWORKS_FILE = (0, import_node_path.join)(RAINBOW_DIR, "src/references/networks.json");
|
|
363
|
+
var RAINBOW_METADATA_BASE_URL = "https://metadata.p.rainbow.me";
|
|
364
|
+
var RAINBOW_METADATA_PROXY_PORT = 9011;
|
|
365
|
+
var RAINBOW_DEMO_METADATA_BASE_URL = `http://127.0.0.1:${RAINBOW_METADATA_PROXY_PORT}`;
|
|
366
|
+
var RAINBOW_PUBLIC_ENS_GRAPHQL_URL = "https://api.thegraph.com/subgraphs/name/ensdomains/ens";
|
|
367
|
+
var RAINBOW_DEMO_QUOTE_SIGNER = "0x0000000000000000000000000000000000000000";
|
|
368
|
+
var RAINBOW_DEMO_RELAY_URL = "https://relay.rainbow.me";
|
|
369
|
+
var RAINBOW_DEMO_MASTER_KEY = "sootsim-rainbow-demo-master-key-do-not-use-for-real-wallets";
|
|
370
|
+
var RAINBOW_DEMO_RPC_PROXY_BASE_URL = "https://rpc.rainbow.me/v1";
|
|
371
|
+
var RAINBOW_DEMO_RPC_API_KEY = "";
|
|
372
|
+
var RAINBOW_DEMO_PUBLIC_RPC_URLS = {
|
|
373
|
+
"1": "https://ethereum-rpc.publicnode.com"
|
|
374
|
+
};
|
|
375
|
+
var RAINBOW_DEMO_SERVICE_API_KEY = "sootsim-rainbow-demo-api-key";
|
|
376
|
+
var RAINBOW_DEMO_SECURE_WALLET_HASH_KEY = "0x736f6f7473696d2d7261696e626f772d64656d6f2d686173682d6b6579000000";
|
|
377
|
+
var RAINBOW_GRAPHQL_SENTINELS = [
|
|
378
|
+
"src/graphql/__generated__/ens.ts",
|
|
379
|
+
"src/graphql/__generated__/metadata.ts",
|
|
380
|
+
"src/graphql/__generated__/metadataPOST.ts"
|
|
381
|
+
];
|
|
382
|
+
function runRainbowSetupCommand(command, cwd) {
|
|
383
|
+
const { execSync } = require("node:child_process");
|
|
384
|
+
execSync(command, {
|
|
385
|
+
cwd,
|
|
386
|
+
stdio: "inherit",
|
|
387
|
+
env: {
|
|
388
|
+
...process.env,
|
|
389
|
+
METADATA_BASE_URL: RAINBOW_METADATA_BASE_URL,
|
|
390
|
+
RAINBOW_RELAY_QUOTE_SIGNER: process.env.RAINBOW_RELAY_QUOTE_SIGNER ?? RAINBOW_DEMO_QUOTE_SIGNER
|
|
287
391
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function ensureRainbowGraphqlConfig() {
|
|
395
|
+
if (!(0, import_node_fs.existsSync)(RAINBOW_GRAPHQL_DIR)) return;
|
|
396
|
+
const source = (0, import_node_fs.existsSync)(RAINBOW_GRAPHQL_CONFIG_FILE) ? (0, import_node_fs.readFileSync)(RAINBOW_GRAPHQL_CONFIG_FILE, "utf8") : "";
|
|
397
|
+
if (source.includes(RAINBOW_PUBLIC_ENS_GRAPHQL_URL) && source.includes(RAINBOW_METADATA_BASE_URL)) {
|
|
398
|
+
return;
|
|
293
399
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
400
|
+
(0, import_node_fs.writeFileSync)(
|
|
401
|
+
RAINBOW_GRAPHQL_CONFIG_FILE,
|
|
402
|
+
`exports.config = {
|
|
403
|
+
ens: {
|
|
404
|
+
__name: 'ens',
|
|
405
|
+
document: './queries/ens.graphql',
|
|
406
|
+
schema: {
|
|
407
|
+
method: 'POST',
|
|
408
|
+
url: '${RAINBOW_PUBLIC_ENS_GRAPHQL_URL}',
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
metadata: {
|
|
412
|
+
__name: 'metadata',
|
|
413
|
+
document: './queries/metadata.graphql',
|
|
414
|
+
schema: { method: 'GET', url: '${RAINBOW_METADATA_BASE_URL}/v1/graph' },
|
|
415
|
+
},
|
|
416
|
+
metadataPOST: {
|
|
417
|
+
__name: 'metadataPOST',
|
|
418
|
+
document: './queries/metadata.graphql',
|
|
419
|
+
schema: { method: 'POST', url: '${RAINBOW_METADATA_BASE_URL}/v1/graph' },
|
|
420
|
+
},
|
|
421
|
+
arc: {
|
|
422
|
+
__name: 'arc',
|
|
423
|
+
document: './queries/arc.graphql',
|
|
424
|
+
schema: {
|
|
425
|
+
method: 'GET',
|
|
426
|
+
url: 'https://arc-graphql.rainbow.me/graphql',
|
|
427
|
+
headers: {
|
|
428
|
+
'x-api-key': 'ARC_GRAPHQL_API_KEY',
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
arcDev: {
|
|
433
|
+
__name: 'arcDev',
|
|
434
|
+
document: './queries/arc.graphql',
|
|
435
|
+
schema: {
|
|
436
|
+
method: 'GET',
|
|
437
|
+
url: 'https://arc-graphql.rainbowdotme.workers.dev/graphql',
|
|
438
|
+
headers: {},
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
`
|
|
443
|
+
);
|
|
304
444
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
445
|
+
function resolveRainbowDemoEnv() {
|
|
446
|
+
const env = {
|
|
447
|
+
ENABLE_DEV_MODE: "1",
|
|
448
|
+
IS_TESTING: "false",
|
|
449
|
+
METADATA_BASE_URL: process.env.RAINBOW_METADATA_BASE_URL ?? RAINBOW_DEMO_METADATA_BASE_URL,
|
|
450
|
+
ADDYS_API_KEY: process.env.ADDYS_API_KEY ?? RAINBOW_DEMO_SERVICE_API_KEY,
|
|
451
|
+
ADDYS_BASE_URL: process.env.ADDYS_BASE_URL ?? RAINBOW_DEMO_METADATA_BASE_URL,
|
|
452
|
+
PLATFORM_API_KEY: process.env.PLATFORM_API_KEY ?? RAINBOW_DEMO_SERVICE_API_KEY,
|
|
453
|
+
PLATFORM_BASE_URL: process.env.PLATFORM_BASE_URL ?? RAINBOW_DEMO_METADATA_BASE_URL,
|
|
454
|
+
RAINBOW_MASTER_KEY: process.env.RAINBOW_MASTER_KEY ?? RAINBOW_DEMO_MASTER_KEY,
|
|
455
|
+
RAINBOW_RELAY_QUOTE_SIGNER: process.env.RAINBOW_RELAY_QUOTE_SIGNER ?? RAINBOW_DEMO_QUOTE_SIGNER,
|
|
456
|
+
RAINBOW_RELAY_URL: process.env.RAINBOW_RELAY_URL ?? RAINBOW_DEMO_RELAY_URL,
|
|
457
|
+
RPC_PROXY_API_KEY_PROD: RAINBOW_DEMO_RPC_API_KEY,
|
|
458
|
+
RPC_PROXY_BASE_URL_PROD: process.env.RAINBOW_RPC_PROXY_BASE_URL ?? process.env.RPC_PROXY_BASE_URL_PROD ?? RAINBOW_DEMO_RPC_PROXY_BASE_URL,
|
|
459
|
+
SECURE_WALLET_HASH_KEY: process.env.SECURE_WALLET_HASH_KEY ?? RAINBOW_DEMO_SECURE_WALLET_HASH_KEY
|
|
460
|
+
};
|
|
461
|
+
const optionalEnvVars = [
|
|
462
|
+
"ADDYS_API_KEY",
|
|
463
|
+
"ADDYS_BASE_URL",
|
|
464
|
+
"IMGIX_DOMAIN",
|
|
465
|
+
"IMGIX_TOKEN",
|
|
466
|
+
"PLATFORM_API_KEY",
|
|
467
|
+
"PLATFORM_BASE_URL",
|
|
468
|
+
"RAINBOW_TEST_WALLET",
|
|
469
|
+
"RAINBOW_RELAY_API_KEY",
|
|
470
|
+
"RAINBOW_RELAY_URL",
|
|
471
|
+
"SECURE_WALLET_HASH_KEY",
|
|
472
|
+
"TOKEN_SEARCH_URL",
|
|
473
|
+
"WC_PROJECT_ID"
|
|
474
|
+
];
|
|
475
|
+
for (const key of optionalEnvVars) {
|
|
476
|
+
const value = process.env[key];
|
|
477
|
+
if (value) env[key] = value;
|
|
318
478
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
checked: true,
|
|
323
|
-
updated: false,
|
|
324
|
-
reason: `manifest is missing version ${latestVersion}`,
|
|
325
|
-
activeVersion: readActiveRuntime(),
|
|
326
|
-
latestVersion
|
|
327
|
-
};
|
|
479
|
+
if (!env.WC_PROJECT_ID && process.env.RAINBOW_WALLETCONNECT_PROJECT_ID) {
|
|
480
|
+
env.WC_PROJECT_ID = process.env.RAINBOW_WALLETCONNECT_PROJECT_ID;
|
|
328
481
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
482
|
+
return env;
|
|
483
|
+
}
|
|
484
|
+
function ensureRainbowDemoNetworks() {
|
|
485
|
+
if (!(0, import_node_fs.existsSync)(RAINBOW_NETWORKS_FILE)) return;
|
|
486
|
+
const payload = JSON.parse((0, import_node_fs.readFileSync)(RAINBOW_NETWORKS_FILE, "utf8"));
|
|
487
|
+
if (!Array.isArray(payload.networks)) return;
|
|
488
|
+
let changed = false;
|
|
489
|
+
for (const network of payload.networks) {
|
|
490
|
+
const url = network.id ? RAINBOW_DEMO_PUBLIC_RPC_URLS[network.id] : void 0;
|
|
491
|
+
if (!url || !network.defaultRPC) continue;
|
|
492
|
+
if (network.defaultRPC.url === url) continue;
|
|
493
|
+
network.defaultRPC.url = url;
|
|
494
|
+
changed = true;
|
|
495
|
+
}
|
|
496
|
+
if (changed) {
|
|
497
|
+
(0, import_node_fs.writeFileSync)(RAINBOW_NETWORKS_FILE, `${JSON.stringify(payload)}
|
|
498
|
+
`);
|
|
341
499
|
}
|
|
342
|
-
const install = await installRuntime({
|
|
343
|
-
version: latestVersion,
|
|
344
|
-
channel,
|
|
345
|
-
cdnOrigin,
|
|
346
|
-
setActive: false
|
|
347
|
-
});
|
|
348
|
-
return {
|
|
349
|
-
checked: true,
|
|
350
|
-
updated: true,
|
|
351
|
-
activeVersion: latestVersion,
|
|
352
|
-
latestVersion,
|
|
353
|
-
install
|
|
354
|
-
};
|
|
355
500
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
501
|
+
function ensureRainbowSetup() {
|
|
502
|
+
if (!(0, import_node_fs.existsSync)(RAINBOW_DIR)) return;
|
|
503
|
+
ensureRainbowGraphqlConfig();
|
|
504
|
+
if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(RAINBOW_GRAPHQL_DIR, "node_modules/.bin/graphql-codegen"))) {
|
|
505
|
+
runRainbowSetupCommand("fnm exec --using=22 yarn install", RAINBOW_GRAPHQL_DIR);
|
|
506
|
+
}
|
|
507
|
+
const missingGraphql = RAINBOW_GRAPHQL_SENTINELS.some(
|
|
508
|
+
(rel) => !(0, import_node_fs.existsSync)((0, import_node_path.join)(RAINBOW_DIR, rel))
|
|
509
|
+
);
|
|
510
|
+
if (missingGraphql) {
|
|
511
|
+
runRainbowSetupCommand("fnm exec --using=22 yarn codegen", RAINBOW_GRAPHQL_DIR);
|
|
360
512
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
await (0, import_promises.pipeline)(
|
|
365
|
-
import_stream.Readable.fromWeb(res.body),
|
|
366
|
-
import_fs2.default.createWriteStream(tmp)
|
|
367
|
-
);
|
|
368
|
-
import_fs2.default.renameSync(tmp, destPath);
|
|
369
|
-
} catch (err) {
|
|
370
|
-
try {
|
|
371
|
-
import_fs2.default.unlinkSync(tmp);
|
|
372
|
-
} catch {
|
|
373
|
-
}
|
|
374
|
-
throw err;
|
|
513
|
+
if (!(0, import_node_fs.existsSync)(RAINBOW_NETWORKS_FILE)) {
|
|
514
|
+
runRainbowSetupCommand("fnm exec --using=22 yarn fetch:networks", RAINBOW_DIR);
|
|
375
515
|
}
|
|
516
|
+
ensureRainbowDemoNetworks();
|
|
376
517
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function extractTarball(tarPath, destDir) {
|
|
387
|
-
return new Promise((resolve2, reject) => {
|
|
388
|
-
const child = (0, import_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
|
|
389
|
-
stdio: ["ignore", "inherit", "inherit"]
|
|
390
|
-
});
|
|
391
|
-
child.on("error", reject);
|
|
392
|
-
child.on("exit", (code) => {
|
|
393
|
-
if (code === 0) resolve2();
|
|
394
|
-
else reject(new Error(`tar exited with code ${code}`));
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/host/agent-host.ts
|
|
400
|
-
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
401
|
-
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
402
|
-
|
|
403
|
-
// scripts/dev-server-scanner.ts
|
|
404
|
-
var import_child_process2 = require("child_process");
|
|
405
|
-
var import_http = __toESM(require("http"), 1);
|
|
406
|
-
var import_net = __toESM(require("net"), 1);
|
|
407
|
-
var import_util = require("util");
|
|
408
|
-
|
|
409
|
-
// src/config.ts
|
|
410
|
-
var SOOTSIM_CONFIG_QUERY_PARAM = "sootsimConfig";
|
|
411
|
-
function hasOwnKeys(value) {
|
|
412
|
-
return !!value && Object.keys(value).length > 0;
|
|
413
|
-
}
|
|
414
|
-
function hasSootSimConfig(config) {
|
|
415
|
-
if (!config) return false;
|
|
416
|
-
return hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.env) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
|
|
417
|
-
}
|
|
418
|
-
function applySootSimConfigToUrl(url, config) {
|
|
419
|
-
const parsed = new URL(url);
|
|
420
|
-
if (hasSootSimConfig(config)) {
|
|
421
|
-
parsed.searchParams.set(SOOTSIM_CONFIG_QUERY_PARAM, JSON.stringify(config));
|
|
422
|
-
} else {
|
|
423
|
-
parsed.searchParams.delete(SOOTSIM_CONFIG_QUERY_PARAM);
|
|
424
|
-
}
|
|
425
|
-
return parsed.toString();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// src/native-dev-bundle-url.ts
|
|
429
|
-
function isAbsoluteHttpUrl(url) {
|
|
430
|
-
return /^https?:\/\//i.test(url);
|
|
431
|
-
}
|
|
432
|
-
function isNativeDevBundlePath(pathname) {
|
|
433
|
-
return pathname.endsWith(".bundle");
|
|
434
|
-
}
|
|
435
|
-
function normalizeNativeDevBundleUrl(bundleUrl) {
|
|
518
|
+
var ARTSY_DIR = (0, import_node_path.join)(HOME, "github/eigen");
|
|
519
|
+
var ARTSY_KEYS_FILE = (0, import_node_path.join)(ARTSY_DIR, "keys.shared.json");
|
|
520
|
+
var ARTSY_METAFLAGS_FILE = (0, import_node_path.join)(ARTSY_DIR, "metaflags.json");
|
|
521
|
+
var ARTSY_RELAY_SENTINEL = (0, import_node_path.join)(ARTSY_DIR, "src/__generated__/.relay-complete");
|
|
522
|
+
function readArtsyKeysPayload() {
|
|
523
|
+
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE)) return void 0;
|
|
436
524
|
try {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
parsed.
|
|
440
|
-
|
|
441
|
-
if (!
|
|
442
|
-
|
|
443
|
-
parsed.searchParams.set("minify", "false");
|
|
444
|
-
}
|
|
445
|
-
parsed.searchParams.delete("transform.bytecode");
|
|
446
|
-
if (isAbsolute) return parsed.toString();
|
|
447
|
-
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
525
|
+
const parsed = JSON.parse((0, import_node_fs.readFileSync)(ARTSY_KEYS_FILE, "utf8"));
|
|
526
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
527
|
+
const secure = parsed.secure && typeof parsed.secure === "object" ? parsed.secure : void 0;
|
|
528
|
+
const publicKeys = parsed.public && typeof parsed.public === "object" ? parsed.public : void 0;
|
|
529
|
+
if (!secure && !publicKeys) return void 0;
|
|
530
|
+
return { secure, public: publicKeys };
|
|
448
531
|
} catch {
|
|
449
|
-
return
|
|
532
|
+
return void 0;
|
|
450
533
|
}
|
|
451
534
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
const packageJsonPath = (0, import_node_path.join)(dir, "package.json");
|
|
465
|
-
if ((0, import_node_fs.existsSync)(packageJsonPath)) {
|
|
466
|
-
try {
|
|
467
|
-
const pkg = JSON.parse((0, import_node_fs.readFileSync)(packageJsonPath, "utf8"));
|
|
468
|
-
if (pkg.workspaces) return dir;
|
|
469
|
-
} catch {
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
const parent = (0, import_node_path.dirname)(dir);
|
|
473
|
-
if (parent === dir) return null;
|
|
474
|
-
dir = parent;
|
|
535
|
+
function resolveArtsyRuntimeConfig() {
|
|
536
|
+
const email = process.env.SOOTSIM_ARTSY_EMAIL ?? process.env.MAESTRO_TEST_EMAIL;
|
|
537
|
+
const password = process.env.SOOTSIM_ARTSY_PASSWORD ?? process.env.MAESTRO_TEST_PASSWORD;
|
|
538
|
+
const keys = readArtsyKeysPayload();
|
|
539
|
+
const env = {};
|
|
540
|
+
if (email && password) {
|
|
541
|
+
env.SOOTSIM_LAUNCH_ARGUMENTS = JSON.stringify({
|
|
542
|
+
email,
|
|
543
|
+
password,
|
|
544
|
+
useMaestroInit: true
|
|
545
|
+
});
|
|
475
546
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const workspaceRoot = findWorkspaceRoot(process.cwd());
|
|
479
|
-
const candidates = [
|
|
480
|
-
workspaceRoot ? (0, import_node_path.resolve)(workspaceRoot, workspaceRelativePath) : null,
|
|
481
|
-
(0, import_node_path.resolve)(process.cwd(), workspaceRelativePath),
|
|
482
|
-
(0, import_node_path.resolve)(process.cwd(), packageRelativePath)
|
|
483
|
-
].filter((candidate) => Boolean(candidate));
|
|
484
|
-
for (const candidate of candidates) {
|
|
485
|
-
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
547
|
+
if (keys) {
|
|
548
|
+
env.SOOTSIM_REACT_NATIVE_KEYS_JSON = JSON.stringify(keys);
|
|
486
549
|
}
|
|
487
|
-
|
|
550
|
+
if (Object.keys(env).length === 0) return void 0;
|
|
551
|
+
return {
|
|
552
|
+
env
|
|
553
|
+
};
|
|
488
554
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
"
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
var UNISWAP_PLACEHOLDER = "stored-in-.env.local";
|
|
502
|
-
var UNISWAP_DEMO_ENV_MARKER = "# sootsim demo env overrides";
|
|
503
|
-
var UNISWAP_FORCE_UPGRADE_HOOK_FILE = (0, import_node_path.join)(
|
|
504
|
-
UNISWAP_REPO_DIR,
|
|
505
|
-
"packages/uniswap/src/features/forceUpgrade/hooks/useForceUpgradeStatus.ts"
|
|
506
|
-
);
|
|
507
|
-
var UNISWAP_FORCE_UPGRADE_NOTIFICATION_FILE = (0, import_node_path.join)(
|
|
508
|
-
UNISWAP_REPO_DIR,
|
|
509
|
-
"apps/mobile/src/notification-service/data-sources/createForceUpgradeNotificationDataSource.ts"
|
|
510
|
-
);
|
|
511
|
-
var UNISWAP_FORCE_UPGRADE_PATCH_MARKER = "SOOTSIM_DEMO_DISABLE_FORCE_UPGRADE";
|
|
512
|
-
function parseEnvFile(filePath) {
|
|
513
|
-
if (!(0, import_node_fs.existsSync)(filePath)) return {};
|
|
514
|
-
const env = {};
|
|
515
|
-
const source = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
516
|
-
for (const rawLine of source.split(/\r?\n/)) {
|
|
517
|
-
const line = rawLine.trim();
|
|
518
|
-
if (!line || line.startsWith("#")) continue;
|
|
519
|
-
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
520
|
-
if (!match) continue;
|
|
521
|
-
let value = match[2].trim();
|
|
522
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
523
|
-
value = value.slice(1, -1);
|
|
555
|
+
function ensureArtsySetup() {
|
|
556
|
+
if (!(0, import_node_fs.existsSync)(ARTSY_DIR)) return;
|
|
557
|
+
const { execSync } = require("node:child_process");
|
|
558
|
+
const yarnRelease = (0, import_node_path.join)(ARTSY_DIR, ".yarn/releases/yarn-4.10.3.cjs");
|
|
559
|
+
if ((0, import_node_fs.existsSync)(yarnRelease)) {
|
|
560
|
+
const src = (0, import_node_fs.readFileSync)(yarnRelease, "utf8");
|
|
561
|
+
const needle = '["clone","-c core.autocrlf=false",';
|
|
562
|
+
if (src.includes(needle)) {
|
|
563
|
+
(0, import_node_fs.writeFileSync)(
|
|
564
|
+
yarnRelease,
|
|
565
|
+
src.replace(needle, '["clone","-c","core.autocrlf=false",')
|
|
566
|
+
);
|
|
524
567
|
}
|
|
525
|
-
env[match[1]] = value;
|
|
526
568
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (isUsableUniswapEnvValue(value)) return value.trim();
|
|
569
|
+
if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(ARTSY_DIR, "node_modules/.yarn-state.yml"))) {
|
|
570
|
+
execSync("yarn install", {
|
|
571
|
+
cwd: ARTSY_DIR,
|
|
572
|
+
stdio: "inherit",
|
|
573
|
+
env: { ...process.env, YARN_CHECKSUM_BEHAVIOR: "update" }
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE) || !(0, import_node_fs.existsSync)(ARTSY_METAFLAGS_FILE)) {
|
|
577
|
+
try {
|
|
578
|
+
execSync("yarn setup:oss", { cwd: ARTSY_DIR, stdio: "inherit" });
|
|
579
|
+
} catch {
|
|
580
|
+
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE) || !(0, import_node_fs.existsSync)(ARTSY_METAFLAGS_FILE)) {
|
|
581
|
+
throw new Error("artsy demo: setup:oss did not create keys/metaflags");
|
|
582
|
+
}
|
|
542
583
|
}
|
|
543
584
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
webEnv
|
|
553
|
-
];
|
|
554
|
-
const env = {};
|
|
555
|
-
const bindings = [
|
|
556
|
-
["AMPLITUDE_PROXY_URL_OVERRIDE", ["REACT_APP_AMPLITUDE_PROXY_URL"]],
|
|
557
|
-
["QUICKNODE_ENDPOINT_NAME", ["REACT_APP_QUICKNODE_ENDPOINT_NAME"]],
|
|
558
|
-
["QUICKNODE_ENDPOINT_TOKEN", ["REACT_APP_QUICKNODE_ENDPOINT_TOKEN"]],
|
|
559
|
-
["INFURA_KEY", ["REACT_APP_INFURA_KEY"]],
|
|
560
|
-
["STATSIG_API_KEY", ["REACT_APP_STATSIG_API_KEY"]],
|
|
561
|
-
["STATSIG_PROXY_URL_OVERRIDE", ["REACT_APP_STATSIG_PROXY_URL"]],
|
|
562
|
-
["WALLETCONNECT_PROJECT_ID", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
563
|
-
["WALLETCONNECT_PROJECT_ID_BETA", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
564
|
-
["WALLETCONNECT_PROJECT_ID_DEV", ["REACT_APP_WALLET_CONNECT_PROJECT_ID"]],
|
|
565
|
-
["TRADING_API_KEY", ["REACT_APP_TRADING_API_KEY"]],
|
|
566
|
-
["UNISWAP_API_KEY", []]
|
|
567
|
-
];
|
|
568
|
-
for (const [target, aliases] of bindings) {
|
|
569
|
-
const value = pickEnvValue(sources, [target, ...aliases]);
|
|
570
|
-
if (!value) continue;
|
|
571
|
-
env[target] = value;
|
|
572
|
-
for (const alias of aliases) {
|
|
573
|
-
env[alias] = value;
|
|
585
|
+
const rnlaPkgJson = (0, import_node_path.join)(
|
|
586
|
+
ARTSY_DIR,
|
|
587
|
+
"node_modules/react-native-launch-arguments/package.json"
|
|
588
|
+
);
|
|
589
|
+
if ((0, import_node_fs.existsSync)(rnlaPkgJson)) {
|
|
590
|
+
const raw = (0, import_node_fs.readFileSync)(rnlaPkgJson, "utf8");
|
|
591
|
+
if (raw.includes('"dist/index.js"')) {
|
|
592
|
+
(0, import_node_fs.writeFileSync)(rnlaPkgJson, raw.replace('"dist/index.js"', '"src/index.ts"'));
|
|
574
593
|
}
|
|
575
594
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
env.API_BASE_URL_OVERRIDE = "https://interface.gateway.uniswap.org";
|
|
580
|
-
env.API_BASE_URL_V2_OVERRIDE = "https://interface.gateway.uniswap.org/v2";
|
|
581
|
-
env.GRAPHQL_URL_OVERRIDE = publicGraphqlUrl;
|
|
582
|
-
env.TRADING_API_URL_OVERRIDE = "https://trading-api-labs.interface.gateway.uniswap.org";
|
|
583
|
-
env.FOR_API_URL_OVERRIDE = "https://for.interface.gateway.uniswap.org/v2/FOR.v1.FORService";
|
|
584
|
-
}
|
|
585
|
-
return env;
|
|
586
|
-
}
|
|
587
|
-
function ensureUniswapDemoEnvLocal() {
|
|
588
|
-
const existingSource = (0, import_node_fs.existsSync)(UNISWAP_ENV_LOCAL_FILE) ? (0, import_node_fs.readFileSync)(UNISWAP_ENV_LOCAL_FILE, "utf8") : "";
|
|
589
|
-
if (existingSource && !existingSource.includes(UNISWAP_DEMO_ENV_MARKER)) {
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
const env = resolveUniswapDemoEnv();
|
|
593
|
-
const lines = [UNISWAP_DEMO_ENV_MARKER];
|
|
594
|
-
for (const [key, value] of Object.entries(env).sort(([a], [b]) => a.localeCompare(b))) {
|
|
595
|
-
lines.push(`${key}=${JSON.stringify(value)}`);
|
|
596
|
-
}
|
|
597
|
-
lines.push("");
|
|
598
|
-
(0, import_node_fs.writeFileSync)(UNISWAP_ENV_LOCAL_FILE, `${lines.join("\n")}
|
|
599
|
-
`);
|
|
600
|
-
}
|
|
601
|
-
function ensureUniswapForceUpgradePatched() {
|
|
602
|
-
const hookNeedle = `export function useForceUpgradeStatus(): ForceUpgradeStatus {
|
|
603
|
-
`;
|
|
604
|
-
const hookPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
605
|
-
return 'not-required'
|
|
606
|
-
|
|
607
|
-
`;
|
|
608
|
-
const hookLegacyPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
609
|
-
if (process.env.${UNISWAP_FORCE_UPGRADE_PATCH_MARKER} === 'true') {
|
|
610
|
-
return 'not-required'
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
`;
|
|
614
|
-
const notificationNeedle = ` const getForceUpgradeStatus = (): ForceUpgradeStatus => {
|
|
615
|
-
`;
|
|
616
|
-
const notificationPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
617
|
-
return 'not-required'
|
|
618
|
-
|
|
619
|
-
`;
|
|
620
|
-
const notificationLegacyPatch = ` // sootsim demo: bypass the force-upgrade gate during local engine demos.
|
|
621
|
-
if (process.env.${UNISWAP_FORCE_UPGRADE_PATCH_MARKER} === 'true') {
|
|
622
|
-
return 'not-required'
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
`;
|
|
626
|
-
const patchWithMigration = (filePath, needle, patch, legacyPatch) => {
|
|
627
|
-
const source = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
628
|
-
if (source.includes(patch)) return;
|
|
629
|
-
if (source.includes(legacyPatch)) {
|
|
630
|
-
(0, import_node_fs.writeFileSync)(filePath, source.replace(legacyPatch, patch));
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (!source.includes(needle)) {
|
|
634
|
-
throw new Error(
|
|
635
|
-
`uniswap demo patch failed: expected snippet not found in ${filePath}`
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
(0, import_node_fs.writeFileSync)(filePath, source.replace(needle, `${needle}${patch}`));
|
|
639
|
-
};
|
|
640
|
-
patchWithMigration(
|
|
641
|
-
UNISWAP_FORCE_UPGRADE_HOOK_FILE,
|
|
642
|
-
hookNeedle,
|
|
643
|
-
hookPatch,
|
|
644
|
-
hookLegacyPatch
|
|
645
|
-
);
|
|
646
|
-
patchWithMigration(
|
|
647
|
-
UNISWAP_FORCE_UPGRADE_NOTIFICATION_FILE,
|
|
648
|
-
notificationNeedle,
|
|
649
|
-
notificationPatch,
|
|
650
|
-
notificationLegacyPatch
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
var ARTSY_DIR = (0, import_node_path.join)(HOME, "github/eigen");
|
|
654
|
-
var ARTSY_KEYS_FILE = (0, import_node_path.join)(ARTSY_DIR, "keys.shared.json");
|
|
655
|
-
var ARTSY_METAFLAGS_FILE = (0, import_node_path.join)(ARTSY_DIR, "metaflags.json");
|
|
656
|
-
var ARTSY_RELAY_SENTINEL = (0, import_node_path.join)(ARTSY_DIR, "src/__generated__/.relay-complete");
|
|
657
|
-
function readArtsyKeysPayload() {
|
|
658
|
-
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE)) return void 0;
|
|
659
|
-
try {
|
|
660
|
-
const parsed = JSON.parse((0, import_node_fs.readFileSync)(ARTSY_KEYS_FILE, "utf8"));
|
|
661
|
-
if (!parsed || typeof parsed !== "object") return void 0;
|
|
662
|
-
const secure = parsed.secure && typeof parsed.secure === "object" ? parsed.secure : void 0;
|
|
663
|
-
const publicKeys = parsed.public && typeof parsed.public === "object" ? parsed.public : void 0;
|
|
664
|
-
if (!secure && !publicKeys) return void 0;
|
|
665
|
-
return { secure, public: publicKeys };
|
|
666
|
-
} catch {
|
|
667
|
-
return void 0;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
function resolveArtsyRuntimeConfig() {
|
|
671
|
-
const email = process.env.SOOTSIM_ARTSY_EMAIL ?? process.env.MAESTRO_TEST_EMAIL;
|
|
672
|
-
const password = process.env.SOOTSIM_ARTSY_PASSWORD ?? process.env.MAESTRO_TEST_PASSWORD;
|
|
673
|
-
const keys = readArtsyKeysPayload();
|
|
674
|
-
const env = {};
|
|
675
|
-
if (email && password) {
|
|
676
|
-
env.SOOTSIM_LAUNCH_ARGUMENTS = JSON.stringify({
|
|
677
|
-
email,
|
|
678
|
-
password,
|
|
679
|
-
useMaestroInit: true
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (keys) {
|
|
683
|
-
env.SOOTSIM_REACT_NATIVE_KEYS_JSON = JSON.stringify(keys);
|
|
684
|
-
}
|
|
685
|
-
if (Object.keys(env).length === 0) return void 0;
|
|
686
|
-
return {
|
|
687
|
-
env
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
function ensureArtsySetup() {
|
|
691
|
-
if (!(0, import_node_fs.existsSync)(ARTSY_DIR)) return;
|
|
692
|
-
const { execSync } = require("node:child_process");
|
|
693
|
-
const yarnRelease = (0, import_node_path.join)(ARTSY_DIR, ".yarn/releases/yarn-4.10.3.cjs");
|
|
694
|
-
if ((0, import_node_fs.existsSync)(yarnRelease)) {
|
|
695
|
-
const src = (0, import_node_fs.readFileSync)(yarnRelease, "utf8");
|
|
696
|
-
const needle = '["clone","-c core.autocrlf=false",';
|
|
697
|
-
if (src.includes(needle)) {
|
|
698
|
-
(0, import_node_fs.writeFileSync)(
|
|
699
|
-
yarnRelease,
|
|
700
|
-
src.replace(needle, '["clone","-c","core.autocrlf=false",')
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(ARTSY_DIR, "node_modules/.yarn-state.yml"))) {
|
|
705
|
-
execSync("yarn install", {
|
|
706
|
-
cwd: ARTSY_DIR,
|
|
707
|
-
stdio: "inherit",
|
|
708
|
-
env: { ...process.env, YARN_CHECKSUM_BEHAVIOR: "update" }
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE) || !(0, import_node_fs.existsSync)(ARTSY_METAFLAGS_FILE)) {
|
|
712
|
-
try {
|
|
713
|
-
execSync("yarn setup:oss", { cwd: ARTSY_DIR, stdio: "inherit" });
|
|
714
|
-
} catch {
|
|
715
|
-
if (!(0, import_node_fs.existsSync)(ARTSY_KEYS_FILE) || !(0, import_node_fs.existsSync)(ARTSY_METAFLAGS_FILE)) {
|
|
716
|
-
throw new Error("artsy demo: setup:oss did not create keys/metaflags");
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const rnlaPkgJson = (0, import_node_path.join)(
|
|
721
|
-
ARTSY_DIR,
|
|
722
|
-
"node_modules/react-native-launch-arguments/package.json"
|
|
723
|
-
);
|
|
724
|
-
if ((0, import_node_fs.existsSync)(rnlaPkgJson)) {
|
|
725
|
-
const raw = (0, import_node_fs.readFileSync)(rnlaPkgJson, "utf8");
|
|
726
|
-
if (raw.includes('"dist/index.js"')) {
|
|
727
|
-
(0, import_node_fs.writeFileSync)(rnlaPkgJson, raw.replace('"dist/index.js"', '"src/index.ts"'));
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (!(0, import_node_fs.existsSync)(ARTSY_RELAY_SENTINEL)) {
|
|
731
|
-
execSync("yarn relay", { cwd: ARTSY_DIR, stdio: "inherit" });
|
|
732
|
-
(0, import_node_fs.writeFileSync)(ARTSY_RELAY_SENTINEL, "");
|
|
595
|
+
if (!(0, import_node_fs.existsSync)(ARTSY_RELAY_SENTINEL)) {
|
|
596
|
+
execSync("yarn relay", { cwd: ARTSY_DIR, stdio: "inherit" });
|
|
597
|
+
(0, import_node_fs.writeFileSync)(ARTSY_RELAY_SENTINEL, "");
|
|
733
598
|
}
|
|
734
599
|
}
|
|
735
600
|
var APPS = [
|
|
@@ -776,7 +641,24 @@ var APPS = [
|
|
|
776
641
|
dir: (0, import_node_path.join)(HOME, "takeout"),
|
|
777
642
|
preferredPort: 8086,
|
|
778
643
|
framework: "one",
|
|
779
|
-
|
|
644
|
+
// takeout needs more than Metro for the demo to actually work: better-auth
|
|
645
|
+
// (login), zero-cache (sync), and a postgres for both. `bun lite` brings
|
|
646
|
+
// up the orez-backed stack (PG + zero + s3 in one binary) plus the One
|
|
647
|
+
// dev server. takeout's env system shifts every port (web, pg, zero,
|
|
648
|
+
// minio) uniformly by PORT_OFFSET, so we derive offset = port - 8081
|
|
649
|
+
// (the base web port) to keep all backend ports clear of the default
|
|
650
|
+
// dev stack while pinning One to the demo slot. OREZ_DATA_DIR is isolated
|
|
651
|
+
// to a per-demo path so we don't fight a soot dev orez that may already
|
|
652
|
+
// be holding pglite locks on ~/takeout/.orez (soot can attach takeout as
|
|
653
|
+
// a project and spin up its own orez against the same data dir).
|
|
654
|
+
readyTimeoutMs: 24e4,
|
|
655
|
+
command: (p) => ({
|
|
656
|
+
cmd: "bun lite",
|
|
657
|
+
env: {
|
|
658
|
+
PORT_OFFSET: String(p - 8081),
|
|
659
|
+
OREZ_DATA_DIR: `${HOME}/.cache/sootsim-demo/takeout-orez`
|
|
660
|
+
}
|
|
661
|
+
})
|
|
780
662
|
},
|
|
781
663
|
{
|
|
782
664
|
name: "expensify",
|
|
@@ -823,361 +705,923 @@ var APPS = [
|
|
|
823
705
|
envVars: ["SOOTSIM_ARTSY_EMAIL", "SOOTSIM_ARTSY_PASSWORD"],
|
|
824
706
|
note: "auto-login reuses Artsy\u2019s built-in Maestro launch-arguments hook"
|
|
825
707
|
}
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
name: "rainbow",
|
|
711
|
+
label: "Rainbow",
|
|
712
|
+
dir: RAINBOW_DIR,
|
|
713
|
+
preferredPort: 8089,
|
|
714
|
+
framework: "rock",
|
|
715
|
+
sidecars: [
|
|
716
|
+
{
|
|
717
|
+
name: "metadata-proxy",
|
|
718
|
+
port: RAINBOW_METADATA_PROXY_PORT,
|
|
719
|
+
readyPath: "/health",
|
|
720
|
+
command: () => ({
|
|
721
|
+
cmd: `bun ${JSON.stringify(getRainbowMetadataProxyScript())}`,
|
|
722
|
+
env: {
|
|
723
|
+
PORT: String(RAINBOW_METADATA_PROXY_PORT),
|
|
724
|
+
RAINBOW_PUBLIC_RPC_URLS: JSON.stringify(RAINBOW_DEMO_PUBLIC_RPC_URLS),
|
|
725
|
+
RAINBOW_UPSTREAM_METADATA_BASE_URL: RAINBOW_METADATA_BASE_URL
|
|
726
|
+
}
|
|
727
|
+
})
|
|
728
|
+
}
|
|
729
|
+
],
|
|
730
|
+
prepare: () => {
|
|
731
|
+
ensureRainbowSetup();
|
|
732
|
+
},
|
|
733
|
+
command: (p) => ({
|
|
734
|
+
cmd: `fnm exec --using=22 yarn start --port ${p} --reset-cache`,
|
|
735
|
+
env: resolveRainbowDemoEnv()
|
|
736
|
+
}),
|
|
737
|
+
credentials: {
|
|
738
|
+
envVars: [
|
|
739
|
+
"RAINBOW_TEST_WALLET",
|
|
740
|
+
"RAINBOW_WALLETCONNECT_PROJECT_ID",
|
|
741
|
+
"WC_PROJECT_ID",
|
|
742
|
+
"IMGIX_DOMAIN",
|
|
743
|
+
"IMGIX_TOKEN"
|
|
744
|
+
],
|
|
745
|
+
note: "launcher supplies a demo encryption key and public mainnet RPC; use only a public throwaway mnemonic for RAINBOW_TEST_WALLET"
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: "rocket-chat",
|
|
750
|
+
label: "Rocket.Chat",
|
|
751
|
+
dir: (0, import_node_path.join)(HOME, "github/Rocket.Chat.ReactNative"),
|
|
752
|
+
preferredPort: 8093,
|
|
753
|
+
framework: "expo",
|
|
754
|
+
command: (p) => ({
|
|
755
|
+
cmd: `npx react-native start --port ${p}`,
|
|
756
|
+
env: { RUNNING_E2E_TESTS: "true" }
|
|
757
|
+
}),
|
|
758
|
+
credentials: {
|
|
759
|
+
envVars: [
|
|
760
|
+
"ROCKET_CHAT_DEMO_SERVER",
|
|
761
|
+
"ROCKET_CHAT_DEMO_USERNAME",
|
|
762
|
+
"ROCKET_CHAT_DEMO_PASSWORD"
|
|
763
|
+
],
|
|
764
|
+
known: { SERVER: "https://mobile.qa.rocket.chat" },
|
|
765
|
+
note: "use packages/sootsim-engine/scripts/rocket-chat-demo-auth.ts to create/login a disposable QA user and print the rocketchat://auth deep link"
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
name: "mattermost",
|
|
770
|
+
label: "Mattermost",
|
|
771
|
+
dir: MATTERMOST_DIR,
|
|
772
|
+
preferredPort: 8090,
|
|
773
|
+
framework: "expo",
|
|
774
|
+
runtimeConfig: {
|
|
775
|
+
modules: {
|
|
776
|
+
// mattermost patches react-native-keychain's JS entry in its own
|
|
777
|
+
// repo; run that package and provide only the native manager seam.
|
|
778
|
+
"react-native-keychain": false,
|
|
779
|
+
"dist/assets/config.json": {
|
|
780
|
+
inline: {
|
|
781
|
+
AuthUrlScheme: "mmauth://",
|
|
782
|
+
AuthUrlSchemeDev: "mmauthbeta://",
|
|
783
|
+
DefaultServerUrl: "http://localhost:8065",
|
|
784
|
+
DefaultServerName: "Mattermost Demo",
|
|
785
|
+
TestServerUrl: "http://localhost:8065",
|
|
786
|
+
AutoSelectServerUrl: true,
|
|
787
|
+
WebsiteURL: "https://mattermost.com",
|
|
788
|
+
ServerNoticeURL: "https://github.com/mattermost/mattermost-server/blob/master/NOTICE.txt",
|
|
789
|
+
MobileNoticeURL: "https://github.com/mattermost/mattermost-mobile/blob/master/NOTICE.txt",
|
|
790
|
+
RudderApiKey: "",
|
|
791
|
+
SentryEnabled: false,
|
|
792
|
+
SentryDsnIos: "",
|
|
793
|
+
SentryDsnAndroid: "",
|
|
794
|
+
SentryOptions: {
|
|
795
|
+
deactivateStacktraceMerging: true,
|
|
796
|
+
autoBreadcrumbs: {
|
|
797
|
+
xhr: false,
|
|
798
|
+
console: true
|
|
799
|
+
},
|
|
800
|
+
severityLevelFilter: ["fatal"]
|
|
801
|
+
},
|
|
802
|
+
ShowReview: false,
|
|
803
|
+
ShowOnboarding: false,
|
|
804
|
+
ExperimentalNormalizeMarkdownLinks: false,
|
|
805
|
+
CustomRequestHeaders: {},
|
|
806
|
+
CollectNetworkMetrics: false
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
nativeModules: {
|
|
811
|
+
RNKeychainManager: { file: getMattermostKeychainNativeModule() },
|
|
812
|
+
RNUtils: { file: getMattermostRNUtilsNativeModule() },
|
|
813
|
+
GenericClient: { file: getMattermostNetworkClientNativeModule() },
|
|
814
|
+
ApiClient: { file: getMattermostNetworkClientNativeModule() },
|
|
815
|
+
WebSocketClient: { file: getMattermostNetworkClientNativeModule() }
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
command: (p) => ({
|
|
819
|
+
cmd: `npx react-native start --host 127.0.0.1 --port ${p}`
|
|
820
|
+
}),
|
|
821
|
+
credentials: {
|
|
822
|
+
known: {
|
|
823
|
+
SERVER: "http://localhost:8065",
|
|
824
|
+
USERNAME: "demo",
|
|
825
|
+
PASSWORD: "DemoPassword1!"
|
|
826
|
+
},
|
|
827
|
+
note: "local mattermost-preview seeded through the real REST API; no signup or OTP needed"
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
name: "joplin",
|
|
832
|
+
label: "Joplin",
|
|
833
|
+
dir: JOPLIN_APP_DIR,
|
|
834
|
+
preferredPort: 8084,
|
|
835
|
+
framework: "expo",
|
|
836
|
+
// joplin is local-first: sync.target defaults to 0 ("None") and the
|
|
837
|
+
// mobile startup runs WelcomeUtils.install() on first launch, which
|
|
838
|
+
// seeds a "Welcome!" folder + welcome notes. no login or external
|
|
839
|
+
// credentials are needed for a usable demo — just boot it. tenant
|
|
840
|
+
// bedrock SQLite (via react-native-sqlite-storage stub) carries the
|
|
841
|
+
// seeded notes across reloads.
|
|
842
|
+
prepare: () => {
|
|
843
|
+
ensureJoplinWatchmanConfigs();
|
|
844
|
+
ensureJoplinBuilt();
|
|
845
|
+
},
|
|
846
|
+
command: (p) => ({
|
|
847
|
+
cmd: `npx expo start --port ${p}`,
|
|
848
|
+
env: { BROWSERSLIST_IGNORE_OLD_DATA: "true" }
|
|
849
|
+
}),
|
|
850
|
+
credentials: {
|
|
851
|
+
note: "no login required \u2014 sync.target=0 (None), seed via WelcomeUtils"
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
];
|
|
855
|
+
|
|
856
|
+
// scripts/dev-server-scanner.ts
|
|
857
|
+
var execP = (0, import_util.promisify)(import_child_process.exec);
|
|
858
|
+
var TIMEOUT_MS = 250;
|
|
859
|
+
var TCP_GATE_MS = 120;
|
|
860
|
+
function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
861
|
+
return new Promise((resolve2) => {
|
|
862
|
+
const sock = new import_net.default.Socket();
|
|
863
|
+
let settled = false;
|
|
864
|
+
const done = (ok) => {
|
|
865
|
+
if (settled) return;
|
|
866
|
+
settled = true;
|
|
867
|
+
sock.destroy();
|
|
868
|
+
resolve2(ok);
|
|
869
|
+
};
|
|
870
|
+
sock.setTimeout(timeout);
|
|
871
|
+
sock.once("connect", () => done(true));
|
|
872
|
+
sock.once("timeout", () => done(false));
|
|
873
|
+
sock.once("error", () => done(false));
|
|
874
|
+
sock.connect(port, "localhost");
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
function httpGet(port, path7, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
|
|
878
|
+
return new Promise((resolve2) => {
|
|
879
|
+
const req = import_http.default.request(
|
|
880
|
+
{ hostname: "localhost", port, path: path7, method, timeout, headers },
|
|
881
|
+
(res) => {
|
|
882
|
+
let body = "";
|
|
883
|
+
res.on("data", (c) => body += c.toString());
|
|
884
|
+
const contentType = (() => {
|
|
885
|
+
const raw = res.headers["content-type"];
|
|
886
|
+
if (typeof raw === "string") return raw;
|
|
887
|
+
if (Array.isArray(raw)) return raw[0];
|
|
888
|
+
return void 0;
|
|
889
|
+
})();
|
|
890
|
+
res.on(
|
|
891
|
+
"end",
|
|
892
|
+
() => resolve2({ statusCode: res.statusCode || 0, body, contentType })
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
req.on("error", () => resolve2(null));
|
|
897
|
+
req.setTimeout(timeout, () => {
|
|
898
|
+
req.destroy();
|
|
899
|
+
resolve2(null);
|
|
900
|
+
});
|
|
901
|
+
req.end();
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
var FALLBACK_PORTS = [
|
|
905
|
+
8081,
|
|
906
|
+
8082,
|
|
907
|
+
8083,
|
|
908
|
+
8084,
|
|
909
|
+
8085,
|
|
910
|
+
8086,
|
|
911
|
+
3e3,
|
|
912
|
+
3001,
|
|
913
|
+
19e3
|
|
914
|
+
].map((port) => ({ port, pid: 0 }));
|
|
915
|
+
function acceptPort(port, excluded) {
|
|
916
|
+
if (port <= 0 || port >= 2e4) return false;
|
|
917
|
+
if (excluded.has(port)) return false;
|
|
918
|
+
if (port >= 5170 && port <= 5200) return false;
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
async function discoverListeningProcesses(excludePorts = []) {
|
|
922
|
+
const excluded = new Set(excludePorts);
|
|
923
|
+
try {
|
|
924
|
+
const { stdout } = await execP(
|
|
925
|
+
`lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -E '^(node|bun)'`,
|
|
926
|
+
{ encoding: "utf8", timeout: 2e3 }
|
|
927
|
+
);
|
|
928
|
+
if (stdout.trim()) {
|
|
929
|
+
const seen = /* @__PURE__ */ new Map();
|
|
930
|
+
for (const line of stdout.trim().split("\n")) {
|
|
931
|
+
const parts = line.trim().split(/\s+/);
|
|
932
|
+
if (parts.length < 9) continue;
|
|
933
|
+
const pid = Number(parts[1]);
|
|
934
|
+
const addr = parts[8];
|
|
935
|
+
const m = addr.match(/:(\d+)$/);
|
|
936
|
+
if (!m) continue;
|
|
937
|
+
const port = Number(m[1]);
|
|
938
|
+
if (!acceptPort(port, excluded)) continue;
|
|
939
|
+
if (!seen.has(port)) seen.set(port, pid);
|
|
940
|
+
}
|
|
941
|
+
if (seen.size > 0) {
|
|
942
|
+
return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
const { stdout } = await execP(`ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`, {
|
|
949
|
+
encoding: "utf8",
|
|
950
|
+
timeout: 2e3
|
|
951
|
+
});
|
|
952
|
+
if (stdout.trim()) {
|
|
953
|
+
const seen = /* @__PURE__ */ new Map();
|
|
954
|
+
for (const line of stdout.trim().split("\n")) {
|
|
955
|
+
const portMatch = line.match(/:(\d+)\s/);
|
|
956
|
+
const pidMatch = line.match(/pid=(\d+)/);
|
|
957
|
+
if (!portMatch) continue;
|
|
958
|
+
const port = Number(portMatch[1]);
|
|
959
|
+
const pid = pidMatch ? Number(pidMatch[1]) : 0;
|
|
960
|
+
if (!acceptPort(port, excluded)) continue;
|
|
961
|
+
if (!seen.has(port)) seen.set(port, pid);
|
|
962
|
+
}
|
|
963
|
+
if (seen.size > 0) {
|
|
964
|
+
return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
return FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded));
|
|
970
|
+
}
|
|
971
|
+
var cwdByPid = /* @__PURE__ */ new Map();
|
|
972
|
+
async function resolveProcessCwd(pid) {
|
|
973
|
+
if (pid <= 0) return null;
|
|
974
|
+
const cached = cwdByPid.get(pid);
|
|
975
|
+
if (cached) return cached;
|
|
976
|
+
try {
|
|
977
|
+
const { stdout } = await execP(`lsof -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
|
|
978
|
+
encoding: "utf8",
|
|
979
|
+
timeout: 1500
|
|
980
|
+
});
|
|
981
|
+
for (const line of stdout.split("\n")) {
|
|
982
|
+
if (line.startsWith("n") && line.length > 1) {
|
|
983
|
+
const cwd = line.slice(1).trim();
|
|
984
|
+
if (cwd) {
|
|
985
|
+
cwdByPid.set(pid, cwd);
|
|
986
|
+
return cwd;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
function makeResult(port, framework) {
|
|
995
|
+
return {
|
|
996
|
+
port,
|
|
997
|
+
framework,
|
|
998
|
+
bundleUrl: withRuntimeConfig(
|
|
999
|
+
port,
|
|
1000
|
+
`http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`
|
|
1001
|
+
),
|
|
1002
|
+
hmrUrl: `ws://localhost:${port}/hot`,
|
|
1003
|
+
lastSeen: Date.now()
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function withRuntimeConfig(port, bundleUrl) {
|
|
1007
|
+
const knownApp = APPS.find((app) => app.preferredPort === port);
|
|
1008
|
+
const configured = knownApp?.runtimeConfig ? applySootSimConfigToUrl(bundleUrl, knownApp.runtimeConfig) : bundleUrl;
|
|
1009
|
+
return normalizeNativeDevBundleUrl(configured);
|
|
1010
|
+
}
|
|
1011
|
+
function isDirectOneBundleUrl(bundleUrl) {
|
|
1012
|
+
return bundleUrl.includes("/node_modules/one/metro-entry.bundle");
|
|
1013
|
+
}
|
|
1014
|
+
function safeParseManifest(body) {
|
|
1015
|
+
try {
|
|
1016
|
+
const parsed = JSON.parse(body);
|
|
1017
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1018
|
+
} catch {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function applyManifest(result, manifestRes, buildIconProxyUrl) {
|
|
1023
|
+
if (!manifestRes) return result;
|
|
1024
|
+
try {
|
|
1025
|
+
const manifest = JSON.parse(manifestRes.body);
|
|
1026
|
+
const client = manifest?.extra?.expoClient || manifest?.extra || {};
|
|
1027
|
+
if (client.name) result.projectName = client.name;
|
|
1028
|
+
if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
|
|
1029
|
+
if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
|
|
1030
|
+
const launchUrl2 = manifest?.launchAsset?.url;
|
|
1031
|
+
if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
|
|
1032
|
+
result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
|
|
1033
|
+
}
|
|
1034
|
+
const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
|
|
1035
|
+
if (rawIconUrl) {
|
|
1036
|
+
result.iconPath = rawIconUrl;
|
|
1037
|
+
if (buildIconProxyUrl) {
|
|
1038
|
+
if (rawIconUrl.startsWith("http")) {
|
|
1039
|
+
result.iconUrl = buildIconProxyUrl(rawIconUrl);
|
|
1040
|
+
} else {
|
|
1041
|
+
const cleanPath = rawIconUrl.replace(/^\.\//, "");
|
|
1042
|
+
result.iconUrl = buildIconProxyUrl(
|
|
1043
|
+
`http://localhost:${result.port}/assets/${cleanPath}`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
} else {
|
|
1047
|
+
result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `http://localhost:${result.port}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
} catch {
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
var knownNonPatched = /* @__PURE__ */ new Set();
|
|
1055
|
+
var knownNonExpo = /* @__PURE__ */ new Set();
|
|
1056
|
+
var knownOne = /* @__PURE__ */ new Set();
|
|
1057
|
+
async function probePort(port, buildIconProxyUrl) {
|
|
1058
|
+
if (!await tcpPing(port)) return null;
|
|
1059
|
+
const onePath = `/node_modules/one/metro-entry.bundle?platform=ios&dev=true`;
|
|
1060
|
+
const [sootsimRes, statusRes, oneRes, manifestRes, expoRes] = await Promise.all([
|
|
1061
|
+
knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(port, "/__soot/"),
|
|
1062
|
+
httpGet(port, "/status"),
|
|
1063
|
+
httpGet(port, onePath, "HEAD"),
|
|
1064
|
+
knownOne.has(port) ? Promise.resolve(null) : httpGet(port, "/", "GET", TIMEOUT_MS, { "expo-platform": "ios" }),
|
|
1065
|
+
knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(port, "/_expo/status")
|
|
1066
|
+
]);
|
|
1067
|
+
if (expoRes && expoRes.statusCode === 200) {
|
|
1068
|
+
knownNonExpo.delete(port);
|
|
1069
|
+
} else if (!knownNonExpo.has(port)) {
|
|
1070
|
+
knownNonExpo.add(port);
|
|
1071
|
+
}
|
|
1072
|
+
const manifestParsed = manifestRes ? safeParseManifest(manifestRes.body) : null;
|
|
1073
|
+
const manifestLaunchUrl = typeof manifestParsed?.launchAsset?.url === "string" ? manifestParsed.launchAsset.url : null;
|
|
1074
|
+
const manifestClient = manifestParsed?.extra?.expoClient || manifestParsed?.extra || {};
|
|
1075
|
+
if (manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string")) {
|
|
1076
|
+
knownNonPatched.add(port);
|
|
1077
|
+
const launchUrl2 = manifestLaunchUrl || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
|
|
1078
|
+
const framework = launchUrl2.includes(
|
|
1079
|
+
"/one/metro-entry.bundle"
|
|
1080
|
+
) ? "one" : "expo";
|
|
1081
|
+
return applyManifest(
|
|
1082
|
+
{
|
|
1083
|
+
port,
|
|
1084
|
+
framework,
|
|
1085
|
+
bundleUrl: withRuntimeConfig(port, launchUrl2),
|
|
1086
|
+
hmrUrl: `ws://localhost:${port}/hot`,
|
|
1087
|
+
lastSeen: Date.now()
|
|
1088
|
+
},
|
|
1089
|
+
manifestRes,
|
|
1090
|
+
buildIconProxyUrl
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (oneRes && oneRes.statusCode > 0 && oneRes.statusCode < 400 && /application\/javascript/i.test(oneRes.contentType || "")) {
|
|
1094
|
+
knownNonPatched.add(port);
|
|
1095
|
+
knownOne.add(port);
|
|
1096
|
+
return applyManifest(
|
|
1097
|
+
{
|
|
1098
|
+
port,
|
|
1099
|
+
framework: "one",
|
|
1100
|
+
bundleUrl: withRuntimeConfig(
|
|
1101
|
+
port,
|
|
1102
|
+
`http://localhost:${port}${onePath}&minify=false`
|
|
1103
|
+
),
|
|
1104
|
+
hmrUrl: `ws://localhost:${port}/hot`,
|
|
1105
|
+
lastSeen: Date.now()
|
|
1106
|
+
},
|
|
1107
|
+
manifestRes,
|
|
1108
|
+
buildIconProxyUrl
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
if (statusRes && statusRes.body.includes("packager-status:running")) {
|
|
1112
|
+
knownNonPatched.add(port);
|
|
1113
|
+
return applyManifest(
|
|
1114
|
+
makeResult(port, expoRes && expoRes.statusCode === 200 ? "expo" : "metro"),
|
|
1115
|
+
manifestRes,
|
|
1116
|
+
buildIconProxyUrl
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
if (sootsimRes && sootsimRes.statusCode === 200 && sootsimRes.body.includes("sootsim-patched")) {
|
|
1120
|
+
knownNonPatched.delete(port);
|
|
1121
|
+
return applyManifest(
|
|
1122
|
+
{
|
|
1123
|
+
port,
|
|
1124
|
+
framework: "one",
|
|
1125
|
+
bundleUrl: withRuntimeConfig(port, `http://localhost:${port}/__soot/bundle.js`),
|
|
1126
|
+
hmrUrl: `ws://localhost:${port}/hot`,
|
|
1127
|
+
lastSeen: Date.now(),
|
|
1128
|
+
patched: true
|
|
1129
|
+
},
|
|
1130
|
+
manifestRes,
|
|
1131
|
+
buildIconProxyUrl
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
knownNonPatched.add(port);
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
function isSootSelfServer(server) {
|
|
1138
|
+
const projectName = server.projectName?.trim().toLowerCase();
|
|
1139
|
+
if (projectName === "soot" || projectName === "sootsim") return true;
|
|
1140
|
+
const bundleId = server.bundleId?.trim().toLowerCase();
|
|
1141
|
+
if (bundleId?.startsWith("dev.soot")) return true;
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
var portCache = /* @__PURE__ */ new Map();
|
|
1145
|
+
var NEGATIVE_CACHE_TTL_MS = 3e4;
|
|
1146
|
+
var WEAK_RESULT_CACHE_TTL_MS = 1500;
|
|
1147
|
+
function isWeakCachedResult(result) {
|
|
1148
|
+
if (!result) return true;
|
|
1149
|
+
if (result.framework === "metro" || result.framework === "unknown") return true;
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
function hasCurrentRuntimeConfig(result) {
|
|
1153
|
+
if (!result) return true;
|
|
1154
|
+
return withRuntimeConfig(result.port, result.bundleUrl) === result.bundleUrl;
|
|
1155
|
+
}
|
|
1156
|
+
function __shouldReuseScannerCacheEntry(entry, pid, now = Date.now()) {
|
|
1157
|
+
if (pid === 0) return false;
|
|
1158
|
+
if (entry.pid !== pid) return false;
|
|
1159
|
+
if (!hasCurrentRuntimeConfig(entry.result)) return false;
|
|
1160
|
+
const ageMs = now - entry.cachedAt;
|
|
1161
|
+
if (entry.result === null && ageMs >= NEGATIVE_CACHE_TTL_MS) return false;
|
|
1162
|
+
if (isWeakCachedResult(entry.result) && ageMs >= WEAK_RESULT_CACHE_TTL_MS) return false;
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
async function scanDevServers(opts = {}) {
|
|
1166
|
+
const processes = await discoverListeningProcesses(opts.excludePorts);
|
|
1167
|
+
const currentPorts = new Set(processes.map((p) => p.port));
|
|
1168
|
+
for (const p of [...portCache.keys()]) {
|
|
1169
|
+
if (!currentPorts.has(p)) portCache.delete(p);
|
|
1170
|
+
}
|
|
1171
|
+
for (const p of [...knownNonPatched]) {
|
|
1172
|
+
if (!currentPorts.has(p)) knownNonPatched.delete(p);
|
|
1173
|
+
}
|
|
1174
|
+
for (const p of [...knownNonExpo]) {
|
|
1175
|
+
if (!currentPorts.has(p)) knownNonExpo.delete(p);
|
|
1176
|
+
}
|
|
1177
|
+
for (const p of [...knownOne]) {
|
|
1178
|
+
if (!currentPorts.has(p)) knownOne.delete(p);
|
|
1179
|
+
}
|
|
1180
|
+
const results = [];
|
|
1181
|
+
const toProbe = [];
|
|
1182
|
+
for (const { port, pid } of processes) {
|
|
1183
|
+
const cached = portCache.get(port);
|
|
1184
|
+
if (cached && __shouldReuseScannerCacheEntry(cached, pid)) {
|
|
1185
|
+
if (cached.result) results.push(cached.result);
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (cached && cached.pid !== pid) {
|
|
1189
|
+
knownNonPatched.delete(port);
|
|
1190
|
+
knownNonExpo.delete(port);
|
|
1191
|
+
knownOne.delete(port);
|
|
1192
|
+
}
|
|
1193
|
+
toProbe.push({ port, pid });
|
|
826
1194
|
}
|
|
827
|
-
|
|
1195
|
+
if (toProbe.length > 0) {
|
|
1196
|
+
const probed = await Promise.all(
|
|
1197
|
+
toProbe.map((p) => probePort(p.port, opts.buildIconProxyUrl))
|
|
1198
|
+
);
|
|
1199
|
+
probed.forEach((result, i) => {
|
|
1200
|
+
const { port, pid } = toProbe[i];
|
|
1201
|
+
if (pid !== 0) portCache.set(port, { pid, result, cachedAt: Date.now() });
|
|
1202
|
+
if (result) results.push(result);
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
const pidByPort = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const { port, pid } of processes) {
|
|
1207
|
+
if (pid > 0) pidByPort.set(port, pid);
|
|
1208
|
+
}
|
|
1209
|
+
await Promise.all(
|
|
1210
|
+
results.map(async (result) => {
|
|
1211
|
+
const pid = pidByPort.get(result.port);
|
|
1212
|
+
if (!pid) return;
|
|
1213
|
+
result.pid = pid;
|
|
1214
|
+
const cwd = await resolveProcessCwd(pid);
|
|
1215
|
+
if (cwd) result.cwd = cwd;
|
|
1216
|
+
})
|
|
1217
|
+
);
|
|
1218
|
+
const livePids = new Set(pidByPort.values());
|
|
1219
|
+
for (const pid of [...cwdByPid.keys()]) {
|
|
1220
|
+
if (!livePids.has(pid)) cwdByPid.delete(pid);
|
|
1221
|
+
}
|
|
1222
|
+
return results.filter((r) => !isSootSelfServer(r));
|
|
1223
|
+
}
|
|
828
1224
|
|
|
829
|
-
//
|
|
830
|
-
var
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1225
|
+
// src/bridge-constants.ts
|
|
1226
|
+
var DEFAULT_SOOTSIM_BRIDGE_PORT = 7668;
|
|
1227
|
+
|
|
1228
|
+
// src/home-paths.ts
|
|
1229
|
+
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
1230
|
+
var import_node_os2 = require("node:os");
|
|
1231
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
1232
|
+
var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
|
|
1233
|
+
var ACTIVE_RUNTIME_FILE = "active";
|
|
1234
|
+
var DAEMON_LOCKFILE = "daemon.json";
|
|
1235
|
+
var CONFIG_FILE = "config.json";
|
|
1236
|
+
var DAEMON_HEARTBEAT_STALE_MS = 3e4;
|
|
1237
|
+
function sootsimHomeDir() {
|
|
1238
|
+
const override = process.env[SOOTSIM_HOME_ENV];
|
|
1239
|
+
if (override && override.length > 0) return import_node_path2.default.resolve(override);
|
|
1240
|
+
return import_node_path2.default.join((0, import_node_os2.homedir)(), ".sootsim");
|
|
1241
|
+
}
|
|
1242
|
+
function runtimesDir() {
|
|
1243
|
+
return import_node_path2.default.join(sootsimHomeDir(), "runtimes");
|
|
1244
|
+
}
|
|
1245
|
+
function runtimeDir(version) {
|
|
1246
|
+
return import_node_path2.default.join(runtimesDir(), version);
|
|
1247
|
+
}
|
|
1248
|
+
function activeRuntimeFile() {
|
|
1249
|
+
return import_node_path2.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
|
|
1250
|
+
}
|
|
1251
|
+
function electronDir() {
|
|
1252
|
+
return import_node_path2.default.join(sootsimHomeDir(), "electron");
|
|
1253
|
+
}
|
|
1254
|
+
function electronUserDataDir() {
|
|
1255
|
+
return import_node_path2.default.join(electronDir(), "userData");
|
|
1256
|
+
}
|
|
1257
|
+
function profilesDir() {
|
|
1258
|
+
return import_node_path2.default.join(sootsimHomeDir(), "profiles");
|
|
1259
|
+
}
|
|
1260
|
+
function cacheDir() {
|
|
1261
|
+
return import_node_path2.default.join(sootsimHomeDir(), "cache");
|
|
1262
|
+
}
|
|
1263
|
+
function daemonLockfilePath() {
|
|
1264
|
+
return import_node_path2.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
|
|
1265
|
+
}
|
|
1266
|
+
function configFilePath() {
|
|
1267
|
+
return import_node_path2.default.join(sootsimHomeDir(), CONFIG_FILE);
|
|
1268
|
+
}
|
|
1269
|
+
function readSharedConfig() {
|
|
1270
|
+
try {
|
|
1271
|
+
const raw = import_node_fs2.default.readFileSync(configFilePath(), "utf8");
|
|
1272
|
+
const parsed = JSON.parse(raw);
|
|
1273
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1274
|
+
} catch {
|
|
1275
|
+
return {};
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function writeSharedConfig(patch) {
|
|
1279
|
+
ensureSootsimHome();
|
|
1280
|
+
const current = readSharedConfig();
|
|
1281
|
+
const next = { ...current, ...patch };
|
|
1282
|
+
if (patch.settings && typeof patch.settings === "object") {
|
|
1283
|
+
next.settings = {
|
|
1284
|
+
...current.settings && typeof current.settings === "object" ? current.settings : {},
|
|
1285
|
+
...patch.settings
|
|
842
1286
|
};
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1287
|
+
}
|
|
1288
|
+
const tmp = `${configFilePath()}.tmp`;
|
|
1289
|
+
import_node_fs2.default.writeFileSync(tmp, `${JSON.stringify(next, null, 2)}
|
|
1290
|
+
`, "utf8");
|
|
1291
|
+
import_node_fs2.default.renameSync(tmp, configFilePath());
|
|
1292
|
+
return next;
|
|
849
1293
|
}
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
res.on("data", (c) => body += c.toString());
|
|
857
|
-
res.on("end", () => resolve2({ statusCode: res.statusCode || 0, body }));
|
|
858
|
-
}
|
|
859
|
-
);
|
|
860
|
-
req.on("error", () => resolve2(null));
|
|
861
|
-
req.setTimeout(timeout, () => {
|
|
862
|
-
req.destroy();
|
|
863
|
-
resolve2(null);
|
|
864
|
-
});
|
|
865
|
-
req.end();
|
|
866
|
-
});
|
|
1294
|
+
function ensureSootsimHome() {
|
|
1295
|
+
import_node_fs2.default.mkdirSync(sootsimHomeDir(), { recursive: true });
|
|
1296
|
+
import_node_fs2.default.mkdirSync(runtimesDir(), { recursive: true });
|
|
1297
|
+
import_node_fs2.default.mkdirSync(electronDir(), { recursive: true });
|
|
1298
|
+
import_node_fs2.default.mkdirSync(profilesDir(), { recursive: true });
|
|
1299
|
+
import_node_fs2.default.mkdirSync(cacheDir(), { recursive: true });
|
|
867
1300
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
3e3,
|
|
876
|
-
3001,
|
|
877
|
-
19e3
|
|
878
|
-
].map((port) => ({ port, pid: 0 }));
|
|
879
|
-
function acceptPort(port, excluded) {
|
|
880
|
-
if (port <= 0 || port >= 2e4) return false;
|
|
881
|
-
if (excluded.has(port)) return false;
|
|
882
|
-
if (port >= 5170 && port <= 5200) return false;
|
|
883
|
-
return true;
|
|
1301
|
+
function readActiveRuntime() {
|
|
1302
|
+
try {
|
|
1303
|
+
const value = import_node_fs2.default.readFileSync(activeRuntimeFile(), "utf8").trim();
|
|
1304
|
+
return value.length > 0 ? value : null;
|
|
1305
|
+
} catch {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
884
1308
|
}
|
|
885
|
-
|
|
886
|
-
|
|
1309
|
+
function writeActiveRuntime(version) {
|
|
1310
|
+
import_node_fs2.default.mkdirSync(runtimesDir(), { recursive: true });
|
|
1311
|
+
import_node_fs2.default.writeFileSync(activeRuntimeFile(), `${version}
|
|
1312
|
+
`, "utf8");
|
|
1313
|
+
}
|
|
1314
|
+
function listInstalledRuntimes() {
|
|
887
1315
|
try {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1316
|
+
return import_node_fs2.default.readdirSync(runtimesDir(), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort(compareSemver);
|
|
1317
|
+
} catch {
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function compareSemver(a, b) {
|
|
1322
|
+
const parse = (v) => {
|
|
1323
|
+
const hyphen = v.indexOf("-");
|
|
1324
|
+
const core = hyphen >= 0 ? v.slice(0, hyphen) : v;
|
|
1325
|
+
const pre = hyphen >= 0 ? v.slice(hyphen + 1) : "";
|
|
1326
|
+
const parts = core.split(".").map((n) => Number.parseInt(n, 10));
|
|
1327
|
+
if (parts.some((n) => !Number.isFinite(n))) return [[Number.POSITIVE_INFINITY], v];
|
|
1328
|
+
return [parts, pre];
|
|
1329
|
+
};
|
|
1330
|
+
const [an, ap] = parse(a);
|
|
1331
|
+
const [bn, bp] = parse(b);
|
|
1332
|
+
for (let i = 0; i < Math.max(an.length, bn.length); i++) {
|
|
1333
|
+
const av = an[i] ?? 0;
|
|
1334
|
+
const bv = bn[i] ?? 0;
|
|
1335
|
+
if (av !== bv) return av - bv;
|
|
1336
|
+
}
|
|
1337
|
+
if (ap === bp) return 0;
|
|
1338
|
+
if (!ap) return 1;
|
|
1339
|
+
if (!bp) return -1;
|
|
1340
|
+
return ap < bp ? -1 : 1;
|
|
1341
|
+
}
|
|
1342
|
+
function activeRuntimeDir() {
|
|
1343
|
+
const version = readActiveRuntime();
|
|
1344
|
+
if (!version) return null;
|
|
1345
|
+
const dir = runtimeDir(version);
|
|
1346
|
+
try {
|
|
1347
|
+
if (import_node_fs2.default.statSync(dir).isDirectory()) return dir;
|
|
909
1348
|
} catch {
|
|
910
1349
|
}
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
|
|
1353
|
+
function readDaemonLockfile() {
|
|
911
1354
|
try {
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const pidMatch = line.match(/pid=(\d+)/);
|
|
921
|
-
if (!portMatch) continue;
|
|
922
|
-
const port = Number(portMatch[1]);
|
|
923
|
-
const pid = pidMatch ? Number(pidMatch[1]) : 0;
|
|
924
|
-
if (!acceptPort(port, excluded)) continue;
|
|
925
|
-
if (!seen.has(port)) seen.set(port, pid);
|
|
926
|
-
}
|
|
927
|
-
if (seen.size > 0) {
|
|
928
|
-
return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
|
|
1355
|
+
const fd = import_node_fs2.default.openSync(daemonLockfilePath(), "r");
|
|
1356
|
+
try {
|
|
1357
|
+
const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
|
|
1358
|
+
const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
|
|
1359
|
+
const raw = buf.subarray(0, bytesRead).toString("utf8");
|
|
1360
|
+
const parsed = JSON.parse(raw);
|
|
1361
|
+
if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number") {
|
|
1362
|
+
return parsed;
|
|
929
1363
|
}
|
|
1364
|
+
return null;
|
|
1365
|
+
} finally {
|
|
1366
|
+
import_node_fs2.default.closeSync(fd);
|
|
930
1367
|
}
|
|
931
1368
|
} catch {
|
|
1369
|
+
return null;
|
|
932
1370
|
}
|
|
933
|
-
return FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded));
|
|
934
1371
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (
|
|
938
|
-
const cached = cwdByPid.get(pid);
|
|
939
|
-
if (cached) return cached;
|
|
1372
|
+
function isDaemonLockfileFresh(lock, now = Date.now()) {
|
|
1373
|
+
if (!lock) return false;
|
|
1374
|
+
if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
|
|
940
1375
|
try {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
timeout: 1500
|
|
944
|
-
});
|
|
945
|
-
for (const line of stdout.split("\n")) {
|
|
946
|
-
if (line.startsWith("n") && line.length > 1) {
|
|
947
|
-
const cwd = line.slice(1).trim();
|
|
948
|
-
if (cwd) {
|
|
949
|
-
cwdByPid.set(pid, cwd);
|
|
950
|
-
return cwd;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
1376
|
+
process.kill(lock.pid, 0);
|
|
1377
|
+
return true;
|
|
954
1378
|
} catch {
|
|
1379
|
+
return false;
|
|
955
1380
|
}
|
|
956
|
-
return null;
|
|
957
1381
|
}
|
|
958
|
-
function
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
`http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`
|
|
965
|
-
),
|
|
966
|
-
hmrUrl: `ws://localhost:${port}/hot`,
|
|
967
|
-
lastSeen: Date.now()
|
|
968
|
-
};
|
|
1382
|
+
function writeDaemonLockfile(data) {
|
|
1383
|
+
ensureSootsimHome();
|
|
1384
|
+
const tmp = `${daemonLockfilePath()}.tmp`;
|
|
1385
|
+
import_node_fs2.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
|
|
1386
|
+
`, "utf8");
|
|
1387
|
+
import_node_fs2.default.renameSync(tmp, daemonLockfilePath());
|
|
969
1388
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
const
|
|
973
|
-
|
|
1389
|
+
function claimDaemonLockfile(data) {
|
|
1390
|
+
ensureSootsimHome();
|
|
1391
|
+
const existing = readDaemonLockfile();
|
|
1392
|
+
if (existing && isDaemonLockfileFresh(existing) && existing.pid !== data.pid) {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
writeDaemonLockfile(data);
|
|
1396
|
+
return true;
|
|
974
1397
|
}
|
|
975
|
-
function
|
|
976
|
-
|
|
1398
|
+
function removeDaemonLockfile() {
|
|
1399
|
+
try {
|
|
1400
|
+
import_node_fs2.default.unlinkSync(daemonLockfilePath());
|
|
1401
|
+
} catch {
|
|
1402
|
+
}
|
|
977
1403
|
}
|
|
978
|
-
|
|
979
|
-
|
|
1404
|
+
|
|
1405
|
+
// src/runtime-delivery.ts
|
|
1406
|
+
var import_child_process2 = require("child_process");
|
|
1407
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
1408
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
1409
|
+
var import_path = __toESM(require("path"), 1);
|
|
1410
|
+
var import_stream = require("stream");
|
|
1411
|
+
var import_promises = require("stream/promises");
|
|
1412
|
+
var DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
|
|
1413
|
+
var RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
|
|
1414
|
+
var RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
|
|
1415
|
+
function readConfig() {
|
|
980
1416
|
try {
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
if (client.name) result.projectName = client.name;
|
|
984
|
-
if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
|
|
985
|
-
if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
|
|
986
|
-
const launchUrl2 = manifest?.launchAsset?.url;
|
|
987
|
-
if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
|
|
988
|
-
result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
|
|
989
|
-
}
|
|
990
|
-
const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
|
|
991
|
-
if (rawIconUrl) {
|
|
992
|
-
result.iconPath = rawIconUrl;
|
|
993
|
-
if (buildIconProxyUrl) {
|
|
994
|
-
if (rawIconUrl.startsWith("http")) {
|
|
995
|
-
result.iconUrl = buildIconProxyUrl(rawIconUrl);
|
|
996
|
-
} else {
|
|
997
|
-
const cleanPath = rawIconUrl.replace(/^\.\//, "");
|
|
998
|
-
result.iconUrl = buildIconProxyUrl(
|
|
999
|
-
`http://localhost:${result.port}/assets/${cleanPath}`
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
} else {
|
|
1003
|
-
result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `http://localhost:${result.port}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1417
|
+
const parsed = JSON.parse(import_fs.default.readFileSync(configFilePath(), "utf8"));
|
|
1418
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1006
1419
|
} catch {
|
|
1420
|
+
return {};
|
|
1007
1421
|
}
|
|
1008
|
-
return result;
|
|
1009
1422
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1423
|
+
function resolveRuntimeCdnOrigin(input) {
|
|
1424
|
+
const config = readConfig();
|
|
1425
|
+
const value = input || process.env[RUNTIME_CDN_ORIGIN_ENV] || config.cdnOrigin || DEFAULT_RUNTIME_CDN_ORIGIN;
|
|
1426
|
+
return value.replace(/\/+$/, "");
|
|
1427
|
+
}
|
|
1428
|
+
function resolveRuntimeChannel(input) {
|
|
1429
|
+
const config = readConfig();
|
|
1430
|
+
return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
|
|
1431
|
+
}
|
|
1432
|
+
function runtimeManifestUrl(cdnOrigin) {
|
|
1433
|
+
const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
|
|
1434
|
+
url.searchParams.set("t", String(Date.now()));
|
|
1435
|
+
return url.toString();
|
|
1436
|
+
}
|
|
1437
|
+
function runtimeTarballUrl(version, cdnOrigin) {
|
|
1438
|
+
return `${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/sootsim-runtime-${version}.tar.gz`;
|
|
1439
|
+
}
|
|
1440
|
+
async function fetchRuntimeManifest(cdnOrigin) {
|
|
1441
|
+
const url = runtimeManifestUrl(cdnOrigin);
|
|
1442
|
+
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
1443
|
+
if (!res.ok) {
|
|
1444
|
+
throw new Error(`manifest fetch failed: ${res.status} ${res.statusText} (${url})`);
|
|
1026
1445
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
`http://localhost:${port}${onePath}&minify=false`
|
|
1036
|
-
),
|
|
1037
|
-
hmrUrl: `ws://localhost:${port}/hot`,
|
|
1038
|
-
lastSeen: Date.now()
|
|
1039
|
-
},
|
|
1040
|
-
manifestRes,
|
|
1041
|
-
buildIconProxyUrl
|
|
1446
|
+
return await res.json();
|
|
1447
|
+
}
|
|
1448
|
+
function resolveRuntimeVersion(manifest, opts = {}) {
|
|
1449
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
1450
|
+
const version = opts.version || manifest.channels[channel]?.latest;
|
|
1451
|
+
if (!version) {
|
|
1452
|
+
throw new Error(
|
|
1453
|
+
`no version specified and channel '${channel}' has no latest entry in the manifest`
|
|
1042
1454
|
);
|
|
1043
1455
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
manifestRes,
|
|
1049
|
-
buildIconProxyUrl
|
|
1456
|
+
const entry = manifest.versions[version];
|
|
1457
|
+
if (!entry) {
|
|
1458
|
+
throw new Error(
|
|
1459
|
+
`version ${version} not found in manifest; available: ${Object.keys(manifest.versions).slice(-10).join(", ") || "(none)"}`
|
|
1050
1460
|
);
|
|
1051
1461
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1462
|
+
return { version, channel, entry };
|
|
1463
|
+
}
|
|
1464
|
+
async function installRuntime(opts = {}) {
|
|
1465
|
+
ensureSootsimHome();
|
|
1466
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
1467
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
1468
|
+
const { version, channel, entry } = resolveRuntimeVersion(manifest, opts);
|
|
1469
|
+
const destDir = runtimeDir(version);
|
|
1470
|
+
const setActive = opts.setActive !== false;
|
|
1471
|
+
if (!opts.force && import_fs.default.existsSync(import_path.default.join(destDir, "index.html"))) {
|
|
1472
|
+
if (setActive) writeActiveRuntime(version);
|
|
1473
|
+
return {
|
|
1474
|
+
version,
|
|
1475
|
+
channel,
|
|
1476
|
+
cdnOrigin,
|
|
1477
|
+
runtimeDir: destDir,
|
|
1478
|
+
installed: false,
|
|
1479
|
+
activated: setActive,
|
|
1480
|
+
manifest
|
|
1481
|
+
};
|
|
1073
1482
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
buildIconProxyUrl
|
|
1483
|
+
const tarUrl = entry.tarball || runtimeTarballUrl(version, cdnOrigin);
|
|
1484
|
+
const tarCachePath = import_path.default.join(cacheDir(), `sootsim-runtime-${version}.tar.gz`);
|
|
1485
|
+
process.stderr.write(`sootsim: downloading runtime ${version}\u2026
|
|
1486
|
+
`);
|
|
1487
|
+
await downloadToFile(tarUrl, tarCachePath);
|
|
1488
|
+
process.stderr.write(`sootsim: extracting runtime ${version}\u2026
|
|
1489
|
+
`);
|
|
1490
|
+
const actualSha = await sha256File(tarCachePath);
|
|
1491
|
+
if (actualSha !== entry.sha256) {
|
|
1492
|
+
import_fs.default.rmSync(tarCachePath, { force: true });
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
`sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
|
|
1087
1495
|
);
|
|
1088
1496
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (entry.pid !== pid) return false;
|
|
1114
|
-
if (!hasCurrentRuntimeConfig(entry.result)) return false;
|
|
1115
|
-
const ageMs = now - entry.cachedAt;
|
|
1116
|
-
if (entry.result === null && ageMs >= NEGATIVE_CACHE_TTL_MS) return false;
|
|
1117
|
-
if (isWeakCachedResult(entry.result) && ageMs >= WEAK_RESULT_CACHE_TTL_MS) return false;
|
|
1118
|
-
return true;
|
|
1497
|
+
const tmpDir = import_path.default.join(import_path.default.dirname(destDir), `.installing-${version}-${process.pid}`);
|
|
1498
|
+
import_fs.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
1499
|
+
import_fs.default.mkdirSync(tmpDir, { recursive: true });
|
|
1500
|
+
try {
|
|
1501
|
+
await extractTarball(tarCachePath, tmpDir);
|
|
1502
|
+
if (!import_fs.default.existsSync(import_path.default.join(tmpDir, "index.html"))) {
|
|
1503
|
+
throw new Error(`extracted tarball for runtime ${version} is missing index.html`);
|
|
1504
|
+
}
|
|
1505
|
+
import_fs.default.rmSync(destDir, { recursive: true, force: true });
|
|
1506
|
+
import_fs.default.renameSync(tmpDir, destDir);
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
import_fs.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
1509
|
+
throw err;
|
|
1510
|
+
}
|
|
1511
|
+
if (setActive) writeActiveRuntime(version);
|
|
1512
|
+
return {
|
|
1513
|
+
version,
|
|
1514
|
+
channel,
|
|
1515
|
+
cdnOrigin,
|
|
1516
|
+
runtimeDir: destDir,
|
|
1517
|
+
installed: true,
|
|
1518
|
+
activated: setActive,
|
|
1519
|
+
manifest
|
|
1520
|
+
};
|
|
1119
1521
|
}
|
|
1120
|
-
async function
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1522
|
+
async function updateRuntimeToLatest(opts = {}) {
|
|
1523
|
+
ensureSootsimHome();
|
|
1524
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
1525
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
1526
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
1527
|
+
const latestVersion = manifest.channels[channel]?.latest;
|
|
1528
|
+
if (!latestVersion) {
|
|
1529
|
+
return {
|
|
1530
|
+
checked: true,
|
|
1531
|
+
updated: false,
|
|
1532
|
+
reason: `channel '${channel}' has no latest runtime`,
|
|
1533
|
+
activeVersion: readActiveRuntime()
|
|
1534
|
+
};
|
|
1125
1535
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1536
|
+
const entry = manifest.versions[latestVersion];
|
|
1537
|
+
if (!entry) {
|
|
1538
|
+
return {
|
|
1539
|
+
checked: true,
|
|
1540
|
+
updated: false,
|
|
1541
|
+
reason: `manifest is missing version ${latestVersion}`,
|
|
1542
|
+
activeVersion: readActiveRuntime(),
|
|
1543
|
+
latestVersion
|
|
1544
|
+
};
|
|
1128
1545
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1546
|
+
const activeVersion = readActiveRuntime();
|
|
1547
|
+
const activeDir = activeVersion ? runtimeDir(activeVersion) : null;
|
|
1548
|
+
const activeInstalled = activeDir ? import_fs.default.existsSync(import_path.default.join(activeDir, "index.html")) : false;
|
|
1549
|
+
const shouldInstall = !activeVersion || !activeInstalled || compareSemver(latestVersion, activeVersion) > 0;
|
|
1550
|
+
if (!shouldInstall) {
|
|
1551
|
+
return {
|
|
1552
|
+
checked: true,
|
|
1553
|
+
updated: false,
|
|
1554
|
+
reason: "active runtime is current",
|
|
1555
|
+
activeVersion,
|
|
1556
|
+
latestVersion
|
|
1557
|
+
};
|
|
1131
1558
|
}
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1559
|
+
const install = await installRuntime({
|
|
1560
|
+
version: latestVersion,
|
|
1561
|
+
channel,
|
|
1562
|
+
cdnOrigin,
|
|
1563
|
+
setActive: false
|
|
1564
|
+
});
|
|
1565
|
+
return {
|
|
1566
|
+
checked: true,
|
|
1567
|
+
updated: true,
|
|
1568
|
+
activeVersion: latestVersion,
|
|
1569
|
+
latestVersion,
|
|
1570
|
+
install
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
async function downloadToFile(url, destPath) {
|
|
1574
|
+
const res = await fetch(url);
|
|
1575
|
+
if (!res.ok || !res.body) {
|
|
1576
|
+
throw new Error(`download failed: ${res.status} ${res.statusText} (${url})`);
|
|
1145
1577
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1578
|
+
import_fs.default.mkdirSync(import_path.default.dirname(destPath), { recursive: true });
|
|
1579
|
+
const tmp = `${destPath}.partial`;
|
|
1580
|
+
try {
|
|
1581
|
+
await (0, import_promises.pipeline)(
|
|
1582
|
+
import_stream.Readable.fromWeb(res.body),
|
|
1583
|
+
import_fs.default.createWriteStream(tmp)
|
|
1149
1584
|
);
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
for (const { port, pid } of processes) {
|
|
1158
|
-
if (pid > 0) pidByPort.set(port, pid);
|
|
1159
|
-
}
|
|
1160
|
-
await Promise.all(
|
|
1161
|
-
results.map(async (result) => {
|
|
1162
|
-
const pid = pidByPort.get(result.port);
|
|
1163
|
-
if (!pid) return;
|
|
1164
|
-
result.pid = pid;
|
|
1165
|
-
const cwd = await resolveProcessCwd(pid);
|
|
1166
|
-
if (cwd) result.cwd = cwd;
|
|
1167
|
-
})
|
|
1168
|
-
);
|
|
1169
|
-
const livePids = new Set(pidByPort.values());
|
|
1170
|
-
for (const pid of [...cwdByPid.keys()]) {
|
|
1171
|
-
if (!livePids.has(pid)) cwdByPid.delete(pid);
|
|
1585
|
+
import_fs.default.renameSync(tmp, destPath);
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
try {
|
|
1588
|
+
import_fs.default.unlinkSync(tmp);
|
|
1589
|
+
} catch {
|
|
1590
|
+
}
|
|
1591
|
+
throw err;
|
|
1172
1592
|
}
|
|
1173
|
-
return results.filter((r) => !isSootSelfServer(r));
|
|
1174
1593
|
}
|
|
1594
|
+
function sha256File(filePath) {
|
|
1595
|
+
return new Promise((resolve2, reject) => {
|
|
1596
|
+
const hash = import_crypto.default.createHash("sha256");
|
|
1597
|
+
const stream = import_fs.default.createReadStream(filePath);
|
|
1598
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
1599
|
+
stream.on("error", reject);
|
|
1600
|
+
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
function extractTarball(tarPath, destDir) {
|
|
1604
|
+
return new Promise((resolve2, reject) => {
|
|
1605
|
+
const child = (0, import_child_process2.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
|
|
1606
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
1607
|
+
});
|
|
1608
|
+
child.on("error", reject);
|
|
1609
|
+
child.on("exit", (code) => {
|
|
1610
|
+
if (code === 0) resolve2();
|
|
1611
|
+
else reject(new Error(`tar exited with code ${code}`));
|
|
1612
|
+
});
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/host/agent-host.ts
|
|
1617
|
+
var import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
1618
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
1175
1619
|
|
|
1176
1620
|
// src/agent-sessions.ts
|
|
1177
1621
|
var import_node_child_process = require("node:child_process");
|
|
1178
1622
|
var import_node_crypto2 = require("node:crypto");
|
|
1179
|
-
var
|
|
1180
|
-
var
|
|
1623
|
+
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
1624
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
1181
1625
|
var import_node_readline = __toESM(require("node:readline"), 1);
|
|
1182
1626
|
|
|
1183
1627
|
// src/agent-events.ts
|
|
@@ -1221,37 +1665,20 @@ function encodeAgentPromptEnvelope(input) {
|
|
|
1221
1665
|
|
|
1222
1666
|
// src/attached-projects.ts
|
|
1223
1667
|
var import_node_crypto = require("node:crypto");
|
|
1224
|
-
var
|
|
1225
|
-
var
|
|
1226
|
-
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
1668
|
+
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
1669
|
+
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
1227
1670
|
var overrideDir = null;
|
|
1228
1671
|
function userDataDir() {
|
|
1229
1672
|
if (overrideDir) return overrideDir;
|
|
1230
1673
|
const fromEnv = process.env.SOOTSIM_USER_DATA_DIR;
|
|
1231
1674
|
if (fromEnv) return fromEnv;
|
|
1232
|
-
|
|
1233
|
-
const electron = require("electron");
|
|
1234
|
-
if (electron.app?.getPath) return electron.app.getPath("userData");
|
|
1235
|
-
} catch {
|
|
1236
|
-
}
|
|
1237
|
-
return platformDefaultUserDataDir();
|
|
1238
|
-
}
|
|
1239
|
-
function platformDefaultUserDataDir() {
|
|
1240
|
-
const home = import_node_os2.default.homedir();
|
|
1241
|
-
if (process.platform === "darwin") {
|
|
1242
|
-
return import_node_path2.default.join(home, "Library", "Application Support", "sootsim");
|
|
1243
|
-
}
|
|
1244
|
-
if (process.platform === "win32") {
|
|
1245
|
-
return import_node_path2.default.join(process.env.APPDATA || home, "sootsim");
|
|
1246
|
-
}
|
|
1247
|
-
const xdg = process.env.XDG_CONFIG_HOME || import_node_path2.default.join(home, ".config");
|
|
1248
|
-
return import_node_path2.default.join(xdg, "sootsim");
|
|
1675
|
+
return electronUserDataDir();
|
|
1249
1676
|
}
|
|
1250
1677
|
function getUserDataDir() {
|
|
1251
1678
|
return userDataDir();
|
|
1252
1679
|
}
|
|
1253
1680
|
function storeFile() {
|
|
1254
|
-
return
|
|
1681
|
+
return import_node_path3.default.join(userDataDir(), "attached-projects.json");
|
|
1255
1682
|
}
|
|
1256
1683
|
function cloneEmpty() {
|
|
1257
1684
|
return {
|
|
@@ -1265,7 +1692,7 @@ function loadStore() {
|
|
|
1265
1692
|
const file = storeFile();
|
|
1266
1693
|
let raw;
|
|
1267
1694
|
try {
|
|
1268
|
-
raw =
|
|
1695
|
+
raw = import_node_fs3.default.readFileSync(file, "utf8");
|
|
1269
1696
|
} catch (err) {
|
|
1270
1697
|
if (err.code === "ENOENT") return cloneEmpty();
|
|
1271
1698
|
throw err;
|
|
@@ -1282,7 +1709,7 @@ function loadStore() {
|
|
|
1282
1709
|
} catch (err) {
|
|
1283
1710
|
const quarantine = `${file}.corrupt-${Date.now()}`;
|
|
1284
1711
|
try {
|
|
1285
|
-
|
|
1712
|
+
import_node_fs3.default.renameSync(file, quarantine);
|
|
1286
1713
|
console.warn(
|
|
1287
1714
|
`[sootsim] attached-projects.json was unparseable; quarantined to ${quarantine}. original error: ${err.message}`
|
|
1288
1715
|
);
|
|
@@ -1293,16 +1720,16 @@ function loadStore() {
|
|
|
1293
1720
|
}
|
|
1294
1721
|
function writeStore(store) {
|
|
1295
1722
|
const file = storeFile();
|
|
1296
|
-
|
|
1723
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
1297
1724
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
1298
|
-
const fd =
|
|
1725
|
+
const fd = import_node_fs3.default.openSync(tmp, "w", 384);
|
|
1299
1726
|
try {
|
|
1300
|
-
|
|
1301
|
-
|
|
1727
|
+
import_node_fs3.default.writeFileSync(fd, JSON.stringify(store, null, 2));
|
|
1728
|
+
import_node_fs3.default.fsyncSync(fd);
|
|
1302
1729
|
} finally {
|
|
1303
|
-
|
|
1730
|
+
import_node_fs3.default.closeSync(fd);
|
|
1304
1731
|
}
|
|
1305
|
-
|
|
1732
|
+
import_node_fs3.default.renameSync(tmp, file);
|
|
1306
1733
|
}
|
|
1307
1734
|
function mutateStore(fn) {
|
|
1308
1735
|
const store = loadStore();
|
|
@@ -1311,13 +1738,13 @@ function mutateStore(fn) {
|
|
|
1311
1738
|
return store;
|
|
1312
1739
|
}
|
|
1313
1740
|
function projectIdForCwd(cwd) {
|
|
1314
|
-
return (0, import_node_crypto.createHash)("sha256").update(
|
|
1741
|
+
return (0, import_node_crypto.createHash)("sha256").update(import_node_path3.default.resolve(cwd)).digest("hex").slice(0, 16);
|
|
1315
1742
|
}
|
|
1316
1743
|
function newSessionId() {
|
|
1317
1744
|
return `s_${(0, import_node_crypto.randomBytes)(10).toString("hex")}`;
|
|
1318
1745
|
}
|
|
1319
1746
|
function upsertProject(input) {
|
|
1320
|
-
const cwd =
|
|
1747
|
+
const cwd = import_node_path3.default.resolve(input.cwd);
|
|
1321
1748
|
const id = projectIdForCwd(cwd);
|
|
1322
1749
|
let result;
|
|
1323
1750
|
mutateStore((store) => {
|
|
@@ -1343,7 +1770,7 @@ function upsertProject(input) {
|
|
|
1343
1770
|
const now = Date.now();
|
|
1344
1771
|
const created = {
|
|
1345
1772
|
id,
|
|
1346
|
-
name: input.name ??
|
|
1773
|
+
name: input.name ?? import_node_path3.default.basename(cwd),
|
|
1347
1774
|
cwd,
|
|
1348
1775
|
repoRoot: input.repoRoot,
|
|
1349
1776
|
sourceRoots: input.sourceRoots ?? [cwd],
|
|
@@ -1483,8 +1910,8 @@ async function seedFromDemoAppRegistry() {
|
|
|
1483
1910
|
const apps = APPS2;
|
|
1484
1911
|
mutateStore((store) => {
|
|
1485
1912
|
for (const app of apps) {
|
|
1486
|
-
if (!
|
|
1487
|
-
const cwd =
|
|
1913
|
+
if (!import_node_fs3.default.existsSync(app.dir)) continue;
|
|
1914
|
+
const cwd = import_node_path3.default.resolve(app.dir);
|
|
1488
1915
|
const id = projectIdForCwd(cwd);
|
|
1489
1916
|
if (store.attachedProjects.some((p) => p.id === id)) continue;
|
|
1490
1917
|
const now = Date.now();
|
|
@@ -1508,16 +1935,16 @@ async function seedFromDemoAppRegistry() {
|
|
|
1508
1935
|
|
|
1509
1936
|
// src/agent-sessions.ts
|
|
1510
1937
|
function sessionDir(sessionId) {
|
|
1511
|
-
return
|
|
1938
|
+
return import_node_path4.default.join(getUserDataDir(), "sessions", sessionId);
|
|
1512
1939
|
}
|
|
1513
1940
|
function promptFifoPath(sessionId) {
|
|
1514
|
-
return
|
|
1941
|
+
return import_node_path4.default.join(sessionDir(sessionId), "prompt.in");
|
|
1515
1942
|
}
|
|
1516
1943
|
function eventsFifoPath(sessionId) {
|
|
1517
|
-
return
|
|
1944
|
+
return import_node_path4.default.join(sessionDir(sessionId), "events.out");
|
|
1518
1945
|
}
|
|
1519
1946
|
function transcriptPath(sessionId) {
|
|
1520
|
-
return
|
|
1947
|
+
return import_node_path4.default.join(getUserDataDir(), "transcripts", `${sessionId}.log`);
|
|
1521
1948
|
}
|
|
1522
1949
|
function pidIsAlive(pid, sessionId) {
|
|
1523
1950
|
if (!pid) return false;
|
|
@@ -1527,7 +1954,7 @@ function pidIsAlive(pid, sessionId) {
|
|
|
1527
1954
|
return false;
|
|
1528
1955
|
}
|
|
1529
1956
|
if (sessionId) {
|
|
1530
|
-
if (!
|
|
1957
|
+
if (!import_node_fs4.default.existsSync(sessionDir(sessionId))) return false;
|
|
1531
1958
|
}
|
|
1532
1959
|
return true;
|
|
1533
1960
|
}
|
|
@@ -1539,11 +1966,11 @@ function resolveSootsimInvocation() {
|
|
|
1539
1966
|
const resourcesPath = process.resourcesPath;
|
|
1540
1967
|
if (resourcesPath) {
|
|
1541
1968
|
const candidates = [
|
|
1542
|
-
|
|
1543
|
-
|
|
1969
|
+
import_node_path4.default.join(resourcesPath, "bin", "sootsim"),
|
|
1970
|
+
import_node_path4.default.join(resourcesPath, "bin", `sootsim-${process.platform}-${process.arch}`)
|
|
1544
1971
|
];
|
|
1545
1972
|
for (const c of candidates) {
|
|
1546
|
-
if (
|
|
1973
|
+
if (import_node_fs4.default.existsSync(c)) return { cmd: c, prefixArgs: [] };
|
|
1547
1974
|
}
|
|
1548
1975
|
}
|
|
1549
1976
|
}
|
|
@@ -1566,15 +1993,15 @@ function tryWorkspaceSootsim() {
|
|
|
1566
1993
|
const sootsimDir = resolveSootsimPackageDir();
|
|
1567
1994
|
if (!sootsimDir) return null;
|
|
1568
1995
|
const binaryName = `sootsim-${process.platform}-${process.arch}`;
|
|
1569
|
-
const distBinary =
|
|
1570
|
-
if (
|
|
1571
|
-
const distBin =
|
|
1572
|
-
if (
|
|
1996
|
+
const distBinary = import_node_path4.default.join(sootsimDir, "dist-bin", binaryName);
|
|
1997
|
+
if (import_node_fs4.default.existsSync(distBinary)) return { cmd: distBinary, prefixArgs: [] };
|
|
1998
|
+
const distBin = import_node_path4.default.join(sootsimDir, "dist-cli", "bin.js");
|
|
1999
|
+
if (import_node_fs4.default.existsSync(distBin)) {
|
|
1573
2000
|
try {
|
|
1574
|
-
const src =
|
|
1575
|
-
if (
|
|
1576
|
-
const srcMtime =
|
|
1577
|
-
const buildMtime =
|
|
2001
|
+
const src = import_node_path4.default.join(sootsimDir, "cli", "commands", "agent-wrapper.ts");
|
|
2002
|
+
if (import_node_fs4.default.existsSync(src)) {
|
|
2003
|
+
const srcMtime = import_node_fs4.default.statSync(src).mtimeMs;
|
|
2004
|
+
const buildMtime = import_node_fs4.default.statSync(distBin).mtimeMs;
|
|
1578
2005
|
if (buildMtime < srcMtime) {
|
|
1579
2006
|
console.warn(
|
|
1580
2007
|
`[sootsim] dist-cli/bin.js is older than agent-wrapper.ts \u2014 rebuild with \`bun run --cwd packages/sootsim build:cli\` (watch:cli:binary builds dist-bin/ instead).`
|
|
@@ -1593,22 +2020,22 @@ function tryWorkspaceSootsim() {
|
|
|
1593
2020
|
function resolveSootsimPackageDir() {
|
|
1594
2021
|
try {
|
|
1595
2022
|
const resolved = require.resolve("sootsim/package.json");
|
|
1596
|
-
return
|
|
2023
|
+
return import_node_path4.default.dirname(resolved);
|
|
1597
2024
|
} catch {
|
|
1598
2025
|
}
|
|
1599
2026
|
const here = fileFromImportMeta();
|
|
1600
2027
|
if (!here) return null;
|
|
1601
|
-
let cur =
|
|
2028
|
+
let cur = import_node_path4.default.dirname(here);
|
|
1602
2029
|
for (let i = 0; i < 8; i++) {
|
|
1603
|
-
const pkg =
|
|
2030
|
+
const pkg = import_node_path4.default.join(cur, "package.json");
|
|
1604
2031
|
try {
|
|
1605
|
-
if (
|
|
1606
|
-
const parsed = JSON.parse(
|
|
2032
|
+
if (import_node_fs4.default.existsSync(pkg)) {
|
|
2033
|
+
const parsed = JSON.parse(import_node_fs4.default.readFileSync(pkg, "utf8"));
|
|
1607
2034
|
if (parsed.name === "sootsim") return cur;
|
|
1608
2035
|
}
|
|
1609
2036
|
} catch {
|
|
1610
2037
|
}
|
|
1611
|
-
const parent =
|
|
2038
|
+
const parent = import_node_path4.default.dirname(cur);
|
|
1612
2039
|
if (parent === cur) break;
|
|
1613
2040
|
cur = parent;
|
|
1614
2041
|
}
|
|
@@ -1624,28 +2051,28 @@ function fileFromImportMeta() {
|
|
|
1624
2051
|
}
|
|
1625
2052
|
}
|
|
1626
2053
|
async function withStartLock(projectId, provider, fn) {
|
|
1627
|
-
const lockDir =
|
|
1628
|
-
|
|
2054
|
+
const lockDir = import_node_path4.default.join(getUserDataDir(), "locks");
|
|
2055
|
+
import_node_fs4.default.mkdirSync(lockDir, { recursive: true });
|
|
1629
2056
|
try {
|
|
1630
|
-
|
|
2057
|
+
import_node_fs4.default.chmodSync(lockDir, 448);
|
|
1631
2058
|
} catch {
|
|
1632
2059
|
}
|
|
1633
|
-
const lockPath =
|
|
2060
|
+
const lockPath = import_node_path4.default.join(lockDir, `start-${projectId}-${provider}.lock`);
|
|
1634
2061
|
const deadline = Date.now() + 4e3;
|
|
1635
2062
|
let fd = null;
|
|
1636
2063
|
while (fd === null) {
|
|
1637
2064
|
try {
|
|
1638
|
-
fd =
|
|
2065
|
+
fd = import_node_fs4.default.openSync(
|
|
1639
2066
|
lockPath,
|
|
1640
|
-
|
|
2067
|
+
import_node_fs4.constants.O_WRONLY | import_node_fs4.constants.O_CREAT | import_node_fs4.constants.O_EXCL,
|
|
1641
2068
|
384
|
|
1642
2069
|
);
|
|
1643
2070
|
} catch (err) {
|
|
1644
2071
|
if (err.code !== "EEXIST") throw err;
|
|
1645
2072
|
try {
|
|
1646
|
-
const stale = Number(
|
|
2073
|
+
const stale = Number(import_node_fs4.default.readFileSync(lockPath, "utf8").trim());
|
|
1647
2074
|
if (stale && !isProcessAlive(stale)) {
|
|
1648
|
-
|
|
2075
|
+
import_node_fs4.default.unlinkSync(lockPath);
|
|
1649
2076
|
continue;
|
|
1650
2077
|
}
|
|
1651
2078
|
} catch {
|
|
@@ -1659,15 +2086,15 @@ async function withStartLock(projectId, provider, fn) {
|
|
|
1659
2086
|
}
|
|
1660
2087
|
}
|
|
1661
2088
|
try {
|
|
1662
|
-
|
|
2089
|
+
import_node_fs4.default.writeFileSync(fd, String(process.pid));
|
|
1663
2090
|
return await fn();
|
|
1664
2091
|
} finally {
|
|
1665
2092
|
try {
|
|
1666
|
-
|
|
2093
|
+
import_node_fs4.default.closeSync(fd);
|
|
1667
2094
|
} catch {
|
|
1668
2095
|
}
|
|
1669
2096
|
try {
|
|
1670
|
-
|
|
2097
|
+
import_node_fs4.default.unlinkSync(lockPath);
|
|
1671
2098
|
} catch {
|
|
1672
2099
|
}
|
|
1673
2100
|
}
|
|
@@ -1681,25 +2108,25 @@ function isProcessAlive(pid) {
|
|
|
1681
2108
|
}
|
|
1682
2109
|
}
|
|
1683
2110
|
function mkfifoSync(p) {
|
|
1684
|
-
const parent =
|
|
1685
|
-
|
|
2111
|
+
const parent = import_node_path4.default.dirname(p);
|
|
2112
|
+
import_node_fs4.default.mkdirSync(parent, { recursive: true });
|
|
1686
2113
|
try {
|
|
1687
|
-
|
|
2114
|
+
import_node_fs4.default.chmodSync(parent, 448);
|
|
1688
2115
|
} catch {
|
|
1689
2116
|
}
|
|
1690
|
-
if (
|
|
2117
|
+
if (import_node_fs4.default.existsSync(p)) {
|
|
1691
2118
|
try {
|
|
1692
|
-
const stat =
|
|
2119
|
+
const stat = import_node_fs4.default.statSync(p);
|
|
1693
2120
|
if (stat.isFIFO()) {
|
|
1694
2121
|
try {
|
|
1695
|
-
|
|
2122
|
+
import_node_fs4.default.chmodSync(p, 384);
|
|
1696
2123
|
} catch {
|
|
1697
2124
|
}
|
|
1698
2125
|
return;
|
|
1699
2126
|
}
|
|
1700
|
-
|
|
2127
|
+
import_node_fs4.default.unlinkSync(p);
|
|
1701
2128
|
} catch {
|
|
1702
|
-
|
|
2129
|
+
import_node_fs4.default.unlinkSync(p);
|
|
1703
2130
|
}
|
|
1704
2131
|
}
|
|
1705
2132
|
const result = (0, import_node_child_process.spawnSync)("mkfifo", ["-m", "600", p]);
|
|
@@ -1746,10 +2173,10 @@ async function startSession(opts) {
|
|
|
1746
2173
|
const transcript = transcriptPath(session.id);
|
|
1747
2174
|
mkfifoSync(promptIn);
|
|
1748
2175
|
mkfifoSync(eventsOut);
|
|
1749
|
-
const transcriptDir =
|
|
1750
|
-
|
|
2176
|
+
const transcriptDir = import_node_path4.default.dirname(transcript);
|
|
2177
|
+
import_node_fs4.default.mkdirSync(transcriptDir, { recursive: true });
|
|
1751
2178
|
try {
|
|
1752
|
-
|
|
2179
|
+
import_node_fs4.default.chmodSync(transcriptDir, 448);
|
|
1753
2180
|
} catch {
|
|
1754
2181
|
}
|
|
1755
2182
|
const { cmd, prefixArgs } = resolveSootsimInvocation();
|
|
@@ -1800,7 +2227,7 @@ async function startSession(opts) {
|
|
|
1800
2227
|
}
|
|
1801
2228
|
}
|
|
1802
2229
|
try {
|
|
1803
|
-
|
|
2230
|
+
import_node_fs4.default.rmSync(sessionDir(session.id), { recursive: true, force: true });
|
|
1804
2231
|
} catch {
|
|
1805
2232
|
}
|
|
1806
2233
|
updateSessionStatus(session.id, { status: "ended" });
|
|
@@ -1828,18 +2255,18 @@ async function sendPrompt(sessionId, prompt) {
|
|
|
1828
2255
|
);
|
|
1829
2256
|
}
|
|
1830
2257
|
const fifo = promptFifoPath(sessionId);
|
|
1831
|
-
if (!
|
|
2258
|
+
if (!import_node_fs4.default.existsSync(fifo)) {
|
|
1832
2259
|
throw new AgentSessionError("NO_FIFO", `prompt FIFO missing: ${fifo}`);
|
|
1833
2260
|
}
|
|
1834
|
-
const fd =
|
|
2261
|
+
const fd = import_node_fs4.default.openSync(fifo, import_node_fs4.constants.O_WRONLY);
|
|
1835
2262
|
try {
|
|
1836
2263
|
const wireText = encodeAgentPromptEnvelope(prompt);
|
|
1837
2264
|
if (!wireText) {
|
|
1838
2265
|
throw new AgentSessionError("EMPTY_PROMPT", "prompt text is empty");
|
|
1839
2266
|
}
|
|
1840
|
-
|
|
2267
|
+
import_node_fs4.default.writeSync(fd, wireText + "\n");
|
|
1841
2268
|
} finally {
|
|
1842
|
-
|
|
2269
|
+
import_node_fs4.default.closeSync(fd);
|
|
1843
2270
|
}
|
|
1844
2271
|
updateSessionStatus(sessionId, {
|
|
1845
2272
|
lastPrompt: prompt.displayText ?? prompt.text,
|
|
@@ -1861,7 +2288,7 @@ async function endSession(sessionId) {
|
|
|
1861
2288
|
const base = getUserDataDir();
|
|
1862
2289
|
if (dir.startsWith(base)) {
|
|
1863
2290
|
try {
|
|
1864
|
-
|
|
2291
|
+
import_node_fs4.default.rmSync(dir, { recursive: true, force: true });
|
|
1865
2292
|
} catch {
|
|
1866
2293
|
}
|
|
1867
2294
|
}
|
|
@@ -1869,11 +2296,11 @@ async function endSession(sessionId) {
|
|
|
1869
2296
|
}
|
|
1870
2297
|
function subscribeEvents(sessionId, onEvent) {
|
|
1871
2298
|
const fifo = eventsFifoPath(sessionId);
|
|
1872
|
-
if (!
|
|
2299
|
+
if (!import_node_fs4.default.existsSync(fifo)) {
|
|
1873
2300
|
throw new AgentSessionError("NO_FIFO", `events FIFO missing: ${fifo}`);
|
|
1874
2301
|
}
|
|
1875
|
-
const fd =
|
|
1876
|
-
const stream =
|
|
2302
|
+
const fd = import_node_fs4.default.openSync(fifo, import_node_fs4.constants.O_RDWR);
|
|
2303
|
+
const stream = import_node_fs4.default.createReadStream("", { fd, autoClose: true });
|
|
1877
2304
|
const rl = import_node_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1878
2305
|
rl.on("line", (line) => {
|
|
1879
2306
|
const event = parseAgentEventLine(line);
|
|
@@ -1894,7 +2321,7 @@ function subscribeEvents(sessionId, onEvent) {
|
|
|
1894
2321
|
};
|
|
1895
2322
|
}
|
|
1896
2323
|
async function waitForFirstEvent(fifo, predicate, timeoutMs) {
|
|
1897
|
-
const fd =
|
|
2324
|
+
const fd = import_node_fs4.default.openSync(fifo, import_node_fs4.constants.O_RDWR | import_node_fs4.constants.O_NONBLOCK);
|
|
1898
2325
|
const buf = Buffer.alloc(8192);
|
|
1899
2326
|
let leftover = "";
|
|
1900
2327
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -1902,7 +2329,7 @@ async function waitForFirstEvent(fifo, predicate, timeoutMs) {
|
|
|
1902
2329
|
while (Date.now() < deadline) {
|
|
1903
2330
|
let n = 0;
|
|
1904
2331
|
try {
|
|
1905
|
-
n =
|
|
2332
|
+
n = import_node_fs4.default.readSync(fd, buf, 0, buf.length, null);
|
|
1906
2333
|
} catch (err) {
|
|
1907
2334
|
if (err.code !== "EAGAIN") throw err;
|
|
1908
2335
|
n = 0;
|
|
@@ -1922,7 +2349,7 @@ async function waitForFirstEvent(fifo, predicate, timeoutMs) {
|
|
|
1922
2349
|
}
|
|
1923
2350
|
return null;
|
|
1924
2351
|
} finally {
|
|
1925
|
-
|
|
2352
|
+
import_node_fs4.default.closeSync(fd);
|
|
1926
2353
|
}
|
|
1927
2354
|
}
|
|
1928
2355
|
|
|
@@ -2093,7 +2520,7 @@ var AgentHost = class {
|
|
|
2093
2520
|
);
|
|
2094
2521
|
const project = upsertProject({
|
|
2095
2522
|
cwd: match.cwd,
|
|
2096
|
-
name: match.projectName ??
|
|
2523
|
+
name: match.projectName ?? import_node_path5.default.basename(match.cwd),
|
|
2097
2524
|
preferredProvider: input.provider ?? existing?.preferredProvider,
|
|
2098
2525
|
sourceRoots: existing?.sourceRoots ?? [match.cwd],
|
|
2099
2526
|
knownBundleUrls,
|
|
@@ -2104,18 +2531,18 @@ var AgentHost = class {
|
|
|
2104
2531
|
}
|
|
2105
2532
|
getTranscript(sessionId) {
|
|
2106
2533
|
const p = transcriptPath(sessionId);
|
|
2107
|
-
if (!
|
|
2534
|
+
if (!import_node_fs5.default.existsSync(p)) {
|
|
2108
2535
|
return { error: "transcript not found", code: "NO_TRANSCRIPT" };
|
|
2109
2536
|
}
|
|
2110
|
-
return
|
|
2537
|
+
return import_node_fs5.default.readFileSync(p, "utf8");
|
|
2111
2538
|
}
|
|
2112
2539
|
getPaths() {
|
|
2113
2540
|
const dir = getUserDataDir();
|
|
2114
2541
|
return {
|
|
2115
2542
|
userDataDir: dir,
|
|
2116
|
-
storeFile:
|
|
2117
|
-
sessionsDir:
|
|
2118
|
-
transcriptsDir:
|
|
2543
|
+
storeFile: import_node_path5.default.join(dir, "attached-projects.json"),
|
|
2544
|
+
sessionsDir: import_node_path5.default.join(dir, "sessions"),
|
|
2545
|
+
transcriptsDir: import_node_path5.default.join(dir, "transcripts")
|
|
2119
2546
|
};
|
|
2120
2547
|
}
|
|
2121
2548
|
// --- subscription management ---
|
|
@@ -2375,11 +2802,212 @@ function mapFrameworkToProjectFramework(fw) {
|
|
|
2375
2802
|
return "unknown";
|
|
2376
2803
|
}
|
|
2377
2804
|
|
|
2805
|
+
// src/host/fetch-proxy-handler.ts
|
|
2806
|
+
var import_http2 = __toESM(require("http"), 1);
|
|
2807
|
+
var import_https = __toESM(require("https"), 1);
|
|
2808
|
+
var FETCH_PROXY_USER_AGENT = "sootsim";
|
|
2809
|
+
var STRIP_FETCH_PROXY_HEADERS = /* @__PURE__ */ new Set([
|
|
2810
|
+
"host",
|
|
2811
|
+
"origin",
|
|
2812
|
+
"referer",
|
|
2813
|
+
"user-agent",
|
|
2814
|
+
"cookie",
|
|
2815
|
+
"connection",
|
|
2816
|
+
"keep-alive",
|
|
2817
|
+
"transfer-encoding",
|
|
2818
|
+
"upgrade",
|
|
2819
|
+
"content-length",
|
|
2820
|
+
"sec-fetch-site",
|
|
2821
|
+
"sec-fetch-mode",
|
|
2822
|
+
"sec-fetch-dest",
|
|
2823
|
+
"sec-ch-ua",
|
|
2824
|
+
"sec-ch-ua-mobile",
|
|
2825
|
+
"sec-ch-ua-platform"
|
|
2826
|
+
]);
|
|
2827
|
+
var FETCH_PROXY_CORS_HEADERS = {
|
|
2828
|
+
"access-control-allow-origin": "*",
|
|
2829
|
+
"access-control-allow-methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS",
|
|
2830
|
+
"access-control-allow-headers": "*",
|
|
2831
|
+
"access-control-expose-headers": "*",
|
|
2832
|
+
"access-control-max-age": "3600"
|
|
2833
|
+
};
|
|
2834
|
+
function applyFetchProxyCors(res) {
|
|
2835
|
+
for (const [key, value] of Object.entries(FETCH_PROXY_CORS_HEADERS)) {
|
|
2836
|
+
res.setHeader(key, value);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
function formatFetchProxyError(targetUrl, err) {
|
|
2840
|
+
const details = [];
|
|
2841
|
+
const error = err;
|
|
2842
|
+
if (error?.code) details.push(error.code);
|
|
2843
|
+
if (error?.message) details.push(error.message);
|
|
2844
|
+
if (error?.cause?.code) details.push(error.cause.code);
|
|
2845
|
+
if (error?.cause?.message) details.push(error.cause.message);
|
|
2846
|
+
const uniqueDetails = [...new Set(details.filter(Boolean))];
|
|
2847
|
+
const message = uniqueDetails.join(" | ") || String(err);
|
|
2848
|
+
if (targetUrl.includes("stored-in-.env.local")) {
|
|
2849
|
+
return `${message} | upstream url still contains placeholder env values`;
|
|
2850
|
+
}
|
|
2851
|
+
return message;
|
|
2852
|
+
}
|
|
2853
|
+
function buildFetchProxyHeaders(reqHeaders) {
|
|
2854
|
+
const headers = {};
|
|
2855
|
+
for (const [key, value] of Object.entries(reqHeaders)) {
|
|
2856
|
+
if (!value) continue;
|
|
2857
|
+
if (STRIP_FETCH_PROXY_HEADERS.has(key.toLowerCase())) continue;
|
|
2858
|
+
headers[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
2859
|
+
}
|
|
2860
|
+
headers["user-agent"] = FETCH_PROXY_USER_AGENT;
|
|
2861
|
+
return headers;
|
|
2862
|
+
}
|
|
2863
|
+
function isFetchProxyRequestUrl(rawUrl) {
|
|
2864
|
+
return rawUrl?.startsWith("/__fetch-proxy?") || rawUrl?.startsWith("/__proxy?") || false;
|
|
2865
|
+
}
|
|
2866
|
+
function isAppApiRequestUrl(rawUrl) {
|
|
2867
|
+
if (!rawUrl) return false;
|
|
2868
|
+
if (rawUrl.startsWith("/__app-api?")) return true;
|
|
2869
|
+
if (rawUrl.startsWith("/__app-api/")) return true;
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
async function handleFetchProxyRequest(req, res) {
|
|
2873
|
+
if (req.method === "OPTIONS") {
|
|
2874
|
+
applyFetchProxyCors(res);
|
|
2875
|
+
res.writeHead(204);
|
|
2876
|
+
res.end();
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const params = new URLSearchParams((req.url || "").split("?")[1] || "");
|
|
2880
|
+
const targetUrl = params.get("url");
|
|
2881
|
+
if (!targetUrl) {
|
|
2882
|
+
applyFetchProxyCors(res);
|
|
2883
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
2884
|
+
res.end("missing url param");
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
let upstreamUrl;
|
|
2888
|
+
try {
|
|
2889
|
+
upstreamUrl = new URL(targetUrl);
|
|
2890
|
+
} catch {
|
|
2891
|
+
applyFetchProxyCors(res);
|
|
2892
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
2893
|
+
res.end("invalid url param");
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
const headers = buildFetchProxyHeaders(req.headers);
|
|
2897
|
+
let body;
|
|
2898
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
2899
|
+
const chunks = [];
|
|
2900
|
+
for await (const chunk of req) {
|
|
2901
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2902
|
+
}
|
|
2903
|
+
if (chunks.length > 0) body = Buffer.concat(chunks);
|
|
2904
|
+
}
|
|
2905
|
+
let upstream;
|
|
2906
|
+
try {
|
|
2907
|
+
upstream = await fetch(upstreamUrl.href, {
|
|
2908
|
+
method: req.method,
|
|
2909
|
+
headers,
|
|
2910
|
+
body,
|
|
2911
|
+
redirect: "follow"
|
|
2912
|
+
});
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
applyFetchProxyCors(res);
|
|
2915
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
2916
|
+
res.end(`fetch proxy error: ${formatFetchProxyError(upstreamUrl.href, err)}`);
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
upstream.headers.forEach((value, key) => {
|
|
2920
|
+
const lowerKey = key.toLowerCase();
|
|
2921
|
+
if (lowerKey === "content-encoding" || lowerKey === "transfer-encoding" || lowerKey === "content-length" || lowerKey === "set-cookie" || lowerKey.startsWith("access-control-")) {
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
res.setHeader(key, value);
|
|
2925
|
+
});
|
|
2926
|
+
applyFetchProxyCors(res);
|
|
2927
|
+
const setCookie = upstream.headers.getSetCookie?.() ?? [];
|
|
2928
|
+
if (setCookie.length > 0) {
|
|
2929
|
+
res.setHeader("x-sootsim-set-cookie", setCookie.join(", "));
|
|
2930
|
+
}
|
|
2931
|
+
res.statusCode = upstream.status;
|
|
2932
|
+
const buf = Buffer.from(await upstream.arrayBuffer());
|
|
2933
|
+
res.end(buf);
|
|
2934
|
+
}
|
|
2935
|
+
function handleAppApiRequest(req, res) {
|
|
2936
|
+
const reqUrl = req.url || "";
|
|
2937
|
+
let targetPath = "";
|
|
2938
|
+
let targetOrigin = "";
|
|
2939
|
+
if (reqUrl.startsWith("/__app-api?")) {
|
|
2940
|
+
const parsed = new URL(reqUrl, "http://sootsim.local");
|
|
2941
|
+
targetPath = parsed.searchParams.get("path") || "";
|
|
2942
|
+
targetOrigin = parsed.searchParams.get("origin")?.trim() || "";
|
|
2943
|
+
} else if (reqUrl.startsWith("/__app-api/")) {
|
|
2944
|
+
targetPath = reqUrl.slice("/__app-api".length);
|
|
2945
|
+
} else {
|
|
2946
|
+
return false;
|
|
2947
|
+
}
|
|
2948
|
+
if (!targetOrigin) {
|
|
2949
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
2950
|
+
res.end("app-api: missing origin query param");
|
|
2951
|
+
return true;
|
|
2952
|
+
}
|
|
2953
|
+
if (req.method === "OPTIONS") {
|
|
2954
|
+
res.writeHead(204, {
|
|
2955
|
+
"Access-Control-Allow-Origin": req.headers.origin || "*",
|
|
2956
|
+
"Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
2957
|
+
"Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "*",
|
|
2958
|
+
"Access-Control-Allow-Credentials": "true",
|
|
2959
|
+
"Access-Control-Max-Age": "86400"
|
|
2960
|
+
});
|
|
2961
|
+
res.end();
|
|
2962
|
+
return true;
|
|
2963
|
+
}
|
|
2964
|
+
let targetUrl;
|
|
2965
|
+
try {
|
|
2966
|
+
targetUrl = new URL(targetPath, targetOrigin);
|
|
2967
|
+
} catch {
|
|
2968
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
2969
|
+
res.end("app-api: invalid origin or path");
|
|
2970
|
+
return true;
|
|
2971
|
+
}
|
|
2972
|
+
const transport = targetUrl.protocol === "https:" ? import_https.default : import_http2.default;
|
|
2973
|
+
const fwdHeaders = { ...req.headers };
|
|
2974
|
+
delete fwdHeaders.host;
|
|
2975
|
+
fwdHeaders.host = targetUrl.host;
|
|
2976
|
+
const proxyReq = transport.request(
|
|
2977
|
+
{
|
|
2978
|
+
hostname: targetUrl.hostname,
|
|
2979
|
+
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
|
|
2980
|
+
path: targetUrl.pathname + targetUrl.search,
|
|
2981
|
+
method: req.method,
|
|
2982
|
+
headers: fwdHeaders
|
|
2983
|
+
},
|
|
2984
|
+
(proxyRes) => {
|
|
2985
|
+
const exposedHeaders = Object.keys(proxyRes.headers).filter((name) => {
|
|
2986
|
+
const lower = name.toLowerCase();
|
|
2987
|
+
return !lower.startsWith("access-control-") && lower !== "set-cookie";
|
|
2988
|
+
}).join(", ");
|
|
2989
|
+
res.writeHead(proxyRes.statusCode ?? 502, {
|
|
2990
|
+
...proxyRes.headers,
|
|
2991
|
+
"access-control-allow-origin": req.headers.origin || "*",
|
|
2992
|
+
"access-control-allow-credentials": "true",
|
|
2993
|
+
"access-control-expose-headers": exposedHeaders
|
|
2994
|
+
});
|
|
2995
|
+
proxyRes.pipe(res);
|
|
2996
|
+
}
|
|
2997
|
+
);
|
|
2998
|
+
proxyReq.on("error", (err) => {
|
|
2999
|
+
res.statusCode = 502;
|
|
3000
|
+
res.end(`app proxy error: ${err.message}`);
|
|
3001
|
+
});
|
|
3002
|
+
req.pipe(proxyReq);
|
|
3003
|
+
return true;
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2378
3006
|
// src/host/open-url.ts
|
|
2379
3007
|
var import_child_process3 = require("child_process");
|
|
2380
|
-
var
|
|
2381
|
-
var
|
|
2382
|
-
var
|
|
3008
|
+
var import_fs2 = require("fs");
|
|
3009
|
+
var import_os = require("os");
|
|
3010
|
+
var import_path2 = require("path");
|
|
2383
3011
|
var MAC_CHROMIUM_CANDIDATES = [
|
|
2384
3012
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
2385
3013
|
"~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -2417,12 +3045,12 @@ var UNIX_CHROMIUM_COMMANDS = [
|
|
|
2417
3045
|
];
|
|
2418
3046
|
var WINDOWS_CHROMIUM_COMMANDS = ["chrome", "msedge", "brave"];
|
|
2419
3047
|
function expandHome(candidate) {
|
|
2420
|
-
return candidate.startsWith("~/") ? (0,
|
|
3048
|
+
return candidate.startsWith("~/") ? (0, import_path2.join)((0, import_os.homedir)(), candidate.slice(2)) : candidate;
|
|
2421
3049
|
}
|
|
2422
3050
|
function firstExisting(candidates) {
|
|
2423
3051
|
for (const candidate of candidates) {
|
|
2424
3052
|
const expanded = expandHome(candidate);
|
|
2425
|
-
if ((0,
|
|
3053
|
+
if ((0, import_fs2.existsSync)(expanded)) return expanded;
|
|
2426
3054
|
}
|
|
2427
3055
|
return null;
|
|
2428
3056
|
}
|
|
@@ -2441,7 +3069,7 @@ function lookupExecutable(command, platform) {
|
|
|
2441
3069
|
}
|
|
2442
3070
|
function resolveChromiumBinary(platform = process.platform) {
|
|
2443
3071
|
const envCandidate = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
|
|
2444
|
-
if (envCandidate && (0,
|
|
3072
|
+
if (envCandidate && (0, import_fs2.existsSync)(envCandidate)) return envCandidate;
|
|
2445
3073
|
const direct = platform === "darwin" ? firstExisting(MAC_CHROMIUM_CANDIDATES) : platform === "win32" ? firstExisting(WINDOWS_CHROMIUM_CANDIDATES) : firstExisting(LINUX_CHROMIUM_CANDIDATES);
|
|
2446
3074
|
if (direct) return direct;
|
|
2447
3075
|
const commands = platform === "win32" ? WINDOWS_CHROMIUM_COMMANDS : UNIX_CHROMIUM_COMMANDS;
|
|
@@ -2558,43 +3186,75 @@ var HTTP_MIME_TYPES = {
|
|
|
2558
3186
|
".map": "application/json",
|
|
2559
3187
|
".txt": "text/plain; charset=utf-8"
|
|
2560
3188
|
};
|
|
3189
|
+
function injectSharedConfigIntoHtml(data) {
|
|
3190
|
+
let payload;
|
|
3191
|
+
try {
|
|
3192
|
+
const cfg = readSharedConfig();
|
|
3193
|
+
payload = JSON.stringify(cfg);
|
|
3194
|
+
} catch {
|
|
3195
|
+
payload = "{}";
|
|
3196
|
+
}
|
|
3197
|
+
const tag = `<script>window.__sootsimSharedConfig=${payload};</script>`;
|
|
3198
|
+
const html = data.toString("utf8");
|
|
3199
|
+
if (html.includes("</head>")) return html.replace("</head>", tag + "</head>");
|
|
3200
|
+
if (html.includes("</body>")) return html.replace("</body>", tag + "</body>");
|
|
3201
|
+
return tag + html;
|
|
3202
|
+
}
|
|
2561
3203
|
var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
2562
3204
|
port;
|
|
2563
3205
|
openUrlHandler;
|
|
2564
3206
|
httpServer = null;
|
|
2565
3207
|
wss = null;
|
|
2566
3208
|
nextCommandId = 1;
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3209
|
+
nextSimNumber = 161;
|
|
3210
|
+
sims = /* @__PURE__ */ new Map();
|
|
3211
|
+
primarySimId = null;
|
|
2570
3212
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
2571
3213
|
cliBySentId = /* @__PURE__ */ new Map();
|
|
2572
|
-
|
|
3214
|
+
cliSimBySocket = /* @__PURE__ */ new Map();
|
|
2573
3215
|
cliLastCommandAt = /* @__PURE__ */ new Map();
|
|
2574
|
-
|
|
3216
|
+
cliIdentityKeyBySocket = /* @__PURE__ */ new Map();
|
|
2575
3217
|
cliLabelBySocket = /* @__PURE__ */ new Map();
|
|
2576
|
-
|
|
3218
|
+
restorableSims = /* @__PURE__ */ new Map();
|
|
2577
3219
|
nextCliFallbackId = 1;
|
|
2578
3220
|
cliIdleTimer = null;
|
|
2579
3221
|
agentHost;
|
|
2580
3222
|
static CLI_IDLE_TIMEOUT_MS = 6e4;
|
|
2581
3223
|
static CLI_LEASE_TTL_MS = 6e5;
|
|
2582
3224
|
static USER_ACTIVE_LEASE_TTL_MS = 8e3;
|
|
2583
|
-
// explicit user actions (clicking Boot, focusing the
|
|
2584
|
-
// hold the
|
|
3225
|
+
// explicit user actions (clicking Boot, focusing the sim to take it over)
|
|
3226
|
+
// hold the sim longer than passive canvas interaction so reconnecting clis
|
|
2585
3227
|
// can't immediately reclaim while the user gets oriented.
|
|
2586
3228
|
static USER_BOOT_LEASE_TTL_MS = 6e4;
|
|
2587
|
-
static
|
|
3229
|
+
static SIM_RECONNECT_TTL_MS = 3e4;
|
|
2588
3230
|
preferredPort;
|
|
2589
3231
|
portFallbackCount;
|
|
2590
3232
|
shouldWriteLockfile;
|
|
2591
3233
|
effectivePort = 0;
|
|
2592
3234
|
startedAt = 0;
|
|
2593
3235
|
heartbeatTimer = null;
|
|
3236
|
+
// ws-level heartbeat: sims that hang up uncleanly (page navigated, network
|
|
3237
|
+
// dropped, sim crashed) leave their server-side WebSocket sitting "open"
|
|
3238
|
+
// forever. ping every WS_HEARTBEAT_INTERVAL_MS; if the previous round's
|
|
3239
|
+
// ping was never answered, terminate(). that fires 'close' which runs the
|
|
3240
|
+
// sim-cleanup path and stops `sootsim list` from showing 8 zombie
|
|
3241
|
+
// sims that all time out on every command.
|
|
3242
|
+
wsHeartbeatTimer = null;
|
|
3243
|
+
wsIsAlive = /* @__PURE__ */ new WeakMap();
|
|
3244
|
+
static WS_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2594
3245
|
runtimeUpdateTimer = null;
|
|
2595
3246
|
runtimeUpdateInFlight = null;
|
|
2596
3247
|
activeRuntimeVersion = null;
|
|
2597
3248
|
activeRuntimeDirPath = null;
|
|
3249
|
+
// /__server-scan cache. mirrors the shell vite dev-middleware so engine
|
|
3250
|
+
// ConnectRN / DemoConnectApp see the same JSON shape whether they boot
|
|
3251
|
+
// from vite dev or from this daemon. without this, the SPA fallback below
|
|
3252
|
+
// would serve index.html for /__server-scan and tenant-worker .json()
|
|
3253
|
+
// crashes with "Unexpected token '<', "<!doctype "... is not valid JSON".
|
|
3254
|
+
scanCache = null;
|
|
3255
|
+
scanCacheAt = 0;
|
|
3256
|
+
inflightScan = null;
|
|
3257
|
+
static SCAN_FRESH_MS = 2e3;
|
|
2598
3258
|
constructor(opts = {}) {
|
|
2599
3259
|
this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
2600
3260
|
this.port = this.preferredPort;
|
|
@@ -2652,7 +3312,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2652
3312
|
}
|
|
2653
3313
|
bindOnce(port, _silent) {
|
|
2654
3314
|
return new Promise((resolve2, reject) => {
|
|
2655
|
-
const server = (0,
|
|
3315
|
+
const server = (0, import_http3.createServer)((req, res) => this.handleHttpRequest(req, res));
|
|
2656
3316
|
let settled = false;
|
|
2657
3317
|
const onError = (err) => {
|
|
2658
3318
|
if (settled) return;
|
|
@@ -2687,14 +3347,18 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2687
3347
|
if (!this.wss) return;
|
|
2688
3348
|
this.wss.on("connection", (ws, req) => {
|
|
2689
3349
|
const origin = req.headers.origin;
|
|
2690
|
-
const role = origin ? "
|
|
2691
|
-
let
|
|
3350
|
+
const role = origin ? "sim" : "cli";
|
|
3351
|
+
let sim = null;
|
|
2692
3352
|
ws.on("error", () => {
|
|
2693
3353
|
});
|
|
3354
|
+
this.wsIsAlive.set(ws, true);
|
|
3355
|
+
ws.on("pong", () => {
|
|
3356
|
+
this.wsIsAlive.set(ws, true);
|
|
3357
|
+
});
|
|
2694
3358
|
this.agentHost.registerSocket(ws);
|
|
2695
|
-
if (role === "
|
|
2696
|
-
|
|
2697
|
-
id:
|
|
3359
|
+
if (role === "sim") {
|
|
3360
|
+
sim = {
|
|
3361
|
+
id: this.allocateSimId(),
|
|
2698
3362
|
ws,
|
|
2699
3363
|
origin,
|
|
2700
3364
|
connectedAt: Date.now(),
|
|
@@ -2702,15 +3366,15 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2702
3366
|
lastActiveAt: 0,
|
|
2703
3367
|
recentActions: []
|
|
2704
3368
|
};
|
|
2705
|
-
this.
|
|
2706
|
-
if (this.
|
|
2707
|
-
this.
|
|
3369
|
+
this.sims.set(sim.id, sim);
|
|
3370
|
+
if (this.shouldPromoteSim(sim)) {
|
|
3371
|
+
this.primarySimId = sim.id;
|
|
2708
3372
|
}
|
|
2709
|
-
this.
|
|
2710
|
-
this.
|
|
3373
|
+
this.broadcastSimAssignments();
|
|
3374
|
+
this.broadcastSimClientStates();
|
|
2711
3375
|
} else {
|
|
2712
3376
|
const fallbackKey = `ws-${this.nextCliFallbackId++}`;
|
|
2713
|
-
this.
|
|
3377
|
+
this.cliIdentityKeyBySocket.set(ws, fallbackKey);
|
|
2714
3378
|
}
|
|
2715
3379
|
ws.on("message", (data) => {
|
|
2716
3380
|
let msg;
|
|
@@ -2785,29 +3449,55 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2785
3449
|
}
|
|
2786
3450
|
return;
|
|
2787
3451
|
}
|
|
2788
|
-
if (role === "
|
|
2789
|
-
if (
|
|
2790
|
-
|
|
3452
|
+
if (role === "sim") {
|
|
3453
|
+
if (sim) {
|
|
3454
|
+
sim.lastSeenAt = Date.now();
|
|
2791
3455
|
}
|
|
2792
|
-
if (msg.type === "bridge:register" &&
|
|
3456
|
+
if (msg.type === "bridge:register" && sim) {
|
|
2793
3457
|
const registration = msg;
|
|
2794
|
-
const restored = this.
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3458
|
+
const restored = this.tryRestoreSimId(sim, registration.simId);
|
|
3459
|
+
sim.url = registration.url;
|
|
3460
|
+
sim.title = registration.title;
|
|
3461
|
+
sim.userAgent = registration.userAgent;
|
|
2798
3462
|
if (restored) {
|
|
2799
|
-
this.
|
|
2800
|
-
this.
|
|
3463
|
+
this.broadcastSimAssignments();
|
|
3464
|
+
this.broadcastSimClientStates();
|
|
2801
3465
|
}
|
|
2802
3466
|
return;
|
|
2803
3467
|
}
|
|
2804
|
-
if (msg.type === "bridge:user-focus-state" &&
|
|
3468
|
+
if (msg.type === "bridge:user-focus-state" && sim) {
|
|
2805
3469
|
const focusState = msg;
|
|
2806
|
-
this.updateUserFocusLease(
|
|
3470
|
+
this.updateUserFocusLease(sim, focusState.focused === true);
|
|
2807
3471
|
return;
|
|
2808
3472
|
}
|
|
2809
|
-
if (msg.type === "bridge:user-interact" &&
|
|
2810
|
-
this.updateUserActivity(
|
|
3473
|
+
if (msg.type === "bridge:user-interact" && sim) {
|
|
3474
|
+
this.updateUserActivity(sim);
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
if (msg.type === "bridge:write-shared-config") {
|
|
3478
|
+
const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : null;
|
|
3479
|
+
if (!patch) return;
|
|
3480
|
+
let next;
|
|
3481
|
+
try {
|
|
3482
|
+
next = writeSharedConfig(patch);
|
|
3483
|
+
} catch (err) {
|
|
3484
|
+
process.stderr.write(
|
|
3485
|
+
`sootsim: bridge:write-shared-config failed: ${err instanceof Error ? err.message : String(err)}
|
|
3486
|
+
`
|
|
3487
|
+
);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
const payload = JSON.stringify({
|
|
3491
|
+
type: "bridge:shared-config-changed",
|
|
3492
|
+
config: next
|
|
3493
|
+
});
|
|
3494
|
+
for (const peer of this.sims.values()) {
|
|
3495
|
+
if (peer.ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
3496
|
+
try {
|
|
3497
|
+
peer.ws.send(payload);
|
|
3498
|
+
} catch {
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
2811
3501
|
return;
|
|
2812
3502
|
}
|
|
2813
3503
|
if (msg.type === "bridge:open-path") {
|
|
@@ -2819,33 +3509,33 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2819
3509
|
}
|
|
2820
3510
|
return;
|
|
2821
3511
|
}
|
|
2822
|
-
if (msg.type === "bridge:boot-clients" &&
|
|
3512
|
+
if (msg.type === "bridge:boot-clients" && sim) {
|
|
2823
3513
|
const booted = [];
|
|
2824
|
-
for (const [cliWs,
|
|
2825
|
-
if (
|
|
3514
|
+
for (const [cliWs, attachedSimId] of this.cliSimBySocket) {
|
|
3515
|
+
if (attachedSimId === sim.id) {
|
|
2826
3516
|
booted.push(cliWs);
|
|
2827
3517
|
}
|
|
2828
3518
|
}
|
|
2829
3519
|
for (const cliWs of booted) {
|
|
2830
|
-
this.
|
|
3520
|
+
this.cliSimBySocket.delete(cliWs);
|
|
2831
3521
|
try {
|
|
2832
|
-
cliWs.close(1e3, "booted by
|
|
3522
|
+
cliWs.close(1e3, "booted by sim");
|
|
2833
3523
|
} catch {
|
|
2834
3524
|
}
|
|
2835
3525
|
}
|
|
2836
|
-
const hadLease = !!
|
|
2837
|
-
|
|
3526
|
+
const hadLease = !!sim.cliLease;
|
|
3527
|
+
sim.cliLease = {
|
|
2838
3528
|
kind: "user-active",
|
|
2839
|
-
|
|
3529
|
+
cliIdentityKey: "__user-active__",
|
|
2840
3530
|
cliLabel: "active user",
|
|
2841
3531
|
expiresAt: Date.now() + _SootSimBridgeHost.USER_BOOT_LEASE_TTL_MS
|
|
2842
3532
|
};
|
|
2843
3533
|
process.stderr.write(
|
|
2844
|
-
`sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held
|
|
3534
|
+
`sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held sim for user [${sim.id}]
|
|
2845
3535
|
`
|
|
2846
3536
|
);
|
|
2847
|
-
this.
|
|
2848
|
-
this.
|
|
3537
|
+
this.recordSimAction(sim.id, "sim booted cli clients");
|
|
3538
|
+
this.broadcastSimClientStates();
|
|
2849
3539
|
return;
|
|
2850
3540
|
}
|
|
2851
3541
|
const internalPending = this.pendingCommands.get(msg.id);
|
|
@@ -2859,10 +3549,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2859
3549
|
if (entry) {
|
|
2860
3550
|
this.cliBySentId.delete(msg.id);
|
|
2861
3551
|
if (entry.ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2862
|
-
const otherCliCount = this.
|
|
2863
|
-
entry.ws,
|
|
2864
|
-
entry.browserId
|
|
2865
|
-
);
|
|
3552
|
+
const otherCliCount = this.getOtherCliIdentityCount(entry.ws, entry.simId);
|
|
2866
3553
|
const response = otherCliCount > 0 ? { ...msg, id: entry.originalId, _otherCliCount: otherCliCount } : { ...msg, id: entry.originalId };
|
|
2867
3554
|
entry.ws.send(JSON.stringify(response));
|
|
2868
3555
|
}
|
|
@@ -2873,19 +3560,19 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2873
3560
|
this.cliLastCommandAt.set(ws, Date.now());
|
|
2874
3561
|
try {
|
|
2875
3562
|
if (msg.type === "bridge:bye") {
|
|
2876
|
-
const
|
|
3563
|
+
const hadSim = this.cliSimBySocket.delete(ws);
|
|
2877
3564
|
this.cliLastCommandAt.delete(ws);
|
|
2878
|
-
this.
|
|
3565
|
+
this.cliIdentityKeyBySocket.delete(ws);
|
|
2879
3566
|
this.cliLabelBySocket.delete(ws);
|
|
2880
3567
|
for (const [sentId2, entry] of this.cliBySentId) {
|
|
2881
3568
|
if (entry.ws === ws) this.cliBySentId.delete(sentId2);
|
|
2882
3569
|
}
|
|
2883
|
-
if (
|
|
3570
|
+
if (hadSim) this.broadcastSimClientStates();
|
|
2884
3571
|
return;
|
|
2885
3572
|
}
|
|
2886
3573
|
if (msg.type === "bridge:hello") {
|
|
2887
|
-
const key = typeof msg.
|
|
2888
|
-
this.
|
|
3574
|
+
const key = typeof msg.cliIdentityKey === "string" && msg.cliIdentityKey.trim() ? msg.cliIdentityKey.trim() : this.cliIdentityKeyBySocket.get(ws) || `ws-${this.nextCliFallbackId++}`;
|
|
3575
|
+
this.cliIdentityKeyBySocket.set(ws, key);
|
|
2889
3576
|
if (typeof msg.cliLabel === "string" && msg.cliLabel.trim()) {
|
|
2890
3577
|
this.cliLabelBySocket.set(ws, msg.cliLabel.trim());
|
|
2891
3578
|
}
|
|
@@ -2894,7 +3581,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2894
3581
|
JSON.stringify({
|
|
2895
3582
|
id: msg.id,
|
|
2896
3583
|
result: {
|
|
2897
|
-
|
|
3584
|
+
cliIdentityKey: key,
|
|
2898
3585
|
leaseTtlMs: _SootSimBridgeHost.CLI_LEASE_TTL_MS,
|
|
2899
3586
|
leasing: true
|
|
2900
3587
|
}
|
|
@@ -2903,12 +3590,12 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2903
3590
|
}
|
|
2904
3591
|
return;
|
|
2905
3592
|
}
|
|
2906
|
-
if (msg.type === "bridge:list-
|
|
3593
|
+
if (msg.type === "bridge:list-sims") {
|
|
2907
3594
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2908
3595
|
ws.send(
|
|
2909
3596
|
JSON.stringify({
|
|
2910
3597
|
id: msg.id,
|
|
2911
|
-
result: this.
|
|
3598
|
+
result: this.listSims()
|
|
2912
3599
|
})
|
|
2913
3600
|
);
|
|
2914
3601
|
}
|
|
@@ -2930,8 +3617,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2930
3617
|
return;
|
|
2931
3618
|
}
|
|
2932
3619
|
if (msg.type === "bridge:claim") {
|
|
2933
|
-
const
|
|
2934
|
-
const outcome = this.tryAcquireLease(ws,
|
|
3620
|
+
const targetSim2 = await this.waitForSim(msg.simId);
|
|
3621
|
+
const outcome = this.tryAcquireLease(ws, targetSim2, {
|
|
2935
3622
|
force: msg.force === true
|
|
2936
3623
|
});
|
|
2937
3624
|
if (!outcome.granted) {
|
|
@@ -2939,25 +3626,25 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2939
3626
|
ws.send(
|
|
2940
3627
|
JSON.stringify({
|
|
2941
3628
|
id: msg.id,
|
|
2942
|
-
error: `
|
|
3629
|
+
error: `sim ${targetSim2.id} is locked by another cli`,
|
|
2943
3630
|
_locked: outcome.lock
|
|
2944
3631
|
})
|
|
2945
3632
|
);
|
|
2946
3633
|
}
|
|
2947
3634
|
return;
|
|
2948
3635
|
}
|
|
2949
|
-
this.
|
|
2950
|
-
this.
|
|
2951
|
-
|
|
2952
|
-
outcome.bootedCount > 0 ? `cli force-claimed
|
|
3636
|
+
this.setCliSimTarget(ws, targetSim2.id);
|
|
3637
|
+
this.recordSimAction(
|
|
3638
|
+
targetSim2.id,
|
|
3639
|
+
outcome.bootedCount > 0 ? `cli force-claimed sim (booted ${outcome.bootedCount})` : "cli claimed sim"
|
|
2953
3640
|
);
|
|
2954
3641
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2955
3642
|
ws.send(
|
|
2956
3643
|
JSON.stringify({
|
|
2957
3644
|
id: msg.id,
|
|
2958
3645
|
result: {
|
|
2959
|
-
|
|
2960
|
-
lockedBy: outcome.lease.
|
|
3646
|
+
simId: targetSim2.id,
|
|
3647
|
+
lockedBy: outcome.lease.cliIdentityKey,
|
|
2961
3648
|
lockExpiresAt: outcome.lease.expiresAt,
|
|
2962
3649
|
bootedCount: outcome.bootedCount
|
|
2963
3650
|
}
|
|
@@ -2966,15 +3653,15 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2966
3653
|
}
|
|
2967
3654
|
return;
|
|
2968
3655
|
}
|
|
2969
|
-
const
|
|
3656
|
+
const targetSim = await this.waitForSim(msg.simId);
|
|
2970
3657
|
if (shouldAcquireLease(msg)) {
|
|
2971
|
-
const outcome = this.tryAcquireLease(ws,
|
|
3658
|
+
const outcome = this.tryAcquireLease(ws, targetSim);
|
|
2972
3659
|
if (!outcome.granted) {
|
|
2973
3660
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2974
3661
|
ws.send(
|
|
2975
3662
|
JSON.stringify({
|
|
2976
3663
|
id: msg.id,
|
|
2977
|
-
error: `
|
|
3664
|
+
error: `sim ${targetSim.id} is locked by another cli \u2014 use \`sootsim claim ${targetSim.id} --force\` or \`sootsim open --new\``,
|
|
2978
3665
|
_locked: outcome.lock
|
|
2979
3666
|
})
|
|
2980
3667
|
);
|
|
@@ -2982,18 +3669,18 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2982
3669
|
return;
|
|
2983
3670
|
}
|
|
2984
3671
|
} else {
|
|
2985
|
-
this.
|
|
3672
|
+
this.ensureCliIdentityKey(ws);
|
|
2986
3673
|
}
|
|
2987
|
-
this.
|
|
2988
|
-
this.
|
|
3674
|
+
this.setCliSimTarget(ws, targetSim.id);
|
|
3675
|
+
this.recordSimAction(targetSim.id, this.describeForwardedCommand(msg));
|
|
2989
3676
|
const sentId = this.nextCommandId++;
|
|
2990
3677
|
this.cliBySentId.set(sentId, {
|
|
2991
|
-
|
|
3678
|
+
simId: targetSim.id,
|
|
2992
3679
|
ws,
|
|
2993
3680
|
originalId: msg.id
|
|
2994
3681
|
});
|
|
2995
|
-
const {
|
|
2996
|
-
|
|
3682
|
+
const { simId: _simId, ...forwarded } = msg;
|
|
3683
|
+
targetSim.ws.send(JSON.stringify({ ...forwarded, id: sentId }));
|
|
2997
3684
|
} catch (err) {
|
|
2998
3685
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2999
3686
|
ws.send(
|
|
@@ -3008,40 +3695,40 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3008
3695
|
});
|
|
3009
3696
|
ws.on("close", () => {
|
|
3010
3697
|
this.agentHost.unregisterSocket(ws);
|
|
3011
|
-
if (role === "
|
|
3012
|
-
this.
|
|
3013
|
-
if (this.
|
|
3014
|
-
this.
|
|
3698
|
+
if (role === "sim" && sim) {
|
|
3699
|
+
this.rememberDisconnectedSim(sim);
|
|
3700
|
+
if (this.primarySimId === sim.id) {
|
|
3701
|
+
this.primarySimId = this.getOpenSim()?.id ?? null;
|
|
3015
3702
|
}
|
|
3016
3703
|
for (const [id, pending] of this.pendingCommands) {
|
|
3017
|
-
if (pending.
|
|
3018
|
-
pending.reject(new Error("
|
|
3704
|
+
if (pending.simId !== sim.id) continue;
|
|
3705
|
+
pending.reject(new Error("sim disconnected"));
|
|
3019
3706
|
this.pendingCommands.delete(id);
|
|
3020
3707
|
}
|
|
3021
3708
|
for (const [sentId, entry] of this.cliBySentId) {
|
|
3022
|
-
if (entry.
|
|
3709
|
+
if (entry.simId !== sim.id) continue;
|
|
3023
3710
|
if (entry.ws.readyState === import_ws.WebSocket.OPEN) {
|
|
3024
3711
|
entry.ws.send(
|
|
3025
3712
|
JSON.stringify({
|
|
3026
3713
|
id: entry.originalId,
|
|
3027
|
-
error: "
|
|
3714
|
+
error: "sim disconnected before responding"
|
|
3028
3715
|
})
|
|
3029
3716
|
);
|
|
3030
3717
|
}
|
|
3031
3718
|
this.cliBySentId.delete(sentId);
|
|
3032
3719
|
}
|
|
3033
|
-
this.
|
|
3034
|
-
this.
|
|
3720
|
+
this.broadcastSimAssignments();
|
|
3721
|
+
this.broadcastSimClientStates();
|
|
3035
3722
|
} else if (role === "cli") {
|
|
3036
|
-
const detached = this.
|
|
3723
|
+
const detached = this.cliSimBySocket.delete(ws);
|
|
3037
3724
|
this.cliLastCommandAt.delete(ws);
|
|
3038
|
-
this.
|
|
3725
|
+
this.cliIdentityKeyBySocket.delete(ws);
|
|
3039
3726
|
this.cliLabelBySocket.delete(ws);
|
|
3040
3727
|
for (const [sentId, entry] of this.cliBySentId) {
|
|
3041
3728
|
if (entry.ws === ws) this.cliBySentId.delete(sentId);
|
|
3042
3729
|
}
|
|
3043
3730
|
if (detached) {
|
|
3044
|
-
this.
|
|
3731
|
+
this.broadcastSimClientStates();
|
|
3045
3732
|
}
|
|
3046
3733
|
}
|
|
3047
3734
|
});
|
|
@@ -3059,6 +3746,11 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3059
3746
|
3e4
|
|
3060
3747
|
);
|
|
3061
3748
|
this.cliIdleTimer.unref();
|
|
3749
|
+
this.wsHeartbeatTimer = setInterval(
|
|
3750
|
+
() => this.sweepDeadWebSockets(),
|
|
3751
|
+
_SootSimBridgeHost.WS_HEARTBEAT_INTERVAL_MS
|
|
3752
|
+
);
|
|
3753
|
+
this.wsHeartbeatTimer.unref();
|
|
3062
3754
|
if (this.shouldWriteLockfile) {
|
|
3063
3755
|
try {
|
|
3064
3756
|
ensureSootsimHome();
|
|
@@ -3108,6 +3800,46 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3108
3800
|
this.activeRuntimeVersion = readActiveRuntime();
|
|
3109
3801
|
this.activeRuntimeDirPath = activeRuntimeDir();
|
|
3110
3802
|
}
|
|
3803
|
+
runServerScan() {
|
|
3804
|
+
if (this.inflightScan) return this.inflightScan;
|
|
3805
|
+
const excludePorts = this.effectivePort > 0 ? [this.effectivePort] : [];
|
|
3806
|
+
this.inflightScan = scanDevServers({
|
|
3807
|
+
excludePorts,
|
|
3808
|
+
buildIconProxyUrl: (externalUrl) => `/__bundle-proxy?url=${encodeURIComponent(externalUrl)}`
|
|
3809
|
+
}).then((results) => {
|
|
3810
|
+
this.scanCache = results;
|
|
3811
|
+
this.scanCacheAt = Date.now();
|
|
3812
|
+
return results;
|
|
3813
|
+
}).catch((err) => {
|
|
3814
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3815
|
+
console.error("[sootsim] /__server-scan failed:", message);
|
|
3816
|
+
return this.scanCache ?? [];
|
|
3817
|
+
}).finally(() => {
|
|
3818
|
+
this.inflightScan = null;
|
|
3819
|
+
});
|
|
3820
|
+
return this.inflightScan;
|
|
3821
|
+
}
|
|
3822
|
+
handleServerScan(res) {
|
|
3823
|
+
const sendJson = (body) => {
|
|
3824
|
+
res.writeHead(200, {
|
|
3825
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
3826
|
+
"Cache-Control": "no-store"
|
|
3827
|
+
});
|
|
3828
|
+
res.end(JSON.stringify(body));
|
|
3829
|
+
};
|
|
3830
|
+
const age = Date.now() - this.scanCacheAt;
|
|
3831
|
+
if (this.scanCache && age < _SootSimBridgeHost.SCAN_FRESH_MS) {
|
|
3832
|
+
sendJson(this.scanCache);
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
if (this.scanCache) {
|
|
3836
|
+
sendJson(this.scanCache);
|
|
3837
|
+
void this.runServerScan().catch(() => {
|
|
3838
|
+
});
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
void this.runServerScan().then((results) => sendJson(results));
|
|
3842
|
+
}
|
|
3111
3843
|
resolveRuntimeUpdateIntervalMs() {
|
|
3112
3844
|
const raw = Number(process.env[RUNTIME_UPDATE_INTERVAL_ENV]);
|
|
3113
3845
|
if (Number.isFinite(raw) && raw > 0) return Math.max(100, Math.round(raw));
|
|
@@ -3165,7 +3897,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3165
3897
|
}
|
|
3166
3898
|
/** update the active runtime on disk + in memory. the caller guarantees
|
|
3167
3899
|
* the version directory exists. pushes a runtime:changed message to all
|
|
3168
|
-
* connected
|
|
3900
|
+
* connected sims so electron (or any renderer) can reload. */
|
|
3169
3901
|
setActiveRuntime(version) {
|
|
3170
3902
|
writeActiveRuntime(version);
|
|
3171
3903
|
this.refreshActiveRuntime();
|
|
@@ -3180,10 +3912,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3180
3912
|
version,
|
|
3181
3913
|
runtimeDir: this.activeRuntimeDirPath
|
|
3182
3914
|
});
|
|
3183
|
-
for (const
|
|
3184
|
-
if (
|
|
3915
|
+
for (const sim of this.sims.values()) {
|
|
3916
|
+
if (sim.ws.readyState === import_ws.WebSocket.OPEN) {
|
|
3185
3917
|
try {
|
|
3186
|
-
|
|
3918
|
+
sim.ws.send(payload);
|
|
3187
3919
|
} catch {
|
|
3188
3920
|
}
|
|
3189
3921
|
}
|
|
@@ -3212,6 +3944,13 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3212
3944
|
* non-upgrade routes that don't match serve index.html (SPA behavior) so
|
|
3213
3945
|
* electron's webContents can navigate freely inside the runtime. */
|
|
3214
3946
|
handleHttpRequest(req, res) {
|
|
3947
|
+
if (isFetchProxyRequestUrl(req.url)) {
|
|
3948
|
+
void handleFetchProxyRequest(req, res);
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3951
|
+
if (isAppApiRequestUrl(req.url) && handleAppApiRequest(req, res)) {
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3215
3954
|
const method = (req.method || "GET").toUpperCase();
|
|
3216
3955
|
if (method !== "GET" && method !== "HEAD") {
|
|
3217
3956
|
res.writeHead(405, { Allow: "GET, HEAD" });
|
|
@@ -3271,6 +4010,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3271
4010
|
})();
|
|
3272
4011
|
return;
|
|
3273
4012
|
}
|
|
4013
|
+
if (url.pathname === "/__server-scan") {
|
|
4014
|
+
this.handleServerScan(res);
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
3274
4017
|
if (url.pathname === "/healthz") {
|
|
3275
4018
|
res.writeHead(200, {
|
|
3276
4019
|
"Content-Type": "application/json",
|
|
@@ -3290,6 +4033,24 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3290
4033
|
);
|
|
3291
4034
|
return;
|
|
3292
4035
|
}
|
|
4036
|
+
if (url.pathname === "/__sootsim/shared-config") {
|
|
4037
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4038
|
+
res.setHeader("Cache-Control", "no-store");
|
|
4039
|
+
if (method === "GET" || method === "HEAD") {
|
|
4040
|
+
let body = "{}";
|
|
4041
|
+
try {
|
|
4042
|
+
body = JSON.stringify(readSharedConfig());
|
|
4043
|
+
} catch {
|
|
4044
|
+
}
|
|
4045
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4046
|
+
if (method === "HEAD") res.end();
|
|
4047
|
+
else res.end(body);
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
res.writeHead(405, { Allow: "GET, HEAD" });
|
|
4051
|
+
res.end("method not allowed (use the bridge over WS for writes)");
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
3293
4054
|
this.refreshActiveRuntime();
|
|
3294
4055
|
const baseDir = this.activeRuntimeDirPath;
|
|
3295
4056
|
if (!baseDir) {
|
|
@@ -3320,41 +4081,46 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3320
4081
|
return;
|
|
3321
4082
|
}
|
|
3322
4083
|
}
|
|
3323
|
-
const resolved =
|
|
3324
|
-
const baseWithSep = baseDir.endsWith(
|
|
4084
|
+
const resolved = import_path3.default.resolve(baseDir, "." + rel);
|
|
4085
|
+
const baseWithSep = baseDir.endsWith(import_path3.default.sep) ? baseDir : baseDir + import_path3.default.sep;
|
|
3325
4086
|
if (!resolved.startsWith(baseWithSep) && resolved !== baseDir) {
|
|
3326
4087
|
res.writeHead(403);
|
|
3327
4088
|
res.end("forbidden");
|
|
3328
4089
|
return;
|
|
3329
4090
|
}
|
|
3330
|
-
|
|
4091
|
+
import_fs3.default.realpath(resolved, (realErr, realResolved) => {
|
|
3331
4092
|
const servePath = realErr ? resolved : realResolved;
|
|
3332
|
-
const servePathWithSep = servePath.endsWith(
|
|
4093
|
+
const servePathWithSep = servePath.endsWith(import_path3.default.sep) ? servePath : servePath + import_path3.default.sep;
|
|
3333
4094
|
if (!realErr) {
|
|
3334
4095
|
const realBaseWithSep = (() => {
|
|
3335
4096
|
try {
|
|
3336
|
-
const rb =
|
|
3337
|
-
return rb.endsWith(
|
|
4097
|
+
const rb = import_fs3.default.realpathSync(baseDir);
|
|
4098
|
+
return rb.endsWith(import_path3.default.sep) ? rb : rb + import_path3.default.sep;
|
|
3338
4099
|
} catch {
|
|
3339
4100
|
return baseWithSep;
|
|
3340
4101
|
}
|
|
3341
4102
|
})();
|
|
3342
|
-
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath +
|
|
4103
|
+
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path3.default.sep !== realBaseWithSep) {
|
|
3343
4104
|
res.writeHead(403);
|
|
3344
4105
|
res.end("forbidden");
|
|
3345
4106
|
return;
|
|
3346
4107
|
}
|
|
3347
4108
|
}
|
|
3348
|
-
|
|
4109
|
+
import_fs3.default.stat(servePath, (err, stats) => {
|
|
3349
4110
|
if (err || !stats?.isFile()) {
|
|
3350
|
-
const ext2 =
|
|
4111
|
+
const ext2 = import_path3.default.extname(rel).toLowerCase();
|
|
3351
4112
|
if (ext2 && ext2 !== ".html") {
|
|
3352
4113
|
res.writeHead(404);
|
|
3353
4114
|
res.end("not found");
|
|
3354
4115
|
return;
|
|
3355
4116
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
4117
|
+
if (rel.startsWith("/__") || rel.startsWith("/api/") || rel === "/api") {
|
|
4118
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
4119
|
+
res.end("not found");
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4122
|
+
const indexPath = import_path3.default.join(baseDir, "index.html");
|
|
4123
|
+
import_fs3.default.readFile(indexPath, (err2, data) => {
|
|
3358
4124
|
if (err2) {
|
|
3359
4125
|
res.writeHead(404);
|
|
3360
4126
|
res.end("not found");
|
|
@@ -3368,11 +4134,11 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3368
4134
|
res.end();
|
|
3369
4135
|
return;
|
|
3370
4136
|
}
|
|
3371
|
-
res.end(data);
|
|
4137
|
+
res.end(injectSharedConfigIntoHtml(data));
|
|
3372
4138
|
});
|
|
3373
4139
|
return;
|
|
3374
4140
|
}
|
|
3375
|
-
const ext =
|
|
4141
|
+
const ext = import_path3.default.extname(servePath).toLowerCase();
|
|
3376
4142
|
const contentType = HTTP_MIME_TYPES[ext] || "application/octet-stream";
|
|
3377
4143
|
res.writeHead(200, {
|
|
3378
4144
|
"Content-Type": contentType,
|
|
@@ -3382,7 +4148,20 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3382
4148
|
res.end();
|
|
3383
4149
|
return;
|
|
3384
4150
|
}
|
|
3385
|
-
|
|
4151
|
+
if (ext === ".html") {
|
|
4152
|
+
import_fs3.default.readFile(servePath, (readErr, data) => {
|
|
4153
|
+
if (readErr) {
|
|
4154
|
+
try {
|
|
4155
|
+
res.end();
|
|
4156
|
+
} catch {
|
|
4157
|
+
}
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
res.end(injectSharedConfigIntoHtml(data));
|
|
4161
|
+
});
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
const stream = import_fs3.default.createReadStream(servePath);
|
|
3386
4165
|
stream.pipe(res);
|
|
3387
4166
|
stream.on("error", () => {
|
|
3388
4167
|
try {
|
|
@@ -3396,10 +4175,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3396
4175
|
sweepIdleCliClients() {
|
|
3397
4176
|
const now = Date.now();
|
|
3398
4177
|
let swept = false;
|
|
3399
|
-
for (const [ws,
|
|
4178
|
+
for (const [ws, simId] of this.cliSimBySocket) {
|
|
3400
4179
|
const lastCommand = this.cliLastCommandAt.get(ws) ?? 0;
|
|
3401
4180
|
if (now - lastCommand < _SootSimBridgeHost.CLI_IDLE_TIMEOUT_MS) continue;
|
|
3402
|
-
this.
|
|
4181
|
+
this.cliSimBySocket.delete(ws);
|
|
3403
4182
|
this.cliLastCommandAt.delete(ws);
|
|
3404
4183
|
for (const [sentId, entry] of this.cliBySentId) {
|
|
3405
4184
|
if (entry.ws === ws) this.cliBySentId.delete(sentId);
|
|
@@ -3411,54 +4190,80 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3411
4190
|
swept = true;
|
|
3412
4191
|
}
|
|
3413
4192
|
if (swept) {
|
|
3414
|
-
this.
|
|
4193
|
+
this.broadcastSimClientStates();
|
|
4194
|
+
}
|
|
4195
|
+
this.sweepRestorableSims(now);
|
|
4196
|
+
}
|
|
4197
|
+
// ping every connected ws; if the previous round's ping went unanswered,
|
|
4198
|
+
// terminate the socket so 'close' fires and the sim cleanup path
|
|
4199
|
+
// runs. matches the recommended ws-library heartbeat pattern.
|
|
4200
|
+
sweepDeadWebSockets() {
|
|
4201
|
+
if (!this.wss) return;
|
|
4202
|
+
for (const ws of this.wss.clients) {
|
|
4203
|
+
if (ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
4204
|
+
const alive = this.wsIsAlive.get(ws);
|
|
4205
|
+
if (alive === false) {
|
|
4206
|
+
try {
|
|
4207
|
+
ws.terminate();
|
|
4208
|
+
} catch {
|
|
4209
|
+
}
|
|
4210
|
+
continue;
|
|
4211
|
+
}
|
|
4212
|
+
this.wsIsAlive.set(ws, false);
|
|
4213
|
+
try {
|
|
4214
|
+
ws.ping();
|
|
4215
|
+
} catch {
|
|
4216
|
+
try {
|
|
4217
|
+
ws.terminate();
|
|
4218
|
+
} catch {
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
3415
4221
|
}
|
|
3416
|
-
this.sweepRestorableBrowsers(now);
|
|
3417
4222
|
}
|
|
3418
|
-
|
|
3419
|
-
return Array.from(this.
|
|
3420
|
-
if (a.id === this.
|
|
3421
|
-
if (b.id === this.
|
|
4223
|
+
listSims() {
|
|
4224
|
+
return Array.from(this.sims.values()).sort((a, b) => {
|
|
4225
|
+
if (a.id === this.primarySimId) return -1;
|
|
4226
|
+
if (b.id === this.primarySimId) return 1;
|
|
3422
4227
|
return a.connectedAt - b.connectedAt;
|
|
3423
|
-
}).map((
|
|
4228
|
+
}).map((sim) => this.describeSim(sim));
|
|
3424
4229
|
}
|
|
3425
4230
|
async sendCommand(cmd) {
|
|
3426
|
-
const
|
|
4231
|
+
const sim = await this.waitForSim(cmd.simId);
|
|
3427
4232
|
const id = this.nextCommandId++;
|
|
3428
4233
|
return new Promise((resolve2, reject) => {
|
|
3429
4234
|
const timeout = setTimeout(() => {
|
|
3430
4235
|
this.pendingCommands.delete(id);
|
|
3431
|
-
this.
|
|
4236
|
+
this.broadcastSimClientStates();
|
|
3432
4237
|
reject(new Error("command timed out after 30s"));
|
|
3433
4238
|
}, 3e4);
|
|
3434
4239
|
this.pendingCommands.set(id, {
|
|
3435
|
-
|
|
4240
|
+
simId: sim.id,
|
|
3436
4241
|
resolve: (value) => {
|
|
3437
4242
|
clearTimeout(timeout);
|
|
3438
4243
|
this.pendingCommands.delete(id);
|
|
3439
|
-
this.
|
|
4244
|
+
this.broadcastSimClientStates();
|
|
3440
4245
|
resolve2(value);
|
|
3441
4246
|
},
|
|
3442
4247
|
reject: (error) => {
|
|
3443
4248
|
clearTimeout(timeout);
|
|
3444
4249
|
this.pendingCommands.delete(id);
|
|
3445
|
-
this.
|
|
4250
|
+
this.broadcastSimClientStates();
|
|
3446
4251
|
reject(error);
|
|
3447
4252
|
}
|
|
3448
4253
|
});
|
|
3449
|
-
this.
|
|
3450
|
-
const {
|
|
3451
|
-
|
|
4254
|
+
this.broadcastSimClientStates();
|
|
4255
|
+
const { simId: _simId, ...forwarded } = cmd;
|
|
4256
|
+
sim.ws.send(JSON.stringify({ ...forwarded, id }));
|
|
3452
4257
|
});
|
|
3453
4258
|
}
|
|
3454
|
-
async evaluate(code,
|
|
3455
|
-
return this.sendCommand({ type: "evaluate", code,
|
|
4259
|
+
async evaluate(code, simId) {
|
|
4260
|
+
return this.sendCommand({ type: "evaluate", code, simId });
|
|
3456
4261
|
}
|
|
3457
|
-
async
|
|
3458
|
-
return this.sendCommand({ type: "focus",
|
|
4262
|
+
async focusSim(simId) {
|
|
4263
|
+
return this.sendCommand({ type: "focus", simId });
|
|
3459
4264
|
}
|
|
3460
|
-
async
|
|
3461
|
-
return this.sendCommand({ type: "close",
|
|
4265
|
+
async closeSim(simId) {
|
|
4266
|
+
return this.sendCommand({ type: "close", simId });
|
|
3462
4267
|
}
|
|
3463
4268
|
async openPathInEditor(filePath, line, column) {
|
|
3464
4269
|
const loc = line != null ? `:${line}${column != null ? `:${column}` : ""}` : "";
|
|
@@ -3508,6 +4313,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3508
4313
|
clearInterval(this.heartbeatTimer);
|
|
3509
4314
|
this.heartbeatTimer = null;
|
|
3510
4315
|
}
|
|
4316
|
+
if (this.wsHeartbeatTimer) {
|
|
4317
|
+
clearInterval(this.wsHeartbeatTimer);
|
|
4318
|
+
this.wsHeartbeatTimer = null;
|
|
4319
|
+
}
|
|
3511
4320
|
if (this.runtimeUpdateTimer) {
|
|
3512
4321
|
clearInterval(this.runtimeUpdateTimer);
|
|
3513
4322
|
this.runtimeUpdateTimer = null;
|
|
@@ -3525,11 +4334,11 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3525
4334
|
pending.reject(new Error("server closing"));
|
|
3526
4335
|
this.pendingCommands.delete(id);
|
|
3527
4336
|
}
|
|
3528
|
-
for (const
|
|
3529
|
-
|
|
4337
|
+
for (const sim of this.sims.values()) {
|
|
4338
|
+
sim.ws.close();
|
|
3530
4339
|
}
|
|
3531
|
-
this.
|
|
3532
|
-
this.
|
|
4340
|
+
this.sims.clear();
|
|
4341
|
+
this.primarySimId = null;
|
|
3533
4342
|
const wss = this.wss;
|
|
3534
4343
|
const httpServer = this.httpServer;
|
|
3535
4344
|
this.wss = null;
|
|
@@ -3547,69 +4356,69 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3547
4356
|
}
|
|
3548
4357
|
}
|
|
3549
4358
|
}
|
|
3550
|
-
|
|
4359
|
+
describeSim(sim) {
|
|
3551
4360
|
let readyState;
|
|
3552
4361
|
try {
|
|
3553
|
-
readyState =
|
|
4362
|
+
readyState = sim.ws.readyState;
|
|
3554
4363
|
} catch {
|
|
3555
4364
|
readyState = import_ws.WebSocket.CLOSED;
|
|
3556
4365
|
}
|
|
3557
|
-
const lease = this.getActiveLease(
|
|
4366
|
+
const lease = this.getActiveLease(sim);
|
|
3558
4367
|
return {
|
|
3559
|
-
id:
|
|
3560
|
-
origin:
|
|
3561
|
-
url:
|
|
3562
|
-
title:
|
|
3563
|
-
userAgent:
|
|
3564
|
-
connectedAt:
|
|
3565
|
-
lastSeenAt:
|
|
3566
|
-
lastActiveAt:
|
|
3567
|
-
isPrimary:
|
|
4368
|
+
id: sim.id,
|
|
4369
|
+
origin: sim.origin,
|
|
4370
|
+
url: sim.url,
|
|
4371
|
+
title: sim.title,
|
|
4372
|
+
userAgent: sim.userAgent,
|
|
4373
|
+
connectedAt: sim.connectedAt,
|
|
4374
|
+
lastSeenAt: sim.lastSeenAt,
|
|
4375
|
+
lastActiveAt: sim.lastActiveAt || void 0,
|
|
4376
|
+
isPrimary: sim.id === this.primarySimId,
|
|
3568
4377
|
readyState: readyState === import_ws.WebSocket.OPEN ? "open" : readyState === import_ws.WebSocket.CLOSING ? "closing" : "closed",
|
|
3569
|
-
attachedCliCount: this.getAttachedCliCount(
|
|
3570
|
-
lockedBy: lease ? lease.cliLabel || lease.
|
|
4378
|
+
attachedCliCount: this.getAttachedCliCount(sim.id),
|
|
4379
|
+
lockedBy: lease ? lease.cliLabel || lease.cliIdentityKey : void 0,
|
|
3571
4380
|
lockedByKind: lease ? lease.kind : void 0,
|
|
3572
4381
|
lockExpiresAt: lease ? lease.expiresAt : void 0,
|
|
3573
|
-
userFocused:
|
|
4382
|
+
userFocused: sim.userFocused || void 0
|
|
3574
4383
|
};
|
|
3575
4384
|
}
|
|
3576
|
-
getActiveLease(
|
|
3577
|
-
const lease =
|
|
4385
|
+
getActiveLease(sim) {
|
|
4386
|
+
const lease = sim.cliLease;
|
|
3578
4387
|
if (!lease) return null;
|
|
3579
4388
|
if (Date.now() >= lease.expiresAt) {
|
|
3580
|
-
|
|
4389
|
+
sim.cliLease = void 0;
|
|
3581
4390
|
return null;
|
|
3582
4391
|
}
|
|
3583
4392
|
return lease;
|
|
3584
4393
|
}
|
|
3585
|
-
tryAcquireLease(ws,
|
|
3586
|
-
const
|
|
4394
|
+
tryAcquireLease(ws, sim, opts = {}) {
|
|
4395
|
+
const cliIdentityKey = this.cliIdentityKeyBySocket.get(ws) ?? (() => {
|
|
3587
4396
|
const fallback = `ws-${this.nextCliFallbackId++}`;
|
|
3588
|
-
this.
|
|
4397
|
+
this.cliIdentityKeyBySocket.set(ws, fallback);
|
|
3589
4398
|
return fallback;
|
|
3590
4399
|
})();
|
|
3591
4400
|
const cliLabel = this.cliLabelBySocket.get(ws);
|
|
3592
4401
|
const now = Date.now();
|
|
3593
|
-
const existing = this.getActiveLease(
|
|
3594
|
-
const ownerMatches = existing && existing.
|
|
4402
|
+
const existing = this.getActiveLease(sim);
|
|
4403
|
+
const ownerMatches = existing && existing.cliIdentityKey === cliIdentityKey;
|
|
3595
4404
|
let bootedCount = 0;
|
|
3596
4405
|
if (existing && !ownerMatches && !opts.force) {
|
|
3597
4406
|
return {
|
|
3598
4407
|
granted: false,
|
|
3599
4408
|
lease: existing,
|
|
3600
4409
|
lock: {
|
|
3601
|
-
by: existing.cliLabel || existing.
|
|
4410
|
+
by: existing.cliLabel || existing.cliIdentityKey,
|
|
3602
4411
|
expiresInMs: Math.max(0, existing.expiresAt - now)
|
|
3603
4412
|
},
|
|
3604
4413
|
bootedCount: 0
|
|
3605
4414
|
};
|
|
3606
4415
|
}
|
|
3607
4416
|
if (existing && !ownerMatches && opts.force) {
|
|
3608
|
-
for (const [cliWs,
|
|
3609
|
-
if (
|
|
3610
|
-
const otherKey = this.
|
|
3611
|
-
if (otherKey && otherKey !==
|
|
3612
|
-
this.
|
|
4417
|
+
for (const [cliWs, attachedSimId] of this.cliSimBySocket) {
|
|
4418
|
+
if (attachedSimId !== sim.id) continue;
|
|
4419
|
+
const otherKey = this.cliIdentityKeyBySocket.get(cliWs);
|
|
4420
|
+
if (otherKey && otherKey !== cliIdentityKey) {
|
|
4421
|
+
this.cliSimBySocket.delete(cliWs);
|
|
3613
4422
|
try {
|
|
3614
4423
|
cliWs.close(1e3, "lease claimed by another cli");
|
|
3615
4424
|
} catch {
|
|
@@ -3620,135 +4429,129 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3620
4429
|
}
|
|
3621
4430
|
const lease = {
|
|
3622
4431
|
kind: "cli",
|
|
3623
|
-
|
|
4432
|
+
cliIdentityKey,
|
|
3624
4433
|
cliLabel,
|
|
3625
4434
|
expiresAt: now + _SootSimBridgeHost.CLI_LEASE_TTL_MS
|
|
3626
4435
|
};
|
|
3627
|
-
|
|
4436
|
+
sim.cliLease = lease;
|
|
3628
4437
|
return { granted: true, lease, bootedCount };
|
|
3629
4438
|
}
|
|
3630
|
-
// user focus is advisory: we track it on the
|
|
4439
|
+
// user focus is advisory: we track it on the sim record so list/UI can
|
|
3631
4440
|
// show "focused" alongside any cli lease, but focus alone never creates a
|
|
3632
|
-
// blocking lease. the old 15s user-focus lease meant clicking on the
|
|
4441
|
+
// blocking lease. the old 15s user-focus lease meant clicking on the sim
|
|
3633
4442
|
// locked out agent inspect calls for 15s — the opposite of what you want
|
|
3634
4443
|
// when debugging something the user is actively looking at. use
|
|
3635
4444
|
// updateUserActivity() to lock on real interaction instead.
|
|
3636
|
-
updateUserFocusLease(
|
|
4445
|
+
updateUserFocusLease(sim, focused) {
|
|
3637
4446
|
const next = focused;
|
|
3638
|
-
if (
|
|
3639
|
-
|
|
3640
|
-
this.
|
|
4447
|
+
if (sim.userFocused === next) return;
|
|
4448
|
+
sim.userFocused = next;
|
|
4449
|
+
this.broadcastSimClientStates();
|
|
3641
4450
|
}
|
|
3642
|
-
// called when the
|
|
4451
|
+
// called when the sim reports a real user interaction (pointerdown,
|
|
3643
4452
|
// keydown, wheel, touch). creates or refreshes a short `user-active` lease
|
|
3644
|
-
// that keeps agent writes from trampling a user who is driving the
|
|
4453
|
+
// that keeps agent writes from trampling a user who is driving the sim.
|
|
3645
4454
|
// reads still pass through — shouldAcquireLease only blocks on writes.
|
|
3646
|
-
updateUserActivity(
|
|
3647
|
-
const existing = this.getActiveLease(
|
|
4455
|
+
updateUserActivity(sim) {
|
|
4456
|
+
const existing = this.getActiveLease(sim);
|
|
3648
4457
|
if (existing && existing.kind === "cli") {
|
|
3649
4458
|
return;
|
|
3650
4459
|
}
|
|
3651
4460
|
const now = Date.now();
|
|
3652
4461
|
const refreshed = now + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS;
|
|
3653
4462
|
const expiresAt = existing && existing.kind === "user-active" ? Math.max(existing.expiresAt, refreshed) : refreshed;
|
|
3654
|
-
|
|
4463
|
+
sim.cliLease = {
|
|
3655
4464
|
kind: "user-active",
|
|
3656
|
-
|
|
4465
|
+
cliIdentityKey: "__user-active__",
|
|
3657
4466
|
cliLabel: "active user",
|
|
3658
4467
|
expiresAt
|
|
3659
4468
|
};
|
|
3660
|
-
this.
|
|
4469
|
+
this.broadcastSimClientStates();
|
|
3661
4470
|
}
|
|
3662
|
-
|
|
3663
|
-
const existing = this.
|
|
4471
|
+
ensureCliIdentityKey(ws) {
|
|
4472
|
+
const existing = this.cliIdentityKeyBySocket.get(ws);
|
|
3664
4473
|
if (existing) return existing;
|
|
3665
4474
|
const fallback = `ws-${this.nextCliFallbackId++}`;
|
|
3666
|
-
this.
|
|
4475
|
+
this.cliIdentityKeyBySocket.set(ws, fallback);
|
|
3667
4476
|
return fallback;
|
|
3668
4477
|
}
|
|
3669
|
-
|
|
3670
|
-
if (
|
|
3671
|
-
const
|
|
3672
|
-
if (
|
|
4478
|
+
getOpenSim(simId) {
|
|
4479
|
+
if (simId) {
|
|
4480
|
+
const sim = this.sims.get(simId);
|
|
4481
|
+
if (sim?.ws.readyState === import_ws.WebSocket.OPEN) return sim;
|
|
3673
4482
|
return null;
|
|
3674
4483
|
}
|
|
3675
|
-
const primary = this.
|
|
4484
|
+
const primary = this.primarySimId != null ? this.sims.get(this.primarySimId) : null;
|
|
3676
4485
|
if (primary?.ws.readyState === import_ws.WebSocket.OPEN) return primary;
|
|
3677
|
-
for (const
|
|
3678
|
-
if (
|
|
4486
|
+
for (const sim of this.sims.values()) {
|
|
4487
|
+
if (sim.ws.readyState === import_ws.WebSocket.OPEN) return sim;
|
|
3679
4488
|
}
|
|
3680
4489
|
return null;
|
|
3681
4490
|
}
|
|
3682
|
-
async
|
|
4491
|
+
async waitForSim(simId, options = {}) {
|
|
3683
4492
|
const attempts = options.attempts ?? 10;
|
|
3684
4493
|
const intervalMs = options.intervalMs ?? 200;
|
|
3685
4494
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
3686
|
-
const
|
|
3687
|
-
if (
|
|
4495
|
+
const sim = this.getOpenSim(simId);
|
|
4496
|
+
if (sim) return sim;
|
|
3688
4497
|
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
3689
4498
|
}
|
|
3690
|
-
throw new Error(
|
|
3691
|
-
browserId ? `no browser connected with id ${browserId}` : "no browser connected"
|
|
3692
|
-
);
|
|
4499
|
+
throw new Error(simId ? `no sim connected with id ${simId}` : "no sim connected");
|
|
3693
4500
|
}
|
|
3694
|
-
|
|
3695
|
-
const current = this.
|
|
3696
|
-
const isPrimaryCandidate =
|
|
4501
|
+
shouldPromoteSim(sim) {
|
|
4502
|
+
const current = this.primarySimId ? this.sims.get(this.primarySimId) : null;
|
|
4503
|
+
const isPrimaryCandidate = sim.origin?.includes(":5173");
|
|
3697
4504
|
const currentIsPrimary = current?.origin?.includes(":5173");
|
|
3698
4505
|
return !current || current.ws.readyState !== import_ws.WebSocket.OPEN || !!isPrimaryCandidate || !currentIsPrimary;
|
|
3699
4506
|
}
|
|
3700
|
-
|
|
3701
|
-
for (const
|
|
3702
|
-
if (
|
|
3703
|
-
|
|
4507
|
+
broadcastSimAssignments() {
|
|
4508
|
+
for (const sim of this.sims.values()) {
|
|
4509
|
+
if (sim.ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
4510
|
+
sim.ws.send(
|
|
3704
4511
|
JSON.stringify({
|
|
3705
4512
|
type: "bridge:welcome",
|
|
3706
|
-
|
|
3707
|
-
isPrimary:
|
|
4513
|
+
simId: sim.id,
|
|
4514
|
+
isPrimary: sim.id === this.primarySimId
|
|
3708
4515
|
})
|
|
3709
4516
|
);
|
|
3710
4517
|
}
|
|
3711
4518
|
}
|
|
3712
|
-
|
|
3713
|
-
for (const
|
|
3714
|
-
if (
|
|
3715
|
-
const lease = this.getActiveLease(
|
|
4519
|
+
broadcastSimClientStates() {
|
|
4520
|
+
for (const sim of this.sims.values()) {
|
|
4521
|
+
if (sim.ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
4522
|
+
const lease = this.getActiveLease(sim);
|
|
3716
4523
|
const message = {
|
|
3717
4524
|
type: "bridge:client-state",
|
|
3718
|
-
attachedCliCount: this.getAttachedCliCount(
|
|
3719
|
-
activeAgentCommandCount: this.getActiveAgentCommandCount(
|
|
3720
|
-
recentActions:
|
|
3721
|
-
lockedBy: lease ? lease.cliLabel || lease.
|
|
4525
|
+
attachedCliCount: this.getAttachedCliCount(sim.id),
|
|
4526
|
+
activeAgentCommandCount: this.getActiveAgentCommandCount(sim.id),
|
|
4527
|
+
recentActions: sim.recentActions,
|
|
4528
|
+
lockedBy: lease ? lease.cliLabel || lease.cliIdentityKey : void 0,
|
|
3722
4529
|
lockedByKind: lease ? lease.kind : void 0,
|
|
3723
4530
|
lockExpiresAt: lease ? lease.expiresAt : void 0,
|
|
3724
|
-
userFocused:
|
|
4531
|
+
userFocused: sim.userFocused || void 0
|
|
3725
4532
|
};
|
|
3726
|
-
|
|
4533
|
+
sim.ws.send(JSON.stringify(message));
|
|
3727
4534
|
}
|
|
3728
4535
|
}
|
|
3729
|
-
|
|
3730
|
-
const
|
|
3731
|
-
if (
|
|
3732
|
-
this.
|
|
3733
|
-
this.
|
|
3734
|
-
|
|
3735
|
-
prevBrowserId ? "cli switched tabs" : "cli connected",
|
|
3736
|
-
false
|
|
3737
|
-
);
|
|
3738
|
-
this.broadcastBrowserClientStates();
|
|
4536
|
+
setCliSimTarget(ws, simId) {
|
|
4537
|
+
const prevSimId = this.cliSimBySocket.get(ws);
|
|
4538
|
+
if (prevSimId === simId) return;
|
|
4539
|
+
this.cliSimBySocket.set(ws, simId);
|
|
4540
|
+
this.recordSimAction(simId, prevSimId ? "cli switched sims" : "cli connected", false);
|
|
4541
|
+
this.broadcastSimClientStates();
|
|
3739
4542
|
}
|
|
3740
|
-
|
|
4543
|
+
recordSimAction(simId, label, broadcast = true) {
|
|
3741
4544
|
const normalized = label?.trim();
|
|
3742
4545
|
if (!normalized) return;
|
|
3743
|
-
const
|
|
3744
|
-
if (!
|
|
4546
|
+
const sim = this.sims.get(simId);
|
|
4547
|
+
if (!sim) return;
|
|
3745
4548
|
const now = Date.now();
|
|
3746
|
-
|
|
3747
|
-
|
|
4549
|
+
sim.lastActiveAt = now;
|
|
4550
|
+
sim.recentActions = [
|
|
3748
4551
|
{ label: normalized, at: now },
|
|
3749
|
-
...
|
|
4552
|
+
...sim.recentActions.filter((entry) => entry.label !== normalized)
|
|
3750
4553
|
].slice(0, 4);
|
|
3751
|
-
if (broadcast) this.
|
|
4554
|
+
if (broadcast) this.broadcastSimClientStates();
|
|
3752
4555
|
}
|
|
3753
4556
|
describeForwardedCommand(msg) {
|
|
3754
4557
|
switch (msg?.type) {
|
|
@@ -3763,90 +4566,97 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3763
4566
|
case "tree":
|
|
3764
4567
|
return "dumped tree";
|
|
3765
4568
|
case "focus":
|
|
3766
|
-
return "focused
|
|
4569
|
+
return "focused sim";
|
|
3767
4570
|
case "close":
|
|
3768
4571
|
return "requested close";
|
|
3769
4572
|
default:
|
|
3770
4573
|
return typeof msg?.type === "string" ? msg.type : null;
|
|
3771
4574
|
}
|
|
3772
4575
|
}
|
|
3773
|
-
// count distinct cli
|
|
4576
|
+
// count distinct cli identity keys attached to a sim, not raw sockets.
|
|
3774
4577
|
// a single agent firing sequential cli commands opens a new ws per call —
|
|
3775
4578
|
// counting sockets would report phantom peers until idle cleanup catches up.
|
|
3776
|
-
getAttachedCliCount(
|
|
4579
|
+
getAttachedCliCount(simId) {
|
|
3777
4580
|
const keys = /* @__PURE__ */ new Set();
|
|
3778
|
-
for (const [ws,
|
|
3779
|
-
if (
|
|
4581
|
+
for (const [ws, attachedSimId] of this.cliSimBySocket) {
|
|
4582
|
+
if (attachedSimId !== simId) continue;
|
|
3780
4583
|
if (ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
3781
|
-
const key = this.
|
|
4584
|
+
const key = this.cliIdentityKeyBySocket.get(ws);
|
|
3782
4585
|
keys.add(key ?? `ws-unknown-${keys.size}`);
|
|
3783
4586
|
}
|
|
3784
4587
|
return keys.size;
|
|
3785
4588
|
}
|
|
3786
|
-
// count distinct
|
|
3787
|
-
// used to warn a cli that other agents/
|
|
3788
|
-
|
|
3789
|
-
const selfKey = this.
|
|
4589
|
+
// count distinct identity keys attached to this sim other than `selfWs`.
|
|
4590
|
+
// used to warn a cli that other agents/identities are also targeting the sim.
|
|
4591
|
+
getOtherCliIdentityCount(selfWs, simId) {
|
|
4592
|
+
const selfKey = this.cliIdentityKeyBySocket.get(selfWs);
|
|
3790
4593
|
const keys = /* @__PURE__ */ new Set();
|
|
3791
|
-
for (const [ws,
|
|
3792
|
-
if (
|
|
4594
|
+
for (const [ws, attachedSimId] of this.cliSimBySocket) {
|
|
4595
|
+
if (attachedSimId !== simId) continue;
|
|
3793
4596
|
if (ws.readyState !== import_ws.WebSocket.OPEN) continue;
|
|
3794
|
-
const key = this.
|
|
4597
|
+
const key = this.cliIdentityKeyBySocket.get(ws);
|
|
3795
4598
|
if (key && key === selfKey) continue;
|
|
3796
4599
|
keys.add(key ?? `ws-unknown-${keys.size}`);
|
|
3797
4600
|
}
|
|
3798
4601
|
return keys.size;
|
|
3799
4602
|
}
|
|
3800
|
-
getActiveAgentCommandCount(
|
|
4603
|
+
getActiveAgentCommandCount(simId) {
|
|
3801
4604
|
let count = 0;
|
|
3802
4605
|
for (const pending of this.pendingCommands.values()) {
|
|
3803
|
-
if (pending.
|
|
4606
|
+
if (pending.simId === simId) count++;
|
|
3804
4607
|
}
|
|
3805
4608
|
return count;
|
|
3806
4609
|
}
|
|
3807
|
-
|
|
4610
|
+
allocateSimId() {
|
|
4611
|
+
for (; ; ) {
|
|
4612
|
+
const id = this.nextSimNumber.toString(16);
|
|
4613
|
+
this.nextSimNumber++;
|
|
4614
|
+
if (!this.sims.has(id) && !this.restorableSims.has(id)) return id;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
tryRestoreSimId(sim, requestedId) {
|
|
3808
4618
|
const nextId = requestedId?.trim();
|
|
3809
|
-
if (!nextId || nextId ===
|
|
3810
|
-
const existing = this.
|
|
3811
|
-
if (existing && existing !==
|
|
4619
|
+
if (!nextId || nextId === sim.id) return false;
|
|
4620
|
+
const existing = this.sims.get(nextId);
|
|
4621
|
+
if (existing && existing !== sim && existing.ws.readyState === import_ws.WebSocket.OPEN) {
|
|
3812
4622
|
return false;
|
|
3813
4623
|
}
|
|
3814
|
-
const restorable = this.
|
|
3815
|
-
const prevId =
|
|
3816
|
-
this.
|
|
3817
|
-
|
|
4624
|
+
const restorable = this.getRestorableSimState(nextId);
|
|
4625
|
+
const prevId = sim.id;
|
|
4626
|
+
this.sims.delete(prevId);
|
|
4627
|
+
sim.id = nextId;
|
|
3818
4628
|
if (restorable) {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
this.
|
|
4629
|
+
sim.recentActions = restorable.recentActions.map((entry) => ({ ...entry }));
|
|
4630
|
+
sim.lastActiveAt = restorable.lastActiveAt;
|
|
4631
|
+
sim.cliLease = restorable.cliLease ? { ...restorable.cliLease } : void 0;
|
|
4632
|
+
this.restorableSims.delete(nextId);
|
|
3823
4633
|
}
|
|
3824
|
-
this.
|
|
3825
|
-
if (this.
|
|
3826
|
-
this.
|
|
4634
|
+
this.sims.set(sim.id, sim);
|
|
4635
|
+
if (this.primarySimId === prevId) {
|
|
4636
|
+
this.primarySimId = sim.id;
|
|
3827
4637
|
}
|
|
3828
|
-
for (const [ws,
|
|
3829
|
-
if (
|
|
3830
|
-
this.
|
|
4638
|
+
for (const [ws, attachedSimId] of this.cliSimBySocket) {
|
|
4639
|
+
if (attachedSimId === prevId) {
|
|
4640
|
+
this.cliSimBySocket.set(ws, sim.id);
|
|
3831
4641
|
}
|
|
3832
4642
|
}
|
|
3833
4643
|
return true;
|
|
3834
4644
|
}
|
|
3835
|
-
|
|
3836
|
-
const lease = this.getActiveLease(
|
|
3837
|
-
this.
|
|
3838
|
-
recentActions:
|
|
3839
|
-
lastActiveAt:
|
|
4645
|
+
rememberDisconnectedSim(sim) {
|
|
4646
|
+
const lease = this.getActiveLease(sim);
|
|
4647
|
+
this.restorableSims.set(sim.id, {
|
|
4648
|
+
recentActions: sim.recentActions.map((entry) => ({ ...entry })),
|
|
4649
|
+
lastActiveAt: sim.lastActiveAt,
|
|
3840
4650
|
cliLease: lease && lease.kind === "cli" ? { ...lease } : void 0,
|
|
3841
|
-
expiresAt: Date.now() + _SootSimBridgeHost.
|
|
4651
|
+
expiresAt: Date.now() + _SootSimBridgeHost.SIM_RECONNECT_TTL_MS
|
|
3842
4652
|
});
|
|
3843
|
-
this.
|
|
4653
|
+
this.sims.delete(sim.id);
|
|
3844
4654
|
}
|
|
3845
|
-
|
|
3846
|
-
const snapshot = this.
|
|
4655
|
+
getRestorableSimState(simId) {
|
|
4656
|
+
const snapshot = this.restorableSims.get(simId);
|
|
3847
4657
|
if (!snapshot) return null;
|
|
3848
4658
|
if (snapshot.expiresAt <= Date.now()) {
|
|
3849
|
-
this.
|
|
4659
|
+
this.restorableSims.delete(simId);
|
|
3850
4660
|
return null;
|
|
3851
4661
|
}
|
|
3852
4662
|
if (snapshot.cliLease && snapshot.cliLease.expiresAt <= Date.now()) {
|
|
@@ -3854,13 +4664,13 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3854
4664
|
}
|
|
3855
4665
|
return snapshot;
|
|
3856
4666
|
}
|
|
3857
|
-
|
|
3858
|
-
for (const [
|
|
4667
|
+
sweepRestorableSims(now = Date.now()) {
|
|
4668
|
+
for (const [simId, snapshot] of this.restorableSims) {
|
|
3859
4669
|
if (snapshot.expiresAt > now) continue;
|
|
3860
|
-
this.
|
|
3861
|
-
for (const [cliWs,
|
|
3862
|
-
if (
|
|
3863
|
-
this.
|
|
4670
|
+
this.restorableSims.delete(simId);
|
|
4671
|
+
for (const [cliWs, attachedSimId] of this.cliSimBySocket) {
|
|
4672
|
+
if (attachedSimId === simId) {
|
|
4673
|
+
this.cliSimBySocket.delete(cliWs);
|
|
3864
4674
|
}
|
|
3865
4675
|
}
|
|
3866
4676
|
}
|
|
@@ -3870,6 +4680,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3870
4680
|
clearInterval(this.cliIdleTimer);
|
|
3871
4681
|
this.cliIdleTimer = null;
|
|
3872
4682
|
}
|
|
4683
|
+
if (this.wsHeartbeatTimer) {
|
|
4684
|
+
clearInterval(this.wsHeartbeatTimer);
|
|
4685
|
+
this.wsHeartbeatTimer = null;
|
|
4686
|
+
}
|
|
3873
4687
|
if (this.runtimeUpdateTimer) {
|
|
3874
4688
|
clearInterval(this.runtimeUpdateTimer);
|
|
3875
4689
|
this.runtimeUpdateTimer = null;
|