@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.
@@ -1,696 +1,1086 @@
1
- import {
2
- aitState
3
- } from "../chunk-YYIIG3JT.js";
4
-
5
- // src/mock/auth/index.ts
6
- function appLogin() {
7
- return Promise.resolve({
8
- authorizationCode: `mock-auth-${crypto.randomUUID()}`,
9
- referrer: aitState.state.environment === "toss" ? "DEFAULT" : "SANDBOX"
10
- });
1
+ //#region src/mock/state.ts
2
+ const DEFAULT_STATE = {
3
+ platform: "ios",
4
+ environment: "sandbox",
5
+ appVersion: "5.240.0",
6
+ locale: "ko-KR",
7
+ schemeUri: "/",
8
+ groupId: "mock-group-id",
9
+ deploymentId: "mock-deployment-id",
10
+ deviceId: "",
11
+ brand: {
12
+ displayName: "Mock App",
13
+ icon: "",
14
+ primaryColor: "#3182F6"
15
+ },
16
+ networkStatus: "WIFI",
17
+ permissions: {
18
+ clipboard: "allowed",
19
+ contacts: "allowed",
20
+ photos: "allowed",
21
+ geolocation: "allowed",
22
+ camera: "allowed",
23
+ microphone: "notDetermined"
24
+ },
25
+ location: {
26
+ coords: {
27
+ latitude: 37.5665,
28
+ longitude: 126.978,
29
+ altitude: 0,
30
+ accuracy: 10,
31
+ altitudeAccuracy: 0,
32
+ heading: 0
33
+ },
34
+ timestamp: Date.now(),
35
+ accessLocation: "FINE"
36
+ },
37
+ safeAreaInsets: {
38
+ top: 47,
39
+ bottom: 34,
40
+ left: 0,
41
+ right: 0
42
+ },
43
+ contacts: [{
44
+ name: "홍길동",
45
+ phoneNumber: "010-1234-5678"
46
+ }, {
47
+ name: "김토스",
48
+ phoneNumber: "010-9876-5432"
49
+ }],
50
+ iap: {
51
+ products: [{
52
+ sku: "mock-gem-100",
53
+ type: "CONSUMABLE",
54
+ displayName: "보석 100개",
55
+ displayAmount: "1,000원",
56
+ iconUrl: "",
57
+ description: "게임에서 사용할 수 있는 보석 100개"
58
+ }],
59
+ nextResult: "success",
60
+ pendingOrders: [],
61
+ completedOrders: []
62
+ },
63
+ payment: {
64
+ nextResult: "success",
65
+ failReason: ""
66
+ },
67
+ auth: {
68
+ isLoggedIn: true,
69
+ isTossLoginIntegrated: true,
70
+ userKeyHash: "mock-user-hash-abc123"
71
+ },
72
+ ads: {
73
+ isLoaded: false,
74
+ nextEvent: "loaded"
75
+ },
76
+ game: {
77
+ profile: {
78
+ nickname: "MockPlayer",
79
+ profileImageUri: ""
80
+ },
81
+ leaderboardScores: []
82
+ },
83
+ analyticsLog: [],
84
+ deviceModes: {
85
+ camera: "mock",
86
+ photos: "mock",
87
+ location: "mock",
88
+ network: "mock",
89
+ clipboard: "web"
90
+ },
91
+ mockData: {
92
+ images: [],
93
+ clipboardText: ""
94
+ },
95
+ panelEditable: true
96
+ };
97
+ function generateDeviceId() {
98
+ const stored = localStorage.getItem("__ait_device_id");
99
+ if (stored) return stored;
100
+ const id = crypto.randomUUID();
101
+ localStorage.setItem("__ait_device_id", id);
102
+ return id;
103
+ }
104
+ var AitStateManager = class {
105
+ _state;
106
+ _listeners = /* @__PURE__ */ new Set();
107
+ constructor() {
108
+ this._state = structuredClone(DEFAULT_STATE);
109
+ try {
110
+ this._state.deviceId = generateDeviceId();
111
+ } catch {
112
+ this._state.deviceId = "mock-device-" + Math.random().toString(36).slice(2);
113
+ }
114
+ }
115
+ get state() {
116
+ return this._state;
117
+ }
118
+ update(partial) {
119
+ this._state = {
120
+ ...this._state,
121
+ ...partial
122
+ };
123
+ this._notify();
124
+ }
125
+ /** 중첩 객체 업데이트용 */
126
+ patch(key, partial) {
127
+ const current = this._state[key];
128
+ if (typeof current === "object" && current !== null && !Array.isArray(current)) this._state = {
129
+ ...this._state,
130
+ [key]: {
131
+ ...current,
132
+ ...partial
133
+ }
134
+ };
135
+ else this._state = {
136
+ ...this._state,
137
+ [key]: partial
138
+ };
139
+ this._notify();
140
+ }
141
+ subscribe(listener) {
142
+ this._listeners.add(listener);
143
+ return () => this._listeners.delete(listener);
144
+ }
145
+ /** 분석 로그 추가 */
146
+ logAnalytics(entry) {
147
+ this._state = {
148
+ ...this._state,
149
+ analyticsLog: [...this._state.analyticsLog, {
150
+ ...entry,
151
+ timestamp: Date.now()
152
+ }]
153
+ };
154
+ this._notify();
155
+ }
156
+ /** 이벤트 트리거 (backEvent, homeEvent 등) */
157
+ trigger(event) {
158
+ window.dispatchEvent(new CustomEvent(`__ait:${event}`));
159
+ }
160
+ reset() {
161
+ const deviceId = this._state.deviceId;
162
+ this._state = {
163
+ ...structuredClone(DEFAULT_STATE),
164
+ deviceId
165
+ };
166
+ this._notify();
167
+ }
168
+ _notify() {
169
+ for (const listener of this._listeners) listener();
170
+ }
171
+ };
172
+ const aitState = new AitStateManager();
173
+ if (typeof window !== "undefined") window.__ait = aitState;
174
+ //#endregion
175
+ //#region src/mock/auth/index.ts
176
+ /**
177
+ * 인증/로그인 mock
178
+ */
179
+ async function appLogin() {
180
+ return {
181
+ authorizationCode: `mock-auth-${crypto.randomUUID()}`,
182
+ referrer: aitState.state.environment === "toss" ? "DEFAULT" : "SANDBOX"
183
+ };
11
184
  }
12
- function getIsTossLoginIntegratedService() {
13
- return Promise.resolve(aitState.state.auth.isTossLoginIntegrated);
185
+ async function getIsTossLoginIntegratedService() {
186
+ return aitState.state.auth.isTossLoginIntegrated;
14
187
  }
15
- function getUserKeyForGame() {
16
- if (!aitState.state.auth.userKeyHash) return Promise.resolve(void 0);
17
- return Promise.resolve({ hash: aitState.state.auth.userKeyHash, type: "HASH" });
188
+ async function getUserKeyForGame() {
189
+ if (!aitState.state.auth.userKeyHash) return void 0;
190
+ return {
191
+ hash: aitState.state.auth.userKeyHash,
192
+ type: "HASH"
193
+ };
18
194
  }
19
- function appsInTossSignTossCert(_params) {
20
- console.log("[ait-devtools] appsInTossSignTossCert called (no-op in mock)");
21
- return Promise.resolve();
195
+ async function appsInTossSignTossCert(_params) {
196
+ console.log("[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)");
22
197
  }
23
-
24
- // src/mock/navigation/index.ts
25
- function closeView() {
26
- console.log("[ait-devtools] closeView called");
27
- window.history.back();
28
- return Promise.resolve();
29
- }
30
- function openURL(url) {
31
- console.log("[ait-devtools] openURL:", url);
32
- window.open(url, "_blank");
33
- return Promise.resolve();
34
- }
35
- function share(message) {
36
- if (navigator.share) {
37
- return navigator.share({ text: message.message }).then(() => {
38
- });
39
- }
40
- console.log("[ait-devtools] share:", message.message);
41
- return Promise.resolve();
42
- }
43
- function getTossShareLink(path, _ogImageUrl) {
44
- return Promise.resolve(`https://toss.im/share/mock${path}`);
45
- }
46
- function setIosSwipeGestureEnabled(_options) {
47
- console.log("[ait-devtools] setIosSwipeGestureEnabled:", _options.isEnabled);
48
- return Promise.resolve();
49
- }
50
- function setDeviceOrientation(_options) {
51
- console.log("[ait-devtools] setDeviceOrientation:", _options.type);
52
- return Promise.resolve();
53
- }
54
- function setScreenAwakeMode(options) {
55
- console.log("[ait-devtools] setScreenAwakeMode:", options.enabled);
56
- return Promise.resolve({ enabled: options.enabled });
57
- }
58
- function setSecureScreen(options) {
59
- console.log("[ait-devtools] setSecureScreen:", options.enabled);
60
- return Promise.resolve({ enabled: options.enabled });
61
- }
62
- function requestReview() {
63
- console.log("[ait-devtools] requestReview called");
64
- return Promise.resolve();
198
+ //#endregion
199
+ //#region src/mock/device/_helpers.ts
200
+ /**
201
+ * 디바이스 모듈 내부 공유 헬퍼
202
+ */
203
+ function generatePlaceholderImage(width, height, text, color) {
204
+ const canvas = document.createElement("canvas");
205
+ canvas.width = width;
206
+ canvas.height = height;
207
+ const ctx = canvas.getContext("2d");
208
+ if (!ctx) {
209
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect fill="${color}" width="${width}" height="${height}"/><text x="50%" y="50%" fill="white" font-size="16" text-anchor="middle" dominant-baseline="middle">${text}</text></svg>`;
210
+ return "data:image/svg+xml;base64," + btoa(svg);
211
+ }
212
+ ctx.fillStyle = color;
213
+ ctx.fillRect(0, 0, width, height);
214
+ ctx.fillStyle = "white";
215
+ ctx.font = "16px sans-serif";
216
+ ctx.textAlign = "center";
217
+ ctx.textBaseline = "middle";
218
+ ctx.fillText(text, width / 2, height / 2);
219
+ return canvas.toDataURL("image/png");
220
+ }
221
+ const DEFAULT_PLACEHOLDERS = [
222
+ {
223
+ text: "Mock Photo 1",
224
+ color: "#3182F6"
225
+ },
226
+ {
227
+ text: "Mock Photo 2",
228
+ color: "#27ae60"
229
+ },
230
+ {
231
+ text: "Mock Photo 3",
232
+ color: "#e67e22"
233
+ }
234
+ ];
235
+ let cachedPlaceholders = null;
236
+ function getDefaultPlaceholderImages() {
237
+ if (!cachedPlaceholders) cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) => generatePlaceholderImage(320, 240, p.text, p.color));
238
+ return [...cachedPlaceholders];
239
+ }
240
+ /** @internal device 모듈 내부 전용 */
241
+ function getMockImages() {
242
+ const images = aitState.state.mockData.images;
243
+ if (images.length > 0) return images;
244
+ return getDefaultPlaceholderImages();
245
+ }
246
+ const PROMPT_TIMEOUT_MS = 3e4;
247
+ /** @internal device 모듈 내부 전용 */
248
+ function waitForPromptResponse(type) {
249
+ return new Promise((resolve, reject) => {
250
+ const eventName = "__ait:prompt-response:" + type;
251
+ const cancelName = "__ait:prompt-cancel";
252
+ function cleanup() {
253
+ clearTimeout(timer);
254
+ window.removeEventListener(eventName, handler);
255
+ window.removeEventListener(cancelName, cancelHandler);
256
+ }
257
+ const timer = setTimeout(() => {
258
+ cleanup();
259
+ const hint = !!document.querySelector(".ait-panel") ? "Please provide input via the DevTools panel." : "Is @ait-co/devtools/panel imported?";
260
+ reject(/* @__PURE__ */ new Error(`[@ait-co/devtools] Prompt timeout for "${type}" after ${PROMPT_TIMEOUT_MS / 1e3}s. ${hint}`));
261
+ }, PROMPT_TIMEOUT_MS);
262
+ const handler = (e) => {
263
+ cleanup();
264
+ resolve(e.detail);
265
+ };
266
+ const cancelHandler = () => {
267
+ cleanup();
268
+ reject(/* @__PURE__ */ new Error(`[@ait-co/devtools] Prompt cancelled for "${type}"`));
269
+ };
270
+ window.addEventListener(eventName, handler);
271
+ window.addEventListener(cancelName, cancelHandler);
272
+ window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type } }));
273
+ });
274
+ }
275
+ //#endregion
276
+ //#region src/mock/proxy.ts
277
+ /**
278
+ * 미구현 API용 Proxy 트립와이어.
279
+ *
280
+ * 미구현 프로퍼티에 접근하면 throw한다. 이는 "devtools에서는 멀쩡히 돌지만
281
+ * 실 SDK에선 실제로 동작하는" 시나리오를 차단하기 위한 의도적 선택이다.
282
+ * mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지
283
+ * 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을
284
+ * 포함해 사용자가 mock 누락을 제보할 수 있게 한다.
285
+ */
286
+ const ISSUES_URL = "https://github.com/apps-in-toss-community/devtools/issues";
287
+ function createMockProxy(moduleName, implementations) {
288
+ return new Proxy(implementations, { get(target, prop) {
289
+ if (typeof prop === "symbol") return void 0;
290
+ if (prop in target) return target[prop];
291
+ throw new Error(`[@ait-co/devtools] ${moduleName}.${prop} is not mocked. This API may exist in @apps-in-toss/web-framework, but devtools' mock does not cover it yet. Please file an issue: ${ISSUES_URL}`);
292
+ } });
293
+ }
294
+ //#endregion
295
+ //#region src/mock/device/storage.ts
296
+ /**
297
+ * Storage mock
298
+ * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리
299
+ */
300
+ const Storage = createMockProxy("Storage", {
301
+ getItem: async (key) => {
302
+ return localStorage.getItem(`__ait_storage:${key}`);
303
+ },
304
+ setItem: async (key, value) => {
305
+ localStorage.setItem(`__ait_storage:${key}`, value);
306
+ },
307
+ removeItem: async (key) => {
308
+ localStorage.removeItem(`__ait_storage:${key}`);
309
+ },
310
+ clearItems: async () => {
311
+ Object.keys(localStorage).filter((k) => k.startsWith("__ait_storage:")).forEach((k) => localStorage.removeItem(k));
312
+ }
313
+ });
314
+ //#endregion
315
+ //#region src/mock/permissions.ts
316
+ /**
317
+ * 권한 시스템 mock
318
+ * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.
319
+ */
320
+ async function getPermission(name) {
321
+ return aitState.state.permissions[name];
322
+ }
323
+ async function openPermissionDialog(name) {
324
+ if (aitState.state.permissions[name] === "allowed") return "allowed";
325
+ aitState.patch("permissions", { [name]: "allowed" });
326
+ return "allowed";
327
+ }
328
+ async function requestPermission(permission) {
329
+ return openPermissionDialog(permission.name);
330
+ }
331
+ /** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */
332
+ function withPermission(fn, permissionName) {
333
+ const enhanced = fn;
334
+ enhanced.getPermission = () => getPermission(permissionName);
335
+ enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);
336
+ return enhanced;
337
+ }
338
+ /** 권한 체크 후 denied면 에러 throw */
339
+ function checkPermission(name, fnName) {
340
+ if (aitState.state.permissions[name] === "denied") throw new Error(`[@ait-co/devtools] ${fnName}: Permission "${name}" is denied. Change it in the DevTools panel.`);
341
+ }
342
+ //#endregion
343
+ //#region src/mock/device/location.ts
344
+ /**
345
+ * Location mock (getCurrentLocation, startUpdateLocation)
346
+ * mock/web/prompt 모드 지원
347
+ */
348
+ var Accuracy = /* @__PURE__ */ function(Accuracy) {
349
+ Accuracy[Accuracy["Lowest"] = 1] = "Lowest";
350
+ Accuracy[Accuracy["Low"] = 2] = "Low";
351
+ Accuracy[Accuracy["Balanced"] = 3] = "Balanced";
352
+ Accuracy[Accuracy["High"] = 4] = "High";
353
+ Accuracy[Accuracy["Highest"] = 5] = "Highest";
354
+ Accuracy[Accuracy["BestForNavigation"] = 6] = "BestForNavigation";
355
+ return Accuracy;
356
+ }(Accuracy || {});
357
+ function buildLocation() {
358
+ return {
359
+ coords: { ...aitState.state.location.coords },
360
+ timestamp: Date.now(),
361
+ accessLocation: aitState.state.location.accessLocation
362
+ };
363
+ }
364
+ async function getCurrentLocationMock() {
365
+ return buildLocation();
366
+ }
367
+ async function getCurrentLocationWeb() {
368
+ return new Promise((resolve) => {
369
+ if (!navigator.geolocation) {
370
+ console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
371
+ resolve(buildLocation());
372
+ return;
373
+ }
374
+ navigator.geolocation.getCurrentPosition((pos) => {
375
+ resolve({
376
+ coords: {
377
+ latitude: pos.coords.latitude,
378
+ longitude: pos.coords.longitude,
379
+ altitude: pos.coords.altitude ?? 0,
380
+ accuracy: pos.coords.accuracy,
381
+ altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
382
+ heading: pos.coords.heading ?? 0
383
+ },
384
+ timestamp: pos.timestamp,
385
+ accessLocation: "FINE"
386
+ });
387
+ }, () => {
388
+ console.warn("[@ait-co/devtools] Geolocation failed, falling back to mock");
389
+ resolve(buildLocation());
390
+ });
391
+ });
392
+ }
393
+ async function getCurrentLocationPrompt() {
394
+ return waitForPromptResponse("location");
395
+ }
396
+ const _getCurrentLocation = async (_options) => {
397
+ checkPermission("geolocation", "getCurrentLocation");
398
+ const mode = aitState.state.deviceModes.location;
399
+ if (mode === "web") return getCurrentLocationWeb();
400
+ if (mode === "prompt") return getCurrentLocationPrompt();
401
+ return getCurrentLocationMock();
402
+ };
403
+ const getCurrentLocation = withPermission(_getCurrentLocation, "geolocation");
404
+ function startUpdateLocationMock(eventParams) {
405
+ const { onEvent, options } = eventParams;
406
+ const interval = Math.max(options.timeInterval, 500);
407
+ const id = setInterval(() => {
408
+ const loc = buildLocation();
409
+ loc.coords.latitude += (Math.random() - .5) * 1e-4;
410
+ loc.coords.longitude += (Math.random() - .5) * 1e-4;
411
+ onEvent(loc);
412
+ }, interval);
413
+ return () => clearInterval(id);
414
+ }
415
+ function startUpdateLocationWeb(eventParams) {
416
+ const { onEvent, onError } = eventParams;
417
+ if (!navigator.geolocation) {
418
+ console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
419
+ return startUpdateLocationMock(eventParams);
420
+ }
421
+ const watchId = navigator.geolocation.watchPosition((pos) => {
422
+ onEvent({
423
+ coords: {
424
+ latitude: pos.coords.latitude,
425
+ longitude: pos.coords.longitude,
426
+ altitude: pos.coords.altitude ?? 0,
427
+ accuracy: pos.coords.accuracy,
428
+ altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
429
+ heading: pos.coords.heading ?? 0
430
+ },
431
+ timestamp: pos.timestamp,
432
+ accessLocation: "FINE"
433
+ });
434
+ }, (err) => onError(err));
435
+ return () => navigator.geolocation.clearWatch(watchId);
436
+ }
437
+ function startUpdateLocationPrompt(eventParams) {
438
+ const { onEvent } = eventParams;
439
+ const handler = (e) => {
440
+ onEvent(e.detail);
441
+ };
442
+ window.addEventListener("__ait:prompt-response:location-update", handler);
443
+ window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type: "location-update" } }));
444
+ return () => window.removeEventListener("__ait:prompt-response:location-update", handler);
445
+ }
446
+ const _startUpdateLocation = (eventParams) => {
447
+ const mode = aitState.state.deviceModes.location;
448
+ if (mode === "web") return startUpdateLocationWeb(eventParams);
449
+ if (mode === "prompt") return startUpdateLocationPrompt(eventParams);
450
+ return startUpdateLocationMock(eventParams);
451
+ };
452
+ const startUpdateLocation = withPermission(_startUpdateLocation, "geolocation");
453
+ //#endregion
454
+ //#region src/mock/device/camera.ts
455
+ /**
456
+ * Camera & Album Photos mock
457
+ * mock/web/prompt 모드 지원
458
+ */
459
+ async function openCameraMock() {
460
+ const images = getMockImages();
461
+ return {
462
+ id: crypto.randomUUID(),
463
+ dataUri: images[0]
464
+ };
465
+ }
466
+ async function openCameraWeb() {
467
+ return new Promise((resolve, reject) => {
468
+ const input = document.createElement("input");
469
+ input.type = "file";
470
+ input.accept = "image/*";
471
+ input.capture = "environment";
472
+ let settled = false;
473
+ input.onchange = () => {
474
+ settled = true;
475
+ const file = input.files?.[0];
476
+ if (!file) {
477
+ reject(/* @__PURE__ */ new Error("No file selected"));
478
+ return;
479
+ }
480
+ const reader = new FileReader();
481
+ reader.onload = () => resolve({
482
+ id: crypto.randomUUID(),
483
+ dataUri: reader.result
484
+ });
485
+ reader.onerror = () => reject(/* @__PURE__ */ new Error("Failed to read file"));
486
+ reader.readAsDataURL(file);
487
+ };
488
+ const onFocus = () => {
489
+ setTimeout(() => {
490
+ if (!settled) reject(/* @__PURE__ */ new Error("File picker cancelled"));
491
+ window.removeEventListener("focus", onFocus);
492
+ }, 300);
493
+ };
494
+ window.addEventListener("focus", onFocus);
495
+ input.click();
496
+ });
497
+ }
498
+ async function openCameraPrompt() {
499
+ const dataUri = await waitForPromptResponse("camera");
500
+ return {
501
+ id: crypto.randomUUID(),
502
+ dataUri
503
+ };
504
+ }
505
+ const _openCamera = async (_options) => {
506
+ checkPermission("camera", "openCamera");
507
+ const mode = aitState.state.deviceModes.camera;
508
+ if (mode === "web") return openCameraWeb();
509
+ if (mode === "prompt") return openCameraPrompt();
510
+ return openCameraMock();
511
+ };
512
+ const openCamera = withPermission(_openCamera, "camera");
513
+ async function fetchAlbumPhotosMock(maxCount) {
514
+ return getMockImages().slice(0, maxCount).map((dataUri) => ({
515
+ id: crypto.randomUUID(),
516
+ dataUri
517
+ }));
518
+ }
519
+ async function fetchAlbumPhotosWeb(maxCount) {
520
+ return new Promise((resolve, reject) => {
521
+ const input = document.createElement("input");
522
+ input.type = "file";
523
+ input.accept = "image/*";
524
+ input.multiple = true;
525
+ let settled = false;
526
+ input.onchange = async () => {
527
+ settled = true;
528
+ const files = Array.from(input.files ?? []).slice(0, maxCount);
529
+ if (files.length === 0) {
530
+ reject(/* @__PURE__ */ new Error("No files selected"));
531
+ return;
532
+ }
533
+ resolve(await Promise.all(files.map((file) => new Promise((res, rej) => {
534
+ const reader = new FileReader();
535
+ reader.onload = () => res({
536
+ id: crypto.randomUUID(),
537
+ dataUri: reader.result
538
+ });
539
+ reader.onerror = () => rej(/* @__PURE__ */ new Error("Failed to read file"));
540
+ reader.readAsDataURL(file);
541
+ }))));
542
+ };
543
+ const onFocus = () => {
544
+ setTimeout(() => {
545
+ if (!settled) reject(/* @__PURE__ */ new Error("File picker cancelled"));
546
+ window.removeEventListener("focus", onFocus);
547
+ }, 300);
548
+ };
549
+ window.addEventListener("focus", onFocus);
550
+ input.click();
551
+ });
552
+ }
553
+ async function fetchAlbumPhotosPrompt(maxCount) {
554
+ return (await waitForPromptResponse("photos")).slice(0, maxCount).map((dataUri) => ({
555
+ id: crypto.randomUUID(),
556
+ dataUri
557
+ }));
558
+ }
559
+ const _fetchAlbumPhotos = async (options) => {
560
+ checkPermission("photos", "fetchAlbumPhotos");
561
+ const maxCount = options?.maxCount ?? 10;
562
+ const mode = aitState.state.deviceModes.photos;
563
+ if (mode === "web") return fetchAlbumPhotosWeb(maxCount);
564
+ if (mode === "prompt") return fetchAlbumPhotosPrompt(maxCount);
565
+ return fetchAlbumPhotosMock(maxCount);
566
+ };
567
+ const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, "photos");
568
+ //#endregion
569
+ //#region src/mock/device/clipboard.ts
570
+ /**
571
+ * Clipboard mock
572
+ * mock/web 모드 지원
573
+ */
574
+ const _getClipboardText = async () => {
575
+ checkPermission("clipboard", "getClipboardText");
576
+ if (aitState.state.deviceModes.clipboard === "mock") return aitState.state.mockData.clipboardText;
577
+ try {
578
+ return await navigator.clipboard.readText();
579
+ } catch {
580
+ return "";
581
+ }
582
+ };
583
+ const getClipboardText = withPermission(_getClipboardText, "clipboard");
584
+ const _setClipboardText = async (text) => {
585
+ checkPermission("clipboard", "setClipboardText");
586
+ if (aitState.state.deviceModes.clipboard === "mock") {
587
+ aitState.patch("mockData", { clipboardText: text });
588
+ return;
589
+ }
590
+ await navigator.clipboard.writeText(text);
591
+ };
592
+ const setClipboardText = withPermission(_setClipboardText, "clipboard");
593
+ //#endregion
594
+ //#region src/mock/device/contacts.ts
595
+ /**
596
+ * Contacts mock
597
+ */
598
+ const _fetchContacts = async (options) => {
599
+ checkPermission("contacts", "fetchContacts");
600
+ let contacts = aitState.state.contacts;
601
+ if (options.query?.contains) {
602
+ const q = options.query.contains.toLowerCase();
603
+ contacts = contacts.filter((c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));
604
+ }
605
+ const sliced = contacts.slice(options.offset, options.offset + options.size);
606
+ const nextOffset = options.offset + options.size;
607
+ return {
608
+ result: sliced,
609
+ nextOffset: nextOffset < contacts.length ? nextOffset : null,
610
+ done: nextOffset >= contacts.length
611
+ };
612
+ };
613
+ const fetchContacts = withPermission(_fetchContacts, "contacts");
614
+ //#endregion
615
+ //#region src/mock/device/haptic.ts
616
+ /**
617
+ * Haptic Feedback & saveBase64Data mock
618
+ */
619
+ async function generateHapticFeedback(options) {
620
+ console.log(`[@ait-co/devtools] haptic: ${options.type}`);
621
+ aitState.logAnalytics({
622
+ type: "haptic",
623
+ params: { hapticType: options.type }
624
+ });
625
+ }
626
+ async function saveBase64Data(params) {
627
+ const a = document.createElement("a");
628
+ a.href = `data:${params.mimeType};base64,${params.data}`;
629
+ a.download = params.fileName;
630
+ a.click();
631
+ }
632
+ //#endregion
633
+ //#region src/mock/device/network.ts
634
+ /**
635
+ * Network Status mock (mode-aware helper)
636
+ * navigation 모듈에서 사용. circular dep 방지를 위해 device에 위치.
637
+ */
638
+ /**
639
+ * Web mode: uses navigator.connection.effectiveType (4g/3g/2g) and navigator.onLine.
640
+ * Limitations: WIFI, 5G, WWAN cannot be detected via the Network Information API.
641
+ * Falls back to state-based value when effectiveType is unavailable.
642
+ */
643
+ function getNetworkStatusByMode() {
644
+ const mode = aitState.state.deviceModes.network;
645
+ if (mode === "mock") return null;
646
+ if (mode === "web") {
647
+ if (!navigator.onLine) return "OFFLINE";
648
+ const conn = navigator.connection;
649
+ if (conn?.effectiveType) return {
650
+ "4g": "4G",
651
+ "3g": "3G",
652
+ "2g": "2G",
653
+ "slow-2g": "2G"
654
+ }[conn.effectiveType] ?? "UNKNOWN";
655
+ return aitState.state.networkStatus;
656
+ }
657
+ return null;
658
+ }
659
+ //#endregion
660
+ //#region src/mock/navigation/index.ts
661
+ /**
662
+ * 화면/네비게이션/이벤트 mock
663
+ */
664
+ async function closeView() {
665
+ console.log("[@ait-co/devtools] closeView called");
666
+ window.history.back();
667
+ }
668
+ async function openURL(url) {
669
+ console.log("[@ait-co/devtools] openURL:", url);
670
+ window.open(url, "_blank");
671
+ }
672
+ async function share(message) {
673
+ if (navigator.share) {
674
+ await navigator.share({ text: message.message });
675
+ return;
676
+ }
677
+ console.log("[@ait-co/devtools] share:", message.message);
678
+ }
679
+ async function getTossShareLink(path, _ogImageUrl) {
680
+ return `https://toss.im/share/mock${path}`;
681
+ }
682
+ async function setIosSwipeGestureEnabled(_options) {
683
+ console.log("[@ait-co/devtools] setIosSwipeGestureEnabled:", _options.isEnabled);
684
+ }
685
+ async function setDeviceOrientation(_options) {
686
+ console.log("[@ait-co/devtools] setDeviceOrientation:", _options.type);
687
+ }
688
+ async function setScreenAwakeMode(options) {
689
+ console.log("[@ait-co/devtools] setScreenAwakeMode:", options.enabled);
690
+ return { enabled: options.enabled };
691
+ }
692
+ async function setSecureScreen(options) {
693
+ console.log("[@ait-co/devtools] setSecureScreen:", options.enabled);
694
+ return { enabled: options.enabled };
695
+ }
696
+ async function requestReview() {
697
+ console.log("[@ait-co/devtools] requestReview called");
65
698
  }
