@fugood/bricks-project 2.25.0-beta.37 → 2.25.0-beta.39
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/package.json +2 -2
- package/package.json.bak +2 -2
- package/skills/bricks-ctor/SKILL.md +20 -18
- package/skills/bricks-ctor/references/simulator.md +98 -0
- package/skills/bricks-ctor/{rules → references}/verification-toolchain.md +9 -7
- package/skills/bricks-design/SKILL.md +1 -1
- package/tools/mcp-tools/compile.ts +2 -2
- package/tools/{preview-main.mjs → simulator-main.mjs} +121 -5
- package/tools/{preview.ts → simulator.ts} +4 -4
- /package/skills/bricks-ctor/{rules → references}/animation.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/architecture-patterns.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/automations.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/buttress.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/data-calculation.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/local-sync.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/media-flow.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/remote-data-bank.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/standby-transition.md +0 -0
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-project",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.39",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
10
|
+
"@fugood/bricks-cli": "^2.25.0-beta.38",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
package/package.json.bak
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-ctor",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.39",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
10
|
+
"@fugood/bricks-cli": "^2.25.0-beta.38",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -11,24 +11,26 @@ This skill covers advanced BRICKS features not in the main project instructions.
|
|
|
11
11
|
|
|
12
12
|
| Rule | Description |
|
|
13
13
|
|------|-------------|
|
|
14
|
-
| [Architecture Patterns](
|
|
15
|
-
| [Animation](
|
|
16
|
-
| [Standby Transition](
|
|
17
|
-
| [Automations](
|
|
18
|
-
| [Data Calculation](
|
|
19
|
-
| [Local Sync](
|
|
20
|
-
| [Remote Data Bank](
|
|
21
|
-
| [Media Flow](
|
|
22
|
-
| [Buttress](
|
|
23
|
-
| [Verification Toolchain](
|
|
14
|
+
| [Architecture Patterns](references/architecture-patterns.md) | **Read first** — decompose flows and select patterns |
|
|
15
|
+
| [Animation](references/animation.md) | Animation system for brick transforms and opacity |
|
|
16
|
+
| [Standby Transition](references/standby-transition.md) | Canvas enter/exit animations |
|
|
17
|
+
| [Automations](references/automations.md) | E2E testing and scheduled tasks |
|
|
18
|
+
| [Data Calculation](references/data-calculation.md) | JS sandbox libraries (25+ available) |
|
|
19
|
+
| [Local Sync](references/local-sync.md) | LAN device synchronization |
|
|
20
|
+
| [Remote Data Bank](references/remote-data-bank.md) | Cloud data sync and API access |
|
|
21
|
+
| [Media Flow](references/media-flow.md) | Media asset management |
|
|
22
|
+
| [Buttress](references/buttress.md) | Remote inference for AI generators |
|
|
23
|
+
| [Verification Toolchain](references/verification-toolchain.md) | Definition of done, compile, preview tool selection, on-device DevTools, Path 1/2/3 decision rule |
|
|
24
|
+
| [Simulator](references/simulator.md) | Path 1 fidelity — Simulator feature support, fallbacks (Camera/LLM/STT/Vector Store, Buttress disabled), when a green run is enough vs. Path 2 |
|
|
24
25
|
|
|
25
26
|
## Quick Reference
|
|
26
27
|
|
|
27
|
-
- **Complex flows**: See [Architecture Patterns](
|
|
28
|
-
- **Multi-device**: See [Local Sync](
|
|
29
|
-
- **Cloud data**: See [Remote Data Bank](
|
|
30
|
-
- **Media assets**: See [Media Flow](
|
|
31
|
-
- **AI offloading**: See [Buttress](
|
|
32
|
-
- **E2E testing**: See [Automations](
|
|
33
|
-
- **Enter animations**: See [Standby Transition](
|
|
34
|
-
- **Verification before done**: See [Verification Toolchain](
|
|
28
|
+
- **Complex flows**: See [Architecture Patterns](references/architecture-patterns.md) for decomposing multi-step workflows
|
|
29
|
+
- **Multi-device**: See [Local Sync](references/local-sync.md) for LAN coordination
|
|
30
|
+
- **Cloud data**: See [Remote Data Bank](references/remote-data-bank.md) for sync and API access
|
|
31
|
+
- **Media assets**: See [Media Flow](references/media-flow.md) for centralized asset management
|
|
32
|
+
- **AI offloading**: See [Buttress](references/buttress.md) for GPU server delegation
|
|
33
|
+
- **E2E testing**: See [Automations](references/automations.md) for test automation
|
|
34
|
+
- **Enter animations**: See [Standby Transition](references/standby-transition.md) for canvas transitions
|
|
35
|
+
- **Verification before done**: See [Verification Toolchain](references/verification-toolchain.md) for the definition-of-done gate and Path 1/2/3 decision rule
|
|
36
|
+
- **Simulator fidelity**: See [Simulator](references/simulator.md) for what the Simulator fakes (Camera, AI generators, Buttress) and when to escalate to a real device
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Simulator
|
|
2
|
+
|
|
3
|
+
What the Simulator can and can't faithfully reproduce — so a passing Simulator run means what you think it means. This is the fidelity companion to Path 1 in [Verification Toolchain](verification-toolchain.md); read that for how to *drive* the Simulator (compile, screenshot, Automations).
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
- The default Path 1 target: it renders your compiled project on your computer, with no device required.
|
|
8
|
+
- It runs in Chromium/Electron — a browser-class runtime, not the device runtime. Two consequences to design verification around: it has a browser's capabilities and limits (below), and its JavaScript engine differs from the device's (iOS runs JSC, Android runs Hermes), so engine-specific behavior won't surface here.
|
|
9
|
+
|
|
10
|
+
A Simulator screenshot proves **layout, navigation, Standby Transitions, Data wiring, and generator/Automation wiring**. It does *not* prove anything that depends on real hardware, real models, or capabilities the preview runtime lacks.
|
|
11
|
+
|
|
12
|
+
## What the Simulator can't reproduce
|
|
13
|
+
|
|
14
|
+
Because it runs in a browser-class preview rather than on device hardware, the Simulator cannot exercise these — a green run says nothing about them. Verify on a real device (Path 2):
|
|
15
|
+
|
|
16
|
+
| Area | In the Simulator |
|
|
17
|
+
|------|------------------|
|
|
18
|
+
| BLE (Central / Peripheral), Video Streaming | Not available |
|
|
19
|
+
| Raw network sockets — UDP, TCP, TCP Server, MQTT Broker | Not available (also why Buttress LAN discovery is a no-op — see [Buttress](buttress.md)) |
|
|
20
|
+
| Android Intent | Not available (Android-only) |
|
|
21
|
+
| On-device database **persistence** (SQLite / Vector Store) | Not available — both run in-memory in the Simulator (see caveats below), so data never survives a reload |
|
|
22
|
+
| GGML Text-to-Speech | No vocoder in the preview — use an ONNX TTS model to hear speech |
|
|
23
|
+
|
|
24
|
+
These work, but with browser caveats:
|
|
25
|
+
|
|
26
|
+
| Area | Caveat |
|
|
27
|
+
|------|--------|
|
|
28
|
+
| Serial Port | Works via WebSerial — requires browser support and a user gesture |
|
|
29
|
+
| WebView / WebCrawler | Subject to browser CORS — a load/fetch that works on device may be blocked |
|
|
30
|
+
| On-device AI (LLM / STT / VAD / Vector Store / Reranker) | Runs **single-threaded** — far slower than the device, not representative of real latency. Also subject to the model fallbacks below |
|
|
31
|
+
| On-device database (SQLite — `GENERATOR_SQLITE`) | Runs for real on the in-memory WASM `sqlite-vec` build — `execute` / `query` / `transaction` / batch all work. `storageType: file` is transparently treated as in-memory, so nothing persists across reloads (see above) |
|
|
32
|
+
| Scene3D / Maps / Sketch / WebRTC | Supported |
|
|
33
|
+
|
|
34
|
+
Feature availability also varies across the device platforms themselves (iOS / tvOS / Android / the desktop OSes). When a deployment targets a specific platform's capability, confirm it on that platform.
|
|
35
|
+
|
|
36
|
+
## Network security
|
|
37
|
+
|
|
38
|
+
The Simulator blocks insecure connections from `applicationPreview` by default: `http://` and `ws://` requests fail, while `https://` and `wss://` are allowed. The Simulator internally allows its own preview host when running the development preview.
|
|
39
|
+
|
|
40
|
+
Generated projects include this project-level setting:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"allowedInsecureHosts": []
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Add explicit entries only when you deliberately need to test an insecure endpoint in the Simulator. A bare `host:port` allows both `http://` and `ws://` for that host:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"allowedInsecureHosts": ["localhost:8080"]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use a URL form when only one insecure protocol should be allowed:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"allowedInsecureHosts": ["http://localhost:8080"]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
In that example, `http://localhost:8080` is allowed and `ws://localhost:8080` remains blocked.
|
|
65
|
+
|
|
66
|
+
Prefer HTTPS/WSS for anything that is not local development.
|
|
67
|
+
|
|
68
|
+
## Feature fallbacks — what the Simulator fakes
|
|
69
|
+
|
|
70
|
+
So that camera and AI features are usable without device permissions, multi-gigabyte downloads, API keys, or a remote inference backend, the Simulator **transparently substitutes a lightweight stand-in** for a few bricks/generators. The config you author is untouched — only the Simulator's runtime behavior differs. **A fallback render proves wiring and layout, not the real feature.**
|
|
71
|
+
|
|
72
|
+
| Brick / Generator | In the Simulator | Does NOT prove |
|
|
73
|
+
|-------------------|------------------|----------------|
|
|
74
|
+
| Camera (`BRICK_CAMERA`) | A 3D mock canvas, no camera permission prompt. `takePicture` snapshots the canvas; recording produces a placeholder clip | Real camera feed, focus, recording, permission flow |
|
|
75
|
+
| Thermal Printer (`GENERATOR_THERMAL_PRINTER`) | A simulated printer — `init` / `checkStatus` / `scan` fake per-driver status and discovered devices (ESC/POS, Star, TSC, Castles); `print` renders an approximate on-screen receipt. A bottom-left bubble shows live status with a fault toggle to exercise error wiring | Real device connection, actual paper output, exact native driver status codes |
|
|
76
|
+
| LLM (`GENERATOR_LLM`) | Swapped to a tiny local stand-in model | Output quality / latency of your real model |
|
|
77
|
+
| Reranker — GGML (`GENERATOR_RERANKER`) | Swapped to a small local multilingual reranker model | Ranking quality / latency of your real model |
|
|
78
|
+
| Speech-to-Text — GGML (`GENERATOR_SPEECH_INFERENCE`) | Swapped to a tiny local model | Accuracy / latency of your real model |
|
|
79
|
+
| Speech-to-Text — ONNX (`GENERATOR_ONNX_STT`) | Swapped to a tiny whisper model | Accuracy / latency of your real model |
|
|
80
|
+
| Text-to-Speech — ONNX (`GENERATOR_TTS`) | Swapped to a tiny VITS model (no vocoder or speaker embedding) | Voice / quality / latency of your real model |
|
|
81
|
+
| Vector Store (`GENERATOR_VECTOR_STORE`) | Runs with a tiny local embedding model — an OpenAI-source config works **without a key** | Real embeddings / dimensions / recall (and no persistence — see above) |
|
|
82
|
+
| Buttress (all generators) | **Disabled** — there is no remote inference backend in the Simulator | The Buttress offload path — see [Buttress](buttress.md) |
|
|
83
|
+
| MLX LLM (`GENERATOR_MLX_LLM`) | Runs as authored, except Buttress is disabled | The MLX / Buttress path |
|
|
84
|
+
|
|
85
|
+
These swaps make the AI generators runnable in the Simulator for wiring checks — validate real models, quality, and latency on a device (Path 2).
|
|
86
|
+
|
|
87
|
+
### Running the real implementation instead
|
|
88
|
+
|
|
89
|
+
Each substituted brick/generator can be switched back to its real implementation per item: open the **gear (Simulator settings)** in the editor's preview toolbar, uncheck the item, and **Apply**. Apply persists the choice and reloads the preview so it takes effect (a plain refresh won't). Use this to, e.g., point a Vector Store at a real API key in the preview. The browser limits above still apply, and **Buttress stays disabled regardless** — there's no backend for it here.
|
|
90
|
+
|
|
91
|
+
The Thermal Printer is the exception: it has no real web implementation to switch to (the native drivers can't run in a browser), so it is **always simulated** and is not in the gear list.
|
|
92
|
+
|
|
93
|
+
## Enough vs. escalate
|
|
94
|
+
|
|
95
|
+
- **Simulator is enough for:** layout, navigation, Standby Transitions, Data wiring, Automation / state-machine happy paths, and confirming a generator fires and handles its events.
|
|
96
|
+
- **Escalate to a real device for:** anything in the "can't reproduce" tables, real camera / peripherals / payment / identity, real model quality & latency, multi-device Local Sync, on-device persistence, the Buttress offload path, and engine-specific behavior.
|
|
97
|
+
|
|
98
|
+
The full Path 1/2/3 decision rule lives in [Verification Toolchain](verification-toolchain.md).
|
|
@@ -26,10 +26,12 @@ What does **not** count as done:
|
|
|
26
26
|
|
|
27
27
|
If any item is unmet, the work is mid-iteration. Say so explicitly to the user; offer a precise list of what remains.
|
|
28
28
|
|
|
29
|
-
## Path 1 —
|
|
29
|
+
## Path 1 — Simulator (no device required)
|
|
30
30
|
|
|
31
31
|
The default loop. Always available; deterministic; no device wear; safe for side-effecting flows because nothing real fires.
|
|
32
32
|
|
|
33
|
+
Before trusting a Simulator screenshot, know what it can and can't reproduce: it runs in the Electron preview (a browser-class runtime, not the device — and not the device's JS engine), can't exercise hardware/peripheral features (BLE, printers, raw sockets, SQLite, …), and substitutes fallbacks for Camera and the AI generators (LLM/STT/Vector Store) with Buttress disabled. See [Simulator](simulator.md) for the fidelity boundaries and the per-item gear to run the real implementation — it's what tells you when a green run is enough vs. when to escalate to Path 2.
|
|
34
|
+
|
|
33
35
|
`bun update-app` and `bun deploy-app` publish to the BRICKS portal. The local preview reads `.bricks/build/application-config.json` (produced by `compile`) directly.
|
|
34
36
|
|
|
35
37
|
### Compile tool
|
|
@@ -38,12 +40,12 @@ Typecheck + compile the project. Gate every iteration on this; everything below
|
|
|
38
40
|
|
|
39
41
|
Agent invocation: call the MCP tool `compile` exposed by the `bricks-ctor` MCP server registered for the project. No arguments.
|
|
40
42
|
|
|
41
|
-
###
|
|
43
|
+
### Simulator tool
|
|
42
44
|
|
|
43
45
|
Use the preview implementation exposed by the current harness:
|
|
44
46
|
|
|
45
|
-
- **CTOR Desktop agent session:** use `
|
|
46
|
-
- **Pure `bricks-ctor` project / other agent harness:** use the `bricks-ctor` MCP `
|
|
47
|
+
- **CTOR Desktop agent session:** use `simulator_invoke`. CTOR Desktop disables the `bricks-ctor` MCP `simulator` tool and routes screenshots/automation through the desktop preview pane instead. If the user already opened the simulator pane, `simulator_invoke` should reuse it rather than start a separate preview.
|
|
48
|
+
- **Pure `bricks-ctor` project / other agent harness:** use the `bricks-ctor` MCP `simulator` tool when `simulator_invoke` is not available.
|
|
47
49
|
|
|
48
50
|
Both forms are the same verification primitive: launch or reuse Electron preview, take a screenshot, and optionally run a named Automation test by id or partial title. Do not hard-code one tool name into the workflow; choose the available preview tool for the environment.
|
|
49
51
|
|
|
@@ -55,7 +57,7 @@ Common arguments:
|
|
|
55
57
|
|
|
56
58
|
Returns text log + saved screenshot path + image content when supported.
|
|
57
59
|
|
|
58
|
-
### `bun
|
|
60
|
+
### `bun invoke-simulator` (project script)
|
|
59
61
|
|
|
60
62
|
Sustained dev session — watches `subspaces/`, recompiles on save, exposes CDP at `localhost:19852` (configurable via `--cdp-port`), writes `.bricks/devtools.json` with port/pid for downstream tooling.
|
|
61
63
|
|
|
@@ -83,7 +85,7 @@ Trigger types: `launch` (runs on app start), `anytime` (manual), `cron` (schedul
|
|
|
83
85
|
|
|
84
86
|
Important: the automation map id must be `'AUTOMATION_MAP_DEFAULT'` (not a generated id) — the preview test runner reads from `automationMap['AUTOMATION_MAP_DEFAULT']?.map`.
|
|
85
87
|
|
|
86
|
-
Run a single test from the agent: call the available preview tool (`
|
|
88
|
+
Run a single test from the agent: call the available preview tool (`simulator_invoke` in CTOR Desktop, otherwise `bricks-ctor` MCP `preview`) with `testId` or `testTitleLike`.
|
|
87
89
|
|
|
88
90
|
## Path 2 — Real device with DevTools enabled
|
|
89
91
|
|
|
@@ -147,7 +149,7 @@ if user is off-LAN
|
|
|
147
149
|
|
|
148
150
|
### Static signage (single-canvas glance loop)
|
|
149
151
|
- Path 1 screenshot captures the loop frame.
|
|
150
|
-
- Watch one full rotation in `bun
|
|
152
|
+
- Watch one full rotation in `bun invoke-simulator` to see all queue items.
|
|
151
153
|
- Cut network mid-loop; confirm cached media plays.
|
|
152
154
|
- Path 2 only if the panel is OLED / has burn-in concerns or specific color profile.
|
|
153
155
|
|
|
@@ -142,7 +142,7 @@ A single hero-Canvas screenshot is **not** done. A "looks roughly like the refer
|
|
|
142
142
|
|
|
143
143
|
**Self-critique pass before declaring done** — every Canvas scored on 5 dimensions (system commitment / visual hierarchy / craft / functional fit / originality), anti-slop top-10 swept clean, < 8 scores either fixed or surfaced as accepted trade-offs. See [`references/design-critique.md`](references/design-critique.md). Verification proves it runs; critique proves it's good — both required.
|
|
144
144
|
|
|
145
|
-
For the toolchain itself — `compile` / `
|
|
145
|
+
For the toolchain itself — `compile` / `simulator` MCP usage, `bun invoke-simulator` flags, Project Automation cases, on-device DevTools setup, the Path 1/2/3 decision rule, and per-deployment-shape verification checklists — see the `bricks-ctor` skill's `rules/verification-toolchain.md`.
|
|
146
146
|
|
|
147
147
|
## Boundaries
|
|
148
148
|
|
|
@@ -27,7 +27,7 @@ export function register(server: McpServer, projectDir: string) {
|
|
|
27
27
|
if (process.env.BRICKS_CTOR_MCP_DISABLE_PREVIEW === '1') return
|
|
28
28
|
|
|
29
29
|
server.tool(
|
|
30
|
-
'
|
|
30
|
+
'simulator',
|
|
31
31
|
{
|
|
32
32
|
delay: z
|
|
33
33
|
.number()
|
|
@@ -68,7 +68,7 @@ export function register(server: McpServer, projectDir: string) {
|
|
|
68
68
|
]
|
|
69
69
|
if (testId) args.push('--test-id', testId)
|
|
70
70
|
if (testTitleLike) args.push('--test-title-like', testTitleLike)
|
|
71
|
-
log = await sh`bunx --bun electron ${toolsDir}/
|
|
71
|
+
log = await sh`bunx --bun electron ${toolsDir}/simulator-main.mjs ${args}`
|
|
72
72
|
.cwd(projectDir)
|
|
73
73
|
.env(noColorEnv)
|
|
74
74
|
.text()
|
|
@@ -28,6 +28,81 @@ const { values } = parseArgs({
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
const cwd = process.cwd()
|
|
31
|
+
const DEFAULT_SIMULATOR_SECURITY_SETTINGS = Object.freeze({
|
|
32
|
+
allowedInsecureHosts: [],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value)
|
|
36
|
+
|
|
37
|
+
const normalizeSimulatorSecuritySettings = (settings = {}) => {
|
|
38
|
+
const source = isObject(settings) ? settings : {}
|
|
39
|
+
const allowedHosts = Array.isArray(source.allowedInsecureHosts)
|
|
40
|
+
? source.allowedInsecureHosts
|
|
41
|
+
: DEFAULT_SIMULATOR_SECURITY_SETTINGS.allowedInsecureHosts
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
allowedInsecureHosts: allowedHosts
|
|
45
|
+
.filter((host) => typeof host === 'string')
|
|
46
|
+
.map((host) => host.trim().toLowerCase())
|
|
47
|
+
.filter(Boolean),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const normalizeAllowedConnection = (value) => {
|
|
52
|
+
const raw = String(value || '')
|
|
53
|
+
.trim()
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
if (!raw) return null
|
|
56
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) return { host: raw, protocol: null }
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(raw)
|
|
59
|
+
return {
|
|
60
|
+
host: url.host.toLowerCase(),
|
|
61
|
+
protocol: url.protocol,
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const shouldBlockInsecureConnection = (rawUrl, settings = {}) => {
|
|
69
|
+
const normalized = normalizeSimulatorSecuritySettings(settings)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const url = new URL(String(rawUrl || ''))
|
|
73
|
+
if (url.protocol !== 'http:' && url.protocol !== 'ws:') return false
|
|
74
|
+
const host = url.host.toLowerCase()
|
|
75
|
+
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
76
|
+
return !normalized.allowedInsecureHosts.some((allowedHost) => {
|
|
77
|
+
const allowed = normalizeAllowedConnection(allowedHost)
|
|
78
|
+
if (!allowed) return false
|
|
79
|
+
if (allowed.protocol && allowed.protocol !== url.protocol) return false
|
|
80
|
+
return allowed.host === host || allowed.host === hostname
|
|
81
|
+
})
|
|
82
|
+
} catch {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const getUnsecureConnectionHost = (rawUrl) => {
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(String(rawUrl || ''))
|
|
90
|
+
if (url.protocol !== 'http:' && url.protocol !== 'ws:') return null
|
|
91
|
+
return url.host.toLowerCase()
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const readSimulatorProjectSettings = async () => {
|
|
98
|
+
try {
|
|
99
|
+
return normalizeSimulatorSecuritySettings(
|
|
100
|
+
JSON.parse(await readFile(`${cwd}/simulator.json`, 'utf8')),
|
|
101
|
+
)
|
|
102
|
+
} catch {
|
|
103
|
+
return normalizeSimulatorSecuritySettings()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
31
106
|
|
|
32
107
|
let takeScreenshotConfig = null
|
|
33
108
|
try {
|
|
@@ -45,6 +120,7 @@ try {
|
|
|
45
120
|
}
|
|
46
121
|
|
|
47
122
|
let config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
|
|
123
|
+
let simulatorSettings = await readSimulatorProjectSettings()
|
|
48
124
|
|
|
49
125
|
// Resolve testId from testTitleLike
|
|
50
126
|
let testId = values['test-id'] || null
|
|
@@ -77,6 +153,13 @@ const previewUrlMap = {
|
|
|
77
153
|
|
|
78
154
|
const previewUrl = previewUrlMap[stage]
|
|
79
155
|
if (!previewUrl) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
|
|
156
|
+
simulatorSettings = normalizeSimulatorSecuritySettings({
|
|
157
|
+
...simulatorSettings,
|
|
158
|
+
allowedInsecureHosts: [
|
|
159
|
+
...simulatorSettings.allowedInsecureHosts,
|
|
160
|
+
getUnsecureConnectionHost(previewUrl),
|
|
161
|
+
].filter(Boolean),
|
|
162
|
+
})
|
|
80
163
|
|
|
81
164
|
// --- CDP WebSocket Server ---
|
|
82
165
|
// Bridges external CDP clients to the preview's postMessage-based CDP bridge.
|
|
@@ -142,9 +225,9 @@ const startCdpServer = async (mainWindow, port) => {
|
|
|
142
225
|
res.end(
|
|
143
226
|
JSON.stringify([
|
|
144
227
|
{
|
|
145
|
-
description: 'BRICKS
|
|
146
|
-
id: 'bricks-
|
|
147
|
-
title: 'BRICKS
|
|
228
|
+
description: 'BRICKS Simulator',
|
|
229
|
+
id: 'bricks-simulator',
|
|
230
|
+
title: 'BRICKS Simulator',
|
|
148
231
|
type: 'page',
|
|
149
232
|
url: previewUrl,
|
|
150
233
|
webSocketDebuggerUrl: `ws://localhost:${actualPort}/ws`,
|
|
@@ -155,7 +238,7 @@ const startCdpServer = async (mainWindow, port) => {
|
|
|
155
238
|
}
|
|
156
239
|
if (req.url === '/json/version') {
|
|
157
240
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
158
|
-
res.end(JSON.stringify({ Browser: 'BRICKS
|
|
241
|
+
res.end(JSON.stringify({ Browser: 'BRICKS Simulator', 'Protocol-Version': '1.3' }))
|
|
159
242
|
return
|
|
160
243
|
}
|
|
161
244
|
// bricks-cli discovery endpoint
|
|
@@ -163,7 +246,7 @@ const startCdpServer = async (mainWindow, port) => {
|
|
|
163
246
|
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
164
247
|
res.end(
|
|
165
248
|
JSON.stringify({
|
|
166
|
-
name: 'BRICKS
|
|
249
|
+
name: 'BRICKS Simulator',
|
|
167
250
|
port: actualPort,
|
|
168
251
|
protocols: ['cdp'],
|
|
169
252
|
hasPasscode: false,
|
|
@@ -215,6 +298,12 @@ app.on('ready', () => {
|
|
|
215
298
|
show,
|
|
216
299
|
})
|
|
217
300
|
mainWindow.setBackgroundColor('#333')
|
|
301
|
+
mainWindow.webContents.session.webRequest.onBeforeRequest(
|
|
302
|
+
{ urls: ['http://*/*', 'ws://*/*'] },
|
|
303
|
+
(details, callback) => {
|
|
304
|
+
callback({ cancel: shouldBlockInsecureConnection(details.url, simulatorSettings) })
|
|
305
|
+
},
|
|
306
|
+
)
|
|
218
307
|
mainWindow.loadURL(previewUrl)
|
|
219
308
|
|
|
220
309
|
const sendConfig = () => {
|
|
@@ -300,7 +389,34 @@ app.on('ready', () => {
|
|
|
300
389
|
async () => {
|
|
301
390
|
console.log('Detected config changed')
|
|
302
391
|
config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
|
|
392
|
+
const nextSimulatorSettings = await readSimulatorProjectSettings()
|
|
393
|
+
simulatorSettings = normalizeSimulatorSecuritySettings({
|
|
394
|
+
...nextSimulatorSettings,
|
|
395
|
+
allowedInsecureHosts: [
|
|
396
|
+
...nextSimulatorSettings.allowedInsecureHosts,
|
|
397
|
+
getUnsecureConnectionHost(previewUrl),
|
|
398
|
+
].filter(Boolean),
|
|
399
|
+
})
|
|
303
400
|
sendConfig()
|
|
304
401
|
},
|
|
305
402
|
)
|
|
403
|
+
watchFile(
|
|
404
|
+
`${cwd}/simulator.json`,
|
|
405
|
+
{
|
|
406
|
+
bigint: false,
|
|
407
|
+
persistent: true,
|
|
408
|
+
interval: 1000,
|
|
409
|
+
},
|
|
410
|
+
async () => {
|
|
411
|
+
console.log('Detected simulator settings changed')
|
|
412
|
+
const nextSimulatorSettings = await readSimulatorProjectSettings()
|
|
413
|
+
simulatorSettings = normalizeSimulatorSecuritySettings({
|
|
414
|
+
...nextSimulatorSettings,
|
|
415
|
+
allowedInsecureHosts: [
|
|
416
|
+
...nextSimulatorSettings.allowedInsecureHosts,
|
|
417
|
+
getUnsecureConnectionHost(previewUrl),
|
|
418
|
+
].filter(Boolean),
|
|
419
|
+
})
|
|
420
|
+
},
|
|
421
|
+
)
|
|
306
422
|
})
|
|
@@ -98,7 +98,7 @@ const cleanupDevtoolsInfo = () => {
|
|
|
98
98
|
} catch {}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
// Kill existing
|
|
101
|
+
// Kill existing simulator process if devtools.json contains a stale pid
|
|
102
102
|
try {
|
|
103
103
|
const devtoolsInfo = JSON.parse(await readFile(devtoolsInfoPath, 'utf8'))
|
|
104
104
|
if (devtoolsInfo.pid) {
|
|
@@ -111,7 +111,7 @@ try {
|
|
|
111
111
|
|
|
112
112
|
const proc = spawn(
|
|
113
113
|
'bunx',
|
|
114
|
-
['--bun', 'electron', `${import.meta.dirname}/
|
|
114
|
+
['--bun', 'electron', `${import.meta.dirname}/simulator-main.mjs`, ...args],
|
|
115
115
|
{
|
|
116
116
|
env: { ...process.env, BRICKS_STAGE: app.stage || 'production' },
|
|
117
117
|
stdio: ['inherit', 'pipe', 'inherit'],
|
|
@@ -127,14 +127,14 @@ rl.on('line', (line) => {
|
|
|
127
127
|
return
|
|
128
128
|
}
|
|
129
129
|
if (!line) return
|
|
130
|
-
// Detect CDP server startup from
|
|
130
|
+
// Detect CDP server startup from simulator-main output
|
|
131
131
|
const cdpMatch = line.match(/^CDP server: ws:\/\/localhost:(\d+)/)
|
|
132
132
|
if (cdpMatch) {
|
|
133
133
|
const info = {
|
|
134
134
|
port: parseInt(cdpMatch[1], 10),
|
|
135
135
|
pid: proc.pid,
|
|
136
136
|
address: 'localhost',
|
|
137
|
-
name: `${app.name || 'Unnamed'} (CTOR
|
|
137
|
+
name: `${app.name || 'Unnamed'} (CTOR Simulator)`,
|
|
138
138
|
startedAt: new Date().toISOString(),
|
|
139
139
|
}
|
|
140
140
|
writeFile(devtoolsInfoPath, JSON.stringify(info, null, 2))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|