@ait-co/devtools 0.0.2 → 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,511 +1,1086 @@
1
- import {
2
- Accuracy,
3
- Storage,
4
- aitState,
5
- createMockProxy,
6
- fetchAlbumPhotos,
7
- fetchContacts,
8
- generateHapticFeedback,
9
- getClipboardText,
10
- getCurrentLocation,
11
- getDefaultPlaceholderImages,
12
- getNetworkStatusByMode,
13
- getPermission,
14
- openCamera,
15
- openPermissionDialog,
16
- requestPermission,
17
- saveBase64Data,
18
- setClipboardText,
19
- startUpdateLocation
20
- } from "../chunk-6PPZTREF.js";
21
-
22
- // src/mock/auth/index.ts
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
+ */
23
179
  async function appLogin() {
24
- return {
25
- authorizationCode: `mock-auth-${crypto.randomUUID()}`,
26
- referrer: aitState.state.environment === "toss" ? "DEFAULT" : "SANDBOX"
27
- };
180
+ return {
181
+ authorizationCode: `mock-auth-${crypto.randomUUID()}`,
182
+ referrer: aitState.state.environment === "toss" ? "DEFAULT" : "SANDBOX"
183
+ };
28
184
  }
29
185
  async function getIsTossLoginIntegratedService() {
30
- return aitState.state.auth.isTossLoginIntegrated;
186
+ return aitState.state.auth.isTossLoginIntegrated;
31
187
  }
32
188
  async function getUserKeyForGame() {
33
- if (!aitState.state.auth.userKeyHash) return void 0;
34
- return { hash: aitState.state.auth.userKeyHash, type: "HASH" };
189
+ if (!aitState.state.auth.userKeyHash) return void 0;
190
+ return {
191
+ hash: aitState.state.auth.userKeyHash,
192
+ type: "HASH"
193
+ };
35
194
  }
36
195
  async function appsInTossSignTossCert(_params) {
37
- console.log("[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)");
196
+ console.log("[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)");
38
197
  }
39
-
40
- // src/mock/navigation/index.ts
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
+ */
41
664
  async function closeView() {
42
- console.log("[@ait-co/devtools] closeView called");
43
- window.history.back();
665
+ console.log("[@ait-co/devtools] closeView called");
666
+ window.history.back();
44
667
  }
45
668
  async function openURL(url) {
46
- console.log("[@ait-co/devtools] openURL:", url);
47
- window.open(url, "_blank");
669
+ console.log("[@ait-co/devtools] openURL:", url);
670
+ window.open(url, "_blank");
48
671
  }
49
672
  async function share(message) {
50
- if (navigator.share) {
51
- await navigator.share({ text: message.message });
52
- return;
53
- }
54
- console.log("[@ait-co/devtools] share:", message.message);
673
+ if (navigator.share) {
674
+ await navigator.share({ text: message.message });
675
+ return;
676
+ }
677
+ console.log("[@ait-co/devtools] share:", message.message);
55
678
  }
56
679
  async function getTossShareLink(path, _ogImageUrl) {
57
- return `https://toss.im/share/mock${path}`;
680
+ return `https://toss.im/share/mock${path}`;
58
681
  }
59
682
  async function setIosSwipeGestureEnabled(_options) {
60
- console.log("[@ait-co/devtools] setIosSwipeGestureEnabled:", _options.isEnabled);
683
+ console.log("[@ait-co/devtools] setIosSwipeGestureEnabled:", _options.isEnabled);
61
684
  }
62
685
  async function setDeviceOrientation(_options) {
63
- console.log("[@ait-co/devtools] setDeviceOrientation:", _options.type);
686
+ console.log("[@ait-co/devtools] setDeviceOrientation:", _options.type);
64
687
  }
65
688
  async function setScreenAwakeMode(options) {
66
- console.log("[@ait-co/devtools] setScreenAwakeMode:", options.enabled);
67
- return { enabled: options.enabled };
689
+ console.log("[@ait-co/devtools] setScreenAwakeMode:", options.enabled);
690
+ return { enabled: options.enabled };
68
691
  }
