@ait-co/devtools 0.1.108 → 0.1.110
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/README.en.md +13 -31
- package/README.md +13 -31
- package/dist/bundle-KFs4t-wc.d.ts +96 -0
- package/dist/bundle-KFs4t-wc.d.ts.map +1 -0
- package/dist/in-app/auto.d.ts.map +1 -1
- package/dist/in-app/auto.js +40 -3
- package/dist/in-app/auto.js.map +1 -1
- package/dist/in-app/index.d.ts.map +1 -1
- package/dist/in-app/index.js +39 -2
- package/dist/in-app/index.js.map +1 -1
- package/dist/mcp/cli.d.ts +4 -16
- package/dist/mcp/cli.d.ts.map +1 -1
- package/dist/mcp/cli.js +803 -712
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +47 -59
- package/dist/mcp/server.js.map +1 -1
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +21 -2
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +47 -32
- package/dist/panel/index.js.map +1 -1
- package/dist/{pool-Dkp7I9Bf.d.ts → pool-Bf6rQci4.d.ts} +210 -48
- package/dist/pool-Bf6rQci4.d.ts.map +1 -0
- package/dist/{qr-http-server-D4EAA7Il.js → qr-http-server-BJJt3ush.js} +8 -17
- package/dist/qr-http-server-BJJt3ush.js.map +1 -0
- package/dist/{qr-http-server-A9vld8r7.cjs → qr-http-server-BVS-HZjU.cjs} +8 -17
- package/dist/qr-http-server-BVS-HZjU.cjs.map +1 -0
- package/dist/{qr-http-server-Dj3Z0NHi.cjs → qr-http-server-C1T4RNbq.cjs} +8 -17
- package/dist/qr-http-server-C1T4RNbq.cjs.map +1 -0
- package/dist/{qr-http-server-HzdCLU8s.js → qr-http-server-Cs93vEPH.js} +8 -17
- package/dist/qr-http-server-Cs93vEPH.js.map +1 -0
- package/dist/{relay-worker-BzFQ3fv9.d.ts → relay-worker-xxanNQGs.d.ts} +3 -3
- package/dist/relay-worker-xxanNQGs.d.ts.map +1 -0
- package/dist/{runtime-ORdrpizY.d.ts → runtime-Wi5d6Ywz.d.ts} +3 -3
- package/dist/{runtime-ORdrpizY.d.ts.map → runtime-Wi5d6Ywz.d.ts.map} +1 -1
- package/dist/test-runner/bundle.d.ts +1 -1
- package/dist/test-runner/bundle.js +148 -11
- package/dist/test-runner/bundle.js.map +1 -1
- package/dist/test-runner/cli.d.ts +59 -14
- package/dist/test-runner/cli.d.ts.map +1 -1
- package/dist/test-runner/cli.js +171 -32
- package/dist/test-runner/cli.js.map +1 -1
- package/dist/test-runner/config.d.ts +1 -1
- package/dist/test-runner/pool.d.ts +1 -1
- package/dist/test-runner/relay-worker.d.ts +1 -1
- package/dist/test-runner/relay-worker.js.map +1 -1
- package/dist/test-runner/rpc.d.ts +1 -1
- package/dist/test-runner/rpc.d.ts.map +1 -1
- package/dist/test-runner/rpc.js +1 -1
- package/dist/test-runner/rpc.js.map +1 -1
- package/dist/test-runner/task-graph.d.ts +1 -1
- package/dist/{tunnel-BjJROkcj.js → tunnel-Cpn3mA4u.js} +3 -3
- package/dist/tunnel-Cpn3mA4u.js.map +1 -0
- package/dist/{tunnel-d_G9AIFn.cjs → tunnel-Dj8Kf2QS.cjs} +3 -3
- package/dist/tunnel-Dj8Kf2QS.cjs.map +1 -0
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.d.cts +196 -34
- package/dist/unplugin/index.d.cts.map +1 -1
- package/dist/unplugin/index.d.ts +196 -34
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/tunnel.cjs +2 -2
- package/dist/unplugin/tunnel.cjs.map +1 -1
- package/dist/unplugin/tunnel.d.cts +1 -1
- package/dist/unplugin/tunnel.d.ts +1 -1
- package/dist/unplugin/tunnel.js +2 -2
- package/dist/unplugin/tunnel.js.map +1 -1
- package/package.json +14 -14
- package/dist/bundle-BJm5jk56.d.ts +0 -49
- package/dist/bundle-BJm5jk56.d.ts.map +0 -1
- package/dist/pool-Dkp7I9Bf.d.ts.map +0 -1
- package/dist/qr-http-server-A9vld8r7.cjs.map +0 -1
- package/dist/qr-http-server-D4EAA7Il.js.map +0 -1
- package/dist/qr-http-server-Dj3Z0NHi.cjs.map +0 -1
- package/dist/qr-http-server-HzdCLU8s.js.map +0 -1
- package/dist/relay-worker-BzFQ3fv9.d.ts.map +0 -1
- package/dist/tunnel-BjJROkcj.js.map +0 -1
- package/dist/tunnel-d_G9AIFn.cjs.map +0 -1
package/README.en.md
CHANGED
|
@@ -60,31 +60,16 @@ Load a dog-food bundle in the real Toss app WebView and debug it via the MCP rel
|
|
|
60
60
|
```bash
|
|
61
61
|
devtools-mcp # start MCP server → QR printed in terminal
|
|
62
62
|
# ait build && ait deploy --scheme-only
|
|
63
|
-
# call
|
|
63
|
+
# one start_attach(scheme_url) call generates the QR and waits for the phone to attach — scan it and the Toss app loads the bundle + relay attaches
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
No HMR (Toss WebView cold-load only). Details: [`docs/scenarios/env-3.md`](./docs/scenarios/env-3.md)
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
**Environment 4 — Live deployed app** (passed review, HMR off, read-only debug)
|
|
71
|
-
|
|
72
|
-
Attach a relay to a live OPENED app to observe runtime behavior.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
devtools-mcp # start MCP server
|
|
76
|
-
# In Claude Code: start_debug({mode: 'relay-live', confirm: true}) ← arms LIVE guard
|
|
77
|
-
# call build_attach_url → scan QR → live app loads + relay attaches
|
|
78
|
-
# call_sdk / evaluate: confirm: true required (LIVE guard — real users affected)
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
`start_debug({mode: 'relay-live', confirm: true})` arms the LIVE guard in-session. Details: [`docs/scenarios/env-4.md`](./docs/scenarios/env-4.md)
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
70
|
## On-device debugging in one line
|
|
86
71
|
|
|
87
|
-
To enable on-device CDP debugging in environments 2
|
|
72
|
+
To enable on-device CDP debugging in environments 2 and 3, add **one line** to your mini-app entry (`main.tsx` or equivalent):
|
|
88
73
|
|
|
89
74
|
```ts
|
|
90
75
|
// main.tsx (or the top of your mini-app entry)
|
|
@@ -106,11 +91,11 @@ For environments 3 and 4 (intoss-private relay), the relay QR deep-link carries
|
|
|
106
91
|
|
|
107
92
|
**"QR window doesn't open"**
|
|
108
93
|
|
|
109
|
-
Either `
|
|
94
|
+
Either `start_attach` wasn't called first, or the MCP server is running in a headless environment where no browser can be opened. The tool result always includes a text QR — scan it directly with your phone camera. On a local GUI machine, the dashboard opens automatically in the browser.
|
|
110
95
|
|
|
111
96
|
**"Page not attached" — list_pages returns an empty array**
|
|
112
97
|
|
|
113
|
-
No page has joined the relay yet. Re-enter via `
|
|
98
|
+
No page has joined the relay yet. Re-enter via `start_attach` → QR scan on your phone. When the MCP error message reads "page not attached — run start_attach then scan QR", this is the case.
|
|
114
99
|
|
|
115
100
|
**"Tunnel down" — no response or timeout**
|
|
116
101
|
|
|
@@ -118,7 +103,7 @@ A cloudflared quick tunnel can drop after a few hours. Restart the `devtools-mcp
|
|
|
118
103
|
|
|
119
104
|
**"Page crash" — list_pages shows a non-null crashDetectedAt**
|
|
120
105
|
|
|
121
|
-
The page on the phone died (OOM, JS exception, or native bridge crash). Relaunch the app, then re-attach via `
|
|
106
|
+
The page on the phone died (OOM, JS exception, or native bridge crash). Relaunch the app, then re-attach via `start_attach` → QR scan. (Related: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
|
|
122
107
|
|
|
123
108
|
**"SDK not available" — window.__sdkCall not injected**
|
|
124
109
|
|
|
@@ -126,7 +111,7 @@ When `call_sdk` returns `ok: false, error: "window.__sdkCall is not available"`,
|
|
|
126
111
|
|
|
127
112
|
**"QR scanned but auth rejected" — TOTP code expired**
|
|
128
113
|
|
|
129
|
-
When `AIT_DEBUG_TOTP_SECRET` is set, `
|
|
114
|
+
When `AIT_DEBUG_TOTP_SECRET` is set, `start_attach` automatically splices the current one-time TOTP code (`at=`) into the returned `attachUrl`. Each code covers a 30-second step, and the relay accepts ±6 steps (~3 min) of backwards skew. While waiting for the phone to attach, `start_attach` re-mints the code in-call as it nears its expiry window (the re-mint count is surfaced as `totp.reminted`), so you usually do not need to re-call during the wait. If you do scan an expired QR and the relay rejects it, call `start_attach` again to get a fresh URL and QR.
|
|
130
115
|
|
|
131
116
|
---
|
|
132
117
|
|
|
@@ -979,15 +964,14 @@ A local browser (env 1) and a phone Toss WebView (env 2/3) both speak CDP, so ev
|
|
|
979
964
|
|---|---|---|---|---|
|
|
980
965
|
| `--target=mobile` (env 2) | `devtools-mcp` → `start_debug({mode:'relay-sandbox'})` | `AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL` | Real-device Safari/WebKit PWA (external Chii relay + cloudflared tunnel, env 2) | console/network/page + DOM/snapshot/screenshot |
|
|
981
966
|
| `--mode=debug --target=relay` (default, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | Dog-food bundle on a phone (CDP/Chii relay + cloudflared tunnel, env 3) | same + `AIT.*` |
|
|
982
|
-
| `--mode=debug --target=relay` LIVE (env 4) | `devtools-mcp` → `start_debug({mode: 'relay-live', confirm: true})` | — (env 4 LIVE guard) | Live deployed app (env 4) — `call_sdk`/`evaluate` require `confirm: true` | same |
|
|
983
967
|
| `--mode=debug --target=local` (env 1) | `devtools-mcp --target=local` | `MCP_ENV=mock` (auto) | Local Chromium launched by the MCP server (CDP direct-attach, no relay needed, env 1) | same |
|
|
984
968
|
| `--mode=dev` | `devtools-mcp --mode=dev` | `MCP_ENV=mock` (auto) | Mock state from a running Vite dev server (AIT.* only, no CDP) | `AIT.*` (+ `devtools_get_mock_state` alias) |
|
|
985
969
|
|
|
986
|
-
`--target=local` opens `AIT_DEVTOOLS_URL` (default `http://localhost:5173`) and attaches directly to a local Chromium — no relay or tunnel required. `--mode=dev` reads the mock-state HTTP endpoint of the Vite dev server and does not provide CDP tools. Switch environments in-session with `start_debug(mode)`: `relay-sandbox` (env 2 PWA), `relay-staging` (env 3 dogfood), `
|
|
970
|
+
`--target=local` opens `AIT_DEVTOOLS_URL` (default `http://localhost:5173`) and attaches directly to a local Chromium — no relay or tunnel required. `--mode=dev` reads the mock-state HTTP endpoint of the Vite dev server and does not provide CDP tools. Switch environments in-session with `start_debug(mode)`: `relay-sandbox` (env 2 PWA), `relay-staging` (env 3 dogfood), `local-browser` (env 1).
|
|
987
971
|
|
|
988
972
|
#### Environment 2 (real-device PWA CDP) — `--target=mobile`
|
|
989
973
|
|
|
990
|
-
Debug on a real phone using Safari/WebKit without Toss review. The Vite dev server with [`tunnel:{cdp:true}`](#tunnel-option) brings up both an app HTTP tunnel and a Chii relay tunnel. The MCP server attaches to that relay and provides `
|
|
974
|
+
Debug on a real phone using Safari/WebKit without Toss review. The Vite dev server with [`tunnel:{cdp:true}`](#tunnel-option) brings up both an app HTTP tunnel and a Chii relay tunnel. The MCP server attaches to that relay and provides `start_attach` → launcher QR.
|
|
991
975
|
|
|
992
976
|
**Setup procedure:**
|
|
993
977
|
|
|
@@ -1018,7 +1002,7 @@ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev serv
|
|
|
1018
1002
|
3. In a Claude Code session:
|
|
1019
1003
|
```
|
|
1020
1004
|
start_debug({mode: 'relay-sandbox'})
|
|
1021
|
-
|
|
1005
|
+
start_attach()
|
|
1022
1006
|
```
|
|
1023
1007
|
Scan the QR with your phone camera. The launcher PWA opens the app in a frame and injects Chii target.js.
|
|
1024
1008
|
|
|
@@ -1032,7 +1016,7 @@ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev serv
|
|
|
1032
1016
|
|
|
1033
1017
|
For a step-by-step walkthrough of the on-device relay debug loop (dog-food build → QR scan → relay attach) including common failure recovery, see **[`docs/dogfood-relay-loop.md`](./docs/dogfood-relay-loop.md)** (Korean). For crash triage — `list_pages.crashDetectedAt`, iOS Console.app `.ips` analysis, and the redact procedure — see **[`docs/crash-triage.md`](./docs/crash-triage.md)** (Korean).
|
|
1034
1018
|
|
|
1035
|
-
Read-only tools only. Tools are registered in two tiers based on attach state — before attach, only the bootstrap tools (`
|
|
1019
|
+
Read-only tools only. Tools are registered in two tiers based on attach state — before attach, only the bootstrap tools (`start_attach`, `list_pages`) are visible; once a relay/local page attaches, the attach-dependent tools are registered dynamically in the same session via `notifications/tools/list_changed` (no session restart needed). The phone attach roundtrip is fully wired; all that remains is a single on-device acceptance run. The tool layer is CI-verified via a mockable injectable CDP connection / AIT source.
|
|
1036
1020
|
|
|
1037
1021
|
Running `devtools-mcp` as a stdio server starts a local Chii relay on an OS-assigned port and opens a cloudflared quick tunnel, printing a public `wss://*.trycloudflare.com` URL and a QR code in the terminal (secrets/auth codes are never printed). When the phone enters the dog-food entry point, the in-app attach UI connects to the relay with that URL, and the agent reads console/network/page state via `chrome-devtools-mcp`-compatible tools — diagnosing regressions without anyone watching the phone.
|
|
1038
1022
|
|
|
@@ -1050,22 +1034,20 @@ Environments 3 and 4 (intoss-private relay) — start `devtools-mcp` as-is, then
|
|
|
1050
1034
|
```
|
|
1051
1035
|
|
|
1052
1036
|
- Environment 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
**`start_debug(mode)` is the single in-session entry path.** `MCP_ENV=relay-live` remains only as a deprecated alias that seeds `liveIntent` at boot — in a new session, enter via `start_debug({mode: 'relay-live', confirm: true})`.
|
|
1037
|
+
**`start_debug(mode)` is the single in-session entry path.**
|
|
1056
1038
|
|
|
1057
1039
|
| Tool | CDP / AIT backing | Description |
|
|
1058
1040
|
|---|---|---|
|
|
1059
1041
|
| `list_console_messages` | `Runtime.consoleAPICalled` | Recent console.log/warn/error messages (level, text, timestamp, args) |
|
|
1060
1042
|
| `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | Recent XHR/fetch requests (url, method, status, timing) |
|
|
1061
1043
|
| `list_pages` | Chii relay target list | Attached pages + tunnel status + wss URL |
|
|
1062
|
-
| `
|
|
1044
|
+
| `start_attach` | (pure synthesis + attach wait) | Splices `debug=1` + the relay URL into an `ait deploy --scheme-only` URL, prints a QR, then waits in the same call until the phone attaches (QR generation and attach in one call). A `mode` arg can switch the session environment too, and the TOTP code is re-minted automatically during the wait. Entry tool for env 2 and 3 (bootstrap) — no `list_pages` needed first |
|
|
1063
1045
|
| `get_dom_document` | `DOM.getDocument` | DOM tree read (structural/layout regression diagnosis) |
|
|
1064
1046
|
| `take_snapshot` | `DOMSnapshot.captureSnapshot` | Page snapshot (documents + interned strings, visual regression) |
|
|
1065
1047
|
| `take_screenshot` | `Page.captureScreenshot` | Page PNG screenshot (returned as an MCP image content block) |
|
|
1066
1048
|
| `measure_safe_area` | `Runtime.evaluate` | Runs a safe-area probe on the attached page → returns normalized safe-area insets, viewport geometry, DPR, and User-Agent. Read-only. Use in a relay session to get ground-truth values for upgrading a viewport preset from extrapolated/placeholder to measured. Requires attach (`list_pages` first) |
|
|
1067
1049
|
| `evaluate` | `Runtime.evaluate` | Evaluates an arbitrary JS expression on the attached page (returnByValue) and returns the result. **Not read-only** — the expression can have side effects (DOM mutations, SDK calls, state changes). Requires attach |
|
|
1068
|
-
| `call_sdk` | `window.__sdkCall` bridge (via `Runtime.evaluate`) | Calls a dog-food SDK method via the `window.__sdkCall` bridge (exported by `@apps-in-toss/web-framework` in `__DEBUG_BUILD__` bundles only). **Not read-only** — SDK calls have side effects (navigation, payments, permissions, etc.). Hits the real SDK on env 3
|
|
1050
|
+
| `call_sdk` | `window.__sdkCall` bridge (via `Runtime.evaluate`) | Calls a dog-food SDK method via the `window.__sdkCall` bridge (exported by `@apps-in-toss/web-framework` in `__DEBUG_BUILD__` bundles only). **Not read-only** — SDK calls have side effects (navigation, payments, permissions, etc.). Hits the real SDK on env 3, mock SDK on env 1. Env 2 (PWA) does not inject the SDK — not available there. Requires attach. Returns `{ok,value}` / `{ok,error}` |
|
|
1069
1051
|
| `AIT.getSdkCallHistory` | AIT domain | SDK call trace (method, args, result/error, timestamp) |
|
|
1070
1052
|
| `AIT.getMockState` | AIT domain | Mock state snapshot (`window.__ait`) |
|
|
1071
1053
|
| `AIT.getOperationalEnvironment` | AIT domain | `getOperationalEnvironment()` + SDK version |
|
package/README.md
CHANGED
|
@@ -60,31 +60,16 @@ pnpm dev:phone # AIT_TUNNEL=1 pnpm dev 와 동일
|
|
|
60
60
|
```bash
|
|
61
61
|
devtools-mcp # MCP 서버 시작 → QR 출력
|
|
62
62
|
# ait build && ait deploy --scheme-only
|
|
63
|
-
#
|
|
63
|
+
# start_attach(scheme_url) 호출 한 번으로 QR 생성 + 폰 attach까지 — QR 스캔하면 토스 앱 로드 + relay attach
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
HMR 없음(토스 WebView cold-load만). 상세: [`docs/scenarios/env-3.md`](./docs/scenarios/env-3.md)
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
**환경 4 — 배포된 앱 (LIVE)** (검수 통과 앱, HMR X, read-only debug)
|
|
71
|
-
|
|
72
|
-
검수를 통과하고 OPENED 상태인 실 출시 앱에 relay를 붙여 런타임을 관측합니다.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
devtools-mcp # MCP 서버 시작
|
|
76
|
-
# Claude Code에서: start_debug({mode: 'relay-live', confirm: true}) ← LIVE guard 활성화
|
|
77
|
-
# build_attach_url 호출 → QR 스캔 → LIVE 앱 로드 + relay attach
|
|
78
|
-
# call_sdk / evaluate 는 confirm: true 필수 (LIVE guard — 실유저 영향)
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
`start_debug({mode: 'relay-live', confirm: true})`가 LIVE guard를 활성화합니다. 상세: [`docs/scenarios/env-4.md`](./docs/scenarios/env-4.md)
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
70
|
## on-device 디버깅 한 줄 설정
|
|
86
71
|
|
|
87
|
-
환경 2·3
|
|
72
|
+
환경 2·3에서 on-device CDP 디버깅을 활성화하려면 미니앱 entry(`main.tsx` 등)에 **한 줄**을 추가하세요:
|
|
88
73
|
|
|
89
74
|
```ts
|
|
90
75
|
// main.tsx (또는 미니앱 entry 최상단)
|
|
@@ -106,11 +91,11 @@ import '@ait-co/devtools/in-app/auto';
|
|
|
106
91
|
|
|
107
92
|
**"QR 창이 안 열림"**
|
|
108
93
|
|
|
109
|
-
`
|
|
94
|
+
`start_attach`을 먼저 호출하지 않았거나, GUI 없는 headless 환경이라 대시보드를 열 수 없는 경우입니다. 도구 결과에 텍스트 QR이 출력되므로 폰 카메라로 직접 스캔하세요. 로컬 GUI 환경에서는 대시보드가 자동으로 브라우저에 열립니다.
|
|
110
95
|
|
|
111
96
|
**"page 미attach" — list_pages가 빈 배열 반환**
|
|
112
97
|
|
|
113
|
-
relay에 붙은 페이지가 없는 상태입니다. `
|
|
98
|
+
relay에 붙은 페이지가 없는 상태입니다. `start_attach` → QR 스캔 순서로 폰을 다시 진입시키세요. MCP 에러 메시지가 "페이지가 attach 안 됨. start_attach → QR 스캔."으로 뜨면 이 케이스입니다.
|
|
114
99
|
|
|
115
100
|
**"tunnel down" — 터널 응답 없음 또는 timeout**
|
|
116
101
|
|
|
@@ -118,7 +103,7 @@ cloudflared quick tunnel은 수 시간 후 drop될 수 있습니다. `devtools-m
|
|
|
118
103
|
|
|
119
104
|
**"page crash" — list_pages에 crashDetectedAt이 찍힘**
|
|
120
105
|
|
|
121
|
-
폰 측 페이지가 OOM·JS exception·native bridge crash로 죽은 상태입니다. 앱을 재실행 후 `
|
|
106
|
+
폰 측 페이지가 OOM·JS exception·native bridge crash로 죽은 상태입니다. 앱을 재실행 후 `start_attach` → QR 스캔으로 다시 attach하세요. (관련: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
|
|
122
107
|
|
|
123
108
|
**"SDK 부재" — window.__sdkCall 미주입**
|
|
124
109
|
|
|
@@ -126,7 +111,7 @@ cloudflared quick tunnel은 수 시간 후 drop될 수 있습니다. `devtools-m
|
|
|
126
111
|
|
|
127
112
|
**"QR 스캔했는데 인증 실패" — TOTP 만료**
|
|
128
113
|
|
|
129
|
-
`AIT_DEBUG_TOTP_SECRET` 설정 시 `
|
|
114
|
+
`AIT_DEBUG_TOTP_SECRET` 설정 시 `start_attach`이 반환하는 attachUrl에는 TOTP 코드(`at=`)가 자동으로 포함됩니다. 코드 1개는 30초 창이고, relay는 만료 후 ~3분(±6 step) 이내 소급을 허용합니다. `start_attach`은 attach를 기다리는 동안 코드가 만료 창에 가까워지면 호출 안에서 코드를 자동 재발행하므로(재발행 횟수는 응답의 `totp.reminted`로 노출), 대기 중에는 보통 재호출이 필요 없습니다. 그래도 만료된 QR을 스캔해 relay가 인증을 거부하면 `start_attach`을 재호출해 새 URL과 QR을 발급받으세요.
|
|
130
115
|
|
|
131
116
|
---
|
|
132
117
|
|
|
@@ -1009,15 +994,14 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
|
|
|
1009
994
|
|---|---|---|---|---|
|
|
1010
995
|
| `--target=mobile` (env 2) | `devtools-mcp` → `start_debug({mode:'relay-sandbox'})` | `AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL` | 실기기 Safari/WebKit PWA (외부 Chii relay + cloudflared 터널, 환경 2) | console/network/page + DOM/snapshot/screenshot |
|
|
1011
996
|
| `--mode=debug --target=relay` (기본값, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | 폰 안 dog-food 번들 (CDP/Chii relay + cloudflared 터널, 환경 3) | 동일 + `AIT.*` |
|
|
1012
|
-
| `--mode=debug --target=relay` LIVE (env 4) | `devtools-mcp` → `start_debug({mode: 'relay-live', confirm: true})` | — (**환경 4 LIVE guard**) | LIVE 배포 앱 (환경 4) — `call_sdk`/`evaluate`에 `confirm: true` 필요 | 동일 |
|
|
1013
997
|
| `--mode=debug --target=local` (env 1) | `devtools-mcp --target=local` | `MCP_ENV=mock` (자동) | MCP가 직접 기동한 로컬 Chromium (CDP direct-attach, relay 불필요, 환경 1) | 동일 |
|
|
1014
998
|
| `--mode=dev` | `devtools-mcp --mode=dev` | `MCP_ENV=mock` (자동) | 실행 중인 Vite dev server의 mock state (AIT.* 전용, CDP 없음) | `AIT.*` (+ `devtools_get_mock_state` alias) |
|
|
1015
999
|
|
|
1016
|
-
`--target=local`은 `AIT_DEVTOOLS_URL`(기본 `http://localhost:5173`)을 열고 로컬 Chromium에 CDP direct-attach합니다 — relay나 터널이 필요하지 않습니다. `--mode=dev`는 Vite dev server의 mock-state HTTP endpoint를 읽으며 CDP tool은 제공하지 않습니다. 세션 내 환경 전환은 `start_debug(mode)` 한 번으로 처리됩니다: `relay-sandbox`(env 2 PWA), `relay-staging`(env 3 dogfood), `
|
|
1000
|
+
`--target=local`은 `AIT_DEVTOOLS_URL`(기본 `http://localhost:5173`)을 열고 로컬 Chromium에 CDP direct-attach합니다 — relay나 터널이 필요하지 않습니다. `--mode=dev`는 Vite dev server의 mock-state HTTP endpoint를 읽으며 CDP tool은 제공하지 않습니다. 세션 내 환경 전환은 `start_debug(mode)` 한 번으로 처리됩니다: `relay-sandbox`(env 2 PWA), `relay-staging`(env 3 dogfood), `local-browser`(env 1).
|
|
1017
1001
|
|
|
1018
1002
|
#### 환경 2 (실기기 PWA CDP) — `--target=mobile`
|
|
1019
1003
|
|
|
1020
|
-
토스 검수 없이 실기기 WebKit 엔진에서 CDP 디버깅이 가능한 모드입니다. [`tunnel:{cdp:true}`](#tunnel-옵션)를 켠 Vite dev server가 앱 HTTP 터널과 Chii relay 터널을 두 개 띄우고, MCP는 그 relay에 붙어 `
|
|
1004
|
+
토스 검수 없이 실기기 WebKit 엔진에서 CDP 디버깅이 가능한 모드입니다. [`tunnel:{cdp:true}`](#tunnel-옵션)를 켠 Vite dev server가 앱 HTTP 터널과 Chii relay 터널을 두 개 띄우고, MCP는 그 relay에 붙어 `start_attach` → launcher QR을 제공합니다.
|
|
1021
1005
|
|
|
1022
1006
|
**진입 절차:**
|
|
1023
1007
|
|
|
@@ -1048,7 +1032,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
|
|
|
1048
1032
|
3. Claude Code 세션에서 진입:
|
|
1049
1033
|
```
|
|
1050
1034
|
start_debug({mode: 'relay-sandbox'})
|
|
1051
|
-
|
|
1035
|
+
start_attach()
|
|
1052
1036
|
```
|
|
1053
1037
|
QR을 폰 카메라로 스캔하면 launcher PWA가 앱을 프레임에 열고 Chii target.js를 주입합니다.
|
|
1054
1038
|
|
|
@@ -1063,7 +1047,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
|
|
|
1063
1047
|
실기기 relay 디버깅 루프(dog-food 빌드 → QR 스캔 → relay attach)의 단계별 절차와 복구 방법은 **[`docs/dogfood-relay-loop.md`](./docs/dogfood-relay-loop.md)** 를 참고하세요. crash가 발생한 경우 — `list_pages.crashDetectedAt`, iOS Console.app `.ips` 분석, redact 절차를 포함한 원인 추적 절차는 **[`docs/crash-triage.md`](./docs/crash-triage.md)** 를 참고하세요.
|
|
1064
1048
|
|
|
1065
1049
|
read-only tool만 노출합니다. 도구는 attach 상태에 따라 2단계로 등록됩니다 — attach 전에는 bootstrap
|
|
1066
|
-
도구(`
|
|
1050
|
+
도구(`start_attach`·`list_pages`)만 보이고, 릴레이/로컬 페이지가 attach되면 `notifications/tools/list_changed`로
|
|
1067
1051
|
attach 의존 도구가 같은 세션에서 동적 등록됩니다(세션 재시작 불필요). 폰 attach 라운드트립은 fully wired
|
|
1068
1052
|
상태이며 남은 것은 실기기 acceptance 한 번뿐입니다. tool 계층은 주입 가능한 CDP 연결 / AIT 소스를 mock해
|
|
1069
1053
|
CI에서 검증됩니다.
|
|
@@ -1088,22 +1072,20 @@ tunnel로 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR을 터미
|
|
|
1088
1072
|
```
|
|
1089
1073
|
|
|
1090
1074
|
- 환경 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
**세션 내 환경 전환은 `start_debug(mode)`가 단일 진입 경로**입니다. `MCP_ENV=relay-live`는 부팅 시 `liveIntent`를 미리 시드하는 deprecated 별칭으로만 남아 있습니다 — 새 세션에서는 `start_debug({mode: 'relay-live', confirm: true})`로 진입하세요.
|
|
1075
|
+
**세션 내 환경 전환은 `start_debug(mode)`가 단일 진입 경로**입니다.
|
|
1094
1076
|
|
|
1095
1077
|
| Tool | CDP / AIT 백킹 | 설명 |
|
|
1096
1078
|
|---|---|---|
|
|
1097
1079
|
| `list_console_messages` | `Runtime.consoleAPICalled` | 최근 console.log/warn/error 메시지 (level, text, timestamp, args) |
|
|
1098
1080
|
| `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | 최근 XHR/fetch 요청 (url, method, status, timing) |
|
|
1099
1081
|
| `list_pages` | Chii 릴레이 target 목록 | attach된 페이지 + tunnel 상태 + wss URL |
|
|
1100
|
-
| `
|
|
1082
|
+
| `start_attach` | (순수 합성 + attach 대기) | `ait deploy --scheme-only` URL에 `debug=1`+릴레이 URL을 끼워 self-attach deep-link를 합성하고 QR을 출력한 뒤, 폰이 attach될 때까지 같은 호출 안에서 대기한다(QR 생성·attach가 한 호출). `mode`로 세션 환경을 함께 전환할 수 있고, 대기 중 TOTP 코드를 자동 재발행한다. 환경 2·3 진입 도구(bootstrap) — `list_pages` 선행 불필요 |
|
|
1101
1083
|
| `get_dom_document` | `DOM.getDocument` | DOM 트리 read (구조/레이아웃 회귀 진단) |
|
|
1102
1084
|
| `take_snapshot` | `DOMSnapshot.captureSnapshot` | 페이지 스냅샷 (documents + interned strings, 시각 회귀 진단) |
|
|
1103
1085
|
| `take_screenshot` | `Page.captureScreenshot` | 페이지 PNG 스크린샷 (MCP image content block 반환) |
|
|
1104
1086
|
| `measure_safe_area` | `Runtime.evaluate` | attach된 페이지에서 safe-area 프로브 실행 → 정규화된 safe-area inset·뷰포트 geometry·DPR·User-Agent 반환. read-only. relay 세션(폰 attach)에서 viewport preset을 extrapolated/placeholder→measured로 승급할 ground truth 수집용. attach 필요 (`list_pages` 먼저) |
|
|
1105
1087
|
| `evaluate` | `Runtime.evaluate` | attach된 페이지에서 임의 JS 표현식 평가(returnByValue) → 결과 반환. **read-only 아님** — 표현식이 부작용(DOM 변경·SDK 호출·상태 변경)을 일으킬 수 있음. attach 필요 |
|
|
1106
|
-
| `call_sdk` | `window.__sdkCall` 브리지 (`Runtime.evaluate` 경유) | dog-food SDK 메서드를 `window.__sdkCall` 브리지로 호출 (`@apps-in-toss/web-framework`가 `__DEBUG_BUILD__` 번들에서만 export). **read-only 아님** — SDK 호출은 부작용(내비게이션·결제·권한 등). 환경 3
|
|
1088
|
+
| `call_sdk` | `window.__sdkCall` 브리지 (`Runtime.evaluate` 경유) | dog-food SDK 메서드를 `window.__sdkCall` 브리지로 호출 (`@apps-in-toss/web-framework`가 `__DEBUG_BUILD__` 번들에서만 export). **read-only 아님** — SDK 호출은 부작용(내비게이션·결제·권한 등). 환경 3(실기기 relay)에선 실 SDK, 환경 1(로컬 mock)에선 mock SDK. 환경 2(PWA)는 SDK 미주입으로 사용 불가. attach 필요. `{ok,value}` / `{ok,error}` 반환 |
|
|
1107
1089
|
| `AIT.getSdkCallHistory` | AIT 도메인 | SDK 호출 trace (method, args, result/error, timestamp) |
|
|
1108
1090
|
| `AIT.getMockState` | AIT 도메인 | mock state 스냅샷 (`window.__ait`) |
|
|
1109
1091
|
| `AIT.getOperationalEnvironment` | AIT 도메인 | `getOperationalEnvironment()` + SDK 버전 |
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/test-runner/bundle.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* esbuild-based bundler for user test files.
|
|
4
|
+
*
|
|
5
|
+
* Bundles a single test file into a self-contained IIFE string that can be
|
|
6
|
+
* injected into a WebView via `Runtime.evaluate`. The bundle includes the
|
|
7
|
+
* test runtime (`runtime.ts`), which provides `describe/it/test/expect` and
|
|
8
|
+
* the `runTestModule(factory)` entry point.
|
|
9
|
+
*
|
|
10
|
+
* ## How the wiring works
|
|
11
|
+
*
|
|
12
|
+
* The bundle exposes two exports on `globalThis.__testBundle`:
|
|
13
|
+
* - `runTestModule` — the runtime's entry function.
|
|
14
|
+
* - `__userFactory` — an async function whose body is the user's top-level
|
|
15
|
+
* test registration code (describe/it/test calls).
|
|
16
|
+
*
|
|
17
|
+
* The Node-side RPC (`rpc.ts`) calls:
|
|
18
|
+
* `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`
|
|
19
|
+
*
|
|
20
|
+
* `runTestModule` then installs `describe/it/test/expect` as globals, invokes
|
|
21
|
+
* the factory (which registers all tests), runs them, and returns a `RunReport`.
|
|
22
|
+
*
|
|
23
|
+
* ## Why a factory wrapper is needed
|
|
24
|
+
*
|
|
25
|
+
* Naively adding the runtime to `entryPoints` and bundling the user file would
|
|
26
|
+
* fail for two reasons:
|
|
27
|
+
* 1. `describe/it/test/expect` from the runtime are module-local in the IIFE
|
|
28
|
+
* scope. The user's top-level `describe(...)` calls expect them as globals —
|
|
29
|
+
* they are not globals until `runTestModule` installs them.
|
|
30
|
+
* 2. Even with globals pre-installed, the user file runs at IIFE-evaluation
|
|
31
|
+
* time, before the RPC layer calls `runTestModule` to reset state and start
|
|
32
|
+
* the test clock.
|
|
33
|
+
*
|
|
34
|
+
* The factory approach solves both: the user's registration code is deferred
|
|
35
|
+
* into a function that `runTestModule` calls AFTER installing the globals.
|
|
36
|
+
*
|
|
37
|
+
* ## Factory extraction algorithm
|
|
38
|
+
*
|
|
39
|
+
* The `userFactoryPlugin` reads the user file and splits lines into:
|
|
40
|
+
* - **top-level**: `import …` and re-export lines — kept at module scope
|
|
41
|
+
* (the only valid position for static `import` in ESM).
|
|
42
|
+
* - **body**: all other statements — moved into the body of the exported
|
|
43
|
+
* `__userFactory` async function.
|
|
44
|
+
*
|
|
45
|
+
* esbuild processes the re-generated module, following each static import
|
|
46
|
+
* through the normal dependency graph (including the SDK-redirect plugin).
|
|
47
|
+
*
|
|
48
|
+
* ## SDK redirect
|
|
49
|
+
*
|
|
50
|
+
* Imports of `@apps-in-toss/web-framework` (and sub-paths) are intercepted via
|
|
51
|
+
* the `sdkRedirectPlugin` and replaced with a virtual `window.__sdk` proxy that
|
|
52
|
+
* `src/in-app/auto.ts` installs at runtime. This works for both 2.x and 3.x SDK.
|
|
53
|
+
*
|
|
54
|
+
* SECRET-HANDLING: the returned bundle code is caller-managed; never log it.
|
|
55
|
+
*/
|
|
56
|
+
/** Options accepted by `bundleTestFile`. */
|
|
57
|
+
interface BundleOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Additional esbuild `external` patterns. The SDK package
|
|
60
|
+
* (`@apps-in-toss/web-framework` and `@apps-in-toss/web-framework/*`) is
|
|
61
|
+
* always handled by the SDK redirect plugin — callers may add more patterns
|
|
62
|
+
* to be left as globals.
|
|
63
|
+
*/
|
|
64
|
+
extraExternals?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Global name for the IIFE output object. Defaults to `__testBundle`.
|
|
67
|
+
* The runtime entry uses this to call `__testBundle.runTestModule(__userFactory)`.
|
|
68
|
+
*/
|
|
69
|
+
globalName?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The result of bundling a test file.
|
|
73
|
+
* `code` is a self-contained IIFE string ready for `Runtime.evaluate`.
|
|
74
|
+
*/
|
|
75
|
+
interface BundleResult {
|
|
76
|
+
code: string;
|
|
77
|
+
warnings: string[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
|
|
81
|
+
*
|
|
82
|
+
* The IIFE installs `window.__testBundle` (or the custom `globalName`) with:
|
|
83
|
+
* - `runTestModule` — the runtime entry (from `runtime.ts`).
|
|
84
|
+
* - `__userFactory` — an async function wrapping the user's test registration
|
|
85
|
+
* code so it runs AFTER `runTestModule` installs the globals.
|
|
86
|
+
*
|
|
87
|
+
* Callers (rpc.ts) invoke:
|
|
88
|
+
* `globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory)`
|
|
89
|
+
*
|
|
90
|
+
* @param absPath - Absolute path to the user test file.
|
|
91
|
+
* @param opts - Optional bundling overrides.
|
|
92
|
+
*/
|
|
93
|
+
declare function bundleTestFile(absPath: string, opts?: BundleOptions): Promise<BundleResult>;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { BundleResult as n, bundleTestFile as r, BundleOptions as t };
|
|
96
|
+
//# sourceMappingURL=bundle-KFs4t-wc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-KFs4t-wc.d.ts","names":[],"sources":["../src/test-runner/bundle.ts"],"mappings":";;AAqEA;;;;;AAmBA;;;;;AA2LA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA9MiB,aAAA;;;;;;;EAOf,cAAA;;;;;EAKA,UAAA;AAAA;;;;;UAOe,YAAA;EACf,IAAA;EACA,QAAA;AAAA;;;;;;;;;;;;;;;iBAyLoB,cAAA,CAAe,OAAA,UAAiB,IAAA,GAAO,aAAA,GAAgB,OAAA,CAAQ,YAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto.d.ts","names":[],"sources":["../../src/in-app/auto.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"auto.d.ts","names":[],"sources":["../../src/in-app/auto.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AA8IA;;;;;AAkCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAhGQ,MAAA;EAAA,UACI,MAAA;;;;;;;;;;IAUR,KAAA,GAAQ,MAAA;;;;;;;;;IAUR,SAAA,IACE,IAAA,aACG,IAAA,gBACA,OAAA;MAAU,EAAA;MAAa,KAAA;MAAiB,KAAA;IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBAsCjC,eAAA,CAAA;;;;;;;;;;;;;;iBAkCA,cAAA,CACd,KAAA,YACA,SAAA"}
|
package/dist/in-app/auto.js
CHANGED
|
@@ -151,6 +151,42 @@ function isTrycloudflareHost(hostname) {
|
|
|
151
151
|
return hostname.endsWith(TRYCLOUDFLARE_HOST_SUFFIX);
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
+
* Returns true when the hostname is a localhost/loopback address.
|
|
155
|
+
* Allowed: `localhost`, `127.x.x.x` (full RFC 5735 loopback block), `[::1]`,
|
|
156
|
+
* `0.0.0.0`, `*.localhost`.
|
|
157
|
+
*
|
|
158
|
+
* Security note: `hostname.startsWith('127.')` is intentionally NOT used —
|
|
159
|
+
* that pattern would accept `127.evil.com`, which starts with "127." but is an
|
|
160
|
+
* attacker-controlled hostname, not a loopback address. Instead, the 127/8
|
|
161
|
+
* loopback block is matched with a strict numeric-quad regex so only valid
|
|
162
|
+
* dotted-decimal IPv4 in the 127.x.x.x range pass (#665 작업 A fix).
|
|
163
|
+
*/
|
|
164
|
+
function isLocalhostHost(hostname) {
|
|
165
|
+
if (hostname === "localhost" || hostname === "0.0.0.0") return true;
|
|
166
|
+
if (hostname === "[::1]") return true;
|
|
167
|
+
if (/^127\.\d+\.\d+\.\d+$/.test(hostname)) return true;
|
|
168
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Positive-allowlist kill-switch (#665): returns true when the hostname is a
|
|
173
|
+
* known debug-allowed host. The debug surface is ONLY active on:
|
|
174
|
+
* - localhost / loopback (env 1 desktop dev)
|
|
175
|
+
* - *.trycloudflare.com (env 2 PWA tunnel)
|
|
176
|
+
* - *.private-apps.tossmini.com (env 3 dog-food)
|
|
177
|
+
*
|
|
178
|
+
* Any other host (including apps.tossmini.com — the former env 4 LIVE host)
|
|
179
|
+
* is silently blocked. This is a positive allowlist — unlisted hosts never
|
|
180
|
+
* had debug surface regardless, but this function makes it explicit and
|
|
181
|
+
* auditable in a single place.
|
|
182
|
+
*
|
|
183
|
+
* SECRET-HANDLING: the hostname value MUST NOT be logged or included in any
|
|
184
|
+
* error reason string — only benign labels ('host not in allowlist') are safe.
|
|
185
|
+
*/
|
|
186
|
+
function isDebugAllowedHost(hostname) {
|
|
187
|
+
return isLocalhostHost(hostname) || isTrycloudflareHost(hostname) || isPrivateAppsHost(hostname);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
154
190
|
* Pure function that evaluates the runtime debug activation layers (B and C).
|
|
155
191
|
*
|
|
156
192
|
* Has no side effects. The input is explicit. Returns a discriminated union
|
|
@@ -173,12 +209,13 @@ function isTrycloudflareHost(hostname) {
|
|
|
173
209
|
*/
|
|
174
210
|
function evaluateDebugGate(input) {
|
|
175
211
|
const isTunnel = isTrycloudflareHost(input.hostname);
|
|
176
|
-
|
|
212
|
+
const isLocal = isLocalhostHost(input.hostname);
|
|
213
|
+
if (!isDebugAllowedHost(input.hostname)) return {
|
|
177
214
|
attach: false,
|
|
178
215
|
reason: "host"
|
|
179
216
|
};
|
|
180
217
|
let deploymentId = "";
|
|
181
|
-
if (!isTunnel) {
|
|
218
|
+
if (!isTunnel && !isLocal) {
|
|
182
219
|
deploymentId = input.searchParams.get("_deploymentId") ?? "";
|
|
183
220
|
if (deploymentId === "") return {
|
|
184
221
|
attach: false,
|
|
@@ -688,7 +725,7 @@ function shouldActivate(isDev = detectDevSignal(), searchStr = typeof window !==
|
|
|
688
725
|
const params = new URLSearchParams(searchStr);
|
|
689
726
|
return params.get("debug") === "1" || params.has("relay");
|
|
690
727
|
}
|
|
691
|
-
if (!shouldActivate()) {} else {
|
|
728
|
+
if (!shouldActivate()) {} else if (typeof window === "undefined" || !isDebugAllowedHost(window.location.hostname)) {} else {
|
|
692
729
|
maybeAttach();
|
|
693
730
|
import("@apps-in-toss/web-framework").then((sdk) => {
|
|
694
731
|
if (typeof window === "undefined") return;
|