66
699
  requestReview.isSupported = () => true;
67
700
  function getPlatformOS() {
68
- return aitState.state.platform;
701
+ return aitState.state.platform;
69
702
  }
70
703
  function getOperationalEnvironment() {
71
- return aitState.state.environment;
704
+ return aitState.state.environment;
72
705
  }
73
706
  function getTossAppVersion() {
74
- return aitState.state.appVersion;
707
+ return aitState.state.appVersion;
75
708
  }
76
709
  function isMinVersionSupported(minVersions) {
77
- const platform = aitState.state.platform;
78
- const required = platform === "ios" ? minVersions.ios : minVersions.android;
79
- if (required === "always") return true;
80
- if (required === "never") return false;
81
- const current = aitState.state.appVersion.split(".").map(Number);
82
- const min = required.split(".").map(Number);
83
- for (let i = 0; i < 3; i++) {
84
- if ((current[i] ?? 0) > (min[i] ?? 0)) return true;
85
- if ((current[i] ?? 0) < (min[i] ?? 0)) return false;
86
- }
87
- return true;
710
+ const required = aitState.state.platform === "ios" ? minVersions.ios : minVersions.android;
711
+ if (required === "always") return true;
712
+ if (required === "never") return false;
713
+ const current = aitState.state.appVersion.split(".").map(Number);
714
+ const min = required.split(".").map(Number);
715
+ for (let i = 0; i < 3; i++) {
716
+ if ((current[i] ?? 0) > (min[i] ?? 0)) return true;
717
+ if ((current[i] ?? 0) < (min[i] ?? 0)) return false;
718
+ }
719
+ return true;
88
720
  }
