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