69
692
  async function setSecureScreen(options) {
70
- console.log("[@ait-co/devtools] setSecureScreen:", options.enabled);
71
- return { enabled: options.enabled };
693
+ console.log("[@ait-co/devtools] setSecureScreen:", options.enabled);
694
+ return { enabled: options.enabled };
72
695
  }
73
696
  async function requestReview() {
74
- console.log("[@ait-co/devtools] requestReview called");
697
+ console.log("[@ait-co/devtools] requestReview called");
75
698
  }
76
699
  requestReview.isSupported = () => true;
77
700
  function getPlatformOS() {
78
- return aitState.state.platform;
701
+ return aitState.state.platform;
79
702
  }
80
703
  function getOperationalEnvironment() {
81
- return aitState.state.environment;
704
+ return aitState.state.environment;
82
705
  }
83
706
  function getTossAppVersion() {
84
- return aitState.state.appVersion;
707
+ return aitState.state.appVersion;
85
708
  }
86
709
  function isMinVersionSupported(minVersions) {
87
- const platform = aitState.state.platform;
88
- const required = platform === "ios" ? minVersions.ios : minVersions.android;
89
- if (required === "always") return true;
90
- if (required === "never") return false;
91
- const current = aitState.state.appVersion.split(".").map(Number);
92
- const min = required.split(".").map(Number);
93
- for (let i = 0; i < 3; i++) {
94
- if ((current[i] ?? 0) > (min[i] ?? 0)) return true;
95
- if ((current[i] ?? 0) < (min[i] ?? 0)) return false;
96
- }
97
- 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;
98
720
  }
99
721
  function getSchemeUri() {
100
- return aitState.state.schemeUri || window.location.pathname;
722
+ return aitState.state.schemeUri || window.location.pathname;
101
723
  }
102
724
  function getLocale() {
103
- return aitState.state.locale;
725
+ return aitState.state.locale;
104
726
  }
105
727
  function getDeviceId() {
106
- return aitState.state.deviceId;
728
+ return aitState.state.deviceId;
107
729
  }
108
730
  function getGroupId() {
109
- return aitState.state.groupId;
731
+ return aitState.state.groupId;
110
732
  }
111
733
  async function getNetworkStatus() {
112
- const modeResult = getNetworkStatusByMode();
113
- if (modeResult) return modeResult;
114
- return aitState.state.networkStatus;
734
+ const modeResult = getNetworkStatusByMode();
735
+ if (modeResult) return modeResult;
736
+ return aitState.state.networkStatus;
115
737
  }
116
738
  async function getServerTime() {
117
- return Date.now();
739
+ return Date.now();
118
740
  }
119
741
  getServerTime.isSupported = () => true;
120
- var graniteEvent = {
121
- addEventListener(event, { onEvent, onError }) {
122
- const handler = () => {
123
- try {
124
- onEvent();
125
- } catch (e) {
126
- onError?.(e instanceof Error ? e : new Error(String(e)));
127
- }
128
- };
129
- window.addEventListener(`__ait:${event}`, handler);
130
- return () => window.removeEventListener(`__ait:${event}`, handler);
131
- }
132
- };
133
- var appsInTossEvent = {
134
- addEventListener(_event, _handlers) {
135
- return () => {
136
- };
137
- }
138
- };
139
- var tdsEvent = {
140
- addEventListener(event, { onEvent }) {
141
- const handler = (e) => {
142
- const detail = e.detail;
143
- onEvent(detail);
144
- };
145
- window.addEventListener(`__ait:${event}`, handler);
146
- return () => window.removeEventListener(`__ait:${event}`, handler);
147
- }
148
- };
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
+ } };
149
764
  function onVisibilityChangedByTransparentServiceWeb(eventParams) {
150
- const handler = () => eventParams.onEvent(!document.hidden);
151
- document.addEventListener("visibilitychange", handler);
152
- return () => document.removeEventListener("visibilitychange", handler);
765
+ const handler = () => eventParams.onEvent(!document.hidden);
766
+ document.addEventListener("visibilitychange", handler);
767
+ return () => document.removeEventListener("visibilitychange", handler);
153
768
  }
