@ait-co/devtools 0.0.1 → 0.0.3
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.md +439 -135
- package/dist/mock/index.d.ts +479 -426
- package/dist/mock/index.d.ts.map +1 -0
- package/dist/mock/index.js +1036 -646
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.d.ts +4 -2
- package/dist/panel/index.d.ts.map +1 -0
- package/dist/panel/index.js +1415 -317
- package/dist/panel/index.js.map +1 -1
- package/dist/unplugin/index.cjs +68 -72
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.d.cts +1395 -0
- package/dist/unplugin/index.d.cts.map +1 -0
- package/dist/unplugin/index.d.ts +1395 -0
- package/dist/unplugin/index.d.ts.map +1 -0
- package/dist/unplugin/index.js +59 -43
- package/dist/unplugin/index.js.map +1 -1
- package/package.json +27 -16
- package/dist/chunk-YYIIG3JT.js +0 -146
- package/dist/chunk-YYIIG3JT.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,284 +1,588 @@
|
|
|
1
|
-
# ait-devtools
|
|
1
|
+
# @ait-co/devtools
|
|
2
|
+
|
|
3
|
+
> **데모: https://apps-in-toss-community.github.io/devtools/**
|
|
2
4
|
|
|
3
5
|
`@apps-in-toss/web-framework` SDK의 mock 라이브러리입니다. `@apps-in-toss/web-bridge`, `@apps-in-toss/web-analytics` import도 함께 mock됩니다.
|
|
4
6
|
|
|
5
7
|
앱인토스(Apps in Toss) 미니앱을 **일반 브라우저**에서 개발하고 테스트할 수 있게 해줍니다. 토스 앱 없이도 SDK의 모든 기능을 시뮬레이션하여 빠른 개발 사이클을 지원합니다.
|
|
6
8
|
|
|
7
9
|
- **60+ SDK API mock** — 인증, 결제, IAP, 위치, 카메라, 스토리지 등
|
|
8
|
-
- **
|
|
10
|
+
- **Device API 모드 시스템** — mock / web / prompt 세 가지 모드로 디바이스 API 동작 전환
|
|
11
|
+
- **Floating DevTools Panel** — 브라우저에서 SDK 상태를 실시간으로 제어 (8개 탭)
|
|
9
12
|
- **모든 번들러 지원** — [unplugin](https://github.com/unjs/unplugin) 기반 Vite, Webpack, Rspack, esbuild, Rollup 통합
|
|
10
13
|
|
|
11
14
|
## 설치
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
|
-
npm install -D ait-devtools
|
|
17
|
+
npm install -D @ait-co/devtools
|
|
18
|
+
# 또는
|
|
19
|
+
pnpm add -D @ait-co/devtools
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
> `@apps-in-toss/web-framework
|
|
22
|
+
> **지원 SDK 버전**: `@apps-in-toss/web-framework >=2.4.0 <2.4.8` (peer, required).
|
|
23
|
+
>
|
|
24
|
+
> devtools는 위 범위의 SDK 버전에서만 동작이 검증됩니다. 범위 밖 SDK를 설치하면
|
|
25
|
+
> 패키지 매니저가 install-time에 peer 경고를 표시합니다. 또한 devtools가 아직 mock하지
|
|
26
|
+
> 않은 API를 호출하면 런타임에 에러가 발생합니다 — "devtools에서는 잘 되는데 실제 SDK에서는
|
|
27
|
+
> 안 되는" 상황을 방지하기 위한 의도적 동작입니다. 누락된 API는
|
|
28
|
+
> [이슈](https://github.com/apps-in-toss-community/devtools/issues)로 알려주세요.
|
|
18
29
|
|
|
19
|
-
##
|
|
30
|
+
## 번들러 설정
|
|
20
31
|
|
|
21
|
-
###
|
|
32
|
+
### Vite
|
|
22
33
|
|
|
23
34
|
```ts
|
|
24
|
-
// vite.config.ts
|
|
25
|
-
import aitDevtools from 'ait-devtools/unplugin';
|
|
35
|
+
// vite.config.ts (개발 전용)
|
|
36
|
+
import aitDevtools from '@ait-co/devtools/unplugin';
|
|
26
37
|
|
|
27
38
|
export default {
|
|
28
39
|
plugins: [aitDevtools.vite()],
|
|
29
40
|
};
|
|
30
41
|
```
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
> 개발 전용 설정입니다. Production 빌드에서 제외하려면 아래 [Production 빌드](#production-빌드) 섹션을 참고하세요.
|
|
44
|
+
|
|
45
|
+
### Webpack / Rspack
|
|
33
46
|
|
|
34
47
|
```js
|
|
35
|
-
// webpack.config.js (ESM)
|
|
36
|
-
import aitDevtools from 'ait-devtools/unplugin';
|
|
48
|
+
// webpack.config.js (ESM, 개발 환경에서만 사용 권장)
|
|
49
|
+
import aitDevtools from '@ait-co/devtools/unplugin';
|
|
37
50
|
config.plugins.push(aitDevtools.webpack());
|
|
38
51
|
|
|
39
52
|
// webpack.config.js (CommonJS)
|
|
40
|
-
const aitDevtools = require('ait-devtools/unplugin');
|
|
53
|
+
const aitDevtools = require('@ait-co/devtools/unplugin');
|
|
41
54
|
config.plugins.push(aitDevtools.webpack());
|
|
42
55
|
```
|
|
43
56
|
|
|
44
|
-
###
|
|
57
|
+
### Next.js (Turbopack)
|
|
58
|
+
|
|
59
|
+
Turbopack은 플러그인 시스템을 지원하지 않으므로 `resolveAlias`를 사용합니다.
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
- `@apps-in-toss/web-bridge`, `@apps-in-toss/web-analytics`도 함께 alias해야 합니다.
|
|
62
|
+
- Turbopack은 일반적으로 `next dev`에서만 사용되므로 별도의 production 가드가 필요하지 않습니다.
|
|
47
63
|
|
|
48
64
|
```js
|
|
49
65
|
// next.config.js (Next.js 15+)
|
|
50
66
|
module.exports = {
|
|
51
67
|
turbo: {
|
|
52
68
|
resolveAlias: {
|
|
53
|
-
'@apps-in-toss/web-framework': 'ait-devtools/mock',
|
|
69
|
+
'@apps-in-toss/web-framework': '@ait-co/devtools/mock',
|
|
70
|
+
'@apps-in-toss/web-bridge': '@ait-co/devtools/mock',
|
|
71
|
+
'@apps-in-toss/web-analytics': '@ait-co/devtools/mock',
|
|
54
72
|
},
|
|
55
73
|
},
|
|
56
74
|
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Next.js 14 이하에서는 `experimental.turbo`를 사용합니다:
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
```js
|
|
80
|
+
// next.config.js (Next.js 14 이하)
|
|
59
81
|
module.exports = {
|
|
60
82
|
experimental: {
|
|
61
83
|
turbo: {
|
|
62
84
|
resolveAlias: {
|
|
63
|
-
'@apps-in-toss/web-framework': 'ait-devtools/mock',
|
|
85
|
+
'@apps-in-toss/web-framework': '@ait-co/devtools/mock',
|
|
86
|
+
'@apps-in-toss/web-bridge': '@ait-co/devtools/mock',
|
|
87
|
+
'@apps-in-toss/web-analytics': '@ait-co/devtools/mock',
|
|
64
88
|
},
|
|
65
89
|
},
|
|
66
90
|
},
|
|
67
91
|
};
|
|
68
92
|
```
|
|
69
93
|
|
|
70
|
-
|
|
94
|
+
> **Panel 주입**: Turbopack은 unplugin을 지원하지 않으므로 Panel이 자동 주입되지 않습니다. 진입점에서 직접 import하세요:
|
|
95
|
+
> ```ts
|
|
96
|
+
> // app/layout.tsx 또는 pages/_app.tsx
|
|
97
|
+
> import '@ait-co/devtools/panel';
|
|
98
|
+
> ```
|
|
99
|
+
|
|
100
|
+
### Next.js (Webpack)
|
|
101
|
+
|
|
102
|
+
Next.js에서 Webpack 모드(`next dev` without `--turbo`, 또는 `next build`)를 사용하는 경우:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
// next.config.js (Webpack 모드)
|
|
106
|
+
const aitDevtools = require('@ait-co/devtools/unplugin'); // CJS entrypoint 제공
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
webpack: (config, { dev }) => {
|
|
110
|
+
if (dev) {
|
|
111
|
+
config.plugins.push(aitDevtools.webpack());
|
|
112
|
+
}
|
|
113
|
+
return config;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 수동 Alias 설정
|
|
71
119
|
|
|
72
120
|
번들러의 `resolve.alias` 설정으로 직접 지정할 수도 있습니다:
|
|
73
121
|
|
|
122
|
+
```ts
|
|
123
|
+
// vite.config.ts
|
|
124
|
+
import { defineConfig } from 'vite';
|
|
125
|
+
|
|
126
|
+
export default defineConfig({
|
|
127
|
+
resolve: {
|
|
128
|
+
alias: {
|
|
129
|
+
'@apps-in-toss/web-framework': '@ait-co/devtools/mock',
|
|
130
|
+
'@apps-in-toss/web-bridge': '@ait-co/devtools/mock',
|
|
131
|
+
'@apps-in-toss/web-analytics': '@ait-co/devtools/mock',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
74
137
|
```js
|
|
75
|
-
//
|
|
76
|
-
{
|
|
138
|
+
// webpack.config.js (Webpack은 절대 경로 필요)
|
|
139
|
+
module.exports = {
|
|
77
140
|
resolve: {
|
|
78
141
|
alias: {
|
|
79
|
-
'@apps-in-toss/web-framework': 'ait-devtools/mock',
|
|
80
|
-
'@apps-in-toss/web-bridge': 'ait-devtools/mock',
|
|
81
|
-
'@apps-in-toss/web-analytics': 'ait-devtools/mock',
|
|
142
|
+
'@apps-in-toss/web-framework': require.resolve('@ait-co/devtools/mock'),
|
|
143
|
+
'@apps-in-toss/web-bridge': require.resolve('@ait-co/devtools/mock'),
|
|
144
|
+
'@apps-in-toss/web-analytics': require.resolve('@ait-co/devtools/mock'),
|
|
82
145
|
},
|
|
83
146
|
},
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **주의**: 수동 alias만 사용하면 DevTools Panel이 자동 주입되지 않습니다. 진입점 파일에 직접 import를 추가하세요:
|
|
151
|
+
> ```ts
|
|
152
|
+
> import '@ait-co/devtools/panel'; // 진입점에 추가
|
|
153
|
+
> ```
|
|
154
|
+
|
|
155
|
+
### 플러그인 옵션
|
|
156
|
+
|
|
157
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| `panel` | `boolean` | `true` | DevTools Panel 자동 주입 여부 |
|
|
160
|
+
| `forceEnable` | `boolean` | `false` | production에서도 devtools 활성화 |
|
|
161
|
+
| `mock` | `boolean` | `true` (dev) / `false` (prod+forceEnable) | mock alias 활성화 여부 |
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
aitDevtools.vite({ panel: false }); // Panel 없이 mock만 사용
|
|
165
|
+
aitDevtools.vite({ forceEnable: true }); // production에서도 활성화 (mock 기본 OFF, panel ON)
|
|
166
|
+
aitDevtools.vite({ forceEnable: true, mock: true }); // production에서 mock도 활성화
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Production 빌드
|
|
170
|
+
|
|
171
|
+
기본적으로 devtools 플러그인은 **production 빌드에서 자동 비활성화**됩니다 (`NODE_ENV === 'production'`이면 alias 변환과 Panel 주입이 모두 스킵). 별도의 조건부 설정 없이도 안전합니다.
|
|
172
|
+
|
|
173
|
+
스테이징 환경 등에서 production 빌드에서도 devtools를 사용하려면 `forceEnable` 옵션을 사용하세요:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
aitDevtools.vite({ forceEnable: true }); // panel ON, mock OFF (모니터링 전용)
|
|
177
|
+
aitDevtools.vite({ forceEnable: true, mock: true }); // panel + mock 모두 ON
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
번들러 설정에서 플러그인 자체를 조건부로 제외할 수도 있습니다:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// vite.config.ts
|
|
184
|
+
import { defineConfig } from 'vite';
|
|
185
|
+
import aitDevtools from '@ait-co/devtools/unplugin';
|
|
186
|
+
|
|
187
|
+
export default defineConfig(({ command }) => ({
|
|
188
|
+
plugins: [
|
|
189
|
+
...(command === 'serve' ? [aitDevtools.vite()] : []),
|
|
190
|
+
],
|
|
191
|
+
}));
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// webpack.config.js (Rspack도 동일)
|
|
196
|
+
const aitDevtools = require('@ait-co/devtools/unplugin');
|
|
197
|
+
const plugins = [];
|
|
198
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
199
|
+
plugins.push(aitDevtools.webpack());
|
|
84
200
|
}
|
|
85
201
|
```
|
|
86
202
|
|
|
87
|
-
|
|
203
|
+
> Next.js 설정은 위의 [Next.js (Webpack)](#nextjs-webpack) 및 [Next.js (Turbopack)](#nextjs-turbopack) 섹션을 참고하세요.
|
|
204
|
+
|
|
205
|
+
## Device API 모드 시스템
|
|
206
|
+
|
|
207
|
+
디바이스 관련 API(카메라, 위치, 클립보드 등)는 세 가지 모드로 동작합니다:
|
|
208
|
+
|
|
209
|
+
| 모드 | 동작 | 사용 사례 |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| **mock** | `aitState`에 저장된 더미 데이터 반환 | 자동화 테스트, 고정된 시나리오 |
|
|
212
|
+
| **web** | 브라우저 네이티브 API 사용 (Geolocation, File API 등) | 실제 디바이스 기능 테스트 |
|
|
213
|
+
| **prompt** | DevTools Panel이 자동으로 열리고 사용자 입력 대기 (30초 타임아웃) | 수동 QA, 특정 값 입력 |
|
|
214
|
+
|
|
215
|
+
### 모드별 지원 API
|
|
216
|
+
|
|
217
|
+
| API | mock | web | prompt |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| `openCamera` | ✅ | ✅ | ✅ |
|
|
220
|
+
| `fetchAlbumPhotos` | ✅ | ✅ | ✅ |
|
|
221
|
+
| `getCurrentLocation` | ✅ | ✅ | ✅ |
|
|
222
|
+
| `startUpdateLocation` | ✅ | ✅ | ✅ |
|
|
223
|
+
| `getNetworkStatus` | ✅ | ✅ | — |
|
|
224
|
+
| `getClipboardText` / `setClipboardText` | ✅ | ✅ | — |
|
|
225
|
+
|
|
226
|
+
### 모드 설정 방법
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
// 콘솔에서 개별 API 모드 변경
|
|
230
|
+
__ait.patch('deviceModes', { camera: 'web', location: 'prompt' });
|
|
231
|
+
|
|
232
|
+
// 또는 DevTools Panel의 Device 탭에서 드롭다운으로 전환
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 더미 이미지 관리
|
|
88
236
|
|
|
89
|
-
|
|
237
|
+
mock 모드에서 카메라/앨범 API는 더미 이미지를 반환합니다.
|
|
238
|
+
|
|
239
|
+
- **기본 플레이스홀더**: 파란색/녹색/주황색 320×240 이미지 3장 자동 생성
|
|
240
|
+
- **커스텀 이미지**: DevTools Panel의 Device 탭에서 파일 추가/제거 가능
|
|
241
|
+
- **콘솔에서 설정**: `__ait.patch('mockData', { images: ['data:image/png;base64,...'] })`
|
|
242
|
+
|
|
243
|
+
## Floating DevTools Panel
|
|
90
244
|
|
|
91
|
-
화면 우하단의 **'AIT' 버튼**을 클릭하면
|
|
245
|
+
플러그인 사용 시 진입점 파일에 패널이 자동 주입됩니다. 화면 우하단의 **'AIT' 버튼**을 클릭하면 토글됩니다.
|
|
92
246
|
|
|
93
|
-
###
|
|
247
|
+
### 8개 탭
|
|
94
248
|
|
|
95
249
|
| 탭 | 설명 |
|
|
96
250
|
|---|---|
|
|
97
|
-
| **Environment** | 플랫폼 OS (ios/android), 앱 버전, 환경 (toss/sandbox), 로케일, 네트워크 상태, Safe Area Insets
|
|
251
|
+
| **Environment** | 플랫폼 OS (ios/android), 앱 버전, 환경 (toss/sandbox), 로케일, 네트워크 상태, Safe Area Insets |
|
|
98
252
|
| **Permissions** | camera, photos, geolocation, clipboard, contacts, microphone 권한 상태 제어 (allowed/denied/notDetermined) |
|
|
99
|
-
| **Location** | 위도, 경도, 정확도
|
|
100
|
-
| **
|
|
253
|
+
| **Location** | 위도, 경도, 정확도 설정 |
|
|
254
|
+
| **Device** | API 모드 전환 (mock/web/prompt), 더미 이미지 관리 (추가/제거/기본값/초기화) |
|
|
255
|
+
| **IAP** | 다음 구매 결과 선택 (success/취소/에러 등), TossPay 결제 결과, 완료된 주문 내역 (최근 5건) |
|
|
101
256
|
| **Events** | Back/Home 네비게이션 이벤트 트리거, 로그인 상태 토글 |
|
|
102
|
-
| **Analytics** | 기록된 분석 이벤트 실시간 로그 뷰어 (
|
|
257
|
+
| **Analytics** | 기록된 분석 이벤트 실시간 로그 뷰어 (최근 30건, 타임스탬프/타입/파라미터) |
|
|
103
258
|
| **Storage** | `Storage` API로 저장된 항목 조회 및 초기화 |
|
|
104
259
|
|
|
105
|
-
|
|
260
|
+
> **prompt 모드 자동 열림**: prompt 모드로 설정된 API가 호출되면, Panel이 자동으로 Device 탭을 열고 사용자 입력 UI를 표시합니다.
|
|
106
261
|
|
|
107
|
-
`window.__ait
|
|
262
|
+
## `window.__ait` 콘솔 API
|
|
263
|
+
|
|
264
|
+
브라우저 콘솔에서 `window.__ait`(또는 `__ait`)로 mock 상태를 직접 제어할 수 있습니다:
|
|
108
265
|
|
|
109
266
|
```js
|
|
110
|
-
//
|
|
111
|
-
__ait.
|
|
267
|
+
// 현재 상태 조회
|
|
268
|
+
__ait.state // 전체 상태 객체
|
|
269
|
+
__ait.state.platform // 'ios' 또는 'android'
|
|
270
|
+
__ait.state.auth.isLoggedIn // 로그인 상태
|
|
271
|
+
__ait.state.deviceModes // 각 API의 현재 모드
|
|
112
272
|
|
|
113
|
-
//
|
|
273
|
+
// 상태 업데이트 (얕은 병합)
|
|
114
274
|
__ait.update({ platform: 'android', locale: 'en-US' });
|
|
275
|
+
__ait.update({ networkStatus: 'OFFLINE' });
|
|
276
|
+
|
|
277
|
+
// 중첩 상태 업데이트
|
|
278
|
+
__ait.patch('permissions', { camera: 'denied' });
|
|
279
|
+
__ait.patch('deviceModes', { location: 'web' });
|
|
280
|
+
__ait.patch('iap', { nextResult: 'USER_CANCELED' });
|
|
115
281
|
|
|
116
282
|
// 이벤트 트리거
|
|
117
283
|
__ait.trigger('backEvent');
|
|
284
|
+
__ait.trigger('homeEvent');
|
|
118
285
|
|
|
119
|
-
//
|
|
120
|
-
__ait.
|
|
286
|
+
// 분석 이벤트 수동 기록
|
|
287
|
+
__ait.logAnalytics({ type: 'click', params: { button: 'purchase' } });
|
|
121
288
|
|
|
122
|
-
//
|
|
123
|
-
|
|
289
|
+
// 상태 초기화 (deviceId는 유지됨)
|
|
290
|
+
__ait.reset();
|
|
291
|
+
|
|
292
|
+
// 상태 변경 구독
|
|
293
|
+
const unsubscribe = __ait.subscribe(() => {
|
|
294
|
+
console.log('상태 변경됨:', __ait.state);
|
|
295
|
+
});
|
|
296
|
+
unsubscribe(); // 구독 해제
|
|
124
297
|
```
|
|
125
298
|
|
|
126
299
|
## Mock API 목록
|
|
127
300
|
|
|
128
301
|
### 인증/로그인
|
|
129
302
|
|
|
130
|
-
| API |
|
|
303
|
+
| API | Mock 동작 |
|
|
131
304
|
|---|---|
|
|
132
|
-
| `appLogin` |
|
|
133
|
-
| `getIsTossLoginIntegratedService` |
|
|
134
|
-
| `getUserKeyForGame` |
|
|
135
|
-
| `appsInTossSignTossCert` |
|
|
305
|
+
| `appLogin` | `{ authorizationCode, referrer }` 반환 |
|
|
306
|
+
| `getIsTossLoginIntegratedService` | state의 `isTossLoginIntegrated` 반환 |
|
|
307
|
+
| `getUserKeyForGame` | `{ hash, type: 'HASH' }` 반환 (비로그인 시 `undefined`) |
|
|
308
|
+
| `appsInTossSignTossCert` | 콘솔 로그만 출력 (no-op) |
|
|
136
309
|
|
|
137
310
|
### 화면/네비게이션
|
|
138
311
|
|
|
139
|
-
| API |
|
|
312
|
+
| API | Mock 동작 |
|
|
140
313
|
|---|---|
|
|
141
|
-
| `closeView` |
|
|
142
|
-
| `openURL` |
|
|
143
|
-
| `share` |
|
|
144
|
-
| `getTossShareLink` |
|
|
145
|
-
| `setIosSwipeGestureEnabled` |
|
|
146
|
-
| `setDeviceOrientation` |
|
|
147
|
-
| `setScreenAwakeMode` |
|
|
148
|
-
| `setSecureScreen` |
|
|
149
|
-
| `requestReview` |
|
|
314
|
+
| `closeView` | `window.history.back()` 호출 |
|
|
315
|
+
| `openURL` | `window.open()`으로 새 탭 |
|
|
316
|
+
| `share` | `navigator.share()` 사용 (미지원 시 콘솔 출력) |
|
|
317
|
+
| `getTossShareLink` | `https://toss.im/share/mock{path}` 반환 |
|
|
318
|
+
| `setIosSwipeGestureEnabled` | 콘솔 로그 (no-op) |
|
|
319
|
+
| `setDeviceOrientation` | 콘솔 로그 (no-op) |
|
|
320
|
+
| `setScreenAwakeMode` | `{ enabled }` 반환 |
|
|
321
|
+
| `setSecureScreen` | `{ enabled }` 반환 |
|
|
322
|
+
| `requestReview` | no-op (`.isSupported()` 메서드 포함) |
|
|
150
323
|
|
|
151
324
|
### 환경 정보
|
|
152
325
|
|
|
153
|
-
| API |
|
|
326
|
+
| API | Mock 동작 |
|
|
154
327
|
|---|---|
|
|
155
|
-
| `getPlatformOS` |
|
|
156
|
-
| `getOperationalEnvironment` |
|
|
157
|
-
| `getTossAppVersion` |
|
|
158
|
-
| `isMinVersionSupported` |
|
|
159
|
-
| `getSchemeUri` |
|
|
160
|
-
| `getLocale` |
|
|
161
|
-
| `getDeviceId` |
|
|
162
|
-
| `getGroupId` |
|
|
163
|
-
| `getNetworkStatus` |
|
|
164
|
-
| `getServerTime` |
|
|
165
|
-
| `env.getDeploymentId` |
|
|
166
|
-
| `getAppsInTossGlobals` |
|
|
328
|
+
| `getPlatformOS` | state의 platform 반환 (기본: `'ios'`) |
|
|
329
|
+
| `getOperationalEnvironment` | state의 environment 반환 (기본: `'sandbox'`) |
|
|
330
|
+
| `getTossAppVersion` | state의 appVersion 반환 (기본: `'5.240.0'`) |
|
|
331
|
+
| `isMinVersionSupported` | 시맨틱 버전 비교 수행 |
|
|
332
|
+
| `getSchemeUri` | state의 schemeUri 또는 `window.location.pathname` |
|
|
333
|
+
| `getLocale` | state의 locale 반환 (기본: `'ko-KR'`) |
|
|
334
|
+
| `getDeviceId` | localStorage에 저장된 고유 UUID 반환 |
|
|
335
|
+
| `getGroupId` | state의 groupId 반환 |
|
|
336
|
+
| `getNetworkStatus` | 모드에 따라 state 또는 브라우저 API 사용 |
|
|
337
|
+
| `getServerTime` | `Date.now()` 반환 |
|
|
338
|
+
| `env.getDeploymentId` | state의 deploymentId 반환 |
|
|
339
|
+
| `getAppsInTossGlobals` | `{ deploymentId, brandDisplayName, brandIcon, brandPrimaryColor }` |
|
|
167
340
|
|
|
168
341
|
### Safe Area
|
|
169
342
|
|
|
170
|
-
| API |
|
|
343
|
+
| API | Mock 동작 |
|
|
171
344
|
|---|---|
|
|
172
|
-
| `SafeAreaInsets.get` |
|
|
173
|
-
| `SafeAreaInsets.subscribe` |
|
|
174
|
-
| `getSafeAreaInsets` |
|
|
175
|
-
|
|
176
|
-
### 이벤트/분석
|
|
177
|
-
|
|
178
|
-
| API | 설명 |
|
|
179
|
-
|---|---|
|
|
180
|
-
| `graniteEvent` | Granite 이벤트 발행 |
|
|
181
|
-
| `appsInTossEvent` | 앱인토스 이벤트 발행 |
|
|
182
|
-
| `tdsEvent` | TDS 이벤트 발행 |
|
|
183
|
-
| `onVisibilityChangedByTransparentServiceWeb` | 투명 서비스웹 가시성 변경 핸들러 |
|
|
184
|
-
| `Analytics` | 분석 네임스페이스 |
|
|
185
|
-
| `eventLog` | 이벤트 로그 기록 |
|
|
345
|
+
| `SafeAreaInsets.get` | `{ top, bottom, left: 0, right: 0 }` 반환 |
|
|
346
|
+
| `SafeAreaInsets.subscribe` | 상태 변경 시 콜백 호출, unsubscribe 함수 반환 |
|
|
347
|
+
| `getSafeAreaInsets` | top inset 값 반환 (deprecated) |
|
|
186
348
|
|
|
187
349
|
### 디바이스 기능
|
|
188
350
|
|
|
189
|
-
| API |
|
|
351
|
+
| API | Mock 동작 |
|
|
190
352
|
|---|---|
|
|
191
|
-
| `Storage` |
|
|
192
|
-
| `getCurrentLocation` |
|
|
193
|
-
| `startUpdateLocation` |
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `generateHapticFeedback` | 햅틱 피드백 생성 |
|
|
201
|
-
| `saveBase64Data` | Base64 데이터 저장 |
|
|
353
|
+
| `Storage.getItem/setItem/removeItem/clearItems` | localStorage에 `__ait_storage:` prefix로 저장 |
|
|
354
|
+
| `getCurrentLocation` | 모드별: mock(state 좌표), web(Geolocation API), prompt(Panel 입력) |
|
|
355
|
+
| `startUpdateLocation` | mock(랜덤 좌표 변동), web(watchPosition), prompt(반복 입력) |
|
|
356
|
+
| `openCamera` | mock(더미 이미지), web(파일 선택기), prompt(Panel 파일 입력) |
|
|
357
|
+
| `fetchAlbumPhotos` | mock(더미 이미지 배열), web(파일 다중 선택), prompt(Panel 파일 입력) |
|
|
358
|
+
| `fetchContacts` | 페이지네이션 지원 mock 연락처 반환, `query.contains` 검색 |
|
|
359
|
+
| `getClipboardText` / `setClipboardText` | mock(state 저장) 또는 web(Clipboard API) |
|
|
360
|
+
| `generateHapticFeedback` | 콘솔 로그 + analytics 기록 |
|
|
361
|
+
| `saveBase64Data` | anchor 엘리먼트로 파일 다운로드 |
|
|
202
362
|
|
|
203
363
|
### IAP/결제
|
|
204
364
|
|
|
205
|
-
| API |
|
|
365
|
+
| API | Mock 동작 |
|
|
206
366
|
|---|---|
|
|
207
|
-
| `IAP.createOneTimePurchaseOrder` |
|
|
208
|
-
| `IAP.createSubscriptionPurchaseOrder` |
|
|
209
|
-
| `IAP.getProductItemList` | 상품 목록
|
|
210
|
-
| `IAP.getPendingOrders` | 대기 중 주문
|
|
211
|
-
| `IAP.getCompletedOrRefundedOrders` | 완료/환불 주문
|
|
212
|
-
| `IAP.completeProductGrant` |
|
|
213
|
-
| `IAP.getSubscriptionInfo` | 구독
|
|
214
|
-
| `checkoutPayment` |
|
|
367
|
+
| `IAP.createOneTimePurchaseOrder` | 300ms 딜레이 후 state의 `nextResult`에 따라 성공/실패 시뮬레이션 |
|
|
368
|
+
| `IAP.createSubscriptionPurchaseOrder` | 위와 동일한 흐름 |
|
|
369
|
+
| `IAP.getProductItemList` | state의 상품 목록 반환 |
|
|
370
|
+
| `IAP.getPendingOrders` | 대기 중 주문 목록 |
|
|
371
|
+
| `IAP.getCompletedOrRefundedOrders` | 완료/환불 주문 목록 |
|
|
372
|
+
| `IAP.completeProductGrant` | 대기 → 완료 주문 이동 |
|
|
373
|
+
| `IAP.getSubscriptionInfo` | 활성 구독 mock (30일 만료, 자동 갱신) |
|
|
374
|
+
| `checkoutPayment` | 300ms 딜레이 후 state의 결제 결과 반환 (TossPay) |
|
|
375
|
+
|
|
376
|
+
**IAP 구매 시뮬레이션 흐름:**
|
|
377
|
+
|
|
378
|
+
1. `IAP.createOneTimePurchaseOrder()` 호출
|
|
379
|
+
2. 300ms 딜레이 (결제 UI 시뮬레이션)
|
|
380
|
+
3. `state.iap.nextResult` 확인 → `'success'`가 아니면 `onError` 호출
|
|
381
|
+
4. 성공 시 `processProductGrant` 콜백 실행 → 실패하면 `'PRODUCT_NOT_GRANTED_BY_PARTNER'` 에러
|
|
382
|
+
5. 모두 성공하면 `completedOrders`에 기록, `onEvent`로 주문 결과 전달
|
|
215
383
|
|
|
216
384
|
### 광고
|
|
217
385
|
|
|
218
|
-
| API |
|
|
386
|
+
| API | Mock 동작 |
|
|
387
|
+
|---|---|
|
|
388
|
+
| `GoogleAdMob.loadAppsInTossAdMob` | 200ms 후 `loaded` 이벤트 |
|
|
389
|
+
| `GoogleAdMob.showAppsInTossAdMob` | 50ms~1.5s에 걸쳐 requested→show→impression→reward→dismissed 이벤트 순차 발행 |
|
|
390
|
+
| `GoogleAdMob.isAppsInTossAdMobLoaded` | 로드 여부 boolean 반환 |
|
|
391
|
+
| `TossAds.initialize/attach/attachBanner` | 회색 플레이스홀더 div 렌더링 |
|
|
392
|
+
| `TossAds.destroy/destroyAll` | no-op |
|
|
393
|
+
| `loadFullScreenAd` / `showFullScreenAd` | GoogleAdMob과 유사한 흐름 |
|
|
394
|
+
|
|
395
|
+
### 이벤트
|
|
396
|
+
|
|
397
|
+
| API | Mock 동작 |
|
|
398
|
+
|---|---|
|
|
399
|
+
| `graniteEvent.addEventListener` | `__ait:backEvent`, `__ait:homeEvent` 커스텀 이벤트 수신 |
|
|
400
|
+
| `appsInTossEvent.addEventListener` | no-op |
|
|
401
|
+
| `tdsEvent.addEventListener` | `__ait:navigationAccessoryEvent` 수신 |
|
|
402
|
+
| `onVisibilityChangedByTransparentServiceWeb` | `document.visibilitychange` 이벤트 위임 |
|
|
403
|
+
|
|
404
|
+
### 분석
|
|
405
|
+
|
|
406
|
+
| API | Mock 동작 |
|
|
219
407
|
|---|---|
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `GoogleAdMob.isAppsInTossAdMobLoaded` | AdMob 광고 로드 여부 확인 |
|
|
223
|
-
| `TossAds.initialize` | 토스 광고 초기화 |
|
|
224
|
-
| `TossAds.attach` | 광고 슬롯 부착 |
|
|
225
|
-
| `TossAds.attachBanner` | 배너 광고 부착 |
|
|
226
|
-
| `TossAds.destroy` | 광고 슬롯 제거 |
|
|
227
|
-
| `TossAds.destroyAll` | 모든 광고 슬롯 제거 |
|
|
228
|
-
| `loadFullScreenAd` | 전면 광고 로드 |
|
|
229
|
-
| `showFullScreenAd` | 전면 광고 표시 |
|
|
408
|
+
| `Analytics.screen/impression/click` | analyticsLog에 타입별 기록, Panel에서 실시간 확인 |
|
|
409
|
+
| `eventLog` | `log_name`, `log_type`, `params`로 커스텀 이벤트 기록 |
|
|
230
410
|
|
|
231
411
|
### 게임/프로모션
|
|
232
412
|
|
|
233
|
-
| API |
|
|
413
|
+
| API | Mock 동작 |
|
|
234
414
|
|---|---|
|
|
235
|
-
| `grantPromotionReward` |
|
|
236
|
-
| `grantPromotionRewardForGame` |
|
|
237
|
-
| `submitGameCenterLeaderBoardScore` |
|
|
238
|
-
| `getGameCenterGameProfile` |
|
|
239
|
-
| `openGameCenterLeaderboard` |
|
|
240
|
-
| `contactsViral` |
|
|
415
|
+
| `grantPromotionReward` | 타임스탬프 기반 mock key 반환 |
|
|
416
|
+
| `grantPromotionRewardForGame` | 위와 동일 |
|
|
417
|
+
| `submitGameCenterLeaderBoardScore` | state에 점수 추가, `{ statusCode: 'SUCCESS' }` |
|
|
418
|
+
| `getGameCenterGameProfile` | mock 프로필 반환 (없으면 `PROFILE_NOT_FOUND`) |
|
|
419
|
+
| `openGameCenterLeaderboard` | 콘솔 로그 (no-op) |
|
|
420
|
+
| `contactsViral` | 500ms 후 close 이벤트 발행 |
|
|
241
421
|
|
|
242
422
|
### 권한
|
|
243
423
|
|
|
244
|
-
| API |
|
|
424
|
+
| API | Mock 동작 |
|
|
245
425
|
|---|---|
|
|
246
|
-
| `getPermission` | 권한 상태
|
|
247
|
-
| `openPermissionDialog` |
|
|
248
|
-
| `requestPermission` |
|
|
426
|
+
| `getPermission` | state의 권한 상태 반환 (allowed/denied/notDetermined) |
|
|
427
|
+
| `openPermissionDialog` | 상태를 `allowed`로 변경 |
|
|
428
|
+
| `requestPermission` | `openPermissionDialog`에 위임 |
|
|
429
|
+
|
|
430
|
+
> 권한이 필요한 함수(openCamera, getCurrentLocation 등)는 `withPermission()`으로 래핑되어 `.getPermission()`, `.openPermissionDialog()` 메서드가 자동 부착됩니다.
|
|
249
431
|
|
|
250
432
|
### 파트너
|
|
251
433
|
|
|
252
|
-
| API |
|
|
434
|
+
| API | Mock 동작 |
|
|
253
435
|
|---|---|
|
|
254
|
-
| `partner.addAccessoryButton` |
|
|
255
|
-
| `partner.removeAccessoryButton` |
|
|
436
|
+
| `partner.addAccessoryButton` | 콘솔 로그 (no-op) |
|
|
437
|
+
| `partner.removeAccessoryButton` | 콘솔 로그 (no-op) |
|
|
438
|
+
|
|
439
|
+
## 테스트에서의 활용
|
|
440
|
+
|
|
441
|
+
vitest/jest에서 mock 라이브러리를 직접 import하여 테스트할 수 있습니다.
|
|
442
|
+
|
|
443
|
+
> mock 함수들이 `window`, `document`, `localStorage` 등 브라우저 API를 사용하므로 **jsdom 환경**이 필요합니다.
|
|
444
|
+
>
|
|
445
|
+
> ```ts
|
|
446
|
+
> // vitest.config.ts
|
|
447
|
+
> import { defineConfig } from 'vitest/config';
|
|
448
|
+
> export default defineConfig({ test: { environment: 'jsdom' } });
|
|
449
|
+
> ```
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
453
|
+
import { appLogin, Storage, getCurrentLocation, getNetworkStatus, openCamera, IAP } from '@ait-co/devtools/mock';
|
|
454
|
+
import { aitState } from '@ait-co/devtools/mock';
|
|
455
|
+
|
|
456
|
+
beforeEach(() => {
|
|
457
|
+
aitState.reset(); // 매 테스트 전 상태 초기화
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// 인증 테스트
|
|
461
|
+
it('appLogin은 authorizationCode를 반환한다', async () => {
|
|
462
|
+
const result = await appLogin();
|
|
463
|
+
expect(result.authorizationCode).toBeDefined();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 상태를 세팅하고 함수 호출
|
|
467
|
+
it('오프라인 상태에서 네트워크 조회', async () => {
|
|
468
|
+
aitState.update({ networkStatus: 'OFFLINE' });
|
|
469
|
+
const status = await getNetworkStatus();
|
|
470
|
+
expect(status).toBe('OFFLINE');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// 권한 denied 시나리오
|
|
474
|
+
it('카메라 권한이 denied면 에러를 던진다', async () => {
|
|
475
|
+
aitState.patch('permissions', { camera: 'denied' });
|
|
476
|
+
await expect(openCamera()).rejects.toThrow();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// IAP 실패 시나리오 (fake timers 필요)
|
|
480
|
+
it('구매 취소 시 onError가 호출된다', async () => {
|
|
481
|
+
vi.useFakeTimers();
|
|
482
|
+
aitState.patch('iap', { nextResult: 'USER_CANCELED' });
|
|
483
|
+
const onError = vi.fn();
|
|
484
|
+
IAP.createOneTimePurchaseOrder({
|
|
485
|
+
options: { sku: 'item_01', processProductGrant: async () => true },
|
|
486
|
+
onEvent: vi.fn(),
|
|
487
|
+
onError,
|
|
488
|
+
});
|
|
489
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
490
|
+
expect(onError).toHaveBeenCalledWith({ code: 'USER_CANCELED' });
|
|
491
|
+
vi.useRealTimers();
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// Storage 테스트
|
|
495
|
+
it('Storage에 값을 저장하고 읽을 수 있다', async () => {
|
|
496
|
+
await Storage.setItem('key1', 'value1');
|
|
497
|
+
const result = await Storage.getItem('key1');
|
|
498
|
+
expect(result).toBe('value1');
|
|
499
|
+
});
|
|
500
|
+
```
|
|
256
501
|
|
|
257
502
|
## SDK 업데이트 대응
|
|
258
503
|
|
|
259
|
-
|
|
504
|
+
세 가지 메커니즘으로 SDK 변경에 안전하게 대응합니다:
|
|
260
505
|
|
|
261
|
-
### 1.
|
|
506
|
+
### 1. 컴파일 타임 타입 검증 (`__typecheck.ts`)
|
|
262
507
|
|
|
263
|
-
`src/__typecheck.ts`에서 mock의 주요 export가 원본 SDK와 타입 호환되는지
|
|
508
|
+
`src/__typecheck.ts`에서 mock의 주요 export가 원본 SDK와 타입 호환되는지 검증합니다. SDK 시그니처가 변경되면 `pnpm typecheck`에서 즉시 에러가 발생합니다.
|
|
264
509
|
|
|
265
510
|
```ts
|
|
266
511
|
type Assert<TMock, TOriginal> = TMock extends TOriginal ? true : never;
|
|
267
512
|
type _AppLogin = Assert<typeof Mock.appLogin, typeof Original.appLogin>;
|
|
513
|
+
// 40+ 타입 호환성 assertion
|
|
268
514
|
```
|
|
269
515
|
|
|
270
|
-
### 2. Proxy
|
|
516
|
+
### 2. Proxy 트립와이어 (런타임 차단)
|
|
271
517
|
|
|
272
|
-
미구현 API
|
|
518
|
+
`createMockProxy()`는 미구현 API 접근 시 즉시 `Error`를 throw합니다. mock에 없는 API가 실 SDK에는 있을 수 있어 "devtools에서는 잘 되는데 실제 SDK에서는 안 되는" 배포 사고를 원천 차단하기 위한 의도적 동작입니다. 누락된 API는 [이슈](https://github.com/apps-in-toss-community/devtools/issues)로 제보하거나 직접 mock을 추가해 주세요.
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
[@ait-co/devtools] IAP.newMethod is not mocked. This API may exist in
|
|
522
|
+
@apps-in-toss/web-framework, but devtools' mock does not cover it yet.
|
|
523
|
+
Please file an issue: https://github.com/apps-in-toss-community/devtools/issues
|
|
524
|
+
```
|
|
273
525
|
|
|
274
526
|
### 3. GitHub Actions 주간 CI
|
|
275
527
|
|
|
276
|
-
`.github/workflows/check-sdk-update.yml`이 **매주 월요일**
|
|
528
|
+
`.github/workflows/check-sdk-update.yml`이 **매주 월요일** 자동으로:
|
|
277
529
|
|
|
278
530
|
1. `@apps-in-toss/web-framework`의 새 버전 확인
|
|
279
531
|
2. 최신 버전으로 업데이트 후 타입 체크 실행
|
|
280
532
|
3. 새 버전 감지 시 자동으로 GitHub Issue 생성 (타입 에러 여부 포함)
|
|
281
533
|
|
|
534
|
+
## Contributing
|
|
535
|
+
|
|
536
|
+
### 새 API mock 추가 절차
|
|
537
|
+
|
|
538
|
+
1. 해당 카테고리 디렉토리에 함수 구현 (예: `src/mock/device/`)
|
|
539
|
+
2. `src/mock/index.ts`에 export 추가
|
|
540
|
+
3. `src/__typecheck.ts`에 타입 호환성 assertion 추가
|
|
541
|
+
4. `pnpm typecheck`로 원본과 호환되는지 검증
|
|
542
|
+
5. `src/__tests/`에 테스트 작성
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
pnpm build # tsup으로 빌드
|
|
546
|
+
pnpm typecheck # 타입 호환성 검증
|
|
547
|
+
pnpm test # 전체 테스트 실행
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Troubleshooting
|
|
551
|
+
|
|
552
|
+
### `[@ait-co/devtools] XXX.method is not mocked` 에러가 날 때
|
|
553
|
+
|
|
554
|
+
사용 중인 SDK API가 아직 mock으로 구현되지 않았습니다. devtools는 "잘 되는 척" 배포를 막기 위해 미구현 API 접근 시 throw합니다. [이슈를 등록](https://github.com/apps-in-toss-community/devtools/issues)하거나 직접 mock을 추가한 뒤 다시 실행하세요.
|
|
555
|
+
|
|
556
|
+
### DevTools Panel이 안 보일 때
|
|
557
|
+
|
|
558
|
+
- 플러그인 옵션에서 `panel: false`로 설정하지 않았는지 확인
|
|
559
|
+
- 수동 alias 설정을 사용 중이라면, 진입점 파일에 직접 import를 추가하세요:
|
|
560
|
+
```ts
|
|
561
|
+
import '@ait-co/devtools/panel';
|
|
562
|
+
```
|
|
563
|
+
- 플러그인은 파일명이 `main`, `index`, `entry`, `app` 중 하나인 진입점에만 자동 주입합니다 (대소문자 무시). 파일명이 이 패턴에 맞지 않으면 수동으로 `import '@ait-co/devtools/panel'`을 추가하세요.
|
|
564
|
+
|
|
565
|
+
### 서브패스 import는 mock되지 않음
|
|
566
|
+
|
|
567
|
+
`@apps-in-toss/web-framework/some-subpath` 형태의 서브패스 import는 alias가 적용되지 않습니다. SDK의 메인 엔트리(`@apps-in-toss/web-framework`)만 mock됩니다. 특정 서브패스도 mock이 필요하다면 번들러의 `resolve.alias`에 해당 서브패스를 수동으로 추가하세요.
|
|
568
|
+
|
|
569
|
+
### Next.js Turbopack에서 설정하는 법
|
|
570
|
+
|
|
571
|
+
Turbopack은 unplugin을 지원하지 않으므로, `next.config.js`에서 `resolveAlias`를 사용하세요 (위의 [Next.js (Turbopack)](#nextjs-turbopack) 섹션 참고). Panel은 진입점에서 직접 import해야 합니다:
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
// app/layout.tsx 또는 pages/_app.tsx
|
|
575
|
+
import '@ait-co/devtools/panel';
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## 패키지 Export 구조
|
|
579
|
+
|
|
580
|
+
| Import path | 용도 |
|
|
581
|
+
|---|---|
|
|
582
|
+
| `@ait-co/devtools` 또는 `@ait-co/devtools/mock` | 모든 mock export (번들러 alias 대상) |
|
|
583
|
+
| `@ait-co/devtools/panel` | Floating DevTools Panel (import 시 자동 마운트) |
|
|
584
|
+
| `@ait-co/devtools/unplugin` | 번들러 플러그인 (.vite, .webpack, .rspack, .esbuild, .rollup) |
|
|
585
|
+
|
|
282
586
|
## 라이센스
|
|
283
587
|
|
|
284
588
|
BSD 3-Clause
|