sootsim 0.0.4 → 0.1.36
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/LICENSE +21 -0
- package/README.md +4 -4
- package/dist-cli/bin.js +12 -12
- package/dist-cli/chunks/{agent-PJAOF4JS.js → agent-YZB6D3DR.js} +4 -4
- package/dist-cli/chunks/agent-wrapper-VHCVS22I.js +15 -0
- package/dist-cli/chunks/{assert-P47NW4AF.js → assert-AIVCKKLG.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-MLNTX23H.js +2 -0
- package/dist-cli/chunks/chunk-27P763IZ.js +61 -0
- package/dist-cli/chunks/chunk-3UIWOHC2.js +62 -0
- package/dist-cli/chunks/chunk-5KGFHWVR.js +1 -0
- package/dist-cli/chunks/chunk-5QIUJNT3.js +5 -0
- package/dist-cli/chunks/{chunk-W7CYWXRZ.js → chunk-6GGMKFWJ.js} +1 -1
- package/dist-cli/chunks/{chunk-EHMSE3Q3.js → chunk-6Z275LCY.js} +2 -2
- package/dist-cli/chunks/chunk-75LBYBKW.js +11 -0
- package/dist-cli/chunks/chunk-A5BRCXYE.js +2 -0
- package/dist-cli/chunks/{chunk-UQ3N6FZF.js → chunk-CYCXOAVZ.js} +2 -2
- package/dist-cli/chunks/{chunk-QIP7LYQI.js → chunk-DFN3GGH7.js} +2 -2
- package/dist-cli/chunks/chunk-EBEHZJRG.js +117 -0
- package/dist-cli/chunks/{chunk-UKYK63H6.js → chunk-EJLNUMMP.js} +1 -1
- package/dist-cli/chunks/{chunk-DQKQYPIG.js → chunk-EWSQSALM.js} +2 -2
- package/dist-cli/chunks/{chunk-BQRM4E66.js → chunk-FE7UI3MT.js} +4 -4
- package/dist-cli/chunks/chunk-G663654J.js +1 -0
- package/dist-cli/chunks/chunk-G7XQD4KC.js +4 -0
- package/dist-cli/chunks/chunk-GW7XY5KC.js +2 -0
- package/dist-cli/chunks/{chunk-QQOBLF7O.js → chunk-H2QO4TDV.js} +2 -2
- package/dist-cli/chunks/{chunk-I6XGFZPA.js → chunk-HWCKZXNJ.js} +2 -2
- package/dist-cli/chunks/{chunk-UNFERMZ3.js → chunk-HWFHBMAQ.js} +2 -2
- package/dist-cli/chunks/chunk-IJMYFYDZ.js +2 -0
- package/dist-cli/chunks/chunk-J7CTD37P.js +1 -0
- package/dist-cli/chunks/{chunk-432TMHBG.js → chunk-KAXZHEKM.js} +1 -1
- package/dist-cli/chunks/{chunk-WWDJCKMI.js → chunk-LHDWH7VS.js} +1 -1
- package/dist-cli/chunks/{chunk-VGXARPIH.js → chunk-N32NCVL2.js} +2 -2
- package/dist-cli/chunks/{chunk-XJF46GU2.js → chunk-NIZBR7EK.js} +2 -2
- package/dist-cli/chunks/{chunk-MQDPKSCK.js → chunk-NYY36OKU.js} +12 -12
- package/dist-cli/chunks/{chunk-5TTQKPGH.js → chunk-OXN2PEB7.js} +1 -1
- package/dist-cli/chunks/{chunk-SY74J6F4.js → chunk-PJL25JQV.js} +1 -1
- package/dist-cli/chunks/{chunk-4XBPZQLW.js → chunk-RMW5BO3S.js} +2 -2
- package/dist-cli/chunks/chunk-SHO54NET.js +2 -0
- package/dist-cli/chunks/chunk-SMVJOWSV.js +16 -0
- package/dist-cli/chunks/chunk-TC6V7YFC.js +3 -0
- package/dist-cli/chunks/{chunk-6SZMLFCR.js → chunk-VFDRZNPN.js} +1 -1
- package/dist-cli/chunks/{chunk-AFQBSK2J.js → chunk-YIO6S3R5.js} +1 -1
- package/dist-cli/chunks/{chunk-AUR2LTNX.js → chunk-YLIIVTTQ.js} +2 -2
- package/dist-cli/chunks/chunk-YR7BGGYE.js +2 -0
- package/dist-cli/chunks/chunk-ZEW3RF5Q.js +1 -0
- package/dist-cli/chunks/{compat-ILLJ7VDL.js → compat-Y2O2U7FL.js} +2 -2
- package/dist-cli/chunks/{config-CDIAJIIT.js → config-SRBOFUCI.js} +2 -2
- package/dist-cli/chunks/control-PL2V2O6S.js +2 -0
- package/dist-cli/chunks/daemon-IZC32PZW.js +50 -0
- package/dist-cli/chunks/{debug-6SMCTPMC.js → debug-BIDMW2PE.js} +3 -3
- package/dist-cli/chunks/demo-app-registry-5JFOUU3D.js +2 -0
- package/dist-cli/chunks/{detox-R4G5INNB.js → detox-B3FDOIS3.js} +2 -2
- package/dist-cli/chunks/{device-YSLCWS4E.js → device-ZZSI363W.js} +2 -2
- package/dist-cli/chunks/drivers-S4NGK4DB.js +2 -0
- package/dist-cli/chunks/{electron-JZOFO37G.js → electron-5YFHXEOI.js} +3 -3
- package/dist-cli/chunks/flow-JJBO6TFY.js +2 -0
- package/dist-cli/chunks/{hints-O4QR6UGI.js → hints-G5HBBV2O.js} +2 -2
- package/dist-cli/chunks/home-paths-VWC3FWA3.js +2 -0
- package/dist-cli/chunks/{inspect-DRFAUJUH.js → inspect-POOPWUQI.js} +56 -52
- package/dist-cli/chunks/install-MP6FHXNZ.js +2 -0
- package/dist-cli/chunks/install-desktop-2MYEI4FM.js +23 -0
- package/dist-cli/chunks/{install-dev-desktop-CAJHPRNP.js → install-dev-desktop-SKH3KEHY.js} +2 -2
- package/dist-cli/chunks/{keys-OWQ7SOTM.js → keys-7PNASIQR.js} +2 -2
- package/dist-cli/chunks/{launch-WUEDHSO5.js → launch-JNS47LAQ.js} +3 -3
- package/dist-cli/chunks/login-YWZWUHBS.js +26 -0
- package/dist-cli/chunks/logout-O6SXMSBP.js +2 -0
- package/dist-cli/chunks/{maestro-PMHK6EHI.js → maestro-CW6XVUKV.js} +3 -3
- package/dist-cli/chunks/{preview-4RVHA2PP.js → preview-WGKJO5FS.js} +2 -2
- package/dist-cli/chunks/{profile-3IVNHUS6.js → profile-SUOBRPIC.js} +2 -2
- package/dist-cli/chunks/{record-KEWLM5JR.js → record-QPWLYH5R.js} +2 -2
- package/dist-cli/chunks/runtime-KEMO2MSB.js +25 -0
- package/dist-cli/chunks/{screenshot-BXRAQERZ.js → screenshot-JTY46V7G.js} +2 -2
- package/dist-cli/chunks/{screenshot-mode-5IXEDIUS.js → screenshot-mode-7OYBBX6D.js} +2 -2
- package/dist-cli/chunks/{screenshots-T4MQF3TB.js → screenshots-QISKC4GD.js} +2 -2
- package/dist-cli/chunks/server-YSFJAKAV.js +34 -0
- package/dist-cli/chunks/setup-repo-LFB3HBEO.js +2 -0
- package/dist-cli/chunks/{skills-DJA6QEVR.js → skills-MO7BFNVM.js} +2 -2
- package/dist-cli/chunks/store-6MFL53I4.js +2 -0
- package/dist-cli/chunks/telemetry-CN42GMVC.js +2 -0
- package/dist-cli/chunks/{test-IWUHNFXV.js → test-XUI3KNNQ.js} +3 -3
- package/dist-cli/chunks/upload-6FUT7AX5.js +2 -0
- package/dist-cli/chunks/{whoami-MCXFWKIH.js → whoami-TQFHY42N.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +3 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +2 -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/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +3 -1
- package/dist-lib/home-paths.cjs +29 -3
- package/dist-lib/host/bridge-host.cjs +499 -59
- package/dist-lib/index.cjs +2 -2
- package/dist-lib/metro.cjs +2 -2
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/vite-base.cjs +800 -102
- package/dist-lib/vite.cjs +1 -1
- package/package.json +5 -3
- package/dist-cli/chunks/agent-wrapper-STO7PLQD.js +0 -15
- package/dist-cli/chunks/auto-bootstrap-SC2LMI2H.js +0 -2
- package/dist-cli/chunks/chunk-47S5DXXX.js +0 -11
- package/dist-cli/chunks/chunk-4VXB2DBA.js +0 -119
- package/dist-cli/chunks/chunk-C3QLIYCS.js +0 -16
- package/dist-cli/chunks/chunk-F4ARVCRR.js +0 -1
- package/dist-cli/chunks/chunk-HAKR72LJ.js +0 -2
- package/dist-cli/chunks/chunk-HGFIS26A.js +0 -2
- package/dist-cli/chunks/chunk-MZPAJ5PQ.js +0 -1
- package/dist-cli/chunks/chunk-OAHMYSMD.js +0 -2
- package/dist-cli/chunks/chunk-W3TYN64D.js +0 -62
- package/dist-cli/chunks/chunk-WRF43M33.js +0 -4
- package/dist-cli/chunks/chunk-WVBPATRA.js +0 -2
- package/dist-cli/chunks/chunk-ZF5FCFLD.js +0 -2
- package/dist-cli/chunks/chunk-ZKNI5MRD.js +0 -1
- package/dist-cli/chunks/control-7QGKUCAX.js +0 -2
- package/dist-cli/chunks/daemon-4BLYGM5N.js +0 -49
- package/dist-cli/chunks/demo-app-registry-HLI5UGGI.js +0 -2
- package/dist-cli/chunks/drivers-YIXRFFBQ.js +0 -2
- package/dist-cli/chunks/flow-L7X5FGIN.js +0 -2
- package/dist-cli/chunks/home-paths-4YJJYGR6.js +0 -2
- package/dist-cli/chunks/install-BATRTWRI.js +0 -65
- package/dist-cli/chunks/install-desktop-6X474IQ3.js +0 -23
- package/dist-cli/chunks/login-54YJ2KH6.js +0 -26
- package/dist-cli/chunks/logout-XECXLEXW.js +0 -2
- package/dist-cli/chunks/runtime-PJKHEB36.js +0 -25
- package/dist-cli/chunks/server-CIP3LH45.js +0 -29
- package/dist-cli/chunks/store-SPC247DB.js +0 -2
- package/dist-cli/chunks/upload-UPD2RSYF.js +0 -2
package/dist-lib/vite-base.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.
|
|
1
|
+
/*! sootsim v0.1.36 | (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;
|
|
@@ -62,6 +62,9 @@ function cacheDir() {
|
|
|
62
62
|
function daemonLockfilePath() {
|
|
63
63
|
return import_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
|
|
64
64
|
}
|
|
65
|
+
function configFilePath() {
|
|
66
|
+
return import_path.default.join(sootsimHomeDir(), CONFIG_FILE);
|
|
67
|
+
}
|
|
65
68
|
function ensureSootsimHome() {
|
|
66
69
|
import_fs.default.mkdirSync(sootsimHomeDir(), { recursive: true });
|
|
67
70
|
import_fs.default.mkdirSync(runtimesDir(), { recursive: true });
|
|
@@ -169,7 +172,7 @@ function removeDaemonLockfile() {
|
|
|
169
172
|
} catch {
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
|
-
var import_fs, import_os, import_path, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
|
|
175
|
+
var import_fs, import_os, import_path, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
|
|
173
176
|
var init_home_paths = __esm({
|
|
174
177
|
"src/home-paths.ts"() {
|
|
175
178
|
"use strict";
|
|
@@ -179,11 +182,230 @@ var init_home_paths = __esm({
|
|
|
179
182
|
SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
|
|
180
183
|
ACTIVE_RUNTIME_FILE = "active";
|
|
181
184
|
DAEMON_LOCKFILE = "daemon.json";
|
|
185
|
+
CONFIG_FILE = "config.json";
|
|
182
186
|
DAEMON_HEARTBEAT_STALE_MS = 3e4;
|
|
183
187
|
DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
|
|
184
188
|
}
|
|
185
189
|
});
|
|
186
190
|
|
|
191
|
+
// src/runtime-delivery.ts
|
|
192
|
+
function readConfig() {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(import_fs2.default.readFileSync(configFilePath(), "utf8"));
|
|
195
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
196
|
+
} catch {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function resolveRuntimeCdnOrigin(input) {
|
|
201
|
+
const config = readConfig();
|
|
202
|
+
const value = input || process.env[RUNTIME_CDN_ORIGIN_ENV] || config.cdnOrigin || DEFAULT_RUNTIME_CDN_ORIGIN;
|
|
203
|
+
return value.replace(/\/+$/, "");
|
|
204
|
+
}
|
|
205
|
+
function resolveRuntimeChannel(input) {
|
|
206
|
+
const config = readConfig();
|
|
207
|
+
return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
|
|
208
|
+
}
|
|
209
|
+
function runtimeManifestUrl(cdnOrigin) {
|
|
210
|
+
const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
|
|
211
|
+
url.searchParams.set("t", String(Date.now()));
|
|
212
|
+
return url.toString();
|
|
213
|
+
}
|
|
214
|
+
function runtimeTarballUrl(version, cdnOrigin) {
|
|
215
|
+
return `${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/sootsim-runtime-${version}.tar.gz`;
|
|
216
|
+
}
|
|
217
|
+
async function fetchRuntimeManifest(cdnOrigin) {
|
|
218
|
+
const url = runtimeManifestUrl(cdnOrigin);
|
|
219
|
+
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
throw new Error(`manifest fetch failed: ${res.status} ${res.statusText} (${url})`);
|
|
222
|
+
}
|
|
223
|
+
return await res.json();
|
|
224
|
+
}
|
|
225
|
+
function resolveRuntimeVersion(manifest, opts = {}) {
|
|
226
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
227
|
+
const version = opts.version || manifest.channels[channel]?.latest;
|
|
228
|
+
if (!version) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`no version specified and channel '${channel}' has no latest entry in the manifest`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const entry = manifest.versions[version];
|
|
234
|
+
if (!entry) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`version ${version} not found in manifest; available: ${Object.keys(manifest.versions).slice(-10).join(", ") || "(none)"}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return { version, channel, entry };
|
|
240
|
+
}
|
|
241
|
+
async function installRuntime(opts = {}) {
|
|
242
|
+
ensureSootsimHome();
|
|
243
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
244
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
245
|
+
const { version, channel, entry } = resolveRuntimeVersion(manifest, opts);
|
|
246
|
+
const destDir = runtimeDir(version);
|
|
247
|
+
const setActive = opts.setActive !== false;
|
|
248
|
+
if (!opts.force && import_fs2.default.existsSync(import_path2.default.join(destDir, "index.html"))) {
|
|
249
|
+
if (setActive) writeActiveRuntime(version);
|
|
250
|
+
return {
|
|
251
|
+
version,
|
|
252
|
+
channel,
|
|
253
|
+
cdnOrigin,
|
|
254
|
+
runtimeDir: destDir,
|
|
255
|
+
installed: false,
|
|
256
|
+
activated: setActive,
|
|
257
|
+
manifest
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const tarUrl = entry.tarball || runtimeTarballUrl(version, cdnOrigin);
|
|
261
|
+
const tarCachePath = import_path2.default.join(cacheDir(), `sootsim-runtime-${version}.tar.gz`);
|
|
262
|
+
process.stderr.write(`sootsim: downloading runtime ${version}\u2026
|
|
263
|
+
`);
|
|
264
|
+
await downloadToFile(tarUrl, tarCachePath);
|
|
265
|
+
process.stderr.write(`sootsim: extracting runtime ${version}\u2026
|
|
266
|
+
`);
|
|
267
|
+
const actualSha = await sha256File(tarCachePath);
|
|
268
|
+
if (actualSha !== entry.sha256) {
|
|
269
|
+
import_fs2.default.rmSync(tarCachePath, { force: true });
|
|
270
|
+
throw new Error(
|
|
271
|
+
`sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const tmpDir = import_path2.default.join(import_path2.default.dirname(destDir), `.installing-${version}-${process.pid}`);
|
|
275
|
+
import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
276
|
+
import_fs2.default.mkdirSync(tmpDir, { recursive: true });
|
|
277
|
+
try {
|
|
278
|
+
await extractTarball(tarCachePath, tmpDir);
|
|
279
|
+
if (!import_fs2.default.existsSync(import_path2.default.join(tmpDir, "index.html"))) {
|
|
280
|
+
throw new Error(`extracted tarball for runtime ${version} is missing index.html`);
|
|
281
|
+
}
|
|
282
|
+
import_fs2.default.rmSync(destDir, { recursive: true, force: true });
|
|
283
|
+
import_fs2.default.renameSync(tmpDir, destDir);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
if (setActive) writeActiveRuntime(version);
|
|
289
|
+
return {
|
|
290
|
+
version,
|
|
291
|
+
channel,
|
|
292
|
+
cdnOrigin,
|
|
293
|
+
runtimeDir: destDir,
|
|
294
|
+
installed: true,
|
|
295
|
+
activated: setActive,
|
|
296
|
+
manifest
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function updateRuntimeToLatest(opts = {}) {
|
|
300
|
+
ensureSootsimHome();
|
|
301
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
302
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
303
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
304
|
+
const latestVersion = manifest.channels[channel]?.latest;
|
|
305
|
+
if (!latestVersion) {
|
|
306
|
+
return {
|
|
307
|
+
checked: true,
|
|
308
|
+
updated: false,
|
|
309
|
+
reason: `channel '${channel}' has no latest runtime`,
|
|
310
|
+
activeVersion: readActiveRuntime()
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const entry = manifest.versions[latestVersion];
|
|
314
|
+
if (!entry) {
|
|
315
|
+
return {
|
|
316
|
+
checked: true,
|
|
317
|
+
updated: false,
|
|
318
|
+
reason: `manifest is missing version ${latestVersion}`,
|
|
319
|
+
activeVersion: readActiveRuntime(),
|
|
320
|
+
latestVersion
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const activeVersion = readActiveRuntime();
|
|
324
|
+
const activeDir = activeVersion ? runtimeDir(activeVersion) : null;
|
|
325
|
+
const activeInstalled = activeDir ? import_fs2.default.existsSync(import_path2.default.join(activeDir, "index.html")) : false;
|
|
326
|
+
const shouldInstall = !activeVersion || !activeInstalled || compareSemver(latestVersion, activeVersion) > 0;
|
|
327
|
+
if (!shouldInstall) {
|
|
328
|
+
return {
|
|
329
|
+
checked: true,
|
|
330
|
+
updated: false,
|
|
331
|
+
reason: "active runtime is current",
|
|
332
|
+
activeVersion,
|
|
333
|
+
latestVersion
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const install = await installRuntime({
|
|
337
|
+
version: latestVersion,
|
|
338
|
+
channel,
|
|
339
|
+
cdnOrigin,
|
|
340
|
+
setActive: false
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
checked: true,
|
|
344
|
+
updated: true,
|
|
345
|
+
activeVersion: latestVersion,
|
|
346
|
+
latestVersion,
|
|
347
|
+
install
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async function downloadToFile(url, destPath) {
|
|
351
|
+
const res = await fetch(url);
|
|
352
|
+
if (!res.ok || !res.body) {
|
|
353
|
+
throw new Error(`download failed: ${res.status} ${res.statusText} (${url})`);
|
|
354
|
+
}
|
|
355
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(destPath), { recursive: true });
|
|
356
|
+
const tmp = `${destPath}.partial`;
|
|
357
|
+
try {
|
|
358
|
+
await (0, import_promises.pipeline)(
|
|
359
|
+
import_stream.Readable.fromWeb(res.body),
|
|
360
|
+
import_fs2.default.createWriteStream(tmp)
|
|
361
|
+
);
|
|
362
|
+
import_fs2.default.renameSync(tmp, destPath);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
try {
|
|
365
|
+
import_fs2.default.unlinkSync(tmp);
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function sha256File(filePath) {
|
|
372
|
+
return new Promise((resolve2, reject) => {
|
|
373
|
+
const hash = import_crypto.default.createHash("sha256");
|
|
374
|
+
const stream = import_fs2.default.createReadStream(filePath);
|
|
375
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
376
|
+
stream.on("error", reject);
|
|
377
|
+
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function extractTarball(tarPath, destDir) {
|
|
381
|
+
return new Promise((resolve2, reject) => {
|
|
382
|
+
const child = (0, import_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
|
|
383
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
384
|
+
});
|
|
385
|
+
child.on("error", reject);
|
|
386
|
+
child.on("exit", (code) => {
|
|
387
|
+
if (code === 0) resolve2();
|
|
388
|
+
else reject(new Error(`tar exited with code ${code}`));
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
var import_child_process, import_crypto, import_fs2, import_path2, import_stream, import_promises, DEFAULT_RUNTIME_CDN_ORIGIN, RUNTIME_CDN_ORIGIN_ENV, RUNTIME_CHANNEL_ENV;
|
|
393
|
+
var init_runtime_delivery = __esm({
|
|
394
|
+
"src/runtime-delivery.ts"() {
|
|
395
|
+
"use strict";
|
|
396
|
+
import_child_process = require("child_process");
|
|
397
|
+
import_crypto = __toESM(require("crypto"), 1);
|
|
398
|
+
import_fs2 = __toESM(require("fs"), 1);
|
|
399
|
+
import_path2 = __toESM(require("path"), 1);
|
|
400
|
+
import_stream = require("stream");
|
|
401
|
+
import_promises = require("stream/promises");
|
|
402
|
+
init_home_paths();
|
|
403
|
+
DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
|
|
404
|
+
RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
|
|
405
|
+
RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
187
409
|
// src/config.ts
|
|
188
410
|
function hasOwnKeys(value) {
|
|
189
411
|
return !!value && Object.keys(value).length > 0;
|
|
@@ -220,11 +442,13 @@ function normalizeNativeDevBundleUrl(bundleUrl) {
|
|
|
220
442
|
try {
|
|
221
443
|
const isAbsolute = isAbsoluteHttpUrl(bundleUrl);
|
|
222
444
|
const parsed = new URL(bundleUrl, "http://soot.local");
|
|
445
|
+
parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, ".bundle");
|
|
223
446
|
if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl;
|
|
224
447
|
if (!parsed.searchParams.has("dev")) parsed.searchParams.set("dev", "true");
|
|
225
448
|
if (!parsed.searchParams.has("minify")) {
|
|
226
449
|
parsed.searchParams.set("minify", "false");
|
|
227
450
|
}
|
|
451
|
+
parsed.searchParams.delete("transform.bytecode");
|
|
228
452
|
if (isAbsolute) return parsed.toString();
|
|
229
453
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
230
454
|
} catch {
|
|
@@ -637,10 +861,10 @@ function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
|
637
861
|
sock.connect(port, "localhost");
|
|
638
862
|
});
|
|
639
863
|
}
|
|
640
|
-
function httpGet(port,
|
|
864
|
+
function httpGet(port, path8, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
|
|
641
865
|
return new Promise((resolve2) => {
|
|
642
866
|
const req = import_http.default.request(
|
|
643
|
-
{ hostname: "localhost", port, path:
|
|
867
|
+
{ hostname: "localhost", port, path: path8, method, timeout, headers },
|
|
644
868
|
(res) => {
|
|
645
869
|
let body = "";
|
|
646
870
|
res.on("data", (c) => body += c.toString());
|
|
@@ -761,9 +985,9 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
|
|
|
761
985
|
if (client.name) result.projectName = client.name;
|
|
762
986
|
if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
|
|
763
987
|
if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
|
|
764
|
-
const
|
|
765
|
-
if (
|
|
766
|
-
result.bundleUrl = withRuntimeConfig(result.port,
|
|
988
|
+
const launchUrl2 = manifest?.launchAsset?.url;
|
|
989
|
+
if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
|
|
990
|
+
result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
|
|
767
991
|
}
|
|
768
992
|
const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
|
|
769
993
|
if (rawIconUrl) {
|
|
@@ -830,13 +1054,13 @@ async function probePort(port, buildIconProxyUrl) {
|
|
|
830
1054
|
const manifest = JSON.parse(manifestRes.body);
|
|
831
1055
|
const client = manifest?.extra?.expoClient || {};
|
|
832
1056
|
if (client.name) {
|
|
833
|
-
const
|
|
1057
|
+
const launchUrl2 = manifest?.launchAsset?.url || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
|
|
834
1058
|
knownNonPatched.add(port);
|
|
835
1059
|
return applyManifest(
|
|
836
1060
|
{
|
|
837
1061
|
port,
|
|
838
1062
|
framework: "one",
|
|
839
|
-
bundleUrl: withRuntimeConfig(port,
|
|
1063
|
+
bundleUrl: withRuntimeConfig(port, launchUrl2),
|
|
840
1064
|
hmrUrl: `ws://localhost:${port}/hot`,
|
|
841
1065
|
lastSeen: Date.now()
|
|
842
1066
|
},
|
|
@@ -945,18 +1169,18 @@ async function scanDevServers(opts = {}) {
|
|
|
945
1169
|
}
|
|
946
1170
|
return results.filter((r) => !isSootSelfServer(r));
|
|
947
1171
|
}
|
|
948
|
-
var
|
|
1172
|
+
var import_child_process2, import_http, import_net, import_util, execP, TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, knownNonPatched, knownNonExpo, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
|
|
949
1173
|
var init_dev_server_scanner = __esm({
|
|
950
1174
|
"scripts/dev-server-scanner.ts"() {
|
|
951
1175
|
"use strict";
|
|
952
|
-
|
|
1176
|
+
import_child_process2 = require("child_process");
|
|
953
1177
|
import_http = __toESM(require("http"), 1);
|
|
954
1178
|
import_net = __toESM(require("net"), 1);
|
|
955
1179
|
import_util = require("util");
|
|
956
1180
|
init_config();
|
|
957
1181
|
init_native_dev_bundle_url();
|
|
958
1182
|
init_demo_app_registry();
|
|
959
|
-
execP = (0, import_util.promisify)(
|
|
1183
|
+
execP = (0, import_util.promisify)(import_child_process2.exec);
|
|
960
1184
|
TIMEOUT_MS = 250;
|
|
961
1185
|
TCP_GATE_MS = 120;
|
|
962
1186
|
FALLBACK_PORTS = [
|
|
@@ -1582,6 +1806,7 @@ async function startSession(opts) {
|
|
|
1582
1806
|
];
|
|
1583
1807
|
if (opts.codexBin) wrapperArgs.push("--codex-bin", opts.codexBin);
|
|
1584
1808
|
if (opts.claudeBin) wrapperArgs.push("--claude-bin", opts.claudeBin);
|
|
1809
|
+
if (opts.freshThread) wrapperArgs.push("--fresh-thread");
|
|
1585
1810
|
if (claudeSessionUuid) {
|
|
1586
1811
|
wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
|
|
1587
1812
|
}
|
|
@@ -2215,6 +2440,161 @@ var init_agent_host = __esm({
|
|
|
2215
2440
|
}
|
|
2216
2441
|
});
|
|
2217
2442
|
|
|
2443
|
+
// src/host/open-url.ts
|
|
2444
|
+
function expandHome(candidate) {
|
|
2445
|
+
return candidate.startsWith("~/") ? (0, import_path3.join)((0, import_os2.homedir)(), candidate.slice(2)) : candidate;
|
|
2446
|
+
}
|
|
2447
|
+
function firstExisting(candidates) {
|
|
2448
|
+
for (const candidate of candidates) {
|
|
2449
|
+
const expanded = expandHome(candidate);
|
|
2450
|
+
if ((0, import_fs3.existsSync)(expanded)) return expanded;
|
|
2451
|
+
}
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
function lookupExecutable(command, platform) {
|
|
2455
|
+
try {
|
|
2456
|
+
const tool = platform === "win32" ? "where" : "which";
|
|
2457
|
+
const out = (0, import_child_process3.execFileSync)(tool, [command], {
|
|
2458
|
+
encoding: "utf8",
|
|
2459
|
+
timeout: 1500,
|
|
2460
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2461
|
+
}).trim();
|
|
2462
|
+
return out ? out.split(/\r?\n/)[0] : null;
|
|
2463
|
+
} catch {
|
|
2464
|
+
return null;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function resolveChromiumBinary(platform = process.platform) {
|
|
2468
|
+
const envCandidate = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
|
|
2469
|
+
if (envCandidate && (0, import_fs3.existsSync)(envCandidate)) return envCandidate;
|
|
2470
|
+
const direct = platform === "darwin" ? firstExisting(MAC_CHROMIUM_CANDIDATES) : platform === "win32" ? firstExisting(WINDOWS_CHROMIUM_CANDIDATES) : firstExisting(LINUX_CHROMIUM_CANDIDATES);
|
|
2471
|
+
if (direct) return direct;
|
|
2472
|
+
const commands = platform === "win32" ? WINDOWS_CHROMIUM_COMMANDS : UNIX_CHROMIUM_COMMANDS;
|
|
2473
|
+
for (const command of commands) {
|
|
2474
|
+
const found = lookupExecutable(command, platform);
|
|
2475
|
+
if (found) return found;
|
|
2476
|
+
}
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
function buildOpenUrlCommand(url, options = {}) {
|
|
2480
|
+
if (!url) throw new Error("openUrl requires a url");
|
|
2481
|
+
const platform = options.platform ?? process.platform;
|
|
2482
|
+
if (options.newWindow) {
|
|
2483
|
+
return buildChromiumUrlCommand(url, { ...options, platform, newWindow: true });
|
|
2484
|
+
}
|
|
2485
|
+
if (platform === "darwin") {
|
|
2486
|
+
return {
|
|
2487
|
+
command: "open",
|
|
2488
|
+
args: options.background === false ? [url] : ["-g", url],
|
|
2489
|
+
via: "system"
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
if (platform === "win32") {
|
|
2493
|
+
return {
|
|
2494
|
+
command: "cmd",
|
|
2495
|
+
args: ["/c", "start", "", url],
|
|
2496
|
+
via: "system"
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
return {
|
|
2500
|
+
command: "xdg-open",
|
|
2501
|
+
args: [url],
|
|
2502
|
+
via: "system"
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function buildChromiumUrlCommand(url, options = {}) {
|
|
2506
|
+
if (!url) throw new Error("openUrl requires a url");
|
|
2507
|
+
const platform = options.platform ?? process.platform;
|
|
2508
|
+
const chromiumBinary = "chromiumBinary" in options ? options.chromiumBinary : resolveChromiumBinary(platform);
|
|
2509
|
+
if (!chromiumBinary) {
|
|
2510
|
+
throw new Error("browser launch requires Chrome, Chromium, Edge, Brave, or Arc");
|
|
2511
|
+
}
|
|
2512
|
+
const args = [];
|
|
2513
|
+
if (options.headless) {
|
|
2514
|
+
args.push("--headless=new");
|
|
2515
|
+
} else if (options.newWindow !== false) {
|
|
2516
|
+
args.push("--new-window");
|
|
2517
|
+
}
|
|
2518
|
+
args.push(url);
|
|
2519
|
+
return {
|
|
2520
|
+
command: chromiumBinary,
|
|
2521
|
+
args,
|
|
2522
|
+
via: "chromium",
|
|
2523
|
+
target: chromiumBinary
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
async function spawnLaunch(command, args, detached) {
|
|
2527
|
+
return new Promise((resolve2, reject) => {
|
|
2528
|
+
const child = (0, import_child_process3.spawn)(command, args, {
|
|
2529
|
+
detached,
|
|
2530
|
+
stdio: "ignore"
|
|
2531
|
+
});
|
|
2532
|
+
child.once("error", reject);
|
|
2533
|
+
child.once("spawn", () => {
|
|
2534
|
+
if (detached) child.unref();
|
|
2535
|
+
resolve2(child.pid);
|
|
2536
|
+
});
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
async function launchUrl(url, options = {}) {
|
|
2540
|
+
const command = buildOpenUrlCommand(url, options);
|
|
2541
|
+
const pid = await spawnLaunch(command.command, command.args, options.detached ?? true);
|
|
2542
|
+
return {
|
|
2543
|
+
...command,
|
|
2544
|
+
pid,
|
|
2545
|
+
attachUrl: url
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
async function openUrl(url, options = {}) {
|
|
2549
|
+
await launchUrl(url, options);
|
|
2550
|
+
}
|
|
2551
|
+
var import_child_process3, import_fs3, import_os2, import_path3, MAC_CHROMIUM_CANDIDATES, LINUX_CHROMIUM_CANDIDATES, WINDOWS_CHROMIUM_CANDIDATES, UNIX_CHROMIUM_COMMANDS, WINDOWS_CHROMIUM_COMMANDS;
|
|
2552
|
+
var init_open_url = __esm({
|
|
2553
|
+
"src/host/open-url.ts"() {
|
|
2554
|
+
"use strict";
|
|
2555
|
+
import_child_process3 = require("child_process");
|
|
2556
|
+
import_fs3 = require("fs");
|
|
2557
|
+
import_os2 = require("os");
|
|
2558
|
+
import_path3 = require("path");
|
|
2559
|
+
MAC_CHROMIUM_CANDIDATES = [
|
|
2560
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
2561
|
+
"~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
2562
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
2563
|
+
"~/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
2564
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
2565
|
+
"~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
2566
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
2567
|
+
"~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
2568
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
2569
|
+
"~/Applications/Arc.app/Contents/MacOS/Arc"
|
|
2570
|
+
];
|
|
2571
|
+
LINUX_CHROMIUM_CANDIDATES = [
|
|
2572
|
+
"/usr/bin/google-chrome",
|
|
2573
|
+
"/usr/bin/chromium",
|
|
2574
|
+
"/usr/bin/chromium-browser",
|
|
2575
|
+
"/usr/bin/microsoft-edge",
|
|
2576
|
+
"/usr/bin/brave-browser",
|
|
2577
|
+
"/snap/bin/chromium"
|
|
2578
|
+
];
|
|
2579
|
+
WINDOWS_CHROMIUM_CANDIDATES = [
|
|
2580
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
2581
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
2582
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
2583
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
2584
|
+
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
2585
|
+
"C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
2586
|
+
];
|
|
2587
|
+
UNIX_CHROMIUM_COMMANDS = [
|
|
2588
|
+
"google-chrome",
|
|
2589
|
+
"chromium",
|
|
2590
|
+
"chromium-browser",
|
|
2591
|
+
"microsoft-edge",
|
|
2592
|
+
"brave-browser"
|
|
2593
|
+
];
|
|
2594
|
+
WINDOWS_CHROMIUM_COMMANDS = ["chrome", "msedge", "brave"];
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2218
2598
|
// src/host/bridge-host.ts
|
|
2219
2599
|
var bridge_host_exports = {};
|
|
2220
2600
|
__export(bridge_host_exports, {
|
|
@@ -2226,20 +2606,24 @@ function shouldAcquireLease(msg) {
|
|
|
2226
2606
|
if (msg.readOnly === true) return false;
|
|
2227
2607
|
return WRITE_COMMAND_TYPES.has(msg.type);
|
|
2228
2608
|
}
|
|
2229
|
-
var
|
|
2609
|
+
var import_child_process4, import_fs4, import_http2, import_path4, import_ws, WRITE_COMMAND_TYPES, DAEMON_HEARTBEAT_INTERVAL_MS, DEFAULT_RUNTIME_UPDATE_INTERVAL_MS, RUNTIME_UPDATE_INTERVAL_ENV, HTTP_MIME_TYPES, SootSimBridgeHost;
|
|
2230
2610
|
var init_bridge_host = __esm({
|
|
2231
2611
|
"src/host/bridge-host.ts"() {
|
|
2232
2612
|
"use strict";
|
|
2233
|
-
|
|
2234
|
-
|
|
2613
|
+
import_child_process4 = require("child_process");
|
|
2614
|
+
import_fs4 = __toESM(require("fs"), 1);
|
|
2235
2615
|
import_http2 = require("http");
|
|
2236
|
-
|
|
2616
|
+
import_path4 = __toESM(require("path"), 1);
|
|
2237
2617
|
import_ws = require("ws");
|
|
2238
2618
|
init_bridge_constants();
|
|
2239
2619
|
init_home_paths();
|
|
2620
|
+
init_runtime_delivery();
|
|
2240
2621
|
init_agent_host();
|
|
2622
|
+
init_open_url();
|
|
2241
2623
|
WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set(["tap", "keyboard", "close"]);
|
|
2242
2624
|
DAEMON_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
2625
|
+
DEFAULT_RUNTIME_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
2626
|
+
RUNTIME_UPDATE_INTERVAL_ENV = "SOOTSIM_RUNTIME_UPDATE_INTERVAL_MS";
|
|
2243
2627
|
HTTP_MIME_TYPES = {
|
|
2244
2628
|
".html": "text/html; charset=utf-8",
|
|
2245
2629
|
".js": "application/javascript",
|
|
@@ -2285,6 +2669,10 @@ var init_bridge_host = __esm({
|
|
|
2285
2669
|
static CLI_IDLE_TIMEOUT_MS = 6e4;
|
|
2286
2670
|
static CLI_LEASE_TTL_MS = 6e5;
|
|
2287
2671
|
static USER_ACTIVE_LEASE_TTL_MS = 8e3;
|
|
2672
|
+
// explicit user actions (clicking Boot, focusing the tab to take it over)
|
|
2673
|
+
// hold the tab longer than passive canvas interaction so reconnecting clis
|
|
2674
|
+
// can't immediately reclaim while the user gets oriented.
|
|
2675
|
+
static USER_BOOT_LEASE_TTL_MS = 6e4;
|
|
2288
2676
|
static BROWSER_RECONNECT_TTL_MS = 3e4;
|
|
2289
2677
|
preferredPort;
|
|
2290
2678
|
portFallbackCount;
|
|
@@ -2292,14 +2680,15 @@ var init_bridge_host = __esm({
|
|
|
2292
2680
|
effectivePort = 0;
|
|
2293
2681
|
startedAt = 0;
|
|
2294
2682
|
heartbeatTimer = null;
|
|
2683
|
+
runtimeUpdateTimer = null;
|
|
2684
|
+
runtimeUpdateInFlight = null;
|
|
2295
2685
|
activeRuntimeVersion = null;
|
|
2296
2686
|
activeRuntimeDirPath = null;
|
|
2297
2687
|
constructor(opts = {}) {
|
|
2298
2688
|
this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
2299
2689
|
this.port = this.preferredPort;
|
|
2300
2690
|
this.shouldWriteLockfile = opts.writeLockfile === true;
|
|
2301
|
-
|
|
2302
|
-
this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? defaultFallback);
|
|
2691
|
+
this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? 10);
|
|
2303
2692
|
this.openUrlHandler = opts.openUrl;
|
|
2304
2693
|
this.agentHost = new AgentHost({ getExcludePorts: opts.agentScanExcludes });
|
|
2305
2694
|
}
|
|
@@ -2389,6 +2778,8 @@ var init_bridge_host = __esm({
|
|
|
2389
2778
|
const origin = req.headers.origin;
|
|
2390
2779
|
const role = origin ? "browser" : "cli";
|
|
2391
2780
|
let browser = null;
|
|
2781
|
+
ws.on("error", () => {
|
|
2782
|
+
});
|
|
2392
2783
|
this.agentHost.registerSocket(ws);
|
|
2393
2784
|
if (role === "browser") {
|
|
2394
2785
|
browser = {
|
|
@@ -2532,15 +2923,18 @@ var init_bridge_host = __esm({
|
|
|
2532
2923
|
}
|
|
2533
2924
|
}
|
|
2534
2925
|
const hadLease = !!browser.cliLease;
|
|
2535
|
-
browser.cliLease =
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2926
|
+
browser.cliLease = {
|
|
2927
|
+
kind: "user-active",
|
|
2928
|
+
cliSessionKey: "__user-active__",
|
|
2929
|
+
cliLabel: "active user",
|
|
2930
|
+
expiresAt: Date.now() + _SootSimBridgeHost.USER_BOOT_LEASE_TTL_MS
|
|
2931
|
+
};
|
|
2932
|
+
process.stderr.write(
|
|
2933
|
+
`sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held tab for user [${browser.id}]
|
|
2539
2934
|
`
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
}
|
|
2935
|
+
);
|
|
2936
|
+
this.recordBrowserAction(browser.id, "browser booted cli clients");
|
|
2937
|
+
this.broadcastBrowserClientStates();
|
|
2544
2938
|
return;
|
|
2545
2939
|
}
|
|
2546
2940
|
const internalPending = this.pendingCommands.get(msg.id);
|
|
@@ -2613,7 +3007,7 @@ var init_bridge_host = __esm({
|
|
|
2613
3007
|
if (typeof msg.url !== "string" || !msg.url) {
|
|
2614
3008
|
throw new Error("bridge:open requires a url");
|
|
2615
3009
|
}
|
|
2616
|
-
await this.openUrl(msg.url);
|
|
3010
|
+
await this.openUrl(msg.url, { newWindow: msg.newWindow === true });
|
|
2617
3011
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2618
3012
|
ws.send(
|
|
2619
3013
|
JSON.stringify({
|
|
@@ -2777,9 +3171,11 @@ var init_bridge_host = __esm({
|
|
|
2777
3171
|
}
|
|
2778
3172
|
}, DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
2779
3173
|
this.heartbeatTimer.unref();
|
|
3174
|
+
this.startRuntimeUpdater();
|
|
2780
3175
|
}
|
|
2781
3176
|
void this.agentHost.seedOnBoot();
|
|
2782
3177
|
}
|
|
3178
|
+
bootstrapping = true;
|
|
2783
3179
|
buildLockfileSnapshot() {
|
|
2784
3180
|
return {
|
|
2785
3181
|
schema: 1,
|
|
@@ -2790,7 +3186,8 @@ var init_bridge_host = __esm({
|
|
|
2790
3186
|
activeRuntime: this.activeRuntimeVersion,
|
|
2791
3187
|
activeRuntimeDir: this.activeRuntimeDirPath,
|
|
2792
3188
|
startedAt: this.startedAt,
|
|
2793
|
-
heartbeatAt: Date.now()
|
|
3189
|
+
heartbeatAt: Date.now(),
|
|
3190
|
+
bootstrapping: this.bootstrapping
|
|
2794
3191
|
};
|
|
2795
3192
|
}
|
|
2796
3193
|
writeLockfileSnapshot() {
|
|
@@ -2800,6 +3197,61 @@ var init_bridge_host = __esm({
|
|
|
2800
3197
|
this.activeRuntimeVersion = readActiveRuntime();
|
|
2801
3198
|
this.activeRuntimeDirPath = activeRuntimeDir();
|
|
2802
3199
|
}
|
|
3200
|
+
resolveRuntimeUpdateIntervalMs() {
|
|
3201
|
+
const raw = Number(process.env[RUNTIME_UPDATE_INTERVAL_ENV]);
|
|
3202
|
+
if (Number.isFinite(raw) && raw > 0) return Math.max(100, Math.round(raw));
|
|
3203
|
+
return DEFAULT_RUNTIME_UPDATE_INTERVAL_MS;
|
|
3204
|
+
}
|
|
3205
|
+
startRuntimeUpdater() {
|
|
3206
|
+
if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
|
|
3207
|
+
void this.runRuntimeUpdate("startup");
|
|
3208
|
+
const intervalMs = this.resolveRuntimeUpdateIntervalMs();
|
|
3209
|
+
this.runtimeUpdateTimer = setInterval(() => {
|
|
3210
|
+
void this.runRuntimeUpdate("periodic");
|
|
3211
|
+
}, intervalMs);
|
|
3212
|
+
this.runtimeUpdateTimer.unref();
|
|
3213
|
+
}
|
|
3214
|
+
runRuntimeUpdate(reason) {
|
|
3215
|
+
if (this.runtimeUpdateInFlight) return this.runtimeUpdateInFlight;
|
|
3216
|
+
this.runtimeUpdateInFlight = (async () => {
|
|
3217
|
+
try {
|
|
3218
|
+
if (reason === "startup") {
|
|
3219
|
+
process.stderr.write("sootsim: checking for runtime updates\u2026\n");
|
|
3220
|
+
}
|
|
3221
|
+
const result = await updateRuntimeToLatest();
|
|
3222
|
+
if (!result.updated || !result.latestVersion) {
|
|
3223
|
+
if (reason === "startup") {
|
|
3224
|
+
process.stderr.write(
|
|
3225
|
+
`sootsim: runtime ${this.activeRuntimeVersion ?? "(none)"} is current
|
|
3226
|
+
`
|
|
3227
|
+
);
|
|
3228
|
+
}
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
const active = this.setActiveRuntime(result.latestVersion);
|
|
3232
|
+
process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})
|
|
3233
|
+
`);
|
|
3234
|
+
} catch (err) {
|
|
3235
|
+
process.stderr.write(
|
|
3236
|
+
`sootsim runtime update failed (${reason}): ${err instanceof Error ? err.message : String(err)}
|
|
3237
|
+
`
|
|
3238
|
+
);
|
|
3239
|
+
} finally {
|
|
3240
|
+
this.runtimeUpdateInFlight = null;
|
|
3241
|
+
if (reason === "startup" && this.bootstrapping) {
|
|
3242
|
+
this.bootstrapping = false;
|
|
3243
|
+
if (this.shouldWriteLockfile && this.httpServer) {
|
|
3244
|
+
try {
|
|
3245
|
+
this.writeLockfileSnapshot();
|
|
3246
|
+
} catch {
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
process.stderr.write("sootsim: ready\n");
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
})();
|
|
3253
|
+
return this.runtimeUpdateInFlight;
|
|
3254
|
+
}
|
|
2803
3255
|
/** update the active runtime on disk + in memory. the caller guarantees
|
|
2804
3256
|
* the version directory exists. pushes a runtime:changed message to all
|
|
2805
3257
|
* connected browsers so electron (or any renderer) can reload. */
|
|
@@ -2957,47 +3409,50 @@ var init_bridge_host = __esm({
|
|
|
2957
3409
|
return;
|
|
2958
3410
|
}
|
|
2959
3411
|
}
|
|
2960
|
-
const resolved =
|
|
2961
|
-
const baseWithSep = baseDir.endsWith(
|
|
3412
|
+
const resolved = import_path4.default.resolve(baseDir, "." + rel);
|
|
3413
|
+
const baseWithSep = baseDir.endsWith(import_path4.default.sep) ? baseDir : baseDir + import_path4.default.sep;
|
|
2962
3414
|
if (!resolved.startsWith(baseWithSep) && resolved !== baseDir) {
|
|
2963
3415
|
res.writeHead(403);
|
|
2964
3416
|
res.end("forbidden");
|
|
2965
3417
|
return;
|
|
2966
3418
|
}
|
|
2967
|
-
|
|
3419
|
+
import_fs4.default.realpath(resolved, (realErr, realResolved) => {
|
|
2968
3420
|
const servePath = realErr ? resolved : realResolved;
|
|
2969
|
-
const servePathWithSep = servePath.endsWith(
|
|
3421
|
+
const servePathWithSep = servePath.endsWith(import_path4.default.sep) ? servePath : servePath + import_path4.default.sep;
|
|
2970
3422
|
if (!realErr) {
|
|
2971
3423
|
const realBaseWithSep = (() => {
|
|
2972
3424
|
try {
|
|
2973
|
-
const rb =
|
|
2974
|
-
return rb.endsWith(
|
|
3425
|
+
const rb = import_fs4.default.realpathSync(baseDir);
|
|
3426
|
+
return rb.endsWith(import_path4.default.sep) ? rb : rb + import_path4.default.sep;
|
|
2975
3427
|
} catch {
|
|
2976
3428
|
return baseWithSep;
|
|
2977
3429
|
}
|
|
2978
3430
|
})();
|
|
2979
|
-
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath +
|
|
3431
|
+
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path4.default.sep !== realBaseWithSep) {
|
|
2980
3432
|
res.writeHead(403);
|
|
2981
3433
|
res.end("forbidden");
|
|
2982
3434
|
return;
|
|
2983
3435
|
}
|
|
2984
3436
|
}
|
|
2985
|
-
|
|
3437
|
+
import_fs4.default.stat(servePath, (err, stats) => {
|
|
2986
3438
|
if (err || !stats?.isFile()) {
|
|
2987
|
-
const ext2 =
|
|
3439
|
+
const ext2 = import_path4.default.extname(rel).toLowerCase();
|
|
2988
3440
|
if (ext2 && ext2 !== ".html") {
|
|
2989
3441
|
res.writeHead(404);
|
|
2990
3442
|
res.end("not found");
|
|
2991
3443
|
return;
|
|
2992
3444
|
}
|
|
2993
|
-
const indexPath =
|
|
2994
|
-
|
|
3445
|
+
const indexPath = import_path4.default.join(baseDir, "index.html");
|
|
3446
|
+
import_fs4.default.readFile(indexPath, (err2, data) => {
|
|
2995
3447
|
if (err2) {
|
|
2996
3448
|
res.writeHead(404);
|
|
2997
3449
|
res.end("not found");
|
|
2998
3450
|
return;
|
|
2999
3451
|
}
|
|
3000
|
-
res.writeHead(200, {
|
|
3452
|
+
res.writeHead(200, {
|
|
3453
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
3454
|
+
"Cache-Control": "no-store"
|
|
3455
|
+
});
|
|
3001
3456
|
if (method === "HEAD") {
|
|
3002
3457
|
res.end();
|
|
3003
3458
|
return;
|
|
@@ -3006,14 +3461,17 @@ var init_bridge_host = __esm({
|
|
|
3006
3461
|
});
|
|
3007
3462
|
return;
|
|
3008
3463
|
}
|
|
3009
|
-
const ext =
|
|
3464
|
+
const ext = import_path4.default.extname(servePath).toLowerCase();
|
|
3010
3465
|
const contentType = HTTP_MIME_TYPES[ext] || "application/octet-stream";
|
|
3011
|
-
res.writeHead(200, {
|
|
3466
|
+
res.writeHead(200, {
|
|
3467
|
+
"Content-Type": contentType,
|
|
3468
|
+
"Cache-Control": "no-store"
|
|
3469
|
+
});
|
|
3012
3470
|
if (method === "HEAD") {
|
|
3013
3471
|
res.end();
|
|
3014
3472
|
return;
|
|
3015
3473
|
}
|
|
3016
|
-
const stream =
|
|
3474
|
+
const stream = import_fs4.default.createReadStream(servePath);
|
|
3017
3475
|
stream.pipe(res);
|
|
3018
3476
|
stream.on("error", () => {
|
|
3019
3477
|
try {
|
|
@@ -3096,7 +3554,7 @@ var init_bridge_host = __esm({
|
|
|
3096
3554
|
const target = `${filePath}${loc}`;
|
|
3097
3555
|
const trySpawn = (cmd, args) => new Promise((resolve2) => {
|
|
3098
3556
|
try {
|
|
3099
|
-
const child = (0,
|
|
3557
|
+
const child = (0, import_child_process4.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
3100
3558
|
let settled = false;
|
|
3101
3559
|
child.on("error", () => {
|
|
3102
3560
|
if (settled) return;
|
|
@@ -3123,26 +3581,12 @@ var init_bridge_host = __esm({
|
|
|
3123
3581
|
if (await trySpawn("code", ["-g", target])) return;
|
|
3124
3582
|
await this.openUrl(filePath);
|
|
3125
3583
|
}
|
|
3126
|
-
async openUrl(url) {
|
|
3584
|
+
async openUrl(url, options = {}) {
|
|
3127
3585
|
if (this.openUrlHandler) {
|
|
3128
|
-
await this.openUrlHandler(url);
|
|
3129
|
-
return;
|
|
3130
|
-
}
|
|
3131
|
-
if (process.platform === "darwin") {
|
|
3132
|
-
const child2 = (0, import_child_process2.spawn)("open", ["-g", url], { detached: true, stdio: "ignore" });
|
|
3133
|
-
child2.unref();
|
|
3586
|
+
await this.openUrlHandler(url, options);
|
|
3134
3587
|
return;
|
|
3135
3588
|
}
|
|
3136
|
-
|
|
3137
|
-
const child2 = (0, import_child_process2.spawn)("cmd", ["/c", "start", "", url], {
|
|
3138
|
-
detached: true,
|
|
3139
|
-
stdio: "ignore"
|
|
3140
|
-
});
|
|
3141
|
-
child2.unref();
|
|
3142
|
-
return;
|
|
3143
|
-
}
|
|
3144
|
-
const child = (0, import_child_process2.spawn)("xdg-open", [url], { detached: true, stdio: "ignore" });
|
|
3145
|
-
child.unref();
|
|
3589
|
+
await openUrl(url, options);
|
|
3146
3590
|
}
|
|
3147
3591
|
async close() {
|
|
3148
3592
|
if (this.cliIdleTimer) {
|
|
@@ -3153,6 +3597,10 @@ var init_bridge_host = __esm({
|
|
|
3153
3597
|
clearInterval(this.heartbeatTimer);
|
|
3154
3598
|
this.heartbeatTimer = null;
|
|
3155
3599
|
}
|
|
3600
|
+
if (this.runtimeUpdateTimer) {
|
|
3601
|
+
clearInterval(this.runtimeUpdateTimer);
|
|
3602
|
+
this.runtimeUpdateTimer = null;
|
|
3603
|
+
}
|
|
3156
3604
|
if (this.shouldWriteLockfile) {
|
|
3157
3605
|
try {
|
|
3158
3606
|
removeDaemonLockfile();
|
|
@@ -3289,11 +3737,14 @@ var init_bridge_host = __esm({
|
|
|
3289
3737
|
if (existing && existing.kind === "cli") {
|
|
3290
3738
|
return;
|
|
3291
3739
|
}
|
|
3740
|
+
const now = Date.now();
|
|
3741
|
+
const refreshed = now + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS;
|
|
3742
|
+
const expiresAt = existing && existing.kind === "user-active" ? Math.max(existing.expiresAt, refreshed) : refreshed;
|
|
3292
3743
|
browser.cliLease = {
|
|
3293
3744
|
kind: "user-active",
|
|
3294
3745
|
cliSessionKey: "__user-active__",
|
|
3295
3746
|
cliLabel: "active user",
|
|
3296
|
-
expiresAt
|
|
3747
|
+
expiresAt
|
|
3297
3748
|
};
|
|
3298
3749
|
this.broadcastBrowserClientStates();
|
|
3299
3750
|
}
|
|
@@ -3508,6 +3959,10 @@ var init_bridge_host = __esm({
|
|
|
3508
3959
|
clearInterval(this.cliIdleTimer);
|
|
3509
3960
|
this.cliIdleTimer = null;
|
|
3510
3961
|
}
|
|
3962
|
+
if (this.runtimeUpdateTimer) {
|
|
3963
|
+
clearInterval(this.runtimeUpdateTimer);
|
|
3964
|
+
this.runtimeUpdateTimer = null;
|
|
3965
|
+
}
|
|
3511
3966
|
const wss = this.wss;
|
|
3512
3967
|
const httpServer = this.httpServer;
|
|
3513
3968
|
this.wss = null;
|
|
@@ -3532,45 +3987,49 @@ var init_bridge_host = __esm({
|
|
|
3532
3987
|
// src/vite-plugin.ts
|
|
3533
3988
|
var vite_plugin_exports = {};
|
|
3534
3989
|
__export(vite_plugin_exports, {
|
|
3990
|
+
engineShellWorkerPlugins: () => engineShellWorkerPlugins,
|
|
3535
3991
|
fixJsxRuntimeExports: () => fixJsxRuntimeExports,
|
|
3992
|
+
keyboardControllerBindingsRedirect: () => keyboardControllerBindingsRedirect,
|
|
3993
|
+
reanimatedNativeSeamRedirect: () => reanimatedNativeSeamRedirect,
|
|
3536
3994
|
sootsim: () => sootsim,
|
|
3537
3995
|
workerReactBridgePlugin: () => workerReactBridgePlugin,
|
|
3996
|
+
workletsBabelTransform: () => workletsBabelTransform,
|
|
3538
3997
|
wsBridgePlugin: () => wsBridgePlugin
|
|
3539
3998
|
});
|
|
3540
3999
|
module.exports = __toCommonJS(vite_plugin_exports);
|
|
3541
|
-
var
|
|
3542
|
-
var
|
|
4000
|
+
var import_fs5 = __toESM(require("fs"), 1);
|
|
4001
|
+
var import_path5 = __toESM(require("path"), 1);
|
|
3543
4002
|
var import_url = require("url");
|
|
3544
4003
|
var import_vite = require("vite");
|
|
3545
4004
|
init_bridge_constants();
|
|
3546
|
-
var sootsimRoot =
|
|
3547
|
-
var workspaceRoot =
|
|
3548
|
-
var workspaceNodeModules =
|
|
3549
|
-
var workspaceTamaguiDir =
|
|
3550
|
-
var engineRoot =
|
|
3551
|
-
var rnShimPath =
|
|
3552
|
-
var reactBridgePath =
|
|
4005
|
+
var sootsimRoot = import_path5.default.resolve(import_path5.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url)), "..");
|
|
4006
|
+
var workspaceRoot = import_path5.default.resolve(sootsimRoot, "..", "..");
|
|
4007
|
+
var workspaceNodeModules = import_path5.default.resolve(workspaceRoot, "node_modules");
|
|
4008
|
+
var workspaceTamaguiDir = import_path5.default.resolve(workspaceNodeModules, "@tamagui");
|
|
4009
|
+
var engineRoot = import_path5.default.resolve(workspaceRoot, "packages/sootsim-engine");
|
|
4010
|
+
var rnShimPath = import_path5.default.resolve(engineRoot, "src/react-native/index.ts");
|
|
4011
|
+
var reactBridgePath = import_path5.default.resolve(engineRoot, "src/react-bridge.ts");
|
|
3553
4012
|
var sootsimBrowserSourceAliases = [
|
|
3554
4013
|
{
|
|
3555
4014
|
find: "sootsim/backend-origin",
|
|
3556
|
-
replacement:
|
|
4015
|
+
replacement: import_path5.default.resolve(sootsimRoot, "src/backend-origin.ts")
|
|
3557
4016
|
},
|
|
3558
4017
|
{
|
|
3559
4018
|
find: "sootsim/bridge-constants",
|
|
3560
|
-
replacement:
|
|
4019
|
+
replacement: import_path5.default.resolve(sootsimRoot, "src/bridge-constants.ts")
|
|
3561
4020
|
},
|
|
3562
4021
|
{
|
|
3563
4022
|
find: "sootsim/dev-bundle-resolution",
|
|
3564
|
-
replacement:
|
|
4023
|
+
replacement: import_path5.default.resolve(sootsimRoot, "src/dev-bundle-resolution.ts")
|
|
3565
4024
|
}
|
|
3566
4025
|
];
|
|
3567
|
-
var compatRoot =
|
|
3568
|
-
var compatStubsDir =
|
|
3569
|
-
var nativeAutoStubPath =
|
|
3570
|
-
var rnDeepStubDefault =
|
|
4026
|
+
var compatRoot = import_path5.default.resolve(sootsimRoot, "..", "compat");
|
|
4027
|
+
var compatStubsDir = import_path5.default.resolve(compatRoot, "src/stubs");
|
|
4028
|
+
var nativeAutoStubPath = import_path5.default.resolve(compatStubsDir, "native-auto-stub.ts");
|
|
4029
|
+
var rnDeepStubDefault = import_path5.default.resolve(compatStubsDir, "react-native-internals.ts");
|
|
3571
4030
|
function getInternalTamaguiPackages() {
|
|
3572
4031
|
try {
|
|
3573
|
-
const scoped =
|
|
4032
|
+
const scoped = import_fs5.default.readdirSync(workspaceTamaguiDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => `@tamagui/${entry.name}`);
|
|
3574
4033
|
return ["tamagui", ...scoped].sort();
|
|
3575
4034
|
} catch {
|
|
3576
4035
|
return ["tamagui"];
|
|
@@ -3578,19 +4037,19 @@ function getInternalTamaguiPackages() {
|
|
|
3578
4037
|
}
|
|
3579
4038
|
var internalTamaguiPackages = getInternalTamaguiPackages();
|
|
3580
4039
|
var rnLibraryStubs = {
|
|
3581
|
-
"react-native/Libraries/Pressability/usePressability":
|
|
4040
|
+
"react-native/Libraries/Pressability/usePressability": import_path5.default.resolve(
|
|
3582
4041
|
compatStubsDir,
|
|
3583
4042
|
"rn-libraries/usePressability.ts"
|
|
3584
4043
|
),
|
|
3585
|
-
"react-native/Libraries/Pressability/Pressability":
|
|
4044
|
+
"react-native/Libraries/Pressability/Pressability": import_path5.default.resolve(
|
|
3586
4045
|
compatStubsDir,
|
|
3587
4046
|
"rn-libraries/Pressability.ts"
|
|
3588
4047
|
),
|
|
3589
|
-
"react-native/Libraries/Renderer/shims/ReactFabric":
|
|
4048
|
+
"react-native/Libraries/Renderer/shims/ReactFabric": import_path5.default.resolve(
|
|
3590
4049
|
compatStubsDir,
|
|
3591
4050
|
"rn-libraries/ReactFabric.ts"
|
|
3592
4051
|
),
|
|
3593
|
-
"react-native/Libraries/Renderer/shims/ReactNative":
|
|
4052
|
+
"react-native/Libraries/Renderer/shims/ReactNative": import_path5.default.resolve(
|
|
3594
4053
|
compatStubsDir,
|
|
3595
4054
|
"rn-libraries/ReactNative.ts"
|
|
3596
4055
|
)
|
|
@@ -3601,11 +4060,18 @@ var builtinStubs = {
|
|
|
3601
4060
|
"react-native-gesture-handler/ReanimatedSwipeable": "reanimated-swipeable.ts",
|
|
3602
4061
|
// NOTE: 'soot-swipeable' is a real workspace package (packages/soot-swipeable),
|
|
3603
4062
|
// resolved naturally by vite — no builtin stub entry needed.
|
|
3604
|
-
|
|
4063
|
+
// react-native-reanimated is mostly pure-JS; only the native seam (turbomodule
|
|
4064
|
+
// + platform functions) is redirected. wired below as `reanimatedNativeSeamRedirect`.
|
|
3605
4065
|
"react-native-screens": "react-native-screens.ts",
|
|
3606
4066
|
"@react-native-masked-view/masked-view": "masked-view.ts",
|
|
3607
4067
|
"react-native-svg": "react-native-svg.ts",
|
|
3608
|
-
|
|
4068
|
+
// react-native-keyboard-controller is mostly pure-JS — KeyboardAwareScrollView,
|
|
4069
|
+
// KeyboardStickyView, hooks, animated module, and KeyboardAvoidingView all sit
|
|
4070
|
+
// on top of reanimated worklets + scrollTo over a thin native-binding seam
|
|
4071
|
+
// (`bindings.ts` / `bindings.native.ts`) plus RN's platform findNodeHandle.
|
|
4072
|
+
// per the project policy of not shimming pure-JS libs, we let the package
|
|
4073
|
+
// resolve from node_modules and redirect only those native/platform seams.
|
|
4074
|
+
// wired below as `keyboardControllerBindingsRedirect`.
|
|
3609
4075
|
"react-native-teleport": "react-native-teleport.tsx",
|
|
3610
4076
|
"react-native-ios-context-menu": "react-native-ios-context-menu.ts",
|
|
3611
4077
|
"react-native-ios-utilities": "react-native-ios-utilities.ts",
|
|
@@ -3625,8 +4091,18 @@ var builtinStubs = {
|
|
|
3625
4091
|
"@callstack/react-native-bottom-tabs": "react-native-bottom-tabs.ts",
|
|
3626
4092
|
"@expo/ui/swift-ui": "expo-ui.ts",
|
|
3627
4093
|
"@expo/ui": "expo-ui.ts",
|
|
3628
|
-
|
|
4094
|
+
// worklets stub lives in a package-shaped directory (with package.json)
|
|
4095
|
+
// so upstream code that does `require('react-native-worklets/package.json')`
|
|
4096
|
+
// (e.g. reanimated's assertWorkletsVersion) can read a real version field.
|
|
4097
|
+
"react-native-worklets": "react-native-worklets-pkg/index.ts",
|
|
4098
|
+
"react-native-worklets/package.json": "react-native-worklets-pkg/package.json",
|
|
3629
4099
|
"react-native-webview": "react-native-webview.ts",
|
|
4100
|
+
// pager-view, video — bundle source paths reach codegenNativeCommands /
|
|
4101
|
+
// requireNativeComponent from `react-native` deep imports that resolve to
|
|
4102
|
+
// undefined, so the JS-only stubs MUST replace the package at build time.
|
|
4103
|
+
"react-native-pager-view": "react-native-pager-view.ts",
|
|
4104
|
+
"react-native-video": "react-native-video.ts",
|
|
4105
|
+
"expo-video": "expo-video.ts",
|
|
3630
4106
|
"expo-haptics": "expo-haptics.ts",
|
|
3631
4107
|
"expo-crypto": "expo-crypto.ts",
|
|
3632
4108
|
"expo-clipboard": "expo-clipboard.ts",
|
|
@@ -3675,14 +4151,14 @@ function isNativePackage(source) {
|
|
|
3675
4151
|
}
|
|
3676
4152
|
function moduleExistsIn(pkgName, dir) {
|
|
3677
4153
|
try {
|
|
3678
|
-
return
|
|
4154
|
+
return import_fs5.default.existsSync(import_path5.default.join(dir, "node_modules", pkgName));
|
|
3679
4155
|
} catch {
|
|
3680
4156
|
return false;
|
|
3681
4157
|
}
|
|
3682
4158
|
}
|
|
3683
4159
|
function sootsim(options = {}) {
|
|
3684
|
-
const appDir = options.app ?
|
|
3685
|
-
const extraSources = (options.sources || []).map((s) =>
|
|
4160
|
+
const appDir = options.app ? import_path5.default.resolve(options.app) : "";
|
|
4161
|
+
const extraSources = (options.sources || []).map((s) => import_path5.default.resolve(s));
|
|
3686
4162
|
const ownedPackages = /* @__PURE__ */ new Set([...coreOwnedPackages, ...options.ownedPackages || []]);
|
|
3687
4163
|
function isAppSource(filePath) {
|
|
3688
4164
|
if (filePath.includes("/node_modules/")) return false;
|
|
@@ -3695,6 +4171,13 @@ function sootsim(options = {}) {
|
|
|
3695
4171
|
sootsimConfigPlugin(appDir, extraSources),
|
|
3696
4172
|
fixJsxRuntimeExports(),
|
|
3697
4173
|
nodeModulesJsxTransform(),
|
|
4174
|
+
// must run before metroNativeResolve — that plugin resolves relative
|
|
4175
|
+
// imports like `./bindings` to absolute file paths via the platform-
|
|
4176
|
+
// extension lookup, and once it returns a resolved id, downstream
|
|
4177
|
+
// resolveId hooks don't see the source string anymore.
|
|
4178
|
+
keyboardControllerBindingsRedirect(),
|
|
4179
|
+
reanimatedNativeSeamRedirect(),
|
|
4180
|
+
workletsBabelTransform(isAppSource),
|
|
3698
4181
|
metroNativeResolve(isAppSource),
|
|
3699
4182
|
reactNativeRequirePlugin(isAppSource),
|
|
3700
4183
|
externalAppResolvePlugin(appDir, isAppSource, ownedPackages),
|
|
@@ -3703,6 +4186,129 @@ function sootsim(options = {}) {
|
|
|
3703
4186
|
stubMissingImages(isAppSource)
|
|
3704
4187
|
];
|
|
3705
4188
|
}
|
|
4189
|
+
function engineShellWorkerPlugins() {
|
|
4190
|
+
return [
|
|
4191
|
+
workerReactBridgePlugin(),
|
|
4192
|
+
fixJsxRuntimeExports(),
|
|
4193
|
+
reanimatedNativeSeamRedirect(),
|
|
4194
|
+
keyboardControllerBindingsRedirect(),
|
|
4195
|
+
workletsBabelTransform(() => true)
|
|
4196
|
+
];
|
|
4197
|
+
}
|
|
4198
|
+
function keyboardControllerBindingsRedirect() {
|
|
4199
|
+
const target = import_path5.default.resolve(compatStubsDir, "react-native-keyboard-controller.ts");
|
|
4200
|
+
const findNodeHandleTarget = "\0sootsim:rnkc-find-node-handle-native";
|
|
4201
|
+
const nativePlatformBasenames = /* @__PURE__ */ new Set(["findNodeHandle", "reanimated"]);
|
|
4202
|
+
const nativeExts = [
|
|
4203
|
+
".ios.tsx",
|
|
4204
|
+
".ios.ts",
|
|
4205
|
+
".ios.jsx",
|
|
4206
|
+
".ios.js",
|
|
4207
|
+
".native.tsx",
|
|
4208
|
+
".native.ts",
|
|
4209
|
+
".native.jsx",
|
|
4210
|
+
".native.js",
|
|
4211
|
+
".native.mjs"
|
|
4212
|
+
];
|
|
4213
|
+
const resolveNativePlatformFile = (source, importer) => {
|
|
4214
|
+
const base = import_path5.default.resolve(import_path5.default.dirname(importer), source);
|
|
4215
|
+
for (const ext of nativeExts) {
|
|
4216
|
+
const candidate = base + ext;
|
|
4217
|
+
try {
|
|
4218
|
+
if (import_fs5.default.existsSync(candidate)) return candidate;
|
|
4219
|
+
} catch {
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
for (const ext of nativeExts) {
|
|
4223
|
+
const candidate = import_path5.default.join(base, "index" + ext);
|
|
4224
|
+
try {
|
|
4225
|
+
if (import_fs5.default.existsSync(candidate)) return candidate;
|
|
4226
|
+
} catch {
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
return null;
|
|
4230
|
+
};
|
|
4231
|
+
return {
|
|
4232
|
+
name: "sootsim-keyboard-controller-bindings-redirect",
|
|
4233
|
+
enforce: "pre",
|
|
4234
|
+
resolveId(source, importer) {
|
|
4235
|
+
if (!importer) return null;
|
|
4236
|
+
if (!importer.includes("/react-native-keyboard-controller/")) return null;
|
|
4237
|
+
const basename = source.split("/").pop() ?? "";
|
|
4238
|
+
if (nativePlatformBasenames.has(basename)) {
|
|
4239
|
+
return resolveNativePlatformFile(source, importer) ?? (basename === "findNodeHandle" ? findNodeHandleTarget : null);
|
|
4240
|
+
}
|
|
4241
|
+
if (basename !== "bindings" && basename !== "bindings.native") return null;
|
|
4242
|
+
if (importer === target) return null;
|
|
4243
|
+
return target;
|
|
4244
|
+
},
|
|
4245
|
+
load(id) {
|
|
4246
|
+
if (id === findNodeHandleTarget) {
|
|
4247
|
+
return `export { findNodeHandle } from ${JSON.stringify(rnShimPath)};`;
|
|
4248
|
+
}
|
|
4249
|
+
return null;
|
|
4250
|
+
}
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
var reanimatedNativeSeamFiles = /* @__PURE__ */ new Set([
|
|
4254
|
+
"NativeReanimatedModule",
|
|
4255
|
+
"scrollTo",
|
|
4256
|
+
"measure",
|
|
4257
|
+
"setNativeProps",
|
|
4258
|
+
"dispatchCommand",
|
|
4259
|
+
"setGestureState"
|
|
4260
|
+
]);
|
|
4261
|
+
var reanimatedFabricUtilsRedirectPath = import_path5.default.resolve(
|
|
4262
|
+
compatStubsDir,
|
|
4263
|
+
"react-native-reanimated-fabric-utils.ts"
|
|
4264
|
+
);
|
|
4265
|
+
var reanimatedValidateWorkletsVersionPath = import_path5.default.resolve(
|
|
4266
|
+
compatStubsDir,
|
|
4267
|
+
"react-native-reanimated-validate-worklets-version.ts"
|
|
4268
|
+
);
|
|
4269
|
+
function reanimatedNativeSeamRedirect() {
|
|
4270
|
+
const target = import_path5.default.resolve(compatStubsDir, "react-native-reanimated.ts");
|
|
4271
|
+
return {
|
|
4272
|
+
name: "sootsim-reanimated-native-seam-redirect",
|
|
4273
|
+
enforce: "pre",
|
|
4274
|
+
resolveId(source, importer) {
|
|
4275
|
+
if (source === "react-native-worklets/package.json") {
|
|
4276
|
+
return "\0sootsim:react-native-worklets-package-json";
|
|
4277
|
+
}
|
|
4278
|
+
if (!importer) return null;
|
|
4279
|
+
if (source === "react-native-reanimated/scripts/validate-worklets-version" || source === "react-native-reanimated/scripts/validate-worklets-version.js") {
|
|
4280
|
+
return reanimatedValidateWorkletsVersionPath;
|
|
4281
|
+
}
|
|
4282
|
+
if (!importer.includes("/react-native-reanimated/")) return null;
|
|
4283
|
+
if (importer === target) return null;
|
|
4284
|
+
const basename = (source.split("/").pop() ?? "").replace(
|
|
4285
|
+
/\.(native|web|ios|android)$/,
|
|
4286
|
+
""
|
|
4287
|
+
);
|
|
4288
|
+
if (basename === "fabricUtils" && importer !== reanimatedFabricUtilsRedirectPath) {
|
|
4289
|
+
return reanimatedFabricUtilsRedirectPath;
|
|
4290
|
+
}
|
|
4291
|
+
if (!reanimatedNativeSeamFiles.has(basename)) return null;
|
|
4292
|
+
if (process.env.SOOTSIM_REANIMATED_REDIRECT_DEBUG) {
|
|
4293
|
+
console.log(
|
|
4294
|
+
"[reanimatedRedirect]",
|
|
4295
|
+
basename,
|
|
4296
|
+
"<-",
|
|
4297
|
+
source,
|
|
4298
|
+
"from",
|
|
4299
|
+
importer.slice(-80)
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
return target;
|
|
4303
|
+
},
|
|
4304
|
+
load(id) {
|
|
4305
|
+
if (id === "\0sootsim:react-native-worklets-package-json") {
|
|
4306
|
+
return `export default ${JSON.stringify({ version: "0.0.0-sootsim", name: "react-native-worklets" })}`;
|
|
4307
|
+
}
|
|
4308
|
+
return null;
|
|
4309
|
+
}
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
3706
4312
|
function wsBridgePlugin() {
|
|
3707
4313
|
let bridgeHost = null;
|
|
3708
4314
|
return {
|
|
@@ -3733,12 +4339,12 @@ function sootsimConfigPlugin(appDir, extraSources) {
|
|
|
3733
4339
|
for (const src of extraSources) fsAllow.push(src);
|
|
3734
4340
|
if (appDir) {
|
|
3735
4341
|
fsAllow.push(appDir);
|
|
3736
|
-
const appNodeModules =
|
|
3737
|
-
if (
|
|
4342
|
+
const appNodeModules = import_path5.default.join(appDir, "node_modules");
|
|
4343
|
+
if (import_fs5.default.existsSync(appNodeModules)) fsAllow.push(appNodeModules);
|
|
3738
4344
|
}
|
|
3739
4345
|
const stubAliases = Object.entries(builtinStubs).sort((a, b) => b[0].length - a[0].length).map(([find, file]) => ({
|
|
3740
4346
|
find,
|
|
3741
|
-
replacement:
|
|
4347
|
+
replacement: import_path5.default.resolve(compatStubsDir, file)
|
|
3742
4348
|
}));
|
|
3743
4349
|
const dedupe = ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"];
|
|
3744
4350
|
if (!appDir) {
|
|
@@ -3920,10 +4526,10 @@ function workerReactBridgePlugin() {
|
|
|
3920
4526
|
async resolveId(source, importer, options) {
|
|
3921
4527
|
if (source !== "react" || !importer) return null;
|
|
3922
4528
|
if (options?.scan || options?.ssr) return null;
|
|
3923
|
-
const shouldRedirect = importer.includes("/sootsim/src/") || importer.includes("/sootsim-engine/src/") || importer.includes("/compat/src/") || importer.includes("react-reconciler");
|
|
3924
|
-
if (!shouldRedirect) return null;
|
|
3925
4529
|
if (importer.includes("react-bridge")) return null;
|
|
3926
4530
|
if (importer.includes("stub-registry")) return null;
|
|
4531
|
+
const shouldRedirect = importer.includes("/sootsim/src/") || importer.includes("/sootsim-engine/src/") || importer.includes("/compat/src/") || importer.includes("react-reconciler") || importer.includes("/node_modules/");
|
|
4532
|
+
if (!shouldRedirect) return null;
|
|
3927
4533
|
return { id: reactBridgePath, external: false };
|
|
3928
4534
|
}
|
|
3929
4535
|
};
|
|
@@ -3971,6 +4577,94 @@ function fixJsxRuntimeExports() {
|
|
|
3971
4577
|
}
|
|
3972
4578
|
};
|
|
3973
4579
|
}
|
|
4580
|
+
var REANIMATED_AUTOWORKLETIZATION_KEYWORDS = [
|
|
4581
|
+
"worklet",
|
|
4582
|
+
"useAnimatedGestureHandler",
|
|
4583
|
+
"useAnimatedScrollHandler",
|
|
4584
|
+
"useFrameCallback",
|
|
4585
|
+
"useAnimatedStyle",
|
|
4586
|
+
"useAnimatedProps",
|
|
4587
|
+
"createAnimatedPropAdapter",
|
|
4588
|
+
"useDerivedValue",
|
|
4589
|
+
"useAnimatedReaction",
|
|
4590
|
+
"useWorkletCallback",
|
|
4591
|
+
"withTiming",
|
|
4592
|
+
"withSpring",
|
|
4593
|
+
"withDecay",
|
|
4594
|
+
"withRepeat",
|
|
4595
|
+
"runOnUI",
|
|
4596
|
+
"executeOnUIRuntimeSync"
|
|
4597
|
+
];
|
|
4598
|
+
var REANIMATED_REGEX = new RegExp(REANIMATED_AUTOWORKLETIZATION_KEYWORDS.join("|"));
|
|
4599
|
+
var REANIMATED_IGNORED_PATHS_REGEX = /node_modules\/(react|react-dom|react-native|react-native-web)\//;
|
|
4600
|
+
var SOOTSIM_ENGINE_SRC_PATH = "/packages/sootsim-engine/src/";
|
|
4601
|
+
var SOOTSIM_ENGINE_TEST_FIXTURE_PATH = "/packages/sootsim-engine/src/test-fixtures/";
|
|
4602
|
+
var REANIMATED_IMPORT_REGEX = /(?:from\s+['"]react-native-reanimated(?:\/[^'"]*)?['"]|require\(\s*['"]react-native-reanimated(?:\/[^'"]*)?['"]\s*\))/;
|
|
4603
|
+
function shouldApplyWorkletsPlugin(id, code) {
|
|
4604
|
+
if (!/\.(tsx?|jsx?|mjs|cjs)$/.test(id)) return false;
|
|
4605
|
+
if (REANIMATED_IGNORED_PATHS_REGEX.test(id)) return false;
|
|
4606
|
+
if (id.includes(SOOTSIM_ENGINE_SRC_PATH) && !id.includes(SOOTSIM_ENGINE_TEST_FIXTURE_PATH) && !REANIMATED_IMPORT_REGEX.test(code))
|
|
4607
|
+
return false;
|
|
4608
|
+
return REANIMATED_REGEX.test(code);
|
|
4609
|
+
}
|
|
4610
|
+
function workletsBabelTransform(_isAppSource = () => false) {
|
|
4611
|
+
let pluginPathPromise = null;
|
|
4612
|
+
let babelCorePromise = null;
|
|
4613
|
+
return {
|
|
4614
|
+
name: "sootsim-worklets-babel-transform",
|
|
4615
|
+
enforce: "pre",
|
|
4616
|
+
async transform(code, id) {
|
|
4617
|
+
if (!shouldApplyWorkletsPlugin(id, code)) return null;
|
|
4618
|
+
try {
|
|
4619
|
+
if (process.env.SOOTSIM_WORKLETS_BABEL_DEBUG) {
|
|
4620
|
+
console.log("[worklets-babel] transforming", id.slice(-80));
|
|
4621
|
+
}
|
|
4622
|
+
if (!babelCorePromise) {
|
|
4623
|
+
babelCorePromise = import("@babel/core");
|
|
4624
|
+
}
|
|
4625
|
+
if (!pluginPathPromise) {
|
|
4626
|
+
pluginPathPromise = (async () => {
|
|
4627
|
+
const sootsimResolveBase = import_path5.default.resolve(workspaceRoot);
|
|
4628
|
+
return import_path5.default.resolve(
|
|
4629
|
+
sootsimResolveBase,
|
|
4630
|
+
"node_modules",
|
|
4631
|
+
"react-native-worklets",
|
|
4632
|
+
"plugin"
|
|
4633
|
+
);
|
|
4634
|
+
})();
|
|
4635
|
+
}
|
|
4636
|
+
const [{ transformAsync }, pluginPath] = await Promise.all([
|
|
4637
|
+
babelCorePromise,
|
|
4638
|
+
pluginPathPromise
|
|
4639
|
+
]);
|
|
4640
|
+
const isTSX = id.endsWith(".tsx");
|
|
4641
|
+
const isTS = isTSX || id.endsWith(".ts");
|
|
4642
|
+
const result = await transformAsync(code, {
|
|
4643
|
+
filename: id,
|
|
4644
|
+
babelrc: false,
|
|
4645
|
+
configFile: false,
|
|
4646
|
+
sourceMaps: true,
|
|
4647
|
+
presets: isTS ? [
|
|
4648
|
+
[
|
|
4649
|
+
"@babel/preset-typescript",
|
|
4650
|
+
{ isTSX, allExtensions: isTSX, allowDeclareFields: true }
|
|
4651
|
+
]
|
|
4652
|
+
] : [],
|
|
4653
|
+
plugins: [
|
|
4654
|
+
["@babel/plugin-syntax-jsx"],
|
|
4655
|
+
[pluginPath, { processNestedWorklets: true }]
|
|
4656
|
+
]
|
|
4657
|
+
});
|
|
4658
|
+
if (!result?.code) return null;
|
|
4659
|
+
return { code: result.code, map: result.map ?? null };
|
|
4660
|
+
} catch (err) {
|
|
4661
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4662
|
+
console.warn(`[sootsim-worklets-babel] transform failed for ${id}: ${message}`);
|
|
4663
|
+
return null;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
3974
4668
|
function nodeModulesJsxTransform() {
|
|
3975
4669
|
return {
|
|
3976
4670
|
name: "sootsim-node-modules-jsx",
|
|
@@ -4008,14 +4702,14 @@ function metroNativeResolve(isAppSource) {
|
|
|
4008
4702
|
for (const ext of platformExts) {
|
|
4009
4703
|
const candidate = base + ext;
|
|
4010
4704
|
try {
|
|
4011
|
-
if (
|
|
4705
|
+
if (import_fs5.default.existsSync(candidate)) return candidate;
|
|
4012
4706
|
} catch {
|
|
4013
4707
|
}
|
|
4014
4708
|
}
|
|
4015
4709
|
for (const ext of platformExts) {
|
|
4016
|
-
const candidate =
|
|
4710
|
+
const candidate = import_path5.default.join(base, "index" + ext);
|
|
4017
4711
|
try {
|
|
4018
|
-
if (
|
|
4712
|
+
if (import_fs5.default.existsSync(candidate)) return candidate;
|
|
4019
4713
|
} catch {
|
|
4020
4714
|
}
|
|
4021
4715
|
}
|
|
@@ -4030,9 +4724,9 @@ function metroNativeResolve(isAppSource) {
|
|
|
4030
4724
|
if (!source.startsWith(".")) return null;
|
|
4031
4725
|
if (platformExts.some((ext) => source.endsWith(ext))) return null;
|
|
4032
4726
|
const hasBaseExt = allExts.some((ext) => source.endsWith(ext));
|
|
4033
|
-
const dir =
|
|
4727
|
+
const dir = import_path5.default.dirname(importer);
|
|
4034
4728
|
if (hasBaseExt) {
|
|
4035
|
-
const resolved =
|
|
4729
|
+
const resolved = import_path5.default.resolve(dir, source);
|
|
4036
4730
|
for (const ext of allExts) {
|
|
4037
4731
|
if (source.endsWith(ext)) {
|
|
4038
4732
|
const found = tryResolve(resolved.slice(0, -ext.length));
|
|
@@ -4041,7 +4735,7 @@ function metroNativeResolve(isAppSource) {
|
|
|
4041
4735
|
}
|
|
4042
4736
|
}
|
|
4043
4737
|
} else {
|
|
4044
|
-
const found = tryResolve(
|
|
4738
|
+
const found = tryResolve(import_path5.default.resolve(dir, source));
|
|
4045
4739
|
if (found) return found;
|
|
4046
4740
|
}
|
|
4047
4741
|
return null;
|
|
@@ -4098,7 +4792,7 @@ function reactNativeRequirePlugin(isAppSource) {
|
|
|
4098
4792
|
}
|
|
4099
4793
|
function externalAppResolvePlugin(appDir, isAppSource, ownedPackages) {
|
|
4100
4794
|
if (!appDir) return { name: "sootsim-external-app-resolve" };
|
|
4101
|
-
const appVirtualImporter =
|
|
4795
|
+
const appVirtualImporter = import_path5.default.join(appDir, "_virtual_.js");
|
|
4102
4796
|
return {
|
|
4103
4797
|
name: "sootsim-external-app-resolve",
|
|
4104
4798
|
enforce: "pre",
|
|
@@ -4108,7 +4802,7 @@ function externalAppResolvePlugin(appDir, isAppSource, ownedPackages) {
|
|
|
4108
4802
|
return null;
|
|
4109
4803
|
if (ownedPackages.has(source) || ownedPackages.has(getPackageName(source)))
|
|
4110
4804
|
return null;
|
|
4111
|
-
if (!isAppSource(importer) && !importer.includes(
|
|
4805
|
+
if (!isAppSource(importer) && !importer.includes(import_path5.default.join(appDir, "node_modules")))
|
|
4112
4806
|
return null;
|
|
4113
4807
|
if (source === "react-native" || source.startsWith("react-native/")) return null;
|
|
4114
4808
|
const resolved = await this.resolve(source, appVirtualImporter, {
|
|
@@ -4210,8 +4904,12 @@ function stubMissingImages(isAppSource) {
|
|
|
4210
4904
|
}
|
|
4211
4905
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4212
4906
|
0 && (module.exports = {
|
|
4907
|
+
engineShellWorkerPlugins,
|
|
4213
4908
|
fixJsxRuntimeExports,
|
|
4909
|
+
keyboardControllerBindingsRedirect,
|
|
4910
|
+
reanimatedNativeSeamRedirect,
|
|
4214
4911
|
sootsim,
|
|
4215
4912
|
workerReactBridgePlugin,
|
|
4913
|
+
workletsBabelTransform,
|
|
4216
4914
|
wsBridgePlugin
|
|
4217
4915
|
});
|