154
- var env = {
155
- getDeploymentId: () => aitState.state.deploymentId
156
- };
769
+ const env = { getDeploymentId: () => aitState.state.deploymentId };
157
770
  function getAppsInTossGlobals() {
158
- return {
159
- deploymentId: aitState.state.deploymentId,
160
- brandDisplayName: aitState.state.brand.displayName,
161
- brandIcon: aitState.state.brand.icon,
162
- brandPrimaryColor: aitState.state.brand.primaryColor
163
- };
164
- }
165
- var SafeAreaInsets = {
166
- get: () => ({ ...aitState.state.safeAreaInsets }),
167
- // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 상태 변경에도 콜백이 호출된다.
168
- // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.
169
- subscribe: ({ onEvent }) => {
170
- return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
171
- }
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
+ };
777
+ }
778
+ const SafeAreaInsets = {
779
+ get: () => ({ ...aitState.state.safeAreaInsets }),
780
+ subscribe: ({ onEvent }) => {
781
+ return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
782
+ }
172
783
  };
784
+ /** @deprecated */
173
785
  function getSafeAreaInsets() {
174
- return aitState.state.safeAreaInsets.top;
786
+ return aitState.state.safeAreaInsets.top;
175
787
  }
176
-
177
- // src/mock/iap/index.ts
178
- var orderCounter = 0;
788
+ //#endregion
789
+ //#region src/mock/iap/index.ts
790
+ /**
791
+ * IAP (인앱결제) mock
792
+ */
793
+ let orderCounter = 0;
179
794
  function generateOrderId() {
180
- return `mock-order-${++orderCounter}-${Date.now()}`;
795
+ return `mock-order-${++orderCounter}-${Date.now()}`;
181
796
  }
182
797
  function buildOrderResult(sku) {
183
- const product = aitState.state.iap.products.find((p) => p.sku === sku);
184
- const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
185
- return {
186
- orderId: generateOrderId(),
187
- displayName: product?.displayName ?? "Mock Product",
188
- displayAmount: product?.displayAmount ?? "1,000\uC6D0",
189
- amount: parseInt(amountStr, 10) || 1e3,
190
- currency: "KRW",
191
- fraction: 0,
192
- miniAppIconUrl: product?.iconUrl || null
193
- };
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
+ };
194
809
  }
