@ait-co/devtools 0.1.41 → 0.1.44

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 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/mock/state.ts","../../src/mock/observe.ts","../../src/mock/proxy.ts","../../src/mock/ads/index.ts","../../src/mock/analytics/index.ts","../../src/mock/auth/index.ts","../../src/mock/device/_helpers.ts","../../src/mock/permissions.ts","../../src/mock/device/camera.ts","../../src/mock/device/clipboard.ts","../../src/mock/device/contacts.ts","../../src/mock/device/haptic.ts","../../src/mock/device/location.ts","../../src/mock/device/network.ts","../../src/mock/device/pdf.ts","../../src/mock/device/storage.ts","../../src/mock/game/index.ts","../../src/mock/iap/index.ts","../../src/mock/navigation/index.ts","../../src/mock/notification.ts","../../src/mock/partner/index.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type { AitSdkCall } from '../mcp/ait-source.js';\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\nexport type { AitSdkCall, AitSdkCallFidelity } from '../mcp/ait-source.js';\nexport type {\n AitNavBarType,\n AnalyticsLogEntry,\n AppOrientation,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LandscapeSide,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotchType,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\n\n/** SDK 호출 로그 ring buffer 상한 */\nconst SDK_CALL_LOG_MAX = 200;\n\nexport interface AitDevtoolsState {\n // 환경\n platform: PlatformOS;\n environment: OperationalEnvironment;\n appVersion: string;\n locale: string;\n schemeUri: string;\n groupId: string;\n deploymentId: string;\n deviceId: string;\n\n // 브랜드\n brand: {\n displayName: string;\n icon: string;\n primaryColor: string;\n };\n\n // 네트워크\n networkStatus: NetworkStatus;\n\n // 네비게이션 동작 — real은 native bridge로 발화하는 no-op API들의 마지막 호출값을\n // 관측 가능한 state로 mirror (real ground-truth: devtools#171 on-device relay).\n // null = 앱이 아직 호출 안 함(real 기본 동작 = iOS 엣지 스와이프 뒤로가기 enabled).\n navigation: {\n iosSwipeGestureEnabled: boolean | null;\n };\n\n // 권한\n permissions: Record<PermissionName, PermissionStatus>;\n\n // 위치\n location: MockLocation;\n\n // Safe Area\n safeAreaInsets: SafeAreaInsets;\n\n // 연락처\n contacts: MockContact[];\n\n // IAP\n iap: {\n products: MockIapProduct[];\n nextResult: IapNextResult;\n pendingOrders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n completedOrders: Array<{\n orderId: string;\n sku: string;\n status: 'COMPLETED' | 'REFUNDED';\n date: string;\n }>;\n };\n\n // 결제 (TossPay)\n payment: {\n nextResult: 'success' | 'fail';\n failReason: string;\n };\n\n // 로그인\n auth: {\n isLoggedIn: boolean;\n isTossLoginIntegrated: boolean;\n userKeyHash: string;\n anonymousKeyHash: string;\n };\n\n // 알림\n notification: {\n nextResult: NotificationAgreementResult;\n };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\n forceNoFill: boolean;\n lastEvent: { type: string; timestamp: number } | null;\n /** AdMob reward 단위 타입 (기본: 'coins') */\n rewardUnitType: string;\n /** AdMob reward 단위 수량 (기본: 10) */\n rewardAmount: number;\n };\n\n // 게임\n game: {\n profile: { nickname: string; profileImageUri: string } | null;\n leaderboardScores: Array<{ score: string; timestamp: number }>;\n };\n\n // 분석 로그\n analyticsLog: AnalyticsLogEntry[];\n\n // SDK 호출 로그 (ring buffer, 상한 SDK_CALL_LOG_MAX)\n sdkCallLog: AitSdkCall[];\n\n // 디바이스 API 모드\n deviceModes: DeviceModes;\n\n // mock 모드용 더미 데이터\n mockData: MockData;\n\n // mock 활성화 상태\n panelEditable: boolean;\n\n // 뷰포트 시뮬레이션 (devtools 전용, SDK와 무관)\n viewport: ViewportState;\n}\n\nconst DEFAULT_STATE: AitDevtoolsState = {\n platform: 'ios',\n environment: 'sandbox',\n appVersion: '5.240.0',\n locale: 'ko-KR',\n schemeUri: '/',\n groupId: 'mock-group-id',\n deploymentId: 'mock-deployment-id',\n deviceId: '',\n\n brand: {\n displayName: 'Mock App',\n icon: '',\n primaryColor: '#3182F6',\n },\n\n networkStatus: 'WIFI',\n\n // null = 앱이 setIosSwipeGestureEnabled를 아직 호출 안 함.\n navigation: {\n iosSwipeGestureEnabled: null,\n },\n\n permissions: {\n clipboard: 'allowed',\n contacts: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n camera: 'allowed',\n microphone: 'notDetermined',\n },\n\n location: {\n coords: {\n latitude: 37.5665,\n longitude: 126.978,\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE',\n },\n\n // iPhone 15 Pro relay 실측값(devtools#190)과 정합: partner WebView portrait에서\n // SafeAreaInsets.get()이 반환한 top=54(토스 nav bar 높이), bottom=34(home indicator).\n // env(safe-area-inset-top)는 0이었으므로 OS 노치는 이 top에 들어가지 않는다.\n // preset이 'none'/'custom'이면 syncSafeAreaFromViewport가 건드리지 않으므로 이 값이\n // SafeAreaInsets.get()의 out-of-box 계약값으로 남는다. preset을 고르면 그 값으로 sync됨.\n safeAreaInsets: { top: 54, bottom: 34, left: 0, right: 0 },\n\n contacts: [\n { name: '홍길동', phoneNumber: '010-1234-5678' },\n { name: '김토스', phoneNumber: '010-9876-5432' },\n ],\n\n iap: {\n products: [\n {\n sku: 'mock-gem-100',\n type: 'CONSUMABLE',\n displayName: '보석 100개',\n displayAmount: '1,000원',\n iconUrl: '',\n description: '게임에서 사용할 수 있는 보석 100개',\n },\n ],\n nextResult: 'success',\n pendingOrders: [],\n completedOrders: [],\n },\n\n payment: {\n nextResult: 'success',\n failReason: '',\n },\n\n auth: {\n isLoggedIn: true,\n isTossLoginIntegrated: true,\n userKeyHash: 'mock-user-hash-abc123',\n anonymousKeyHash: 'mock-anon-hash-xyz789',\n },\n\n notification: {\n nextResult: 'newAgreement',\n },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n forceNoFill: false,\n lastEvent: null,\n rewardUnitType: 'coins',\n rewardAmount: 10,\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\n\n sdkCallLog: [],\n\n deviceModes: {\n camera: 'mock',\n photos: 'mock',\n location: 'mock',\n network: 'mock',\n // 'mock' so the clipboard mock is self-contained. With 'web' the mock\n // calls `navigator.clipboard.readText()` directly, which — when paired\n // with `@ait-co/polyfill` — recurses: polyfill routes `navigator.clipboard`\n // back to the SDK's `getClipboardText`, which is this mock, which calls\n // `navigator.clipboard.readText`, … Users who want true browser\n // clipboard integration can flip this to 'web' from the panel.\n clipboard: 'mock',\n },\n\n mockData: {\n images: [],\n clipboardText: '',\n },\n\n panelEditable: true,\n\n viewport: {\n preset: 'none',\n orientation: 'auto',\n appOrientation: null,\n landscapeSide: 'left',\n customWidth: 402,\n customHeight: 874,\n frame: false,\n aitNavBar: true,\n aitNavBarType: 'partner',\n },\n};\n\nfunction generateDeviceId(): string {\n const stored = localStorage.getItem('__ait_device_id');\n if (stored) return stored;\n const id = crypto.randomUUID();\n localStorage.setItem('__ait_device_id', id);\n return id;\n}\n\nexport class AitStateManager {\n private _state: AitDevtoolsState;\n private _listeners = new Set<Listener>();\n private _inTransaction = false;\n\n constructor() {\n this._state = structuredClone(DEFAULT_STATE);\n try {\n this._state.deviceId = generateDeviceId();\n } catch {\n this._state.deviceId = `mock-device-${Math.random().toString(36).slice(2)}`;\n }\n }\n\n get state(): AitDevtoolsState {\n return this._state;\n }\n\n update(partial: Partial<AitDevtoolsState>) {\n this._state = { ...this._state, ...partial };\n this._notify();\n }\n\n /** 중첩 객체 업데이트용 */\n patch<K extends keyof AitDevtoolsState>(key: K, partial: Partial<AitDevtoolsState[K]>) {\n const current = this._state[key];\n if (typeof current === 'object' && current !== null && !Array.isArray(current)) {\n this._state = {\n ...this._state,\n [key]: { ...(current as Record<string, unknown>), ...(partial as Record<string, unknown>) },\n };\n } else {\n this._state = { ...this._state, [key]: partial as AitDevtoolsState[K] };\n }\n this._notify();\n }\n\n subscribe(listener: Listener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n /**\n * 한 묶음의 update/patch 호출을 묶어 listener notify 1회로 만든다.\n * preset 적용처럼 여러 슬라이스를 동시에 바꿀 때 panel re-render 폭주를\n * 방지한다. 중첩 호출은 outermost transaction이 끝날 때 한 번만 notify\n * (inner도 throw해도 outer finally가 flag를 복구한다).\n *\n * Rollback은 없다 — `fn`이 throw해도 그때까지의 state 변경은 유지된다.\n * 구독자가 partial state를 영원히 못 보는 사고를 막기 위해, throw 여부와\n * 무관하게 항상 한 번 notify한 뒤 throw를 propagate한다. DB transaction이\n * 아니라 \"여러 mutation을 한 notify로 묶는 batch\"라고 생각하면 된다.\n *\n * Listener는 throw해선 안 된다 — finally 안의 `_notify()`가 throw하면 원래\n * `fn`의 throw를 덮어버린다. 우리 구독자는 panel re-render뿐이라 실제\n * 발생 사례는 없지만, 외부에서 listener를 등록할 때 주의.\n */\n transaction(fn: () => void): void {\n if (this._inTransaction) {\n fn();\n return;\n }\n this._inTransaction = true;\n try {\n fn();\n } finally {\n this._inTransaction = false;\n this._notify();\n }\n }\n\n /** 분석 로그 추가 */\n logAnalytics(entry: Omit<AnalyticsLogEntry, 'timestamp'>) {\n this._state = {\n ...this._state,\n analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }],\n };\n this._notify();\n }\n\n /**\n * SDK 호출 로그 추가 (ring buffer, 상한 SDK_CALL_LOG_MAX).\n * `observe()`가 호출하고, proxy의 KNOWN_UNIMPLEMENTED 경로도 직접 호출한다.\n */\n logSdkCall(entry: AitSdkCall) {\n const log = this._state.sdkCallLog;\n const next = log.length >= SDK_CALL_LOG_MAX ? log.slice(1 - SDK_CALL_LOG_MAX) : log;\n this._state = { ...this._state, sdkCallLog: [...next, entry] };\n this._notify();\n }\n\n /** 이벤트 트리거 (backEvent, homeEvent 등) */\n trigger(event: string) {\n window.dispatchEvent(new CustomEvent(`__ait:${event}`));\n }\n\n reset() {\n const deviceId = this._state.deviceId;\n this._state = { ...structuredClone(DEFAULT_STATE), deviceId };\n this._notify();\n }\n\n private _notify() {\n if (this._inTransaction) return;\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\n// `tsdown.config.ts`는 mock/panel/unplugin entry를 별도 config object로 빌드한다\n// (\"every entry is self-contained\"). 그 결과 소비자가 두 entry(예: `@ait-co/devtools` +\n// `@ait-co/devtools/panel`)를 동시에 import하면 `state.ts`가 entry별로 따로 번들되어\n// `AitStateManager` 인스턴스가 entry당 1개씩 만들어진다. panel이 toggle한 state는\n// mock SDK가 보는 state와 다른 인스턴스가 되어 모든 토글이 비기능이 된다.\n//\n// build pipeline을 건드리지 않고 runtime guard로 해결한다: globalThis에 인스턴스를\n// 캐시해 같은 페이지의 모든 entry가 동일 인스턴스를 공유하도록 한다.\nconst SINGLETON_KEY = '__aitDevtoolsStateSingleton__';\ntype GlobalWithSingleton = typeof globalThis & { [SINGLETON_KEY]?: AitStateManager };\nconst globalRef = globalThis as GlobalWithSingleton;\nif (!globalRef[SINGLETON_KEY]) {\n globalRef[SINGLETON_KEY] = new AitStateManager();\n}\nexport const aitState: AitStateManager = globalRef[SINGLETON_KEY]!;\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * SDK 호출 관측 래퍼.\n *\n * `observe(apiName, fidelity, fn)` — fn의 시그니처를 그대로 보존하면서\n * 호출 시 args·resolve·reject 결과를 `aitState.sdkCallLog`에 기록한다.\n *\n * **signature 보존 절대 조건**: 제네릭 pass-through로 `__typecheck.ts`의\n * `Assert<Mock, Original>` 불변을 유지한다. observe()로 감싼 함수는\n * 원본과 동일한 타입을 가진다.\n */\n\nimport type { AitSdkCallFidelity } from '../mcp/ait-source.js';\nimport { aitState } from './state.js';\n\n/**\n * fn을 observe로 감싼다.\n *\n * @param apiName - 로그에 기록할 SDK 메서드 이름 (예: `'setScreenAwakeMode'`)\n * @param fidelity - 이 mock의 fidelity grade ('faithful' | 'partial' | 'inert')\n * @param fn - 실제 mock 구현체. 시그니처를 그대로 통과시킨다.\n * @returns fn과 동일한 타입의 래퍼 함수\n */\nexport function observe<TArgs extends unknown[], TReturn>(\n apiName: string,\n fidelity: AitSdkCallFidelity,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn {\n return (...args: TArgs): TReturn => {\n const timestamp = Date.now();\n // args를 JSON-safe하게 직렬화한다. 직렬화할 수 없는 값(함수·순환 참조 등)은\n // 문자열 표현으로 대체해 로그가 깨지지 않도록 한다.\n const safeArgs: unknown[] = args.map((a) => safeSerialize(a));\n\n const result = fn(...args);\n\n if (result instanceof Promise) {\n // pending 상태로 먼저 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'pending',\n fidelity,\n });\n\n // resolve/reject 결과로 업데이트 (ring buffer에서 마지막 pending 항목 덮어쓰기)\n (result as Promise<unknown>).then(\n (value) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(value),\n fidelity,\n });\n },\n (err: unknown) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'rejected',\n error: err instanceof Error ? err.message : String(err),\n fidelity,\n });\n },\n );\n\n return result;\n }\n\n // 동기 반환 — 즉시 resolved로 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(result),\n fidelity,\n });\n\n return result;\n };\n}\n\n/**\n * 값을 JSON-safe한 형태로 변환한다.\n * - null / primitive — 그대로.\n * - 함수 — `'[Function: name]'` 문자열.\n * - 기타 객체 — JSON.stringify 실패 시 `'[unserializable]'`.\n */\nfunction safeSerialize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;\n if (typeof value !== 'object') return value;\n try {\n return JSON.parse(JSON.stringify(value));\n } catch {\n return '[unserializable]';\n }\n}\n","/**\n * 미구현 API용 Proxy 트립와이어.\n *\n * 미구현 프로퍼티에 접근하면 throw한다. 이는 \"devtools에서는 멀쩡히 돌지만\n * 실 SDK에선 실제로 동작하는\" 시나리오를 차단하기 위한 의도적 선택이다.\n * mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지\n * 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을\n * 포함해 사용자가 mock 누락을 제보할 수 있게 한다.\n *\n * ## KNOWN_UNIMPLEMENTED 정책\n * SDK에 존재하는 것으로 알려져 있으나 현재 mock이 없는 API 이름만 이 집합에 둔다.\n * 이 경우에만 throw 대신 🔴 inert no-op을 반환하고 sdkCallLog에 기록한다.\n * 완전히 미지의 이름은 여전히 throw — \"잘 되는 척\" 방지.\n */\n\nimport { aitState } from './state.js';\n\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\n\n/**\n * SDK에 존재하나 mock이 아직 없는 것으로 확인된 이름 목록.\n * 새 API가 SDK에 추가되면 여기에 추가하고 별도 PR에서 mock 구현으로 이동한다.\n * 확인되지 않은 이름은 절대 여기에 추가하지 않는다 — throw가 더 안전하다.\n */\nconst KNOWN_UNIMPLEMENTED = new Set<string>([\n // 예: 'someNewSdkApi',\n]);\n\nexport function createMockProxy<T extends Record<string, unknown>>(\n moduleName: string,\n implementations: T,\n): T {\n return new Proxy(implementations, {\n get(target, prop) {\n // 심볼 접근(Symbol.toPrimitive, Symbol.iterator 등)은 프레임워크/런타임이\n // 내부적으로 호출하므로 throw하면 console.log, 구조분해 등이 깨진다.\n if (typeof prop === 'symbol') return undefined;\n if (prop in target) return target[prop];\n\n const name = String(prop);\n\n // SDK에 존재하나 mock 미구현으로 확인된 API — throw 대신 🔴 inert no-op 반환.\n if (KNOWN_UNIMPLEMENTED.has(name)) {\n return (...args: unknown[]): undefined => {\n console.warn(\n `[@ait-co/devtools] ${moduleName}.${name} is known-unimplemented (🔴 inert). ` +\n `Returning undefined. Please file or upvote an issue: ${ISSUES_URL}`,\n );\n aitState.logSdkCall({\n method: `${moduleName}.${name}`,\n args: args,\n timestamp: Date.now(),\n status: 'resolved',\n result: undefined,\n fidelity: 'inert',\n });\n return undefined;\n };\n }\n\n throw new Error(\n `[@ait-co/devtools] ${moduleName}.${prop} is not mocked. ` +\n `This API may exist in @apps-in-toss/web-framework, ` +\n `but devtools' mock does not cover it yet. ` +\n `Please file an issue: ${ISSUES_URL}`,\n );\n },\n }) as T;\n}\n","/**\n * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)\n *\n * 변경 이력 (#196):\n * - slot 레지스트리로 TossAds destroy/destroyAll 누수 수정 (🟡→🟢)\n * - attachBanner BannerSlotCallbacks 발화 (onAdRendered/onAdImpression/onNoFill 등)\n * - initialize onInitialized/onInitializationFailed 발화\n * - AdMob reward 파라미터화 (state.ads.rewardUnitType/rewardAmount)\n * - 모든 호출 observe()로 sdkCallLog에 기록\n */\n\nimport { observe } from '../observe.js';\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\nfunction withIsSupported<T extends (...args: never[]) => unknown>(\n fn: T,\n): T & { isSupported: () => boolean } {\n (fn as T & { isSupported: () => boolean }).isSupported = () => true;\n return fn as T & { isSupported: () => boolean };\n}\n\n// --- slot 레지스트리 (TossAds destroy 누수 수정) ---\n// attachBanner가 생성한 placeholder를 slotId로 추적해서\n// destroy/destroyAll이 실제 el.remove()를 수행할 수 있게 한다.\nconst _slotRegistry = new Map<string, HTMLElement>();\n\nlet _slotCounter = 0;\nfunction _nextSlotId(adGroupId: string): string {\n _slotCounter += 1;\n return `mock-slot-${adGroupId}-${_slotCounter}`;\n}\n\n/** 테스트에서 레지스트리를 초기화할 수 있게 export */\nexport function _resetSlotRegistry(): void {\n _slotRegistry.clear();\n _slotCounter = 0;\n}\n\n// --- Google AdMob ---\n\nexport const GoogleAdMob = createMockProxy('GoogleAdMob', {\n loadAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.loadAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'requested' }), 50);\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'impression' }), 150);\n setTimeout(() => {\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n });\n }, 1000);\n setTimeout(() => {\n args.onEvent({ type: 'dismissed' });\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n return () => {};\n },\n ),\n ),\n\n isAppsInTossAdMobLoaded: withIsSupported(\n observe(\n 'GoogleAdMob.isAppsInTossAdMobLoaded',\n 'faithful',\n async (_options: { adGroupId?: string }): Promise<boolean> => aitState.state.ads.isLoaded,\n ),\n ),\n});\n\n// --- TossAds ---\n\nexport const TossAds = createMockProxy('TossAds', {\n initialize: withIsSupported(\n observe(\n 'TossAds.initialize',\n 'partial',\n (options: {\n callbacks?: { onInitialized?: () => void; onInitializationFailed?: (error: Error) => void };\n }): void => {\n // forceNoFill을 initialization failure로도 활용한다\n if (aitState.state.ads.forceNoFill) {\n options.callbacks?.onInitializationFailed?.(new Error('No fill'));\n return;\n }\n options.callbacks?.onInitialized?.();\n },\n ),\n ),\n\n attach: withIsSupported(\n observe(\n 'TossAds.attach',\n 'partial',\n (_adGroupId: string, target: string | HTMLElement, _options?: unknown): void => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText =\n 'background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;';\n placeholder.textContent = '[@ait-co/devtools] TossAds Placeholder';\n el.appendChild(placeholder);\n }\n },\n ),\n ),\n\n attachBanner: withIsSupported(\n observe(\n 'TossAds.attachBanner',\n 'faithful',\n (\n adGroupId: string,\n target: string | HTMLElement,\n options?: {\n theme?: 'auto' | 'light' | 'dark';\n tone?: 'blackAndWhite' | 'grey';\n variant?: 'card' | 'expanded';\n callbacks?: {\n onAdRendered?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdViewable?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdClicked?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdImpression?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdFailedToRender?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n error: { code: number; message: string; domain?: string };\n }) => void;\n onNoFill?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n }) => void;\n };\n },\n ): { destroy: () => void } => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n const slotId = _nextSlotId(adGroupId);\n\n const placeholder = document.createElement('div');\n\n // AttachBannerOptions를 placeholder 스타일에 반영\n const theme = options?.theme ?? 'auto';\n const variant = options?.variant ?? 'card';\n const isDark =\n theme === 'dark' ||\n (theme === 'auto' &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const bg = isDark ? '#1a1a1a' : '#f0f0f0';\n const textColor = isDark ? '#aaa' : '#666';\n const borderColor = isDark ? '#555' : '#999';\n const height = variant === 'expanded' ? '120px' : '60px';\n\n placeholder.dataset.aitSlotId = slotId;\n placeholder.style.cssText = `background:${bg};border:1px dashed ${borderColor};padding:8px 12px;text-align:center;color:${textColor};font-size:12px;min-height:${height};display:flex;align-items:center;justify-content:center;`;\n placeholder.textContent = `[@ait-co/devtools] Banner Ad (${variant})`;\n\n if (el) {\n el.appendChild(placeholder);\n _slotRegistry.set(slotId, placeholder);\n }\n\n const destroySlot = () => {\n const registered = _slotRegistry.get(slotId);\n if (registered) {\n registered.remove();\n _slotRegistry.delete(slotId);\n }\n };\n\n // 콜백 발화 (setTimeout으로 비동기 — 실 SDK와 동일하게 렌더 완료 후)\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n options?.callbacks?.onNoFill?.({\n slotId,\n adGroupId,\n adMetadata: {},\n });\n options?.callbacks?.onAdFailedToRender?.({\n slotId,\n adGroupId,\n adMetadata: {},\n error: { code: 0, message: 'No fill' },\n });\n return;\n }\n\n const eventPayload = {\n slotId,\n adGroupId,\n adMetadata: { creativeId: `mock-creative-${slotId}`, requestId: `mock-req-${slotId}` },\n };\n options?.callbacks?.onAdRendered?.(eventPayload);\n options?.callbacks?.onAdImpression?.(eventPayload);\n }, 100);\n\n return { destroy: destroySlot };\n },\n ),\n ),\n\n destroy: withIsSupported(\n observe('TossAds.destroy', 'faithful', (slotId: string): void => {\n const el = _slotRegistry.get(slotId);\n if (el) {\n el.remove();\n _slotRegistry.delete(slotId);\n }\n }),\n ),\n\n destroyAll: withIsSupported(\n observe('TossAds.destroyAll', 'faithful', (): void => {\n for (const el of _slotRegistry.values()) {\n el.remove();\n }\n _slotRegistry.clear();\n }),\n ),\n});\n\n// --- FullScreen Ad ---\n\nexport const loadFullScreenAd = withIsSupported(\n observe(\n 'loadFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'dismissed' }), 1500);\n return () => {};\n },\n ),\n);\n","/**\n * Analytics mock\n */\n\nimport { aitState } from '../state.js';\n\ntype Primitive = string | number | boolean | null | undefined | symbol;\ntype LoggerParams = { log_name?: string } & Record<string, Primitive>;\n\n// Analytics methods return `Promise<void> | undefined` to match the original SDK signature,\n// so they cannot use `async` (which always returns a Promise).\nexport const Analytics = {\n screen: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'screen', params: params ?? {} });\n return Promise.resolve();\n },\n impression: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'impression', params: params ?? {} });\n return Promise.resolve();\n },\n click: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'click', params: params ?? {} });\n return Promise.resolve();\n },\n};\n\nexport async function eventLog(params: {\n log_name: string;\n log_type: 'debug' | 'info' | 'warn' | 'error' | 'event' | 'screen' | 'impression' | 'click';\n params: Record<string, Primitive>;\n}): Promise<void> {\n aitState.logAnalytics({\n type: params.log_type,\n params: { log_name: params.log_name, ...params.params },\n });\n}\n","/**\n * 인증/로그인 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport async function appLogin(): Promise<{\n authorizationCode: string;\n referrer: 'DEFAULT' | 'SANDBOX';\n}> {\n return {\n authorizationCode: `mock-auth-${crypto.randomUUID()}`,\n referrer: aitState.state.environment === 'toss' ? 'DEFAULT' : 'SANDBOX',\n };\n}\n\nexport async function getIsTossLoginIntegratedService(): Promise<boolean | undefined> {\n return aitState.state.auth.isTossLoginIntegrated;\n}\n\nexport async function getUserKeyForGame(): Promise<\n { hash: string; type: 'HASH' } | 'INVALID_CATEGORY' | 'ERROR' | undefined\n> {\n if (!aitState.state.auth.userKeyHash) return undefined;\n return { hash: aitState.state.auth.userKeyHash, type: 'HASH' };\n}\n\nexport async function getAnonymousKey(): Promise<\n { hash: string; type: 'HASH' } | 'ERROR' | undefined\n> {\n if (!aitState.state.auth.anonymousKeyHash) return undefined;\n return { hash: aitState.state.auth.anonymousKeyHash, type: 'HASH' };\n}\n\nexport interface AppsInTossSignTossCertParams {\n txId: string;\n}\n\nexport async function appsInTossSignTossCert(_params: AppsInTossSignTossCertParams): Promise<void> {\n console.log('[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)');\n}\n","/**\n * 디바이스 모듈 내부 공유 헬퍼\n */\n\nimport { aitState } from '../state.js';\n\n// --- Placeholder Image Generator ---\n\nfunction generatePlaceholderImage(\n width: number,\n height: number,\n text: string,\n color: string,\n): string {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n // jsdom 등 Canvas API 미지원 환경에서는 간단한 SVG data URI 반환\n 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>`;\n return `data:image/svg+xml;base64,${btoa(svg)}`;\n }\n ctx.fillStyle = color;\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = 'white';\n ctx.font = '16px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, width / 2, height / 2);\n return canvas.toDataURL('image/png');\n}\n\nconst DEFAULT_PLACEHOLDERS = [\n { text: 'Mock Photo 1', color: '#3182F6' },\n { text: 'Mock Photo 2', color: '#27ae60' },\n { text: 'Mock Photo 3', color: '#e67e22' },\n];\n\nlet cachedPlaceholders: string[] | null = null;\n\nexport function getDefaultPlaceholderImages(): string[] {\n if (!cachedPlaceholders) {\n cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) =>\n generatePlaceholderImage(320, 240, p.text, p.color),\n );\n }\n return [...cachedPlaceholders];\n}\n\n/** @internal device 모듈 내부 전용 */\nexport function getMockImages(): string[] {\n const images = aitState.state.mockData.images;\n if (images.length > 0) return images;\n return getDefaultPlaceholderImages();\n}\n\n// --- Prompt Mode Helper ---\n\nconst PROMPT_TIMEOUT_MS = 30_000;\n\n/** @internal device 모듈 내부 전용 */\nexport function waitForPromptResponse<T>(type: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const eventName = `__ait:prompt-response:${type}`;\n const cancelName = '__ait:prompt-cancel';\n\n function cleanup() {\n clearTimeout(timer);\n window.removeEventListener(eventName, handler);\n window.removeEventListener(cancelName, cancelHandler);\n }\n\n const timer = setTimeout(() => {\n cleanup();\n const panelMounted = !!document.querySelector('.ait-panel');\n const hint = panelMounted\n ? 'Please provide input via the DevTools panel.'\n : 'Is @ait-co/devtools/panel imported?';\n reject(\n new Error(\n `[@ait-co/devtools] Prompt timeout for \"${type}\" after ${PROMPT_TIMEOUT_MS / 1000}s. ${hint}`,\n ),\n );\n }, PROMPT_TIMEOUT_MS);\n\n const handler = (e: Event) => {\n cleanup();\n resolve((e as CustomEvent).detail as T);\n };\n\n const cancelHandler = () => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt cancelled for \"${type}\"`));\n };\n\n window.addEventListener(eventName, handler);\n window.addEventListener(cancelName, cancelHandler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type } }));\n });\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState } from './state.js';\nimport type { PermissionName, PermissionStatus } from './types.js';\n\nexport async function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return aitState.state.permissions[name];\n}\n\nexport async function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return 'allowed';\n}\n\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: string;\n}): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n} {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(\n `[@ait-co/devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`,\n );\n }\n}\n","/**\n * Camera & Album Photos & Album Items mock\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport { getMockImages, waitForPromptResponse } from './_helpers.js';\n\n// --- 타입 ---\n\nexport type AlbumItemType = 'PHOTO' | 'VIDEO';\n\n// --- Camera ---\n\nasync function openCameraMock(): Promise<{ id: string; dataUri: string }> {\n const images = getMockImages();\n return { id: crypto.randomUUID(), dataUri: images[0] };\n}\n\nasync function openCameraWeb(): Promise<{ id: string; dataUri: string }> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.capture = 'environment';\n let settled = false;\n input.onchange = () => {\n settled = true;\n const file = input.files?.[0];\n if (!file) {\n reject(new Error('No file selected'));\n return;\n }\n const reader = new FileReader();\n reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n };\n // Detect file picker cancel via focus heuristic.\n // Note: unreliable on some mobile browsers and Safari where focus events differ.\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function openCameraPrompt(): Promise<{ id: string; dataUri: string }> {\n const dataUri = await waitForPromptResponse<string>('camera');\n return { id: crypto.randomUUID(), dataUri };\n}\n\nconst _openCamera = async (_options?: {\n base64?: boolean;\n maxWidth?: number;\n}): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n const mode = aitState.state.deviceModes.camera;\n if (mode === 'web') return openCameraWeb();\n if (mode === 'prompt') return openCameraPrompt();\n return openCameraMock();\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nasync function fetchAlbumPhotosMock(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const images = getMockImages();\n return images.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nasync function fetchAlbumPhotosWeb(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n reject(new Error('No files selected'));\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<{ id: string; dataUri: string }>((res, rej) => {\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumPhotosPrompt(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nconst _fetchAlbumPhotos = async (options?: {\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumPhotosWeb(maxCount);\n if (mode === 'prompt') return fetchAlbumPhotosPrompt(maxCount);\n return fetchAlbumPhotosMock(maxCount);\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Album Items (사진·동영상 복합 선택) ---\n\nexport interface FetchAlbumItemsOptions {\n types?: AlbumItemType[];\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}\n\nexport interface AlbumItemResponse {\n id: string;\n dataUri: string;\n type: AlbumItemType;\n}\n\nasync function fetchAlbumItemsMock(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n const images = getMockImages();\n return images\n .slice(0, maxCount)\n .filter(() => types.includes('PHOTO'))\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nasync function fetchAlbumItemsWeb(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = types.includes('VIDEO') ? 'image/*,video/*' : 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n resolve([]);\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<AlbumItemResponse>((res, rej) => {\n const itemType: AlbumItemType = file.type.startsWith('video/') ? 'VIDEO' : 'PHOTO';\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string, type: itemType });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) resolve([]);\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumItemsPrompt(maxCount: number): Promise<AlbumItemResponse[]> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris\n .slice(0, maxCount)\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nconst _fetchAlbumItems = async (options?: FetchAlbumItemsOptions): Promise<AlbumItemResponse[]> => {\n checkPermission('photos', 'fetchAlbumItems');\n const maxCount = options?.maxCount ?? 10;\n const types = options?.types ?? ['PHOTO'];\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumItemsWeb(maxCount, types);\n if (mode === 'prompt') return fetchAlbumItemsPrompt(maxCount);\n return fetchAlbumItemsMock(maxCount, types);\n};\nexport const fetchAlbumItems = withPermission(_fetchAlbumItems, 'photos');\n","/**\n * Clipboard mock\n * mock/web 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') return aitState.state.mockData.clipboardText;\n // web mode (default)\n try {\n return await navigator.clipboard.readText();\n } catch {\n return '';\n }\n};\nexport const getClipboardText = withPermission(_getClipboardText, 'clipboard');\n\nconst _setClipboardText = async (text: string): Promise<void> => {\n checkPermission('clipboard', 'setClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') {\n aitState.patch('mockData', { clipboardText: text });\n return;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n","/**\n * Contacts mock\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _fetchContacts = async (options: {\n size: number;\n offset: number;\n query?: { contains?: string };\n}) => {\n checkPermission('contacts', 'fetchContacts');\n let contacts = aitState.state.contacts;\n if (options.query?.contains) {\n const q = options.query.contains.toLowerCase();\n contacts = contacts.filter(\n (c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q),\n );\n }\n const sliced = contacts.slice(options.offset, options.offset + options.size);\n const nextOffset = options.offset + options.size;\n return {\n result: sliced,\n nextOffset: nextOffset < contacts.length ? nextOffset : null,\n done: nextOffset >= contacts.length,\n };\n};\nexport const fetchContacts = withPermission(_fetchContacts, 'contacts');\n","/**\n * Haptic Feedback & saveBase64Data mock\n *\n * generateHapticFeedback — 영역 3 (하드웨어 API 관측):\n * - 10종 HapticFeedbackType을 navigator.vibrate 패턴으로 매핑(근사, best-effort).\n * - `typeof navigator.vibrate === 'function'` 가드 — API 없는 환경에서 throw 없이 skip.\n * - sdkCallLog에 🟡(partial)로 기록. params: { hapticType, vibrated: boolean }.\n * - 시그니처 불변 — __typecheck.ts의 Assert<Mock, Original> 통과.\n */\n\nimport { aitState } from '../state.js';\nimport type { HapticFeedbackType } from '../types.js';\n\n/**\n * HapticFeedbackType 10종 → navigator.vibrate 패턴 매핑.\n * 숫자: 진동 ms. 배열: [진동, 정지, 진동, …] 교대 패턴.\n */\nexport const HAPTIC_VIBRATE_PATTERN: Record<HapticFeedbackType, VibratePattern> = {\n tickWeak: 10,\n tap: 20,\n tickMedium: 30,\n softMedium: 40,\n basicWeak: 15,\n basicMedium: 50,\n success: [10, 40, 10],\n error: [40, 30, 40],\n wiggle: [20, 20, 20, 20, 20],\n confetti: [10, 20, 10, 20, 10, 20, 10],\n};\n\nexport async function generateHapticFeedback(options: { type: HapticFeedbackType }): Promise<void> {\n const timestamp = Date.now();\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n\n const pattern = HAPTIC_VIBRATE_PATTERN[options.type] ?? 30;\n const vibrated = typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\n\n aitState.logSdkCall({\n method: 'generateHapticFeedback',\n args: [{ type: options.type }],\n timestamp,\n status: 'resolved',\n result: { hapticType: options.type, vibrated },\n fidelity: 'partial',\n });\n}\n\nexport async function saveBase64Data(params: {\n data: string;\n fileName: string;\n mimeType: string;\n}): Promise<void> {\n const a = document.createElement('a');\n a.href = `data:${params.mimeType};base64,${params.data}`;\n a.download = params.fileName;\n a.click();\n}\n","/**\n * Location mock (getCurrentLocation, startUpdateLocation)\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport type { MockLocation } from '../types.js';\nimport { waitForPromptResponse } from './_helpers.js';\n\nenum Accuracy {\n Lowest = 1,\n Low = 2,\n Balanced = 3,\n High = 4,\n Highest = 5,\n BestForNavigation = 6,\n}\n\nexport { Accuracy };\n\nfunction buildLocation(): MockLocation {\n return {\n coords: { ...aitState.state.location.coords },\n timestamp: Date.now(),\n accessLocation: aitState.state.location.accessLocation,\n };\n}\n\n// -- getCurrentLocation --\n\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n return buildLocation();\n}\n\nasync function getCurrentLocationWeb(): Promise<MockLocation> {\n return new Promise((resolve) => {\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n resolve(buildLocation());\n return;\n }\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n resolve({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n () => {\n console.warn('[@ait-co/devtools] Geolocation failed, falling back to mock');\n resolve(buildLocation());\n },\n );\n });\n}\n\nasync function getCurrentLocationPrompt(): Promise<MockLocation> {\n return waitForPromptResponse<MockLocation>('location');\n}\n\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return getCurrentLocationWeb();\n if (mode === 'prompt') return getCurrentLocationPrompt();\n return getCurrentLocationMock();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\n// -- startUpdateLocation --\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction startUpdateLocationMock(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n const loc = buildLocation();\n loc.coords.latitude += (Math.random() - 0.5) * 0.0001;\n loc.coords.longitude += (Math.random() - 0.5) * 0.0001;\n onEvent(loc);\n }, interval);\n return () => clearInterval(id);\n}\n\nfunction startUpdateLocationWeb(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, onError } = eventParams;\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n return startUpdateLocationMock(eventParams);\n }\n const watchId = navigator.geolocation.watchPosition(\n (pos) => {\n onEvent({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n (err) => onError(err),\n );\n return () => navigator.geolocation.clearWatch(watchId);\n}\n\nfunction startUpdateLocationPrompt(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent } = eventParams;\n const handler = (e: Event) => {\n onEvent((e as CustomEvent).detail as MockLocation);\n };\n window.addEventListener('__ait:prompt-response:location-update', handler);\n window.dispatchEvent(\n new CustomEvent('__ait:prompt-request', { detail: { type: 'location-update' } }),\n );\n return () => window.removeEventListener('__ait:prompt-response:location-update', handler);\n}\n\nconst _startUpdateLocation = (eventParams: StartUpdateLocationEventParams): (() => void) => {\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return startUpdateLocationWeb(eventParams);\n if (mode === 'prompt') return startUpdateLocationPrompt(eventParams);\n return startUpdateLocationMock(eventParams);\n};\nexport const startUpdateLocation = withPermission(_startUpdateLocation, 'geolocation');\n","/**\n * Network Status mock (mode-aware helper)\n * navigation 모듈에서 사용. circular dep 방지를 위해 device에 위치.\n */\n\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\n/**\n * Web mode: uses navigator.connection.effectiveType (4g/3g/2g) and navigator.onLine.\n * Limitations: WIFI, 5G, WWAN cannot be detected via the Network Information API.\n * Falls back to state-based value when effectiveType is unavailable.\n */\nexport function getNetworkStatusByMode(): NetworkStatus | null {\n const mode = aitState.state.deviceModes.network;\n if (mode === 'mock') return null; // use default state-based logic\n if (mode === 'web') {\n if (!navigator.onLine) return 'OFFLINE';\n const conn = (navigator as unknown as Record<string, unknown>).connection as\n | { effectiveType?: string }\n | undefined;\n if (conn?.effectiveType) {\n const mapping: Record<string, NetworkStatus> = {\n '4g': '4G',\n '3g': '3G',\n '2g': '2G',\n 'slow-2g': '2G',\n };\n return mapping[conn.effectiveType] ?? 'UNKNOWN';\n }\n return aitState.state.networkStatus;\n }\n // prompt mode: not supported for network, fall back to mock\n return null;\n}\n","/**\n * PDF Viewer mock\n * 네이티브 PDF 뷰어 동작을 시뮬레이션한다.\n * 권한 게이트 없음. 모드 분기 없음.\n */\n\nexport interface OpenPDFViewerParams {\n data: string;\n filename?: string;\n}\n\nexport type OpenPDFViewerResult = 'CLOSE';\n\n/**\n * Base64로 인코딩된 PDF 데이터를 네이티브 PDF 뷰어로 여는 mock.\n * mock 환경에서는 즉시 `'CLOSE'`를 반환한다.\n */\nexport async function openPDFViewer(_params: OpenPDFViewerParams): Promise<OpenPDFViewerResult> {\n // 실 SDK와 동일하게 비동기로 resolve한다.\n await Promise.resolve();\n return 'CLOSE';\n}\n","/**\n * Storage mock\n * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리\n */\n\nimport { createMockProxy } from '../proxy.js';\n\nexport const Storage = createMockProxy('Storage', {\n getItem: async (key: string): Promise<string | null> => {\n return localStorage.getItem(`__ait_storage:${key}`);\n },\n setItem: async (key: string, value: string): Promise<void> => {\n localStorage.setItem(`__ait_storage:${key}`, value);\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n },\n clearItems: async (): Promise<void> => {\n const keys = Object.keys(localStorage).filter((k) => k.startsWith('__ait_storage:'));\n for (const k of keys) {\n localStorage.removeItem(k);\n }\n },\n});\n","/**\n * 게임/프로모션 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport async function grantPromotionReward(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[@ait-co/devtools] grantPromotionReward:', params.params);\n return { key: `mock-reward-${Date.now()}` };\n}\n\nexport async function grantPromotionRewardForGame(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[@ait-co/devtools] grantPromotionRewardForGame:', params.params);\n return { key: `mock-reward-${Date.now()}` };\n}\n\nexport async function submitGameCenterLeaderBoardScore(params: {\n score: string;\n}): Promise<\n | { statusCode: 'SUCCESS' | 'LEADERBOARD_NOT_FOUND' | 'PROFILE_NOT_FOUND' | 'UNPARSABLE_SCORE' }\n | undefined\n> {\n aitState.patch('game', {\n leaderboardScores: [\n ...aitState.state.game.leaderboardScores,\n { score: params.score, timestamp: Date.now() },\n ],\n });\n return { statusCode: 'SUCCESS' };\n}\n\nexport async function getGameCenterGameProfile(): Promise<\n | { statusCode: 'SUCCESS'; nickname: string; profileImageUri: string }\n | { statusCode: 'PROFILE_NOT_FOUND' }\n | undefined\n> {\n const profile = aitState.state.game.profile;\n if (!profile) return { statusCode: 'PROFILE_NOT_FOUND' };\n return {\n statusCode: 'SUCCESS',\n nickname: profile.nickname,\n profileImageUri: profile.profileImageUri,\n };\n}\n\nexport async function openGameCenterLeaderboard(): Promise<void> {\n console.log('[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)');\n}\n\ninterface ContactsViralEvent {\n type: string;\n data: Record<string, unknown>;\n}\n\nexport function contactsViral(params: {\n options: { moduleId: string };\n onEvent: (event: ContactsViralEvent) => void;\n onError: (error: unknown) => void;\n}): () => void {\n setTimeout(() => {\n params.onEvent({\n type: 'close',\n data: {\n closeReason: 'noReward',\n sentRewardsCount: 0,\n },\n });\n }, 500);\n return () => {};\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\n// orderCounter는 모듈 레벨 상태로 reset()에 의해 초기화되지 않는다.\n// 테스트에서는 orderId를 stringContaining('mock-order-')로 검증하여 카운터 값에 의존하지 않는다.\nlet orderCounter = 0;\n\nfunction generateOrderId(): string {\n return `mock-order-${++orderCounter}-${Date.now()}`;\n}\n\ninterface IapCreateOneTimePurchaseOrderOptions {\n options: {\n sku?: string;\n productId?: string;\n processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface CreateSubscriptionPurchaseOrderOptions {\n options: {\n sku: string;\n offerId?: string | null;\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface IapOrderResult {\n orderId: string;\n displayName: string;\n displayAmount: string;\n amount: number;\n currency: string;\n fraction: number;\n miniAppIconUrl: string | null;\n}\n\nfunction buildOrderResult(sku: string): IapOrderResult {\n const product = aitState.state.iap.products.find((p) => p.sku === sku);\n const amountStr = product?.displayAmount?.replace(/[^0-9]/g, '') ?? '1000';\n return {\n orderId: generateOrderId(),\n displayName: product?.displayName ?? 'Mock Product',\n displayAmount: product?.displayAmount ?? '1,000원',\n amount: parseInt(amountStr, 10) || 1000,\n currency: 'KRW',\n fraction: 0,\n miniAppIconUrl: product?.iconUrl || null,\n };\n}\n\nasync function handlePurchase(\n sku: string,\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>,\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>,\n onError: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n const nextResult = aitState.state.iap.nextResult;\n\n // 비동기 시뮬레이션 (실제로는 결제 UI가 뜨는 시간)\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult !== 'success') {\n onError({ code: nextResult });\n return;\n }\n\n const result = buildOrderResult(sku);\n\n try {\n const granted = await processProductGrant({ orderId: result.orderId });\n if (!granted) {\n onError({ code: 'PRODUCT_NOT_GRANTED_BY_PARTNER' });\n return;\n }\n } catch (e) {\n onError(e);\n return;\n }\n\n // 주문 완료 기록\n aitState.patch('iap', {\n completedOrders: [\n ...aitState.state.iap.completedOrders,\n {\n orderId: result.orderId,\n sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ],\n });\n\n await onEvent({ type: 'success', data: result });\n}\n\nexport const IAP = createMockProxy('IAP', {\n // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)\n createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void {\n const sku = params.options.sku ?? params.options.productId ?? '';\n handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch(\n (e) => console.error('[@ait-co/devtools] IAP unexpected error:', e),\n );\n return () => {};\n },\n\n createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {\n handlePurchase(\n params.options.sku,\n params.options.processProductGrant,\n params.onEvent,\n params.onError,\n ).catch((e) => console.error('[@ait-co/devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n async getProductItemList(): Promise<{ products: unknown[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })),\n };\n },\n\n async getPendingOrders(): Promise<{\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n }> {\n return { orders: [...aitState.state.iap.pendingOrders] };\n },\n\n async getCompletedOrRefundedOrders(): Promise<{\n hasNext: boolean;\n nextKey?: string | null;\n orders: Array<{ orderId: string; sku: string; status: 'COMPLETED' | 'REFUNDED'; date: string }>;\n }> {\n return {\n hasNext: false,\n nextKey: null,\n orders: [...aitState.state.iap.completedOrders],\n };\n },\n\n async completeProductGrant(args: { params: { orderId: string } }): Promise<boolean> {\n // pending → completed 전이\n const idx = aitState.state.iap.pendingOrders.findIndex(\n (o) => o.orderId === args.params.orderId,\n );\n if (idx !== -1) {\n const order = aitState.state.iap.pendingOrders[idx];\n const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);\n const completedOrders = [\n ...aitState.state.iap.completedOrders,\n {\n orderId: order.orderId,\n sku: order.sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ];\n aitState.patch('iap', { pendingOrders, completedOrders });\n }\n return true;\n },\n\n async getSubscriptionInfo(_args: { params: { orderId: string } }) {\n return {\n subscription: {\n catalogId: 1,\n status: 'ACTIVE',\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n isAutoRenew: true,\n gracePeriodExpiresAt: null,\n isAccessible: true,\n },\n };\n },\n});\n\n// --- TossPay ---\n\nexport async function checkoutPayment(options: {\n params: { payToken: string };\n}): Promise<{ success: boolean; reason?: string }> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] checkoutPayment:', options.params.payToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock payment failed' };\n}\n\nexport const requestTossPayPaysBilling = Object.assign(\n async function requestTossPayPaysBilling(options: {\n params: { wrappedToken: string };\n }): Promise<{ success: boolean; reason?: string } | undefined> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] requestTossPayPaysBilling:', options.params.wrappedToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock billing auth failed' };\n },\n { isSupported: () => true },\n);\n","/**\n * 화면/네비게이션/이벤트 mock\n */\n\nimport { getNetworkStatusByMode } from '../device/index.js';\nimport { observe } from '../observe.js';\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\nexport async function closeView(): Promise<void> {\n console.log('[@ait-co/devtools] closeView called');\n window.history.back();\n}\n\nexport async function openURL(url: string): Promise<void> {\n console.log('[@ait-co/devtools] openURL:', url);\n window.open(url, '_blank');\n}\n\nexport async function share(message: { message: string }): Promise<void> {\n if (navigator.share) {\n await navigator.share({ text: message.message });\n return;\n }\n console.log('[@ait-co/devtools] share:', message.message);\n}\n\nexport async function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n return `https://toss.im/share/mock${path}`;\n}\n\nexport async function setIosSwipeGestureEnabled(options: { isEnabled: boolean }): Promise<void> {\n console.log('[@ait-co/devtools] setIosSwipeGestureEnabled:', options.isEnabled);\n // real(토스 WebView)에선 이 호출이 native bridge로 발화한다(devtools#171 실측). mock은\n // 그 \"마지막 호출값\"을 관측 가능한 state로 mirror해, toss-gated 가드(예: sdk-example\n // useDisableIosSwipeGestureInToss)가 실제로 돌았는지를 AIT.getMockState로 대조할 수 있게 한다.\n aitState.patch('navigation', { iosSwipeGestureEnabled: options.isEnabled });\n}\n\nexport async function setDeviceOrientation(options: {\n type: 'portrait' | 'landscape';\n}): Promise<void> {\n const current = aitState.state.viewport.orientation;\n if (current === 'auto') {\n console.log('[@ait-co/devtools] setDeviceOrientation:', options.type);\n // appOrientation은 Panel이 'auto'일 때 effective orientation을 결정하는 별도 필드.\n // viewport.orientation은 사용자 의도이므로 SDK가 임의로 덮어쓰지 않는다 — 그래야\n // 앱이 같은 방향으로 여러 번 호출해도 매번 정상 반영된다.\n aitState.patch('viewport', { appOrientation: options.type });\n return;\n }\n console.warn(\n `[@ait-co/devtools] setDeviceOrientation(${options.type}) ignored — Panel is forcing \"${current}\". Change the Viewport tab's orientation to \"auto\" to let the app control rotation.`,\n );\n}\n\nexport const setScreenAwakeMode = observe(\n 'setScreenAwakeMode',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nexport const setSecureScreen = observe(\n 'setSecureScreen',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nconst _requestReviewImpl = observe('requestReview', 'inert', async (): Promise<void> => {\n console.log('[@ait-co/devtools] requestReview called');\n});\nexport const requestReview: typeof _requestReviewImpl & { isSupported: () => boolean } =\n _requestReviewImpl as typeof _requestReviewImpl & { isSupported: () => boolean };\nrequestReview.isSupported = () => true;\n\n// --- 환경 정보 ---\n\nexport function getPlatformOS(): 'ios' | 'android' {\n return aitState.state.platform;\n}\n\nexport function getOperationalEnvironment(): 'toss' | 'sandbox' {\n return aitState.state.environment;\n}\n\nexport function getTossAppVersion(): string {\n return aitState.state.appVersion;\n}\n\nexport function isMinVersionSupported(minVersions: { android: string; ios: string }): boolean {\n const platform = aitState.state.platform;\n const required = platform === 'ios' ? minVersions.ios : minVersions.android;\n if (required === 'always') return true;\n if (required === 'never') return false;\n\n const current = aitState.state.appVersion.split('.').map(Number);\n const min = required.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n if ((current[i] ?? 0) > (min[i] ?? 0)) return true;\n if ((current[i] ?? 0) < (min[i] ?? 0)) return false;\n }\n return true; // equal\n}\n\nexport function getSchemeUri(): string {\n return aitState.state.schemeUri || window.location.pathname;\n}\n\nexport function getLocale(): string {\n return aitState.state.locale;\n}\n\nexport function getDeviceId(): string {\n return aitState.state.deviceId;\n}\n\nexport function getGroupId(): string {\n return aitState.state.groupId;\n}\n\nexport async function getNetworkStatus(): Promise<NetworkStatus> {\n const modeResult = getNetworkStatusByMode();\n if (modeResult) return modeResult;\n return aitState.state.networkStatus;\n}\n\nexport async function getServerTime(): Promise<number | undefined> {\n return Date.now();\n}\n(getServerTime as unknown as { isSupported: () => boolean }).isSupported = () => true;\n\n// --- 이벤트 시스템 ---\n\ninterface GraniteEventMap {\n backEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n homeEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n}\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEventMap>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEventMap[K]['onEvent'];\n onError?: GraniteEventMap[K]['onError'];\n options?: GraniteEventMap[K]['options'];\n },\n ): () => void {\n const handler = () => {\n try {\n onEvent();\n } catch (e) {\n onError?.(e instanceof Error ? e : new Error(String(e)));\n }\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport const appsInTossEvent = {\n addEventListener<K extends string>(\n _event: K,\n _handlers: {\n onEvent: (...args: unknown[]) => void;\n onError?: (error: Error) => void;\n options?: unknown;\n },\n ): () => void {\n return () => {};\n },\n};\n\ninterface TdsEventMap {\n navigationAccessoryEvent: {\n onEvent: (data: { id: string }) => void;\n onError?: (error: Error) => void;\n options: undefined;\n };\n}\n\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEventMap>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEventMap[K]['onEvent'];\n onError?: TdsEventMap[K]['onError'];\n options?: TdsEventMap[K]['options'];\n },\n ): () => void {\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n onEvent(detail);\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport function onVisibilityChangedByTransparentServiceWeb(eventParams: {\n options: { callbackId: string };\n onEvent: (isVisible: boolean) => void;\n onError: (error: unknown) => void;\n}): () => void {\n const handler = () => eventParams.onEvent(!document.hidden);\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n}\n\n// --- env / globals ---\n\nexport const env = {\n getDeploymentId: () => aitState.state.deploymentId,\n};\n\nexport function getAppsInTossGlobals() {\n return {\n deploymentId: aitState.state.deploymentId,\n brandDisplayName: aitState.state.brand.displayName,\n brandIcon: aitState.state.brand.icon,\n brandPrimaryColor: aitState.state.brand.primaryColor,\n };\n}\n\n// --- SafeAreaInsets ---\n\ntype SafeAreaInsetsValue = { top: number; bottom: number; left: number; right: number };\ntype SafeAreaInsetsSubscribeHandler = { onEvent: (data: SafeAreaInsetsValue) => void };\n\nexport const SafeAreaInsets = {\n get: (): SafeAreaInsetsValue => ({ ...aitState.state.safeAreaInsets }),\n // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.\n // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.\n subscribe: ({ onEvent }: SafeAreaInsetsSubscribeHandler): (() => void) => {\n return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));\n },\n};\n\n/** @deprecated */\nexport function getSafeAreaInsets(): number {\n return aitState.state.safeAreaInsets.top;\n}\n","/**\n * 알림 동의 mock\n *\n * SDK는 callback-style: `requestNotificationAgreement(params)`이 즉시 cancel 함수를\n * 반환하고, 결과는 `params.onEvent`로 전달된다. mock도 같은 모양을 흉내내며,\n * 결과는 panel(Notifications 탭)이 토글한 `aitState.state.notification.nextResult`를\n * 그대로 사용한다.\n *\n * `agreementRejected`도 정상 결과의 한 종류이므로 `onEvent`로 전달한다.\n * `onError`는 `onEvent` 호출 자체가 throw할 때만 들어간다 (실제 SDK도 reject를\n * error가 아닌 event type으로 표현한다).\n */\n\nimport { aitState } from './state.js';\nimport type { NotificationAgreementResult } from './types.js';\n\ninterface RequestNotificationAgreementOptions {\n options: { templateCode: string };\n onEvent: (result: { type: NotificationAgreementResult }) => void;\n onError: (error: unknown) => void | Promise<void>;\n}\n\nexport function requestNotificationAgreement(\n params: RequestNotificationAgreementOptions,\n): () => void {\n let cancelled = false;\n\n Promise.resolve().then(async () => {\n if (cancelled) return;\n const type = aitState.state.notification.nextResult;\n\n console.log(\n '[@ait-co/devtools] requestNotificationAgreement:',\n params.options.templateCode,\n '→',\n type,\n );\n\n try {\n params.onEvent({ type });\n } catch (e) {\n await params.onError(e);\n }\n });\n\n return () => {\n cancelled = true;\n };\n}\n","/**\n * Partner / TDS mock\n */\n\ninterface AddAccessoryButtonOptions {\n id: string;\n title: string;\n icon: { name: string };\n}\n\nexport const partner = {\n async addAccessoryButton(options: AddAccessoryButtonOptions): Promise<void> {\n console.log('[@ait-co/devtools] partner.addAccessoryButton:', options);\n },\n async removeAccessoryButton(): Promise<void> {\n console.log('[@ait-co/devtools] partner.removeAccessoryButton');\n },\n};\n","/**\n * 사용자 저장 preset CRUD. localStorage `__ait_preset:<id>` 한 키당 하나로 저장한다.\n * 패널-내부 storage(`__ait_btn_pos`, `__ait_device_id`, `__ait_storage:`)와 같은\n * 패턴 — 새 storage 도입 없음.\n *\n * 외부 의존성 0. SSR 환경(Node)에서 import만 되어도 안전하도록 모든 접근은\n * `localStorage` 존재 여부를 확인한다.\n */\n\nimport type { MockPreset, MockPresetState } from './presets.js';\n\nconst PREFIX = '__ait_preset:';\n\nfunction safeLocalStorage(): Storage | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Storage에서 읽은 임의 JSON을 MockPreset으로 검증. id/label 필수, state는\n * object여야 함. 실패하면 null — caller가 storage entry를 무시하거나 정리하면 된다.\n *\n * `state`의 내부 키/값은 검증하지 않는다. `applyPreset`이 `pickKnownKeys`로\n * 키만 거른 뒤 그대로 state에 패치하므로 잘못된 enum 값이 통과될 수 있지만,\n * mock state라 보안 위협은 없다 — 새 enum 값이 추가됐을 때 저장된 preset을\n * reject하지 않으려는 의도.\n */\nfunction parsePreset(raw: string): MockPreset | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!isObject(parsed)) return null;\n const { id, label, description, state } = parsed;\n if (typeof id !== 'string' || id.length === 0) return null;\n if (typeof label !== 'string' || label.length === 0) return null;\n if (!isObject(state)) return null;\n return {\n id,\n label,\n description: typeof description === 'string' ? description : undefined,\n state: state as MockPresetState,\n };\n } catch {\n return null;\n }\n}\n\nexport function listUserPresets(): MockPreset[] {\n const ls = safeLocalStorage();\n if (!ls) return [];\n const out: MockPreset[] = [];\n for (let i = 0; i < ls.length; i++) {\n const key = ls.key(i);\n if (!key?.startsWith(PREFIX)) continue;\n const raw = ls.getItem(key);\n if (!raw) continue;\n const preset = parsePreset(raw);\n if (preset) out.push(preset);\n }\n return out.sort((a, b) => a.label.localeCompare(b.label));\n}\n\n/**\n * Preset을 저장한다. label에서 slug를 derive — 같은 slug가 이미 있으면 `-2`, `-3`\n * suffix를 붙여 새 entry를 만든다 (기존 entry 덮어쓰기 아님). UI는 label만 받으면 된다.\n *\n * Throws:\n * - label trim한 뒤 빈 문자열일 때\n * - localStorage 미가용 환경일 때 (SSR 등)\n * - `setItem` 실패 (`QuotaExceededError` 등) — caller가 처리해야 함\n */\nexport function saveUserPreset(\n label: string,\n state: MockPresetState,\n description?: string,\n): MockPreset {\n const trimmed = label.trim();\n if (trimmed.length === 0) {\n throw new Error('Preset label cannot be empty');\n }\n const ls = safeLocalStorage();\n if (!ls) throw new Error('localStorage not available');\n const id = generateId(trimmed, ls);\n const preset: MockPreset = {\n id,\n label: trimmed,\n state,\n ...(description !== undefined && description.length > 0 ? { description } : {}),\n };\n ls.setItem(PREFIX + id, JSON.stringify(preset));\n return preset;\n}\n\nexport function deleteUserPreset(id: string): void {\n const ls = safeLocalStorage();\n if (!ls) return;\n ls.removeItem(PREFIX + id);\n}\n\n/** 충돌 시 `-2`, `-3` 등 suffix를 붙여 unique한 id 만든다. */\nfunction generateId(label: string, ls: Storage): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'preset';\n let candidate = base;\n let n = 2;\n while (ls.getItem(PREFIX + candidate) !== null) {\n candidate = `${base}-${n}`;\n n += 1;\n }\n return candidate;\n}\n","/**\n * Mock state preset library.\n *\n * 자주 쓰는 QA 시나리오(권한 거부, 오프라인, 미로그인 등)를 한 클릭으로 적용할 수 있는\n * preset 정의 + apply 유틸. 토글 한 번에 여러 mock 키가 동시에 일정 상태가 되어야 하는\n * 경우 매번 손으로 맞추는 번잡함을 제거한다.\n *\n * Preset state는 `aitState`의 일부만 다룬다 — preset이 어떤 키도 건드리지 않으면 해당\n * 키는 현재 값을 유지한다. forward-compat: schema 외 키가 들어와도 무시된다.\n */\n\nimport type { AitDevtoolsState } from './state.js';\nimport { aitState } from './state.js';\n\n/**\n * Preset이 덮어쓸 수 있는 mock state slice. 모든 키는 optional —\n * 한 preset이 모든 분야를 정의할 필요 없다.\n *\n * 일부러 좁게 잡았다: viewport / brand / mockData / analyticsLog 등 QA 시나리오와\n * 직접 관련 없는 영역은 preset 대상에서 제외한다 (preset 적용으로 unrelated state가\n * 흔들리는 사고 방지). 필요해지면 추가.\n *\n * `iap` slice는 일부러 `nextResult`만 노출한다 — products / pendingOrders /\n * completedOrders는 array/object 비교가 까다롭고 QA 시나리오에서 강제할 일이 거의\n * 없다. `captureCurrentState` / `matchesPreset` 의 iap 처리 범위와 동기화.\n */\nexport interface MockPresetState {\n networkStatus?: AitDevtoolsState['networkStatus'];\n permissions?: Partial<AitDevtoolsState['permissions']>;\n auth?: Partial<AitDevtoolsState['auth']>;\n iap?: { nextResult?: AitDevtoolsState['iap']['nextResult'] };\n ads?: Partial<AitDevtoolsState['ads']>;\n payment?: Partial<AitDevtoolsState['payment']>;\n}\n\nexport interface MockPreset {\n id: string;\n label: string;\n description?: string;\n state: MockPresetState;\n}\n\nexport const builtInPresets: readonly MockPreset[] = [\n {\n id: 'all-allowed',\n label: 'All allowed (default-ish)',\n description: '모든 권한 허용, WIFI, 로그인됨, IAP success',\n state: {\n networkStatus: 'WIFI',\n permissions: {\n camera: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n clipboard: 'allowed',\n contacts: 'allowed',\n microphone: 'allowed',\n },\n auth: { isLoggedIn: true },\n iap: { nextResult: 'success' },\n ads: { forceNoFill: false },\n payment: { nextResult: 'success', failReason: '' },\n },\n },\n {\n id: 'permission-denied',\n label: 'Permissions denied',\n description: 'camera / photos / geolocation / contacts 거부',\n state: {\n permissions: {\n camera: 'denied',\n photos: 'denied',\n geolocation: 'denied',\n contacts: 'denied',\n },\n },\n },\n {\n id: 'offline',\n label: 'Offline',\n description: 'getNetworkStatus → OFFLINE, IAP NETWORK_ERROR',\n state: {\n networkStatus: 'OFFLINE',\n iap: { nextResult: 'NETWORK_ERROR' },\n payment: { nextResult: 'fail', failReason: 'NETWORK_ERROR' },\n },\n },\n {\n id: 'logged-out',\n label: 'Logged out',\n description: 'auth.isLoggedIn=false. login flow 검증용',\n state: {\n auth: { isLoggedIn: false },\n },\n },\n {\n id: 'iap-pending',\n label: 'IAP payment pending',\n description: '결제 진행 중 분기 검증',\n state: {\n iap: { nextResult: 'PAYMENT_PENDING' },\n },\n },\n {\n id: 'ads-no-fill',\n label: 'Ads — no fill',\n description: '광고 fill 실패 분기 검증',\n state: {\n networkStatus: 'WIFI',\n ads: { forceNoFill: true },\n },\n },\n];\n\n/**\n * Preset의 nested slice를 검증된 키만 골라서 풀어낸다. Forward-compat 차원에서\n * 알지 못하는 키는 drop, drop된 키 전부를 모아 한 번에 warn한다.\n *\n * Value 단위 검증은 하지 않는다 — `permissions.camera`에 enum 외 값이 들어와도\n * 그대로 통과한다. mock state라 잘못된 값은 mock 함수 분기 결과만 흔든다.\n * 새 enum 값이 추가됐을 때 저장된 preset을 reject하지 않으려는 의도.\n */\nfunction pickKnownKeys<T extends object>(\n input: unknown,\n allowed: readonly (keyof T)[],\n): Partial<T> {\n if (typeof input !== 'object' || input === null) return {};\n const out: Partial<T> = {};\n const dropped: string[] = [];\n for (const [key, value] of Object.entries(input)) {\n if ((allowed as readonly string[]).includes(key)) {\n (out as Record<string, unknown>)[key] = value;\n } else {\n dropped.push(key);\n }\n }\n if (dropped.length > 0) {\n console.warn(`[@ait-co/devtools] Preset dropped unknown keys: ${dropped.join(', ')}`);\n }\n return out;\n}\n\nconst PERMISSION_KEYS = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n] as const;\nconst AUTH_KEYS = ['isLoggedIn', 'isTossLoginIntegrated', 'userKeyHash'] as const;\nconst IAP_KEYS = ['nextResult'] as const;\nconst ADS_KEYS = ['isLoaded', 'nextEvent', 'forceNoFill', 'lastEvent'] as const;\nconst PAYMENT_KEYS = ['nextResult', 'failReason'] as const;\n\n/**\n * Preset state를 현재 `aitState`에 적용한다. 정의된 키만 덮어쓰고, 알지 못하는 키는\n * 조용히 drop한다 (한 번 warn). 여러 슬라이스를 적용해도 listener notify는 한 번이다\n * (`aitState.transaction` 사용 — panel re-render 폭주 방지).\n */\nexport function applyPreset(state: MockPresetState): void {\n aitState.transaction(() => {\n if (state.networkStatus !== undefined) {\n aitState.update({ networkStatus: state.networkStatus });\n }\n if (state.permissions !== undefined) {\n aitState.patch(\n 'permissions',\n pickKnownKeys<AitDevtoolsState['permissions']>(state.permissions, PERMISSION_KEYS),\n );\n }\n if (state.auth !== undefined) {\n aitState.patch('auth', pickKnownKeys<AitDevtoolsState['auth']>(state.auth, AUTH_KEYS));\n }\n if (state.iap !== undefined) {\n const picked = pickKnownKeys<{ nextResult: AitDevtoolsState['iap']['nextResult'] }>(\n state.iap,\n IAP_KEYS,\n );\n aitState.patch('iap', picked);\n }\n if (state.ads !== undefined) {\n aitState.patch('ads', pickKnownKeys<AitDevtoolsState['ads']>(state.ads, ADS_KEYS));\n }\n if (state.payment !== undefined) {\n aitState.patch(\n 'payment',\n pickKnownKeys<AitDevtoolsState['payment']>(state.payment, PAYMENT_KEYS),\n );\n }\n });\n}\n\n/**\n * Preset의 모든 정의된 슬라이스가 현재 state와 일치하는지 검사. UI에서 dirty\n * indicator를 그릴 때 쓴다.\n *\n * 일치한다 = preset이 정의한 키 전부가 그대로다. preset이 정의하지 않은 키는\n * 비교 대상이 아니다 — preset은 partial이므로 다른 토글이 바뀌어도 dirty가 아니다.\n */\nexport function matchesPreset(snapshot: AitDevtoolsState, preset: MockPresetState): boolean {\n if (preset.networkStatus !== undefined && snapshot.networkStatus !== preset.networkStatus) {\n return false;\n }\n if (preset.permissions !== undefined) {\n for (const k of PERMISSION_KEYS) {\n const want = preset.permissions[k];\n if (want !== undefined && snapshot.permissions[k] !== want) return false;\n }\n }\n if (preset.auth !== undefined) {\n for (const k of AUTH_KEYS) {\n const want = preset.auth[k];\n if (want !== undefined && snapshot.auth[k] !== want) return false;\n }\n }\n if (preset.iap !== undefined) {\n if (preset.iap.nextResult !== undefined && snapshot.iap.nextResult !== preset.iap.nextResult) {\n return false;\n }\n }\n if (preset.ads !== undefined) {\n if (preset.ads.forceNoFill !== undefined && snapshot.ads.forceNoFill !== preset.ads.forceNoFill)\n return false;\n if (preset.ads.isLoaded !== undefined && snapshot.ads.isLoaded !== preset.ads.isLoaded)\n return false;\n if (preset.ads.nextEvent !== undefined && snapshot.ads.nextEvent !== preset.ads.nextEvent)\n return false;\n }\n if (preset.payment !== undefined) {\n for (const k of PAYMENT_KEYS) {\n const want = preset.payment[k];\n if (want !== undefined && snapshot.payment[k] !== want) return false;\n }\n }\n return true;\n}\n\n/**\n * 현재 state에서 preset에 저장할 만한 슬라이스를 추출. \"save current as preset\"에서 쓴다.\n */\nexport function captureCurrentState(snapshot: AitDevtoolsState): MockPresetState {\n return {\n networkStatus: snapshot.networkStatus,\n permissions: { ...snapshot.permissions },\n auth: {\n isLoggedIn: snapshot.auth.isLoggedIn,\n isTossLoginIntegrated: snapshot.auth.isTossLoginIntegrated,\n userKeyHash: snapshot.auth.userKeyHash,\n },\n iap: { nextResult: snapshot.iap.nextResult },\n ads: {\n forceNoFill: snapshot.ads.forceNoFill,\n isLoaded: snapshot.ads.isLoaded,\n nextEvent: snapshot.ads.nextEvent,\n },\n payment: { ...snapshot.payment },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/mock/state.ts","../../src/mock/observe.ts","../../src/mock/proxy.ts","../../src/mock/ads/index.ts","../../src/mock/analytics/index.ts","../../src/mock/auth/index.ts","../../src/mock/device/_helpers.ts","../../src/mock/permissions.ts","../../src/mock/device/camera.ts","../../src/mock/device/clipboard.ts","../../src/mock/device/contacts.ts","../../src/mock/device/haptic.ts","../../src/mock/device/location.ts","../../src/mock/device/network.ts","../../src/mock/device/pdf.ts","../../src/mock/device/storage.ts","../../src/mock/game/index.ts","../../src/mock/iap/index.ts","../../src/mock/navigation/index.ts","../../src/mock/notification.ts","../../src/mock/partner/index.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type { AitSdkCall } from '../mcp/ait-source.js';\nimport type {\n AnalyticsLogEntry,\n DeviceModes,\n IapNextResult,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n ViewportState,\n} from './types.js';\n\nexport type { AitSdkCall, AitSdkCallFidelity } from '../mcp/ait-source.js';\nexport type {\n AitNavBarType,\n AnalyticsLogEntry,\n AppOrientation,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LandscapeSide,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n NotchType,\n NotificationAgreementResult,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n SafeAreaInsets,\n SafeAreaProvenance,\n ViewportOrientation,\n ViewportPreset,\n ViewportPresetId,\n ViewportState,\n} from './types.js';\n\ntype Listener = () => void;\n\n/** SDK 호출 로그 ring buffer 상한 */\nconst SDK_CALL_LOG_MAX = 200;\n\nexport interface AitDevtoolsState {\n // 환경\n platform: PlatformOS;\n environment: OperationalEnvironment;\n appVersion: string;\n locale: string;\n schemeUri: string;\n groupId: string;\n deploymentId: string;\n deviceId: string;\n\n // 브랜드\n brand: {\n displayName: string;\n icon: string;\n primaryColor: string;\n };\n\n // 네트워크\n networkStatus: NetworkStatus;\n\n // 네비게이션 동작 — real은 native bridge로 발화하는 no-op API들의 마지막 호출값을\n // 관측 가능한 state로 mirror (real ground-truth: devtools#171 on-device relay).\n // null = 앱이 아직 호출 안 함(real 기본 동작 = iOS 엣지 스와이프 뒤로가기 enabled).\n navigation: {\n iosSwipeGestureEnabled: boolean | null;\n };\n\n // 권한\n permissions: Record<PermissionName, PermissionStatus>;\n\n // 위치\n location: MockLocation;\n\n // Safe Area\n safeAreaInsets: SafeAreaInsets;\n\n // 연락처\n contacts: MockContact[];\n\n // IAP\n iap: {\n products: MockIapProduct[];\n nextResult: IapNextResult;\n pendingOrders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n completedOrders: Array<{\n orderId: string;\n sku: string;\n status: 'COMPLETED' | 'REFUNDED';\n date: string;\n }>;\n };\n\n // 결제 (TossPay)\n payment: {\n nextResult: 'success' | 'fail';\n failReason: string;\n };\n\n // 로그인\n auth: {\n isLoggedIn: boolean;\n isTossLoginIntegrated: boolean;\n userKeyHash: string;\n anonymousKeyHash: string;\n };\n\n // 알림\n notification: {\n nextResult: NotificationAgreementResult;\n };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent:\n | 'loaded'\n | 'clicked'\n | 'dismissed'\n | 'failedToShow'\n | 'impression'\n | 'userEarnedReward';\n forceNoFill: boolean;\n lastEvent: { type: string; timestamp: number } | null;\n /** AdMob reward 단위 타입 (기본: 'coins') */\n rewardUnitType: string;\n /** AdMob reward 단위 수량 (기본: 10) */\n rewardAmount: number;\n };\n\n // 게임\n game: {\n profile: { nickname: string; profileImageUri: string } | null;\n leaderboardScores: Array<{ score: string; timestamp: number }>;\n };\n\n // 분석 로그\n analyticsLog: AnalyticsLogEntry[];\n\n // SDK 호출 로그 (ring buffer, 상한 SDK_CALL_LOG_MAX)\n sdkCallLog: AitSdkCall[];\n\n // 디바이스 API 모드\n deviceModes: DeviceModes;\n\n // mock 모드용 더미 데이터\n mockData: MockData;\n\n // mock 활성화 상태\n panelEditable: boolean;\n\n // 뷰포트 시뮬레이션 (devtools 전용, SDK와 무관)\n viewport: ViewportState;\n}\n\nconst DEFAULT_STATE: AitDevtoolsState = {\n platform: 'ios',\n environment: 'sandbox',\n appVersion: '5.240.0',\n locale: 'ko-KR',\n schemeUri: '/',\n groupId: 'mock-group-id',\n deploymentId: 'mock-deployment-id',\n deviceId: '',\n\n brand: {\n displayName: 'Mock App',\n icon: '',\n primaryColor: '#3182F6',\n },\n\n networkStatus: 'WIFI',\n\n // null = 앱이 setIosSwipeGestureEnabled를 아직 호출 안 함.\n navigation: {\n iosSwipeGestureEnabled: null,\n },\n\n permissions: {\n clipboard: 'allowed',\n contacts: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n camera: 'allowed',\n microphone: 'notDetermined',\n },\n\n location: {\n coords: {\n latitude: 37.5665,\n longitude: 126.978,\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE',\n },\n\n // iPhone 15 Pro relay 실측값(devtools#190)과 정합: partner WebView portrait에서\n // SafeAreaInsets.get()이 반환한 top=54(토스 nav bar 높이), bottom=34(home indicator).\n // env(safe-area-inset-top)는 0이었으므로 OS 노치는 이 top에 들어가지 않는다.\n // preset이 'none'/'custom'이면 syncSafeAreaFromViewport가 건드리지 않으므로 이 값이\n // SafeAreaInsets.get()의 out-of-box 계약값으로 남는다. preset을 고르면 그 값으로 sync됨.\n safeAreaInsets: { top: 54, bottom: 34, left: 0, right: 0 },\n\n contacts: [\n { name: '홍길동', phoneNumber: '010-1234-5678' },\n { name: '김토스', phoneNumber: '010-9876-5432' },\n ],\n\n iap: {\n products: [\n {\n sku: 'mock-gem-100',\n type: 'CONSUMABLE',\n displayName: '보석 100개',\n displayAmount: '1,000원',\n iconUrl: '',\n description: '게임에서 사용할 수 있는 보석 100개',\n },\n ],\n nextResult: 'success',\n pendingOrders: [],\n completedOrders: [],\n },\n\n payment: {\n nextResult: 'success',\n failReason: '',\n },\n\n auth: {\n isLoggedIn: true,\n isTossLoginIntegrated: true,\n userKeyHash: 'mock-user-hash-abc123',\n anonymousKeyHash: 'mock-anon-hash-xyz789',\n },\n\n notification: {\n nextResult: 'newAgreement',\n },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n forceNoFill: false,\n lastEvent: null,\n rewardUnitType: 'coins',\n rewardAmount: 10,\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\n\n sdkCallLog: [],\n\n deviceModes: {\n camera: 'mock',\n photos: 'mock',\n location: 'mock',\n network: 'mock',\n // 'mock' so the clipboard mock is self-contained. With 'web' the mock\n // calls `navigator.clipboard.readText()` directly, which — when paired\n // with `@ait-co/polyfill` — recurses: polyfill routes `navigator.clipboard`\n // back to the SDK's `getClipboardText`, which is this mock, which calls\n // `navigator.clipboard.readText`, … Users who want true browser\n // clipboard integration can flip this to 'web' from the panel.\n clipboard: 'mock',\n },\n\n mockData: {\n images: [],\n clipboardText: '',\n },\n\n panelEditable: true,\n\n viewport: {\n preset: 'none',\n orientation: 'auto',\n appOrientation: null,\n customWidth: 402,\n customHeight: 874,\n frame: false,\n aitNavBar: true,\n aitNavBarType: 'partner',\n },\n};\n\nfunction generateDeviceId(): string {\n const stored = localStorage.getItem('__ait_device_id');\n if (stored) return stored;\n const id = crypto.randomUUID();\n localStorage.setItem('__ait_device_id', id);\n return id;\n}\n\nexport class AitStateManager {\n private _state: AitDevtoolsState;\n private _listeners = new Set<Listener>();\n private _inTransaction = false;\n\n constructor() {\n this._state = structuredClone(DEFAULT_STATE);\n try {\n this._state.deviceId = generateDeviceId();\n } catch {\n this._state.deviceId = `mock-device-${Math.random().toString(36).slice(2)}`;\n }\n }\n\n get state(): AitDevtoolsState {\n return this._state;\n }\n\n update(partial: Partial<AitDevtoolsState>) {\n this._state = { ...this._state, ...partial };\n this._notify();\n }\n\n /** 중첩 객체 업데이트용 */\n patch<K extends keyof AitDevtoolsState>(key: K, partial: Partial<AitDevtoolsState[K]>) {\n const current = this._state[key];\n if (typeof current === 'object' && current !== null && !Array.isArray(current)) {\n this._state = {\n ...this._state,\n [key]: { ...(current as Record<string, unknown>), ...(partial as Record<string, unknown>) },\n };\n } else {\n this._state = { ...this._state, [key]: partial as AitDevtoolsState[K] };\n }\n this._notify();\n }\n\n subscribe(listener: Listener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n /**\n * 한 묶음의 update/patch 호출을 묶어 listener notify 1회로 만든다.\n * preset 적용처럼 여러 슬라이스를 동시에 바꿀 때 panel re-render 폭주를\n * 방지한다. 중첩 호출은 outermost transaction이 끝날 때 한 번만 notify\n * (inner도 throw해도 outer finally가 flag를 복구한다).\n *\n * Rollback은 없다 — `fn`이 throw해도 그때까지의 state 변경은 유지된다.\n * 구독자가 partial state를 영원히 못 보는 사고를 막기 위해, throw 여부와\n * 무관하게 항상 한 번 notify한 뒤 throw를 propagate한다. DB transaction이\n * 아니라 \"여러 mutation을 한 notify로 묶는 batch\"라고 생각하면 된다.\n *\n * Listener는 throw해선 안 된다 — finally 안의 `_notify()`가 throw하면 원래\n * `fn`의 throw를 덮어버린다. 우리 구독자는 panel re-render뿐이라 실제\n * 발생 사례는 없지만, 외부에서 listener를 등록할 때 주의.\n */\n transaction(fn: () => void): void {\n if (this._inTransaction) {\n fn();\n return;\n }\n this._inTransaction = true;\n try {\n fn();\n } finally {\n this._inTransaction = false;\n this._notify();\n }\n }\n\n /** 분석 로그 추가 */\n logAnalytics(entry: Omit<AnalyticsLogEntry, 'timestamp'>) {\n this._state = {\n ...this._state,\n analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }],\n };\n this._notify();\n }\n\n /**\n * SDK 호출 로그 추가 (ring buffer, 상한 SDK_CALL_LOG_MAX).\n * `observe()`가 호출하고, proxy의 KNOWN_UNIMPLEMENTED 경로도 직접 호출한다.\n */\n logSdkCall(entry: AitSdkCall) {\n const log = this._state.sdkCallLog;\n const next = log.length >= SDK_CALL_LOG_MAX ? log.slice(1 - SDK_CALL_LOG_MAX) : log;\n this._state = { ...this._state, sdkCallLog: [...next, entry] };\n this._notify();\n }\n\n /** 이벤트 트리거 (backEvent, homeEvent 등) */\n trigger(event: string) {\n window.dispatchEvent(new CustomEvent(`__ait:${event}`));\n }\n\n reset() {\n const deviceId = this._state.deviceId;\n this._state = { ...structuredClone(DEFAULT_STATE), deviceId };\n this._notify();\n }\n\n private _notify() {\n if (this._inTransaction) return;\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\n// `tsdown.config.ts`는 mock/panel/unplugin entry를 별도 config object로 빌드한다\n// (\"every entry is self-contained\"). 그 결과 소비자가 두 entry(예: `@ait-co/devtools` +\n// `@ait-co/devtools/panel`)를 동시에 import하면 `state.ts`가 entry별로 따로 번들되어\n// `AitStateManager` 인스턴스가 entry당 1개씩 만들어진다. panel이 toggle한 state는\n// mock SDK가 보는 state와 다른 인스턴스가 되어 모든 토글이 비기능이 된다.\n//\n// build pipeline을 건드리지 않고 runtime guard로 해결한다: globalThis에 인스턴스를\n// 캐시해 같은 페이지의 모든 entry가 동일 인스턴스를 공유하도록 한다.\nconst SINGLETON_KEY = '__aitDevtoolsStateSingleton__';\ntype GlobalWithSingleton = typeof globalThis & { [SINGLETON_KEY]?: AitStateManager };\nconst globalRef = globalThis as GlobalWithSingleton;\nif (!globalRef[SINGLETON_KEY]) {\n globalRef[SINGLETON_KEY] = new AitStateManager();\n}\nexport const aitState: AitStateManager = globalRef[SINGLETON_KEY]!;\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * SDK 호출 관측 래퍼.\n *\n * `observe(apiName, fidelity, fn)` — fn의 시그니처를 그대로 보존하면서\n * 호출 시 args·resolve·reject 결과를 `aitState.sdkCallLog`에 기록한다.\n *\n * **signature 보존 절대 조건**: 제네릭 pass-through로 `__typecheck.ts`의\n * `Assert<Mock, Original>` 불변을 유지한다. observe()로 감싼 함수는\n * 원본과 동일한 타입을 가진다.\n */\n\nimport type { AitSdkCallFidelity } from '../mcp/ait-source.js';\nimport { aitState } from './state.js';\n\n/**\n * fn을 observe로 감싼다.\n *\n * @param apiName - 로그에 기록할 SDK 메서드 이름 (예: `'setScreenAwakeMode'`)\n * @param fidelity - 이 mock의 fidelity grade ('faithful' | 'partial' | 'inert')\n * @param fn - 실제 mock 구현체. 시그니처를 그대로 통과시킨다.\n * @returns fn과 동일한 타입의 래퍼 함수\n */\nexport function observe<TArgs extends unknown[], TReturn>(\n apiName: string,\n fidelity: AitSdkCallFidelity,\n fn: (...args: TArgs) => TReturn,\n): (...args: TArgs) => TReturn {\n return (...args: TArgs): TReturn => {\n const timestamp = Date.now();\n // args를 JSON-safe하게 직렬화한다. 직렬화할 수 없는 값(함수·순환 참조 등)은\n // 문자열 표현으로 대체해 로그가 깨지지 않도록 한다.\n const safeArgs: unknown[] = args.map((a) => safeSerialize(a));\n\n const result = fn(...args);\n\n if (result instanceof Promise) {\n // pending 상태로 먼저 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'pending',\n fidelity,\n });\n\n // resolve/reject 결과로 업데이트 (ring buffer에서 마지막 pending 항목 덮어쓰기)\n (result as Promise<unknown>).then(\n (value) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(value),\n fidelity,\n });\n },\n (err: unknown) => {\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'rejected',\n error: err instanceof Error ? err.message : String(err),\n fidelity,\n });\n },\n );\n\n return result;\n }\n\n // 동기 반환 — 즉시 resolved로 기록\n aitState.logSdkCall({\n method: apiName,\n args: safeArgs,\n timestamp,\n status: 'resolved',\n result: safeSerialize(result),\n fidelity,\n });\n\n return result;\n };\n}\n\n/**\n * 값을 JSON-safe한 형태로 변환한다.\n * - null / primitive — 그대로.\n * - 함수 — `'[Function: name]'` 문자열.\n * - 기타 객체 — JSON.stringify 실패 시 `'[unserializable]'`.\n */\nfunction safeSerialize(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;\n if (typeof value !== 'object') return value;\n try {\n return JSON.parse(JSON.stringify(value));\n } catch {\n return '[unserializable]';\n }\n}\n","/**\n * 미구현 API용 Proxy 트립와이어.\n *\n * 미구현 프로퍼티에 접근하면 throw한다. 이는 \"devtools에서는 멀쩡히 돌지만\n * 실 SDK에선 실제로 동작하는\" 시나리오를 차단하기 위한 의도적 선택이다.\n * mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지\n * 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을\n * 포함해 사용자가 mock 누락을 제보할 수 있게 한다.\n *\n * ## KNOWN_UNIMPLEMENTED 정책\n * SDK에 존재하는 것으로 알려져 있으나 현재 mock이 없는 API 이름만 이 집합에 둔다.\n * 이 경우에만 throw 대신 🔴 inert no-op을 반환하고 sdkCallLog에 기록한다.\n * 완전히 미지의 이름은 여전히 throw — \"잘 되는 척\" 방지.\n */\n\nimport { aitState } from './state.js';\n\nconst ISSUES_URL = 'https://github.com/apps-in-toss-community/devtools/issues';\n\n/**\n * SDK에 존재하나 mock이 아직 없는 것으로 확인된 이름 목록.\n * 새 API가 SDK에 추가되면 여기에 추가하고 별도 PR에서 mock 구현으로 이동한다.\n * 확인되지 않은 이름은 절대 여기에 추가하지 않는다 — throw가 더 안전하다.\n */\nconst KNOWN_UNIMPLEMENTED = new Set<string>([\n // 예: 'someNewSdkApi',\n]);\n\nexport function createMockProxy<T extends Record<string, unknown>>(\n moduleName: string,\n implementations: T,\n): T {\n return new Proxy(implementations, {\n get(target, prop) {\n // 심볼 접근(Symbol.toPrimitive, Symbol.iterator 등)은 프레임워크/런타임이\n // 내부적으로 호출하므로 throw하면 console.log, 구조분해 등이 깨진다.\n if (typeof prop === 'symbol') return undefined;\n if (prop in target) return target[prop];\n\n const name = String(prop);\n\n // SDK에 존재하나 mock 미구현으로 확인된 API — throw 대신 🔴 inert no-op 반환.\n if (KNOWN_UNIMPLEMENTED.has(name)) {\n return (...args: unknown[]): undefined => {\n console.warn(\n `[@ait-co/devtools] ${moduleName}.${name} is known-unimplemented (🔴 inert). ` +\n `Returning undefined. Please file or upvote an issue: ${ISSUES_URL}`,\n );\n aitState.logSdkCall({\n method: `${moduleName}.${name}`,\n args: args,\n timestamp: Date.now(),\n status: 'resolved',\n result: undefined,\n fidelity: 'inert',\n });\n return undefined;\n };\n }\n\n throw new Error(\n `[@ait-co/devtools] ${moduleName}.${prop} is not mocked. ` +\n `This API may exist in @apps-in-toss/web-framework, ` +\n `but devtools' mock does not cover it yet. ` +\n `Please file an issue: ${ISSUES_URL}`,\n );\n },\n }) as T;\n}\n","/**\n * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)\n *\n * 변경 이력 (#196):\n * - slot 레지스트리로 TossAds destroy/destroyAll 누수 수정 (🟡→🟢)\n * - attachBanner BannerSlotCallbacks 발화 (onAdRendered/onAdImpression/onNoFill 등)\n * - initialize onInitialized/onInitializationFailed 발화\n * - AdMob reward 파라미터화 (state.ads.rewardUnitType/rewardAmount)\n * - 모든 호출 observe()로 sdkCallLog에 기록\n */\n\nimport { observe } from '../observe.js';\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\nfunction withIsSupported<T extends (...args: never[]) => unknown>(\n fn: T,\n): T & { isSupported: () => boolean } {\n (fn as T & { isSupported: () => boolean }).isSupported = () => true;\n return fn as T & { isSupported: () => boolean };\n}\n\n// --- slot 레지스트리 (TossAds destroy 누수 수정) ---\n// attachBanner가 생성한 placeholder를 slotId로 추적해서\n// destroy/destroyAll이 실제 el.remove()를 수행할 수 있게 한다.\nconst _slotRegistry = new Map<string, HTMLElement>();\n\nlet _slotCounter = 0;\nfunction _nextSlotId(adGroupId: string): string {\n _slotCounter += 1;\n return `mock-slot-${adGroupId}-${_slotCounter}`;\n}\n\n/** 테스트에서 레지스트리를 초기화할 수 있게 export */\nexport function _resetSlotRegistry(): void {\n _slotRegistry.clear();\n _slotCounter = 0;\n}\n\n// --- Google AdMob ---\n\nexport const GoogleAdMob = createMockProxy('GoogleAdMob', {\n loadAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.loadAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'requested' }), 50);\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'impression' }), 150);\n setTimeout(() => {\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n });\n }, 1000);\n setTimeout(() => {\n args.onEvent({ type: 'dismissed' });\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n return () => {};\n },\n ),\n ),\n\n isAppsInTossAdMobLoaded: withIsSupported(\n observe(\n 'GoogleAdMob.isAppsInTossAdMobLoaded',\n 'faithful',\n async (_options: { adGroupId?: string }): Promise<boolean> => aitState.state.ads.isLoaded,\n ),\n ),\n});\n\n// --- TossAds ---\n\nexport const TossAds = createMockProxy('TossAds', {\n initialize: withIsSupported(\n observe(\n 'TossAds.initialize',\n 'partial',\n (options: {\n callbacks?: { onInitialized?: () => void; onInitializationFailed?: (error: Error) => void };\n }): void => {\n // forceNoFill을 initialization failure로도 활용한다\n if (aitState.state.ads.forceNoFill) {\n options.callbacks?.onInitializationFailed?.(new Error('No fill'));\n return;\n }\n options.callbacks?.onInitialized?.();\n },\n ),\n ),\n\n attach: withIsSupported(\n observe(\n 'TossAds.attach',\n 'partial',\n (_adGroupId: string, target: string | HTMLElement, _options?: unknown): void => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText =\n 'background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;';\n placeholder.textContent = '[@ait-co/devtools] TossAds Placeholder';\n el.appendChild(placeholder);\n }\n },\n ),\n ),\n\n attachBanner: withIsSupported(\n observe(\n 'TossAds.attachBanner',\n 'faithful',\n (\n adGroupId: string,\n target: string | HTMLElement,\n options?: {\n theme?: 'auto' | 'light' | 'dark';\n tone?: 'blackAndWhite' | 'grey';\n variant?: 'card' | 'expanded';\n callbacks?: {\n onAdRendered?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdViewable?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdClicked?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdImpression?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: { creativeId: string; requestId: string };\n }) => void;\n onAdFailedToRender?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n error: { code: number; message: string; domain?: string };\n }) => void;\n onNoFill?: (payload: {\n slotId: string;\n adGroupId: string;\n adMetadata: Record<string, never>;\n }) => void;\n };\n },\n ): { destroy: () => void } => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n const slotId = _nextSlotId(adGroupId);\n\n const placeholder = document.createElement('div');\n\n // AttachBannerOptions를 placeholder 스타일에 반영\n const theme = options?.theme ?? 'auto';\n const variant = options?.variant ?? 'card';\n const isDark =\n theme === 'dark' ||\n (theme === 'auto' &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const bg = isDark ? '#1a1a1a' : '#f0f0f0';\n const textColor = isDark ? '#aaa' : '#666';\n const borderColor = isDark ? '#555' : '#999';\n const height = variant === 'expanded' ? '120px' : '60px';\n\n placeholder.dataset.aitSlotId = slotId;\n placeholder.style.cssText = `background:${bg};border:1px dashed ${borderColor};padding:8px 12px;text-align:center;color:${textColor};font-size:12px;min-height:${height};display:flex;align-items:center;justify-content:center;`;\n placeholder.textContent = `[@ait-co/devtools] Banner Ad (${variant})`;\n\n if (el) {\n el.appendChild(placeholder);\n _slotRegistry.set(slotId, placeholder);\n }\n\n const destroySlot = () => {\n const registered = _slotRegistry.get(slotId);\n if (registered) {\n registered.remove();\n _slotRegistry.delete(slotId);\n }\n };\n\n // 콜백 발화 (setTimeout으로 비동기 — 실 SDK와 동일하게 렌더 완료 후)\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n options?.callbacks?.onNoFill?.({\n slotId,\n adGroupId,\n adMetadata: {},\n });\n options?.callbacks?.onAdFailedToRender?.({\n slotId,\n adGroupId,\n adMetadata: {},\n error: { code: 0, message: 'No fill' },\n });\n return;\n }\n\n const eventPayload = {\n slotId,\n adGroupId,\n adMetadata: { creativeId: `mock-creative-${slotId}`, requestId: `mock-req-${slotId}` },\n };\n options?.callbacks?.onAdRendered?.(eventPayload);\n options?.callbacks?.onAdImpression?.(eventPayload);\n }, 100);\n\n return { destroy: destroySlot };\n },\n ),\n ),\n\n destroy: withIsSupported(\n observe('TossAds.destroy', 'faithful', (slotId: string): void => {\n const el = _slotRegistry.get(slotId);\n if (el) {\n el.remove();\n _slotRegistry.delete(slotId);\n }\n }),\n ),\n\n destroyAll: withIsSupported(\n observe('TossAds.destroyAll', 'faithful', (): void => {\n for (const el of _slotRegistry.values()) {\n el.remove();\n }\n _slotRegistry.clear();\n }),\n ),\n});\n\n// --- FullScreen Ad ---\n\nexport const loadFullScreenAd = withIsSupported(\n observe(\n 'loadFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n if (aitState.state.ads.forceNoFill) {\n args.onError(new Error('No fill'));\n return;\n }\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'dismissed' }), 1500);\n return () => {};\n },\n ),\n);\n","/**\n * Analytics mock\n */\n\nimport { aitState } from '../state.js';\n\ntype Primitive = string | number | boolean | null | undefined | symbol;\ntype LoggerParams = { log_name?: string } & Record<string, Primitive>;\n\n// Analytics methods return `Promise<void> | undefined` to match the original SDK signature,\n// so they cannot use `async` (which always returns a Promise).\nexport const Analytics = {\n screen: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'screen', params: params ?? {} });\n return Promise.resolve();\n },\n impression: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'impression', params: params ?? {} });\n return Promise.resolve();\n },\n click: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'click', params: params ?? {} });\n return Promise.resolve();\n },\n};\n\nexport async function eventLog(params: {\n log_name: string;\n log_type: 'debug' | 'info' | 'warn' | 'error' | 'event' | 'screen' | 'impression' | 'click';\n params: Record<string, Primitive>;\n}): Promise<void> {\n aitState.logAnalytics({\n type: params.log_type,\n params: { log_name: params.log_name, ...params.params },\n });\n}\n","/**\n * 인증/로그인 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport async function appLogin(): Promise<{\n authorizationCode: string;\n referrer: 'DEFAULT' | 'SANDBOX';\n}> {\n return {\n authorizationCode: `mock-auth-${crypto.randomUUID()}`,\n referrer: aitState.state.environment === 'toss' ? 'DEFAULT' : 'SANDBOX',\n };\n}\n\nexport async function getIsTossLoginIntegratedService(): Promise<boolean | undefined> {\n return aitState.state.auth.isTossLoginIntegrated;\n}\n\nexport async function getUserKeyForGame(): Promise<\n { hash: string; type: 'HASH' } | 'INVALID_CATEGORY' | 'ERROR' | undefined\n> {\n if (!aitState.state.auth.userKeyHash) return undefined;\n return { hash: aitState.state.auth.userKeyHash, type: 'HASH' };\n}\n\nexport async function getAnonymousKey(): Promise<\n { hash: string; type: 'HASH' } | 'ERROR' | undefined\n> {\n if (!aitState.state.auth.anonymousKeyHash) return undefined;\n return { hash: aitState.state.auth.anonymousKeyHash, type: 'HASH' };\n}\n\nexport interface AppsInTossSignTossCertParams {\n txId: string;\n}\n\nexport async function appsInTossSignTossCert(_params: AppsInTossSignTossCertParams): Promise<void> {\n console.log('[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)');\n}\n","/**\n * 디바이스 모듈 내부 공유 헬퍼\n */\n\nimport { aitState } from '../state.js';\n\n// --- Placeholder Image Generator ---\n\nfunction generatePlaceholderImage(\n width: number,\n height: number,\n text: string,\n color: string,\n): string {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n // jsdom 등 Canvas API 미지원 환경에서는 간단한 SVG data URI 반환\n 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>`;\n return `data:image/svg+xml;base64,${btoa(svg)}`;\n }\n ctx.fillStyle = color;\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = 'white';\n ctx.font = '16px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, width / 2, height / 2);\n return canvas.toDataURL('image/png');\n}\n\nconst DEFAULT_PLACEHOLDERS = [\n { text: 'Mock Photo 1', color: '#3182F6' },\n { text: 'Mock Photo 2', color: '#27ae60' },\n { text: 'Mock Photo 3', color: '#e67e22' },\n];\n\nlet cachedPlaceholders: string[] | null = null;\n\nexport function getDefaultPlaceholderImages(): string[] {\n if (!cachedPlaceholders) {\n cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) =>\n generatePlaceholderImage(320, 240, p.text, p.color),\n );\n }\n return [...cachedPlaceholders];\n}\n\n/** @internal device 모듈 내부 전용 */\nexport function getMockImages(): string[] {\n const images = aitState.state.mockData.images;\n if (images.length > 0) return images;\n return getDefaultPlaceholderImages();\n}\n\n// --- Prompt Mode Helper ---\n\nconst PROMPT_TIMEOUT_MS = 30_000;\n\n/** @internal device 모듈 내부 전용 */\nexport function waitForPromptResponse<T>(type: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const eventName = `__ait:prompt-response:${type}`;\n const cancelName = '__ait:prompt-cancel';\n\n function cleanup() {\n clearTimeout(timer);\n window.removeEventListener(eventName, handler);\n window.removeEventListener(cancelName, cancelHandler);\n }\n\n const timer = setTimeout(() => {\n cleanup();\n const panelMounted = !!document.querySelector('.ait-panel');\n const hint = panelMounted\n ? 'Please provide input via the DevTools panel.'\n : 'Is @ait-co/devtools/panel imported?';\n reject(\n new Error(\n `[@ait-co/devtools] Prompt timeout for \"${type}\" after ${PROMPT_TIMEOUT_MS / 1000}s. ${hint}`,\n ),\n );\n }, PROMPT_TIMEOUT_MS);\n\n const handler = (e: Event) => {\n cleanup();\n resolve((e as CustomEvent).detail as T);\n };\n\n const cancelHandler = () => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt cancelled for \"${type}\"`));\n };\n\n window.addEventListener(eventName, handler);\n window.addEventListener(cancelName, cancelHandler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type } }));\n });\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState } from './state.js';\nimport type { PermissionName, PermissionStatus } from './types.js';\n\nexport async function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return aitState.state.permissions[name];\n}\n\nexport async function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return 'allowed';\n}\n\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: string;\n}): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n} {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(\n `[@ait-co/devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`,\n );\n }\n}\n","/**\n * Camera & Album Photos & Album Items mock\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport { getMockImages, waitForPromptResponse } from './_helpers.js';\n\n// --- 타입 ---\n\nexport type AlbumItemType = 'PHOTO' | 'VIDEO';\n\n// --- Camera ---\n\nasync function openCameraMock(): Promise<{ id: string; dataUri: string }> {\n const images = getMockImages();\n return { id: crypto.randomUUID(), dataUri: images[0] };\n}\n\nasync function openCameraWeb(): Promise<{ id: string; dataUri: string }> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.capture = 'environment';\n let settled = false;\n input.onchange = () => {\n settled = true;\n const file = input.files?.[0];\n if (!file) {\n reject(new Error('No file selected'));\n return;\n }\n const reader = new FileReader();\n reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n };\n // Detect file picker cancel via focus heuristic.\n // Note: unreliable on some mobile browsers and Safari where focus events differ.\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function openCameraPrompt(): Promise<{ id: string; dataUri: string }> {\n const dataUri = await waitForPromptResponse<string>('camera');\n return { id: crypto.randomUUID(), dataUri };\n}\n\nconst _openCamera = async (_options?: {\n base64?: boolean;\n maxWidth?: number;\n}): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n const mode = aitState.state.deviceModes.camera;\n if (mode === 'web') return openCameraWeb();\n if (mode === 'prompt') return openCameraPrompt();\n return openCameraMock();\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nasync function fetchAlbumPhotosMock(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const images = getMockImages();\n return images.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nasync function fetchAlbumPhotosWeb(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n reject(new Error('No files selected'));\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<{ id: string; dataUri: string }>((res, rej) => {\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumPhotosPrompt(\n maxCount: number,\n): Promise<Array<{ id: string; dataUri: string }>> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nconst _fetchAlbumPhotos = async (options?: {\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumPhotosWeb(maxCount);\n if (mode === 'prompt') return fetchAlbumPhotosPrompt(maxCount);\n return fetchAlbumPhotosMock(maxCount);\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Album Items (사진·동영상 복합 선택) ---\n\nexport interface FetchAlbumItemsOptions {\n types?: AlbumItemType[];\n maxCount?: number;\n maxWidth?: number;\n base64?: boolean;\n}\n\nexport interface AlbumItemResponse {\n id: string;\n dataUri: string;\n type: AlbumItemType;\n}\n\nasync function fetchAlbumItemsMock(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n const images = getMockImages();\n return images\n .slice(0, maxCount)\n .filter(() => types.includes('PHOTO'))\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nasync function fetchAlbumItemsWeb(\n maxCount: number,\n types: AlbumItemType[],\n): Promise<AlbumItemResponse[]> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = types.includes('VIDEO') ? 'image/*,video/*' : 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) {\n resolve([]);\n return;\n }\n const results = await Promise.all(\n files.map(\n (file) =>\n new Promise<AlbumItemResponse>((res, rej) => {\n const itemType: AlbumItemType = file.type.startsWith('video/') ? 'VIDEO' : 'PHOTO';\n const reader = new FileReader();\n reader.onload = () =>\n res({ id: crypto.randomUUID(), dataUri: reader.result as string, type: itemType });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n }),\n ),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) resolve([]);\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumItemsPrompt(maxCount: number): Promise<AlbumItemResponse[]> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris\n .slice(0, maxCount)\n .map((dataUri) => ({ id: crypto.randomUUID(), dataUri, type: 'PHOTO' as AlbumItemType }));\n}\n\nconst _fetchAlbumItems = async (options?: FetchAlbumItemsOptions): Promise<AlbumItemResponse[]> => {\n checkPermission('photos', 'fetchAlbumItems');\n const maxCount = options?.maxCount ?? 10;\n const types = options?.types ?? ['PHOTO'];\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumItemsWeb(maxCount, types);\n if (mode === 'prompt') return fetchAlbumItemsPrompt(maxCount);\n return fetchAlbumItemsMock(maxCount, types);\n};\nexport const fetchAlbumItems = withPermission(_fetchAlbumItems, 'photos');\n","/**\n * Clipboard mock\n * mock/web 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') return aitState.state.mockData.clipboardText;\n // web mode (default)\n try {\n return await navigator.clipboard.readText();\n } catch {\n return '';\n }\n};\nexport const getClipboardText = withPermission(_getClipboardText, 'clipboard');\n\nconst _setClipboardText = async (text: string): Promise<void> => {\n checkPermission('clipboard', 'setClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') {\n aitState.patch('mockData', { clipboardText: text });\n return;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n","/**\n * Contacts mock\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\n\nconst _fetchContacts = async (options: {\n size: number;\n offset: number;\n query?: { contains?: string };\n}) => {\n checkPermission('contacts', 'fetchContacts');\n let contacts = aitState.state.contacts;\n if (options.query?.contains) {\n const q = options.query.contains.toLowerCase();\n contacts = contacts.filter(\n (c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q),\n );\n }\n const sliced = contacts.slice(options.offset, options.offset + options.size);\n const nextOffset = options.offset + options.size;\n return {\n result: sliced,\n nextOffset: nextOffset < contacts.length ? nextOffset : null,\n done: nextOffset >= contacts.length,\n };\n};\nexport const fetchContacts = withPermission(_fetchContacts, 'contacts');\n","/**\n * Haptic Feedback & saveBase64Data mock\n *\n * generateHapticFeedback — 영역 3 (하드웨어 API 관측):\n * - 10종 HapticFeedbackType을 navigator.vibrate 패턴으로 매핑(근사, best-effort).\n * - `typeof navigator.vibrate === 'function'` 가드 — API 없는 환경에서 throw 없이 skip.\n * - sdkCallLog에 🟡(partial)로 기록. params: { hapticType, vibrated: boolean }.\n * - 시그니처 불변 — __typecheck.ts의 Assert<Mock, Original> 통과.\n */\n\nimport { aitState } from '../state.js';\nimport type { HapticFeedbackType } from '../types.js';\n\n/**\n * HapticFeedbackType 10종 → navigator.vibrate 패턴 매핑.\n * 숫자: 진동 ms. 배열: [진동, 정지, 진동, …] 교대 패턴.\n */\nexport const HAPTIC_VIBRATE_PATTERN: Record<HapticFeedbackType, VibratePattern> = {\n tickWeak: 10,\n tap: 20,\n tickMedium: 30,\n softMedium: 40,\n basicWeak: 15,\n basicMedium: 50,\n success: [10, 40, 10],\n error: [40, 30, 40],\n wiggle: [20, 20, 20, 20, 20],\n confetti: [10, 20, 10, 20, 10, 20, 10],\n};\n\nexport async function generateHapticFeedback(options: { type: HapticFeedbackType }): Promise<void> {\n const timestamp = Date.now();\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n\n const pattern = HAPTIC_VIBRATE_PATTERN[options.type] ?? 30;\n const vibrated = typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\n\n aitState.logSdkCall({\n method: 'generateHapticFeedback',\n args: [{ type: options.type }],\n timestamp,\n status: 'resolved',\n result: { hapticType: options.type, vibrated },\n fidelity: 'partial',\n });\n}\n\nexport async function saveBase64Data(params: {\n data: string;\n fileName: string;\n mimeType: string;\n}): Promise<void> {\n const a = document.createElement('a');\n a.href = `data:${params.mimeType};base64,${params.data}`;\n a.download = params.fileName;\n a.click();\n}\n","/**\n * Location mock (getCurrentLocation, startUpdateLocation)\n * mock/web/prompt 모드 지원\n */\n\nimport { checkPermission, withPermission } from '../permissions.js';\nimport { aitState } from '../state.js';\nimport type { MockLocation } from '../types.js';\nimport { waitForPromptResponse } from './_helpers.js';\n\nenum Accuracy {\n Lowest = 1,\n Low = 2,\n Balanced = 3,\n High = 4,\n Highest = 5,\n BestForNavigation = 6,\n}\n\nexport { Accuracy };\n\nfunction buildLocation(): MockLocation {\n return {\n coords: { ...aitState.state.location.coords },\n timestamp: Date.now(),\n accessLocation: aitState.state.location.accessLocation,\n };\n}\n\n// -- getCurrentLocation --\n\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n return buildLocation();\n}\n\nasync function getCurrentLocationWeb(): Promise<MockLocation> {\n return new Promise((resolve) => {\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n resolve(buildLocation());\n return;\n }\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n resolve({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n () => {\n console.warn('[@ait-co/devtools] Geolocation failed, falling back to mock');\n resolve(buildLocation());\n },\n );\n });\n}\n\nasync function getCurrentLocationPrompt(): Promise<MockLocation> {\n return waitForPromptResponse<MockLocation>('location');\n}\n\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return getCurrentLocationWeb();\n if (mode === 'prompt') return getCurrentLocationPrompt();\n return getCurrentLocationMock();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\n// -- startUpdateLocation --\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction startUpdateLocationMock(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n const loc = buildLocation();\n loc.coords.latitude += (Math.random() - 0.5) * 0.0001;\n loc.coords.longitude += (Math.random() - 0.5) * 0.0001;\n onEvent(loc);\n }, interval);\n return () => clearInterval(id);\n}\n\nfunction startUpdateLocationWeb(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, onError } = eventParams;\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n return startUpdateLocationMock(eventParams);\n }\n const watchId = navigator.geolocation.watchPosition(\n (pos) => {\n onEvent({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n (err) => onError(err),\n );\n return () => navigator.geolocation.clearWatch(watchId);\n}\n\nfunction startUpdateLocationPrompt(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent } = eventParams;\n const handler = (e: Event) => {\n onEvent((e as CustomEvent).detail as MockLocation);\n };\n window.addEventListener('__ait:prompt-response:location-update', handler);\n window.dispatchEvent(\n new CustomEvent('__ait:prompt-request', { detail: { type: 'location-update' } }),\n );\n return () => window.removeEventListener('__ait:prompt-response:location-update', handler);\n}\n\nconst _startUpdateLocation = (eventParams: StartUpdateLocationEventParams): (() => void) => {\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return startUpdateLocationWeb(eventParams);\n if (mode === 'prompt') return startUpdateLocationPrompt(eventParams);\n return startUpdateLocationMock(eventParams);\n};\nexport const startUpdateLocation = withPermission(_startUpdateLocation, 'geolocation');\n","/**\n * Network Status mock (mode-aware helper)\n * navigation 모듈에서 사용. circular dep 방지를 위해 device에 위치.\n */\n\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\n/**\n * Web mode: uses navigator.connection.effectiveType (4g/3g/2g) and navigator.onLine.\n * Limitations: WIFI, 5G, WWAN cannot be detected via the Network Information API.\n * Falls back to state-based value when effectiveType is unavailable.\n */\nexport function getNetworkStatusByMode(): NetworkStatus | null {\n const mode = aitState.state.deviceModes.network;\n if (mode === 'mock') return null; // use default state-based logic\n if (mode === 'web') {\n if (!navigator.onLine) return 'OFFLINE';\n const conn = (navigator as unknown as Record<string, unknown>).connection as\n | { effectiveType?: string }\n | undefined;\n if (conn?.effectiveType) {\n const mapping: Record<string, NetworkStatus> = {\n '4g': '4G',\n '3g': '3G',\n '2g': '2G',\n 'slow-2g': '2G',\n };\n return mapping[conn.effectiveType] ?? 'UNKNOWN';\n }\n return aitState.state.networkStatus;\n }\n // prompt mode: not supported for network, fall back to mock\n return null;\n}\n","/**\n * PDF Viewer mock\n * 네이티브 PDF 뷰어 동작을 시뮬레이션한다.\n * 권한 게이트 없음. 모드 분기 없음.\n */\n\nexport interface OpenPDFViewerParams {\n data: string;\n filename?: string;\n}\n\nexport type OpenPDFViewerResult = 'CLOSE';\n\n/**\n * Base64로 인코딩된 PDF 데이터를 네이티브 PDF 뷰어로 여는 mock.\n * mock 환경에서는 즉시 `'CLOSE'`를 반환한다.\n */\nexport async function openPDFViewer(_params: OpenPDFViewerParams): Promise<OpenPDFViewerResult> {\n // 실 SDK와 동일하게 비동기로 resolve한다.\n await Promise.resolve();\n return 'CLOSE';\n}\n","/**\n * Storage mock\n * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리\n */\n\nimport { createMockProxy } from '../proxy.js';\n\nexport const Storage = createMockProxy('Storage', {\n getItem: async (key: string): Promise<string | null> => {\n return localStorage.getItem(`__ait_storage:${key}`);\n },\n setItem: async (key: string, value: string): Promise<void> => {\n localStorage.setItem(`__ait_storage:${key}`, value);\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n },\n clearItems: async (): Promise<void> => {\n const keys = Object.keys(localStorage).filter((k) => k.startsWith('__ait_storage:'));\n for (const k of keys) {\n localStorage.removeItem(k);\n }\n },\n});\n","/**\n * 게임/프로모션 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport async function grantPromotionReward(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[@ait-co/devtools] grantPromotionReward:', params.params);\n return { key: `mock-reward-${Date.now()}` };\n}\n\nexport async function grantPromotionRewardForGame(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[@ait-co/devtools] grantPromotionRewardForGame:', params.params);\n return { key: `mock-reward-${Date.now()}` };\n}\n\nexport async function submitGameCenterLeaderBoardScore(params: {\n score: string;\n}): Promise<\n | { statusCode: 'SUCCESS' | 'LEADERBOARD_NOT_FOUND' | 'PROFILE_NOT_FOUND' | 'UNPARSABLE_SCORE' }\n | undefined\n> {\n aitState.patch('game', {\n leaderboardScores: [\n ...aitState.state.game.leaderboardScores,\n { score: params.score, timestamp: Date.now() },\n ],\n });\n return { statusCode: 'SUCCESS' };\n}\n\nexport async function getGameCenterGameProfile(): Promise<\n | { statusCode: 'SUCCESS'; nickname: string; profileImageUri: string }\n | { statusCode: 'PROFILE_NOT_FOUND' }\n | undefined\n> {\n const profile = aitState.state.game.profile;\n if (!profile) return { statusCode: 'PROFILE_NOT_FOUND' };\n return {\n statusCode: 'SUCCESS',\n nickname: profile.nickname,\n profileImageUri: profile.profileImageUri,\n };\n}\n\nexport async function openGameCenterLeaderboard(): Promise<void> {\n console.log('[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)');\n}\n\ninterface ContactsViralEvent {\n type: string;\n data: Record<string, unknown>;\n}\n\nexport function contactsViral(params: {\n options: { moduleId: string };\n onEvent: (event: ContactsViralEvent) => void;\n onError: (error: unknown) => void;\n}): () => void {\n setTimeout(() => {\n params.onEvent({\n type: 'close',\n data: {\n closeReason: 'noReward',\n sentRewardsCount: 0,\n },\n });\n }, 500);\n return () => {};\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport { createMockProxy } from '../proxy.js';\nimport { aitState } from '../state.js';\n\n// orderCounter는 모듈 레벨 상태로 reset()에 의해 초기화되지 않는다.\n// 테스트에서는 orderId를 stringContaining('mock-order-')로 검증하여 카운터 값에 의존하지 않는다.\nlet orderCounter = 0;\n\nfunction generateOrderId(): string {\n return `mock-order-${++orderCounter}-${Date.now()}`;\n}\n\ninterface IapCreateOneTimePurchaseOrderOptions {\n options: {\n sku?: string;\n productId?: string;\n processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface CreateSubscriptionPurchaseOrderOptions {\n options: {\n sku: string;\n offerId?: string | null;\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>;\n };\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>;\n onError: (error: unknown) => void | Promise<void>;\n}\n\ninterface IapOrderResult {\n orderId: string;\n displayName: string;\n displayAmount: string;\n amount: number;\n currency: string;\n fraction: number;\n miniAppIconUrl: string | null;\n}\n\nfunction buildOrderResult(sku: string): IapOrderResult {\n const product = aitState.state.iap.products.find((p) => p.sku === sku);\n const amountStr = product?.displayAmount?.replace(/[^0-9]/g, '') ?? '1000';\n return {\n orderId: generateOrderId(),\n displayName: product?.displayName ?? 'Mock Product',\n displayAmount: product?.displayAmount ?? '1,000원',\n amount: parseInt(amountStr, 10) || 1000,\n currency: 'KRW',\n fraction: 0,\n miniAppIconUrl: product?.iconUrl || null,\n };\n}\n\nasync function handlePurchase(\n sku: string,\n processProductGrant: (params: {\n orderId: string;\n subscriptionId?: string;\n }) => boolean | Promise<boolean>,\n onEvent: (event: { type: 'success'; data: IapOrderResult }) => void | Promise<void>,\n onError: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n const nextResult = aitState.state.iap.nextResult;\n\n // 비동기 시뮬레이션 (실제로는 결제 UI가 뜨는 시간)\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult !== 'success') {\n onError({ code: nextResult });\n return;\n }\n\n const result = buildOrderResult(sku);\n\n try {\n const granted = await processProductGrant({ orderId: result.orderId });\n if (!granted) {\n onError({ code: 'PRODUCT_NOT_GRANTED_BY_PARTNER' });\n return;\n }\n } catch (e) {\n onError(e);\n return;\n }\n\n // 주문 완료 기록\n aitState.patch('iap', {\n completedOrders: [\n ...aitState.state.iap.completedOrders,\n {\n orderId: result.orderId,\n sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ],\n });\n\n await onEvent({ type: 'success', data: result });\n}\n\nexport const IAP = createMockProxy('IAP', {\n // 반환되는 cancel 함수는 mock에서는 no-op이다 (실제 SDK는 결제 UI를 닫음)\n createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void {\n const sku = params.options.sku ?? params.options.productId ?? '';\n handlePurchase(sku, params.options.processProductGrant, params.onEvent, params.onError).catch(\n (e) => console.error('[@ait-co/devtools] IAP unexpected error:', e),\n );\n return () => {};\n },\n\n createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {\n handlePurchase(\n params.options.sku,\n params.options.processProductGrant,\n params.onEvent,\n params.onError,\n ).catch((e) => console.error('[@ait-co/devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n async getProductItemList(): Promise<{ products: unknown[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })),\n };\n },\n\n async getPendingOrders(): Promise<{\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n }> {\n return { orders: [...aitState.state.iap.pendingOrders] };\n },\n\n async getCompletedOrRefundedOrders(): Promise<{\n hasNext: boolean;\n nextKey?: string | null;\n orders: Array<{ orderId: string; sku: string; status: 'COMPLETED' | 'REFUNDED'; date: string }>;\n }> {\n return {\n hasNext: false,\n nextKey: null,\n orders: [...aitState.state.iap.completedOrders],\n };\n },\n\n async completeProductGrant(args: { params: { orderId: string } }): Promise<boolean> {\n // pending → completed 전이\n const idx = aitState.state.iap.pendingOrders.findIndex(\n (o) => o.orderId === args.params.orderId,\n );\n if (idx !== -1) {\n const order = aitState.state.iap.pendingOrders[idx];\n const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);\n const completedOrders = [\n ...aitState.state.iap.completedOrders,\n {\n orderId: order.orderId,\n sku: order.sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n },\n ];\n aitState.patch('iap', { pendingOrders, completedOrders });\n }\n return true;\n },\n\n async getSubscriptionInfo(_args: { params: { orderId: string } }) {\n return {\n subscription: {\n catalogId: 1,\n status: 'ACTIVE',\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n isAutoRenew: true,\n gracePeriodExpiresAt: null,\n isAccessible: true,\n },\n };\n },\n});\n\n// --- TossPay ---\n\nexport async function checkoutPayment(options: {\n params: { payToken: string };\n}): Promise<{ success: boolean; reason?: string }> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] checkoutPayment:', options.params.payToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock payment failed' };\n}\n\nexport const requestTossPayPaysBilling = Object.assign(\n async function requestTossPayPaysBilling(options: {\n params: { wrappedToken: string };\n }): Promise<{ success: boolean; reason?: string } | undefined> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[@ait-co/devtools] requestTossPayPaysBilling:', options.params.wrappedToken);\n\n await new Promise((r) => setTimeout(r, 300));\n\n if (nextResult === 'success') {\n return { success: true };\n }\n return { success: false, reason: failReason || 'Mock billing auth failed' };\n },\n { isSupported: () => true },\n);\n","/**\n * 화면/네비게이션/이벤트 mock\n */\n\nimport { getNetworkStatusByMode } from '../device/index.js';\nimport { observe } from '../observe.js';\nimport { aitState } from '../state.js';\nimport type { NetworkStatus } from '../types.js';\n\nexport async function closeView(): Promise<void> {\n console.log('[@ait-co/devtools] closeView called');\n window.history.back();\n}\n\nexport async function openURL(url: string): Promise<void> {\n console.log('[@ait-co/devtools] openURL:', url);\n window.open(url, '_blank');\n}\n\nexport async function share(message: { message: string }): Promise<void> {\n if (navigator.share) {\n await navigator.share({ text: message.message });\n return;\n }\n console.log('[@ait-co/devtools] share:', message.message);\n}\n\nexport async function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n return `https://toss.im/share/mock${path}`;\n}\n\nexport async function setIosSwipeGestureEnabled(options: { isEnabled: boolean }): Promise<void> {\n console.log('[@ait-co/devtools] setIosSwipeGestureEnabled:', options.isEnabled);\n // real(토스 WebView)에선 이 호출이 native bridge로 발화한다(devtools#171 실측). mock은\n // 그 \"마지막 호출값\"을 관측 가능한 state로 mirror해, toss-gated 가드(예: sdk-example\n // useDisableIosSwipeGestureInToss)가 실제로 돌았는지를 AIT.getMockState로 대조할 수 있게 한다.\n aitState.patch('navigation', { iosSwipeGestureEnabled: options.isEnabled });\n}\n\nexport async function setDeviceOrientation(options: {\n type: 'portrait' | 'landscape';\n}): Promise<void> {\n const current = aitState.state.viewport.orientation;\n if (current === 'auto') {\n console.log('[@ait-co/devtools] setDeviceOrientation:', options.type);\n // appOrientation은 Panel이 'auto'일 때 effective orientation을 결정하는 별도 필드.\n // viewport.orientation은 사용자 의도이므로 SDK가 임의로 덮어쓰지 않는다 — 그래야\n // 앱이 같은 방향으로 여러 번 호출해도 매번 정상 반영된다.\n aitState.patch('viewport', { appOrientation: options.type });\n return;\n }\n console.warn(\n `[@ait-co/devtools] setDeviceOrientation(${options.type}) ignored — Panel is forcing \"${current}\". Change the Viewport tab's orientation to \"auto\" to let the app control rotation.`,\n );\n}\n\nexport const setScreenAwakeMode = observe(\n 'setScreenAwakeMode',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setScreenAwakeMode:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nexport const setSecureScreen = observe(\n 'setSecureScreen',\n 'inert',\n async (options: { enabled: boolean }): Promise<{ enabled: boolean }> => {\n console.log('[@ait-co/devtools] setSecureScreen:', options.enabled);\n return { enabled: options.enabled };\n },\n);\n\nconst _requestReviewImpl = observe('requestReview', 'inert', async (): Promise<void> => {\n console.log('[@ait-co/devtools] requestReview called');\n});\nexport const requestReview: typeof _requestReviewImpl & { isSupported: () => boolean } =\n _requestReviewImpl as typeof _requestReviewImpl & { isSupported: () => boolean };\nrequestReview.isSupported = () => true;\n\n// --- 환경 정보 ---\n\nexport function getPlatformOS(): 'ios' | 'android' {\n return aitState.state.platform;\n}\n\nexport function getOperationalEnvironment(): 'toss' | 'sandbox' {\n return aitState.state.environment;\n}\n\nexport function getTossAppVersion(): string {\n return aitState.state.appVersion;\n}\n\nexport function isMinVersionSupported(minVersions: { android: string; ios: string }): boolean {\n const platform = aitState.state.platform;\n const required = platform === 'ios' ? minVersions.ios : minVersions.android;\n if (required === 'always') return true;\n if (required === 'never') return false;\n\n const current = aitState.state.appVersion.split('.').map(Number);\n const min = required.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n if ((current[i] ?? 0) > (min[i] ?? 0)) return true;\n if ((current[i] ?? 0) < (min[i] ?? 0)) return false;\n }\n return true; // equal\n}\n\nexport function getSchemeUri(): string {\n return aitState.state.schemeUri || window.location.pathname;\n}\n\nexport function getLocale(): string {\n return aitState.state.locale;\n}\n\nexport function getDeviceId(): string {\n return aitState.state.deviceId;\n}\n\nexport function getGroupId(): string {\n return aitState.state.groupId;\n}\n\nexport async function getNetworkStatus(): Promise<NetworkStatus> {\n const modeResult = getNetworkStatusByMode();\n if (modeResult) return modeResult;\n return aitState.state.networkStatus;\n}\n\nexport async function getServerTime(): Promise<number | undefined> {\n return Date.now();\n}\n(getServerTime as unknown as { isSupported: () => boolean }).isSupported = () => true;\n\n// --- 이벤트 시스템 ---\n\ninterface GraniteEventMap {\n backEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n homeEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: undefined };\n}\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEventMap>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEventMap[K]['onEvent'];\n onError?: GraniteEventMap[K]['onError'];\n options?: GraniteEventMap[K]['options'];\n },\n ): () => void {\n const handler = () => {\n try {\n onEvent();\n } catch (e) {\n onError?.(e instanceof Error ? e : new Error(String(e)));\n }\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport const appsInTossEvent = {\n addEventListener<K extends string>(\n _event: K,\n _handlers: {\n onEvent: (...args: unknown[]) => void;\n onError?: (error: Error) => void;\n options?: unknown;\n },\n ): () => void {\n return () => {};\n },\n};\n\ninterface TdsEventMap {\n navigationAccessoryEvent: {\n onEvent: (data: { id: string }) => void;\n onError?: (error: Error) => void;\n options: undefined;\n };\n}\n\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEventMap>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEventMap[K]['onEvent'];\n onError?: TdsEventMap[K]['onError'];\n options?: TdsEventMap[K]['options'];\n },\n ): () => void {\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n onEvent(detail);\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport function onVisibilityChangedByTransparentServiceWeb(eventParams: {\n options: { callbackId: string };\n onEvent: (isVisible: boolean) => void;\n onError: (error: unknown) => void;\n}): () => void {\n const handler = () => eventParams.onEvent(!document.hidden);\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n}\n\n// --- env / globals ---\n\nexport const env = {\n getDeploymentId: () => aitState.state.deploymentId,\n};\n\nexport function getAppsInTossGlobals() {\n return {\n deploymentId: aitState.state.deploymentId,\n brandDisplayName: aitState.state.brand.displayName,\n brandIcon: aitState.state.brand.icon,\n brandPrimaryColor: aitState.state.brand.primaryColor,\n };\n}\n\n// --- SafeAreaInsets ---\n\ntype SafeAreaInsetsValue = { top: number; bottom: number; left: number; right: number };\ntype SafeAreaInsetsSubscribeHandler = { onEvent: (data: SafeAreaInsetsValue) => void };\n\nexport const SafeAreaInsets = {\n get: (): SafeAreaInsetsValue => ({ ...aitState.state.safeAreaInsets }),\n // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.\n // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.\n subscribe: ({ onEvent }: SafeAreaInsetsSubscribeHandler): (() => void) => {\n return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));\n },\n};\n\n/** @deprecated */\nexport function getSafeAreaInsets(): number {\n return aitState.state.safeAreaInsets.top;\n}\n","/**\n * 알림 동의 mock\n *\n * SDK는 callback-style: `requestNotificationAgreement(params)`이 즉시 cancel 함수를\n * 반환하고, 결과는 `params.onEvent`로 전달된다. mock도 같은 모양을 흉내내며,\n * 결과는 panel(Notifications 탭)이 토글한 `aitState.state.notification.nextResult`를\n * 그대로 사용한다.\n *\n * `agreementRejected`도 정상 결과의 한 종류이므로 `onEvent`로 전달한다.\n * `onError`는 `onEvent` 호출 자체가 throw할 때만 들어간다 (실제 SDK도 reject를\n * error가 아닌 event type으로 표현한다).\n */\n\nimport { aitState } from './state.js';\nimport type { NotificationAgreementResult } from './types.js';\n\ninterface RequestNotificationAgreementOptions {\n options: { templateCode: string };\n onEvent: (result: { type: NotificationAgreementResult }) => void;\n onError: (error: unknown) => void | Promise<void>;\n}\n\nexport function requestNotificationAgreement(\n params: RequestNotificationAgreementOptions,\n): () => void {\n let cancelled = false;\n\n Promise.resolve().then(async () => {\n if (cancelled) return;\n const type = aitState.state.notification.nextResult;\n\n console.log(\n '[@ait-co/devtools] requestNotificationAgreement:',\n params.options.templateCode,\n '→',\n type,\n );\n\n try {\n params.onEvent({ type });\n } catch (e) {\n await params.onError(e);\n }\n });\n\n return () => {\n cancelled = true;\n };\n}\n","/**\n * Partner / TDS mock\n */\n\ninterface AddAccessoryButtonOptions {\n id: string;\n title: string;\n icon: { name: string };\n}\n\nexport const partner = {\n async addAccessoryButton(options: AddAccessoryButtonOptions): Promise<void> {\n console.log('[@ait-co/devtools] partner.addAccessoryButton:', options);\n },\n async removeAccessoryButton(): Promise<void> {\n console.log('[@ait-co/devtools] partner.removeAccessoryButton');\n },\n};\n","/**\n * 사용자 저장 preset CRUD. localStorage `__ait_preset:<id>` 한 키당 하나로 저장한다.\n * 패널-내부 storage(`__ait_btn_pos`, `__ait_device_id`, `__ait_storage:`)와 같은\n * 패턴 — 새 storage 도입 없음.\n *\n * 외부 의존성 0. SSR 환경(Node)에서 import만 되어도 안전하도록 모든 접근은\n * `localStorage` 존재 여부를 확인한다.\n */\n\nimport type { MockPreset, MockPresetState } from './presets.js';\n\nconst PREFIX = '__ait_preset:';\n\nfunction safeLocalStorage(): Storage | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n return localStorage;\n } catch {\n return null;\n }\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Storage에서 읽은 임의 JSON을 MockPreset으로 검증. id/label 필수, state는\n * object여야 함. 실패하면 null — caller가 storage entry를 무시하거나 정리하면 된다.\n *\n * `state`의 내부 키/값은 검증하지 않는다. `applyPreset`이 `pickKnownKeys`로\n * 키만 거른 뒤 그대로 state에 패치하므로 잘못된 enum 값이 통과될 수 있지만,\n * mock state라 보안 위협은 없다 — 새 enum 값이 추가됐을 때 저장된 preset을\n * reject하지 않으려는 의도.\n */\nfunction parsePreset(raw: string): MockPreset | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!isObject(parsed)) return null;\n const { id, label, description, state } = parsed;\n if (typeof id !== 'string' || id.length === 0) return null;\n if (typeof label !== 'string' || label.length === 0) return null;\n if (!isObject(state)) return null;\n return {\n id,\n label,\n description: typeof description === 'string' ? description : undefined,\n state: state as MockPresetState,\n };\n } catch {\n return null;\n }\n}\n\nexport function listUserPresets(): MockPreset[] {\n const ls = safeLocalStorage();\n if (!ls) return [];\n const out: MockPreset[] = [];\n for (let i = 0; i < ls.length; i++) {\n const key = ls.key(i);\n if (!key?.startsWith(PREFIX)) continue;\n const raw = ls.getItem(key);\n if (!raw) continue;\n const preset = parsePreset(raw);\n if (preset) out.push(preset);\n }\n return out.sort((a, b) => a.label.localeCompare(b.label));\n}\n\n/**\n * Preset을 저장한다. label에서 slug를 derive — 같은 slug가 이미 있으면 `-2`, `-3`\n * suffix를 붙여 새 entry를 만든다 (기존 entry 덮어쓰기 아님). UI는 label만 받으면 된다.\n *\n * Throws:\n * - label trim한 뒤 빈 문자열일 때\n * - localStorage 미가용 환경일 때 (SSR 등)\n * - `setItem` 실패 (`QuotaExceededError` 등) — caller가 처리해야 함\n */\nexport function saveUserPreset(\n label: string,\n state: MockPresetState,\n description?: string,\n): MockPreset {\n const trimmed = label.trim();\n if (trimmed.length === 0) {\n throw new Error('Preset label cannot be empty');\n }\n const ls = safeLocalStorage();\n if (!ls) throw new Error('localStorage not available');\n const id = generateId(trimmed, ls);\n const preset: MockPreset = {\n id,\n label: trimmed,\n state,\n ...(description !== undefined && description.length > 0 ? { description } : {}),\n };\n ls.setItem(PREFIX + id, JSON.stringify(preset));\n return preset;\n}\n\nexport function deleteUserPreset(id: string): void {\n const ls = safeLocalStorage();\n if (!ls) return;\n ls.removeItem(PREFIX + id);\n}\n\n/** 충돌 시 `-2`, `-3` 등 suffix를 붙여 unique한 id 만든다. */\nfunction generateId(label: string, ls: Storage): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'preset';\n let candidate = base;\n let n = 2;\n while (ls.getItem(PREFIX + candidate) !== null) {\n candidate = `${base}-${n}`;\n n += 1;\n }\n return candidate;\n}\n","/**\n * Mock state preset library.\n *\n * 자주 쓰는 QA 시나리오(권한 거부, 오프라인, 미로그인 등)를 한 클릭으로 적용할 수 있는\n * preset 정의 + apply 유틸. 토글 한 번에 여러 mock 키가 동시에 일정 상태가 되어야 하는\n * 경우 매번 손으로 맞추는 번잡함을 제거한다.\n *\n * Preset state는 `aitState`의 일부만 다룬다 — preset이 어떤 키도 건드리지 않으면 해당\n * 키는 현재 값을 유지한다. forward-compat: schema 외 키가 들어와도 무시된다.\n */\n\nimport type { AitDevtoolsState } from './state.js';\nimport { aitState } from './state.js';\n\n/**\n * Preset이 덮어쓸 수 있는 mock state slice. 모든 키는 optional —\n * 한 preset이 모든 분야를 정의할 필요 없다.\n *\n * 일부러 좁게 잡았다: viewport / brand / mockData / analyticsLog 등 QA 시나리오와\n * 직접 관련 없는 영역은 preset 대상에서 제외한다 (preset 적용으로 unrelated state가\n * 흔들리는 사고 방지). 필요해지면 추가.\n *\n * `iap` slice는 일부러 `nextResult`만 노출한다 — products / pendingOrders /\n * completedOrders는 array/object 비교가 까다롭고 QA 시나리오에서 강제할 일이 거의\n * 없다. `captureCurrentState` / `matchesPreset` 의 iap 처리 범위와 동기화.\n */\nexport interface MockPresetState {\n networkStatus?: AitDevtoolsState['networkStatus'];\n permissions?: Partial<AitDevtoolsState['permissions']>;\n auth?: Partial<AitDevtoolsState['auth']>;\n iap?: { nextResult?: AitDevtoolsState['iap']['nextResult'] };\n ads?: Partial<AitDevtoolsState['ads']>;\n payment?: Partial<AitDevtoolsState['payment']>;\n}\n\nexport interface MockPreset {\n id: string;\n label: string;\n description?: string;\n state: MockPresetState;\n}\n\nexport const builtInPresets: readonly MockPreset[] = [\n {\n id: 'all-allowed',\n label: 'All allowed (default-ish)',\n description: '모든 권한 허용, WIFI, 로그인됨, IAP success',\n state: {\n networkStatus: 'WIFI',\n permissions: {\n camera: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n clipboard: 'allowed',\n contacts: 'allowed',\n microphone: 'allowed',\n },\n auth: { isLoggedIn: true },\n iap: { nextResult: 'success' },\n ads: { forceNoFill: false },\n payment: { nextResult: 'success', failReason: '' },\n },\n },\n {\n id: 'permission-denied',\n label: 'Permissions denied',\n description: 'camera / photos / geolocation / contacts 거부',\n state: {\n permissions: {\n camera: 'denied',\n photos: 'denied',\n geolocation: 'denied',\n contacts: 'denied',\n },\n },\n },\n {\n id: 'offline',\n label: 'Offline',\n description: 'getNetworkStatus → OFFLINE, IAP NETWORK_ERROR',\n state: {\n networkStatus: 'OFFLINE',\n iap: { nextResult: 'NETWORK_ERROR' },\n payment: { nextResult: 'fail', failReason: 'NETWORK_ERROR' },\n },\n },\n {\n id: 'logged-out',\n label: 'Logged out',\n description: 'auth.isLoggedIn=false. login flow 검증용',\n state: {\n auth: { isLoggedIn: false },\n },\n },\n {\n id: 'iap-pending',\n label: 'IAP payment pending',\n description: '결제 진행 중 분기 검증',\n state: {\n iap: { nextResult: 'PAYMENT_PENDING' },\n },\n },\n {\n id: 'ads-no-fill',\n label: 'Ads — no fill',\n description: '광고 fill 실패 분기 검증',\n state: {\n networkStatus: 'WIFI',\n ads: { forceNoFill: true },\n },\n },\n];\n\n/**\n * Preset의 nested slice를 검증된 키만 골라서 풀어낸다. Forward-compat 차원에서\n * 알지 못하는 키는 drop, drop된 키 전부를 모아 한 번에 warn한다.\n *\n * Value 단위 검증은 하지 않는다 — `permissions.camera`에 enum 외 값이 들어와도\n * 그대로 통과한다. mock state라 잘못된 값은 mock 함수 분기 결과만 흔든다.\n * 새 enum 값이 추가됐을 때 저장된 preset을 reject하지 않으려는 의도.\n */\nfunction pickKnownKeys<T extends object>(\n input: unknown,\n allowed: readonly (keyof T)[],\n): Partial<T> {\n if (typeof input !== 'object' || input === null) return {};\n const out: Partial<T> = {};\n const dropped: string[] = [];\n for (const [key, value] of Object.entries(input)) {\n if ((allowed as readonly string[]).includes(key)) {\n (out as Record<string, unknown>)[key] = value;\n } else {\n dropped.push(key);\n }\n }\n if (dropped.length > 0) {\n console.warn(`[@ait-co/devtools] Preset dropped unknown keys: ${dropped.join(', ')}`);\n }\n return out;\n}\n\nconst PERMISSION_KEYS = [\n 'camera',\n 'photos',\n 'geolocation',\n 'clipboard',\n 'contacts',\n 'microphone',\n] as const;\nconst AUTH_KEYS = ['isLoggedIn', 'isTossLoginIntegrated', 'userKeyHash'] as const;\nconst IAP_KEYS = ['nextResult'] as const;\nconst ADS_KEYS = ['isLoaded', 'nextEvent', 'forceNoFill', 'lastEvent'] as const;\nconst PAYMENT_KEYS = ['nextResult', 'failReason'] as const;\n\n/**\n * Preset state를 현재 `aitState`에 적용한다. 정의된 키만 덮어쓰고, 알지 못하는 키는\n * 조용히 drop한다 (한 번 warn). 여러 슬라이스를 적용해도 listener notify는 한 번이다\n * (`aitState.transaction` 사용 — panel re-render 폭주 방지).\n */\nexport function applyPreset(state: MockPresetState): void {\n aitState.transaction(() => {\n if (state.networkStatus !== undefined) {\n aitState.update({ networkStatus: state.networkStatus });\n }\n if (state.permissions !== undefined) {\n aitState.patch(\n 'permissions',\n pickKnownKeys<AitDevtoolsState['permissions']>(state.permissions, PERMISSION_KEYS),\n );\n }\n if (state.auth !== undefined) {\n aitState.patch('auth', pickKnownKeys<AitDevtoolsState['auth']>(state.auth, AUTH_KEYS));\n }\n if (state.iap !== undefined) {\n const picked = pickKnownKeys<{ nextResult: AitDevtoolsState['iap']['nextResult'] }>(\n state.iap,\n IAP_KEYS,\n );\n aitState.patch('iap', picked);\n }\n if (state.ads !== undefined) {\n aitState.patch('ads', pickKnownKeys<AitDevtoolsState['ads']>(state.ads, ADS_KEYS));\n }\n if (state.payment !== undefined) {\n aitState.patch(\n 'payment',\n pickKnownKeys<AitDevtoolsState['payment']>(state.payment, PAYMENT_KEYS),\n );\n }\n });\n}\n\n/**\n * Preset의 모든 정의된 슬라이스가 현재 state와 일치하는지 검사. UI에서 dirty\n * indicator를 그릴 때 쓴다.\n *\n * 일치한다 = preset이 정의한 키 전부가 그대로다. preset이 정의하지 않은 키는\n * 비교 대상이 아니다 — preset은 partial이므로 다른 토글이 바뀌어도 dirty가 아니다.\n */\nexport function matchesPreset(snapshot: AitDevtoolsState, preset: MockPresetState): boolean {\n if (preset.networkStatus !== undefined && snapshot.networkStatus !== preset.networkStatus) {\n return false;\n }\n if (preset.permissions !== undefined) {\n for (const k of PERMISSION_KEYS) {\n const want = preset.permissions[k];\n if (want !== undefined && snapshot.permissions[k] !== want) return false;\n }\n }\n if (preset.auth !== undefined) {\n for (const k of AUTH_KEYS) {\n const want = preset.auth[k];\n if (want !== undefined && snapshot.auth[k] !== want) return false;\n }\n }\n if (preset.iap !== undefined) {\n if (preset.iap.nextResult !== undefined && snapshot.iap.nextResult !== preset.iap.nextResult) {\n return false;\n }\n }\n if (preset.ads !== undefined) {\n if (preset.ads.forceNoFill !== undefined && snapshot.ads.forceNoFill !== preset.ads.forceNoFill)\n return false;\n if (preset.ads.isLoaded !== undefined && snapshot.ads.isLoaded !== preset.ads.isLoaded)\n return false;\n if (preset.ads.nextEvent !== undefined && snapshot.ads.nextEvent !== preset.ads.nextEvent)\n return false;\n }\n if (preset.payment !== undefined) {\n for (const k of PAYMENT_KEYS) {\n const want = preset.payment[k];\n if (want !== undefined && snapshot.payment[k] !== want) return false;\n }\n }\n return true;\n}\n\n/**\n * 현재 state에서 preset에 저장할 만한 슬라이스를 추출. \"save current as preset\"에서 쓴다.\n */\nexport function captureCurrentState(snapshot: AitDevtoolsState): MockPresetState {\n return {\n networkStatus: snapshot.networkStatus,\n permissions: { ...snapshot.permissions },\n auth: {\n isLoggedIn: snapshot.auth.isLoggedIn,\n isTossLoginIntegrated: snapshot.auth.isTossLoginIntegrated,\n userKeyHash: snapshot.auth.userKeyHash,\n },\n iap: { nextResult: snapshot.iap.nextResult },\n ads: {\n forceNoFill: snapshot.ads.forceNoFill,\n isLoaded: snapshot.ads.isLoaded,\n nextEvent: snapshot.ads.nextEvent,\n },\n payment: { ...snapshot.payment },\n };\n}\n"],"mappings":"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"}