89
721
  function getSchemeUri() {
90
- return aitState.state.schemeUri || window.location.pathname;
722
+ return aitState.state.schemeUri || window.location.pathname;
91
723
  }
92
724
  function getLocale() {
93
- return aitState.state.locale;
725
+ return aitState.state.locale;
94
726
  }
95
727
  function getDeviceId() {
96
- return aitState.state.deviceId;
728
+ return aitState.state.deviceId;
97
729
  }
98
730
  function getGroupId() {
99
- return aitState.state.groupId;
731
+ return aitState.state.groupId;
100
732
  }
101
- function getNetworkStatus() {
102
- return Promise.resolve(aitState.state.networkStatus);
733
+ async function getNetworkStatus() {
734
+ const modeResult = getNetworkStatusByMode();
735
+ if (modeResult) return modeResult;
736
+ return aitState.state.networkStatus;
103
737
  }
104
- function getServerTime() {
105
- return Promise.resolve(Date.now());
738
+ async function getServerTime() {
739
+ return Date.now();
106
740
  }
107
741
  getServerTime.isSupported = () => true;
108
- var graniteEvent = {
109
- addEventListener(event, { onEvent, onError }) {
110
- const handler = () => {
111
- try {
112
- onEvent();
113
- } catch (e) {
114
- onError?.(e instanceof Error ? e : new Error(String(e)));
115
- }
116
- };
117
- window.addEventListener(`__ait:${event}`, handler);
118
- return () => window.removeEventListener(`__ait:${event}`, handler);
119
- }
120
- };
121
- var appsInTossEvent = {
122
- addEventListener(_event, _handlers) {
123
- return () => {
124
- };
125
- }
126
- };
127
- var tdsEvent = {
128
- addEventListener(event, { onEvent }) {
129
- const handler = (e) => {
130
- const detail = e.detail;
131
- onEvent(detail);
132
- };
133
- window.addEventListener(`__ait:${event}`, handler);
134
- return () => window.removeEventListener(`__ait:${event}`, handler);
135
- }
136
- };
742
+ const graniteEvent = { addEventListener(event, { onEvent, onError }) {
743
+ const handler = () => {
744
+ try {
745
+ onEvent();
746
+ } catch (e) {
747
+ onError?.(e instanceof Error ? e : new Error(String(e)));
748
+ }
749
+ };
750
+ window.addEventListener(`__ait:${event}`, handler);
751
+ return () => window.removeEventListener(`__ait:${event}`, handler);
752
+ } };
753
+ const appsInTossEvent = { addEventListener(_event, _handlers) {
754
+ return () => {};
755
+ } };
756
+ const tdsEvent = { addEventListener(event, { onEvent }) {
757
+ const handler = (e) => {
758
+ const detail = e.detail;
759
+ onEvent(detail);
760
+ };
761
+ window.addEventListener(`__ait:${event}`, handler);
762
+ return () => window.removeEventListener(`__ait:${event}`, handler);
763
+ } };
137
764
  function onVisibilityChangedByTransparentServiceWeb(eventParams) {
138
- const handler = () => eventParams.onEvent(!document.hidden);
139
- document.addEventListener("visibilitychange", handler);
140
- return () => document.removeEventListener("visibilitychange", handler);
765
+ const handler = () => eventParams.onEvent(!document.hidden);
766
+ document.addEventListener("visibilitychange", handler);
767
+ return () => document.removeEventListener("visibilitychange", handler);
141
768
  }
142
- var env = {
143
- getDeploymentId: () => aitState.state.deploymentId
144
- };
769
+ const env = { getDeploymentId: () => aitState.state.deploymentId };
145
770
  function getAppsInTossGlobals() {
146
- return {
147
- deploymentId: aitState.state.deploymentId,
148
- brandDisplayName: aitState.state.brand.displayName,
149
- brandIcon: aitState.state.brand.icon,
150
- brandPrimaryColor: aitState.state.brand.primaryColor
151
- };
152
- }
153
- var SafeAreaInsets = {
154
- get: () => ({ ...aitState.state.safeAreaInsets }),
155
- // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.
156
- // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.
157
- subscribe: ({ onEvent }) => {
158
- return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
159
- }
160
- };
161
- function getSafeAreaInsets() {
162
- return aitState.state.safeAreaInsets.top;
771
+ return {
772
+ deploymentId: aitState.state.deploymentId,
773
+ brandDisplayName: aitState.state.brand.displayName,
774
+ brandIcon: aitState.state.brand.icon,
775
+ brandPrimaryColor: aitState.state.brand.primaryColor
776
+ };
163
777
  }
164
-
165
- // src/mock/proxy.ts
166
- var WARNED = /* @__PURE__ */ new Set();
167
- function createMockProxy(moduleName, implementations) {
168
- return new Proxy(implementations, {
169
- get(target, prop) {
170
- if (prop in target) return target[prop];
171
- if (typeof prop === "symbol") return void 0;
172
- if (!WARNED.has(`${moduleName}.${prop}`)) {
173
- console.warn(
174
- `[ait-devtools] ${moduleName}.${prop} is not mocked yet. Returning no-op. Please update ait-devtools or file an issue.`
175
- );
176
- WARNED.add(`${moduleName}.${prop}`);
177
- }
178
- return async () => void 0;
179
- }
180
- });
181
- }
182
-
183
- // src/mock/permissions.ts
184
- function getPermission(name) {
185
- return Promise.resolve(aitState.state.permissions[name]);
186
- }
187
- function openPermissionDialog(name) {
188
- const current = aitState.state.permissions[name];
189
- if (current === "allowed") return Promise.resolve("allowed");
190
- aitState.patch("permissions", { [name]: "allowed" });
191
- return Promise.resolve("allowed");
192
- }
193
- function requestPermission(permission) {
194
- return openPermissionDialog(permission.name);
195
- }
196
- function withPermission(fn, permissionName) {
197
- const enhanced = fn;
198
- enhanced.getPermission = () => getPermission(permissionName);
199
- enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);
200
- return enhanced;
201
- }
202
- function checkPermission(name, fnName) {
203
- const status = aitState.state.permissions[name];
204
- if (status === "denied") {
205
- throw new Error(`[ait-devtools] ${fnName}: Permission "${name}" is denied. Change it in the DevTools panel.`);
206
- }
207
- }
208
-
209
- // src/mock/device/index.ts
210
- var Storage = createMockProxy("Storage", {
211
- getItem: async (key) => {
212
- return localStorage.getItem(`__ait_storage:${key}`);
213
- },
214
- setItem: async (key, value) => {
215
- localStorage.setItem(`__ait_storage:${key}`, value);
216
- },
217
- removeItem: async (key) => {
218
- localStorage.removeItem(`__ait_storage:${key}`);
219
- },
220
- clearItems: async () => {
221
- const keys = Object.keys(localStorage).filter((k) => k.startsWith("__ait_storage:"));
222
- keys.forEach((k) => localStorage.removeItem(k));
223
- }
224
- });
225
- var Accuracy = /* @__PURE__ */ ((Accuracy2) => {
226
- Accuracy2[Accuracy2["Lowest"] = 1] = "Lowest";
227
- Accuracy2[Accuracy2["Low"] = 2] = "Low";
228
- Accuracy2[Accuracy2["Balanced"] = 3] = "Balanced";
229
- Accuracy2[Accuracy2["High"] = 4] = "High";
230
- Accuracy2[Accuracy2["Highest"] = 5] = "Highest";
231
- Accuracy2[Accuracy2["BestForNavigation"] = 6] = "BestForNavigation";
232
- return Accuracy2;
233
- })(Accuracy || {});
234
- function buildLocation() {
235
- return {
236
- coords: { ...aitState.state.location.coords },
237
- timestamp: Date.now(),
238
- accessLocation: aitState.state.location.accessLocation
239
- };
240
- }
241
- var _getCurrentLocation = async (_options) => {
242
- checkPermission("geolocation", "getCurrentLocation");
243
- return buildLocation();
244
- };
245
- var getCurrentLocation = withPermission(_getCurrentLocation, "geolocation");
246
- function _startUpdateLocation(eventParams) {
247
- const { onEvent, options } = eventParams;
248
- const interval = Math.max(options.timeInterval, 500);
249
- const id = setInterval(() => {
250
- const loc = buildLocation();
251
- loc.coords.latitude += (Math.random() - 0.5) * 1e-4;
252
- loc.coords.longitude += (Math.random() - 0.5) * 1e-4;
253
- onEvent(loc);
254
- }, interval);
255
- return () => clearInterval(id);
256
- }
257
- var startUpdateLocation = Object.assign(_startUpdateLocation, {
258
- getPermission: () => withPermission(_getCurrentLocation, "geolocation").getPermission(),
259
- openPermissionDialog: () => withPermission(_getCurrentLocation, "geolocation").openPermissionDialog()
260
- });
261
- var _openCamera = async (options) => {
262
- checkPermission("camera", "openCamera");
263
- return new Promise((resolve, reject) => {
264
- const input = document.createElement("input");
265
- input.type = "file";
266
- input.accept = "image/*";
267
- input.capture = "environment";
268
- input.onchange = () => {
269
- const file = input.files?.[0];
270
- if (!file) {
271
- reject(new Error("No file selected"));
272
- return;
273
- }
274
- const reader = new FileReader();
275
- reader.onload = () => {
276
- resolve({
277
- id: crypto.randomUUID(),
278
- dataUri: reader.result
279
- });
280
- };
281
- reader.readAsDataURL(file);
282
- };
283
- input.click();
284
- });
285
- };
286
- var openCamera = withPermission(_openCamera, "camera");
287
- var _fetchAlbumPhotos = async (options) => {
288
- checkPermission("photos", "fetchAlbumPhotos");
289
- const maxCount = options?.maxCount ?? 10;
290
- return new Promise((resolve, reject) => {
291
- const input = document.createElement("input");
292
- input.type = "file";
293
- input.accept = "image/*";
294
- input.multiple = true;
295
- input.onchange = async () => {
296
- const files = Array.from(input.files ?? []).slice(0, maxCount);
297
- if (files.length === 0) {
298
- reject(new Error("No files selected"));
299
- return;
300
- }
301
- const results = await Promise.all(
302
- files.map((file) => new Promise((res) => {
303
- const reader = new FileReader();
304
- reader.onload = () => res({ id: crypto.randomUUID(), dataUri: reader.result });
305
- reader.readAsDataURL(file);
306
- }))
307
- );
308
- resolve(results);
309
- };
310
- input.click();
311
- });
312
- };
313
- var fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, "photos");
314
- var _fetchContacts = async (options) => {
315
- checkPermission("contacts", "fetchContacts");
316
- let contacts = aitState.state.contacts;
317
- if (options.query?.contains) {
318
- const q = options.query.contains.toLowerCase();
319
- contacts = contacts.filter((c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));
320
- }
321
- const sliced = contacts.slice(options.offset, options.offset + options.size);
322
- const nextOffset = options.offset + options.size;
323
- return {
324
- result: sliced,
325
- nextOffset: nextOffset < contacts.length ? nextOffset : null,
326
- done: nextOffset >= contacts.length
327
- };
778
+ const SafeAreaInsets = {
779
+ get: () => ({ ...aitState.state.safeAreaInsets }),
780
+ subscribe: ({ onEvent }) => {
781
+ return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
782
+ }
328
783
  };
329
- var fetchContacts = withPermission(_fetchContacts, "contacts");
330
- var _getClipboardText = async () => {
331
- checkPermission("clipboard", "getClipboardText");
332
- try {
333
- return await navigator.clipboard.readText();
334
- } catch {
335
- return "";
336
- }
337
- };
338
- var getClipboardText = withPermission(_getClipboardText, "clipboard");
339
- var _setClipboardText = async (text) => {
340
- checkPermission("clipboard", "setClipboardText");
341
- await navigator.clipboard.writeText(text);
342
- };
343
- var setClipboardText = withPermission(_setClipboardText, "clipboard");
344
- function generateHapticFeedback(options) {
345
- console.log(`[ait-devtools] haptic: ${options.type}`);
346
- aitState.logAnalytics({ type: "haptic", params: { hapticType: options.type } });
347
- return Promise.resolve();
348
- }
349
- function saveBase64Data(params) {
350
- const a = document.createElement("a");
351
- a.href = `data:${params.mimeType};base64,${params.data}`;
352
- a.download = params.fileName;
353
- a.click();
354
- return Promise.resolve();
784
+ /** @deprecated */
785
+ function getSafeAreaInsets() {
786
+ return aitState.state.safeAreaInsets.top;
355
787
  }
356
-
357
- // src/mock/iap/index.ts
358
- var orderCounter = 0;
788
+ //#endregion
789
+ //#region src/mock/iap/index.ts
790
+ /**
791
+ * IAP (인앱결제) mock
792
+ */
793
+ let orderCounter = 0;
359
794
  function generateOrderId() {
360
- return `mock-order-${++orderCounter}-${Date.now()}`;
795
+ return `mock-order-${++orderCounter}-${Date.now()}`;
361
796
  }
362
797
  function buildOrderResult(sku) {
363
- const product = aitState.state.iap.products.find((p) => p.sku === sku);
364
- const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
365
- return {
366
- orderId: generateOrderId(),
367
- displayName: product?.displayName ?? "Mock Product",
368
- displayAmount: product?.displayAmount ?? "1,000\uC6D0",
369
- amount: parseInt(amountStr, 10) || 1e3,
370
- currency: "KRW",
371
- fraction: 0,
372
- miniAppIconUrl: product?.iconUrl || null
373
- };
798
+ const product = aitState.state.iap.products.find((p) => p.sku === sku);
799
+ const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
800
+ return {
801
+ orderId: generateOrderId(),
802
+ displayName: product?.displayName ?? "Mock Product",
803
+ displayAmount: product?.displayAmount ?? "1,000",
804
+ amount: parseInt(amountStr, 10) || 1e3,
805
+ currency: "KRW",
806
+ fraction: 0,
807
+ miniAppIconUrl: product?.iconUrl || null
808
+ };
374
809
  }
375
810
  async function handlePurchase(sku, processProductGrant, onEvent, onError) {
376
- const nextResult = aitState.state.iap.nextResult;
377
- await new Promise((r) => setTimeout(r, 300));
378
- if (nextResult !== "success") {
379
- onError({ code: nextResult });
380
- return;
381
- }
382
- const result = buildOrderResult(sku);
383
- try {
384
- const granted = await processProductGrant({ orderId: result.orderId });
385
- if (!granted) {
386
- onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
387
- return;
388
- }
389
- } catch (e) {
390
- onError(e);
391
- return;
392
- }
393
- aitState.patch("iap", {
394
- completedOrders: [...aitState.state.iap.completedOrders, {
395
- orderId: result.orderId,
396
- sku,
397
- status: "COMPLETED",
398
- date: (/* @__PURE__ */ new Date()).toISOString()
399
- }]
400
- });
401
- await onEvent({ type: "success", data: result });
402
- }
403
- var IAP = createMockProxy("IAP", {
404
- // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)
405
- createOneTimePurchaseOrder(params) {
406
- const sku = params.options.sku ?? params.options.productId ?? "";
407
- handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[ait-devtools] IAP unexpected error:", e));
408
- return () => {
409
- };
410
- },
411
- createSubscriptionPurchaseOrder(params) {
412
- handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[ait-devtools] IAP unexpected error:", e));
413
- return () => {
414
- };
415
- },
416
- async getProductItemList() {
417
- return {
418
- products: aitState.state.iap.products.map((p) => ({
419
- ...p,
420
- ...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
421
- }))
422
- };
423
- },
424
- async getPendingOrders() {
425
- return { orders: [...aitState.state.iap.pendingOrders] };
426
- },
427
- async getCompletedOrRefundedOrders() {
428
- return {
429
- hasNext: false,
430
- nextKey: null,
431
- orders: [...aitState.state.iap.completedOrders]
432
- };
433
- },
434
- async completeProductGrant(args) {
435
- const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
436
- if (idx !== -1) {
437
- const order = aitState.state.iap.pendingOrders[idx];
438
- const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
439
- const completedOrders = [...aitState.state.iap.completedOrders, {
440
- orderId: order.orderId,
441
- sku: order.sku,
442
- status: "COMPLETED",
443
- date: (/* @__PURE__ */ new Date()).toISOString()
444
- }];
445
- aitState.patch("iap", { pendingOrders, completedOrders });
446
- }
447
- return true;
448
- },
449
- async getSubscriptionInfo(_args) {
450
- return {
451
- subscription: {
452
- catalogId: 1,
453
- status: "ACTIVE",
454
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(),
455
- isAutoRenew: true,
456
- gracePeriodExpiresAt: null,
457
- isAccessible: true
458
- }
459
- };
460
- }
811
+ const nextResult = aitState.state.iap.nextResult;
812
+ await new Promise((r) => setTimeout(r, 300));
813
+ if (nextResult !== "success") {
814
+ onError({ code: nextResult });
815
+ return;
816
+ }
817
+ const result = buildOrderResult(sku);
818
+ try {
819
+ if (!await processProductGrant({ orderId: result.orderId })) {
820
+ onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
821
+ return;
822
+ }
823
+ } catch (e) {
824
+ onError(e);
825
+ return;
826
+ }
827
+ aitState.patch("iap", { completedOrders: [...aitState.state.iap.completedOrders, {
828
+ orderId: result.orderId,
829
+ sku,
830
+ status: "COMPLETED",
831
+ date: (/* @__PURE__ */ new Date()).toISOString()
832
+ }] });
833
+ await onEvent({
834
+ type: "success",
835
+ data: result
836
+ });
837
+ }
838
+ const IAP = createMockProxy("IAP", {
839
+ createOneTimePurchaseOrder(params) {
840
+ handlePurchase(params.options.sku ?? params.options.productId ?? "", params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
841
+ return () => {};
842
+ },
843
+ createSubscriptionPurchaseOrder(params) {
844
+ handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
845
+ return () => {};
846
+ },
847
+ async getProductItemList() {
848
+ return { products: aitState.state.iap.products.map((p) => ({
849
+ ...p,
850
+ ...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
851
+ })) };
852
+ },
853
+ async getPendingOrders() {
854
+ return { orders: [...aitState.state.iap.pendingOrders] };
855
+ },
856
+ async getCompletedOrRefundedOrders() {
857
+ return {
858
+ hasNext: false,
859
+ nextKey: null,
860
+ orders: [...aitState.state.iap.completedOrders]
861
+ };
862
+ },
863
+ async completeProductGrant(args) {
864
+ const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
865
+ if (idx !== -1) {
866
+ const order = aitState.state.iap.pendingOrders[idx];
867
+ const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
868
+ const completedOrders = [...aitState.state.iap.completedOrders, {
869
+ orderId: order.orderId,
870
+ sku: order.sku,
871
+ status: "COMPLETED",
872
+ date: (/* @__PURE__ */ new Date()).toISOString()
873
+ }];
874
+ aitState.patch("iap", {
875
+ pendingOrders,
876
+ completedOrders
877
+ });
878
+ }
879
+ return true;
880
+ },
881
+ async getSubscriptionInfo(_args) {
882
+ return { subscription: {
883
+ catalogId: 1,
884
+ status: "ACTIVE",
885
+ expiresAt: new Date(Date.now() + 720 * 60 * 60 * 1e3).toISOString(),
886
+ isAutoRenew: true,
887
+ gracePeriodExpiresAt: null,
888
+ isAccessible: true
889
+ } };
890
+ }
461
891
  });
462
- function checkoutPayment(options) {
463
- const { nextResult, failReason } = aitState.state.payment;
464
- console.log("[ait-devtools] checkoutPayment:", options.params.payToken);
465
- return new Promise((resolve) => {
466
- setTimeout(() => {
467
- if (nextResult === "success") {
468
- resolve({ success: true });
469
- } else {
470
- resolve({ success: false, reason: failReason || "Mock payment failed" });
471
- }
472
- }, 300);
473
- });
892
+ async function checkoutPayment(options) {
893
+ const { nextResult, failReason } = aitState.state.payment;
894
+ console.log("[@ait-co/devtools] checkoutPayment:", options.params.payToken);
895
+ await new Promise((r) => setTimeout(r, 300));
896
+ if (nextResult === "success") return { success: true };
897
+ return {
898
+ success: false,
899
+ reason: failReason || "Mock payment failed"
900
+ };
474
901
  }
475
-
476
- // src/mock/ads/index.ts
902
+ //#endregion
903
+ //#region src/mock/ads/index.ts
904
+ /**
905
+ * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)
906
+ */
477
907
  function withIsSupported(fn) {
478
- fn.isSupported = () => true;
479
- return fn;
480
- }
481
- var GoogleAdMob = createMockProxy("GoogleAdMob", {
482
- loadAppsInTossAdMob: withIsSupported((args) => {
483
- setTimeout(() => {
484
- aitState.patch("ads", { isLoaded: true });
485
- args.onEvent({ type: "loaded", data: { adGroupId: args.options?.adGroupId } });
486
- }, 200);
487
- return () => {
488
- };
489
- }),
490
- showAppsInTossAdMob: withIsSupported((args) => {
491
- if (!aitState.state.ads.isLoaded) {
492
- args.onError(new Error("Ad not loaded"));
493
- return () => {
494
- };
495
- }
496
- setTimeout(() => args.onEvent({ type: "requested" }), 50);
497
- setTimeout(() => args.onEvent({ type: "show" }), 100);
498
- setTimeout(() => args.onEvent({ type: "impression" }), 150);
499
- setTimeout(() => {
500
- args.onEvent({ type: "userEarnedReward", data: { unitType: "coins", unitAmount: 10 } });
501
- }, 1e3);
502
- setTimeout(() => {
503
- args.onEvent({ type: "dismissed" });
504
- aitState.patch("ads", { isLoaded: false });
505
- }, 1500);
506
- return () => {
507
- };
508
- }),
509
- isAppsInTossAdMobLoaded: withIsSupported(
510
- async (_options) => aitState.state.ads.isLoaded
511
- )
908
+ fn.isSupported = () => true;
909
+ return fn;
910
+ }
911
+ const GoogleAdMob = createMockProxy("GoogleAdMob", {
912
+ loadAppsInTossAdMob: withIsSupported((args) => {
913
+ setTimeout(() => {
914
+ aitState.patch("ads", { isLoaded: true });
915
+ args.onEvent({
916
+ type: "loaded",
917
+ data: { adGroupId: args.options?.adGroupId }
918
+ });
919
+ }, 200);
920
+ return () => {};
921
+ }),
922
+ showAppsInTossAdMob: withIsSupported((args) => {
923
+ if (!aitState.state.ads.isLoaded) {
924
+ args.onError(/* @__PURE__ */ new Error("Ad not loaded"));
925
+ return () => {};
926
+ }
927
+ setTimeout(() => args.onEvent({ type: "requested" }), 50);
928
+ setTimeout(() => args.onEvent({ type: "show" }), 100);
929
+ setTimeout(() => args.onEvent({ type: "impression" }), 150);
930
+ setTimeout(() => {
931
+ args.onEvent({
932
+ type: "userEarnedReward",
933
+ data: {
934
+ unitType: "coins",
935
+ unitAmount: 10
936
+ }
937
+ });
938
+ }, 1e3);
939
+ setTimeout(() => {
940
+ args.onEvent({ type: "dismissed" });
941
+ aitState.patch("ads", { isLoaded: false });
942
+ }, 1500);
943
+ return () => {};
944
+ }),
945
+ isAppsInTossAdMobLoaded: withIsSupported(async (_options) => aitState.state.ads.isLoaded)
512
946
  });
513
- var TossAds = createMockProxy("TossAds", {
514
- initialize: withIsSupported((_options) => {
515
- console.log("[ait-devtools] TossAds.initialize (mock)");
516
- }),
517
- attach: withIsSupported((_adGroupId, target, _options) => {
518
- const el = typeof target === "string" ? document.querySelector(target) : target;
519
- if (el) {
520
- const placeholder = document.createElement("div");
521
- placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;";
522
- placeholder.textContent = "[ait-devtools] TossAds Placeholder";
523
- el.appendChild(placeholder);
524
- }
525
- }),
526
- attachBanner: withIsSupported((_adGroupId, target, _options) => {
527
- const el = typeof target === "string" ? document.querySelector(target) : target;
528
- if (el) {
529
- const placeholder = document.createElement("div");
530
- placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:12px;text-align:center;color:#666;font-size:12px;";
531
- placeholder.textContent = "[ait-devtools] Banner Ad Placeholder";
532
- el.appendChild(placeholder);
533
- }
534
- return { destroy: () => {
535
- } };
536
- }),
537
- destroy: withIsSupported((_slotId) => {
538
- }),
539
- destroyAll: withIsSupported(() => {
540
- })
947
+ const TossAds = createMockProxy("TossAds", {
948
+ initialize: withIsSupported((_options) => {
949
+ console.log("[@ait-co/devtools] TossAds.initialize (mock)");
950
+ }),
951
+ attach: withIsSupported((_adGroupId, target, _options) => {
952
+ const el = typeof target === "string" ? document.querySelector(target) : target;
953
+ if (el) {
954
+ const placeholder = document.createElement("div");
955
+ placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;";
956
+ placeholder.textContent = "[@ait-co/devtools] TossAds Placeholder";
957
+ el.appendChild(placeholder);
958
+ }
959
+ }),
960
+ attachBanner: withIsSupported((_adGroupId, target, _options) => {
961
+ const el = typeof target === "string" ? document.querySelector(target) : target;
962
+ if (el) {
963
+ const placeholder = document.createElement("div");
964
+ placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:12px;text-align:center;color:#666;font-size:12px;";
965
+ placeholder.textContent = "[@ait-co/devtools] Banner Ad Placeholder";
966
+ el.appendChild(placeholder);
967
+ }
968
+ return { destroy: () => {} };
969
+ }),
970
+ destroy: withIsSupported((_slotId) => {}),
971
+ destroyAll: withIsSupported(() => {})
541
972
  });
542
- var loadFullScreenAd = withIsSupported((args) => {
543
- setTimeout(() => {
544
- aitState.patch("ads", { isLoaded: true });
545
- args.onEvent({ type: "loaded", data: { adGroupId: args.options?.adGroupId } });
546
- }, 200);
547
- return () => {
548
- };
973
+ const loadFullScreenAd = withIsSupported((args) => {
974
+ setTimeout(() => {
975
+ aitState.patch("ads", { isLoaded: true });
976
+ args.onEvent({
977
+ type: "loaded",
978
+ data: { adGroupId: args.options?.adGroupId }
979
+ });
980
+ }, 200);
981
+ return () => {};
549
982
  });
550
- var showFullScreenAd = withIsSupported((args) => {
551
- if (!aitState.state.ads.isLoaded) {
552
- args.onError(new Error("Ad not loaded"));
553
- return () => {
554
- };
555
- }
556
- setTimeout(() => args.onEvent({ type: "show" }), 100);
557
- setTimeout(() => args.onEvent({ type: "dismissed" }), 1500);
558
- return () => {
559
- };
983
+ const showFullScreenAd = withIsSupported((args) => {
984
+ if (!aitState.state.ads.isLoaded) {
985
+ args.onError(/* @__PURE__ */ new Error("Ad not loaded"));
986
+ return () => {};
987
+ }
988
+ setTimeout(() => args.onEvent({ type: "show" }), 100);
989
+ setTimeout(() => args.onEvent({ type: "dismissed" }), 1500);
990
+ return () => {};
560
991
  });
561
-
562
- // src/mock/game/index.ts
563
- function grantPromotionReward(params) {
564
- console.log("[ait-devtools] grantPromotionReward:", params.params);
565
- return Promise.resolve({ key: `mock-reward-${Date.now()}` });
566
- }
567
- function grantPromotionRewardForGame(params) {
568
- console.log("[ait-devtools] grantPromotionRewardForGame:", params.params);
569
- return Promise.resolve({ key: `mock-reward-${Date.now()}` });
570
- }
571
- function submitGameCenterLeaderBoardScore(params) {
572
- aitState.patch("game", {
573
- leaderboardScores: [...aitState.state.game.leaderboardScores, { score: params.score, timestamp: Date.now() }]
574
- });
575
- return Promise.resolve({ statusCode: "SUCCESS" });
576
- }
577
- function getGameCenterGameProfile() {
578
- const profile = aitState.state.game.profile;
579
- if (!profile) return Promise.resolve({ statusCode: "PROFILE_NOT_FOUND" });
580
- return Promise.resolve({
581
- statusCode: "SUCCESS",
582
- nickname: profile.nickname,
583
- profileImageUri: profile.profileImageUri
584
- });
585
- }
586
- function openGameCenterLeaderboard() {
587
- console.log("[ait-devtools] openGameCenterLeaderboard (no-op in browser)");
588
- return Promise.resolve();
992
+ //#endregion
993
+ //#region src/mock/game/index.ts
994
+ /**
995
+ * 게임/프로모션 mock
996
+ */
997
+ async function grantPromotionReward(params) {
998
+ console.log("[@ait-co/devtools] grantPromotionReward:", params.params);
999
+ return { key: `mock-reward-${Date.now()}` };
1000
+ }
1001
+ async function grantPromotionRewardForGame(params) {
1002
+ console.log("[@ait-co/devtools] grantPromotionRewardForGame:", params.params);
1003
+ return { key: `mock-reward-${Date.now()}` };
1004
+ }
1005
+ async function submitGameCenterLeaderBoardScore(params) {
1006
+ aitState.patch("game", { leaderboardScores: [...aitState.state.game.leaderboardScores, {
1007
+ score: params.score,
1008
+ timestamp: Date.now()
1009
+ }] });
1010
+ return { statusCode: "SUCCESS" };
1011
+ }
1012
+ async function getGameCenterGameProfile() {
1013
+ const profile = aitState.state.game.profile;
1014
+ if (!profile) return { statusCode: "PROFILE_NOT_FOUND" };
1015
+ return {
1016
+ statusCode: "SUCCESS",
1017
+ nickname: profile.nickname,
1018
+ profileImageUri: profile.profileImageUri
1019
+ };
1020
+ }
1021
+ async function openGameCenterLeaderboard() {
1022
+ console.log("[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)");
589
1023
  }
590
1024
  function contactsViral(params) {
591
- setTimeout(() => {
592
- params.onEvent({
593
- type: "close",
594
- data: {
595
- closeReason: "noReward",
596
- sentRewardsCount: 0
597
- }
598
- });
599
- }, 500);
600
- return () => {
601
- };
1025
+ setTimeout(() => {
1026
+ params.onEvent({
1027
+ type: "close",
1028
+ data: {
1029
+ closeReason: "noReward",
1030
+ sentRewardsCount: 0
1031
+ }
1032
+ });
1033
+ }, 500);
1034
+ return () => {};
602
1035
  }
603
-
604
- // src/mock/analytics/index.ts
605
- var Analytics = {
606
- screen: (params) => {
607
- aitState.logAnalytics({ type: "screen", params: params ?? {} });
608
- return Promise.resolve();
609
- },
610
- impression: (params) => {
611
- aitState.logAnalytics({ type: "impression", params: params ?? {} });
612
- return Promise.resolve();
613
- },
614
- click: (params) => {
615
- aitState.logAnalytics({ type: "click", params: params ?? {} });
616
- return Promise.resolve();
617
- }
1036
+ //#endregion
1037
+ //#region src/mock/analytics/index.ts
1038
+ /**
1039
+ * Analytics mock
1040
+ */
1041
+ const Analytics = {
1042
+ screen: (params) => {
1043
+ aitState.logAnalytics({
1044
+ type: "screen",
1045
+ params: params ?? {}
1046
+ });
1047
+ return Promise.resolve();
1048
+ },
1049
+ impression: (params) => {
1050
+ aitState.logAnalytics({
1051
+ type: "impression",
1052
+ params: params ?? {}
1053
+ });
1054
+ return Promise.resolve();
1055
+ },
1056
+ click: (params) => {
1057
+ aitState.logAnalytics({
1058
+ type: "click",
1059
+ params: params ?? {}
1060
+ });
1061
+ return Promise.resolve();
1062
+ }
618
1063
  };
619
- function eventLog(params) {
620
- aitState.logAnalytics({ type: params.log_type, params: { log_name: params.log_name, ...params.params } });
621
- return Promise.resolve();
1064
+ async function eventLog(params) {
1065
+ aitState.logAnalytics({
1066
+ type: params.log_type,
1067
+ params: {
1068
+ log_name: params.log_name,
1069
+ ...params.params
1070
+ }
1071
+ });
622
1072
  }
623
-
624
- // src/mock/partner/index.ts
625
- var partner = {
626
- async addAccessoryButton(options) {
627
- console.log("[ait-devtools] partner.addAccessoryButton:", options);
628
- },
629
- async removeAccessoryButton() {
630
- console.log("[ait-devtools] partner.removeAccessoryButton");
631
- }
632
- };
633
- export {
634
- Accuracy,
635
- Analytics,
636
- GoogleAdMob,
637
- IAP,
638
- SafeAreaInsets,
639
- Storage,
640
- TossAds,
641
- aitState,
642
- appLogin,
643
- appsInTossEvent,
644
- appsInTossSignTossCert,
645
- checkoutPayment,
646
- closeView,
647
- contactsViral,
648
- env,
649
- eventLog,
650
- fetchAlbumPhotos,
651
- fetchContacts,
652
- generateHapticFeedback,
653
- getAppsInTossGlobals,
654
- getClipboardText,
655
- getCurrentLocation,
656
- getDeviceId,
657
- getGameCenterGameProfile,
658
- getGroupId,
659
- getIsTossLoginIntegratedService,
660
- getLocale,
661
- getNetworkStatus,
662
- getOperationalEnvironment,
663
- getPermission,
664
- getPlatformOS,
665
- getSafeAreaInsets,
666
- getSchemeUri,
667
- getServerTime,
668
- getTossAppVersion,
669
- getTossShareLink,
670
- getUserKeyForGame,
671
- graniteEvent,
672
- grantPromotionReward,
673
- grantPromotionRewardForGame,
674
- isMinVersionSupported,
675
- loadFullScreenAd,
676
- onVisibilityChangedByTransparentServiceWeb,
677
- openCamera,
678
- openGameCenterLeaderboard,
679
- openPermissionDialog,
680
- openURL,
681
- partner,
682
- requestPermission,
683
- requestReview,
684
- saveBase64Data,
685
- setClipboardText,
686
- setDeviceOrientation,
687
- setIosSwipeGestureEnabled,
688
- setScreenAwakeMode,
689
- setSecureScreen,
690
- share,
691
- showFullScreenAd,
692
- startUpdateLocation,
693
- submitGameCenterLeaderBoardScore,
694
- tdsEvent
1073
+ //#endregion
1074
+ //#region src/mock/partner/index.ts
1075
+ const partner = {
1076
+ async addAccessoryButton(options) {
1077
+ console.log("[@ait-co/devtools] partner.addAccessoryButton:", options);
1078
+ },
1079
+ async removeAccessoryButton() {
1080
+ console.log("[@ait-co/devtools] partner.removeAccessoryButton");
1081
+ }
695
1082
  };
1083
+ //#endregion
1084
+ export { Accuracy, Analytics, GoogleAdMob, IAP, SafeAreaInsets, Storage, TossAds, aitState, appLogin, appsInTossEvent, appsInTossSignTossCert, checkoutPayment, closeView, contactsViral, env, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getDefaultPlaceholderImages, getDeviceId, getGameCenterGameProfile, getGroupId, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPermission, getPlatformOS, getSafeAreaInsets, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, graniteEvent, grantPromotionReward, grantPromotionRewardForGame, isMinVersionSupported, loadFullScreenAd, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openPermissionDialog, openURL, partner, requestPermission, requestReview, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, showFullScreenAd, startUpdateLocation, submitGameCenterLeaderBoardScore, tdsEvent };
1085
+
696
1086
  //# sourceMappingURL=index.js.map