195
810
  async function handlePurchase(sku, processProductGrant, onEvent, onError) {
196
- const nextResult = aitState.state.iap.nextResult;
197
- await new Promise((r) => setTimeout(r, 300));
198
- if (nextResult !== "success") {
199
- onError({ code: nextResult });
200
- return;
201
- }
202
- const result = buildOrderResult(sku);
203
- try {
204
- const granted = await processProductGrant({ orderId: result.orderId });
205
- if (!granted) {
206
- onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
207
- return;
208
- }
209
- } catch (e) {
210
- onError(e);
211
- return;
212
- }
213
- aitState.patch("iap", {
214
- completedOrders: [...aitState.state.iap.completedOrders, {
215
- orderId: result.orderId,
216
- sku,
217
- status: "COMPLETED",
218
- date: (/* @__PURE__ */ new Date()).toISOString()
219
- }]
220
- });
221
- await onEvent({ type: "success", data: result });
222
- }
223
- var IAP = createMockProxy("IAP", {
224
- // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)
225
- createOneTimePurchaseOrder(params) {
226
- const sku = params.options.sku ?? params.options.productId ?? "";
227
- handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
228
- return () => {
229
- };
230
- },
231
- createSubscriptionPurchaseOrder(params) {
232
- handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
233
- return () => {
234
- };
235
- },
236
- async getProductItemList() {
237
- return {
238
- products: aitState.state.iap.products.map((p) => ({
239
- ...p,
240
- ...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
241
- }))
242
- };
243
- },
244
- async getPendingOrders() {
245
- return { orders: [...aitState.state.iap.pendingOrders] };
246
- },
247
- async getCompletedOrRefundedOrders() {
248
- return {
249
- hasNext: false,
250
- nextKey: null,
251
- orders: [...aitState.state.iap.completedOrders]
252
- };
253
- },
254
- async completeProductGrant(args) {
255
- const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
256
- if (idx !== -1) {
257
- const order = aitState.state.iap.pendingOrders[idx];
258
- const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
259
- const completedOrders = [...aitState.state.iap.completedOrders, {
260
- orderId: order.orderId,
261
- sku: order.sku,
262
- status: "COMPLETED",
263
- date: (/* @__PURE__ */ new Date()).toISOString()
264
- }];
265
- aitState.patch("iap", { pendingOrders, completedOrders });
266
- }
267
- return true;
268
- },
269
- async getSubscriptionInfo(_args) {
270
- return {
271
- subscription: {
272
- catalogId: 1,
273
- status: "ACTIVE",
274
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(),
275
- isAutoRenew: true,
276
- gracePeriodExpiresAt: null,
277
- isAccessible: true
278
- }
279
- };
280
- }
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
+ }
281
891
  });
282
892
  async function checkoutPayment(options) {
283
- const { nextResult, failReason } = aitState.state.payment;
284
- console.log("[@ait-co/devtools] checkoutPayment:", options.params.payToken);
285
- await new Promise((r) => setTimeout(r, 300));
286
- if (nextResult === "success") {
287
- return { success: true };
288
- }
289
- return { success: false, reason: failReason || "Mock payment failed" };
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
+ };
290
901
  }
291
-
292
- // src/mock/ads/index.ts
902
+ //#endregion
903
+ //#region src/mock/ads/index.ts
904
+ /**
905
+ * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)
906
+ */
293
907
  function withIsSupported(fn) {
294
- fn.isSupported = () => true;
295
- return fn;
296
- }
297
- var GoogleAdMob = createMockProxy("GoogleAdMob", {
298
- loadAppsInTossAdMob: withIsSupported((args) => {
299
- setTimeout(() => {
300
- aitState.patch("ads", { isLoaded: true });
301
- args.onEvent({ type: "loaded", data: { adGroupId: args.options?.adGroupId } });
302
- }, 200);
303
- return () => {
304
- };
305
- }),
306
- showAppsInTossAdMob: withIsSupported((args) => {
307
- if (!aitState.state.ads.isLoaded) {
308
- args.onError(new Error("Ad not loaded"));
309
- return () => {
310
- };
311
- }
312
- setTimeout(() => args.onEvent({ type: "requested" }), 50);
313
- setTimeout(() => args.onEvent({ type: "show" }), 100);
314
- setTimeout(() => args.onEvent({ type: "impression" }), 150);
315
- setTimeout(() => {
316
- args.onEvent({ type: "userEarnedReward", data: { unitType: "coins", unitAmount: 10 } });
317
- }, 1e3);
318
- setTimeout(() => {
319
- args.onEvent({ type: "dismissed" });
320
- aitState.patch("ads", { isLoaded: false });
321
- }, 1500);
322
- return () => {
323
- };
324
- }),
325
- isAppsInTossAdMobLoaded: withIsSupported(
326
- async (_options) => aitState.state.ads.isLoaded
327
- )
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)
328
946
  });
