sootsim 0.1.84 → 0.1.86
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/detox/element-types.ts +36 -0
- package/detox/expectations.ts +1 -1
- package/detox/index.ts +2 -35
- package/dist-cli/bin.js +3 -3
- package/dist-cli/chunks/{agent-2CWD6W6P.js → agent-S3WLX5Z4.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-5W3LOX6S.js → agent-wrapper-PFPEQTPG.js} +2 -2
- package/dist-cli/chunks/{assert-ZOMAMKRT.js → assert-RQD66YGE.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-PWF7OYCV.js +2 -0
- package/dist-cli/chunks/beta-2HH7F2DQ.js +2 -0
- package/dist-cli/chunks/{chunk-D4HUVLZR.js → chunk-2IYMBWHL.js} +1 -1
- package/dist-cli/chunks/{chunk-DUUSJDES.js → chunk-3GCSX5H5.js} +1 -1
- package/dist-cli/chunks/{chunk-EQCKGC4B.js → chunk-3TNIXR6J.js} +1 -1
- package/dist-cli/chunks/{chunk-4OWVPRZV.js → chunk-535UNERF.js} +2 -2
- package/dist-cli/chunks/{chunk-4K7BH2D4.js → chunk-5DFVKWYQ.js} +3 -3
- package/dist-cli/chunks/{chunk-AJVTY6KY.js → chunk-5NW6W7YF.js} +1 -1
- package/dist-cli/chunks/{chunk-XQ2OBHBE.js → chunk-5ZYANOOI.js} +2 -2
- package/dist-cli/chunks/{chunk-ELJLF4SG.js → chunk-6A7IWFXR.js} +63 -62
- package/dist-cli/chunks/{chunk-73UZXB4B.js → chunk-6LG7WJMD.js} +2 -2
- package/dist-cli/chunks/{chunk-OOOR7NT2.js → chunk-7YZHI7V6.js} +1 -1
- package/dist-cli/chunks/{chunk-TL7SIZ7S.js → chunk-CMAANHYQ.js} +1 -1
- package/dist-cli/chunks/{chunk-7NWNTUJF.js → chunk-CQAQTU5K.js} +1 -1
- package/dist-cli/chunks/{chunk-C3DPQZ4J.js → chunk-CSJS4MRN.js} +2 -2
- package/dist-cli/chunks/{chunk-QMSJR5R2.js → chunk-DKW7Q4F3.js} +2 -2
- package/dist-cli/chunks/{chunk-EQ7TFQ2F.js → chunk-EDWDFOPL.js} +1 -1
- package/dist-cli/chunks/{chunk-WNVNU2OW.js → chunk-FF5KD3BS.js} +2 -2
- package/dist-cli/chunks/{chunk-PPKKA5VW.js → chunk-FO52BFW4.js} +2 -2
- package/dist-cli/chunks/chunk-I3JMONYJ.js +2 -0
- package/dist-cli/chunks/{chunk-7YHDJLO2.js → chunk-JB467MUR.js} +45 -45
- package/dist-cli/chunks/{chunk-HYPJW65U.js → chunk-JBYW57OA.js} +2 -2
- package/dist-cli/chunks/{chunk-SQX5CAYG.js → chunk-JITAVV2G.js} +1 -1
- package/dist-cli/chunks/{chunk-V2GQ4WXJ.js → chunk-JMILXXI4.js} +2 -2
- package/dist-cli/chunks/chunk-KGYC4SZA.js +2 -0
- package/dist-cli/chunks/{chunk-BKBL6K2G.js → chunk-KQYOS5SM.js} +1 -1
- package/dist-cli/chunks/{chunk-VH7F45CN.js → chunk-M6GOCS27.js} +1 -1
- package/dist-cli/chunks/{chunk-3HXQ7MJK.js → chunk-NBBH2PVW.js} +2 -2
- package/dist-cli/chunks/chunk-NIRJVBXJ.js +1 -0
- package/dist-cli/chunks/{chunk-SQZAC7C4.js → chunk-NLH7FNSG.js} +1 -1
- package/dist-cli/chunks/{chunk-RIXUH3NK.js → chunk-OBHJKTWA.js} +2 -2
- package/dist-cli/chunks/{chunk-KU6MSPAH.js → chunk-OCTDP37S.js} +2 -2
- package/dist-cli/chunks/{chunk-SFGUPL2X.js → chunk-P7IKKZTG.js} +2 -2
- package/dist-cli/chunks/{chunk-5XCXOLG2.js → chunk-PVMX5UNR.js} +2 -2
- package/dist-cli/chunks/chunk-QUYC7CVV.js +1 -0
- package/dist-cli/chunks/{chunk-YCIA4BHJ.js → chunk-STHMWSVN.js} +2 -2
- package/dist-cli/chunks/{chunk-AWSQUOAS.js → chunk-TSNBQ4ZV.js} +10 -10
- package/dist-cli/chunks/{chunk-BCBNVJVG.js → chunk-UWSP2AT7.js} +1 -1
- package/dist-cli/chunks/{chunk-RF4R2U46.js → chunk-V7CFSKMC.js} +2 -2
- package/dist-cli/chunks/{chunk-TK3OJSEO.js → chunk-VFGAEMSI.js} +2 -2
- package/dist-cli/chunks/{chunk-4OPRODFA.js → chunk-VUYCS6QI.js} +2 -2
- package/dist-cli/chunks/chunk-XB4QIINM.js +24 -0
- package/dist-cli/chunks/{chunk-P7WDNKOS.js → chunk-XFJYEKYK.js} +3 -3
- package/dist-cli/chunks/{chunk-SV7FOGJ3.js → chunk-XZE53P4L.js} +2 -2
- package/dist-cli/chunks/chunk-Y7BXSTVX.js +1 -0
- package/dist-cli/chunks/cli-version-TGWWTYQX.js +2 -0
- package/dist-cli/chunks/{compat-FWSEEGEH.js → compat-I2U3P4KP.js} +3 -3
- package/dist-cli/chunks/{config-CYI2WAGP.js → config-S73CCGP5.js} +2 -2
- package/dist-cli/chunks/{control-UXY7YQVX.js → control-QR6MY7RA.js} +2 -2
- package/dist-cli/chunks/{cpu-profile-IKAE3KTY.js → cpu-profile-RFYCTVAF.js} +2 -2
- package/dist-cli/chunks/{daemon-ZUMF53YB.js → daemon-D5MV2B22.js} +2 -2
- package/dist-cli/chunks/{debug-P6KULKKS.js → debug-ZYEI75AG.js} +3 -3
- package/dist-cli/chunks/{detox-SPWAZCYG.js → detox-J5IH52RV.js} +2 -2
- package/dist-cli/chunks/{device-JWEPK6I2.js → device-NOBLSUOD.js} +2 -2
- package/dist-cli/chunks/{diagnose-IZODTXV2.js → diagnose-B6J5ZUHV.js} +2 -2
- package/dist-cli/chunks/drivers-RRHVOU6S.js +2 -0
- package/dist-cli/chunks/{electron-R5GP6RVB.js → electron-PSX4KDCC.js} +3 -3
- package/dist-cli/chunks/flow-FWNVFKMP.js +2 -0
- package/dist-cli/chunks/{hints-DYDNYX7N.js → hints-ZE4I3YO3.js} +2 -2
- package/dist-cli/chunks/{home-paths-GLMX5OKL.js → home-paths-N76MJE3D.js} +2 -2
- package/dist-cli/chunks/{inspect-FJOPCTY2.js → inspect-V2TXTDOG.js} +3 -3
- package/dist-cli/chunks/install-4PINRR2O.js +2 -0
- package/dist-cli/chunks/{install-desktop-YPJZMZM5.js → install-desktop-6ZRTRRCU.js} +3 -3
- package/dist-cli/chunks/{keys-GSYPHWNY.js → keys-L2RN4URM.js} +2 -2
- package/dist-cli/chunks/{launch-4G2PKW5X.js → launch-BJGXPNZR.js} +3 -3
- package/dist-cli/chunks/{login-KJQGHA64.js → login-HYNEMAYR.js} +4 -4
- package/dist-cli/chunks/{logout-XM2SYH5C.js → logout-AO4YS27T.js} +2 -2
- package/dist-cli/chunks/{maestro-EOWGI7DG.js → maestro-PRACYFKV.js} +2 -2
- package/dist-cli/chunks/{preview-F73TKK37.js → preview-ZTANXVEK.js} +2 -2
- package/dist-cli/chunks/{profile-22FDKBUO.js → profile-FNMAGUDB.js} +2 -2
- package/dist-cli/chunks/{react-5L6VPFUP.js → react-6ZV2FQIM.js} +2 -2
- package/dist-cli/chunks/{record-JZXCQ4IN.js → record-MLFVJZ6Y.js} +2 -2
- package/dist-cli/chunks/runtime-5762IE56.js +2 -0
- package/dist-cli/chunks/{runtime-delivery-LXUM3R4A.js → runtime-delivery-ATYW2SQR.js} +2 -2
- package/dist-cli/chunks/{screenshot-HDRRG33Q.js → screenshot-UOMYMFZ4.js} +2 -2
- package/dist-cli/chunks/{screenshot-mode-WY63LZIX.js → screenshot-mode-MWSVD4YG.js} +2 -2
- package/dist-cli/chunks/{screenshots-MPV2ENL5.js → screenshots-GSA3VCWB.js} +2 -2
- package/dist-cli/chunks/server-YPFC6POG.js +40 -0
- package/dist-cli/chunks/setup-repo-QBQ4VWFO.js +2 -0
- package/dist-cli/chunks/{skills-BQ73YOBF.js → skills-YE5OPWMQ.js} +2 -2
- package/dist-cli/chunks/{start-2WU4W6ZU.js → start-BSSQ5U2V.js} +4 -4
- package/dist-cli/chunks/store-EG4SONAH.js +2 -0
- package/dist-cli/chunks/telemetry-XXN4LRDS.js +2 -0
- package/dist-cli/chunks/{test-OVO4CQTG.js → test-5JMLBH2O.js} +3 -3
- package/dist-cli/chunks/{three-mode-BKM3KFM7.js → three-mode-TRBWZJQY.js} +2 -2
- package/dist-cli/chunks/{timeline-MDXGEDQL.js → timeline-YMZPIEB4.js} +2 -2
- package/dist-cli/chunks/{upgrade-JGQABWVF.js → upgrade-JLAS7FIF.js} +2 -2
- package/dist-cli/chunks/upload-K6UNCFQH.js +2 -0
- package/dist-cli/chunks/{web-WYFAYQ72.js → web-D6S5UXOO.js} +2 -2
- package/dist-cli/chunks/{what-happened-PZW2KW6A.js → what-happened-65NXWU2S.js} +2 -2
- package/dist-cli/chunks/{whoami-7ATWJQS6.js → whoami-6BSB6FQC.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +15 -0
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +244 -35
- package/dist-lib/host/fetch-proxy-handler.cjs +24 -4
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +33 -0
- package/dist-lib/host/websocket-proxy.cjs +207 -0
- package/dist-lib/index.cjs +136 -138
- package/dist-lib/metro.cjs +31 -26
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/demo-app-registry.cjs +14 -3
- package/dist-lib/scripts/dev-server-scanner.cjs +14 -3
- package/dist-lib/skills.cjs +9737 -76
- package/dist-lib/vite.cjs +129 -39
- package/package.json +8 -6
- package/scripts/demo-app-registry.ts +17 -1
- package/src/host/bridge-host.ts +8 -1
- package/src/host/fetch-proxy-handler.ts +26 -3
- package/src/host/websocket-proxy.ts +201 -0
- package/src/metro-plugin.ts +9 -77
- package/src/runtime-assets.ts +84 -0
- package/src/skills/builtin/compat-check.ts +1 -1
- package/src/vite-plugin-one.ts +60 -58
- package/dist-cli/chunks/auto-bootstrap-NYYSMTIM.js +0 -2
- package/dist-cli/chunks/beta-4K2SQACK.js +0 -2
- package/dist-cli/chunks/chunk-67ZZ2CM5.js +0 -1
- package/dist-cli/chunks/chunk-D3ZSBIIY.js +0 -2
- package/dist-cli/chunks/chunk-FUCGLWNN.js +0 -1
- package/dist-cli/chunks/chunk-IILJQCZA.js +0 -2
- package/dist-cli/chunks/chunk-PS2G44GT.js +0 -24
- package/dist-cli/chunks/chunk-ZSMMJMPA.js +0 -1
- package/dist-cli/chunks/cli-version-QB4VH24H.js +0 -2
- package/dist-cli/chunks/drivers-MK6WJKBC.js +0 -2
- package/dist-cli/chunks/flow-6O4GEOPJ.js +0 -2
- package/dist-cli/chunks/install-A3TUGGHN.js +0 -2
- package/dist-cli/chunks/runtime-EEBX7CFV.js +0 -2
- package/dist-cli/chunks/server-5LBMCJ3G.js +0 -35
- package/dist-cli/chunks/setup-repo-SZSYNKNI.js +0 -2
- package/dist-cli/chunks/store-RE45SUBF.js +0 -2
- package/dist-cli/chunks/telemetry-DG6GJLCP.js +0 -2
- package/dist-cli/chunks/upload-UJNUA4ZV.js +0 -2
- package/dist-lib/vite-base.cjs +0 -6937
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.1.
|
|
1
|
+
/*! sootsim v0.1.86 | (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,11 +36,45 @@ __export(vite_plugin_one_exports, {
|
|
|
36
36
|
sootsimPlugin: () => sootsimPlugin
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(vite_plugin_one_exports);
|
|
39
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
40
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
41
|
+
|
|
42
|
+
// src/runtime-assets.ts
|
|
39
43
|
var import_fs = __toESM(require("fs"), 1);
|
|
40
44
|
var import_path = __toESM(require("path"), 1);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
var
|
|
45
|
+
|
|
46
|
+
// src/home-paths.ts
|
|
47
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
48
|
+
var import_node_os = require("node:os");
|
|
49
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
50
|
+
var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
|
|
51
|
+
var ACTIVE_RUNTIME_FILE = "active";
|
|
52
|
+
function sootsimHomeDir() {
|
|
53
|
+
const override = process.env[SOOTSIM_HOME_ENV];
|
|
54
|
+
if (override && override.length > 0) return import_node_path.default.resolve(override);
|
|
55
|
+
return import_node_path.default.join((0, import_node_os.homedir)(), ".sootsim");
|
|
56
|
+
}
|
|
57
|
+
function runtimesDir() {
|
|
58
|
+
return import_node_path.default.join(sootsimHomeDir(), "runtimes");
|
|
59
|
+
}
|
|
60
|
+
function runtimeDir(version) {
|
|
61
|
+
return import_node_path.default.join(runtimesDir(), version);
|
|
62
|
+
}
|
|
63
|
+
function activeRuntimeFile() {
|
|
64
|
+
return import_node_path.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
|
|
65
|
+
}
|
|
66
|
+
function readActiveRuntime() {
|
|
67
|
+
try {
|
|
68
|
+
const value = import_node_fs.default.readFileSync(activeRuntimeFile(), "utf8").trim();
|
|
69
|
+
return value.length > 0 ? value : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
|
|
75
|
+
|
|
76
|
+
// src/runtime-assets.ts
|
|
77
|
+
var SOOTSIM_RUNTIME_MISSING_MESSAGE = "[sootsim] no engine runtime installed \u2014 run `sootsim setup-repo` in your project, or `sootsim runtime install`";
|
|
44
78
|
var MIME_TYPES = {
|
|
45
79
|
".js": "application/javascript",
|
|
46
80
|
".mjs": "application/javascript",
|
|
@@ -48,17 +82,64 @@ var MIME_TYPES = {
|
|
|
48
82
|
".html": "text/html",
|
|
49
83
|
".wasm": "application/wasm",
|
|
50
84
|
".json": "application/json",
|
|
85
|
+
".jpg": "image/jpeg",
|
|
86
|
+
".jpeg": "image/jpeg",
|
|
51
87
|
".png": "image/png",
|
|
52
88
|
".svg": "image/svg+xml",
|
|
89
|
+
".webp": "image/webp",
|
|
90
|
+
".glb": "model/gltf-binary",
|
|
53
91
|
".ttf": "font/ttf",
|
|
54
92
|
".otf": "font/otf",
|
|
55
93
|
".woff": "font/woff",
|
|
56
94
|
".woff2": "font/woff2",
|
|
57
95
|
".mp3": "audio/mpeg",
|
|
58
|
-
".wav": "audio/wav"
|
|
59
|
-
".jpg": "image/jpeg",
|
|
60
|
-
".webp": "image/webp"
|
|
96
|
+
".wav": "audio/wav"
|
|
61
97
|
};
|
|
98
|
+
var ROOT_RUNTIME_PATHS = [
|
|
99
|
+
"/assets/",
|
|
100
|
+
"/engine/",
|
|
101
|
+
"/engine-tenant/",
|
|
102
|
+
"/photos/",
|
|
103
|
+
"/three-mode/",
|
|
104
|
+
"/canvaskit.wasm",
|
|
105
|
+
"/fonts/",
|
|
106
|
+
"/icons/",
|
|
107
|
+
"/sounds/",
|
|
108
|
+
"/spike/",
|
|
109
|
+
"/test-wallpaper.jpg",
|
|
110
|
+
"/preview-sw.js"
|
|
111
|
+
];
|
|
112
|
+
function resolveActiveRuntimeRoot() {
|
|
113
|
+
const active = readActiveRuntime();
|
|
114
|
+
if (!active) return null;
|
|
115
|
+
const dir = runtimeDir(active);
|
|
116
|
+
if (!import_fs.default.existsSync(import_path.default.join(dir, "index.html"))) return null;
|
|
117
|
+
return dir;
|
|
118
|
+
}
|
|
119
|
+
function isRootRuntimeAssetPath(pathname) {
|
|
120
|
+
return ROOT_RUNTIME_PATHS.some((p) => pathname === p || pathname.startsWith(p));
|
|
121
|
+
}
|
|
122
|
+
function resolveRuntimeFilePath(runtimeRoot, pathname) {
|
|
123
|
+
if (!pathname.startsWith("/")) return null;
|
|
124
|
+
if (pathname.includes("\0") || pathname.includes("\\")) return null;
|
|
125
|
+
for (const segment of pathname.split("/")) {
|
|
126
|
+
if (segment === "..") return null;
|
|
127
|
+
}
|
|
128
|
+
const fullPath = import_path.default.resolve(runtimeRoot, pathname.replace(/^\/+/, ""));
|
|
129
|
+
const rootWithSep = runtimeRoot.endsWith(import_path.default.sep) ? runtimeRoot : runtimeRoot + import_path.default.sep;
|
|
130
|
+
if (!fullPath.startsWith(rootWithSep) && fullPath !== runtimeRoot) return null;
|
|
131
|
+
if (!import_fs.default.existsSync(fullPath) || !import_fs.default.statSync(fullPath).isFile()) return null;
|
|
132
|
+
return fullPath;
|
|
133
|
+
}
|
|
134
|
+
function serveRuntimeFile(res, fullPath) {
|
|
135
|
+
const ext = import_path.default.extname(fullPath);
|
|
136
|
+
res.setHeader("content-type", MIME_TYPES[ext] || "application/octet-stream");
|
|
137
|
+
res.setHeader("cache-control", "max-age=31536000,immutable");
|
|
138
|
+
import_fs.default.createReadStream(fullPath).pipe(res);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/vite-plugin-one.ts
|
|
142
|
+
var sootsimRoot = import_path2.default.resolve(import_path2.default.dirname(new URL(__sootsim_import_meta_url).pathname), "..");
|
|
62
143
|
function sootsimPlugin(options = {}) {
|
|
63
144
|
if (options.enabled === false) return [];
|
|
64
145
|
const prefix = options.prefix || "/__soot";
|
|
@@ -85,14 +166,27 @@ function sootsimPlugin(options = {}) {
|
|
|
85
166
|
});
|
|
86
167
|
server.middlewares.use((req, res, next) => {
|
|
87
168
|
const url = req.url || "";
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
169
|
+
const pathname = url.split("?")[0];
|
|
170
|
+
const runtimeRoot = resolveActiveRuntimeRoot();
|
|
171
|
+
if (!runtimeRoot) {
|
|
172
|
+
if (pathname === prefix || pathname === prefix + "/" || pathname.startsWith(prefix + "/")) {
|
|
91
173
|
res.statusCode = 500;
|
|
92
|
-
res.end(
|
|
174
|
+
res.end(SOOTSIM_RUNTIME_MISSING_MESSAGE);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
next();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (isRootRuntimeAssetPath(pathname)) {
|
|
181
|
+
const fullPath = resolveRuntimeFilePath(runtimeRoot, pathname);
|
|
182
|
+
if (fullPath) {
|
|
183
|
+
serveRuntimeFile(res, fullPath);
|
|
93
184
|
return;
|
|
94
185
|
}
|
|
95
|
-
|
|
186
|
+
}
|
|
187
|
+
if (pathname === prefix || pathname === prefix + "/") {
|
|
188
|
+
const htmlPath = import_path2.default.join(runtimeRoot, "index.html");
|
|
189
|
+
let html = import_fs2.default.readFileSync(htmlPath, "utf8");
|
|
96
190
|
html = html.replace(
|
|
97
191
|
"</head>",
|
|
98
192
|
`<script>history.replaceState(null,'','${prefix}/?bundle=${encodeURIComponent(bundleUrl)}')</script></head>`
|
|
@@ -101,31 +195,25 @@ function sootsimPlugin(options = {}) {
|
|
|
101
195
|
res.end(html);
|
|
102
196
|
return;
|
|
103
197
|
}
|
|
104
|
-
if (
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
res
|
|
111
|
-
import_fs.default.createReadStream(fullPath).pipe(res);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const publicPath = import_path.default.join(publicDir, filePath);
|
|
115
|
-
if (import_fs.default.existsSync(publicPath) && import_fs.default.statSync(publicPath).isFile()) {
|
|
116
|
-
const ext = import_path.default.extname(publicPath);
|
|
117
|
-
res.setHeader("content-type", MIME_TYPES[ext] || "application/octet-stream");
|
|
118
|
-
import_fs.default.createReadStream(publicPath).pipe(res);
|
|
198
|
+
if (pathname.startsWith(prefix + "/")) {
|
|
199
|
+
const fullPath = resolveRuntimeFilePath(
|
|
200
|
+
runtimeRoot,
|
|
201
|
+
pathname.slice(prefix.length)
|
|
202
|
+
);
|
|
203
|
+
if (fullPath) {
|
|
204
|
+
serveRuntimeFile(res, fullPath);
|
|
119
205
|
return;
|
|
120
206
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
207
|
+
const ext = import_path2.default.extname(pathname);
|
|
208
|
+
if (!ext) {
|
|
209
|
+
const htmlPath = import_path2.default.join(runtimeRoot, "index.html");
|
|
210
|
+
let html = import_fs2.default.readFileSync(htmlPath, "utf8");
|
|
211
|
+
html = html.replace(
|
|
212
|
+
"</head>",
|
|
213
|
+
`<script>history.replaceState(null,'','${prefix}/?bundle=${encodeURIComponent(bundleUrl)}')</script></head>`
|
|
214
|
+
);
|
|
215
|
+
res.setHeader("content-type", "text/html");
|
|
216
|
+
res.end(html);
|
|
129
217
|
return;
|
|
130
218
|
}
|
|
131
219
|
}
|
|
@@ -134,7 +222,9 @@ function sootsimPlugin(options = {}) {
|
|
|
134
222
|
const port = server.config.server.port || 8081;
|
|
135
223
|
const sootsimUrl = `http://localhost:${port}${prefix}/`;
|
|
136
224
|
console.log(`[sootsim] serving at ${sootsimUrl}`);
|
|
137
|
-
|
|
225
|
+
if (options.open !== false) {
|
|
226
|
+
openElectronApp(sootsimUrl);
|
|
227
|
+
}
|
|
138
228
|
}
|
|
139
229
|
}
|
|
140
230
|
];
|
|
@@ -150,10 +240,10 @@ async function openElectronApp(sootsimUrl) {
|
|
|
150
240
|
}
|
|
151
241
|
const candidates = [
|
|
152
242
|
"/Applications/sootsim.app",
|
|
153
|
-
|
|
154
|
-
|
|
243
|
+
import_path2.default.join(process.env.HOME || "", "Applications/sootsim.app"),
|
|
244
|
+
import_path2.default.join(sootsimRoot, "release/mac-arm64/sootsim.app")
|
|
155
245
|
];
|
|
156
|
-
let appPath = candidates.find((p) =>
|
|
246
|
+
let appPath = candidates.find((p) => import_fs2.default.existsSync(p));
|
|
157
247
|
if (!appPath) {
|
|
158
248
|
try {
|
|
159
249
|
const found = execSync(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sootsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
|
|
5
5
|
"author": "Tamagui LLC",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,10 +25,6 @@
|
|
|
25
25
|
"source": "./src/vite-plugin-one.ts",
|
|
26
26
|
"default": "./dist-lib/vite.cjs"
|
|
27
27
|
},
|
|
28
|
-
"./vite-base": {
|
|
29
|
-
"source": "./src/vite-plugin.ts",
|
|
30
|
-
"default": "./dist-lib/vite-base.cjs"
|
|
31
|
-
},
|
|
32
28
|
"./metro": {
|
|
33
29
|
"source": "./src/metro-plugin.ts",
|
|
34
30
|
"default": "./dist-lib/metro.cjs"
|
|
@@ -39,6 +35,7 @@
|
|
|
39
35
|
},
|
|
40
36
|
"./beta": {
|
|
41
37
|
"source": "./src/beta.ts",
|
|
38
|
+
"import": "./dist-lib/beta.mjs",
|
|
42
39
|
"default": "./dist-lib/beta.cjs"
|
|
43
40
|
},
|
|
44
41
|
"./agent-events": {
|
|
@@ -95,8 +92,13 @@
|
|
|
95
92
|
},
|
|
96
93
|
"./host/fetch-proxy-overrides": {
|
|
97
94
|
"source": "./src/host/fetch-proxy-overrides.ts",
|
|
95
|
+
"import": "./dist-lib/host/fetch-proxy-overrides.mjs",
|
|
98
96
|
"default": "./dist-lib/host/fetch-proxy-overrides.cjs"
|
|
99
97
|
},
|
|
98
|
+
"./host/websocket-proxy": {
|
|
99
|
+
"source": "./src/host/websocket-proxy.ts",
|
|
100
|
+
"default": "./dist-lib/host/websocket-proxy.cjs"
|
|
101
|
+
},
|
|
100
102
|
"./scripts/dev-server-scanner": {
|
|
101
103
|
"source": "./scripts/dev-server-scanner.ts",
|
|
102
104
|
"default": "./dist-lib/scripts/dev-server-scanner.cjs"
|
|
@@ -137,7 +139,6 @@
|
|
|
137
139
|
"postinstall": "node ./scripts/postinstall.cjs"
|
|
138
140
|
},
|
|
139
141
|
"dependencies": {
|
|
140
|
-
"@soot/compat": "workspace:*",
|
|
141
142
|
"ws": "^8.18.0"
|
|
142
143
|
},
|
|
143
144
|
"peerDependencies": {
|
|
@@ -149,6 +150,7 @@
|
|
|
149
150
|
}
|
|
150
151
|
},
|
|
151
152
|
"devDependencies": {
|
|
153
|
+
"@soot/compat": "workspace:*",
|
|
152
154
|
"@soot/sootsim-globals": "workspace:*",
|
|
153
155
|
"@soot/sootsim-skills": "workspace:*",
|
|
154
156
|
"@types/ws": "^8.5.13",
|
|
@@ -11,6 +11,11 @@ export interface DemoApp {
|
|
|
11
11
|
framework: 'expo' | 'one' | 'rock'
|
|
12
12
|
runtimeConfig?: SootSimConfig
|
|
13
13
|
sidecars?: DemoSidecar[]
|
|
14
|
+
// extra ports owned by the main demo command, derived from the chosen app
|
|
15
|
+
// port. unlike sidecars, these are not independently reusable services; the
|
|
16
|
+
// launcher clears/reserves them with the app command so stale child stacks
|
|
17
|
+
// cannot leave the app talking to the wrong backend.
|
|
18
|
+
managedPorts?: (port: number) => number[]
|
|
14
19
|
prepare?: () => void | Promise<void>
|
|
15
20
|
command: (port: number) => { cmd: string; env?: Record<string, string> }
|
|
16
21
|
disabled?: boolean
|
|
@@ -775,11 +780,22 @@ export const APPS: DemoApp[] = [
|
|
|
775
780
|
// be holding pglite locks on ~/takeout/.orez (soot can attach takeout as
|
|
776
781
|
// a project and spin up its own orez against the same data dir).
|
|
777
782
|
readyTimeoutMs: 240_000,
|
|
783
|
+
managedPorts: (p) => {
|
|
784
|
+
const offset = p - 8081
|
|
785
|
+
return [5433 + offset, 4848 + offset, 9200 + offset]
|
|
786
|
+
},
|
|
778
787
|
command: (p) => ({
|
|
779
|
-
cmd: 'bun lite',
|
|
788
|
+
cmd: 'bun lite:demo',
|
|
780
789
|
env: {
|
|
790
|
+
TAKEOUT_ENV_MODE: 'development',
|
|
781
791
|
PORT_OFFSET: String(p - 8081),
|
|
782
792
|
OREZ_DATA_DIR: `${HOME}/.cache/sootsim-demo/takeout-orez`,
|
|
793
|
+
VITE_DEMO_MODE: '1',
|
|
794
|
+
ZERO_APP_ID: 'takeout',
|
|
795
|
+
ZERO_APP_PUBLICATIONS: 'zero_takeout',
|
|
796
|
+
ZERO_CVR_MAX_CONNS: '4',
|
|
797
|
+
ZERO_NUM_SYNC_WORKERS: '2',
|
|
798
|
+
ZERO_UPSTREAM_MAX_CONNS: '8',
|
|
783
799
|
},
|
|
784
800
|
}),
|
|
785
801
|
},
|
package/src/host/bridge-host.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
isFetchProxyRequestUrl,
|
|
37
37
|
} from './fetch-proxy-handler'
|
|
38
38
|
import { openUrl as openUrlInBrowser, type OpenUrlOptions } from './open-url.ts'
|
|
39
|
+
import { handleWebSocketProxyUpgrade } from './websocket-proxy'
|
|
39
40
|
|
|
40
41
|
export interface BridgeSimInfo {
|
|
41
42
|
id: string
|
|
@@ -493,8 +494,14 @@ export class SootSimBridgeHost {
|
|
|
493
494
|
process.stderr.write(`ws bridge http error: ${String(err)}\n`)
|
|
494
495
|
})
|
|
495
496
|
this.httpServer = server
|
|
496
|
-
this.wss = new WebSocketServer({
|
|
497
|
+
this.wss = new WebSocketServer({ noServer: true })
|
|
497
498
|
this.wireWebSocketServer()
|
|
499
|
+
server.on('upgrade', (req, socket, head) => {
|
|
500
|
+
if (handleWebSocketProxyUpgrade(req, socket, head)) return
|
|
501
|
+
this.wss?.handleUpgrade(req, socket, head, (ws) => {
|
|
502
|
+
this.wss?.emit('connection', ws, req)
|
|
503
|
+
})
|
|
504
|
+
})
|
|
498
505
|
resolve()
|
|
499
506
|
})
|
|
500
507
|
})
|
|
@@ -58,6 +58,15 @@ const FETCH_PROXY_CORS_HEADERS: Record<string, string> = {
|
|
|
58
58
|
'access-control-max-age': '3600',
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
const APP_API_HEADER_REWRITES = new Set([
|
|
62
|
+
'host',
|
|
63
|
+
'origin',
|
|
64
|
+
'referer',
|
|
65
|
+
'sec-fetch-site',
|
|
66
|
+
'sec-fetch-mode',
|
|
67
|
+
'sec-fetch-dest',
|
|
68
|
+
])
|
|
69
|
+
|
|
61
70
|
function applyFetchProxyCors(res: ServerResponse) {
|
|
62
71
|
for (const [key, value] of Object.entries(FETCH_PROXY_CORS_HEADERS)) {
|
|
63
72
|
res.setHeader(key, value)
|
|
@@ -108,6 +117,22 @@ export function buildFetchProxyHeaders(
|
|
|
108
117
|
return headers
|
|
109
118
|
}
|
|
110
119
|
|
|
120
|
+
export function buildAppApiProxyHeaders(
|
|
121
|
+
reqHeaders: Record<string, string | string[] | undefined>,
|
|
122
|
+
targetUrl: URL,
|
|
123
|
+
): Record<string, string | string[]> {
|
|
124
|
+
const headers: Record<string, string | string[]> = {}
|
|
125
|
+
for (const [key, value] of Object.entries(reqHeaders)) {
|
|
126
|
+
if (!value) continue
|
|
127
|
+
if (APP_API_HEADER_REWRITES.has(key.toLowerCase())) continue
|
|
128
|
+
headers[key] = value
|
|
129
|
+
}
|
|
130
|
+
headers.host = targetUrl.host
|
|
131
|
+
headers.origin = targetUrl.origin
|
|
132
|
+
headers.referer = `${targetUrl.origin}/`
|
|
133
|
+
return headers
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
export function isFetchProxyRequestUrl(rawUrl: string | undefined): boolean {
|
|
112
137
|
return rawUrl?.startsWith('/__fetch-proxy?') || rawUrl?.startsWith('/__proxy?') || false
|
|
113
138
|
}
|
|
@@ -251,9 +276,7 @@ export function handleAppApiRequest(req: IncomingMessage, res: ServerResponse):
|
|
|
251
276
|
|
|
252
277
|
const transport = targetUrl.protocol === 'https:' ? https : http
|
|
253
278
|
|
|
254
|
-
const fwdHeaders =
|
|
255
|
-
delete fwdHeaders.host
|
|
256
|
-
fwdHeaders.host = targetUrl.host
|
|
279
|
+
const fwdHeaders = buildAppApiProxyHeaders(req.headers, targetUrl)
|
|
257
280
|
|
|
258
281
|
const proxyReq = transport.request(
|
|
259
282
|
{
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from 'ws'
|
|
2
|
+
import type { IncomingMessage } from 'http'
|
|
3
|
+
import type { Duplex } from 'stream'
|
|
4
|
+
|
|
5
|
+
export const WEBSOCKET_PROXY_PATH = '/__websocket-proxy'
|
|
6
|
+
|
|
7
|
+
const STRIP_UPSTREAM_HEADERS = new Set([
|
|
8
|
+
'host',
|
|
9
|
+
'connection',
|
|
10
|
+
'upgrade',
|
|
11
|
+
'transfer-encoding',
|
|
12
|
+
'content-length',
|
|
13
|
+
'sec-websocket-accept',
|
|
14
|
+
'sec-websocket-extensions',
|
|
15
|
+
'sec-websocket-key',
|
|
16
|
+
'sec-websocket-protocol',
|
|
17
|
+
'sec-websocket-version',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
function rejectUpgrade(socket: Duplex, status: number, message: string) {
|
|
21
|
+
try {
|
|
22
|
+
socket.write(
|
|
23
|
+
`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: ${message.length}\r\n\r\n${message}`,
|
|
24
|
+
)
|
|
25
|
+
} catch {}
|
|
26
|
+
socket.destroy()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isSameOriginUpgrade(req: IncomingMessage): boolean {
|
|
30
|
+
const origin = req.headers.origin
|
|
31
|
+
const host = req.headers.host
|
|
32
|
+
if (!origin || !host) return false
|
|
33
|
+
try {
|
|
34
|
+
return new URL(origin).host === host
|
|
35
|
+
} catch {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function decodeProxyHeaders(encoded: string | null): Record<string, string> {
|
|
41
|
+
if (!encoded) return {}
|
|
42
|
+
const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
|
|
43
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4)
|
|
44
|
+
const parsed = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'))
|
|
45
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
|
|
46
|
+
const headers: Record<string, string> = {}
|
|
47
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
48
|
+
if (value == null) continue
|
|
49
|
+
if (STRIP_UPSTREAM_HEADERS.has(key.toLowerCase())) continue
|
|
50
|
+
headers[key] = Array.isArray(value) ? value.join(', ') : String(value)
|
|
51
|
+
}
|
|
52
|
+
return headers
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getDefaultWebSocketOrigin(targetUrl: URL): string {
|
|
56
|
+
const origin = new URL(targetUrl.href)
|
|
57
|
+
origin.protocol = targetUrl.protocol === 'wss:' ? 'https:' : 'http:'
|
|
58
|
+
return origin.origin
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getRequestedProtocols(req: IncomingMessage): string[] {
|
|
62
|
+
const header = req.headers['sec-websocket-protocol']
|
|
63
|
+
const value = Array.isArray(header) ? header.join(',') : header || ''
|
|
64
|
+
return value
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((part) => part.trim())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function safeClose(ws: WebSocket, code: number, reason: string) {
|
|
71
|
+
if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
ws.close(code, reason)
|
|
76
|
+
} catch {
|
|
77
|
+
ws.terminate()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function connectProxyPair(clientWs: WebSocket, upstream: WebSocket) {
|
|
82
|
+
let closing = false
|
|
83
|
+
|
|
84
|
+
const closeBoth = (
|
|
85
|
+
source: WebSocket,
|
|
86
|
+
target: WebSocket,
|
|
87
|
+
code: number,
|
|
88
|
+
reason: Buffer,
|
|
89
|
+
) => {
|
|
90
|
+
if (closing) return
|
|
91
|
+
closing = true
|
|
92
|
+
safeClose(target, code, reason.toString())
|
|
93
|
+
if (source.readyState === WebSocket.OPEN) {
|
|
94
|
+
safeClose(source, code, reason.toString())
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clientWs.on('message', (data, isBinary) => {
|
|
99
|
+
if (upstream.readyState === WebSocket.OPEN) {
|
|
100
|
+
upstream.send(data, { binary: isBinary })
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
upstream.on('message', (data, isBinary) => {
|
|
104
|
+
if (clientWs.readyState === WebSocket.OPEN) {
|
|
105
|
+
clientWs.send(data, { binary: isBinary })
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
clientWs.on('close', (code, reason) => closeBoth(clientWs, upstream, code, reason))
|
|
110
|
+
upstream.on('close', (code, reason) => closeBoth(upstream, clientWs, code, reason))
|
|
111
|
+
clientWs.on('error', () => safeClose(upstream, 1011, 'proxy client error'))
|
|
112
|
+
upstream.on('error', () => safeClose(clientWs, 1011, 'upstream websocket error'))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createUpstreamWebSocket(
|
|
116
|
+
targetUrl: URL,
|
|
117
|
+
protocols: string[],
|
|
118
|
+
headers: Record<string, string>,
|
|
119
|
+
): WebSocket {
|
|
120
|
+
const upstreamHeaders = { ...headers }
|
|
121
|
+
if (!Object.keys(upstreamHeaders).some((key) => key.toLowerCase() === 'origin')) {
|
|
122
|
+
upstreamHeaders.origin = getDefaultWebSocketOrigin(targetUrl)
|
|
123
|
+
}
|
|
124
|
+
return new WebSocket(targetUrl.href, protocols, {
|
|
125
|
+
headers: upstreamHeaders,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isWebSocketProxyRequestUrl(rawUrl: string | undefined): boolean {
|
|
130
|
+
if (!rawUrl) return false
|
|
131
|
+
try {
|
|
132
|
+
return new URL(rawUrl, 'http://localhost').pathname === WEBSOCKET_PROXY_PATH
|
|
133
|
+
} catch {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function handleWebSocketProxyUpgrade(
|
|
139
|
+
req: IncomingMessage,
|
|
140
|
+
socket: Duplex,
|
|
141
|
+
head: Buffer,
|
|
142
|
+
): boolean {
|
|
143
|
+
if (!isWebSocketProxyRequestUrl(req.url)) return false
|
|
144
|
+
if (!isSameOriginUpgrade(req)) {
|
|
145
|
+
rejectUpgrade(socket, 403, 'forbidden websocket proxy origin')
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let targetUrl: URL
|
|
150
|
+
let headers: Record<string, string>
|
|
151
|
+
try {
|
|
152
|
+
const requestUrl = new URL(req.url || '/', 'http://localhost')
|
|
153
|
+
const target = requestUrl.searchParams.get('url')
|
|
154
|
+
if (!target) {
|
|
155
|
+
rejectUpgrade(socket, 400, 'missing websocket proxy url')
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
targetUrl = new URL(target)
|
|
159
|
+
if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
|
|
160
|
+
rejectUpgrade(socket, 400, 'invalid websocket proxy protocol')
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
headers = decodeProxyHeaders(requestUrl.searchParams.get('headers'))
|
|
164
|
+
} catch {
|
|
165
|
+
rejectUpgrade(socket, 400, 'invalid websocket proxy request')
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const protocols = getRequestedProtocols(req)
|
|
170
|
+
const upstream = createUpstreamWebSocket(targetUrl, protocols, headers)
|
|
171
|
+
let completed = false
|
|
172
|
+
socket.once('close', () => {
|
|
173
|
+
if (!completed) upstream.terminate()
|
|
174
|
+
})
|
|
175
|
+
upstream.once('open', () => {
|
|
176
|
+
if (completed) return
|
|
177
|
+
completed = true
|
|
178
|
+
const selectedProtocol = upstream.protocol
|
|
179
|
+
const proxyServer = new WebSocketServer({
|
|
180
|
+
noServer: true,
|
|
181
|
+
clientTracking: false,
|
|
182
|
+
handleProtocols(requestedProtocols) {
|
|
183
|
+
return selectedProtocol || requestedProtocols.values().next().value || false
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
proxyServer.handleUpgrade(req, socket, head, (clientWs) => {
|
|
187
|
+
connectProxyPair(clientWs, upstream)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
upstream.once('error', () => {
|
|
191
|
+
if (completed) return
|
|
192
|
+
completed = true
|
|
193
|
+
rejectUpgrade(socket, 502, 'upstream websocket error')
|
|
194
|
+
})
|
|
195
|
+
upstream.once('close', () => {
|
|
196
|
+
if (completed) return
|
|
197
|
+
completed = true
|
|
198
|
+
rejectUpgrade(socket, 502, 'upstream websocket closed')
|
|
199
|
+
})
|
|
200
|
+
return true
|
|
201
|
+
}
|