@apps-in-toss/devtools 3.0.5 → 3.1.0-beta.1
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 +28 -201
- package/README.md +871 -237
- package/dist/mock/2x.d.ts +155 -3
- package/dist/mock/2x.js +1560 -1306
- package/dist/mock/3x.d.ts +123 -3
- package/dist/mock/3x.js +1507 -1245
- package/dist/mock/index.d.ts +123 -3
- package/dist/mock/index.js +1507 -1245
- package/dist/panel/index.js +106 -154
- package/dist/tunnel-BvEf1qGV.js +186 -0
- package/dist/tunnel-DtCTOUlp.cjs +187 -0
- package/dist/unplugin/index.cjs +147 -12
- package/dist/unplugin/index.d.cts +78 -408
- package/dist/unplugin/index.d.ts +78 -408
- package/dist/unplugin/index.js +147 -13
- package/dist/unplugin/tunnel.cjs +191 -0
- package/dist/unplugin/tunnel.d.cts +140 -0
- package/dist/unplugin/tunnel.d.ts +140 -0
- package/dist/unplugin/tunnel.js +186 -0
- package/package.json +8 -5
- package/CHANGELOG.md +0 -32
package/README.md
CHANGED
|
@@ -1,41 +1,173 @@
|
|
|
1
1
|
# @apps-in-toss/devtools
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
**한국어** · [English](./README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@apps-in-toss/devtools) [](./LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- **플로팅 DevTools 패널** — 12개 탭에서 권한·네트워크·IAP·광고·위치·뷰포트 등 mock 상태를 즉시 전환해요.
|
|
11
|
-
- **디바이스 시뮬레이션** — iPhone/Galaxy 프리셋 13종 + 프레임/노치/홈 인디케이터/앱인토스 nav bar 오버레이.
|
|
12
|
-
- **모든 번들러 지원** — Vite · Webpack · Rspack · Rollup · esbuild.
|
|
9
|
+
`@apps-in-toss/web-framework` SDK의 mock 라이브러리입니다. `@apps-in-toss/webview-bridge` import도 unplugin이 함께 인터셉트합니다(high-level SDK 함수만 노출 — bridge primitive는 미노출). (2.x의 `@apps-in-toss/web-bridge`, `@apps-in-toss/web-analytics`도 back-compat으로 지원.)
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
앱인토스(Apps in Toss) 미니앱을 **일반 브라우저**에서 개발하고 테스트할 수 있게 해줍니다. 토스 앱 없이도 SDK의 모든 기능을 시뮬레이션하여 빠른 개발 사이클을 지원합니다.
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
이 프로젝트는 더 이상 유지보수되지 않습니다. repo는 archive되어 read-only가 되며, 소스와 이슈 기록은 GitHub에 그대로 남습니다. npm에 올라간 패키지는 계속 설치할 수 있지만 더 이상 업데이트되지 않습니다. `aitc.dev` 도메인과 그 위에서 서비스하던 사이트도 함께 종료되므로, 문서와 예제는 GitHub 소스를 보세요. 이 패키지에서 그 도메인에 의존하는 부분은 환경 2(실기기 PWA 미리보기)입니다 — 진입점인 launcher PWA가 `https://devtools.aitc.dev/launcher/`에 배포돼 있고, 터널 QR/deep-link도 그 주소로 만들어집니다. 이미 배포된 버전과 폰에 이미 설치된 launcher에는 이 사실을 소급 적용할 수 없습니다. 환경 1(로컬 브라우저 + mock SDK + DevTools 패널)은 외부 호스트에 의존하지 않습니다.
|
|
14
|
+
|
|
15
|
+
- **60+ SDK API mock** — 인증, 결제, IAP, 위치, 카메라, 스토리지 등
|
|
16
|
+
- **Device API 모드 시스템** — mock / web / prompt 세 가지 모드로 디바이스 API 동작 전환
|
|
17
|
+
- **Device simulation** — iPhone/Galaxy 프리셋 + orientation 토글로 데스크탑 브라우저에서 모바일 뷰포트 시뮬레이션
|
|
18
|
+
- **Floating DevTools Panel** — 브라우저에서 SDK 상태를 실시간으로 제어 (12개 탭, mock state preset library 포함)
|
|
19
|
+
- **모든 번들러 지원** — [unplugin](https://github.com/unjs/unplugin) 기반 Vite, Webpack, Rspack, esbuild, Rollup 통합
|
|
20
|
+
|
|
21
|
+
데모 소스: [`e2e/fixture/`](https://github.com/apps-in-toss-community/devtools/tree/main/e2e/fixture) (self-contained 데모 앱. GitHub Pages 호스팅은 도메인 종료와 함께 내려갑니다 — 로컬에서 `pnpm e2e:build`로 빌드해 볼 수 있습니다).
|
|
22
|
+
|
|
23
|
+
## 15초 quickstart — 내 상황에 맞는 환경 고르기
|
|
24
|
+
|
|
25
|
+
3가지 실행 환경이 있습니다. 지금 상황에 맞는 카드 하나를 고르고, 해당 상세 시나리오 문서로 이동하세요.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
**환경 1 — 로컬 브라우저** (가장 빠름, HMR O)
|
|
30
|
+
|
|
31
|
+
데스크탑 Chrome에서 mock SDK + DevTools 패널로 개발합니다. 토스 앱·폰 없이 즉시 시작.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add -D @apps-in-toss/devtools
|
|
35
|
+
# vite.config.ts에 unplugin 추가 → pnpm dev
|
|
18
36
|
```
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
DevTools 패널: 화면 우하단 **AIT** 버튼. 상세: [`docs/scenarios/env-1.md`](./docs/scenarios/env-1.md)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
**환경 2 — 실기기 PWA** (실 WebKit 엔진, HMR O, 토스 검수 불필요)
|
|
43
|
+
|
|
44
|
+
폰에서 실기기 Safari/WebKit 엔진으로 미니앱을 확인합니다. launcher PWA를 한 번 설치하고, 매 세션마다 QR 스캔.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# vite.config.ts에 tunnel 옵션 추가 후:
|
|
48
|
+
pnpm dev:phone # AIT_TUNNEL=1 pnpm dev 와 동일
|
|
49
|
+
# 터미널에 QR 출력 → 폰 카메라로 스캔 → launcher PWA에서 자동 열림
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`tunnel: { cdp: true }`를 켜면 같은 QR 한 번으로 화면 미리보기 + on-device CDP가 함께 열려 실기기 WebKit의 DOM·콘솔·예외를 MCP로 관측합니다 (`call_sdk`는 환경 2에서 mock — 실 SDK는 환경 3). CDP를 쓰려면 디버깅 패키지 두 개를 추가로 설치하세요 — 아래 [디버깅 패키지](#디버깅-패키지-환경-23) 참고.
|
|
53
|
+
|
|
54
|
+
사전: 폰에 `https://devtools.aitc.dev/launcher/` 를 홈 화면에 한 번 추가. 이 호스트는 `aitc.dev` 도메인 정리와 함께 사라집니다. 상세: [`docs/scenarios/env-2.md`](./docs/scenarios/env-2.md)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
**환경 3 — intoss-private** (토스 WebView, HMR X, debug 전용)
|
|
59
|
+
|
|
60
|
+
실기기 토스 앱 WebView에서 dog-food 번들을 로드하고 MCP relay로 디버깅합니다. `@ait-co/debugger` 설치가 필요합니다 — 아래 [디버깅 패키지](#디버깅-패키지-환경-23) 참고.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx -y -p @ait-co/debugger debugger # MCP 서버 시작 → QR 출력 (devDep이면 pnpm exec debugger)
|
|
64
|
+
# ait build && ait deploy --scheme-only
|
|
65
|
+
# start_attach(scheme_url) 호출 한 번으로 QR 생성 + 폰 attach까지 — QR 스캔하면 토스 앱 로드 + relay attach
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
HMR 없음(토스 WebView cold-load만). 상세: [`docs/scenarios/env-3.md`](./docs/scenarios/env-3.md)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## on-device 디버깅 한 줄 설정
|
|
73
|
+
|
|
74
|
+
환경 2·3에서 on-device CDP 디버깅을 활성화하려면 미니앱 entry(`main.tsx` 등)에 **한 줄**을 추가하세요:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// main.tsx (또는 미니앱 entry 최상단)
|
|
78
|
+
import "@ait-co/debug-console/auto";
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
이 한 줄이 하는 일:
|
|
82
|
+
|
|
83
|
+
- **self-gate**: URL에 `?debug=1` 또는 `?relay=`가 없고, DEV 빌드도 아니라면 아무것도 하지 않습니다. 청크는 dormant 상태로 남아 일반 프로덕션 로드에 영향을 주지 않습니다.
|
|
84
|
+
- **attach**: gate가 통과하면 `maybeAttach()`를 호출해 Chii `target.js`를 주입합니다 (Layer B·C 게이트 시맨틱스는 완전히 유지).
|
|
85
|
+
- **SDK 브리지**: `window.__sdk` / `window.__sdkCall`을 설치해 에이전트가 CDP relay의 `Runtime.evaluate`로 SDK API를 직접 구동할 수 있게 합니다. `@apps-in-toss/web-framework`가 없으면 조용히 skip합니다.
|
|
86
|
+
- **타입**: `Window.__sdk` / `__sdkCall` 글로벌 타입을 자동으로 제공합니다 — 별도 `globals.d.ts` 불필요.
|
|
87
|
+
|
|
88
|
+
환경 3(intoss-private relay) 빌드는 relay QR deep-link가 `?debug=1&relay=<wss>` 파라미터를 실어 보내므로, 이 한 줄만 있으면 별도 게이트 코드가 필요 없습니다. 환경 2(PWA, `tunnel: { cdp: true }`)도 동일하게 동작합니다.
|
|
89
|
+
|
|
90
|
+
옛 경로 `@apps-in-toss/devtools/in-app/auto`는 0.2.x에서도 계속 resolve되지만 inert한 no-op 스텁이며 1.0.0에서 제거됩니다 — import를 위 새 경로로 옮기세요.
|
|
91
|
+
|
|
92
|
+
> TOTP 인증이 필요한 dog-food 빌드는 빌드 define으로 `__DEBUG_TOTP_SECRET__`을 주입하고 `@ait-co/debug-console`을 직접 import해 `evaluateDebugGate({ verifyTotpCode })` + `maybeAttach()`를 사용하세요. `in-app/auto`는 TOTP verifier를 주입하지 않으므로 C3 레이어가 비활성화됩니다.
|
|
93
|
+
|
|
94
|
+
## 자주 겪는 문제 5가지
|
|
95
|
+
|
|
96
|
+
**"QR 창이 안 열림"**
|
|
97
|
+
|
|
98
|
+
`start_attach`을 먼저 호출하지 않았거나, GUI 없는 headless 환경이라 대시보드를 열 수 없는 경우입니다. 도구 결과에 텍스트 QR이 출력되므로 폰 카메라로 직접 스캔하세요. 로컬 GUI 환경에서는 대시보드가 자동으로 브라우저에 열립니다.
|
|
99
|
+
|
|
100
|
+
**"page 미attach" — list_pages가 빈 배열 반환**
|
|
101
|
+
|
|
102
|
+
relay에 붙은 페이지가 없는 상태입니다. `start_attach` → QR 스캔 순서로 폰을 다시 진입시키세요. MCP 에러 메시지가 "페이지가 attach 안 됨. start_attach → QR 스캔."으로 뜨면 이 케이스입니다.
|
|
103
|
+
|
|
104
|
+
**"tunnel down" — 터널 응답 없음 또는 timeout**
|
|
105
|
+
|
|
106
|
+
cloudflared quick tunnel은 수 시간 후 drop될 수 있습니다. `debugger` 프로세스를 재시작하면 새 tunnel URL이 발급됩니다. 재발급 후 QR을 다시 스캔하세요. (관련: [#290](https://github.com/apps-in-toss-community/devtools/issues/290))
|
|
107
|
+
|
|
108
|
+
**"page crash" — list_pages에 crashDetectedAt이 찍힘**
|
|
109
|
+
|
|
110
|
+
폰 측 페이지가 OOM·JS exception·native bridge crash로 죽은 상태입니다. 앱을 재실행 후 `start_attach` → QR 스캔으로 다시 attach하세요. (관련: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
|
|
111
|
+
|
|
112
|
+
**"SDK 부재" — window.\__sdkCall 미주입**
|
|
113
|
+
|
|
114
|
+
`call_sdk` 호출 시 `ok: false, error: "window.__sdkCall is not available"` 에러가 뜨면 SDK 브리지가 아직 설치되지 않은 상태입니다. 아래 "on-device 디버깅 한 줄 설정" 섹션을 참고해 `import '@ait-co/debug-console/auto'`가 미니앱 entry에 추가돼 있는지 확인하세요. 환경 2(PWA)에서는 이 에러가 예상 결과입니다. (관련: [#285](https://github.com/apps-in-toss-community/devtools/issues/285))
|
|
115
|
+
|
|
116
|
+
**"QR 스캔했는데 인증 실패" — TOTP 만료**
|
|
117
|
+
|
|
118
|
+
`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을 발급받으세요.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 설치
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm install -D @apps-in-toss/devtools
|
|
126
|
+
# 또는
|
|
127
|
+
pnpm add -D @apps-in-toss/devtools
|
|
128
|
+
```
|
|
21
129
|
|
|
22
130
|
### 지원 SDK 버전
|
|
23
131
|
|
|
24
|
-
|
|
132
|
+
stable `latest` 하나가 web-framework 2.x와 3.x를 함께 지원합니다.
|
|
133
|
+
|
|
134
|
+
| SDK 라인 | 검증 버전 | 자동 facade | 수동 alias |
|
|
135
|
+
| -------- | --------- | --------------------------- | ------------------------------------------------- |
|
|
136
|
+
| **2.x** | `2.10.8` | unplugin이 설치 버전을 감지 | `@apps-in-toss/devtools/mock/2x` |
|
|
137
|
+
| **3.x** | `3.0.1` | unplugin이 설치 버전을 감지 | `@apps-in-toss/devtools/mock/3x` (`/mock` 기본값) |
|
|
138
|
+
|
|
139
|
+
peer 범위는 `>=2.6.0 <3.0.0 || >=3.0.1 <4.0.0`이며 optional입니다. MCP/패널만 쓰는 프로젝트에는 SDK를 강제로 설치하지 않습니다. 자동 감지가 어려운 monorepo에서는 `aitDevtools.vite({ sdkVersion: '2' })` 또는 `'3'`으로 명시하세요.
|
|
140
|
+
|
|
141
|
+
### 디버깅 패키지 (환경 2·3)
|
|
25
142
|
|
|
26
|
-
|
|
27
|
-
| -------- | -------------------------------- | -------------------------------- |
|
|
28
|
-
| 2.x | unplugin이 설치 버전을 읽어 감지 | `@apps-in-toss/devtools/mock/2x` |
|
|
29
|
-
| 3.x | 위와 동일 (감지 실패 시 기본값) | `@apps-in-toss/devtools/mock/3x` |
|
|
143
|
+
**환경 1(로컬 브라우저 + mock + 패널)만 쓴다면 위 설치가 전부입니다.** 아무것도 더 설치하지 않아도 됩니다.
|
|
30
144
|
|
|
31
|
-
|
|
145
|
+
on-device CDP 디버깅(환경 2의 `tunnel: { cdp: true }`, 환경 3의 relay attach)을 쓰려면 디버깅 패키지 두 개를 추가로 설치하세요:
|
|
32
146
|
|
|
33
|
-
|
|
147
|
+
```bash
|
|
148
|
+
pnpm add -D @ait-co/debugger @ait-co/debug-console
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| 패키지 | 역할 | 번들 반입 |
|
|
152
|
+
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | ------------------------------------------ |
|
|
153
|
+
| [`@ait-co/debugger`](https://www.npmjs.com/package/@ait-co/debugger) | MCP 데몬 · 실기기 테스트 러너 · dev-bridge(환경 2 CDP relay + QR 대시보드) | 안 됨 — devDependency / `npx` 전용 |
|
|
154
|
+
| [`@ait-co/debug-console`](https://www.npmjs.com/package/@ait-co/debug-console) | on-device attach + 인앱 eruda 콘솔 | 됨 — debug 빌드에만 들어가는 유일한 패키지 |
|
|
155
|
+
|
|
156
|
+
두 패키지 모두 devtools의 **optional peer**입니다.
|
|
157
|
+
|
|
158
|
+
- `@ait-co/debugger`가 없으면 `tunnel: { cdp: true }`는 CDP 배선을 건너뛰고 일반 화면 미리보기 터널로 degrade하며, 터미널에 설치 안내를 한 번 출력합니다.
|
|
159
|
+
- `@ait-co/debug-console`이 없으면 unplugin이 in-app attach를 아예 주입하지 않습니다 — attach 코드가 번들에 구조적으로 들어갈 수 없다는 뜻이고, 이게 디버그 표면의 기술적 경계입니다.
|
|
160
|
+
|
|
161
|
+
## Reference consumer
|
|
162
|
+
|
|
163
|
+
[`sdk-example`](https://github.com/apps-in-toss-community/sdk-example)이 devtools의 reference consumer다. 모든 SDK API를 인터랙티브하게 실행해볼 수 있는 카탈로그 앱이다 — 웹 데모 호스팅은 도메인 종료와 함께 내려가므로 위 GitHub 소스를 보면 된다. 새 mock을 추가하면 sdk-example의 카드에서 그대로 동작하는 게 1차 sanity check. 단, 이 repo의 E2E suite는 sdk-example을 clone하지 않고 **내부 자기완결 fixture(`e2e/fixture/`)** 로 운영한다 — sdk-example이 깨져도 devtools CI는 영향받지 않는다.
|
|
164
|
+
|
|
165
|
+
## 번들러 설정
|
|
34
166
|
|
|
35
167
|
### Vite
|
|
36
168
|
|
|
37
169
|
```ts
|
|
38
|
-
// vite.config.ts
|
|
170
|
+
// vite.config.ts (개발 전용)
|
|
39
171
|
import aitDevtools from "@apps-in-toss/devtools/unplugin";
|
|
40
172
|
|
|
41
173
|
export default {
|
|
@@ -43,12 +175,12 @@ export default {
|
|
|
43
175
|
};
|
|
44
176
|
```
|
|
45
177
|
|
|
46
|
-
|
|
178
|
+
> 개발 전용 설정입니다. Production 빌드에서 제외하려면 아래 [Production 빌드](#production-빌드) 섹션을 참고하세요.
|
|
47
179
|
|
|
48
|
-
|
|
180
|
+
### Webpack / Rspack
|
|
49
181
|
|
|
50
182
|
```js
|
|
51
|
-
// webpack.config.js (ESM)
|
|
183
|
+
// webpack.config.js (ESM, 개발 환경에서만 사용 권장)
|
|
52
184
|
import aitDevtools from "@apps-in-toss/devtools/unplugin";
|
|
53
185
|
config.plugins.push(aitDevtools.webpack());
|
|
54
186
|
|
|
@@ -57,137 +189,327 @@ const aitDevtools = require("@apps-in-toss/devtools/unplugin");
|
|
|
57
189
|
config.plugins.push(aitDevtools.webpack());
|
|
58
190
|
```
|
|
59
191
|
|
|
60
|
-
|
|
192
|
+
### Next.js (Turbopack)
|
|
61
193
|
|
|
62
|
-
|
|
194
|
+
Turbopack은 플러그인 시스템을 지원하지 않으므로 `resolveAlias`를 사용합니다.
|
|
63
195
|
|
|
64
|
-
|
|
196
|
+
- `@apps-in-toss/web-framework` 하나만 alias하면 됩니다. SDK 호출은 모두 이 패키지를 거치므로, mock으로 치환하면 web-framework 모듈 자체가 모듈 그래프에서 빠지고, 그 안의 `@apps-in-toss/webview-bridge` import도 함께 사라집니다.
|
|
197
|
+
- Turbopack은 일반적으로 `next dev`에서만 사용되므로 별도의 production 가드가 필요하지 않습니다.
|
|
65
198
|
|
|
66
|
-
```
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
199
|
+
```js
|
|
200
|
+
// next.config.js (Next.js 15+, web-framework 3.0+)
|
|
201
|
+
module.exports = {
|
|
202
|
+
turbo: {
|
|
203
|
+
resolveAlias: {
|
|
71
204
|
"@apps-in-toss/web-framework": "@apps-in-toss/devtools/mock",
|
|
72
205
|
},
|
|
73
206
|
},
|
|
74
207
|
};
|
|
75
208
|
```
|
|
76
209
|
|
|
77
|
-
|
|
210
|
+
Next.js 14 이하에서는 `experimental.turbo`를 사용합니다:
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
// next.config.js (Next.js 14 이하, web-framework 3.0+)
|
|
214
|
+
module.exports = {
|
|
215
|
+
experimental: {
|
|
216
|
+
turbo: {
|
|
217
|
+
resolveAlias: {
|
|
218
|
+
"@apps-in-toss/web-framework": "@apps-in-toss/devtools/mock",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
```
|
|
78
224
|
|
|
79
|
-
>
|
|
225
|
+
> **Panel 주입**: Turbopack은 unplugin을 지원하지 않으므로 Panel이 자동 주입되지 않습니다. 진입점에서 직접 import하세요:
|
|
80
226
|
>
|
|
81
227
|
> ```ts
|
|
228
|
+
> // app/layout.tsx 또는 pages/_app.tsx
|
|
82
229
|
> import "@apps-in-toss/devtools/panel";
|
|
83
230
|
> ```
|
|
84
231
|
|
|
85
|
-
###
|
|
232
|
+
### Next.js (Webpack)
|
|
86
233
|
|
|
87
|
-
|
|
88
|
-
| ------------------------------ | ----------------------------- |
|
|
89
|
-
| `@apps-in-toss/web-framework` | 감지된 라인의 mock facade |
|
|
90
|
-
| `@apps-in-toss/webview-bridge` | 3.x mock facade |
|
|
91
|
-
| `@apps-in-toss/web-bridge` | 2.x mock facade (back-compat) |
|
|
92
|
-
| `@apps-in-toss/web-analytics` | 2.x mock facade (back-compat) |
|
|
234
|
+
Next.js에서 Webpack 모드(`next dev` without `--turbo`, 또는 `next build`)를 사용하는 경우:
|
|
93
235
|
|
|
94
|
-
|
|
236
|
+
```js
|
|
237
|
+
// next.config.js (Webpack 모드)
|
|
238
|
+
const aitDevtools = require("@apps-in-toss/devtools/unplugin"); // CJS entrypoint 제공
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
webpack: (config, { dev }) => {
|
|
242
|
+
if (dev) {
|
|
243
|
+
config.plugins.push(aitDevtools.webpack());
|
|
244
|
+
}
|
|
245
|
+
return config;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
```
|
|
95
249
|
|
|
96
|
-
###
|
|
250
|
+
### 수동 Alias 설정
|
|
97
251
|
|
|
98
|
-
`
|
|
252
|
+
번들러의 `resolve.alias` 설정으로 직접 지정할 수도 있습니다:
|
|
99
253
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
254
|
+
```ts
|
|
255
|
+
// vite.config.ts (web-framework 3.0+)
|
|
256
|
+
import { defineConfig } from "vite";
|
|
257
|
+
|
|
258
|
+
export default defineConfig({
|
|
259
|
+
resolve: {
|
|
260
|
+
alias: {
|
|
261
|
+
"@apps-in-toss/web-framework": "@apps-in-toss/devtools/mock",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
web-framework 2.x에서 수동 alias를 쓰면 위 값을 `@apps-in-toss/devtools/mock/2x`로 바꾸세요. 3.x는 명시적으로 `@apps-in-toss/devtools/mock/3x`를 써도 됩니다.
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
// webpack.config.js (Webpack은 절대 경로 필요, web-framework 3.0+)
|
|
271
|
+
module.exports = {
|
|
272
|
+
resolve: {
|
|
273
|
+
alias: {
|
|
274
|
+
"@apps-in-toss/web-framework":
|
|
275
|
+
require.resolve("@apps-in-toss/devtools/mock"),
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
> **주의**: 수동 alias만 사용하면 DevTools Panel이 자동 주입되지 않습니다. 진입점 파일에 직접 import를 추가하세요:
|
|
282
|
+
>
|
|
283
|
+
> ```ts
|
|
284
|
+
> import "@apps-in-toss/devtools/panel"; // 진입점에 추가
|
|
285
|
+
> ```
|
|
286
|
+
|
|
287
|
+
### 플러그인 옵션
|
|
288
|
+
|
|
289
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
290
|
+
| ------------- | ----------------------------------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
291
|
+
| `panel` | `boolean` | `true` | DevTools Panel 자동 주입 여부 |
|
|
292
|
+
| `sdkVersion` | `'auto' \| '2' \| '3'` | `'auto'` | mock facade 자동 감지 또는 강제 선택 |
|
|
293
|
+
| `forceEnable` | `boolean` | `false` | production에서도 devtools 활성화 |
|
|
294
|
+
| `mock` | `boolean` | `true` (dev) / `false` (prod+forceEnable) | mock alias 활성화 여부 |
|
|
295
|
+
| `mcp` | `boolean` | `false` | MCP state sync opt-in. `true`일 때만 Vite endpoint와 Panel의 상태 POST를 함께 활성화 ([MCP 섹션](#mcp-server) 참조) |
|
|
296
|
+
| `tunnel` | `boolean \| { port?: number; qr?: boolean; cdp?: boolean }` | `false` | Vite dev 서버를 Cloudflare quick tunnel로 노출 (실기기 미리보기, [아래](#run-on-a-real-phone-실기기-미리보기) 참고). `cdp: true`면 환경 2 PWA에 on-device CDP 디버깅도 배선. **Vite dev 모드 전용** |
|
|
109
297
|
|
|
110
298
|
```ts
|
|
111
|
-
aitDevtools.vite({ panel: false }); //
|
|
112
|
-
aitDevtools.vite({ entryPattern: /\/my-bridge\.[tj]sx?$/i }); // 비표준 진입점 파일명에도 패널 자동 주입
|
|
113
|
-
aitDevtools.vite({ sdkVersion: "2" }); // 2.x facade 강제
|
|
299
|
+
aitDevtools.vite({ panel: false }); // Panel 없이 mock만 사용
|
|
114
300
|
aitDevtools.vite({ forceEnable: true }); // production에서도 활성화 (mock 기본 OFF, panel ON)
|
|
115
301
|
aitDevtools.vite({ forceEnable: true, mock: true }); // production에서 mock도 활성화
|
|
116
|
-
aitDevtools.vite({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
products: [
|
|
120
|
-
{
|
|
121
|
-
sku: "my-sku",
|
|
122
|
-
type: "CONSUMABLE",
|
|
123
|
-
displayName: "다이아몬드 10개",
|
|
124
|
-
displayAmount: "1,100원",
|
|
125
|
-
iconUrl: "",
|
|
126
|
-
description: "게임에서 사용할 수 있는 다이아몬드 10개",
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
}); // IAP mock 카탈로그를 프로젝트 상품으로 교체
|
|
132
|
-
aitDevtools.vite({ mcp: true }); // mock state endpoint 개방
|
|
302
|
+
aitDevtools.vite({ mcp: true }); // AI 에이전트용 MCP endpoint 활성화
|
|
303
|
+
aitDevtools.vite({ tunnel: true }); // dev 서버를 *.trycloudflare.com으로 노출
|
|
304
|
+
aitDevtools.vite({ tunnel: { cdp: true } }); // 실기기 미리보기 + on-device CDP 디버깅
|
|
133
305
|
```
|
|
134
306
|
|
|
135
|
-
|
|
307
|
+
`mcp` 기본값은 `false`입니다. 옵션을 생략하면 endpoint를 등록하지 않고 Panel도 상태를 직렬화하거나 네트워크 요청을 보내지 않습니다.
|
|
136
308
|
|
|
137
|
-
##
|
|
309
|
+
## Production 빌드
|
|
138
310
|
|
|
139
|
-
|
|
311
|
+
기본적으로 devtools 플러그인은 **production 빌드에서 자동 비활성화**됩니다 (`NODE_ENV === 'production'`이면 alias 변환과 Panel 주입이 모두 스킵). 별도의 조건부 설정 없이도 안전합니다. `@apps-in-toss/devtools`는 devDependency이고 production 번들에 기여하는 바이트 수는 0입니다. CI가 실제 소비자 fixture를 빌드해 결과물을 grep하는 방식으로 이를 강제합니다.
|
|
140
312
|
|
|
141
|
-
|
|
313
|
+
스테이징 환경 등에서 production 빌드에서도 devtools를 사용하려면 `forceEnable` 옵션을 사용하세요:
|
|
142
314
|
|
|
143
|
-
|
|
144
|
-
|
|
315
|
+
```ts
|
|
316
|
+
aitDevtools.vite({ forceEnable: true }); // panel ON, mock OFF (모니터링 전용)
|
|
317
|
+
aitDevtools.vite({ forceEnable: true, mock: true }); // panel + mock 모두 ON
|
|
318
|
+
```
|
|
145
319
|
|
|
146
|
-
|
|
320
|
+
번들러 설정에서 플러그인 자체를 조건부로 제외할 수도 있습니다:
|
|
147
321
|
|
|
148
|
-
|
|
322
|
+
```ts
|
|
323
|
+
// vite.config.ts
|
|
324
|
+
import { defineConfig } from "vite";
|
|
325
|
+
import aitDevtools from "@apps-in-toss/devtools/unplugin";
|
|
326
|
+
|
|
327
|
+
export default defineConfig(({ command }) => ({
|
|
328
|
+
plugins: [...(command === "serve" ? [aitDevtools.vite()] : [])],
|
|
329
|
+
}));
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
// webpack.config.js (Rspack도 동일)
|
|
334
|
+
const aitDevtools = require("@apps-in-toss/devtools/unplugin");
|
|
335
|
+
const plugins = [];
|
|
336
|
+
if (process.env.NODE_ENV !== "production") {
|
|
337
|
+
plugins.push(aitDevtools.webpack());
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
> Next.js 설정은 위의 [Next.js (Webpack)](#nextjs-webpack) 및 [Next.js (Turbopack)](#nextjs-turbopack) 섹션을 참고하세요.
|
|
342
|
+
|
|
343
|
+
## Run on a real phone (실기기 미리보기)
|
|
344
|
+
|
|
345
|
+
데스크톱 크롬에서 잘 돌던 미니앱을 **실제 폰**에서 보고 싶을 때. Vite dev 서버를 Cloudflare quick tunnel(`*.trycloudflare.com`, **계정 불필요**)로 노출하고, 폰에는 고정 URL의 launcher PWA를 한 번만 추가해 그 안에서 매번의 tunnel URL을 띄웁니다.
|
|
346
|
+
|
|
347
|
+
셋업은 세 갈래입니다:
|
|
149
348
|
|
|
150
|
-
|
|
349
|
+
- **프로젝트당 1회** — `vite.config`에 옵션 + `package.json`에 pnpm 설정 + (선택) `dev:phone` 스크립트
|
|
350
|
+
- **폰당 1회** — launcher PWA를 홈 화면에 추가
|
|
351
|
+
- **매 세션** — `pnpm dev:phone` (또는 `AIT_TUNNEL=1 pnpm dev`) 한 줄
|
|
151
352
|
|
|
152
|
-
|
|
353
|
+
### 1. 프로젝트당 1회 셋업
|
|
354
|
+
|
|
355
|
+
(a) **`vite.config.ts`에 `tunnel` 옵션 추가** — 항상 켜져 있어 매번 cloudflared가 떠도 괜찮으면 `tunnel: true`, 평소엔 끄고 명시할 때만 켜고 싶으면 env-gate 권장:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
// vite.config.ts
|
|
359
|
+
import { defineConfig } from "vite";
|
|
360
|
+
import aitDevtools from "@apps-in-toss/devtools/unplugin";
|
|
361
|
+
|
|
362
|
+
export default defineConfig({
|
|
363
|
+
plugins: [
|
|
364
|
+
aitDevtools.vite({
|
|
365
|
+
tunnel: !!process.env.AIT_TUNNEL, // 평소 OFF, AIT_TUNNEL=1 일 때만 ON
|
|
366
|
+
}),
|
|
367
|
+
],
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
> `process.env.AIT_TUNNEL`은 `vite.config.ts`를 로드하는 시점(= vite 프로세스 기동 시)에 평가됩니다. 따라서 env 변수는 **vite를 띄우기 전에** 설정되어 있어야 합니다 (아래 (c)의 `dev:phone` 스크립트가 이를 자동으로 해결합니다).
|
|
372
|
+
|
|
373
|
+
> on-device CDP 디버깅까지 켜려면 `tunnel: process.env.AIT_TUNNEL ? { cdp: true } : false`처럼 객체 형태로 줍니다. 그러면 HTTP 터널과 별도로 Chii relay가 떠서, QR 한 번으로 화면 미리보기와 CDP attach가 동시에 열립니다. AI host MCP를 그 relay에 붙이면 실기기 WebKit의 DOM·콘솔·예외·`measure_safe_area`를 관측합니다 (`call_sdk`는 환경 2에서 mock).
|
|
374
|
+
|
|
375
|
+
(b) **`package.json`에 pnpm 10+ 빌드 스크립트 허용** — pnpm은 보안상 dependency의 postinstall을 기본 차단합니다. `cloudflared`는 postinstall에서 바이너리(~38 MB)를 받으므로 명시 허용 필요:
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"pnpm": {
|
|
380
|
+
"onlyBuiltDependencies": ["cloudflared"]
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
> 명시하지 않아도 동작은 됩니다 — `tunnel.ts`가 첫 기동 시 `cloudflared.install()`을 lazy로 호출. 다만 `pnpm install`마다 "Ignored build scripts" 경고가 남고, 바이너리 다운로드가 첫 `pnpm dev` 시점으로 미뤄집니다. 참고: [`sdk-example#60`](https://github.com/apps-in-toss-community/sdk-example/pull/60).
|
|
386
|
+
|
|
387
|
+
(c) **(선택) `dev:phone` 스크립트** — env 변수 매번 타기 귀찮으면:
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"scripts": {
|
|
392
|
+
"dev": "vite",
|
|
393
|
+
"dev:phone": "AIT_TUNNEL=1 vite"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 2. 폰당 1회 셋업 (필수)
|
|
399
|
+
|
|
400
|
+
폰에서 `https://devtools.aitc.dev/launcher/`를 열고 **홈 화면에 추가**합니다. (이 호스트는 `aitc.dev` 도메인 정리와 함께 사라지며, 대체 호스트는 없습니다.) launcher는 페이지 상단에 "Install launcher to your phone" 버튼을 띄우는데, 누르면 플랫폼별 네이티브 설치 흐름이 자동으로 안내됩니다 — Android Chrome은 인앱 설치 프롬프트, iOS Safari는 "공유 → 홈 화면에 추가" 일러스트, Firefox/Samsung Internet 등은 수동 안내 카드. launcher URL은 매번 동일하므로 폰당 한 번만 하면 됩니다.
|
|
401
|
+
|
|
402
|
+
launcher는 **PWA(홈 화면 앱)로 실행할 때만 동작**합니다. 일반 브라우저 탭에서 열면 설치 안내만 노출되고 입력/스캐너 UI는 숨겨집니다 — 크롬리스 standalone 디스플레이가 PWA 셸의 본질이라, 일반 탭에서의 동작은 의도적으로 막아둡니다.
|
|
403
|
+
|
|
404
|
+
### 3. 매 세션
|
|
405
|
+
|
|
406
|
+
1. 데스크톱에서 `pnpm dev:phone`을 실행합니다 (1-(c) 스크립트를 추가하지 않았다면 `AIT_TUNNEL=1 pnpm dev`). 터미널에 `https://*.trycloudflare.com` URL + ASCII QR이 출력됩니다.
|
|
407
|
+
2. 폰의 카메라(또는 launcher 아이콘 안의 "Scan QR")로 QR을 스캔합니다. QR은 `https://devtools.aitc.dev/launcher/?url=<tunnel>` deep-link라 launcher PWA가 자동으로 열리고 그날의 dev 앱이 풀스크린으로 뜹니다 — URL 붙여넣기 단계가 필요 없습니다.
|
|
408
|
+
3. 다음 세션엔 새 QR을 스캔만 하면 됩니다. launcher는 마지막 URL을 기억하고, "Rescan" 버튼으로 언제든 교체할 수 있습니다.
|
|
409
|
+
|
|
410
|
+
> QR을 일반 카메라 앱으로 찍었을 때 Safari/Chrome이 일반 탭이 아닌 설치된 launcher PWA로 곧장 라우팅하는 동작은 Android Chrome에서 가장 안정적이고, iOS Safari는 버전에 따라 일반 탭으로 폴백할 수 있습니다. 그 경우 launcher 홈 화면 아이콘에서 한 번 열어주면 그 안의 QR 스캐너로 다시 시도할 수 있습니다.
|
|
411
|
+
|
|
412
|
+
### 배경
|
|
413
|
+
|
|
414
|
+
> **왜 launcher를 거치나요?** quick tunnel URL은 매 실행마다 바뀌므로 그 URL 자체를 PWA로 설치하면 다음 세션엔 죽은 링크가 됩니다. cross-origin으로 페이지를 전환하면 iOS/Android 모두 standalone(크롬리스)이 깨집니다. → 고정 URL의 launcher를 한 번 설치하고, 그 안의 `<iframe>`으로 그날의 dev 앱을 full-bleed로 보여주는 구조입니다.
|
|
415
|
+
>
|
|
416
|
+
> quick tunnel은 **인증이 없고**, **URL이 매 실행마다 바뀌며**, **프로덕션용이 아닙니다**. (계정·도메인이 있다면 named tunnel로 고정 hostname을 받는 방식은 추후 `tunnel: { hostname }` 옵션으로 확장 여지를 남겨뒀습니다.)
|
|
417
|
+
>
|
|
418
|
+
> `tunnel` 옵션은 Vite dev 모드에서만 동작합니다 — production 빌드는 `forceEnable`이어도 터널을 띄우지 않습니다. 다른 번들러(Webpack/Rspack 등)에서는 무시됩니다. 이 옵션을 켜면 `cloudflared` / `qrcode-terminal`가 동적 import로만 로드되므로, 끄면 번들 그래프에 들어오지 않습니다.
|
|
419
|
+
|
|
420
|
+
### 한 줄 셋업
|
|
421
|
+
|
|
422
|
+
위 "프로젝트당 1회" 단계(vite.config 패치 + `onlyBuiltDependencies` + `dev:phone` 스크립트)는 [`agent-plugin`](https://github.com/apps-in-toss-community/agent-plugin)의 `/ait:setup-phone-preview` 한 명령으로 자동화돼 있습니다. 이 README가 그 자동화의 명세서 역할을 하므로, 수동 셋업 단계가 줄어들어도 동작 모델 자체는 동일합니다.
|
|
423
|
+
|
|
424
|
+
## Device API 모드 시스템
|
|
425
|
+
|
|
426
|
+
디바이스 관련 API(카메라, 위치, 클립보드 등)는 세 가지 모드로 동작합니다:
|
|
427
|
+
|
|
428
|
+
| 모드 | 동작 | 사용 사례 |
|
|
429
|
+
| ---------- | ----------------------------------------------------------------- | ------------------------------ |
|
|
430
|
+
| **mock** | `aitState`에 저장된 더미 데이터 반환 | 자동화 테스트, 고정된 시나리오 |
|
|
431
|
+
| **web** | 브라우저 네이티브 API 사용 (Geolocation, File API 등) | 실제 디바이스 기능 테스트 |
|
|
432
|
+
| **prompt** | DevTools Panel이 자동으로 열리고 사용자 입력 대기 (30초 타임아웃) | 수동 QA, 특정 값 입력 |
|
|
433
|
+
|
|
434
|
+
### 모드별 지원 API
|
|
435
|
+
|
|
436
|
+
| API | mock | web | prompt |
|
|
437
|
+
| --------------------------------------- | ---- | --- | ------ |
|
|
438
|
+
| `openCamera` | ✅ | ✅ | ✅ |
|
|
439
|
+
| `fetchAlbumPhotos` | ✅ | ✅ | ✅ |
|
|
440
|
+
| `getCurrentLocation` | ✅ | ✅ | ✅ |
|
|
441
|
+
| `startUpdateLocation` | ✅ | ✅ | ✅ |
|
|
442
|
+
| `getNetworkStatus` | ✅ | ✅ | — |
|
|
443
|
+
| `getClipboardText` / `setClipboardText` | ✅ | ✅ | — |
|
|
444
|
+
|
|
445
|
+
### 모드 설정 방법
|
|
446
|
+
|
|
447
|
+
```js
|
|
448
|
+
// 콘솔에서 개별 API 모드 변경
|
|
449
|
+
__ait.patch("deviceModes", { camera: "web", location: "prompt" });
|
|
450
|
+
|
|
451
|
+
// 또는 DevTools Panel의 Device 탭에서 드롭다운으로 전환
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 더미 이미지 관리
|
|
455
|
+
|
|
456
|
+
mock 모드에서 카메라/앨범 API는 더미 이미지를 반환합니다.
|
|
457
|
+
|
|
458
|
+
- **기본 플레이스홀더**: 파란색/녹색/주황색 320×240 이미지 3장 자동 생성
|
|
459
|
+
- **커스텀 이미지**: DevTools Panel의 Device 탭에서 파일 추가/제거 가능
|
|
460
|
+
- **콘솔에서 설정**: `__ait.patch('mockData', { images: ['data:image/png;base64,...'] })`
|
|
461
|
+
|
|
462
|
+
## Floating DevTools Panel
|
|
463
|
+
|
|
464
|
+
플러그인 사용 시 진입점 파일에 패널이 자동 주입됩니다. 화면 우하단의 **'AIT' 버튼**을 클릭하면 토글됩니다.
|
|
153
465
|
|
|
154
466
|
### 12개 탭
|
|
155
467
|
|
|
156
|
-
| 탭 |
|
|
157
|
-
| ----------------- |
|
|
158
|
-
| **Environment** | 플랫폼 OS
|
|
159
|
-
| **Presets** | QA 시나리오
|
|
160
|
-
| **Viewport** | 디바이스 프리셋 + orientation
|
|
161
|
-
| **Permissions** | camera
|
|
162
|
-
| **Notifications** | 알림 동의 흐름의 다음 결과 선택
|
|
163
|
-
| **Location** |
|
|
164
|
-
| **Device** |
|
|
165
|
-
| **IAP** |
|
|
166
|
-
| **Ads** | 광고 load/show
|
|
167
|
-
| **Events** | Back/Home 네비게이션 이벤트 트리거, 로그인 상태 토글
|
|
168
|
-
| **Analytics** | 기록된 분석 이벤트 실시간 로그 뷰어
|
|
169
|
-
| **Storage** | `Storage` API로 저장된 항목 조회 및 초기화
|
|
468
|
+
| 탭 | 설명 |
|
|
469
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
470
|
+
| **Environment** | 플랫폼 OS (ios/android), 앱 버전, 환경 (toss/sandbox), 로케일, 네트워크 상태, Safe Area Insets, Navigation (SDK no-op API 호출값 관측) |
|
|
471
|
+
| **Presets** | 자주 쓰는 QA 시나리오(권한 거부, offline, 미로그인 등)를 한 클릭으로 적용/해제. 사용자 preset 저장/삭제 가능 |
|
|
472
|
+
| **Viewport** | 디바이스 프리셋(iPhone/Galaxy) + orientation 토글로 모바일 뷰포트 시뮬레이션 |
|
|
473
|
+
| **Permissions** | camera, photos, geolocation, clipboard, contacts, microphone 권한 상태 제어 (allowed/denied/notDetermined) |
|
|
474
|
+
| **Notifications** | 알림 동의 흐름의 다음 결과 선택 (신규 동의 / 이미 동의함 / 거부) |
|
|
475
|
+
| **Location** | 위도, 경도, 정확도 설정 |
|
|
476
|
+
| **Device** | API 모드 전환 (mock/web/prompt), 더미 이미지 관리 (추가/제거/기본값/초기화) |
|
|
477
|
+
| **IAP** | 다음 구매 결과 선택 (success/취소/에러 등), TossPay 결제 결과, 완료된 주문 내역 (최근 5건) |
|
|
478
|
+
| **Ads** | 전면 광고 load/show 트리거 및 마지막 광고 이벤트 로그 |
|
|
479
|
+
| **Events** | Back/Home 네비게이션 이벤트 트리거, 로그인 상태 토글 |
|
|
480
|
+
| **Analytics** | 기록된 분석 이벤트 실시간 로그 뷰어 (최근 30건, 타임스탬프/타입/파라미터) |
|
|
481
|
+
| **Storage** | `Storage` API로 저장된 항목 조회 및 초기화 |
|
|
482
|
+
|
|
483
|
+
> **prompt 모드 자동 열림**: prompt 모드로 설정된 API가 호출되면, Panel이 자동으로 Device 탭을 열고 사용자 입력 UI를 표시합니다.
|
|
484
|
+
|
|
485
|
+
### toss-gated 동작을 dev에서 시험하기 (Environment + Navigation)
|
|
486
|
+
|
|
487
|
+
실 토스 WebView에서 native bridge로만 발화하던 일부 no-op API(예: `setIosSwipeGestureEnabled`)는 mock에서 그 **마지막 호출값**을 관측 가능한 state로 비춥니다. Environment 탭의 **Navigation** 섹션이 이 값을 read-only로 표시합니다.
|
|
488
|
+
|
|
489
|
+
이로써 `getOperationalEnvironment() === 'toss'`로 게이트된 코드 경로를 토스 앱 없이 검증할 수 있습니다:
|
|
170
490
|
|
|
171
|
-
|
|
491
|
+
1. Environment 탭에서 **환경(Environment)** 을 `toss`로 전환 (기본은 `sandbox` — toss 진입은 명시적 opt-in).
|
|
492
|
+
2. 앱의 toss-gated 가드(예: sdk-example `useDisableIosSwipeGestureInToss`)가 실행되며 `setIosSwipeGestureEnabled({ isEnabled: false })`를 호출.
|
|
493
|
+
3. Navigation 섹션의 `iOS swipe-back` 값이 `미호출` → `disabled`로 실시간 전환되는 것을 패널에서 확인. `AIT.getMockState()`로도 `navigation.iosSwipeGestureEnabled`를 대조할 수 있습니다.
|
|
172
494
|
|
|
173
|
-
|
|
495
|
+
### Mock state preset library (Presets 탭)
|
|
174
496
|
|
|
175
|
-
|
|
497
|
+
한 시나리오에 여러 mock 키가 동시에 일정 상태여야 하는 경우(예: "offline일 때 IAP `NETWORK_ERROR` + 결제 fail")를 매번 손으로 맞추지 않고 한 클릭으로 적용합니다. 적용된 preset은 ✓ 표시되며, 정의된 키 중 하나라도 변경되면 자동으로 indicator가 풀립니다 (preset이 정의하지 않은 키는 비교 대상이 아님).
|
|
176
498
|
|
|
177
|
-
|
|
499
|
+
내장 preset:
|
|
178
500
|
|
|
179
501
|
| ID | 의미 |
|
|
180
502
|
| ------------------- | ---------------------------------------------------------------- |
|
|
181
503
|
| `all-allowed` | 모든 권한 허용, WIFI, 로그인됨, IAP success — 기본 시나리오 복귀 |
|
|
182
504
|
| `permission-denied` | camera / photos / geolocation / contacts 거부 |
|
|
183
|
-
| `offline` | `getNetworkStatus` → OFFLINE, IAP `NETWORK_ERROR`, 결제
|
|
184
|
-
| `logged-out` | `auth.isLoggedIn
|
|
505
|
+
| `offline` | `getNetworkStatus` → OFFLINE, IAP `NETWORK_ERROR`, 결제 fail |
|
|
506
|
+
| `logged-out` | `auth.isLoggedIn=false`. 로그인 플로우 검증 |
|
|
185
507
|
| `iap-pending` | IAP `nextResult` → `PAYMENT_PENDING` |
|
|
186
508
|
| `ads-no-fill` | 광고 fill 실패 분기 |
|
|
187
509
|
|
|
188
|
-
preset
|
|
510
|
+
사용자가 토글로 만든 임의 상태는 "Save current as preset" 버튼으로 저장됩니다 (`localStorage` 영속, `__ait_preset:<id>` prefix). 저장된 preset은 새로고침/탭 재진입 후에도 유지됩니다. Preset 적용 범위는 `networkStatus / permissions / auth / iap / ads / payment` 슬라이스로 제한 — viewport나 brand 같은 무관한 상태가 흔들리지 않습니다.
|
|
189
511
|
|
|
190
|
-
코드에서도
|
|
512
|
+
코드에서도 export됩니다:
|
|
191
513
|
|
|
192
514
|
```ts
|
|
193
515
|
import {
|
|
@@ -196,9 +518,11 @@ import {
|
|
|
196
518
|
saveUserPreset,
|
|
197
519
|
} from "@apps-in-toss/devtools";
|
|
198
520
|
|
|
521
|
+
// 내장 preset 적용
|
|
199
522
|
const offline = builtInPresets.find((p) => p.id === "offline")!;
|
|
200
523
|
applyPreset(offline.state);
|
|
201
524
|
|
|
525
|
+
// 커스텀 preset 저장
|
|
202
526
|
saveUserPreset("My QA scenario", {
|
|
203
527
|
networkStatus: "OFFLINE",
|
|
204
528
|
permissions: { camera: "denied" },
|
|
@@ -206,157 +530,366 @@ saveUserPreset("My QA scenario", {
|
|
|
206
530
|
});
|
|
207
531
|
```
|
|
208
532
|
|
|
209
|
-
###
|
|
533
|
+
### Panel mount / dispose
|
|
210
534
|
|
|
211
|
-
|
|
535
|
+
`@apps-in-toss/devtools/panel`을 import하면 DOM ready 시 자동으로 마운트됩니다. 마운트는 idempotent — 같은 페이지에 여러 번 import되거나 `mount()`를 다시 불러도 토글 버튼은 하나만 떠 있습니다.
|
|
212
536
|
|
|
213
|
-
|
|
214
|
-
| -------- | ------------------------------------------------------------------------------------------------------- |
|
|
215
|
-
| Apple | iPhone SE (3rd gen), iPhone 15 Pro, iPhone 16e, iPhone 17, iPhone Air, iPhone 17 Pro, iPhone 17 Pro Max |
|
|
216
|
-
| Samsung | Galaxy S26, S26+, S26 Ultra, Z Flip7, Z Fold7 (folded / unfolded) |
|
|
217
|
-
| 기타 | Custom (width/height 직접 입력, 1–4096으로 클램프), None (기본) |
|
|
537
|
+
HMR이나 SPA 라우팅에서 패널을 명시적으로 떼어내야 하는 경우 `disposePanel()`을 사용하세요:
|
|
218
538
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
539
|
+
```ts
|
|
540
|
+
import { disposePanel, mount } from "@apps-in-toss/devtools/panel";
|
|
541
|
+
|
|
542
|
+
disposePanel(); // 토글 / 패널 / inject된 <style> / 모든 listener 제거.
|
|
543
|
+
// 호출 전이거나 두 번 호출해도 안전.
|
|
544
|
+
mount(); // 깨끗한 상태로 다시 마운트. 중복 <style>·listener 없음.
|
|
545
|
+
```
|
|
224
546
|
|
|
225
|
-
|
|
547
|
+
내부에서 `disposeViewport()`도 함께 호출하므로 viewport 시뮬레이션도 함께 원복됩니다.
|
|
548
|
+
|
|
549
|
+
## Device simulation (Viewport 탭)
|
|
550
|
+
|
|
551
|
+
데스크탑 브라우저에서 모바일 미니앱을 개발할 때, 실제 디바이스 해상도/safe area/노치/홈 인디케이터/앱인토스 nav bar를 반영해 레이아웃을 검증할 수 있습니다.
|
|
552
|
+
|
|
553
|
+
### 프리셋 (2026)
|
|
554
|
+
|
|
555
|
+
| 카테고리 | 기기 |
|
|
556
|
+
| -------- | ---------------------------------------------------------------------------------------- |
|
|
557
|
+
| Apple | iPhone SE (3rd gen), iPhone 16e, iPhone 17, iPhone Air, iPhone 17 Pro, iPhone 17 Pro Max |
|
|
558
|
+
| Samsung | Galaxy S26, S26+, S26 Ultra, Z Flip7, Z Fold7 (folded / unfolded) |
|
|
559
|
+
| 기타 | Custom (width/height 직접 입력), None (기본) |
|
|
560
|
+
|
|
561
|
+
> **Galaxy S26 시리즈** (2026-03-11 출시)의 CSS viewport 값은 [phone-simulator.com](https://www.phone-simulator.com/)의 측정치를 사용합니다. safe area insets는 토스 호스트 환경 실측 전까지 S25 값을 잠정 사용 — 픽셀 단위 정확도가 필요한 QA는 실 기기에서 한 번 더 확인하세요.
|
|
562
|
+
>
|
|
563
|
+
> iPhone 17 시리즈는 2025-09에 출시되어 실제 spec 기반입니다.
|
|
564
|
+
|
|
565
|
+
각 프리셋은 다음 정보를 포함합니다:
|
|
566
|
+
|
|
567
|
+
- **CSS viewport** (portrait `width × height`)
|
|
568
|
+
- **DPR** (devicePixelRatio: 2, 3, 3.5 등)
|
|
569
|
+
- **Notch** 종류 (`none` / `notch` / `dynamic-island` / `punch-hole-center`)
|
|
570
|
+
- **notch inset** (OS 노치/status bar — landscape 좌우 인셋 + 시각 노치용, 기기별)
|
|
571
|
+
- **nav bar height** (토스 호스트 nav bar — `partner` portrait의 `SafeAreaInsets.get().top`, 실측 54px)
|
|
572
|
+
- **home-indicator inset** (`safeAreaBottom`, 기기별)
|
|
573
|
+
|
|
574
|
+
### Orientation
|
|
575
|
+
|
|
576
|
+
- **auto** (기본) — Panel이 강제하지 않음. 앱의 `setDeviceOrientation` 호출이 별도 필드(`appOrientation`)에 기록되어 effective orientation 결정에 쓰입니다. 같은 앱이 여러 번 호출해도 매번 정상 반영됩니다.
|
|
577
|
+
- **portrait / landscape** — Panel이 override. 앱의 `setDeviceOrientation` 호출은 무시되고 `console.warn`으로 알림.
|
|
578
|
+
|
|
579
|
+
Landscape로 전환하면:
|
|
580
|
+
|
|
581
|
+
- CSS viewport width/height가 swap됩니다.
|
|
582
|
+
- iPhone(notch/Dynamic Island) 프리셋은 safe area의 top이 0이 되고, **Notch side** 토글(left/right, default left)에 따라 한쪽 변에만 인셋이 생깁니다 (실 기기 동작과 일치).
|
|
583
|
+
- Android(punch-hole) 프리셋은 status bar가 top에 유지됩니다.
|
|
584
|
+
|
|
585
|
+
### Frame + 노치 + 홈 인디케이터 + 앱인토스 nav bar
|
|
586
|
+
|
|
587
|
+
**Show frame** 토글을 켜면:
|
|
588
|
+
|
|
589
|
+
- 디바이스 베젤을 모사하는 border-radius + box-shadow
|
|
590
|
+
- Notch / Dynamic Island / punch-hole 오버레이 — WebView(body) **밖** 위쪽 status bar 영역에 그립니다. 실기기에서 OS 노치는 WebView 뷰포트 바깥이라 `env(safe-area-inset-top)`이 0이기 때문입니다.
|
|
591
|
+
- 홈 인디케이터 pill (iPhone 등 `safeAreaBottom > 0` 디바이스에 한정, body 하단에 배치)
|
|
592
|
+
- 앱 이름은 `aitState.brand.displayName`을 사용 (Environment 탭에서 변경 가능, 자동 갱신)
|
|
593
|
+
- 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출 — 실제 SDK 이벤트 플러밍을 패널에서 직접 검증할 수 있습니다.
|
|
594
|
+
|
|
595
|
+
**Show Apps in Toss nav bar** 토글(기본 on)을 켜면:
|
|
596
|
+
|
|
597
|
+
- 토스 호스트의 상단 nav bar를 54px 높이로 오버레이. `Nav bar type`에 따라 모양이 다릅니다:
|
|
598
|
+
- `partner` (비게임 기본): 흰 배경 + 뒤로가기 / 앱 아이콘·이름 / ⋯ / ×. 콘텐츠를 nav bar 높이만큼 아래로 밀어냅니다.
|
|
599
|
+
- `game`: 투명 배경 + ⋯ / × 만. 게임 캔버스 위에 떠 있어 콘텐츠를 밀어내지 않습니다 — 인게임 화면은 full-screen이 [출시 요건](https://developers-apps-in-toss.toss.im/checklist/app-game.html).
|
|
600
|
+
- nav bar는 WebView(body) 좌표계의 **최상단(top 0)**에 앉습니다. 실기기에서 OS 노치는 WebView 밖(위쪽 status bar)이라 `env(safe-area-inset-top)`이 0이고, 콘텐츠 영역은 nav bar 바로 아래(= `SafeAreaInsets.get().top`)에서 시작하기 때문입니다 — 시뮬레이터는 이 스택(노치 status bar → nav bar → 콘텐츠)을 그대로 재현합니다.
|
|
601
|
+
- `partner` WebView에서는 **이 nav bar 높이가 곧 `SafeAreaInsets.get().top`** 입니다. iPhone 15 Pro on-device relay 실측([devtools#190](https://github.com/apps-in-toss-community/devtools/issues/190))에서 `env(safe-area-inset-top)`은 0(노치는 WebView 뷰포트 밖)이고 `SafeAreaInsets.get().top`은 54px이었으며, 그 54px가 호스트 nav bar 높이였습니다. 즉 `partner` 앱은 콘텐츠 상단을 `insets.top`만큼만 보정하면 됩니다(별도 `+ navBarHeight` 불필요). `game`은 콘텐츠를 밀어내지 않으므로 top inset이 0입니다. 이 54px는 iOS partner에서 실측됐고 Android nav bar 높이는 같은 값을 잠정 적용합니다. SDK의 `webViewProps.type`은 `partner` / `game` 외에 `external`도 있습니다 (현재 패널은 앞 둘만 시뮬레이션).
|
|
602
|
+
|
|
603
|
+
### 콘솔에서 직접 조작
|
|
226
604
|
|
|
227
605
|
```js
|
|
228
|
-
|
|
606
|
+
// iPhone 17 Pro 세로 + 프레임 켜기
|
|
607
|
+
__ait.patch("viewport", {
|
|
608
|
+
preset: "iphone-17-pro",
|
|
609
|
+
orientation: "auto",
|
|
610
|
+
frame: true,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// Landscape 강제 (앱의 setDeviceOrientation 호출은 무시됨)
|
|
229
614
|
__ait.patch("viewport", { orientation: "landscape" });
|
|
615
|
+
|
|
616
|
+
// Landscape 시 노치 위치 (iOS 기본 'left')
|
|
617
|
+
__ait.patch("viewport", { landscapeSide: "right" });
|
|
618
|
+
|
|
619
|
+
// Custom 크기 (1 ≤ value ≤ 4096으로 자동 클램프)
|
|
230
620
|
__ait.patch("viewport", {
|
|
231
621
|
preset: "custom",
|
|
232
622
|
customWidth: 360,
|
|
233
623
|
customHeight: 740,
|
|
234
624
|
});
|
|
235
|
-
__ait.patch("viewport", { aitNavBarType: "game" });
|
|
236
|
-
__ait.patch("viewport", { preset: "none" }); // 해제
|
|
237
|
-
```
|
|
238
625
|
|
|
239
|
-
|
|
626
|
+
// 앱인토스 nav bar 숨기기 (순수 뷰포트만 보고 싶을 때)
|
|
627
|
+
__ait.patch("viewport", { aitNavBar: false });
|
|
240
628
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { disposePanel, mount } from "@apps-in-toss/devtools/panel";
|
|
629
|
+
// Nav bar 변형 토글 ('partner' = 흰 배경 + 아이콘/이름, 'game' = 투명 배경 + ⋯/× 만)
|
|
630
|
+
__ait.patch("viewport", { aitNavBarType: "game" });
|
|
245
631
|
|
|
246
|
-
|
|
247
|
-
|
|
632
|
+
// 해제
|
|
633
|
+
__ait.patch("viewport", { preset: "none" });
|
|
248
634
|
```
|
|
249
635
|
|
|
250
|
-
|
|
636
|
+
### Status 패널
|
|
251
637
|
|
|
252
|
-
|
|
638
|
+
Viewport 탭 하단에 현재 적용된 값을 실시간으로 보여줍니다:
|
|
253
639
|
|
|
254
|
-
|
|
640
|
+
- **CSS / physical**: `402×874@3x | 1206×2622 portrait (auto)`
|
|
641
|
+
- **Safe area**: `T54 R0 B34 L0` (partner nav bar 기준 — top이 곧 nav bar 높이)
|
|
642
|
+
- **AIT nav bar**: `54px → SafeArea top · partner`
|
|
255
643
|
|
|
256
|
-
|
|
257
|
-
| -------- | ------------------------------------------------- | ---------------------------- |
|
|
258
|
-
| `mock` | 상태에 저장된 더미 데이터 반환 | 자동화 테스트, 고정 시나리오 |
|
|
259
|
-
| `web` | 브라우저 네이티브 API 사용 (Geolocation, File 등) | 실제 브라우저 기능 확인 |
|
|
260
|
-
| `prompt` | 패널이 Device 탭을 열고 사용자 입력을 기다림 | 수동 QA |
|
|
644
|
+
### 영속성 + 기술 세부
|
|
261
645
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
646
|
+
- 상태는 sessionStorage(`__ait_viewport`)에 저장되어 페이지 reload 시 복원됩니다.
|
|
647
|
+
- 프리셋 선택 시 `aitState.safeAreaInsets`도 자동 업데이트 → SDK의 `SafeAreaInsets.get()` / `.subscribe()`가 따라갑니다.
|
|
648
|
+
- 뷰포트는 `document.body`에 `max-width`/`max-height` + `margin:auto`로 적용됩니다. iframe을 쓰지 않으므로 앱 JS/CSS가 그대로 실행되고, 콘솔·DevTools도 정상 접근 가능합니다.
|
|
649
|
+
- body에 `isolation: isolate`를 적용해 노치/nav bar/홈 인디케이터의 z-index가 stacking context 밖으로 새지 않습니다 (DevTools 패널이 그 위에 떠 있음).
|
|
650
|
+
- 패널을 동적으로 제거하고 싶다면 `disposeViewport()`를 export로 제공합니다.
|
|
651
|
+
- **기기 프리셋이 active일 때(= `none`/`custom` 아님) 브라우저 특성을 그 기기와 정합시킵니다**: `navigator.userAgent`(토스 WebView 형태 — `… AppsInToss TossApp/<appVersion>`), `navigator.platform`, `window.devicePixelRatio`(preset DPR), `screen.width`/`height`(CSS px × DPR), 그리고 `getPlatformOS()`가 읽는 `platform`(Apple→`ios` / Galaxy→`android`)을 모두 그 기기 값으로 override합니다. 특정 기기 frame을 제공하는 이상 UA/DPR만 호스트 데스크톱 값으로 남으면 비일관적이기 때문입니다. `none`/`custom`에선 override를 걸지 않아 일반 dev의 호스트 환경을 건드리지 않습니다.
|
|
266
652
|
|
|
267
|
-
|
|
268
|
-
__ait.patch("deviceModes", { camera: "web", location: "prompt" });
|
|
269
|
-
```
|
|
653
|
+
### Known limitations
|
|
270
654
|
|
|
271
|
-
|
|
655
|
+
- **Body가 스크롤 컨테이너가 됩니다** — 뷰포트 활성화 중에는 스크롤이 `window`가 아닌 `document.body`에서 발생합니다. `window.addEventListener('scroll', ...)`나 root에 붙은 `IntersectionObserver`는 실 디바이스와 다른 동작을 보일 수 있습니다. 미니앱 코드에서 스크롤을 다룬다면 `body`도 함께 검증하세요.
|
|
656
|
+
- **추정 safe area** — Galaxy S26 시리즈는 출시 spec(phone-simulator.com 측정치) 기반이지만 safe area는 S25 값을 잠정 사용합니다 — 픽셀 단위 정확도가 필요한 QA는 실 기기 확인을 권장합니다.
|
|
657
|
+
- **기기 특성 override는 JS 읽기값만 바꿉니다** — 프리셋이 거는 `navigator.userAgent`·`devicePixelRatio`·`screen.*` override는 page-JS가 읽는 값만 그 기기로 보이게 합니다. 실 CSS media query(`@media (resolution)`, `@media (pointer)`), 실제 터치 이벤트, 엔진 레벨 레이아웃 단위는 호스트 브라우저 값이 그대로입니다 (프리셋 frame이 시각적 width/height는 이미 강제하므로 레이아웃은 근사됩니다). 픽셀·입력 단위까지 완전한 emulation이 필요하면 Chrome DevTools device-mode(또는 CDP)를 쓰세요.
|
|
272
658
|
|
|
273
659
|
## `window.__ait` 콘솔 API
|
|
274
660
|
|
|
275
|
-
브라우저 콘솔에서 mock 상태를 직접
|
|
661
|
+
브라우저 콘솔에서 `window.__ait`(또는 `__ait`)로 mock 상태를 직접 제어할 수 있습니다:
|
|
276
662
|
|
|
277
663
|
```js
|
|
278
|
-
// 조회
|
|
279
|
-
__ait.state; // 전체 상태
|
|
280
|
-
__ait.state.
|
|
281
|
-
__ait.state.
|
|
664
|
+
// 현재 상태 조회
|
|
665
|
+
__ait.state; // 전체 상태 객체
|
|
666
|
+
__ait.state.platform; // 'ios' 또는 'android'
|
|
667
|
+
__ait.state.auth.isLoggedIn; // 로그인 상태
|
|
668
|
+
__ait.state.deviceModes; // 각 API의 현재 모드
|
|
282
669
|
|
|
283
|
-
//
|
|
670
|
+
// 상태 업데이트 (얕은 병합)
|
|
284
671
|
__ait.update({ platform: "android", locale: "en-US" });
|
|
672
|
+
__ait.update({ networkStatus: "OFFLINE" });
|
|
673
|
+
|
|
674
|
+
// 중첩 상태 업데이트
|
|
285
675
|
__ait.patch("permissions", { camera: "denied" });
|
|
676
|
+
__ait.patch("deviceModes", { location: "web" });
|
|
286
677
|
__ait.patch("iap", { nextResult: "USER_CANCELED" });
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
__ait.update({ networkStatus: "OFFLINE" });
|
|
291
|
-
__ait.patch("iap", { nextResult: "NETWORK_ERROR" });
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// 실패-모드 다이얼 — 실기기에서만 나던 거부를 로컬에서 재현
|
|
295
|
-
__ait.patch("failureModes", { loadAdMob: "PLACEMENT_ID_FETCH_FAILED" });
|
|
678
|
+
__ait.patch("failureModes", { loadAdMob: "PLACEMENT_ID_FETCH_FAILED" }); // 실기기 광고 지면 조회 실패 재현
|
|
679
|
+
// 네이티브 브리지의 per-method rate limit 재현 — 아래 메서드를 1초 안에 재호출하면 APP_BRIDGE_THROTTLED로 거부된다.
|
|
680
|
+
// 훅이 삽입된 메서드: getClipboardText · setClipboardText · getCurrentLocation · loadAppsInTossAdMob · loadFullScreenAd
|
|
296
681
|
__ait.patch("failureModes", {
|
|
297
682
|
throttled: { methods: ["getCurrentLocation"], intervalMs: 1000 },
|
|
298
683
|
});
|
|
299
684
|
|
|
300
|
-
// 이벤트
|
|
685
|
+
// 이벤트 트리거
|
|
301
686
|
__ait.trigger("backEvent");
|
|
302
|
-
__ait.
|
|
303
|
-
__ait.reset(); // deviceId는 유지
|
|
304
|
-
const unsubscribe = __ait.subscribe(() => console.log(__ait.state));
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
`failureModes`는 값을 설정한 API만 거부하고 나머지는 기존대로 성공해요 — 다이얼을 쓰지 않으면 동작 변화가 전혀 없어요. 실패 envelope의 모양은 `failureModes.sdkLine`(`'2.x'` 기본 / `'3.x'`)을 따라요.
|
|
308
|
-
|
|
309
|
-
## Mock이 커버하는 SDK 표면
|
|
310
|
-
|
|
311
|
-
mock은 SDK의 **런타임 export 전수**를 덮는 것을 목표로 하고, `check:sdk-exports`가 이를 CI에서 강제해요. 이 스크립트는 두 단계로 검사해요.
|
|
687
|
+
__ait.trigger("homeEvent");
|
|
312
688
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
현재 통과 기준선은 2.x 라인 export 74종 / 도메인 객체 19개, 3.x 라인 export 92종 / 도메인 객체 34개예요.
|
|
689
|
+
// 분석 이벤트 수동 기록
|
|
690
|
+
__ait.logAnalytics({ type: "click", params: { button: "purchase" } });
|
|
317
691
|
|
|
318
|
-
|
|
692
|
+
// 상태 초기화 (deviceId는 유지됨)
|
|
693
|
+
__ait.reset();
|
|
319
694
|
|
|
695
|
+
// 상태 변경 구독
|
|
696
|
+
const unsubscribe = __ait.subscribe(() => {
|
|
697
|
+
console.log("상태 변경됨:", __ait.state);
|
|
698
|
+
});
|
|
699
|
+
unsubscribe(); // 구독 해제
|
|
320
700
|
```
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
701
|
+
|
|
702
|
+
## Mock API 목록
|
|
703
|
+
|
|
704
|
+
### 인증/로그인
|
|
705
|
+
|
|
706
|
+
| API | Mock 동작 |
|
|
707
|
+
| --------------------------------- | ------------------------------------------------------- |
|
|
708
|
+
| `appLogin` | `{ authorizationCode, referrer }` 반환 |
|
|
709
|
+
| `getIsTossLoginIntegratedService` | state의 `isTossLoginIntegrated` 반환 |
|
|
710
|
+
| `getUserKeyForGame` | `{ hash, type: 'HASH' }` 반환 (비로그인 시 `undefined`) |
|
|
711
|
+
| `appsInTossSignTossCert` | 콘솔 로그만 출력 (no-op) |
|
|
712
|
+
|
|
713
|
+
### 화면/네비게이션
|
|
714
|
+
|
|
715
|
+
| API | Mock 동작 |
|
|
716
|
+
| --------------------------- | ---------------------------------------------- |
|
|
717
|
+
| `closeView` | `window.history.back()` 호출 |
|
|
718
|
+
| `openURL` | `window.open()`으로 새 탭 |
|
|
719
|
+
| `share` | `navigator.share()` 사용 (미지원 시 콘솔 출력) |
|
|
720
|
+
| `getTossShareLink` | `https://toss.im/share/mock{path}` 반환 |
|
|
721
|
+
| `setIosSwipeGestureEnabled` | 콘솔 로그 (no-op) |
|
|
722
|
+
| `setDeviceOrientation` | 콘솔 로그 (no-op) |
|
|
723
|
+
| `setScreenAwakeMode` | `{ enabled }` 반환 |
|
|
724
|
+
| `setSecureScreen` | `{ enabled }` 반환 |
|
|
725
|
+
| `requestReview` | no-op (`.isSupported()` 메서드 포함) |
|
|
726
|
+
|
|
727
|
+
### 환경 정보
|
|
728
|
+
|
|
729
|
+
| API | Mock 동작 |
|
|
730
|
+
| --------------------------- | ------------------------------------------------------------------ |
|
|
731
|
+
| `getPlatformOS` | state의 platform 반환 (기본: `'ios'`) |
|
|
732
|
+
| `getOperationalEnvironment` | state의 environment 반환 (기본: `'sandbox'`) |
|
|
733
|
+
| `getTossAppVersion` | state의 appVersion 반환 (기본: `'5.240.0'`) |
|
|
734
|
+
| `isMinVersionSupported` | 시맨틱 버전 비교 수행 |
|
|
735
|
+
| `getSchemeUri` | state의 schemeUri 또는 `window.location.pathname` |
|
|
736
|
+
| `getLocale` | state의 locale 반환 (기본: `'ko-KR'`) |
|
|
737
|
+
| `getDeviceId` | localStorage에 저장된 고유 UUID 반환 |
|
|
738
|
+
| `getGroupId` | state의 groupId 반환 |
|
|
739
|
+
| `getNetworkStatus` | 모드에 따라 state 또는 브라우저 API 사용 |
|
|
740
|
+
| `getServerTime` | `Date.now()` 반환 |
|
|
741
|
+
| `env.getDeploymentId` | state의 deploymentId 반환 |
|
|
742
|
+
| `getAppsInTossGlobals` | `{ deploymentId, brandDisplayName, brandIcon, brandPrimaryColor }` |
|
|
743
|
+
|
|
744
|
+
### Safe Area
|
|
745
|
+
|
|
746
|
+
| API | Mock 동작 |
|
|
747
|
+
| ------------------------------------- | ----------------------------------------------------------------------------- |
|
|
748
|
+
| `SafeArea.get` / `SafeAreaInsets.get` | `{ top, bottom, left, right }` 동기 반환 (3.x 이름 + 2.x alias) |
|
|
749
|
+
| `SafeAreaInsets.subscribe` | 상태 변경 시 콜백 호출, unsubscribe 함수 반환 |
|
|
750
|
+
| `getSafeAreaInsets` | 2.x facade는 실측대로 Promise, 3.x facade는 inset 객체 동기 반환 (deprecated) |
|
|
751
|
+
|
|
752
|
+
### 디바이스 기능
|
|
753
|
+
|
|
754
|
+
| API | Mock 동작 |
|
|
755
|
+
| ----------------------------------------------- | -------------------------------------------------------------------- |
|
|
756
|
+
| `Storage.getItem/setItem/removeItem/clearItems` | localStorage에 `__ait_storage:` prefix로 저장 |
|
|
757
|
+
| `getCurrentLocation` | 모드별: mock(state 좌표), web(Geolocation API), prompt(Panel 입력) |
|
|
758
|
+
| `startUpdateLocation` | mock(랜덤 좌표 변동), web(watchPosition), prompt(반복 입력) |
|
|
759
|
+
| `openCamera` | mock(더미 이미지), web(파일 선택기), prompt(Panel 파일 입력) |
|
|
760
|
+
| `fetchAlbumPhotos` | mock(더미 이미지 배열), web(파일 다중 선택), prompt(Panel 파일 입력) |
|
|
761
|
+
| `fetchContacts` | 페이지네이션 지원 mock 연락처 반환, `query.contains` 검색 |
|
|
762
|
+
| `getClipboardText` / `setClipboardText` | mock(state 저장) 또는 web(Clipboard API) |
|
|
763
|
+
| `generateHapticFeedback` | 콘솔 로그 + analytics 기록 |
|
|
764
|
+
| `saveBase64Data` | anchor 엘리먼트로 파일 다운로드 |
|
|
765
|
+
|
|
766
|
+
### IAP/결제
|
|
767
|
+
|
|
768
|
+
| API | Mock 동작 |
|
|
769
|
+
| ------------------------------------- | ---------------------------------------------------------------- |
|
|
770
|
+
| `IAP.createOneTimePurchaseOrder` | 300ms 딜레이 후 state의 `nextResult`에 따라 성공/실패 시뮬레이션 |
|
|
771
|
+
| `IAP.createSubscriptionPurchaseOrder` | 위와 동일한 흐름 |
|
|
772
|
+
| `IAP.getProductItemList` | state의 상품 목록 반환 |
|
|
773
|
+
| `IAP.getPendingOrders` | 대기 중 주문 목록 |
|
|
774
|
+
| `IAP.getCompletedOrRefundedOrders` | 완료/환불 주문 목록 |
|
|
775
|
+
| `IAP.completeProductGrant` | 대기 → 완료 주문 이동 |
|
|
776
|
+
| `IAP.getSubscriptionInfo` | 활성 구독 mock (30일 만료, 자동 갱신) |
|
|
777
|
+
| `checkoutPayment` | 300ms 딜레이 후 state의 결제 결과 반환 (TossPay) |
|
|
778
|
+
|
|
779
|
+
**IAP 구매 시뮬레이션 흐름:**
|
|
780
|
+
|
|
781
|
+
1. `IAP.createOneTimePurchaseOrder()` 호출
|
|
782
|
+
2. 300ms 딜레이 (결제 UI 시뮬레이션)
|
|
783
|
+
3. `state.iap.nextResult` 확인 → `'success'`가 아니면 `onError` 호출
|
|
784
|
+
4. 성공 시 `processProductGrant` 콜백 실행 → 실패하면 `'PRODUCT_NOT_GRANTED_BY_PARTNER'` 에러
|
|
785
|
+
5. 모두 성공하면 `completedOrders`에 기록, `onEvent`로 주문 결과 전달
|
|
786
|
+
|
|
787
|
+
### 광고
|
|
788
|
+
|
|
789
|
+
| API | Mock 동작 |
|
|
790
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------- |
|
|
791
|
+
| `GoogleAdMob.loadAppsInTossAdMob` | 200ms 후 `loaded` 이벤트 |
|
|
792
|
+
| `GoogleAdMob.showAppsInTossAdMob` | 50ms~1.5s에 걸쳐 requested→show→impression→reward→dismissed 이벤트 순차 발행 |
|
|
793
|
+
| `GoogleAdMob.isAppsInTossAdMobLoaded` | 로드 여부 boolean 반환 |
|
|
794
|
+
| `TossAds.initialize/attach/attachBanner` | 회색 플레이스홀더 div 렌더링 |
|
|
795
|
+
| `TossAds.destroy/destroyAll` | no-op |
|
|
796
|
+
| `loadFullScreenAd` / `showFullScreenAd` | GoogleAdMob과 유사한 흐름 |
|
|
797
|
+
|
|
798
|
+
> 실패 다이얼(`failureModes.loadAdMob`, 패널 `forceNoFill`)을 설정하지 않으면 위 이벤트들은 매번 동일하게 발화합니다 — mock은 `adGroupId`를 판정에 쓰지 않고, 서버 측 지면(placement) 조회 단계를 모델링하지 않습니다. 실기기는 광고가 존재하기도 전에 지면 조회 자체가 실패해(예: `PLACEMENT_ID_FETCH_FAILED`) 즉시 거부될 수 있으므로, mock의 `loaded` 발화가 "실기기에서 광고가 실제로 나간다"는 신호는 아닙니다. 로컬에서 이 실패를 재현하려면: `__ait.patch('failureModes', { loadAdMob: 'PLACEMENT_ID_FETCH_FAILED' })`.
|
|
799
|
+
|
|
800
|
+
### 이벤트
|
|
801
|
+
|
|
802
|
+
| API | Mock 동작 |
|
|
803
|
+
| -------------------------------------------- | ------------------------------------------------------- |
|
|
804
|
+
| `graniteEvent.addEventListener` | `__ait:backEvent`, `__ait:homeEvent` 커스텀 이벤트 수신 |
|
|
805
|
+
| `appsInTossEvent.addEventListener` | no-op |
|
|
806
|
+
| `tdsEvent.addEventListener` | `__ait:navigationAccessoryEvent` 수신 |
|
|
807
|
+
| `onVisibilityChangedByTransparentServiceWeb` | `document.visibilitychange` 이벤트 위임 |
|
|
808
|
+
|
|
809
|
+
### 분석
|
|
810
|
+
|
|
811
|
+
| API | Mock 동작 |
|
|
812
|
+
| ----------------------------------- | ----------------------------------------------------- |
|
|
813
|
+
| `Analytics.screen/impression/click` | analyticsLog에 타입별 기록, Panel에서 실시간 확인 |
|
|
814
|
+
| `eventLog` | `log_name`, `log_type`, `params`로 커스텀 이벤트 기록 |
|
|
815
|
+
|
|
816
|
+
### 게임/프로모션
|
|
817
|
+
|
|
818
|
+
| API | Mock 동작 |
|
|
819
|
+
| ---------------------------------- | ---------------------------------------------- |
|
|
820
|
+
| `grantPromotionReward` | 타임스탬프 기반 mock key 반환 |
|
|
821
|
+
| `grantPromotionRewardForGame` | 위와 동일 |
|
|
822
|
+
| `submitGameCenterLeaderBoardScore` | state에 점수 추가, `{ statusCode: 'SUCCESS' }` |
|
|
823
|
+
| `getGameCenterGameProfile` | mock 프로필 반환 (없으면 `PROFILE_NOT_FOUND`) |
|
|
824
|
+
| `openGameCenterLeaderboard` | 콘솔 로그 (no-op) |
|
|
825
|
+
| `contactsViral` | 500ms 후 close 이벤트 발행 |
|
|
826
|
+
|
|
827
|
+
### 권한
|
|
828
|
+
|
|
829
|
+
| API | Mock 동작 |
|
|
830
|
+
| ---------------------- | ----------------------------------------------------- |
|
|
831
|
+
| `getPermission` | state의 권한 상태 반환 (allowed/denied/notDetermined) |
|
|
832
|
+
| `openPermissionDialog` | 상태를 `allowed`로 변경 |
|
|
833
|
+
| `requestPermission` | `openPermissionDialog`에 위임 |
|
|
834
|
+
|
|
835
|
+
> 권한이 필요한 함수(openCamera, getCurrentLocation 등)는 `withPermission()`으로 래핑되어 `.getPermission()`, `.openPermissionDialog()` 메서드가 자동 부착됩니다.
|
|
836
|
+
|
|
837
|
+
### 파트너
|
|
838
|
+
|
|
839
|
+
| API | Mock 동작 |
|
|
840
|
+
| ------------------------------- | ----------------- |
|
|
841
|
+
| `partner.addAccessoryButton` | 콘솔 로그 (no-op) |
|
|
842
|
+
| `partner.removeAccessoryButton` | 콘솔 로그 (no-op) |
|
|
324
843
|
|
|
325
844
|
## 테스트에서의 활용
|
|
326
845
|
|
|
327
|
-
|
|
846
|
+
vitest/jest에서 mock 라이브러리를 직접 import하여 테스트할 수 있습니다.
|
|
328
847
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
848
|
+
> mock 함수들이 `window`, `document`, `localStorage` 등 브라우저 API를 사용하므로 **jsdom 환경**이 필요합니다.
|
|
849
|
+
>
|
|
850
|
+
> ```ts
|
|
851
|
+
> // vitest.config.ts
|
|
852
|
+
> import { defineConfig } from "vitest/config";
|
|
853
|
+
> export default defineConfig({ test: { environment: "jsdom" } });
|
|
854
|
+
> ```
|
|
334
855
|
|
|
335
856
|
```ts
|
|
336
|
-
import {
|
|
857
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
337
858
|
import {
|
|
338
|
-
|
|
859
|
+
appLogin,
|
|
860
|
+
Storage,
|
|
861
|
+
getCurrentLocation,
|
|
339
862
|
getNetworkStatus,
|
|
340
|
-
IAP,
|
|
341
863
|
openCamera,
|
|
342
|
-
|
|
864
|
+
IAP,
|
|
343
865
|
} from "@apps-in-toss/devtools/mock";
|
|
866
|
+
import { aitState } from "@apps-in-toss/devtools/mock";
|
|
344
867
|
|
|
345
868
|
beforeEach(() => {
|
|
346
|
-
aitState.reset();
|
|
869
|
+
aitState.reset(); // 매 테스트 전 상태 초기화
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// 인증 테스트
|
|
873
|
+
it("appLogin은 authorizationCode를 반환한다", async () => {
|
|
874
|
+
const result = await appLogin();
|
|
875
|
+
expect(result.authorizationCode).toBeDefined();
|
|
347
876
|
});
|
|
348
877
|
|
|
349
|
-
|
|
878
|
+
// 상태를 세팅하고 함수 호출
|
|
879
|
+
it("오프라인 상태에서 네트워크 조회", async () => {
|
|
350
880
|
aitState.update({ networkStatus: "OFFLINE" });
|
|
351
|
-
await
|
|
881
|
+
const status = await getNetworkStatus();
|
|
882
|
+
expect(status).toBe("OFFLINE");
|
|
352
883
|
});
|
|
353
884
|
|
|
354
|
-
|
|
885
|
+
// 권한 denied 시나리오
|
|
886
|
+
it("카메라 권한이 denied면 에러를 던진다", async () => {
|
|
355
887
|
aitState.patch("permissions", { camera: "denied" });
|
|
356
888
|
await expect(openCamera()).rejects.toThrow();
|
|
357
889
|
});
|
|
358
890
|
|
|
359
|
-
|
|
891
|
+
// IAP 실패 시나리오 (fake timers 필요)
|
|
892
|
+
it("구매 취소 시 onError가 호출된다", async () => {
|
|
360
893
|
vi.useFakeTimers();
|
|
361
894
|
aitState.patch("iap", { nextResult: "USER_CANCELED" });
|
|
362
895
|
const onError = vi.fn();
|
|
@@ -370,79 +903,180 @@ it("구매가 취소되면 onError가 호출된다", async () => {
|
|
|
370
903
|
vi.useRealTimers();
|
|
371
904
|
});
|
|
372
905
|
|
|
373
|
-
|
|
906
|
+
// Storage 테스트
|
|
907
|
+
it("Storage에 값을 저장하고 읽을 수 있다", async () => {
|
|
374
908
|
await Storage.setItem("key1", "value1");
|
|
375
|
-
await
|
|
909
|
+
const result = await Storage.getItem("key1");
|
|
910
|
+
expect(result).toBe("value1");
|
|
376
911
|
});
|
|
377
912
|
```
|
|
378
913
|
|
|
379
|
-
|
|
914
|
+
## 실기기 테스트 러너 (`debugger-test`)
|
|
380
915
|
|
|
381
|
-
|
|
916
|
+
위 "테스트에서의 활용"이 데스크톱 jsdom에서 mock을 검증하는 경로라면, 같은 스타일의 테스트를 **실기기 토스 앱 WebView(환경 3)에서 실 SDK로** 돌리는 러너는 `@ait-co/debugger`로 이동했습니다(#818) — bin도 `devtools-test`에서 `debugger-test`로 바뀌었습니다.
|
|
382
917
|
|
|
383
|
-
|
|
918
|
+
```bash
|
|
919
|
+
pnpm add -D @ait-co/debugger
|
|
920
|
+
pnpm exec debugger-test 'src/**/*.ait.test.ts' \
|
|
921
|
+
--scheme-url "intoss-private://my-mini-app?_deploymentId=<uuid>" \
|
|
922
|
+
--cell-sdk-line 3.x --cell-platform ios --report-dir .ait-report
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
전체 플래그·QR 스캔 절차·산출물 레퍼런스는 이제 `@ait-co/debugger` 패키지가 정본입니다 — `debugger-test --help`를 참고하세요. 미니앱 entry에 필요한 한 줄만 이 패키지 쪽에 남아 있습니다: `import '@ait-co/debug-console/auto'` ([위 섹션](#on-device-디버깅-한-줄-설정)).
|
|
384
926
|
|
|
385
|
-
|
|
386
|
-
- `POST` — 패널이 자동으로 호출해요. 파싱 불가능한 본문은 `400`.
|
|
387
|
-
- CORS는 전면 허용이에요 (로컬 에이전트가 읽을 수 있어야 하므로).
|
|
927
|
+
## SDK 업데이트 대응
|
|
388
928
|
|
|
389
|
-
|
|
929
|
+
devtools는 [`@apps-in-toss/web-framework`](https://www.npmjs.com/package/@apps-in-toss/web-framework)를 추적하고, [`sdk-example`](https://github.com/apps-in-toss-community/sdk-example)은 원본 SDK와 devtools를 모두 추적한다. 즉 새 SDK 버전이 나오면 (1) devtools가 mock/타입 시그니처를 따라잡고 → (2) sdk-example이 양쪽 새 버전을 동시에 반영하는 흐름. devtools 단독 PR이 sdk-example을 깨뜨리면 양쪽을 함께 본다.
|
|
390
930
|
|
|
391
|
-
|
|
931
|
+
세 가지 메커니즘으로 SDK 변경에 안전하게 대응합니다:
|
|
392
932
|
|
|
393
|
-
|
|
933
|
+
### 1. 컴파일 타임 타입 검증 (`__typecheck.ts`)
|
|
394
934
|
|
|
395
|
-
|
|
935
|
+
`src/__typecheck.ts`에서 mock의 주요 export가 원본 SDK와 타입 호환되는지 검증합니다. SDK 시그니처가 변경되면 `pnpm typecheck`에서 즉시 에러가 발생합니다.
|
|
396
936
|
|
|
397
937
|
```ts
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
938
|
+
type Assert<TMock, TOriginal> = TMock extends TOriginal ? true : never;
|
|
939
|
+
type _AppLogin = Assert<typeof Mock.appLogin, typeof Original.appLogin>;
|
|
940
|
+
// 40+ 타입 호환성 assertion
|
|
941
|
+
```
|
|
401
942
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
943
|
+
### 2. Proxy 트립와이어 (런타임 차단)
|
|
944
|
+
|
|
945
|
+
`createMockProxy()`는 미구현 API 접근 시 즉시 `Error`를 throw합니다. mock에 없는 API가 실 SDK에는 있을 수 있어 "devtools에서는 잘 되는데 실제 SDK에서는 안 되는" 배포 사고를 원천 차단하기 위한 의도적 동작입니다. 누락된 API는 [이슈](https://github.com/apps-in-toss-community/devtools/issues)로 제보하거나 직접 mock을 추가해 주세요.
|
|
946
|
+
|
|
947
|
+
```
|
|
948
|
+
[@apps-in-toss/devtools] IAP.newMethod is not mocked. This API may exist in
|
|
949
|
+
@apps-in-toss/web-framework, but devtools' mock does not cover it yet.
|
|
950
|
+
Please file an issue: https://github.com/apps-in-toss-community/devtools/issues
|
|
405
951
|
```
|
|
406
952
|
|
|
407
|
-
|
|
953
|
+
### 3. GitHub Actions 주간 CI
|
|
408
954
|
|
|
409
|
-
|
|
955
|
+
`.github/workflows/check-sdk-update.yml`이 **매주 월요일** 자동으로:
|
|
410
956
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
957
|
+
1. `@apps-in-toss/web-framework`의 새 버전 확인
|
|
958
|
+
2. 최신 버전으로 업데이트 후 타입 체크 실행
|
|
959
|
+
3. 새 버전 감지 시 자동으로 GitHub Issue 생성 (타입 에러 여부 포함)
|
|
960
|
+
|
|
961
|
+
## Fidelity QA
|
|
962
|
+
|
|
963
|
+
`scripts/fidelity-qa/`는 mock SDK와 실기기 relay 사이의 **SDK API 정합성을 자동으로 측정**하는 도구다.
|
|
964
|
+
|
|
965
|
+
```bash
|
|
966
|
+
pnpm qa:fidelity --runner=mock # mock-only (CI 기본값, 회귀 감지)
|
|
967
|
+
pnpm qa:fidelity --runner=relay # 실기기 relay 필요 (devtools MCP 연결)
|
|
968
|
+
pnpm qa:fidelity --runner=both --diff # mock+relay 동시 실행 + diff 출력
|
|
969
|
+
pnpm qa:fidelity --include-writes # Storage write 사이클 포함 (기본 OFF)
|
|
970
|
+
pnpm qa:fidelity --output=results.json # JSON 결과 파일 저장
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
CI는 `pnpm qa:fidelity --runner=mock`을 자동 실행한다 (mock-only, exit 0이면 통과).
|
|
974
|
+
|
|
975
|
+
**Diff 레이블**:
|
|
976
|
+
|
|
977
|
+
- `MATCH` — mock과 relay 값이 동일
|
|
978
|
+
- `EXPECTED_MISMATCH` — `scripts/fidelity-qa/whitelist.json`에 등록된 알려진 차이 (예: jsdom UA vs 실 WebView UA)
|
|
979
|
+
- `UNEXPECTED` — whitelist에 없는 불일치 → exit 1 (회귀 의심)
|
|
980
|
+
|
|
981
|
+
**whitelist 갱신 절차**: relay 세션에서 의도적 차이가 발견되면 `scripts/fidelity-qa/whitelist.json`에 `{ "id": "<probe-id>", "reason": "<설명>" }`을 추가한다.
|
|
982
|
+
|
|
983
|
+
relay runner는 현재 stub (devtools#261 follow-up에서 CDP Runtime.evaluate 구현 예정).
|
|
984
|
+
|
|
985
|
+
## Contributing
|
|
986
|
+
|
|
987
|
+
### 새 API mock 추가 절차
|
|
988
|
+
|
|
989
|
+
1. 해당 카테고리 디렉토리에 함수 구현 (예: `src/mock/device/`)
|
|
990
|
+
2. 공통 API는 `src/mock/index.ts`, SDK별 계약은 `src/mock/index-2x.ts` 또는 `src/mock/index-3x.ts`에 export 추가
|
|
991
|
+
3. `src/__typecheck.ts`와 `src/__typecheck-2x.ts`에 타입 호환성 assertion 추가
|
|
992
|
+
4. `pnpm typecheck`로 2.10.8·3.0.1 모두와 호환되는지 검증
|
|
993
|
+
5. `src/__tests/`에 테스트 작성
|
|
994
|
+
|
|
995
|
+
```bash
|
|
996
|
+
pnpm build # tsdown으로 빌드
|
|
997
|
+
pnpm typecheck # 타입 호환성 검증
|
|
998
|
+
pnpm test # 전체 테스트 실행
|
|
415
999
|
```
|
|
416
1000
|
|
|
417
|
-
|
|
1001
|
+
### Pre-commit hook (선택)
|
|
1002
|
+
|
|
1003
|
+
Optional이지만 권장합니다. clone 후 아래 명령으로 표준 pre-commit hook을 활성화하면 staged 파일에 대해 `biome check`가 자동 실행됩니다.
|
|
1004
|
+
|
|
1005
|
+
```sh
|
|
1006
|
+
git config core.hooksPath .githooks
|
|
1007
|
+
```
|
|
418
1008
|
|
|
419
|
-
|
|
420
|
-
2. 공통 표면은 `src/mock/index.ts`, 라인별 계약은 `src/mock/index-2x.ts` / `src/mock/index-3x.ts`(+ `src/mock/domains-3x.ts`)에 export해요.
|
|
421
|
-
3. `src/__typecheck.ts` / `src/__typecheck-2x.ts`에 타입 호환 assertion을 추가해요.
|
|
422
|
-
4. `typecheck`로 두 라인 모두와 호환되는지 확인해요 — `check:sdk-exports`가 함께 돌아요.
|
|
423
|
-
5. `src/__tests__/`에 테스트를 추가해요.
|
|
1009
|
+
이 hook은 push 전에 빠르게 lint 이슈를 잡기 위한 개발자 편의 장치입니다. 실제 강제 계층은 CI의 `pnpm lint` job이므로, hook을 활성화하지 않은 contributor도 PR 단계에서 lint 실패를 보게 됩니다.
|
|
424
1010
|
|
|
425
1011
|
## Troubleshooting
|
|
426
1012
|
|
|
427
|
-
|
|
1013
|
+
### `[@apps-in-toss/devtools] XXX.method is not mocked` 에러가 날 때
|
|
1014
|
+
|
|
1015
|
+
사용 중인 SDK API가 아직 mock으로 구현되지 않았습니다. devtools는 "잘 되는 척" 배포를 막기 위해 미구현 API 접근 시 throw합니다. [이슈를 등록](https://github.com/apps-in-toss-community/devtools/issues)하거나 직접 mock을 추가한 뒤 다시 실행하세요.
|
|
1016
|
+
|
|
1017
|
+
### DevTools Panel이 안 보일 때
|
|
1018
|
+
|
|
1019
|
+
- 플러그인 옵션에서 `panel: false`로 설정하지 않았는지 확인
|
|
1020
|
+
- 수동 alias 설정을 사용 중이라면, 진입점 파일에 직접 import를 추가하세요:
|
|
1021
|
+
```ts
|
|
1022
|
+
import "@apps-in-toss/devtools/panel";
|
|
1023
|
+
```
|
|
1024
|
+
- 플러그인은 파일명이 `main`, `index`, `entry`, `app` 중 하나인 진입점에만 자동 주입합니다 (대소문자 무시). 파일명이 이 패턴에 맞지 않으면 수동으로 `import '@apps-in-toss/devtools/panel'`을 추가하세요.
|
|
1025
|
+
|
|
1026
|
+
### 서브패스 import는 mock되지 않음
|
|
1027
|
+
|
|
1028
|
+
`@apps-in-toss/web-framework/some-subpath` 형태의 서브패스 import는 alias가 적용되지 않습니다. SDK의 메인 엔트리(`@apps-in-toss/web-framework`)만 mock됩니다. 특정 서브패스도 mock이 필요하다면 번들러의 `resolve.alias`에 해당 서브패스를 수동으로 추가하세요.
|
|
1029
|
+
|
|
1030
|
+
### Next.js Turbopack에서 설정하는 법
|
|
1031
|
+
|
|
1032
|
+
Turbopack은 unplugin을 지원하지 않으므로, `next.config.js`에서 `resolveAlias`를 사용하세요 (위의 [Next.js (Turbopack)](#nextjs-turbopack) 섹션 참고). Panel은 진입점에서 직접 import해야 합니다:
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
// app/layout.tsx 또는 pages/_app.tsx
|
|
1036
|
+
import "@apps-in-toss/devtools/panel";
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
## MCP Server
|
|
1040
|
+
|
|
1041
|
+
MCP 표면(데몬 · attach · CDP tool)은 `@ait-co/debugger`로 이동했습니다(#818). 에이전트 등록도 이제 devtools가 아니라 debugger를 가리킵니다:
|
|
1042
|
+
|
|
1043
|
+
```json
|
|
1044
|
+
{
|
|
1045
|
+
"mcpServers": {
|
|
1046
|
+
"ait-debug": {
|
|
1047
|
+
"command": "npx",
|
|
1048
|
+
"args": ["-y", "-p", "@ait-co/debugger", "debugger"]
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
이 서버가 주는 것: 로컬 브라우저(환경 1)·PWA(환경 2)·intoss-private WebView(환경 3)에서 실행 중인 미니앱을 CDP로 관측 — console, network, DOM, snapshot, screenshot — 그리고 환경 2·3 on-device 진입용 `start_attach`. 전체 tool 목록·모드/타깃 매트릭스·환경별 설정은 [`@ait-co/debugger`](https://www.npmjs.com/package/@ait-co/debugger) 패키지 문서가 정본입니다.
|
|
1055
|
+
|
|
1056
|
+
## 패키지 Export 구조
|
|
1057
|
+
|
|
1058
|
+
이 패키지가 실제로 출하하는 진입점입니다:
|
|
428
1059
|
|
|
429
|
-
|
|
1060
|
+
| Import path | 용도 |
|
|
1061
|
+
| ------------------------------------ | ------------------------------------------------------------- |
|
|
1062
|
+
| `@apps-in-toss/devtools` (= `/mock`) | 번들러 alias 대상, 모든 mock export |
|
|
1063
|
+
| `@apps-in-toss/devtools/panel` | Floating DevTools Panel (import 시 자동 마운트) |
|
|
1064
|
+
| `@apps-in-toss/devtools/unplugin` | 번들러 플러그인 (.vite, .webpack, .rspack, .esbuild, .rollup) |
|
|
430
1065
|
|
|
431
|
-
|
|
1066
|
+
아래는 **전환 스텁**입니다 — 0.2.x에만 존재하고 1.0.0에서 제거됩니다(#818). 새 패키지로 마이그레이션하세요.
|
|
432
1067
|
|
|
433
|
-
|
|
1068
|
+
| Import path | 이동처 | import 시 |
|
|
1069
|
+
| ------------------------------------ | ------------------------------ | --------------------------- |
|
|
1070
|
+
| `@apps-in-toss/devtools/mcp/server` | `@ait-co/debugger/mcp/server` | throw |
|
|
1071
|
+
| `@apps-in-toss/devtools/mcp/cli` | `@ait-co/debugger/mcp/cli` | throw |
|
|
1072
|
+
| `@apps-in-toss/devtools/test-runner` | `@ait-co/debugger/test-runner` | throw |
|
|
1073
|
+
| `@apps-in-toss/devtools/in-app` | `@ait-co/debug-console` | no-op + `console.error` 1회 |
|
|
1074
|
+
| `@apps-in-toss/devtools/in-app/auto` | `@ait-co/debug-console/auto` | no-op + `console.error` 1회 |
|
|
434
1075
|
|
|
435
|
-
##
|
|
1076
|
+
## 라이센스
|
|
436
1077
|
|
|
437
|
-
|
|
438
|
-
| --------------------------------- | ---------------------------------------------------------------------- |
|
|
439
|
-
| `@apps-in-toss/devtools` | `/mock`과 동일 (3.x facade) |
|
|
440
|
-
| `@apps-in-toss/devtools/mock` | 번들러 alias 대상 기본 진입점 (3.x facade) |
|
|
441
|
-
| `@apps-in-toss/devtools/mock/2x` | 2.x facade |
|
|
442
|
-
| `@apps-in-toss/devtools/mock/3x` | 3.x facade |
|
|
443
|
-
| `@apps-in-toss/devtools/panel` | 플로팅 패널 (import 시 자동 마운트, `mount`/`disposePanel` export) |
|
|
444
|
-
| `@apps-in-toss/devtools/unplugin` | 번들러 플러그인 (`vite` · `webpack` · `rspack` · `rollup` · `esbuild`) |
|
|
1078
|
+
BSD 3-Clause
|
|
445
1079
|
|
|
446
|
-
|
|
1080
|
+
---
|
|
447
1081
|
|
|
448
|
-
|
|
1082
|
+
커뮤니티 오픈소스 프로젝트입니다.
|