329
- var TossAds = createMockProxy("TossAds", {
330
- initialize: withIsSupported((_options) => {
331
- console.log("[@ait-co/devtools] TossAds.initialize (mock)");
332
- }),
333
- attach: withIsSupported((_adGroupId, target, _options) => {
334
- const el = typeof target === "string" ? document.querySelector(target) : target;
335
- if (el) {
336
- const placeholder = document.createElement("div");
337
- placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;";
338
- placeholder.textContent = "[@ait-co/devtools] TossAds Placeholder";
339
- el.appendChild(placeholder);
340
- }
341
- }),
342
- attachBanner: withIsSupported((_adGroupId, target, _options) => {
343
- const el = typeof target === "string" ? document.querySelector(target) : target;
344
- if (el) {
345
- const placeholder = document.createElement("div");
346
- placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:12px;text-align:center;color:#666;font-size:12px;";
347
- placeholder.textContent = "[@ait-co/devtools] Banner Ad Placeholder";
348
- el.appendChild(placeholder);
349
- }
350
- return { destroy: () => {
351
- } };
352
- }),
353
- destroy: withIsSupported((_slotId) => {
354
- }),
355
- destroyAll: withIsSupported(() => {
356
- })
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(() => {})
357
972
  });
358
- var loadFullScreenAd = withIsSupported((args) => {
359
- setTimeout(() => {
360
- aitState.patch("ads", { isLoaded: true });
361
- args.onEvent({ type: "loaded", data: { adGroupId: args.options?.adGroupId } });
362
- }, 200);
363
- return () => {
364
- };
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 () => {};
365
982
  });
366
- var showFullScreenAd = withIsSupported((args) => {
367
- if (!aitState.state.ads.isLoaded) {
368
- args.onError(new Error("Ad not loaded"));
369
- return () => {
370
- };
371
- }
372
- setTimeout(() => args.onEvent({ type: "show" }), 100);
373
- setTimeout(() => args.onEvent({ type: "dismissed" }), 1500);
374
- return () => {
375
- };
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 () => {};
376
991
  });
377
-
378
- // src/mock/game/index.ts
992
+ //#endregion
993
+ //#region src/mock/game/index.ts
994
+ /**
995
+ * 게임/프로모션 mock
996
+ */
379
997
  async function grantPromotionReward(params) {
380
- console.log("[@ait-co/devtools] grantPromotionReward:", params.params);
381
- return { key: `mock-reward-${Date.now()}` };
998
+ console.log("[@ait-co/devtools] grantPromotionReward:", params.params);
999
+ return { key: `mock-reward-${Date.now()}` };
382
1000
  }
383
1001
  async function grantPromotionRewardForGame(params) {
384
- console.log("[@ait-co/devtools] grantPromotionRewardForGame:", params.params);
385
- return { key: `mock-reward-${Date.now()}` };
1002
+ console.log("[@ait-co/devtools] grantPromotionRewardForGame:", params.params);
1003
+ return { key: `mock-reward-${Date.now()}` };
386
1004
  }
387
1005
  async function submitGameCenterLeaderBoardScore(params) {
388
- aitState.patch("game", {
389
- leaderboardScores: [...aitState.state.game.leaderboardScores, { score: params.score, timestamp: Date.now() }]
390
- });
391
- return { statusCode: "SUCCESS" };
1006
+ aitState.patch("game", { leaderboardScores: [...aitState.state.game.leaderboardScores, {
1007
+ score: params.score,
1008
+ timestamp: Date.now()
1009
+ }] });
1010
+ return { statusCode: "SUCCESS" };
392
1011
  }
393
1012
  async function getGameCenterGameProfile() {
394
- const profile = aitState.state.game.profile;
395
- if (!profile) return { statusCode: "PROFILE_NOT_FOUND" };
396
- return {
397
- statusCode: "SUCCESS",
398
- nickname: profile.nickname,
399
- profileImageUri: profile.profileImageUri
400
- };
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
+ };
401
1020
  }
402
1021
  async function openGameCenterLeaderboard() {
403
- console.log("[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)");
1022
+ console.log("[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)");
404
1023
  }
