sootsim 0.1.291 → 0.1.293
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/expectations.ts +32 -12
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-EDS4HRKB.js → agent-YWI2NWL7.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-ZCSAPGFF.js → agent-wrapper-IYJ7UZOA.js} +2 -2
- package/dist-cli/chunks/{app-fonts-4WW4EQEW.js → app-fonts-FQFUC4LR.js} +2 -2
- package/dist-cli/chunks/{assert-XEWAR4HT.js → assert-XUCELWAC.js} +2 -2
- package/dist-cli/chunks/{auth-SAYME64O.js → auth-4DQTV3OW.js} +2 -2
- package/dist-cli/chunks/beta-CFDAW6UN.js +2 -0
- package/dist-cli/chunks/{camera-2AHKTJ5I.js → camera-RYOHBVZA.js} +2 -2
- package/dist-cli/chunks/{chunk-7CU7FXRN.js → chunk-2M57EAWS.js} +1 -1
- package/dist-cli/chunks/{chunk-LJU4JU3N.js → chunk-2VKTOZQ5.js} +2 -2
- package/dist-cli/chunks/{chunk-NGPK2CLP.js → chunk-2XYCSFGN.js} +3 -3
- package/dist-cli/chunks/{chunk-UQLIMTO6.js → chunk-3UX6AY2B.js} +1 -1
- package/dist-cli/chunks/{chunk-R36NUSCM.js → chunk-4B4VFT73.js} +2 -2
- package/dist-cli/chunks/{chunk-KQKLLUX5.js → chunk-547W23BB.js} +2 -2
- package/dist-cli/chunks/{chunk-KSN2WTV6.js → chunk-67CIS55C.js} +1 -1
- package/dist-cli/chunks/{chunk-VK3IISND.js → chunk-6IFJLHDA.js} +1 -1
- package/dist-cli/chunks/{chunk-NRXMKMIJ.js → chunk-6VVLDYPV.js} +2 -2
- package/dist-cli/chunks/{chunk-LD5ZN4ZU.js → chunk-7B6RGFTM.js} +2 -2
- package/dist-cli/chunks/{chunk-HFJEB4UJ.js → chunk-7H34H74K.js} +2 -2
- package/dist-cli/chunks/{chunk-HQRKN435.js → chunk-7H5G4HUK.js} +2 -2
- package/dist-cli/chunks/{chunk-OC6PU44E.js → chunk-ACSTEPSD.js} +1 -1
- package/dist-cli/chunks/chunk-AFRXWKTA.js +118 -0
- package/dist-cli/chunks/{chunk-7GKJ3U2Z.js → chunk-B7TX4WFB.js} +2 -2
- package/dist-cli/chunks/{chunk-EFTPP4W3.js → chunk-BQT5YDTV.js} +3 -3
- package/dist-cli/chunks/{chunk-NPP6ZJLD.js → chunk-DE4ZA3NL.js} +2 -2
- package/dist-cli/chunks/{chunk-462J7KLI.js → chunk-E5WYXRGV.js} +3 -3
- package/dist-cli/chunks/chunk-HKMWUN7F.js +2 -0
- package/dist-cli/chunks/{chunk-5CJQNMXY.js → chunk-J6P2UPXH.js} +2 -2
- package/dist-cli/chunks/{chunk-GJDCBMAP.js → chunk-JOPKZVOW.js} +1 -1
- package/dist-cli/chunks/{chunk-EPJO7YNS.js → chunk-K5JEUI5G.js} +1 -1
- package/dist-cli/chunks/{chunk-5D5XSE5V.js → chunk-KC5SCOGS.js} +2 -2
- package/dist-cli/chunks/{chunk-FWC4J42U.js → chunk-LA5DIO33.js} +1 -1
- package/dist-cli/chunks/{chunk-653O3F3M.js → chunk-LOQLZFRK.js} +2 -2
- package/dist-cli/chunks/{chunk-QOIOK64H.js → chunk-MOU2WLO4.js} +1 -1
- package/dist-cli/chunks/{chunk-LHBU5QMM.js → chunk-NCCMRFXU.js} +1 -1
- package/dist-cli/chunks/{chunk-5IBT7JOS.js → chunk-PCAPHXN3.js} +2 -2
- package/dist-cli/chunks/chunk-QXDXUDJD.js +1 -0
- package/dist-cli/chunks/{chunk-NN4CZ2QW.js → chunk-RKXHQ2Q3.js} +1 -1
- package/dist-cli/chunks/{chunk-Z6I5M7VJ.js → chunk-RMLJXP7T.js} +2 -2
- package/dist-cli/chunks/chunk-RV67J726.js +2 -0
- package/dist-cli/chunks/{chunk-UZA3Q67P.js → chunk-TJS5IMOH.js} +1 -1
- package/dist-cli/chunks/{chunk-KTTLGU7Y.js → chunk-U3564F2Y.js} +2 -2
- package/dist-cli/chunks/{chunk-3FBFOGQJ.js → chunk-UOHGYYSO.js} +1 -1
- package/dist-cli/chunks/{chunk-DLMAMKWL.js → chunk-UPKYK52K.js} +2 -2
- package/dist-cli/chunks/{chunk-GZON2OYB.js → chunk-USJTVC7T.js} +1 -1
- package/dist-cli/chunks/{chunk-U6GFEFUD.js → chunk-VO74GDXL.js} +3 -3
- package/dist-cli/chunks/{chunk-76O3ZWTI.js → chunk-VZ44FQDG.js} +1 -1
- package/dist-cli/chunks/chunk-WI36YJ23.js +2 -0
- package/dist-cli/chunks/{chunk-5NGZRMTN.js → chunk-WOWLO4LL.js} +1 -1
- package/dist-cli/chunks/chunk-WP3TFHFL.js +10 -0
- package/dist-cli/chunks/{chunk-KBG6WN24.js → chunk-WPPMUUML.js} +2 -2
- package/dist-cli/chunks/{chunk-ZYS5SEB2.js → chunk-WVMYRKSS.js} +2 -2
- package/dist-cli/chunks/chunk-YNG4C3ZS.js +2 -0
- package/dist-cli/chunks/{chunk-JTZNB4PB.js → chunk-YRFWATLA.js} +2 -2
- package/dist-cli/chunks/{cleanup-KXX5W42A.js → cleanup-BLFDJSFS.js} +2 -2
- package/dist-cli/chunks/cli-version-TQHZOK4L.js +2 -0
- package/dist-cli/chunks/{compat-VDVJ2I74.js → compat-Q3WCSTB7.js} +3 -3
- package/dist-cli/chunks/{config-FIQQP5FR.js → config-NHNMRRNB.js} +2 -2
- package/dist-cli/chunks/control-I7YBDRJT.js +2 -0
- package/dist-cli/chunks/daemon-4R3SZLZY.js +2 -0
- package/dist-cli/chunks/{debug-LPLH46JF.js → debug-PGRMZFXA.js} +3 -3
- package/dist-cli/chunks/{desktop-4HDRWKMN.js → desktop-NVTQ37LH.js} +3 -3
- package/dist-cli/chunks/{detox-U23WKW23.js → detox-W3JDXS76.js} +2 -2
- package/dist-cli/chunks/{device-HZKTA7ZV.js → device-KQYN3EVK.js} +2 -2
- package/dist-cli/chunks/{diagnose-M64QUWBW.js → diagnose-CPUKSGNO.js} +2 -2
- package/dist-cli/chunks/disk-cleanup-2COWZWBC.js +2 -0
- package/dist-cli/chunks/drivers-RRZM6E72.js +2 -0
- package/dist-cli/chunks/{film-4IB44CT3.js → film-VZGAZ2YY.js} +3 -3
- package/dist-cli/chunks/flow-VH6DFDQ4.js +2 -0
- package/dist-cli/chunks/help-3HQ2RINO.js +2 -0
- package/dist-cli/chunks/{hidden-runtime-alias-UEU5POA4.js → hidden-runtime-alias-KWHABCNZ.js} +2 -2
- package/dist-cli/chunks/{home-paths-2C4YKPPX.js → home-paths-O4CGYVHF.js} +2 -2
- package/dist-cli/chunks/inspect-PKMJGXDH.js +2 -0
- package/dist-cli/chunks/install-desktop-K5TP3ZHJ.js +2 -0
- package/dist-cli/chunks/{login-MY54AOXW.js → login-KPN4Z26Q.js} +4 -4
- package/dist-cli/chunks/{logout-LYR2RDJY.js → logout-E3UCJAMC.js} +2 -2
- package/dist-cli/chunks/{maestro-4RAMCNZQ.js → maestro-PYA4AHTS.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-CBSUR73A.js → maestro-generate-KCMYWPWL.js} +3 -3
- package/dist-cli/chunks/{mode-2KATMMXB.js → mode-AEB7G2SJ.js} +2 -2
- package/dist-cli/chunks/optional-demo-registry-AIWOBAAA.js +2 -0
- package/dist-cli/chunks/{perf-FVL2ZRQW.js → perf-YLXGZI3E.js} +2 -2
- package/dist-cli/chunks/permissions-7VMVZOUV.js +11 -0
- package/dist-cli/chunks/{record-TCD4VLNH.js → record-AVQO45PE.js} +3 -3
- package/dist-cli/chunks/runtime-HOGETVVD.js +2 -0
- package/dist-cli/chunks/{screenshot-command-ENCJ4W2W.js → screenshot-command-LACQLA4N.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-FVVMZPFO.js → screenshots-capture-PROOBSGB.js} +2 -2
- package/dist-cli/chunks/{serve-HNHS3QBP.js → serve-FJN2FRGW.js} +2 -2
- package/dist-cli/chunks/{setup-MRLIGVU4.js → setup-IVV4NRYQ.js} +3 -3
- package/dist-cli/chunks/{skills-2LW5BE7P.js → skills-WYR3PJOH.js} +2 -2
- package/dist-cli/chunks/{storage-KH5HQF3V.js → storage-GM7K4MGM.js} +2 -2
- package/dist-cli/chunks/store-YLVHPKG7.js +2 -0
- package/dist-cli/chunks/telemetry-MQKB6PFX.js +2 -0
- package/dist-cli/chunks/{timeline-BWFBU4VM.js → timeline-R5UL3N4T.js} +2 -2
- package/dist-cli/chunks/{upgrade-GZFX4JEO.js → upgrade-RWHSFZRX.js} +2 -2
- package/dist-cli/chunks/upload-RBH4XGJ2.js +2 -0
- package/dist-cli/chunks/{version-OKMVZCP5.js → version-7K6TVQOW.js} +2 -2
- package/dist-cli/chunks/web-46ZPQCAL.js +2 -0
- package/dist-cli/chunks/{what-happened-MC7JDW6C.js → what-happened-K24TSJZX.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.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 +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/detox/index.cjs +24 -6
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +411 -66
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +38 -2
- package/dist-lib/menu.mjs +38 -2
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +354 -2
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +443 -91
- package/dist-lib/vite.cjs +1 -1
- package/package.json +2 -1
- package/scripts/dev-server-scanner.ts +27 -1
- package/skills/contrast/SKILL.md +1 -0
- package/src/menu.ts +38 -1
- package/src/settings-bundle.ts +439 -0
- package/dist-cli/chunks/beta-HKVUNMLY.js +0 -2
- package/dist-cli/chunks/chunk-5ZBUNUUE.js +0 -2
- package/dist-cli/chunks/chunk-7NW75BHD.js +0 -5
- package/dist-cli/chunks/chunk-H7F2BILD.js +0 -118
- package/dist-cli/chunks/chunk-IDZP6WN5.js +0 -2
- package/dist-cli/chunks/chunk-IGPDVTHJ.js +0 -2
- package/dist-cli/chunks/chunk-KU3T3N2W.js +0 -1
- package/dist-cli/chunks/chunk-ZEYCE3XJ.js +0 -2
- package/dist-cli/chunks/cli-version-IQXVYN67.js +0 -2
- package/dist-cli/chunks/control-GDBZBVQC.js +0 -2
- package/dist-cli/chunks/daemon-Y76JSFGV.js +0 -2
- package/dist-cli/chunks/disk-cleanup-P662TT6J.js +0 -2
- package/dist-cli/chunks/drivers-CKWEDYVG.js +0 -2
- package/dist-cli/chunks/flow-WDFYSQKC.js +0 -2
- package/dist-cli/chunks/help-2S3OHQLT.js +0 -2
- package/dist-cli/chunks/inspect-SKU24VE3.js +0 -2
- package/dist-cli/chunks/install-desktop-RKURHJ34.js +0 -2
- package/dist-cli/chunks/optional-demo-registry-26Q7AW7X.js +0 -2
- package/dist-cli/chunks/runtime-Z3D53UGW.js +0 -2
- package/dist-cli/chunks/store-IBEI6PK4.js +0 -2
- package/dist-cli/chunks/telemetry-3T56HLDZ.js +0 -2
- package/dist-cli/chunks/upload-PCXRS5D7.js +0 -2
- package/dist-cli/chunks/web-ZHCP6WFY.js +0 -2
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.1.
|
|
1
|
+
/*! sootsim v0.1.293 | (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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sootsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.293",
|
|
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",
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
"preuninstall": "node ./scripts/preuninstall.cjs"
|
|
164
164
|
},
|
|
165
165
|
"dependencies": {
|
|
166
|
+
"@expo/plist": "^0.5.4",
|
|
166
167
|
"ws": "^8.18.0"
|
|
167
168
|
},
|
|
168
169
|
"peerDependencies": {
|
|
@@ -17,10 +17,15 @@ import { exec } from 'child_process'
|
|
|
17
17
|
import http from 'http'
|
|
18
18
|
import net from 'net'
|
|
19
19
|
import { promisify } from 'util'
|
|
20
|
+
import type { AppSettingsSpecifier } from '@sootsim/globals'
|
|
20
21
|
|
|
21
22
|
const execP = promisify(exec)
|
|
22
23
|
import { applySootSimConfigToUrl } from '../src/config.ts'
|
|
23
24
|
import { normalizeNativeDevBundleUrl } from '../src/native-dev-bundle-url.ts'
|
|
25
|
+
import {
|
|
26
|
+
__resetAppSettingsBundleCacheForTests,
|
|
27
|
+
readAppSettingsBundle,
|
|
28
|
+
} from '../src/settings-bundle.ts'
|
|
24
29
|
import { loadOptionalDemoApps, optionalDemoAppsSync } from './optional-demo-registry.ts'
|
|
25
30
|
|
|
26
31
|
// pre-warm the optional internal demo registry once at module load. in-repo
|
|
@@ -41,6 +46,8 @@ export interface DiscoveredServer {
|
|
|
41
46
|
iconUrl?: string
|
|
42
47
|
iconPath?: string
|
|
43
48
|
bundleId?: string
|
|
49
|
+
settingsBundle?: readonly AppSettingsSpecifier[]
|
|
50
|
+
settingsStorageScopeUrl?: string
|
|
44
51
|
patched?: boolean
|
|
45
52
|
/** absolute cwd of the owning node/bun process — resolved from `lsof -d cwd`
|
|
46
53
|
* when the scanner can see the pid. used to auto-attach the project for
|
|
@@ -709,8 +716,25 @@ export function __resetScannerCache() {
|
|
|
709
716
|
knownOne.clear()
|
|
710
717
|
enrichmentCache.clear()
|
|
711
718
|
enrichmentInFlight.clear()
|
|
719
|
+
__resetAppSettingsBundleCacheForTests()
|
|
712
720
|
}
|
|
713
721
|
|
|
722
|
+
async function attachAppSettingsBundle(
|
|
723
|
+
result: DiscoveredServer,
|
|
724
|
+
projectRoot: string,
|
|
725
|
+
): Promise<void> {
|
|
726
|
+
const settingsBundle = await readAppSettingsBundle(projectRoot)
|
|
727
|
+
if (settingsBundle) {
|
|
728
|
+
result.settingsBundle = settingsBundle
|
|
729
|
+
result.settingsStorageScopeUrl = result.bundleUrl
|
|
730
|
+
} else {
|
|
731
|
+
delete result.settingsBundle
|
|
732
|
+
delete result.settingsStorageScopeUrl
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export const __attachAppSettingsBundleForTests = attachAppSettingsBundle
|
|
737
|
+
|
|
714
738
|
export async function scanDevServers(
|
|
715
739
|
opts: ScanOptions = {},
|
|
716
740
|
): Promise<DiscoveredServer[]> {
|
|
@@ -796,7 +820,9 @@ export async function scanDevServers(
|
|
|
796
820
|
if (!pid) return
|
|
797
821
|
result.pid = pid
|
|
798
822
|
const cwd = await resolveProcessCwd(pid)
|
|
799
|
-
if (cwd)
|
|
823
|
+
if (!cwd) return
|
|
824
|
+
result.cwd = cwd
|
|
825
|
+
await attachAppSettingsBundle(result, cwd)
|
|
800
826
|
}),
|
|
801
827
|
)
|
|
802
828
|
// evict stale cwd cache entries for pids no longer listening
|
package/skills/contrast/SKILL.md
CHANGED
|
@@ -25,6 +25,7 @@ automation, inspecting the render tree, and managing sims.
|
|
|
25
25
|
| `sootsim record` | capture the live sim as webm/mp4/gif, or sample N png frames (--frames) |
|
|
26
26
|
| `sootsim film` | record a flow on the flat canvas, replay it on the 3d device stage under a camera cinematic, save a clip |
|
|
27
27
|
| `sootsim storage` | manage guest app storage and launch profiles |
|
|
28
|
+
| `sootsim permissions` | inspect and change simulated operating-system permissions |
|
|
28
29
|
| `sootsim perf` | CPU, React render, shell frame, and scroll-layer profiling |
|
|
29
30
|
| `sootsim describe` | curated human-readable UI summary (default inspection tool) |
|
|
30
31
|
| `sootsim find` | locate nodes by text / testID / role / type / predicate |
|
package/src/menu.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface SootSimMenuState {
|
|
|
36
36
|
currentDevice: string
|
|
37
37
|
showBezels: boolean
|
|
38
38
|
showTopBar: boolean
|
|
39
|
+
showRail: boolean
|
|
39
40
|
screenshotMode: boolean
|
|
40
41
|
threeMode: boolean
|
|
41
42
|
inspectMode: boolean
|
|
@@ -107,6 +108,36 @@ function appMenu(): SootSimMenuDef {
|
|
|
107
108
|
shortcutLabel: 'settings',
|
|
108
109
|
showInShortcutDocs: true,
|
|
109
110
|
},
|
|
111
|
+
{
|
|
112
|
+
label: 'Settings Pages',
|
|
113
|
+
items: [
|
|
114
|
+
{
|
|
115
|
+
label: 'Accessibility',
|
|
116
|
+
action: 'open-settings-page',
|
|
117
|
+
actionPayload: 'accessibility',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
label: 'Display & Text Size',
|
|
121
|
+
action: 'open-settings-page',
|
|
122
|
+
actionPayload: 'display-and-text-size',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: 'Notifications',
|
|
126
|
+
action: 'open-settings-page',
|
|
127
|
+
actionPayload: 'notifications',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: 'Apps',
|
|
131
|
+
action: 'open-settings-page',
|
|
132
|
+
actionPayload: 'apps',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Privacy & Security',
|
|
136
|
+
action: 'open-settings-page',
|
|
137
|
+
actionPayload: 'privacy',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
110
141
|
{ type: 'separator' },
|
|
111
142
|
{
|
|
112
143
|
label: 'Hide SootSim',
|
|
@@ -338,9 +369,15 @@ function windowMenu(state: SootSimMenuState): SootSimMenuDef {
|
|
|
338
369
|
label: 'Show Top Bar',
|
|
339
370
|
type: 'checkbox',
|
|
340
371
|
checked: state.showTopBar,
|
|
341
|
-
enabled: state.showBezels,
|
|
342
372
|
action: 'toggle-topbar',
|
|
343
373
|
},
|
|
374
|
+
{
|
|
375
|
+
label: 'Show Rail',
|
|
376
|
+
type: 'checkbox',
|
|
377
|
+
checked: state.showRail,
|
|
378
|
+
action: 'toggle-rail',
|
|
379
|
+
hiddenIn: 'electron',
|
|
380
|
+
},
|
|
344
381
|
{
|
|
345
382
|
label: 'Screenshot',
|
|
346
383
|
type: 'checkbox',
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import plistModule from '@expo/plist'
|
|
4
|
+
import type { AppPreferenceValue, AppSettingsSpecifier } from '@sootsim/globals'
|
|
5
|
+
|
|
6
|
+
const MAX_PLIST_BYTES = 1_048_576
|
|
7
|
+
const MAX_CHILD_DEPTH = 8
|
|
8
|
+
const MAX_PLIST_FILES = 32
|
|
9
|
+
const MAX_SPECIFIERS = 512
|
|
10
|
+
|
|
11
|
+
type PlistRecord = Record<string, unknown>
|
|
12
|
+
|
|
13
|
+
interface ParseBudget {
|
|
14
|
+
files: number
|
|
15
|
+
specifiers: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CachedSettingsBundle {
|
|
19
|
+
fingerprint: string
|
|
20
|
+
specifiers: readonly AppSettingsSpecifier[] | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const settingsBundleCache = new Map<string, CachedSettingsBundle>()
|
|
24
|
+
|
|
25
|
+
function isRecord(value: unknown): value is PlistRecord {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolvePlistParser(value: unknown): ((source: string) => unknown) | null {
|
|
30
|
+
if (!isRecord(value)) return null
|
|
31
|
+
const direct = value.parse
|
|
32
|
+
if (typeof direct === 'function') {
|
|
33
|
+
return (source) => Reflect.apply(direct, value, [source])
|
|
34
|
+
}
|
|
35
|
+
const nested = value.default
|
|
36
|
+
if (!isRecord(nested)) return null
|
|
37
|
+
const nestedParse = nested.parse
|
|
38
|
+
return typeof nestedParse === 'function'
|
|
39
|
+
? (source) => Reflect.apply(nestedParse, nested, [source])
|
|
40
|
+
: null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// bun consumes @expo/plist's default export directly, while the compiled commonjs
|
|
44
|
+
// scanner receives the package namespace with that export nested under
|
|
45
|
+
// `default`. resolve the parser once at this package boundary so source and
|
|
46
|
+
// published scanners execute the same implementation.
|
|
47
|
+
const parsePlist = resolvePlistParser(plistModule)
|
|
48
|
+
|
|
49
|
+
function optionalString(value: unknown): string | undefined {
|
|
50
|
+
return typeof value === 'string' ? value : undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requiredString(value: unknown): string | null {
|
|
54
|
+
if (typeof value !== 'string') return null
|
|
55
|
+
const normalized = value.trim()
|
|
56
|
+
return normalized ? normalized : null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function finiteNumber(value: unknown): number | null {
|
|
60
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function preferenceValue(value: unknown): AppPreferenceValue | null {
|
|
64
|
+
if (typeof value === 'string' || typeof value === 'boolean') return value
|
|
65
|
+
return finiteNumber(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function keyboardType(
|
|
69
|
+
value: unknown,
|
|
70
|
+
): Extract<AppSettingsSpecifier, { type: 'text' }>['keyboardType'] {
|
|
71
|
+
if (typeof value !== 'string') return undefined
|
|
72
|
+
switch (value.replaceAll(/[-_\s]/g, '').toLowerCase()) {
|
|
73
|
+
case 'alphabet':
|
|
74
|
+
return 'alphabet'
|
|
75
|
+
case 'numbersandpunctuation':
|
|
76
|
+
return 'numbers-and-punctuation'
|
|
77
|
+
case 'numberpad':
|
|
78
|
+
return 'number-pad'
|
|
79
|
+
case 'url':
|
|
80
|
+
return 'url'
|
|
81
|
+
case 'emailaddress':
|
|
82
|
+
return 'email-address'
|
|
83
|
+
case 'namephonepad':
|
|
84
|
+
return 'name-phone-pad'
|
|
85
|
+
case 'asciicapable':
|
|
86
|
+
return 'ascii-capable'
|
|
87
|
+
default:
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function autocapitalization(
|
|
93
|
+
value: unknown,
|
|
94
|
+
): Extract<AppSettingsSpecifier, { type: 'text' }>['autocapitalization'] {
|
|
95
|
+
if (typeof value !== 'string') return undefined
|
|
96
|
+
switch (value.replaceAll(/[-_\s]/g, '').toLowerCase()) {
|
|
97
|
+
case 'none':
|
|
98
|
+
return 'none'
|
|
99
|
+
case 'sentences':
|
|
100
|
+
return 'sentences'
|
|
101
|
+
case 'words':
|
|
102
|
+
return 'words'
|
|
103
|
+
case 'allcharacters':
|
|
104
|
+
return 'all-characters'
|
|
105
|
+
default:
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function autocorrection(value: unknown): boolean | undefined {
|
|
111
|
+
if (typeof value === 'boolean') return value
|
|
112
|
+
if (typeof value !== 'string') return undefined
|
|
113
|
+
switch (value.trim().toLowerCase()) {
|
|
114
|
+
case 'yes':
|
|
115
|
+
return true
|
|
116
|
+
case 'no':
|
|
117
|
+
return false
|
|
118
|
+
default:
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function safeChildPlistName(value: unknown): { file: string; name: string } | null {
|
|
124
|
+
const file = requiredString(value)
|
|
125
|
+
if (!file || file.includes('/') || file.includes('\\') || file.includes('..')) {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
file,
|
|
130
|
+
name: file.toLowerCase().endsWith('.plist') ? file : `${file}.plist`,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function readableSettingsBundle(path: string): Promise<boolean> {
|
|
135
|
+
try {
|
|
136
|
+
const root = await stat(join(path, 'Root.plist'))
|
|
137
|
+
return root.isFile() && root.size <= MAX_PLIST_BYTES
|
|
138
|
+
} catch {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function findSettingsBundle(projectRoot: string): Promise<string | null> {
|
|
144
|
+
const directCandidates = [
|
|
145
|
+
join(projectRoot, 'Settings.bundle'),
|
|
146
|
+
join(projectRoot, 'ios', 'Settings.bundle'),
|
|
147
|
+
]
|
|
148
|
+
for (const candidate of directCandidates) {
|
|
149
|
+
if (await readableSettingsBundle(candidate)) return candidate
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const iosRoot = join(projectRoot, 'ios')
|
|
154
|
+
const children = await readdir(iosRoot, { withFileTypes: true })
|
|
155
|
+
const candidates = children
|
|
156
|
+
.filter(
|
|
157
|
+
(entry) =>
|
|
158
|
+
entry.isDirectory() &&
|
|
159
|
+
entry.name !== 'Pods' &&
|
|
160
|
+
entry.name !== 'build' &&
|
|
161
|
+
!entry.name.startsWith('.'),
|
|
162
|
+
)
|
|
163
|
+
.map((entry) => join(iosRoot, entry.name, 'Settings.bundle'))
|
|
164
|
+
.sort()
|
|
165
|
+
for (const candidate of candidates) {
|
|
166
|
+
if (await readableSettingsBundle(candidate)) return candidate
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function settingsBundleFingerprint(bundlePath: string): Promise<string | null> {
|
|
174
|
+
try {
|
|
175
|
+
const entries = await readdir(bundlePath, { withFileTypes: true })
|
|
176
|
+
const names = entries
|
|
177
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.plist'))
|
|
178
|
+
.map((entry) => entry.name)
|
|
179
|
+
.sort()
|
|
180
|
+
.slice(0, MAX_PLIST_FILES + 1)
|
|
181
|
+
if (names.length === 0 || names.length > MAX_PLIST_FILES) return null
|
|
182
|
+
|
|
183
|
+
const parts = await Promise.all(
|
|
184
|
+
names.map(async (name) => {
|
|
185
|
+
const metadata = await stat(join(bundlePath, name))
|
|
186
|
+
if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null
|
|
187
|
+
return `${name}:${metadata.size}:${metadata.mtimeMs}:${metadata.ctimeMs}`
|
|
188
|
+
}),
|
|
189
|
+
)
|
|
190
|
+
if (parts.some((part) => part === null)) return null
|
|
191
|
+
return parts.join('|')
|
|
192
|
+
} catch {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function parsePlistRecord(path: string): Promise<PlistRecord | null> {
|
|
198
|
+
try {
|
|
199
|
+
if (!parsePlist) return null
|
|
200
|
+
const metadata = await stat(path)
|
|
201
|
+
if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null
|
|
202
|
+
const source = await readFile(path, 'utf8')
|
|
203
|
+
const parsed = parsePlist(source)
|
|
204
|
+
return isRecord(parsed) ? parsed : null
|
|
205
|
+
} catch {
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeMultiValue(
|
|
211
|
+
raw: PlistRecord,
|
|
212
|
+
key: string,
|
|
213
|
+
title: string,
|
|
214
|
+
): AppSettingsSpecifier | null {
|
|
215
|
+
if (!Array.isArray(raw.Values) || !Array.isArray(raw.Titles)) return null
|
|
216
|
+
const values: AppPreferenceValue[] = []
|
|
217
|
+
const titles: string[] = []
|
|
218
|
+
let optionType: 'string' | 'number' | 'boolean' | undefined
|
|
219
|
+
const pairCount = Math.min(raw.Values.length, raw.Titles.length)
|
|
220
|
+
for (let index = 0; index < pairCount; index++) {
|
|
221
|
+
const value = preferenceValue(raw.Values[index])
|
|
222
|
+
const optionTitle = optionalString(raw.Titles[index])
|
|
223
|
+
if (value === null || optionTitle === undefined) continue
|
|
224
|
+
const valueType: 'string' | 'number' | 'boolean' =
|
|
225
|
+
typeof value === 'string'
|
|
226
|
+
? 'string'
|
|
227
|
+
: typeof value === 'number'
|
|
228
|
+
? 'number'
|
|
229
|
+
: 'boolean'
|
|
230
|
+
if (optionType !== undefined && valueType !== optionType) continue
|
|
231
|
+
optionType = valueType
|
|
232
|
+
values.push(value)
|
|
233
|
+
titles.push(optionTitle)
|
|
234
|
+
}
|
|
235
|
+
const firstValue = values[0]
|
|
236
|
+
if (firstValue === undefined) return null
|
|
237
|
+
const declaredDefault = preferenceValue(raw.DefaultValue)
|
|
238
|
+
const defaultValue =
|
|
239
|
+
declaredDefault !== null && values.includes(declaredDefault)
|
|
240
|
+
? declaredDefault
|
|
241
|
+
: firstValue
|
|
242
|
+
return {
|
|
243
|
+
type: 'multi-value',
|
|
244
|
+
key,
|
|
245
|
+
title,
|
|
246
|
+
defaultValue,
|
|
247
|
+
currentValue: defaultValue,
|
|
248
|
+
values,
|
|
249
|
+
titles,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function normalizeSpecifier(
|
|
254
|
+
raw: unknown,
|
|
255
|
+
context: {
|
|
256
|
+
bundlePath: string
|
|
257
|
+
depth: number
|
|
258
|
+
budget: ParseBudget
|
|
259
|
+
stack: ReadonlySet<string>
|
|
260
|
+
fileCache: Map<string, readonly AppSettingsSpecifier[] | null>
|
|
261
|
+
},
|
|
262
|
+
): Promise<AppSettingsSpecifier | null> {
|
|
263
|
+
if (!isRecord(raw)) return null
|
|
264
|
+
if (context.budget.specifiers >= MAX_SPECIFIERS) return null
|
|
265
|
+
const type = requiredString(raw.Type)
|
|
266
|
+
if (!type) return null
|
|
267
|
+
context.budget.specifiers += 1
|
|
268
|
+
|
|
269
|
+
if (type === 'PSGroupSpecifier') {
|
|
270
|
+
const title = optionalString(raw.Title)
|
|
271
|
+
const footer = optionalString(raw.FooterText)
|
|
272
|
+
return {
|
|
273
|
+
type: 'group',
|
|
274
|
+
...(title !== undefined ? { title } : null),
|
|
275
|
+
...(footer !== undefined ? { footer } : null),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const key = requiredString(raw.Key)
|
|
280
|
+
if (type === 'PSSliderSpecifier') {
|
|
281
|
+
if (!key) return null
|
|
282
|
+
const minimumValue = finiteNumber(raw.MinimumValue)
|
|
283
|
+
const maximumValue = finiteNumber(raw.MaximumValue)
|
|
284
|
+
if (minimumValue === null || maximumValue === null || maximumValue <= minimumValue) {
|
|
285
|
+
return null
|
|
286
|
+
}
|
|
287
|
+
const declaredDefault = finiteNumber(raw.DefaultValue)
|
|
288
|
+
const defaultValue = Math.min(
|
|
289
|
+
maximumValue,
|
|
290
|
+
Math.max(minimumValue, declaredDefault ?? minimumValue),
|
|
291
|
+
)
|
|
292
|
+
const title = optionalString(raw.Title)
|
|
293
|
+
const minimumValueImage = optionalString(raw.MinimumValueImage)
|
|
294
|
+
const maximumValueImage = optionalString(raw.MaximumValueImage)
|
|
295
|
+
return {
|
|
296
|
+
type: 'slider',
|
|
297
|
+
key,
|
|
298
|
+
defaultValue,
|
|
299
|
+
currentValue: defaultValue,
|
|
300
|
+
minimumValue,
|
|
301
|
+
maximumValue,
|
|
302
|
+
...(title !== undefined ? { title } : null),
|
|
303
|
+
...(minimumValueImage !== undefined ? { minimumValueImage } : null),
|
|
304
|
+
...(maximumValueImage !== undefined ? { maximumValueImage } : null),
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (type === 'PSChildPaneSpecifier') {
|
|
309
|
+
const title = requiredString(raw.Title)
|
|
310
|
+
const child = safeChildPlistName(raw.File)
|
|
311
|
+
if (!title || !child || context.depth >= MAX_CHILD_DEPTH) return null
|
|
312
|
+
const childSpecifiers = await parseSpecifierFile(child.name, {
|
|
313
|
+
...context,
|
|
314
|
+
depth: context.depth + 1,
|
|
315
|
+
})
|
|
316
|
+
if (!childSpecifiers || childSpecifiers.length === 0) return null
|
|
317
|
+
return {
|
|
318
|
+
type: 'child-pane',
|
|
319
|
+
title,
|
|
320
|
+
file: child.file,
|
|
321
|
+
specifiers: childSpecifiers,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!key) return null
|
|
326
|
+
const title = requiredString(raw.Title) ?? key
|
|
327
|
+
|
|
328
|
+
if (type === 'PSToggleSwitchSpecifier') {
|
|
329
|
+
const defaultValue = typeof raw.DefaultValue === 'boolean' ? raw.DefaultValue : false
|
|
330
|
+
return {
|
|
331
|
+
type: 'toggle',
|
|
332
|
+
key,
|
|
333
|
+
title,
|
|
334
|
+
defaultValue,
|
|
335
|
+
currentValue: defaultValue,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (type === 'PSTextFieldSpecifier') {
|
|
340
|
+
const defaultValue = optionalString(raw.DefaultValue) ?? ''
|
|
341
|
+
const placeholder = optionalString(raw.Placeholder)
|
|
342
|
+
const resolvedKeyboardType = keyboardType(raw.KeyboardType)
|
|
343
|
+
const resolvedAutocapitalization = autocapitalization(raw.AutocapitalizationType)
|
|
344
|
+
const resolvedAutocorrection = autocorrection(raw.AutocorrectionType)
|
|
345
|
+
return {
|
|
346
|
+
type: 'text',
|
|
347
|
+
key,
|
|
348
|
+
title,
|
|
349
|
+
defaultValue,
|
|
350
|
+
currentValue: defaultValue,
|
|
351
|
+
...(placeholder !== undefined ? { placeholder } : null),
|
|
352
|
+
...(typeof raw.IsSecure === 'boolean' ? { secure: raw.IsSecure } : null),
|
|
353
|
+
...(resolvedKeyboardType ? { keyboardType: resolvedKeyboardType } : null),
|
|
354
|
+
...(resolvedAutocapitalization
|
|
355
|
+
? { autocapitalization: resolvedAutocapitalization }
|
|
356
|
+
: null),
|
|
357
|
+
...(resolvedAutocorrection !== undefined
|
|
358
|
+
? { autocorrection: resolvedAutocorrection }
|
|
359
|
+
: null),
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (type === 'PSMultiValueSpecifier' || type === 'PSRadioGroupSpecifier') {
|
|
364
|
+
return normalizeMultiValue(raw, key, title)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (type === 'PSTitleValueSpecifier') {
|
|
368
|
+
const defaultValue = preferenceValue(raw.DefaultValue) ?? ''
|
|
369
|
+
return {
|
|
370
|
+
type: 'title-value',
|
|
371
|
+
key,
|
|
372
|
+
title,
|
|
373
|
+
defaultValue,
|
|
374
|
+
currentValue: defaultValue,
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return null
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function parseSpecifierFile(
|
|
382
|
+
fileName: string,
|
|
383
|
+
context: {
|
|
384
|
+
bundlePath: string
|
|
385
|
+
depth: number
|
|
386
|
+
budget: ParseBudget
|
|
387
|
+
stack: ReadonlySet<string>
|
|
388
|
+
fileCache: Map<string, readonly AppSettingsSpecifier[] | null>
|
|
389
|
+
},
|
|
390
|
+
): Promise<readonly AppSettingsSpecifier[] | null> {
|
|
391
|
+
const cached = context.fileCache.get(fileName)
|
|
392
|
+
if (cached !== undefined) return cached
|
|
393
|
+
if (context.stack.has(fileName) || context.budget.files >= MAX_PLIST_FILES) {
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
context.budget.files += 1
|
|
397
|
+
const root = await parsePlistRecord(join(context.bundlePath, fileName))
|
|
398
|
+
if (!root || !Array.isArray(root.PreferenceSpecifiers)) {
|
|
399
|
+
context.fileCache.set(fileName, null)
|
|
400
|
+
return null
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const stack = new Set(context.stack)
|
|
404
|
+
stack.add(fileName)
|
|
405
|
+
const specifiers: AppSettingsSpecifier[] = []
|
|
406
|
+
for (const raw of root.PreferenceSpecifiers) {
|
|
407
|
+
if (context.budget.specifiers >= MAX_SPECIFIERS) break
|
|
408
|
+
const specifier = await normalizeSpecifier(raw, { ...context, stack })
|
|
409
|
+
if (specifier) specifiers.push(specifier)
|
|
410
|
+
}
|
|
411
|
+
context.fileCache.set(fileName, specifiers)
|
|
412
|
+
return specifiers
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function readAppSettingsBundle(
|
|
416
|
+
projectRoot: string,
|
|
417
|
+
): Promise<readonly AppSettingsSpecifier[] | null> {
|
|
418
|
+
const bundlePath = await findSettingsBundle(projectRoot)
|
|
419
|
+
if (!bundlePath) return null
|
|
420
|
+
const fingerprint = await settingsBundleFingerprint(bundlePath)
|
|
421
|
+
if (!fingerprint) return null
|
|
422
|
+
const cached = settingsBundleCache.get(bundlePath)
|
|
423
|
+
if (cached?.fingerprint === fingerprint) return cached.specifiers
|
|
424
|
+
|
|
425
|
+
const specifiers = await parseSpecifierFile('Root.plist', {
|
|
426
|
+
bundlePath,
|
|
427
|
+
depth: 0,
|
|
428
|
+
budget: { files: 0, specifiers: 0 },
|
|
429
|
+
stack: new Set(),
|
|
430
|
+
fileCache: new Map(),
|
|
431
|
+
})
|
|
432
|
+
const normalized = specifiers && specifiers.length > 0 ? specifiers : null
|
|
433
|
+
settingsBundleCache.set(bundlePath, { fingerprint, specifiers: normalized })
|
|
434
|
+
return normalized
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function __resetAppSettingsBundleCacheForTests(): void {
|
|
438
|
+
settingsBundleCache.clear()
|
|
439
|
+
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
/*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
-
import{c as k,k as l,l as C,m as w}from"./chunk-IDZP6WN5.js";var m="sootsim:settings",a=["errorReporting","colorScheme","reduceMotion","a11yMode","a11yDepth","screenshotCanvas","screenshotBackground","screenshotText","screenshotPose","screenshotDynamicSize","alwaysShowLiveWebViews","agentProvider","onboardingComplete","betaConsentVersion","detailedTelemetry"];function R(){if(typeof window>"u")return null;let e=window.sootsimElectron;return e&&typeof e=="object"?e:null}function g(e){let n={};for(let t of a){if(!(t in e))continue;let o=e[t],i=l[t];i.validate&&!i.validate(o)||i.options&&!i.options.includes(o)||(n[t]=o)}return n}function V(e){let n={};for(let t of a)n[t]=e[t];return n}function D(){if(typeof localStorage>"u")return{};try{let e=localStorage.getItem(m);return e?g(JSON.parse(e)):{}}catch{return{}}}function j(e){if(!(typeof localStorage>"u"))try{localStorage.setItem(m,JSON.stringify(e))}catch{}}function x(){let n=R()?.sharedConfig?.settings;return!n||typeof n!="object"?{}:g(n)}function M(e){let n=R();if(n?.writeSharedConfig)try{n.writeSharedConfig({settings:e})}catch{}}function K(){if(typeof window>"u")return null;let e=window.__sootsimSharedConfig;return e&&typeof e=="object"?e:null}function T(){let e=K()?.settings;return!e||typeof e!="object"?{}:g(e)}function I(e){if(!K())return;let n={settings:e};try{if(globalThis.__sootsimBridgeRequest?.writeSharedConfig?.(n))return}catch{}if(!(typeof fetch>"u"))try{fetch("/__sootsim/shared-config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0}).catch(()=>{})}catch{}}function P(){let e=D(),n=T(),t=x();return{...e,...n,...t}}function _(e,n={}){let t=V(e);for(let o of a)n[o]!==void 0&&(t[o]=n[o]);j(t),M(t),I(t)}function E(e){if(typeof window>"u")return()=>{};let n=r=>{if(!(r.key!==m||r.newValue==null))try{e(g(JSON.parse(r.newValue)))}catch{}};window.addEventListener("storage",n);let t=R(),o;t?.onSharedConfigChanged&&(o=t.onSharedConfigChanged(r=>{let d=r?.settings;!d||typeof d!="object"||e(g(d))}));let c=globalThis.__sootsimBridgeRequest?.subscribeSharedConfig?.(r=>{let d=r?.settings;!d||typeof d!="object"||e(g(d))});return()=>{window.removeEventListener("storage",n),o?.(),c?.()}}var O=C(),B=w(),p=P(),u=new Set,S={};for(let e of a)O[e]!==void 0&&(u.add(e),p[e]!==void 0&&(S[e]=p[e]),delete p[e]);var s={...B,...p},h=[],f=null;function y(){for(let e of h)e()}function v(){y(),_(s,S)}function b(e,n){let t=l[e];return t.validate&&!t.validate(n)||t.options&&!t.options.includes(n)||s[e]===n?!1:(s={...s,[e]:n},e==="deviceModel"&&(f=null),!0)}E(e=>{let n={...s},t=!1;for(let[o,i]of Object.entries(e)){let c=o;if(u.has(c)){S[c]=i;continue}n[c]!==i&&(n[c]=i,t=!0)}t&&(s=n,f=null,y())});var N={get(e){return s[e]},set(e,n){b(e,n)&&v()},setRuntime(e,n){u.has(e)||(u.add(e),a.includes(e)&&(S[e]=s[e])),b(e,n)&&y()},isRuntimeControlled(e){return u.has(e)},getAll(){return s},apply(e){let n=!1,t={...s};for(let[o,i]of Object.entries(e)){if(i===void 0)continue;let c=o,r=l[c];r&&(r.validate&&!r.validate(i)||r.options&&!r.options.includes(i)||t[c]!==i&&(t[c]=i,n=!0))}n&&(s=t,f=null,v())},applyRuntime(e){let n=!1;for(let t of Object.keys(e)){let o=e[t];o!==void 0&&(u.has(t)||(u.add(t),a.includes(t)&&(S[t]=s[t])),b(t,o)&&(n=!0))}n&&y()},reset(){s=w(),f=null,v()},subscribe(e){return h.push(e),()=>{h=h.filter(n=>n!==e)}},getDeviceSpec(){return f||(f=k(s.deviceModel)),f},getA11yMode(){return s.a11yMode},setA11yMode(e){this.set("a11yMode",e)},getInspectMode(){return s.inspectMode},setInspectMode(e){this.set("inspectMode",e)}};export{N as a};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/*! sootsim v0.1.291 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
-
import{a as C,b as D}from"./chunk-VK3IISND.js";import{H as E}from"./chunk-5NGZRMTN.js";var N="sootsimConfig";function p(e){return!!e&&Object.keys(e).length>0}function $(e){return e?p(e.modules)||p(e.turboModules)||p(e.nativeModules)||p(e.env)||p(e.network?.remap)||p(e.settings)||p(e.initialState):!1}function A(e,n){let t=new URL(e);return $(n)?t.searchParams.set(N,JSON.stringify(n)):t.searchParams.delete(N),t.toString()}var pe="/index.bundle?platform=ios&dev=true&hot=true&minify=false";function j(e){return/^https?:\/\//i.test(e)}function O(e){return e.endsWith(".bundle")}function H(e){process.env.SOOTSIM_PREVIEW_PROD_BUNDLE&&(e.searchParams.set("dev","false"),e.searchParams.set("minify","true"),e.searchParams.has("hot")&&e.searchParams.set("hot","false"))}function I(e){try{let n=j(e),t=new URL(e,"http://sootsim.local");return t.pathname=t.pathname.replace(/\.\.bundle$/,".bundle"),O(t.pathname)?(t.searchParams.delete("transform.bytecode"),t.searchParams.get("lazy")==="true"&&t.searchParams.set("lazy","false"),H(t),n?t.toString():`${t.pathname}${t.search}${t.hash}`):e}catch{return e}}function ge(e,n){try{let t=j(e),o=new URL(e,"http://sootsim.local");return O(o.pathname)?(o.searchParams.set("hot",n?"true":"false"),t?o.toString():`${o.pathname}${o.search}${o.hash}`):e}catch{return e}}import{exec as B}from"child_process";import W from"http";import F from"net";import{promisify as G}from"util";var T=G(B);C();var z=250,J=1500,K=120;function V(e,n=K){return new Promise(t=>{let o=new F.Socket,s=!1,i=a=>{s||(s=!0,o.destroy(),t(a))};o.setTimeout(n),o.once("connect",()=>i(!0)),o.once("timeout",()=>i(!1)),o.once("error",()=>i(!1)),o.connect(e,"localhost")})}function h(e,n,t="GET",o=z,s={}){return new Promise(i=>{let a=W.request({hostname:"localhost",port:e,path:n,method:t,timeout:o,headers:s},r=>{let c="";r.on("data",l=>c+=l.toString());let u=(()=>{let l=r.headers["content-type"];if(typeof l=="string")return l;if(Array.isArray(l))return l[0]})();r.on("end",()=>i({statusCode:r.statusCode||0,body:c,contentType:u}))});a.on("error",()=>i(null)),a.setTimeout(o,()=>{a.destroy(),i(null)}),a.end()})}var q=[8081,8082,8083,8084,8085,8086,3e3,3001,19e3].map(e=>({port:e,pid:0}));function k(e,n){return!(e<=0||e>=2e4||n.has(e)||e>=5170&&e<=5200)}async function Y(e=[]){let n=new Set(e);try{let{stdout:t}=await T("lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -E '^(node|bun)'",{encoding:"utf8",timeout:2e3});if(t.trim()){let o=new Map;for(let s of t.trim().split(`
|
|
3
|
-
`)){let i=s.trim().split(/\s+/);if(i.length<9)continue;let a=Number(i[1]),c=i[8].match(/:(\d+)$/);if(!c)continue;let u=Number(c[1]);k(u,n)&&(o.has(u)||o.set(u,a))}if(o.size>0)return[...o.entries()].map(([s,i])=>({port:s,pid:i}))}}catch{}try{let{stdout:t}=await T(`ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`,{encoding:"utf8",timeout:2e3});if(t.trim()){let o=new Map;for(let s of t.trim().split(`
|
|
4
|
-
`)){let i=s.match(/:(\d+)\s/),a=s.match(/pid=(\d+)/);if(!i)continue;let r=Number(i[1]),c=a?Number(a[1]):0;k(r,n)&&(o.has(r)||o.set(r,c))}if(o.size>0)return[...o.entries()].map(([s,i])=>({port:s,pid:i}))}}catch{}return q.filter(t=>k(t.port,n))}var x=new Map;async function Q(e){if(e<=0)return null;let n=x.get(e);if(n)return n;try{let{stdout:t}=await T(`lsof -p ${e} -a -d cwd -Fn 2>/dev/null`,{encoding:"utf8",timeout:1500});for(let o of t.split(`
|
|
5
|
-
`))if(o.startsWith("n")&&o.length>1){let s=o.slice(1).trim();if(s)return x.set(e,s),s}}catch{}return null}function X(e,n){return{port:e,framework:n,bundleUrl:S(e,`http://localhost:${e}/index.bundle?platform=ios&dev=true&hot=true&minify=false`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()}}function S(e,n){let t=D().find(s=>s.preferredPort===e),o=t?.runtimeConfig?A(n,t.runtimeConfig):n;return I(o)}function Z(e){return e.includes("/node_modules/one/metro-entry.bundle")}function ee(e){try{let n=JSON.parse(e);return n&&typeof n=="object"?n:null}catch{return null}}function w(e,n,t){if(!n)return e;try{let o=JSON.parse(n.body),s=o?.extra?.expoClient||o?.extra||{};s.name&&(e.projectName=s.name),s.ios?.bundleIdentifier&&(e.bundleId=s.ios.bundleIdentifier),e.framework==="metro"&&s.sdkVersion&&(e.framework="expo");let i=o?.launchAsset?.url;i&&!e.patched&&!Z(e.bundleUrl)&&(e.bundleUrl=S(e.port,i));let a=s.iconUrl||s.ios?.iconUrl||s.icon||s.ios?.icon;if(a)if(e.iconPath=a,t)if(a.startsWith("http"))e.iconUrl=t(a);else{let r=a.replace(/^\.\//,"");e.iconUrl=t(`http://localhost:${e.port}/assets/${r}`)}else e.iconUrl=a.startsWith("http")?a:`http://localhost:${e.port}/assets/${a.replace(/^\.\//,"")}`}catch{}return e}var ne=2e4,v=new Map,M=new Set;function _(e,n){if(e.projectName)return e;let t=v.get(e.port);if(t)return t.projectName&&(e.projectName=t.projectName),t.iconUrl&&!e.iconUrl&&(e.iconUrl=t.iconUrl,e.iconPath=t.iconPath),t.bundleId&&!e.bundleId&&(e.bundleId=t.bundleId),e;if(!M.has(e.port)){M.add(e.port);let o=e.port;te(o,n).finally(()=>M.delete(o))}return e}async function te(e,n){let t=await h(e,"/","GET",ne,{"expo-platform":"ios"});if(!t||t.statusCode!==200)return;let o=w({port:e,framework:"one",bundleUrl:"",lastSeen:0},t,n);!o.projectName&&!o.iconUrl||(v.set(e,{projectName:o.projectName,iconUrl:o.iconUrl,iconPath:o.iconPath,bundleId:o.bundleId}),b.delete(e))}var d=new Set,g=new Set,y=new Set;async function oe(e,n){if(!await V(e))return null;let t="/node_modules/one/metro-entry.bundle?platform=ios&dev=true",[o,s,i,a]=await Promise.all([d.has(e)?Promise.resolve(null):h(e,"/__soot/"),h(e,"/status"),y.has(e)?Promise.resolve(null):h(e,"/","GET",J,{"expo-platform":"ios"}),g.has(e)?Promise.resolve(null):h(e,"/_expo/status")]);a&&a.statusCode===200?g.delete(e):g.has(e)||g.add(e);let r=i?ee(i.body):null,c=typeof r?.launchAsset?.url=="string"?r.launchAsset.url:null,u=r?.extra?.expoClient||r?.extra||{};if(r&&(c||typeof u.name=="string")){d.add(e);let U=c||`http://localhost:${e}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,L=U.includes("/one/metro-entry.bundle")?"one":"expo";return w({port:e,framework:L,bundleUrl:S(e,U),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()},i,n)}if(s&&s.body.includes("packager-status:running"))return d.add(e),_(w(X(e,a&&a.statusCode===200?"expo":"metro"),i,n),n);if(o&&o.statusCode===200&&o.body.includes("sootsim-patched"))return d.delete(e),_(w({port:e,framework:"one",bundleUrl:S(e,`http://localhost:${e}/__soot/bundle.js`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now(),patched:!0},i,n),n);if(s&&s.statusCode===200||a&&a.statusCode===200||i&&i.statusCode===200&&r!=null)return null;let m=await h(e,t,"HEAD");return m&&m.statusCode>0&&m.statusCode<400&&/application\/javascript/i.test(m.contentType||"")?(d.add(e),y.add(e),_(w({port:e,framework:"one",bundleUrl:S(e,`http://localhost:${e}${t}&minify=false`),hmrUrl:`ws://localhost:${e}/hot`,lastSeen:Date.now()},i,n),n)):(d.add(e),null)}function re(e){return!!e.bundleId?.trim().toLowerCase()?.startsWith("dev.sootsim.")}var b=new Map,se=3e4,ie=1500;function ae(e){return!e||e.framework==="metro"||e.framework==="unknown"}function ce(e){return e?S(e.port,e.bundleUrl)===e.bundleUrl:!0}function ue(e,n,t=Date.now()){if(n===0||e.pid!==n||!ce(e.result))return!1;let o=t-e.cachedAt;return!(e.result===null&&o>=se||ae(e.result)&&o>=ie)}async function Pe(e={}){await C();let n=await Y(e.excludePorts),t=new Set(n.map(r=>r.port));for(let r of[...b.keys()])t.has(r)||b.delete(r);for(let r of[...d])t.has(r)||d.delete(r);for(let r of[...g])t.has(r)||g.delete(r);for(let r of[...y])t.has(r)||y.delete(r);for(let r of[...v.keys()])t.has(r)||v.delete(r);let o=[],s=[];for(let{port:r,pid:c}of n){let u=b.get(r);if(u&&ue(u,c)){u.result&&o.push(u.result);continue}u&&u.pid!==c&&(d.delete(r),g.delete(r),y.delete(r),v.delete(r)),s.push({port:r,pid:c})}s.length>0&&(await Promise.all(s.map(c=>oe(c.port,e.buildIconProxyUrl)))).forEach((c,u)=>{let{port:l,pid:m}=s[u];m!==0&&b.set(l,{pid:m,result:c,cachedAt:Date.now()}),c&&o.push(c)});let i=new Map;for(let{port:r,pid:c}of n)c>0&&i.set(r,c);await Promise.all(o.map(async r=>{let c=i.get(r.port);if(!c)return;r.pid=c;let u=await Q(c);u&&(r.cwd=u)}));let a=new Set(i.values());for(let r of[...x.keys()])a.has(r)||x.delete(r);return o.filter(r=>!re(r))}function le(e){return e.replace(/^\[|\]$/g,"").toLowerCase()}function ke(e){let n=le(e);return n==="localhost"||n.endsWith(".localhost")||n==="0.0.0.0"||n==="::1"||/^127(?:\.\d{1,3}){3}$/.test(n)}import R from"fs";import f from"path";import{fileURLToPath as de}from"url";var P;function fe(){if(P!==void 0)return P;let e=f.dirname(de(import.meta.url));for(let n=0;n<12&&e!==f.dirname(e);n++){if(R.existsSync(f.join(e,"packages/sootsim-engine/package.json"))&&R.existsSync(f.join(e,"public/sootsim")))return P=e,e;e=f.dirname(e)}return P=null,null}function Ne(){if(process.env.SOOTSIM_HOME&&E())return null;let e=fe();if(!e)return null;let n=f.join(e,"public/sootsim");return R.existsSync(f.join(n,"index.html"))&&R.existsSync(f.join(n,"engine/index.html"))&&R.existsSync(f.join(n,"engine-tenant/worker.js"))?n:null}export{A as a,pe as b,I as c,ge as d,oe as e,Pe as f,ke as g,Ne as h};
|