rnxsim 0.0.0 → 0.1.312
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 +512 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +39 -0
- package/detox/expectations.ts +537 -0
- package/detox/gestures.ts +480 -0
- package/detox/index.ts +2118 -0
- package/detox/jest-preset.cjs +51 -0
- package/detox/jest-setup-after-env.cjs +18 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation.ts +43 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-cli/bin.js +15 -0
- package/dist-cli/chunks/agent-NXVDDCFG.js +48 -0
- package/dist-cli/chunks/agent-wrapper-DXJCFKL3.js +17 -0
- package/dist-cli/chunks/app-fonts-W5AJZQJF.js +4 -0
- package/dist-cli/chunks/assert-3LOJEUDT.js +49 -0
- package/dist-cli/chunks/auth-MFTYYPNX.js +12 -0
- package/dist-cli/chunks/beta-GT3NN67B.js +4 -0
- package/dist-cli/chunks/camera-6EAK7CUL.js +22 -0
- package/dist-cli/chunks/chunk-274UD5FD.js +4 -0
- package/dist-cli/chunks/chunk-2SA2VTGP.js +5 -0
- package/dist-cli/chunks/chunk-33FKQH54.js +5 -0
- package/dist-cli/chunks/chunk-37I5AIS7.js +3 -0
- package/dist-cli/chunks/chunk-3D57IBPC.js +4 -0
- package/dist-cli/chunks/chunk-4BXCZHAU.js +4 -0
- package/dist-cli/chunks/chunk-5OX7ANRA.js +5 -0
- package/dist-cli/chunks/chunk-66NF2KNG.js +9 -0
- package/dist-cli/chunks/chunk-6UKJB2VC.js +125 -0
- package/dist-cli/chunks/chunk-7INBNA3U.js +26 -0
- package/dist-cli/chunks/chunk-7VWOSTD5.js +5 -0
- package/dist-cli/chunks/chunk-AY47WKS5.js +23 -0
- package/dist-cli/chunks/chunk-B7KMNHX7.js +4 -0
- package/dist-cli/chunks/chunk-BKNOLP3J.js +4 -0
- package/dist-cli/chunks/chunk-BRIZOR4K.js +28 -0
- package/dist-cli/chunks/chunk-DM6KUAOZ.js +4 -0
- package/dist-cli/chunks/chunk-EUAVBTKU.js +4 -0
- package/dist-cli/chunks/chunk-FTC4M26Q.js +4 -0
- package/dist-cli/chunks/chunk-FWYYCUAZ.js +4 -0
- package/dist-cli/chunks/chunk-G74YIEVS.js +7 -0
- package/dist-cli/chunks/chunk-GZDHEONW.js +507 -0
- package/dist-cli/chunks/chunk-HG7ORJMT.js +5 -0
- package/dist-cli/chunks/chunk-HMDOLCKG.js +15 -0
- package/dist-cli/chunks/chunk-JG6S4RSH.js +4 -0
- package/dist-cli/chunks/chunk-KR5BGMBZ.js +4 -0
- package/dist-cli/chunks/chunk-KZW5WBGU.js +983 -0
- package/dist-cli/chunks/chunk-L24Z3CRS.js +30 -0
- package/dist-cli/chunks/chunk-LDQ7HVCW.js +4 -0
- package/dist-cli/chunks/chunk-MENV7VLD.js +4 -0
- package/dist-cli/chunks/chunk-MNGQ42QE.js +568 -0
- package/dist-cli/chunks/chunk-N6A3SXMR.js +4 -0
- package/dist-cli/chunks/chunk-NLEMQ4PY.js +6 -0
- package/dist-cli/chunks/chunk-NPVGMH2N.js +101 -0
- package/dist-cli/chunks/chunk-OR3XTXEB.js +107 -0
- package/dist-cli/chunks/chunk-PEPUGJVI.js +4 -0
- package/dist-cli/chunks/chunk-Q3RGLERJ.js +142 -0
- package/dist-cli/chunks/chunk-SI3BUTQR.js +4 -0
- package/dist-cli/chunks/chunk-T4FT6CM7.js +4 -0
- package/dist-cli/chunks/chunk-TBMVBI26.js +7 -0
- package/dist-cli/chunks/chunk-TCWQJILS.js +5 -0
- package/dist-cli/chunks/chunk-TGQNQ6XT.js +766 -0
- package/dist-cli/chunks/chunk-TYE3WBSR.js +5 -0
- package/dist-cli/chunks/chunk-U4PN6JOT.js +6 -0
- package/dist-cli/chunks/chunk-VUFRB347.js +4 -0
- package/dist-cli/chunks/chunk-VZBLFMYC.js +10 -0
- package/dist-cli/chunks/chunk-WBT32R6N.js +25 -0
- package/dist-cli/chunks/chunk-XRGEEPJ5.js +38 -0
- package/dist-cli/chunks/chunk-Y2FSZSO7.js +71 -0
- package/dist-cli/chunks/chunk-YRV7HR3P.js +74 -0
- package/dist-cli/chunks/chunk-YZRFF4YA.js +14 -0
- package/dist-cli/chunks/chunk-ZMZTHU7V.js +4 -0
- package/dist-cli/chunks/cleanup-H3GWQB63.js +25 -0
- package/dist-cli/chunks/cli-version-3QPAJ3LC.js +4 -0
- package/dist-cli/chunks/compat-5WG2XJV7.js +20 -0
- package/dist-cli/chunks/config-ANMBVVLL.js +13 -0
- package/dist-cli/chunks/control-NSGNQFJG.js +4 -0
- package/dist-cli/chunks/daemon-JHF4BJ3U.js +4 -0
- package/dist-cli/chunks/debug-CHV2O2KF.js +200 -0
- package/dist-cli/chunks/desktop-FIPIK4CH.js +31 -0
- package/dist-cli/chunks/detox-CVUJP7K6.js +52 -0
- package/dist-cli/chunks/device-6WCUM4W6.js +18 -0
- package/dist-cli/chunks/diagnose-I6EMWEII.js +43 -0
- package/dist-cli/chunks/disk-cleanup-P27CYFTN.js +4 -0
- package/dist-cli/chunks/drivers-HQ6AKZT6.js +4 -0
- package/dist-cli/chunks/film-UOQFFYNF.js +74 -0
- package/dist-cli/chunks/flow-N27MRRNL.js +4 -0
- package/dist-cli/chunks/help-SF6FEGEE.js +4 -0
- package/dist-cli/chunks/hidden-runtime-alias-NJYFSJKZ.js +4 -0
- package/dist-cli/chunks/home-paths-XOV44JEF.js +4 -0
- package/dist-cli/chunks/inspect-CWFRFQTY.js +4 -0
- package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +4 -0
- package/dist-cli/chunks/login-BPFQENCX.js +28 -0
- package/dist-cli/chunks/logout-5A45ZGY7.js +4 -0
- package/dist-cli/chunks/maestro-E4CSDMLM.js +104 -0
- package/dist-cli/chunks/maestro-generate-CKMOJQX2.js +36 -0
- package/dist-cli/chunks/mode-SZYNMIQF.js +26 -0
- package/dist-cli/chunks/optional-demo-registry-FRH4SFVG.js +4 -0
- package/dist-cli/chunks/perf-VXU2XJAX.js +48 -0
- package/dist-cli/chunks/permissions-IRIAOVND.js +13 -0
- package/dist-cli/chunks/record-RY7FPLLH.js +72 -0
- package/dist-cli/chunks/report-issue-KNWXOE53.js +29 -0
- package/dist-cli/chunks/runtime-AR3GJTGK.js +4 -0
- package/dist-cli/chunks/screenshot-command-QLALR6GY.js +159 -0
- package/dist-cli/chunks/screenshot-layers-OQFMFVRL.js +20 -0
- package/dist-cli/chunks/screenshots-capture-6TSRJFGB.js +42 -0
- package/dist-cli/chunks/serve-F54J2NQO.js +44 -0
- package/dist-cli/chunks/setup-7V6UKX4U.js +32 -0
- package/dist-cli/chunks/skills-QQBOSETH.js +50 -0
- package/dist-cli/chunks/storage-TAPMKK5O.js +37 -0
- package/dist-cli/chunks/store-7NX3S43H.js +4 -0
- package/dist-cli/chunks/telemetry-E7BUK6RA.js +4 -0
- package/dist-cli/chunks/timeline-IAM564NA.js +24 -0
- package/dist-cli/chunks/upgrade-CD55QXIX.js +7 -0
- package/dist-cli/chunks/upload-GZ6LIMYM.js +4 -0
- package/dist-cli/chunks/version-3DYJ5AS3.js +6 -0
- package/dist-cli/chunks/web-6NW6YLPI.js +4 -0
- package/dist-cli/chunks/what-happened-CZZDOOVG.js +25 -0
- package/dist-lib/agent-daemon-client.cjs +468 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +725 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +195 -0
- package/dist-lib/backend-origin.cjs +70 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +2863 -0
- package/dist-lib/detox/jest-preset.cjs +51 -0
- package/dist-lib/dev-bundle-resolution.cjs +223 -0
- package/dist-lib/home-paths.cjs +421 -0
- package/dist-lib/host/bridge-host.cjs +5600 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/websocket-proxy.cjs +214 -0
- package/dist-lib/index.cjs +513 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/menu.cjs +620 -0
- package/dist-lib/menu.mjs +581 -0
- package/dist-lib/metro.cjs +297 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
- package/dist-lib/sdk.cjs +2333 -0
- package/dist-lib/sdk.mjs +2240 -0
- package/dist-lib/skills.cjs +23911 -0
- package/dist-lib/vite.cjs +358 -0
- package/package.json +189 -18
- package/scripts/dev-server-scanner.ts +941 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/scripts/postinstall.cjs +3 -0
- package/scripts/preuninstall.cjs +118 -0
- package/skills/contrast/SKILL.md +171 -0
- package/skills/rnx-debug/SKILL.md +425 -0
- package/skills/rnx-perf/SKILL.md +295 -0
- package/skills/rnx-setup/SKILL.md +160 -0
- package/skills/rnx-test/SKILL.md +292 -0
- package/skills/rnx-visual/SKILL.md +122 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +573 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +204 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +171 -0
- package/src/browser-cache.ts +16 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +32 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +134 -0
- package/src/dev-bundle-resolution.ts +236 -0
- package/src/disk-cleanup.ts +466 -0
- package/src/home-paths.ts +552 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2626 -0
- package/src/host/fetch-proxy-handler.ts +397 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/websocket-proxy.ts +210 -0
- package/src/index.ts +12 -705
- package/src/jump-to-source-babel.ts +135 -0
- package/src/menu.ts +734 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +96 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +92 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +228 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +454 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +7 -0
- package/src/settings-bundle.ts +418 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +190 -0
- package/src/vite-plugin.ts +1750 -0
- package/src/worklets-babel.ts +134 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'node:fs'
|
|
9
|
+
import { dirname, join, resolve } from 'node:path'
|
|
10
|
+
import { electronUserDataDir } from '../home-paths'
|
|
11
|
+
|
|
12
|
+
export type SharedDesktopAuthTeamSummary = {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
role: 'owner' | 'member'
|
|
16
|
+
githubOrg: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type SharedDesktopAuthUser = {
|
|
20
|
+
id: string
|
|
21
|
+
name?: string
|
|
22
|
+
email?: string
|
|
23
|
+
image?: string
|
|
24
|
+
githubUsername?: string
|
|
25
|
+
teams?: SharedDesktopAuthTeamSummary[]
|
|
26
|
+
isAdmin?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SharedDesktopAuthSession = {
|
|
30
|
+
version: 1
|
|
31
|
+
token: string
|
|
32
|
+
user: SharedDesktopAuthUser | null
|
|
33
|
+
origin: string
|
|
34
|
+
source: 'cli' | 'electron' | 'browser' | 'unknown'
|
|
35
|
+
updatedAt: string
|
|
36
|
+
validatedAt?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SESSION_VERSION = 1 as const
|
|
40
|
+
const SESSION_FILE_ENV = 'SOOTSIM_SHARED_AUTH_FILE'
|
|
41
|
+
const DEFAULT_ORIGIN = 'https://contrast.dev'
|
|
42
|
+
|
|
43
|
+
function getBaseDir() {
|
|
44
|
+
const explicit = process.env[SESSION_FILE_ENV]
|
|
45
|
+
if (explicit?.trim()) {
|
|
46
|
+
return dirname(resolve(explicit))
|
|
47
|
+
}
|
|
48
|
+
return electronUserDataDir()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getSharedDesktopAuthFilePath() {
|
|
52
|
+
const explicit = process.env[SESSION_FILE_ENV]
|
|
53
|
+
if (explicit?.trim()) return resolve(explicit)
|
|
54
|
+
return join(getBaseDir(), 'desktop-auth.json')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeUser(input: unknown): SharedDesktopAuthUser | null {
|
|
58
|
+
if (!input || typeof input !== 'object') return null
|
|
59
|
+
const value = input as Record<string, unknown>
|
|
60
|
+
if (typeof value.id !== 'string' || !value.id.trim()) return null
|
|
61
|
+
return {
|
|
62
|
+
id: value.id.trim(),
|
|
63
|
+
name: typeof value.name === 'string' ? value.name : undefined,
|
|
64
|
+
email: typeof value.email === 'string' ? value.email : undefined,
|
|
65
|
+
image: typeof value.image === 'string' ? value.image : undefined,
|
|
66
|
+
githubUsername:
|
|
67
|
+
typeof value.githubUsername === 'string' ? value.githubUsername : undefined,
|
|
68
|
+
isAdmin: value.isAdmin === true,
|
|
69
|
+
teams: Array.isArray(value.teams)
|
|
70
|
+
? value.teams
|
|
71
|
+
.map((team): SharedDesktopAuthTeamSummary | null => {
|
|
72
|
+
if (!team || typeof team !== 'object') return null
|
|
73
|
+
const t = team as Record<string, unknown>
|
|
74
|
+
if (typeof t.id !== 'string' || typeof t.name !== 'string') return null
|
|
75
|
+
return {
|
|
76
|
+
id: t.id,
|
|
77
|
+
name: t.name,
|
|
78
|
+
role: t.role === 'owner' ? 'owner' : 'member',
|
|
79
|
+
githubOrg: typeof t.githubOrg === 'string' ? t.githubOrg : null,
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.filter((team): team is SharedDesktopAuthTeamSummary => !!team)
|
|
83
|
+
: undefined,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeSession(input: unknown): SharedDesktopAuthSession | null {
|
|
88
|
+
if (!input || typeof input !== 'object') return null
|
|
89
|
+
const value = input as Record<string, unknown>
|
|
90
|
+
if (value.version !== SESSION_VERSION) return null
|
|
91
|
+
if (typeof value.token !== 'string' || !value.token.trim()) return null
|
|
92
|
+
const origin =
|
|
93
|
+
typeof value.origin === 'string' && value.origin.trim()
|
|
94
|
+
? value.origin.trim()
|
|
95
|
+
: DEFAULT_ORIGIN
|
|
96
|
+
const source =
|
|
97
|
+
value.source === 'cli' ||
|
|
98
|
+
value.source === 'electron' ||
|
|
99
|
+
value.source === 'browser' ||
|
|
100
|
+
value.source === 'unknown'
|
|
101
|
+
? value.source
|
|
102
|
+
: 'unknown'
|
|
103
|
+
const updatedAt =
|
|
104
|
+
typeof value.updatedAt === 'string' && value.updatedAt
|
|
105
|
+
? value.updatedAt
|
|
106
|
+
: new Date().toISOString()
|
|
107
|
+
const validatedAt =
|
|
108
|
+
typeof value.validatedAt === 'string' && value.validatedAt
|
|
109
|
+
? value.validatedAt
|
|
110
|
+
: undefined
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
version: SESSION_VERSION,
|
|
114
|
+
token: value.token.trim(),
|
|
115
|
+
user: normalizeUser(value.user),
|
|
116
|
+
origin,
|
|
117
|
+
source,
|
|
118
|
+
updatedAt,
|
|
119
|
+
validatedAt,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function readSharedDesktopAuthSession(): SharedDesktopAuthSession | null {
|
|
124
|
+
const filepath = getSharedDesktopAuthFilePath()
|
|
125
|
+
if (!existsSync(filepath)) return null
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(readFileSync(filepath, 'utf8')) as unknown
|
|
129
|
+
const normalized = normalizeSession(parsed)
|
|
130
|
+
if (!normalized) {
|
|
131
|
+
rmSync(filepath, { force: true })
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
return normalized
|
|
135
|
+
} catch {
|
|
136
|
+
rmSync(filepath, { force: true })
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function writeSharedDesktopAuthSession(
|
|
142
|
+
session: Omit<SharedDesktopAuthSession, 'version' | 'updatedAt'> & {
|
|
143
|
+
updatedAt?: string
|
|
144
|
+
},
|
|
145
|
+
) {
|
|
146
|
+
const filepath = getSharedDesktopAuthFilePath()
|
|
147
|
+
mkdirSync(dirname(filepath), { recursive: true })
|
|
148
|
+
const normalized: SharedDesktopAuthSession = {
|
|
149
|
+
version: SESSION_VERSION,
|
|
150
|
+
token: session.token.trim(),
|
|
151
|
+
user: session.user ? normalizeUser(session.user) : null,
|
|
152
|
+
origin: session.origin?.trim() || DEFAULT_ORIGIN,
|
|
153
|
+
source: session.source,
|
|
154
|
+
updatedAt: session.updatedAt || new Date().toISOString(),
|
|
155
|
+
validatedAt: session.validatedAt,
|
|
156
|
+
}
|
|
157
|
+
writeFileSync(filepath, JSON.stringify(normalized, null, 2) + '\n')
|
|
158
|
+
try {
|
|
159
|
+
chmodSync(filepath, 0o600)
|
|
160
|
+
} catch {
|
|
161
|
+
// best-effort only
|
|
162
|
+
}
|
|
163
|
+
return normalized
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function clearSharedDesktopAuthSession() {
|
|
167
|
+
rmSync(getSharedDesktopAuthFilePath(), { force: true })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function refreshSharedDesktopAuthSession(
|
|
171
|
+
originOverride?: string,
|
|
172
|
+
): Promise<SharedDesktopAuthSession | null> {
|
|
173
|
+
const current = readSharedDesktopAuthSession()
|
|
174
|
+
if (!current?.token) return null
|
|
175
|
+
|
|
176
|
+
const origin = originOverride || current.origin || DEFAULT_ORIGIN
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const res = await fetch(`${origin.replace(/\/$/, '')}/api/auth/me`, {
|
|
180
|
+
headers: { authorization: `Bearer ${current.token}` },
|
|
181
|
+
})
|
|
182
|
+
if (res.status === 401) {
|
|
183
|
+
clearSharedDesktopAuthSession()
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
if (!res.ok) return current
|
|
187
|
+
|
|
188
|
+
const data = (await res.json()) as { user?: SharedDesktopAuthUser | null }
|
|
189
|
+
if (!data.user?.id) {
|
|
190
|
+
clearSharedDesktopAuthSession()
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return writeSharedDesktopAuthSession({
|
|
195
|
+
token: current.token,
|
|
196
|
+
user: data.user,
|
|
197
|
+
origin,
|
|
198
|
+
source: current.source,
|
|
199
|
+
validatedAt: new Date().toISOString(),
|
|
200
|
+
})
|
|
201
|
+
} catch {
|
|
202
|
+
return current
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const DEFAULT_CONTRAST_BACKEND_ORIGIN = 'https://contrast.dev'
|
|
2
|
+
|
|
3
|
+
function normalizeHostname(hostname: string): string {
|
|
4
|
+
return hostname.replace(/^\[|\]$/g, '').toLowerCase()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isLoopbackHost(hostname: string): boolean {
|
|
8
|
+
const normalized = normalizeHostname(hostname)
|
|
9
|
+
return (
|
|
10
|
+
normalized === 'localhost' ||
|
|
11
|
+
normalized.endsWith('.localhost') ||
|
|
12
|
+
normalized === '0.0.0.0' ||
|
|
13
|
+
normalized === '::1' ||
|
|
14
|
+
/^127(?:\.\d{1,3}){3}$/.test(normalized)
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isContrastBackendHost(hostname: string): boolean {
|
|
19
|
+
const normalized = normalizeHostname(hostname)
|
|
20
|
+
return (
|
|
21
|
+
isLoopbackHost(normalized) ||
|
|
22
|
+
normalized === 'contrast.dev' ||
|
|
23
|
+
normalized.endsWith('.contrast.dev')
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isContrastBackendOrigin(origin: string): boolean {
|
|
28
|
+
try {
|
|
29
|
+
return isContrastBackendHost(new URL(origin).hostname)
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveContrastBackendOrigin(currentOrigin?: string | null): string {
|
|
36
|
+
const origin = currentOrigin?.trim()
|
|
37
|
+
if (!origin) return DEFAULT_CONTRAST_BACKEND_ORIGIN
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(origin)
|
|
41
|
+
if (isLoopbackHost(parsed.hostname) || isContrastBackendHost(parsed.hostname)) {
|
|
42
|
+
return parsed.origin
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
|
|
46
|
+
return DEFAULT_CONTRAST_BACKEND_ORIGIN
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { DEFAULT_CONTRAST_BACKEND_ORIGIN }
|
package/src/beta.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// public beta flag + copy. single source of truth for the badge that
|
|
2
|
+
// appears on the CLI banner, electron title, shell rail pill, and the
|
|
3
|
+
// download page. flip IS_BETA to false when 1.0 ships and the badge
|
|
4
|
+
// disappears everywhere automatically.
|
|
5
|
+
//
|
|
6
|
+
// see plans/sootsim-public-beta.md for the rollout plan.
|
|
7
|
+
|
|
8
|
+
export const IS_BETA = true
|
|
9
|
+
|
|
10
|
+
// short label — used on the rail pill, electron title, etc.
|
|
11
|
+
export const BETA_LABEL = 'public beta'
|
|
12
|
+
|
|
13
|
+
// one-line tagline — used in the CLI banner.
|
|
14
|
+
export const BETA_TAGLINE = BETA_LABEL
|
|
15
|
+
|
|
16
|
+
// what we ask for in return — kept here so the onboarding flow, the
|
|
17
|
+
// privacy page, and any future surface render the same words.
|
|
18
|
+
export const BETA_ASK_HEADLINE =
|
|
19
|
+
'we send anonymous error and dependency data while you use it'
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// canonical bridge port for sootsim's WebSocket connection between the CLI
|
|
2
|
+
// and the running engine. matches main — kept in a single module so CLI,
|
|
3
|
+
// dev plugin, and runtime agree on the same value.
|
|
4
|
+
export const DEFAULT_SOOTSIM_BRIDGE_PORT = 7668
|
|
5
|
+
export const DEFAULT_SOOTSIM_BRIDGE_URL = `http://localhost:${DEFAULT_SOOTSIM_BRIDGE_PORT}/`
|
|
6
|
+
|
|
7
|
+
function parsePort(value: unknown): number | null {
|
|
8
|
+
if (typeof value === 'number') {
|
|
9
|
+
return Number.isInteger(value) && value > 0 ? value : null
|
|
10
|
+
}
|
|
11
|
+
if (typeof value !== 'string') return null
|
|
12
|
+
const trimmed = value.trim()
|
|
13
|
+
if (!trimmed) return null
|
|
14
|
+
const parsed = Number(trimmed)
|
|
15
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePortOffset(value: unknown): number {
|
|
19
|
+
const parsed = parsePort(value)
|
|
20
|
+
return parsed === null ? 0 : parsed
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveSootsimBridgePort(
|
|
24
|
+
input: {
|
|
25
|
+
explicitPort?: unknown
|
|
26
|
+
portOffset?: unknown
|
|
27
|
+
} = {},
|
|
28
|
+
): number {
|
|
29
|
+
const explicitPort = parsePort(input.explicitPort)
|
|
30
|
+
if (explicitPort !== null) return explicitPort
|
|
31
|
+
return DEFAULT_SOOTSIM_BRIDGE_PORT + parsePortOffset(input.portOffset)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveSootsimBridgePortForHttpPort(input: {
|
|
35
|
+
explicitPort?: unknown
|
|
36
|
+
httpPort?: unknown
|
|
37
|
+
baseHttpPort: number
|
|
38
|
+
}): number {
|
|
39
|
+
const explicitPort = parsePort(input.explicitPort)
|
|
40
|
+
if (explicitPort !== null) return explicitPort
|
|
41
|
+
const httpPort = parsePort(input.httpPort)
|
|
42
|
+
const portOffset = httpPort === null ? 0 : Math.max(0, httpPort - input.baseHttpPort)
|
|
43
|
+
return resolveSootsimBridgePort({ portOffset })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const CONTRAST_DEV_HTTP_BASE_PORT = 3000
|
|
47
|
+
const SOOTSIM_SITE_DEV_HTTP_BASE_PORT = 3001
|
|
48
|
+
const SOOTSIM_SHELL_DEV_HTTP_BASE_PORT = 5173
|
|
49
|
+
const LOCAL_DEV_HTTP_BASE_PORTS = [
|
|
50
|
+
CONTRAST_DEV_HTTP_BASE_PORT,
|
|
51
|
+
SOOTSIM_SHELL_DEV_HTTP_BASE_PORT,
|
|
52
|
+
]
|
|
53
|
+
const MAX_LOCAL_DEV_PORT_OFFSET = 2000
|
|
54
|
+
|
|
55
|
+
function parseHrefUrl(value: unknown): URL | null {
|
|
56
|
+
if (typeof value !== 'string' || !value.trim()) return null
|
|
57
|
+
try {
|
|
58
|
+
return new URL(value)
|
|
59
|
+
} catch {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function closestLocalDevBaseHttpPort(httpPort: number): number | null {
|
|
65
|
+
let closest: { basePort: number; offset: number } | null = null
|
|
66
|
+
for (const basePort of LOCAL_DEV_HTTP_BASE_PORTS) {
|
|
67
|
+
const offset = httpPort - basePort
|
|
68
|
+
if (offset < 0 || offset > MAX_LOCAL_DEV_PORT_OFFSET) continue
|
|
69
|
+
if (!closest || offset < closest.offset) closest = { basePort, offset }
|
|
70
|
+
}
|
|
71
|
+
return closest?.basePort ?? null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** the vite dev shell that shares a world with a bridge port. the two are one
|
|
75
|
+
* offset apart by construction (bridge 7668 + N, shell 5173 + N), so a caller
|
|
76
|
+
* that pinned an explicit bridge port can name its own shell directly. that
|
|
77
|
+
* matters because the dev-bridge lockfile records only the stack that
|
|
78
|
+
* heartbeated last: on a machine running several worktree stacks it routinely
|
|
79
|
+
* names someone else's shell, and a page opened there while we poll our own
|
|
80
|
+
* bridge never connects. null when the port is not a local dev bridge port. */
|
|
81
|
+
export function resolveSootsimShellBaseUrlForBridgePort(
|
|
82
|
+
bridgePort: number,
|
|
83
|
+
): string | null {
|
|
84
|
+
const offset = bridgePort - DEFAULT_SOOTSIM_BRIDGE_PORT
|
|
85
|
+
if (offset < 0 || offset > MAX_LOCAL_DEV_PORT_OFFSET) return null
|
|
86
|
+
return `http://localhost:${SOOTSIM_SHELL_DEV_HTTP_BASE_PORT + offset}/`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveSootsimBridgePortForRuntimeLocation(
|
|
90
|
+
input: {
|
|
91
|
+
explicitPort?: unknown
|
|
92
|
+
href?: unknown
|
|
93
|
+
httpPort?: unknown
|
|
94
|
+
} = {},
|
|
95
|
+
): number {
|
|
96
|
+
const explicitPort = parsePort(input.explicitPort)
|
|
97
|
+
if (explicitPort !== null) return explicitPort
|
|
98
|
+
const hrefUrl = parseHrefUrl(input.href)
|
|
99
|
+
const httpPort = parsePort(input.httpPort) ?? parsePort(hrefUrl?.port)
|
|
100
|
+
if (httpPort === null) return DEFAULT_SOOTSIM_BRIDGE_PORT
|
|
101
|
+
if (
|
|
102
|
+
hrefUrl?.pathname.startsWith('/sootsim') &&
|
|
103
|
+
(hrefUrl.searchParams.has('embed') || hrefUrl.searchParams.has('nativePreview'))
|
|
104
|
+
) {
|
|
105
|
+
return resolveSootsimBridgePortForHttpPort({
|
|
106
|
+
httpPort,
|
|
107
|
+
baseHttpPort: CONTRAST_DEV_HTTP_BASE_PORT,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
hrefUrl?.pathname.startsWith('/sootsim') &&
|
|
112
|
+
httpPort >= SOOTSIM_SITE_DEV_HTTP_BASE_PORT &&
|
|
113
|
+
httpPort <= SOOTSIM_SITE_DEV_HTTP_BASE_PORT + MAX_LOCAL_DEV_PORT_OFFSET
|
|
114
|
+
) {
|
|
115
|
+
return resolveSootsimBridgePortForHttpPort({
|
|
116
|
+
httpPort,
|
|
117
|
+
baseHttpPort: SOOTSIM_SITE_DEV_HTTP_BASE_PORT,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
const baseHttpPort = closestLocalDevBaseHttpPort(httpPort)
|
|
121
|
+
if (baseHttpPort === null) return DEFAULT_SOOTSIM_BRIDGE_PORT
|
|
122
|
+
return resolveSootsimBridgePortForHttpPort({ httpPort, baseHttpPort })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function resolveSootsimWebSocketProxyUrl(input: {
|
|
126
|
+
targetUrl: string
|
|
127
|
+
href: string
|
|
128
|
+
}): string {
|
|
129
|
+
const pageUrl = parseHrefUrl(input.href)
|
|
130
|
+
if (!pageUrl || (pageUrl.protocol !== 'http:' && pageUrl.protocol !== 'https:')) {
|
|
131
|
+
throw new Error(`invalid rnx websocket proxy page URL: ${input.href}`)
|
|
132
|
+
}
|
|
133
|
+
let targetUrl: URL
|
|
134
|
+
try {
|
|
135
|
+
targetUrl = new URL(input.targetUrl)
|
|
136
|
+
} catch {
|
|
137
|
+
throw new Error(`invalid rnx websocket proxy target URL: ${input.targetUrl}`)
|
|
138
|
+
}
|
|
139
|
+
if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
|
|
140
|
+
throw new Error(`invalid rnx websocket proxy target URL: ${input.targetUrl}`)
|
|
141
|
+
}
|
|
142
|
+
const proxyUrl = new URL('/__websocket-proxy', pageUrl)
|
|
143
|
+
proxyUrl.protocol = pageUrl.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
144
|
+
proxyUrl.searchParams.set('url', input.targetUrl)
|
|
145
|
+
return proxyUrl.href
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveSootsimBridgeWebSocketUrl(input: {
|
|
149
|
+
bridgePort: number
|
|
150
|
+
href: string
|
|
151
|
+
}): string {
|
|
152
|
+
return resolveSootsimWebSocketProxyUrl({
|
|
153
|
+
targetUrl: `ws://localhost:${input.bridgePort}`,
|
|
154
|
+
href: input.href,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function isSootsimBridgeHealth(value: unknown, expectedPort: number): boolean {
|
|
159
|
+
return (
|
|
160
|
+
typeof value === 'object' &&
|
|
161
|
+
value !== null &&
|
|
162
|
+
Reflect.get(value, 'ok') === true &&
|
|
163
|
+
Reflect.get(value, 'bridgePort') === expectedPort
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// private websocket close code used when the daemon intentionally closes a
|
|
168
|
+
// sim. browser clients treat it as terminal and skip their normal reconnect
|
|
169
|
+
// loop; playwright-owned sims use it to close their hosting context.
|
|
170
|
+
export const SOOTSIM_BRIDGE_SIM_CLOSE_CODE = 4001
|
|
171
|
+
export const SOOTSIM_BRIDGE_SIM_CLOSE_REASON = 'sootsim close'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DISPOSABLE_BROWSER_CACHE_DIR_NAMES = [
|
|
2
|
+
'Cache',
|
|
3
|
+
'Code Cache',
|
|
4
|
+
'DawnCache',
|
|
5
|
+
'DawnGraphiteCache',
|
|
6
|
+
'DawnWebGPUCache',
|
|
7
|
+
'GPUCache',
|
|
8
|
+
'GrShaderCache',
|
|
9
|
+
'ShaderCache',
|
|
10
|
+
] as const
|
|
11
|
+
|
|
12
|
+
const disposableBrowserCacheDirs = new Set<string>(DISPOSABLE_BROWSER_CACHE_DIR_NAMES)
|
|
13
|
+
|
|
14
|
+
export function isDisposableBrowserCacheDir(name: string): boolean {
|
|
15
|
+
return disposableBrowserCacheDirs.has(name)
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_SOOTSIM_SHELL_URL = 'http://localhost:5173/'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// sootsim CLI version — the `sootsim` npm package version. resolved once
|
|
2
|
+
// and cached. shared by `--version`, `--help`, the `upgrade` command, the
|
|
3
|
+
// startup flow, and the bridge-host (which injects it into the served
|
|
4
|
+
// runtime html as window.__sootsimCliVersion).
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { rnxPublicBrand } from './public-brand.ts'
|
|
9
|
+
|
|
10
|
+
let cached: string | null = null
|
|
11
|
+
|
|
12
|
+
export function getCliVersion(): string {
|
|
13
|
+
if (cached != null) return cached
|
|
14
|
+
// prefer package resolution (works for npm/global installs); fall back to
|
|
15
|
+
// the package.json next to this module's source for repo / bundled runs.
|
|
16
|
+
const candidates: Array<() => string> = [
|
|
17
|
+
() =>
|
|
18
|
+
fileURLToPath(import.meta.resolve(`${rnxPublicBrand.packageName}/package.json`)),
|
|
19
|
+
() => fileURLToPath(new URL('../package.json', import.meta.url)),
|
|
20
|
+
]
|
|
21
|
+
for (const resolve of candidates) {
|
|
22
|
+
try {
|
|
23
|
+
const version = JSON.parse(readFileSync(resolve(), 'utf8')).version
|
|
24
|
+
if (typeof version === 'string' && version) {
|
|
25
|
+
cached = version
|
|
26
|
+
return cached
|
|
27
|
+
}
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
cached = '0.0.0'
|
|
31
|
+
return cached
|
|
32
|
+
}
|