405
1024
  function contactsViral(params) {
406
- setTimeout(() => {
407
- params.onEvent({
408
- type: "close",
409
- data: {
410
- closeReason: "noReward",
411
- sentRewardsCount: 0
412
- }
413
- });
414
- }, 500);
415
- return () => {
416
- };
1025
+ setTimeout(() => {
1026
+ params.onEvent({
1027
+ type: "close",
1028
+ data: {
1029
+ closeReason: "noReward",
1030
+ sentRewardsCount: 0
1031
+ }
1032
+ });
1033
+ }, 500);
1034
+ return () => {};
417
1035
  }
418
-
419
- // src/mock/analytics/index.ts
420
- var Analytics = {
421
- screen: (params) => {
422
- aitState.logAnalytics({ type: "screen", params: params ?? {} });
423
- return Promise.resolve();
424
- },
425
- impression: (params) => {
426
- aitState.logAnalytics({ type: "impression", params: params ?? {} });
427
- return Promise.resolve();
428
- },
429
- click: (params) => {
430
- aitState.logAnalytics({ type: "click", params: params ?? {} });
431
- return Promise.resolve();
432
- }
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
+ }
433
1063
  };
434
1064
  async function eventLog(params) {
435
- aitState.logAnalytics({ type: params.log_type, params: { log_name: params.log_name, ...params.params } });
1065
+ aitState.logAnalytics({
1066
+ type: params.log_type,
1067
+ params: {
1068
+ log_name: params.log_name,
1069
+ ...params.params
1070
+ }
1071
+ });
436
1072
  }
437
-
438
- // src/mock/partner/index.ts
439
- var partner = {
440
- async addAccessoryButton(options) {
441
- console.log("[@ait-co/devtools] partner.addAccessoryButton:", options);
442
- },
443
- async removeAccessoryButton() {
444
- console.log("[@ait-co/devtools] partner.removeAccessoryButton");
445
- }
446
- };
447
- export {
448
- Accuracy,
449
- Analytics,
450
- GoogleAdMob,
451
- IAP,
452
- SafeAreaInsets,
453
- Storage,
454
- TossAds,
455
- aitState,
456
- appLogin,
457
- appsInTossEvent,
458
- appsInTossSignTossCert,
459
- checkoutPayment,
460
- closeView,
461
- contactsViral,
462
- env,
463
- eventLog,
464
- fetchAlbumPhotos,
465
- fetchContacts,
466
- generateHapticFeedback,
467
- getAppsInTossGlobals,
468
- getClipboardText,
469
- getCurrentLocation,
470
- getDefaultPlaceholderImages,
471
- getDeviceId,
472
- getGameCenterGameProfile,
473
- getGroupId,
474
- getIsTossLoginIntegratedService,
475
- getLocale,
476
- getNetworkStatus,
477
- getOperationalEnvironment,
478
- getPermission,
479
- getPlatformOS,
480
- getSafeAreaInsets,
481
- getSchemeUri,
482
- getServerTime,
483
- getTossAppVersion,
484
- getTossShareLink,
485
- getUserKeyForGame,
486
- graniteEvent,
487
- grantPromotionReward,
488
- grantPromotionRewardForGame,
489
- isMinVersionSupported,
490
- loadFullScreenAd,
491
- onVisibilityChangedByTransparentServiceWeb,
492
- openCamera,
493
- openGameCenterLeaderboard,
494
- openPermissionDialog,
495
- openURL,
496
- partner,
497
- requestPermission,
498
- requestReview,
499
- saveBase64Data,
500
- setClipboardText,
501
- setDeviceOrientation,
502
- setIosSwipeGestureEnabled,
503
- setScreenAwakeMode,
504
- setSecureScreen,
505
- share,
506
- showFullScreenAd,
507
- startUpdateLocation,
508
- submitGameCenterLeaderBoardScore,
509
- 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
+ }
510
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
+
511
1086
  //# sourceMappingURL=index.js.map