@ait-co/devtools 0.1.137 → 0.1.139

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.
Files changed (119) hide show
  1. package/dist/{attach-orchestrator-Cjdhir2U.js → attach-orchestrator-6kz_ZdWL.js} +5 -3
  2. package/dist/{attach-orchestrator-Cjdhir2U.js.map → attach-orchestrator-6kz_ZdWL.js.map} +1 -1
  3. package/dist/{attach-orchestrator-CE0S09YU.js → attach-orchestrator-BEiz3wAx.js} +5 -3
  4. package/dist/{attach-orchestrator-CMoDuG2A.js.map → attach-orchestrator-BEiz3wAx.js.map} +1 -1
  5. package/dist/{attach-orchestrator-CMoDuG2A.js → attach-orchestrator-BG8TGJOZ.js} +5 -3
  6. package/dist/{attach-orchestrator-CE0S09YU.js.map → attach-orchestrator-BG8TGJOZ.js.map} +1 -1
  7. package/dist/{bundle-BKqyhEK9.d.ts → bundle-C796JIwG.d.ts} +65 -2
  8. package/dist/bundle-C796JIwG.d.ts.map +1 -0
  9. package/dist/{capture-ltuV0gZa.d.ts → capture-DsP525OZ.d.ts} +1 -1
  10. package/dist/{capture-ltuV0gZa.d.ts.map → capture-DsP525OZ.d.ts.map} +1 -1
  11. package/dist/{cdp-connection-BYE9meXe.d.ts → cdp-connection-rP1WdnH5.d.ts} +1 -1
  12. package/dist/{cdp-connection-BYE9meXe.d.ts.map → cdp-connection-rP1WdnH5.d.ts.map} +1 -1
  13. package/dist/{cell-D1y4shoV.js → cell-BMlFh08P.js} +2 -2
  14. package/dist/cell-BMlFh08P.js.map +1 -0
  15. package/dist/{cell-DHA578lX.js → cell-Dtgtusog.js} +90 -16
  16. package/dist/cell-Dtgtusog.js.map +1 -0
  17. package/dist/{cell-BIdSQb9T.js → cell-KsRXFdDs.js} +122 -16
  18. package/dist/cell-KsRXFdDs.js.map +1 -0
  19. package/dist/{debug-server-CBK7dn8O.js → debug-server-6c22qSWa.js} +132 -9
  20. package/dist/debug-server-6c22qSWa.js.map +1 -0
  21. package/dist/{debug-server-BQplCzf8.js → debug-server-BQHbaKPT.js} +134 -11
  22. package/dist/debug-server-BQHbaKPT.js.map +1 -0
  23. package/dist/{debug-server-D2wpPK3_.js → debug-server-DWat7d-9.js} +82 -6
  24. package/dist/debug-server-DWat7d-9.js.map +1 -0
  25. package/dist/devtools-opener-3Drge_RJ.js +75 -0
  26. package/dist/devtools-opener-3Drge_RJ.js.map +1 -0
  27. package/dist/devtools-opener-CJpEsXXQ.js +76 -0
  28. package/dist/devtools-opener-CJpEsXXQ.js.map +1 -0
  29. package/dist/devtools-opener-CxtryS8c.js +75 -0
  30. package/dist/devtools-opener-CxtryS8c.js.map +1 -0
  31. package/dist/in-app/auto.js +59 -12
  32. package/dist/in-app/auto.js.map +1 -1
  33. package/dist/in-app/index.d.ts +21 -2
  34. package/dist/in-app/index.d.ts.map +1 -1
  35. package/dist/in-app/index.js +60 -13
  36. package/dist/in-app/index.js.map +1 -1
  37. package/dist/mcp/cli.js +299 -60
  38. package/dist/mcp/cli.js.map +1 -1
  39. package/dist/mcp/server.js +1 -1
  40. package/dist/mock/index.d.ts +79 -1
  41. package/dist/mock/index.d.ts.map +1 -1
  42. package/dist/mock/index.js +218 -14
  43. package/dist/mock/index.js.map +1 -1
  44. package/dist/panel/index.js +171 -10
  45. package/dist/panel/index.js.map +1 -1
  46. package/dist/{pool-CmfvXnwh.d.ts → pool-CrP5CPvU.d.ts} +3 -3
  47. package/dist/{pool-CmfvXnwh.d.ts.map → pool-CrP5CPvU.d.ts.map} +1 -1
  48. package/dist/{qr-http-server-ZkT6F7is.js → qr-http-server-D1hpoyBF.js} +1 -1
  49. package/dist/{qr-http-server-ZkT6F7is.js.map → qr-http-server-D1hpoyBF.js.map} +1 -1
  50. package/dist/qr-http-server-D4rGz8M7.js +1640 -0
  51. package/dist/qr-http-server-D4rGz8M7.js.map +1 -0
  52. package/dist/{qr-http-server-C7q8Gn04.js → qr-http-server-DIjCV7h_.js} +1 -1
  53. package/dist/{qr-http-server-C7q8Gn04.js.map → qr-http-server-DIjCV7h_.js.map} +1 -1
  54. package/dist/{relay-factory-DVzpBRxz.js → relay-factory-C1X3G3WY.js} +71 -15
  55. package/dist/relay-factory-C1X3G3WY.js.map +1 -0
  56. package/dist/{relay-secret-store-CLEGyHou.js → relay-secret-store-Bmyleu0A.js} +1 -1
  57. package/dist/{relay-secret-store-CLEGyHou.js.map → relay-secret-store-Bmyleu0A.js.map} +1 -1
  58. package/dist/{relay-secret-store-DhzAnnj-.js → relay-secret-store-CQenfcSL.js} +2 -2
  59. package/dist/{relay-secret-store-DhzAnnj-.js.map → relay-secret-store-CQenfcSL.js.map} +1 -1
  60. package/dist/{relay-secret-store-BcVrWwTq.js → relay-secret-store-DKxs7zwq.js} +1 -1
  61. package/dist/{relay-secret-store-BcVrWwTq.js.map → relay-secret-store-DKxs7zwq.js.map} +1 -1
  62. package/dist/{relay-secret-store-DGduVJhs.js → relay-secret-store-WJ8EGkIl.js} +1 -1
  63. package/dist/{relay-secret-store-DGduVJhs.js.map → relay-secret-store-WJ8EGkIl.js.map} +1 -1
  64. package/dist/{relay-url-store-CKW8RQzf.js → relay-url-store-BR2XodiO.js} +2 -2
  65. package/dist/{relay-url-store-CKW8RQzf.js.map → relay-url-store-BR2XodiO.js.map} +1 -1
  66. package/dist/{relay-url-store-CdA58fgw.js → relay-url-store-CH63fVCm.js} +2 -2
  67. package/dist/{relay-url-store-B0X8TsGr.js.map → relay-url-store-CH63fVCm.js.map} +1 -1
  68. package/dist/{relay-url-store-CwKT7i04.js → relay-url-store-DaY1QPes.js} +2 -2
  69. package/dist/{relay-url-store-CwKT7i04.js.map → relay-url-store-DaY1QPes.js.map} +1 -1
  70. package/dist/{relay-url-store-B0X8TsGr.js → relay-url-store-xmUuTjXA.js} +2 -2
  71. package/dist/{relay-url-store-CdA58fgw.js.map → relay-url-store-xmUuTjXA.js.map} +1 -1
  72. package/dist/{relay-worker-6fy1eaIo.js → relay-worker-BUXI0K0b.js} +8 -4
  73. package/dist/{relay-worker-6fy1eaIo.js.map → relay-worker-BUXI0K0b.js.map} +1 -1
  74. package/dist/{relay-worker-DJnZkXza.d.ts → relay-worker-DcyboK43.d.ts} +30 -5
  75. package/dist/relay-worker-DcyboK43.d.ts.map +1 -0
  76. package/dist/{runtime-DfHHZms2.d.ts → runtime-BiigOuvb.d.ts} +30 -3
  77. package/dist/runtime-BiigOuvb.d.ts.map +1 -0
  78. package/dist/test-runner/bin.js +376 -38
  79. package/dist/test-runner/bin.js.map +1 -1
  80. package/dist/test-runner/bundle.d.ts +2 -2
  81. package/dist/test-runner/bundle.js +66 -13
  82. package/dist/test-runner/bundle.js.map +1 -1
  83. package/dist/test-runner/capture.d.ts +1 -1
  84. package/dist/test-runner/config.d.ts +58 -5
  85. package/dist/test-runner/config.d.ts.map +1 -1
  86. package/dist/test-runner/config.js +1 -1
  87. package/dist/test-runner/method-pace.d.ts +82 -0
  88. package/dist/test-runner/method-pace.d.ts.map +1 -0
  89. package/dist/test-runner/method-pace.js +120 -0
  90. package/dist/test-runner/method-pace.js.map +1 -0
  91. package/dist/test-runner/pool.d.ts +1 -1
  92. package/dist/test-runner/pool.js +1 -1
  93. package/dist/test-runner/relay-factory.d.ts +57 -4
  94. package/dist/test-runner/relay-factory.d.ts.map +1 -1
  95. package/dist/test-runner/relay-factory.js +70 -14
  96. package/dist/test-runner/relay-factory.js.map +1 -1
  97. package/dist/test-runner/relay-worker.d.ts +1 -1
  98. package/dist/test-runner/relay-worker.js +1 -1
  99. package/dist/test-runner/report.d.ts +3 -3
  100. package/dist/test-runner/rpc.d.ts +2 -2
  101. package/dist/test-runner/runtime.d.ts +2 -2
  102. package/dist/test-runner/runtime.js +83 -6
  103. package/dist/test-runner/runtime.js.map +1 -1
  104. package/dist/test-runner/task-graph.d.ts +1 -1
  105. package/dist/throttle-DKKzX1qC.js +59 -0
  106. package/dist/throttle-DKKzX1qC.js.map +1 -0
  107. package/dist/totp-Dwft0Kz7.js +3 -0
  108. package/package.json +1 -1
  109. package/dist/bundle-BKqyhEK9.d.ts.map +0 -1
  110. package/dist/cell-BIdSQb9T.js.map +0 -1
  111. package/dist/cell-D1y4shoV.js.map +0 -1
  112. package/dist/cell-DHA578lX.js.map +0 -1
  113. package/dist/debug-server-BQplCzf8.js.map +0 -1
  114. package/dist/debug-server-CBK7dn8O.js.map +0 -1
  115. package/dist/debug-server-D2wpPK3_.js.map +0 -1
  116. package/dist/relay-factory-DVzpBRxz.js.map +0 -1
  117. package/dist/relay-worker-DJnZkXza.d.ts.map +0 -1
  118. package/dist/runtime-DfHHZms2.d.ts.map +0 -1
  119. package/dist/totp-D1pulXLa.js +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/mock/state.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/proxy.ts","../../src/mock/device/storage.ts","../../src/mock/observe.ts","../../src/mock/navigation/index.ts","../../src/mock/safe-area-bridge.ts","../../src/mock/ads/index.ts","../../src/mock/analytics/index.ts","../../src/mock/auth/index.ts","../../src/mock/game/index.ts","../../src/mock/iap/index.ts","../../src/mock/notification.ts","../../src/mock/partner/index.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts","../../src/mock/index.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 * 디바이스 모듈 내부 공유 헬퍼\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 type {\n PermissionAccess,\n PermissionName,\n PermissionStatus,\n} from '@apps-in-toss/web-framework';\nimport { aitState } from './state.js';\n\n// --- PermissionError 계층 (web-framework 3.0+ 신규) ---\n// checkPermission()이 권한 거부 시 per-API *PermissionError 서브클래스를 throw한다 (#372).\n\n/**\n * web-framework 3.0+ 권한 에러 기반 클래스.\n * `instanceof PermissionError`로 체크하는 코드와 호환된다.\n */\nexport class PermissionError extends Error {\n constructor({ methodName, message }: { methodName: string; message?: string }) {\n super(message ?? `${methodName}: permission denied`);\n this.name = `${methodName}PermissionError`;\n }\n}\n\n/** openCamera 권한 에러 */\nexport class OpenCameraPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'openCamera' });\n }\n}\n\n/** fetchAlbumPhotos 권한 에러 */\nexport class FetchAlbumPhotosPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'fetchAlbumPhotos' });\n }\n}\n\n/** fetchContacts 권한 에러 */\nexport class FetchContactsPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'fetchContacts' });\n }\n}\n\n/** getCurrentLocation 권한 에러 */\nexport class GetCurrentLocationPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'getCurrentLocation' });\n }\n}\n\n/** getClipboardText 권한 에러 */\nexport class GetClipboardTextPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'getClipboardText' });\n }\n}\n\n/** setClipboardText 권한 에러 */\nexport class SetClipboardTextPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'setClipboardText' });\n }\n}\n\n/**\n * startUpdateLocation 권한 에러.\n * web-framework 3.0에서 GetCurrentLocationPermissionError의 alias.\n */\nexport const StartUpdateLocationPermissionError = GetCurrentLocationPermissionError;\n\n// --- API 이름 → PermissionError 매핑 (web-framework 3.0 표면 기준) ---\n// 실 SDK가 각 API 거부 시 throw하는 클래스와 1:1 대응. SDK에 없는 API(fetchAlbumItems)는\n// 동일한 'photos' 권한을 공유하는 FetchAlbumPhotosPermissionError를 사용한다.\n\nconst permissionErrorMap: Record<string, new () => PermissionError> = {\n openCamera: OpenCameraPermissionError,\n fetchAlbumPhotos: FetchAlbumPhotosPermissionError,\n // fetchAlbumItems는 SDK에 별도 PermissionError 없음 — photos 권한을 공유하므로 동일 클래스.\n fetchAlbumItems: FetchAlbumPhotosPermissionError,\n fetchContacts: FetchContactsPermissionError,\n getCurrentLocation: GetCurrentLocationPermissionError,\n getClipboardText: GetClipboardTextPermissionError,\n setClipboardText: SetClipboardTextPermissionError,\n};\n\n// SDK 시그니처: getPermission(permission: { name: PermissionName; access: PermissionAccess }): Promise<PermissionStatus>\nexport async function getPermission(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<PermissionStatus> {\n return aitState.state.permissions[permission.name];\n}\n\n// SDK 시그니처: openPermissionDialog(permission: { name: PermissionName; access: PermissionAccess }): Promise<Exclude<PermissionStatus, \"notDetermined\">>\nexport async function openPermissionDialog(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[permission.name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [permission.name]: 'allowed' });\n return 'allowed';\n}\n\n// SDK 시그니처: requestPermission(permission: { name: PermissionName; access: PermissionAccess }): Promise<Exclude<PermissionStatus, \"notDetermined\">>\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission);\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({ name: permissionName, access: 'access' });\n enhanced.openPermissionDialog = () =>\n openPermissionDialog({ name: permissionName, access: 'access' });\n return enhanced;\n}\n\n/**\n * 권한 체크 후 denied면 per-API *PermissionError 서브클래스를 throw한다.\n * 실 3.0 SDK 동작과 일치 — `instanceof PermissionError` 분기가 mock에서도 동작한다 (#372).\n */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n const ErrorClass = permissionErrorMap[fnName];\n if (ErrorClass) {\n throw new ErrorClass();\n }\n // 매핑에 없는 fnName은 기반 클래스로 fallback (SDK 표면 밖의 경로 보호)\n throw new PermissionError({ methodName: fnName });\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};\nconst _fetchAlbumItemsWithPermission = withPermission(_fetchAlbumItems, 'photos');\nexport const fetchAlbumItems: typeof _fetchAlbumItemsWithPermission & {\n isSupported: () => boolean;\n} = Object.assign(_fetchAlbumItemsWithPermission, { isSupported: () => true });\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 * - @ait-co/polyfill 동시 사용 시 재귀 방지: polyfill이 navigator.vibrate를 override하고\n * 내부에서 mock의 generateHapticFeedback을 호출하므로 무한 재귀가 발생한다. polyfill이\n * 원본 vibrate를 BACKUP_KEY(Symbol.for('@ait-co/polyfill/vibrate.original'))에 저장하면\n * 그 원본을 직접 호출해 재귀를 끊는다.\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\n/**\n * navigator.vibrate를 안전하게 호출한다.\n *\n * @ait-co/polyfill/auto가 설치된 환경에서는 navigator.vibrate가 polyfill shim으로\n * override되어 있고, 그 shim은 내부적으로 mock의 generateHapticFeedback을 호출한다.\n * mock이 다시 navigator.vibrate(현재 = shim)를 호출하면 무한 재귀가 발생한다.\n * polyfill은 원본 vibrate를 BACKUP_KEY에 저장하므로 그쪽을 직접 호출한다.\n */\nconst POLYFILL_VIBRATE_BACKUP = Symbol.for('@ait-co/polyfill/vibrate.original');\n\nfunction callVibrate(pattern: VibratePattern): boolean {\n if (typeof navigator === 'undefined') return false;\n // polyfill이 설치되어 있으면 원본 vibrate를 직접 호출해 재귀를 방지한다.\n const nav = navigator as Navigator & Record<symbol, ((p: VibratePattern) => boolean) | undefined>;\n const original = POLYFILL_VIBRATE_BACKUP in nav ? nav[POLYFILL_VIBRATE_BACKUP] : null;\n if (typeof original === 'function') return original(pattern);\n // polyfill 없음 — 그냥 navigator.vibrate 호출.\n return typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\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 = callVibrate(pattern);\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 */\nconst _openPDFViewerImpl = async (_params: OpenPDFViewerParams): Promise<OpenPDFViewerResult> => {\n // 실 SDK와 동일하게 비동기로 resolve한다.\n await Promise.resolve();\n return 'CLOSE';\n};\nexport const openPDFViewer: ((params: OpenPDFViewerParams) => Promise<OpenPDFViewerResult>) & {\n isSupported: () => boolean;\n} = Object.assign(_openPDFViewerImpl, { isSupported: () => true });\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 * 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 * 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 * 화면/네비게이션/이벤트 mock\n */\n\nimport type { GraniteEvent, TdsEvent } from '@apps-in-toss/web-framework';\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\nconst _getServerTimeImpl = async (): Promise<number | undefined> => {\n return Date.now();\n};\nexport const getServerTime: (() => Promise<number | undefined>) & { isSupported: () => boolean } =\n Object.assign(_getServerTimeImpl, { isSupported: () => true });\n\n// --- 이벤트 시스템 ---\n\n/**\n * 현재 backEvent 구독자 수. graniteEvent.addEventListener('backEvent', …)가\n * 증가시키고, 반환된 cleanup이 감소시킨다. 호스트 back 메시지 처리 시 인터셉트\n * 여부를 판단하는 데 쓰인다.\n *\n * @internal 테스트 및 safe-area-bridge에서만 사용.\n */\nlet _backEventSubscriberCount = 0;\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEvent>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEvent[K]['onEvent'];\n onError?: GraniteEvent[K]['onError'];\n options?: GraniteEvent[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\n // backEvent 구독자 카운터 관리\n if (event === 'backEvent') {\n _backEventSubscriberCount++;\n }\n\n let cleaned = false;\n return () => {\n if (cleaned) return; // 이중 호출 방지\n cleaned = true;\n window.removeEventListener(`__ait:${event}`, handler);\n if (event === 'backEvent') {\n _backEventSubscriberCount--;\n }\n };\n },\n};\n\n/**\n * 호스트 back 내비게이션을 처리한다.\n *\n * backEvent 구독자가 1명 이상이면 `window.dispatchEvent(new CustomEvent('__ait:backEvent'))`만\n * 발사한다 — 미니앱이 back을 가로채는(intercept) 채널이고 실제 토스 호스트와 동일한 시맨틱.\n * 구독자가 없으면 `history.back()`을 호출해 기본 브라우저 뒤로가기를 수행한다.\n *\n * env 1 패널의 back 버튼(`src/panel/viewport.ts` `aitState.trigger('backEvent')`)과\n * 동일한 경로를 거쳐 back 시맨틱의 단일 소유처를 navigation 모듈에 유지한다.\n */\nexport function dispatchHostBackNavigation(): void {\n if (_backEventSubscriberCount > 0) {\n window.dispatchEvent(new CustomEvent('__ait:backEvent'));\n } else {\n history.back();\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\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEvent>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEvent[K]['onEvent'];\n onError?: TdsEvent[K]['onError'];\n options?: TdsEvent[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\n/**\n * @deprecated web-framework 3.0 에서 제거됨. 2.x 소비자 back-compat용으로 유지.\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 * env-2 postMessage bridges (#484, #510).\n *\n * In the AITC Sandbox PWA (env 2) the dev app runs inside the launcher's\n * full-viewport `<iframe>`. The launcher is the top-level document, so its\n * `env(safe-area-inset-*)` measurement is the ground truth for the real device\n * geometry. The framed page's mock would otherwise report a synthetic preset\n * value (e.g. top=54), which sdk-example then double-pads on top of a viewport\n * that already starts below the status bar — the env-2 \"dead band\" defect.\n *\n * This module installs receive-half listeners for two message types:\n *\n * 1. `ait:safe-area-insets` (#484): the launcher forwards its real env() insets\n * to the framed page on iframe load and resize/orientationchange. Validates the\n * envelope and writes real insets into the mock SafeAreaInsets state, firing the\n * subscribe path (see navigation/index.ts) so apps that subscribe re-read the\n * corrected values.\n *\n * 2. `ait:navigate-back` (#510): the launcher partner bar's `←` button posts this\n * command to the framed page. The receive half calls `dispatchHostBackNavigation()`\n * (navigation/index.ts): if backEvent subscribers are present, a `__ait:backEvent`\n * CustomEvent is dispatched (the mini-app intercept channel, matching the env-1\n * panel path); otherwise `history.back()` is called. No data other than `type` is\n * read from or written to the message — shape validation rejects anything that\n * carries extra fields with the wrong type. Apps that do not install this mock\n * (older builds) silently ignore the message (natural no-op).\n *\n * Origin policy: neither message type carries sensitive data, so we do NOT\n * restrict by origin — the launcher posts cross-origin from a *.trycloudflare.com\n * tunnel with targetOrigin '*'. Shape validation is still mandatory: a malformed\n * or out-of-range message is silently ignored so a stray postMessage can never\n * corrupt the mock state or trigger spurious navigation.\n *\n * Message-driven by design: env 1 (desktop browser, no launcher) never receives\n * these messages, so the panel preset stays authoritative there with zero special\n * casing here.\n */\n\nimport { dispatchHostBackNavigation } from './navigation/index.js';\nimport { aitState } from './state.js';\nimport type { SafeAreaInsets } from './types.js';\n\n/** The postMessage envelope the launcher posts to the framed dev app (inset forward). */\nexport const SAFE_AREA_INSETS_MESSAGE_TYPE = 'ait:safe-area-insets' as const;\n\n/**\n * The postMessage command the launcher partner bar's `←` button sends to the\n * framed dev app (#510). The framed page calls `history.back()` in response.\n *\n * Protocol: only `{ type: 'ait:navigate-back' }` is valid. No other fields are\n * read or acted on — extra fields are silently ignored by the shape guard.\n * Game variant never sends this message (back button is partner-bar-only).\n */\nexport const NAVIGATE_BACK_MESSAGE_TYPE = 'ait:navigate-back' as const;\n\n/**\n * The postMessage envelope the framed mini-app self-reports its webViewType\n * with (#580). The mini-app knows its own type from the build constant\n * `__WEB_VIEW_TYPE__` (`granite.config.ts`'s `webViewProps.type`, injected by\n * the devtools unplugin). The launcher is cross-origin so it cannot read that\n * constant directly — the mini-app posts it to `window.parent` once so the\n * launcher (env-2 PWA shell) switches to game mode automatically, with no\n * manual `?navBarType=game` URL edit.\n *\n * Direction: this is the SEND side's contract (posted from inside the iframe by\n * `src/in-app/attach.ts`). The launcher's receive half lives in\n * `e2e/fixture/launcher/Launcher.tsx` and mirrors the same value enum inline,\n * staying decoupled from the mock package internals — the same pattern the\n * other launcher message types follow.\n *\n * Value enum: only `'partner'` and `'game'` are valid. The SDK's deprecated\n * `'external'` alias of `partner` (web-framework 2.6.1) is mapped to `'partner'`\n * at the send site so the wire only ever carries the two shapes the launcher\n * emulates.\n */\nexport const WEB_VIEW_TYPE_MESSAGE_TYPE = 'ait:web-view-type' as const;\n\n/** The two webViewType shapes the launcher can emulate (#580). */\nexport type WebViewTypeValue = 'partner' | 'game';\n\n// Insets are CSS px; a real device tops out well under this. The bound rejects\n// nonsense (NaN/Infinity/negative/absurd) without being so tight it clips a\n// future large-notch device.\nconst MAX_INSET_PX = 200;\n\nfunction isValidInset(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= MAX_INSET_PX;\n}\n\n/**\n * Parse + validate a raw postMessage payload into a `SafeAreaInsets`, or return\n * null when it is not a well-formed `ait:safe-area-insets` message. Pure — unit\n * tested without a real MessageEvent.\n */\nexport function parseSafeAreaInsetsMessage(data: unknown): SafeAreaInsets | null {\n if (typeof data !== 'object' || data === null) return null;\n if ((data as { type?: unknown }).type !== SAFE_AREA_INSETS_MESSAGE_TYPE) return null;\n\n const insets = (data as { insets?: unknown }).insets;\n if (typeof insets !== 'object' || insets === null) return null;\n\n const { top, bottom, left, right } = insets as Record<string, unknown>;\n if (!isValidInset(top) || !isValidInset(bottom) || !isValidInset(left) || !isValidInset(right)) {\n return null;\n }\n return { top, bottom, left, right };\n}\n\n/**\n * Parse + validate a raw postMessage payload into a webViewType value\n * (`'partner'` | `'game'`), or return `null` when it is not a well-formed\n * `ait:web-view-type` message (#580). Pure — unit tested without a real\n * MessageEvent.\n *\n * Strict shape guard (the safety boundary for the cross-origin receive path):\n * the payload must be a non-null object whose `type` is exactly\n * {@link WEB_VIEW_TYPE_MESSAGE_TYPE} and whose `value` is exactly `'partner'`\n * or `'game'` (an enum allow-list). Anything else — a foreign type, a missing\n * or non-string value, the deprecated `'external'` alias, or any other string —\n * returns `null` so a stray postMessage can never flip the launcher's visual\n * mode. The send site is responsible for collapsing `'external'` → `'partner'`\n * before posting; the parser does NOT silently accept it.\n */\nexport function parseWebViewTypeMessage(data: unknown): WebViewTypeValue | null {\n if (typeof data !== 'object' || data === null) return null;\n if ((data as { type?: unknown }).type !== WEB_VIEW_TYPE_MESSAGE_TYPE) return null;\n const value = (data as { value?: unknown }).value;\n if (value === 'partner' || value === 'game') return value;\n return null;\n}\n\n/**\n * Apply forwarded insets to the mock state. Skips the write (and the resulting\n * subscribe notify) when nothing changed, so repeated identical messages from a\n * resize storm don't churn subscribers.\n */\nexport function applyForwardedSafeAreaInsets(insets: SafeAreaInsets): void {\n const current = aitState.state.safeAreaInsets;\n if (\n current.top === insets.top &&\n current.bottom === insets.bottom &&\n current.left === insets.left &&\n current.right === insets.right\n ) {\n return;\n }\n aitState.update({ safeAreaInsets: insets });\n}\n\n/**\n * The `id` attribute of the `<style>` element injected by\n * {@link applyEnv2Compensation}. Using a stable id makes the injection\n * idempotent — the element is reused on every subsequent message rather than\n * accumulating multiple `<style>` nodes.\n */\nexport const ENV2_COMPENSATION_STYLE_ID = 'ait-env2-safe-area-compensation';\n\n/**\n * The CSS text injected when the launcher forwards partner-mode insets\n * (`top === 0`). The negative margin pulls the mini-app document up by exactly\n * the device's status-bar inset so the launcher's own bar offset is not\n * double-counted.\n *\n * jsdom cannot evaluate `env()` expressions, so unit tests assert on this\n * string literal rather than computed geometry.\n */\nexport const ENV2_COMPENSATION_CSS = 'body { margin-top: calc(-1 * env(safe-area-inset-top)); }';\n\n/**\n * Inject or remove the env-2 safe-area compensation `<style>` based on the\n * forwarded insets received from the launcher.\n *\n * ### Why this is needed\n *\n * In env 2 (AITC Sandbox PWA) the launcher frames the mini-app in a\n * full-bleed `<iframe>` whose top edge is already positioned BELOW the\n * launcher's partner bar (at `env(safe-area-inset-top) + 54px`). However,\n * inside the cross-origin child iframe, CSS `env(safe-area-inset-top)` STILL\n * reports the full device status-bar inset (e.g. 62 px on an iPhone 15). Any\n * mini-app that pads itself by that CSS `env()` value ends up double-counting\n * the status-bar, producing a ≈62 px white dead-band between the partner bar\n * and the first content.\n *\n * The fix is a compensating negative margin on `body` inside the iframe. When\n * the launcher forwards partner insets (`top === 0`, meaning \"the iframe\n * already starts below the bar\"), we inject:\n *\n * ```css\n * body { margin-top: calc(-1 * env(safe-area-inset-top)); }\n * ```\n *\n * This cancels the duplicate padding without knowing the app's internal DOM\n * structure. The launcher's html/body `screen.height` force (#527) lives in the\n * LAUNCHER document (cross-origin) and never touches the mini-app document, so\n * margin and height force compose independently — they cannot interfere.\n *\n * ### Gating\n *\n * - **`top === 0`** (partner mode) → install the style (dead-band compensation).\n * - **`top > 0`** (game mode — launcher forwards the raw device inset for the\n * floating status-bar capsule) → remove any previously installed style so\n * `env()` is the authoritative clearance and nothing is double-removed.\n *\n * On every insets message (resize/orientation re-post) this function is called\n * so the gate is re-evaluated and the style toggled accordingly.\n *\n * ### env 1 / env 3 / env 4 no-regression\n *\n * This function is only called from the `message` listener installed by\n * {@link installSafeAreaInsetsBridge}. That listener fires only when the\n * launcher posts an `ait:safe-area-insets` message, which never happens in\n * env 1 (desktop browser, no launcher), env 3 (intoss-private WebView), or\n * env 4 (live relay) — they have no launcher posting to the iframe.\n *\n * @param top - The forwarded `top` inset value from the launcher message.\n */\nexport function applyEnv2Compensation(top: number): void {\n if (typeof document === 'undefined') return;\n\n const existing = document.getElementById(ENV2_COMPENSATION_STYLE_ID);\n\n if (top === 0) {\n // Partner mode: install the compensation style (idempotent — reuse if present).\n if (existing !== null) return; // already installed, nothing to do\n const style = document.createElement('style');\n style.id = ENV2_COMPENSATION_STYLE_ID;\n style.textContent = ENV2_COMPENSATION_CSS;\n document.head.appendChild(style);\n } else {\n // Game mode (or any non-zero top): remove the compensation if it was\n // previously installed so env() remains the authoritative clearance.\n if (existing !== null) existing.remove();\n }\n}\n\nlet installed = false;\n\n/**\n * Install the window `message` listener that receives forwarded insets. Safe to\n * call multiple times (idempotent) and a no-op outside a browser (SSR/jsdom\n * without a window). Imported for its side effect by the mock barrel so any\n * consumer that aliases `@apps-in-toss/web-framework` to the mock gets it wired.\n *\n * On each valid `ait:safe-area-insets` message:\n * 1. Writes the corrected insets into the mock `SafeAreaInsets` state\n * (existing #484 behaviour — for apps that still read SDK insets).\n * 2. Drives the env-2 dead-band compensation style via\n * {@link applyEnv2Compensation} — injects a `body { margin-top: calc(-1 *\n * env(safe-area-inset-top)) }` style when `top === 0` (partner mode) and\n * removes it when `top > 0` (game / full-bleed mode).\n */\nexport function installSafeAreaInsetsBridge(): void {\n if (installed || typeof window === 'undefined') return;\n installed = true;\n window.addEventListener('message', (event: MessageEvent) => {\n const insets = parseSafeAreaInsetsMessage(event.data);\n if (insets) {\n applyForwardedSafeAreaInsets(insets);\n applyEnv2Compensation(insets.top);\n }\n });\n}\n\n/**\n * Parse a raw postMessage payload as an `ait:navigate-back` command.\n *\n * Returns true when the payload is a well-formed navigate-back command\n * (`{ type: 'ait:navigate-back' }`), false otherwise. Pure — unit tested\n * without a real MessageEvent.\n *\n * Shape guard: only the `type` field is inspected; any extra fields are\n * ignored so future extensions do not break older receivers. The function\n * does NOT read any data field beyond `type` — no sensitive values, no host\n * disclosure (same principle as the insets bridge).\n */\nexport function isNavigateBackMessage(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false;\n return (data as { type?: unknown }).type === NAVIGATE_BACK_MESSAGE_TYPE;\n}\n\nlet navigateBackInstalled = false;\n\n/**\n * Install the window `message` listener that handles `ait:navigate-back`\n * commands (#510). When the launcher partner bar's `←` button is clicked it\n * posts `{ type: 'ait:navigate-back' }` to the framed dev app; this listener\n * calls `dispatchHostBackNavigation()` from the navigation module.\n *\n * Dispatch semantics: if there are any `graniteEvent.addEventListener('backEvent', …)`\n * subscribers the CustomEvent `__ait:backEvent` is fired (same path as the env-1\n * panel back button — the mini-app intercept channel). When there are no\n * subscribers `history.back()` is called as the fallback. Back semantics are\n * owned entirely by the navigation module; this bridge only delegates.\n *\n * Safe to call multiple times (idempotent) and a no-op outside a browser.\n * Installed together with the inset bridge by `installBridges()` so any consumer\n * of the mock barrel gets both wired automatically.\n *\n * No-op on apps that predate this bridge — the launcher posts the message but\n * older mocks simply have no listener (harmless).\n */\nexport function installNavigateBackBridge(): void {\n if (navigateBackInstalled || typeof window === 'undefined') return;\n navigateBackInstalled = true;\n window.addEventListener('message', (event: MessageEvent) => {\n if (isNavigateBackMessage(event.data)) {\n dispatchHostBackNavigation();\n }\n });\n}\n\n/**\n * Install both env-2 postMessage bridges in one call (#484 insets + #510\n * navigate-back). The mock barrel calls this at import time so consumers get\n * all bridges wired without any explicit setup.\n */\nexport function installBridges(): void {\n installSafeAreaInsetsBridge();\n installNavigateBackBridge();\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 type {\n LoadAdMobEvent,\n LoadAdMobOptions,\n LoadFullScreenAdEvent,\n LoadFullScreenAdOptions,\n ShowAdMobEvent,\n ShowAdMobOptions,\n ShowFullScreenAdEvent,\n ShowFullScreenAdOptions,\n} from '@apps-in-toss/web-framework';\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 options: LoadAdMobOptions;\n onEvent: (event: LoadAdMobEvent) => void;\n onError: (error: unknown) => void;\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({\n type: 'loaded',\n data: { responseInfo: { responseId: `mock-response-${args.options.adGroupId}` } },\n });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n options: ShowAdMobOptions;\n onEvent: (event: ShowAdMobEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n setTimeout(\n () =>\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n }),\n 1000,\n );\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 options: LoadFullScreenAdOptions;\n onEvent: (event: LoadFullScreenAdEvent) => void;\n onError: (error: unknown) => void;\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' });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n options: ShowFullScreenAdOptions;\n onEvent: (event: ShowFullScreenAdEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'clicked' }), 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: string;\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\n// SDK 3.0에서 getUserKeyForGame = typeof getAnonymousKey → Promise<{ hash: string; type: 'HASH' }>\n// 이전의 'INVALID_CATEGORY'|'ERROR'|undefined sentinel은 실제 SDK 타입에 없으므로 제거.\nexport async function getUserKeyForGame(): Promise<{ hash: string; type: 'HASH' }> {\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 * 게임/프로모션 mock\n */\n\nimport type {\n ContactsViralEvent,\n GrantPromotionRewardParams,\n GrantPromotionRewardResponse,\n} from '@apps-in-toss/web-framework';\nimport { aitState } from '../state.js';\n\n// SDK: grantPromotionReward(params: GrantPromotionRewardParams): Promise<GrantPromotionRewardResponse>\n// 이전 mock의 sentinel('ERROR'|undefined) 리턴값은 실제 SDK 타입에 없는 것이었으므로 제거.\nexport async function grantPromotionReward(\n params: GrantPromotionRewardParams,\n): Promise<GrantPromotionRewardResponse> {\n console.log('[@ait-co/devtools] grantPromotionReward:', params.params);\n return { key: `mock-reward-${Date.now()}` };\n}\n\n// SDK: grantPromotionRewardForGame = typeof grantPromotionReward\nexport async function grantPromotionRewardForGame(\n params: GrantPromotionRewardParams,\n): Promise<GrantPromotionRewardResponse> {\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\nexport function contactsViral(params: {\n options: { moduleId: string };\n onEvent: (event: ContactsViralEvent) => void;\n onError: (error: unknown) => void;\n}): () => void {\n setTimeout(() => {\n const event: ContactsViralEvent = {\n type: 'close',\n data: { closeReason: 'noReward', sentRewardsCount: 0 },\n };\n params.onEvent(event);\n }, 500);\n return () => {};\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport type { IapProductListItem, IapSubscriptionInfoResult } from '@apps-in-toss/web-framework';\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: IapProductListItem[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })) as IapProductListItem[],\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: {\n params: { orderId: string };\n }): Promise<{ subscription: IapSubscriptionInfoResult }> {\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 * 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\nconst _requestNotificationAgreementImpl = (\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};\nexport const requestNotificationAgreement: ((\n params: RequestNotificationAgreementOptions,\n) => () => void) & { isSupported: () => boolean } = Object.assign(\n _requestNotificationAgreementImpl,\n { isSupported: () => true },\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","/**\n * @ait-co/devtools/mock\n *\n * @apps-in-toss/web-framework의 모든 export를 mock으로 대체한다.\n * 번들러 alias로 원본 대신 이 모듈이 resolve된다.\n */\n\nimport { installBridges } from './safe-area-bridge.js';\n\n// env-2 postMessage bridges (#484 safe-area-insets, #510 navigate-back): wire\n// both receiver halves at import time so any consumer that aliases the SDK to\n// this mock gets real device geometry and back-navigation bridged automatically.\n// No-op outside a browser.\ninstallBridges();\n\n// --- 광고 ---\nexport { GoogleAdMob, loadFullScreenAd, showFullScreenAd, TossAds } from './ads/index.js';\n// --- 분석 ---\nexport { Analytics, eventLog } from './analytics/index.js';\n// --- 인증/로그인 ---\nexport {\n appLogin,\n appsInTossSignTossCert,\n getAnonymousKey,\n getIsTossLoginIntegratedService,\n getUserKeyForGame,\n} from './auth/index.js';\n// --- 디바이스 기능 ---\nexport {\n Accuracy,\n fetchAlbumItems,\n fetchAlbumPhotos,\n fetchContacts,\n generateHapticFeedback,\n getClipboardText,\n getCurrentLocation,\n getDefaultPlaceholderImages,\n openCamera,\n openPDFViewer,\n Storage,\n saveBase64Data,\n setClipboardText,\n startUpdateLocation,\n} from './device/index.js';\n// --- 게임/프로모션 ---\nexport {\n contactsViral,\n getGameCenterGameProfile,\n grantPromotionReward,\n grantPromotionRewardForGame,\n openGameCenterLeaderboard,\n submitGameCenterLeaderBoardScore,\n} from './game/index.js';\n// --- IAP / 결제 ---\nexport { checkoutPayment, IAP, requestTossPayPaysBilling } from './iap/index.js';\n// --- 화면/네비게이션/환경정보/이벤트 ---\nexport {\n appsInTossEvent,\n closeView,\n env,\n getAppsInTossGlobals,\n getDeviceId,\n getGroupId,\n getLocale,\n getNetworkStatus,\n getOperationalEnvironment,\n getPlatformOS,\n getSafeAreaInsets,\n getSchemeUri,\n getServerTime,\n getTossAppVersion,\n getTossShareLink,\n graniteEvent,\n isMinVersionSupported,\n onVisibilityChangedByTransparentServiceWeb,\n openURL,\n requestReview,\n SafeAreaInsets,\n setDeviceOrientation,\n setIosSwipeGestureEnabled,\n setScreenAwakeMode,\n setSecureScreen,\n share,\n tdsEvent,\n} from './navigation/index.js';\n// --- 알림 ---\nexport { requestNotificationAgreement } from './notification.js';\n// --- 파트너 ---\nexport { partner } from './partner/index.js';\n// --- 권한 (bridge-core 호환) ---\nexport {\n FetchAlbumPhotosPermissionError,\n FetchContactsPermissionError,\n GetClipboardTextPermissionError,\n GetCurrentLocationPermissionError,\n getPermission,\n OpenCameraPermissionError,\n openPermissionDialog,\n PermissionError,\n requestPermission,\n SetClipboardTextPermissionError,\n StartUpdateLocationPermissionError,\n} from './permissions.js';\nexport {\n deleteUserPreset,\n listUserPresets,\n saveUserPreset,\n} from './preset-store.js';\n// --- Mock state preset library ---\nexport {\n applyPreset,\n builtInPresets,\n captureCurrentState,\n type MockPreset,\n type MockPresetState,\n matchesPreset,\n} from './presets.js';\nexport type { WebViewTypeValue } from './safe-area-bridge.js';\n// --- env-2 postMessage bridges (#484 safe-area-insets, #510 navigate-back) ---\nexport {\n applyForwardedSafeAreaInsets,\n installBridges,\n installNavigateBackBridge,\n installSafeAreaInsetsBridge,\n isNavigateBackMessage,\n NAVIGATE_BACK_MESSAGE_TYPE,\n parseSafeAreaInsetsMessage,\n parseWebViewTypeMessage,\n SAFE_AREA_INSETS_MESSAGE_TYPE,\n WEB_VIEW_TYPE_MESSAGE_TYPE,\n} from './safe-area-bridge.js';\nexport type { AitDevtoolsState } from './state.js';\n// --- 상태 관리 (내부 + 외부 접근용) ---\nexport { aitState } from './state.js';\n// --- @apps-in-toss/types re-export 호환 ---\nexport type {\n AnalyticsLogEntry,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n Primitive,\n SafeAreaInsets as SafeAreaInsetsType,\n} from './types.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/mock/state.ts","../../src/mock/device/_helpers.ts","../../src/mock/native-error.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/proxy.ts","../../src/mock/device/storage.ts","../../src/mock/observe.ts","../../src/mock/navigation/index.ts","../../src/mock/safe-area-bridge.ts","../../src/mock/ads/index.ts","../../src/mock/analytics/index.ts","../../src/mock/auth/index.ts","../../src/mock/game/index.ts","../../src/mock/iap/index.ts","../../src/mock/notification.ts","../../src/mock/partner/index.ts","../../src/mock/preset-store.ts","../../src/mock/presets.ts","../../src/mock/index.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nimport type { AitSdkCall } from '../mcp/ait-source.js';\nimport type { NativeErrorCode } from './native-error.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 { NativeErrorCode, NativeErrorEnvelope } from './native-error.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 // 실패-모드 다이얼 (devtools#770) — env1이 env3의 프로비저닝-의존 reject를\n // 재현하도록 per-API 실패 코드를 프로그래매틱하게 설정한다. 패널 UI 노출은\n // 범위 밖(1차는 aitState.patch 표면만). 값이 없는 API는 기존처럼 낙관적으로\n // resolve — 다이얼 미사용 시 zero behavior change.\n failureModes: FailureModes;\n}\n\n/**\n * per-API 실패 코드 다이얼. 값이 설정된 API만 mock이 reject하고, 나머지는\n * 기존 낙관적 resolve를 유지한다. `sdkLine`은 envelope shape 분기 축 —\n * `'2.x'`(기본)는 native envelope(`{name,code,userInfo,moduleName,__isError}`),\n * `'3.x'`는 맨 Error로 평탄화(sdk-example#284 매트릭스 \"패턴 ① envelope 평탄화\").\n */\nexport interface FailureModes {\n /** 네이티브 실패 envelope의 라인 축. 기본 '2.x'. */\n sdkLine: '2.x' | '3.x';\n /** appLogin 실패 코드 (예: 'APP_LOGIN'). 미설정 시 기존처럼 항상 resolve. */\n appLogin?: NativeErrorCode;\n /** GoogleAdMob.loadAppsInTossAdMob 실패 코드 (예: 'PLACEMENT_ID_FETCH_FAILED'). */\n loadAdMob?: NativeErrorCode;\n /** loadFullScreenAd 실패 코드 (예: 'EXECUTION_ERROR'). */\n loadFullScreenAd?: NativeErrorCode;\n /**\n * getIsTossLoginIntegratedService 실패 코드 (devtools#783 실측: env3 run11,\n * 2.x/iOS `A1-awaited-is-boolean` 시나리오 — rejected/`Error`/`EXECUTION_ERROR`).\n */\n getIsTossLoginIntegratedService?: NativeErrorCode;\n /**\n * requestNotificationAgreement 실패 코드 (devtools#783 실측: env3 run11,\n * 2.x/iOS `happy-force-*`/`A1-empty-templateCode` 시나리오 전부 —\n * rejected/`Error`/`4000`).\n */\n requestNotificationAgreement?: NativeErrorCode;\n /**\n * 권한 이름별 실패 코드 (devtools#783 실측: env3 run11, 2.x/iOS\n * `permissions.ait.test.ts` PERMISSION_NAMES 순회 — `geolocation`/`camera`/\n * `microphone`만 rejected/`Error`/`NO_PERMISSION`, `clipboard`/`contacts`/\n * `photos`는 resolved. 31146의 `granite.config.ts`가 `permissions: []`라\n * 선언 안 된 권한만 거부되는 그림과 정합 — 전역 on/off가 아니라 이름 단위 맵.\n * 설정된 이름만 reject, 나머지는 기존대로 resolve.\n */\n getPermission?: Partial<Record<PermissionName, NativeErrorCode>>;\n\n /**\n * soft-resolve 다이얼 (#789) — reject가 아니라 \"다른 shape로 resolve\"하는 env3\n * 프로비저닝-의존 실패를 재현한다. 켠 API만 실측 대체 shape로 resolve하고, 미설정\n * API는 선언 타입대로 성공 shape를 유지한다(다이얼 미사용 시 zero behavior change).\n * shape는 API별 고정(env3 run11 2.x/iOS 실측 — valueKeys만 실측, 문자열 내용은 예시):\n * grantPromotionReward/grantPromotionRewardForGame → { errorCode, message }\n * getSubscriptionInfo → {}\n * (checkoutPayment/requestTossPayPaysBilling은 valueKeys=['false','reason']로 실측됐으나\n * 값 수준 재확인이 폰-gated라 여기 아직 배선 안 함 — #303/#789.)\n */\n softResolve?: {\n grantPromotionReward?: boolean;\n grantPromotionRewardForGame?: boolean;\n getSubscriptionInfo?: boolean;\n };\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 // 다이얼 전부 미설정 = 기존 낙관적 resolve 그대로 (zero behavior change).\n failureModes: {\n sdkLine: '2.x',\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 * 디바이스 모듈 내부 공유 헬퍼\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 * 실패-모드 다이얼 — 네이티브 에러 envelope 조립 (devtools#770).\n *\n * env1(mock)이 env3(실기기)의 프로비저닝-의존 reject 계약을 재현할 수 있게 하는\n * 공용 헬퍼. `aitState.failureModes`(state.ts)에 per-API 코드를 설정하면, 각\n * mock 도메인(auth/ads 등)이 이 모듈의 {@link buildNativeError}로 실기기 캡처와\n * 필드 단위로 일치하는 에러 객체를 만들어 reject/onError한다.\n *\n * 코드 인벤토리와 envelope shape의 정본은 sdk-example#284 2.x↔3.0 diff\n * 매트릭스(2026-07-10 iOS run10 캡처 + 이후 manual 패스)다:\n *\n * - 2.x 라인: 네이티브 브리지가 `{ name, code, userInfo, moduleName, __isError }`\n * envelope을 얹은 `Error` 인스턴스로 reject한다. `errorKeys` 관측이 이 5개\n * 필드로 고정.\n * - 3.x 라인: 같은 실패가 \"맨 Error\"(위 envelope 필드 없음)로 평탄화된다 —\n * sdk-example#284 \"패턴 ① 오류 envelope 평탄화\" 표.\n *\n * `src/test-runner/bridge-stub.ts`의 `NativeBridgeErrorShape`/`makeNativeError`가\n * 같은 shape을 이미 CI 러너 쪽에서 재현하고 있다(devtools#740) — 이 모듈은 그\n * shape을 mock 소비자 표면(`src/mock/**`)에 대해 재사용 가능한 형태로 옮긴 것이다.\n * 두 모듈은 레이어가 다르다: test-runner 쪽은 env3 blocking-call 인터셉터(빌드에\n * 안 실림, CI 전용), 이쪽은 `@ait-co/devtools` 소비자가 `pnpm dev`에서 실제로\n * import하는 mock 런타임이다 — 서로 import하지 않는다(devtools#740이 test-runner\n * 전용 모듈임을 명시).\n */\n\nimport { aitState } from './state.js';\n\n/** 실기기 2.x native bridge가 reject에 얹는 envelope 필드. */\nexport interface NativeErrorEnvelope {\n name: string;\n code: string;\n message: string;\n userInfo: Record<string, unknown>;\n moduleName: string;\n __isError: true;\n}\n\n/**\n * sdk-example#284 매트릭스에 등재된 2.x 네이티브 실패 코드 인벤토리.\n * 새 코드가 매트릭스에 추가되면 여기에도 추가한다.\n */\nexport type NativeErrorCode =\n | 'APP_LOGIN'\n | 'PLACEMENT_ID_FETCH_FAILED'\n | 'EXECUTION_ERROR'\n | 'NO_PERMISSION'\n | 'INVALID_REQUEST'\n | 'INVALID_DATA'\n | 'FAILED_TO_GET_LOADED_AD'\n | 'APP_BRIDGE_THROTTLED'\n | '1006'\n | '4000';\n\n/** 코드별 기본 message/moduleName — 실기기 캡처 관측값. */\nconst CODE_META: Record<NativeErrorCode, { message: string; moduleName: string }> = {\n APP_LOGIN: { message: 'Login failed', moduleName: 'RNTossLogin' },\n PLACEMENT_ID_FETCH_FAILED: { message: 'Failed to fetch placement id', moduleName: 'RNAdMob' },\n EXECUTION_ERROR: { message: 'Execution error', moduleName: 'RNFullScreenAd' },\n NO_PERMISSION: { message: 'No permission', moduleName: 'RNPermissions' },\n INVALID_REQUEST: { message: 'Invalid request', moduleName: 'RNPermissions' },\n INVALID_DATA: { message: 'Invalid data', moduleName: 'RNFileSystem' },\n FAILED_TO_GET_LOADED_AD: { message: 'Failed to get loaded ad', moduleName: 'RNAdMob' },\n APP_BRIDGE_THROTTLED: {\n message: 'Too many app bridge calls from this method.',\n moduleName: 'RNBridge',\n },\n '1006': { message: '광고가 로드 중이거나 준비되지 않았습니다', moduleName: 'RNFullScreenAd' },\n '4000': { message: 'Notification agreement failed', moduleName: 'RNNotification' },\n};\n\n/**\n * `aitState.failureModes.sdkLine`이 가리키는 라인으로 네이티브 실패를 조립한다.\n *\n * - `'2.x'`(기본): {@link NativeErrorEnvelope} 필드가 실린 `Error` — 실기기 2.x\n * 캡처(`{name, code, userInfo, moduleName, __isError}`)와 필드 단위 일치.\n * - `'3.x'`: 같은 실패가 \"맨 Error\"로 평탄화된 것을 재현 — envelope 필드 없이\n * message만 실린 순수 `Error` 인스턴스.\n *\n * 호출부는 두 종류다:\n *\n * - **다이얼 게이트 뒤**(대부분): 프로비저닝처럼 환경에 따라 갈리는 실패는\n * 다이얼이 설정된 경우에만 재현한다 — 미설정 시 기존 동작 무변화\n * (zero behavior change). 호출 전에 다이얼 값을 명시적으로 확인한다.\n * - **무조건**(입력 검증): 빈 `data`나 알 수 없는 haptic type처럼 환경과 무관하게\n * 실기기가 **항상** 거부하는 입력은 다이얼 없이 바로 던진다. 이건 \"가끔 일어나는\n * 실패\"의 시뮬레이션이 아니라 결정적 계약이므로 opt-in 대상이 아니다.\n */\nexport function buildNativeError(code: NativeErrorCode): Error {\n const meta = CODE_META[code];\n const sdkLine = aitState.state.failureModes.sdkLine;\n\n if (sdkLine === '3.x') {\n // 패턴 ①(sdk-example#284): 3.0 라인은 envelope 없이 맨 Error로 평탄화된다.\n return new Error(meta.message);\n }\n\n const err = new Error(meta.message) as Error & NativeErrorEnvelope;\n err.name = 'Error';\n err.code = code;\n err.userInfo = {};\n err.moduleName = meta.moduleName;\n err.__isError = true;\n return err;\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport type {\n PermissionAccess,\n PermissionName,\n PermissionStatus,\n} from '@apps-in-toss/web-framework';\nimport { buildNativeError, type NativeErrorCode } from './native-error.js';\nimport { aitState } from './state.js';\n\n// --- PermissionError 계층 (web-framework 3.0+ 신규) ---\n// checkPermission()이 권한 거부 시 per-API *PermissionError 서브클래스를 throw한다 (#372).\n\n/**\n * web-framework 3.0+ 권한 에러 기반 클래스.\n * `instanceof PermissionError`로 체크하는 코드와 호환된다.\n */\nexport class PermissionError extends Error {\n constructor({ methodName, message }: { methodName: string; message?: string }) {\n super(message ?? `${methodName}: permission denied`);\n this.name = `${methodName}PermissionError`;\n }\n}\n\n/** openCamera 권한 에러 */\nexport class OpenCameraPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'openCamera' });\n }\n}\n\n/** fetchAlbumPhotos 권한 에러 */\nexport class FetchAlbumPhotosPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'fetchAlbumPhotos' });\n }\n}\n\n/** fetchContacts 권한 에러 */\nexport class FetchContactsPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'fetchContacts' });\n }\n}\n\n/** getCurrentLocation 권한 에러 */\nexport class GetCurrentLocationPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'getCurrentLocation' });\n }\n}\n\n/** getClipboardText 권한 에러 */\nexport class GetClipboardTextPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'getClipboardText' });\n }\n}\n\n/** setClipboardText 권한 에러 */\nexport class SetClipboardTextPermissionError extends PermissionError {\n constructor() {\n super({ methodName: 'setClipboardText' });\n }\n}\n\n/**\n * startUpdateLocation 권한 에러.\n * web-framework 3.0에서 GetCurrentLocationPermissionError의 alias.\n */\nexport const StartUpdateLocationPermissionError = GetCurrentLocationPermissionError;\n\n// --- API 이름 → PermissionError 매핑 (web-framework 3.0 표면 기준) ---\n// 실 SDK가 각 API 거부 시 throw하는 클래스와 1:1 대응. SDK에 없는 API(fetchAlbumItems)는\n// 동일한 'photos' 권한을 공유하는 FetchAlbumPhotosPermissionError를 사용한다.\n\nconst permissionErrorMap: Record<string, new () => PermissionError> = {\n openCamera: OpenCameraPermissionError,\n fetchAlbumPhotos: FetchAlbumPhotosPermissionError,\n // fetchAlbumItems는 SDK에 별도 PermissionError 없음 — photos 권한을 공유하므로 동일 클래스.\n fetchAlbumItems: FetchAlbumPhotosPermissionError,\n fetchContacts: FetchContactsPermissionError,\n getCurrentLocation: GetCurrentLocationPermissionError,\n getClipboardText: GetClipboardTextPermissionError,\n setClipboardText: SetClipboardTextPermissionError,\n};\n\n// SDK 시그니처: getPermission(permission: { name: PermissionName; access: PermissionAccess }): Promise<PermissionStatus>\nexport async function getPermission(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<PermissionStatus> {\n // 실패-모드 다이얼 (devtools#783): aitState.patch('failureModes',\n // { getPermission: { geolocation: 'NO_PERMISSION' } })로 실기기 실측(env3 run11,\n // 2.x/iOS — geolocation/camera/microphone만 rejected/`Error`/`NO_PERMISSION`,\n // clipboard/contacts/photos는 resolved)을 권한 이름별로 재현한다. 전역 on/off가\n // 아니라 이름 단위 맵 — 다이얼이 걸린 이름만 reject하고 나머지는 기존대로\n // resolve (zero behavior change). withPermission()이 부착하는 `.getPermission()`\n // 도 이 함수를 그대로 호출하므로 배선 지점은 여기 하나뿐이다.\n //\n // `access` 축 (devtools#783 잔여 해소): 다이얼은 `read`/`write`에만 건다.\n // 실측(env3 run11, `happy-each-access` — geolocation 고정 + access 3종 순회):\n // { geolocation, read } → rejected NO_PERMISSION\n // { geolocation, write } → rejected NO_PERMISSION\n // { geolocation, access } → resolved\n // `PermissionAccess`는 `'read' | 'write' | 'access'`이고, `'access'`만 통과하는\n // 그림은 \"권한 *상태 조회*는 선언 여부와 무관하게 허용, 실제 capability 요구\n // (`read`/`write`)만 선언 게이트를 탄다\"로 읽힌다. 그래서 이름 단위 맵을\n // access 축과 곱하지 않고, `'access'`일 때 다이얼 자체를 건너뛴다 —\n // 이름×access 2차원 맵으로 확장할 근거(이름별로 access 프로파일이 다르다는\n // 관측)는 아직 없고, 근거 없는 확장은 #783에서 이름 단위 맵을 택한 원칙에\n // 어긋난다.\n const failureCode = permissionGateCode(permission);\n if (failureCode) {\n throw buildNativeError(failureCode);\n }\n\n return aitState.state.permissions[permission.name];\n}\n\n/**\n * 선언 게이트에 걸리는 이름·access 조합이면 다이얼에 등록된 native errorCode를\n * 돌려준다. 세 권한 API(`getPermission`/`requestPermission`/`openPermissionDialog`)가\n * **걸리는 조건**은 공유하지만 **떨어지는 코드**는 공유하지 않는다 — 아래 참조.\n */\nfunction permissionGateCode(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): NativeErrorCode | undefined {\n if (permission.access === 'access') {\n return undefined;\n }\n return aitState.state.failureModes.getPermission?.[permission.name];\n}\n\n/**\n * `openPermissionDialog`가 선언 게이트에 걸렸을 때의 코드.\n *\n * 형제 API와 갈린다 — env3 run11 실측(2.x/iOS, sdk-example#313에서 시나리오 키가\n * 통일되며 비교 대상에 들어온 값):\n *\n * getPermission { geolocation, read } → rejected NO_PERMISSION\n * requestPermission { geolocation, read } → rejected NO_PERMISSION\n * openPermissionDialog { geolocation, read } → rejected INVALID_REQUEST\n * openPermissionDialog { camera, access } → resolved\n *\n * 즉 **걸리는 조건은 셋이 같고(access 축 모델 그대로) 코드만 갈린다.** 처음엔\n * `requestPermission`이 `openPermissionDialog`에 위임하니 게이트도 위임하면\n * 된다고 보고 한 지점에만 배선했는데, 그 모델은 코드 층위에서 실측과 어긋났다\n * (env1 NO_PERMISSION ↔ env3 INVALID_REQUEST). 그래서 `requestPermission`은\n * 위임 **전에** 자기 게이트를 먼저 타고, 다이얼로그를 여는 호출만 이 코드를 쓴다.\n *\n * 관측이 geolocation/read 한 조합뿐이라 이름·access별 분기는 두지 않는다 —\n * 근거 없는 확장은 #783에서 이름 단위 맵을 택한 원칙에 어긋난다.\n */\nconst OPEN_DIALOG_GATE_CODE: NativeErrorCode = 'INVALID_REQUEST';\n\n// SDK 시그니처: openPermissionDialog(permission: { name: PermissionName; access: PermissionAccess }): Promise<Exclude<PermissionStatus, \"notDetermined\">>\nexport async function openPermissionDialog(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<'allowed' | 'denied'> {\n // 선언 게이트는 다이얼로그를 열기 **전에** 탄다 — 미선언 권한은 실기기에서\n // 프롬프트 자체가 뜨지 않고 native 오류로 떨어진다. 코드는 형제 API와 갈린다\n // (`OPEN_DIALOG_GATE_CODE` 참조).\n if (permissionGateCode(permission)) {\n throw buildNativeError(OPEN_DIALOG_GATE_CODE);\n }\n\n const current = aitState.state.permissions[permission.name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [permission.name]: 'allowed' });\n return 'allowed';\n}\n\n// SDK 시그니처: requestPermission(permission: { name: PermissionName; access: PermissionAccess }): Promise<Exclude<PermissionStatus, \"notDetermined\">>\nexport async function requestPermission(permission: {\n name: PermissionName;\n access: PermissionAccess;\n}): Promise<'allowed' | 'denied'> {\n // 게이트를 위임 **전에** 직접 탄다 — 실기기에서 이 API는 다이얼로그를 여는\n // 쪽(INVALID_REQUEST)이 아니라 `getPermission`과 같은 코드(NO_PERMISSION)로\n // 떨어진다(`OPEN_DIALOG_GATE_CODE` 참조). 게이트를 통과한 뒤의 상태 전이만\n // `openPermissionDialog`에 위임한다.\n const failureCode = permissionGateCode(permission);\n if (failureCode) {\n throw buildNativeError(failureCode);\n }\n\n return openPermissionDialog(permission);\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({ name: permissionName, access: 'access' });\n enhanced.openPermissionDialog = () =>\n openPermissionDialog({ name: permissionName, access: 'access' });\n return enhanced;\n}\n\n/**\n * 권한 체크 후 denied면 per-API *PermissionError 서브클래스를 throw한다.\n * 실 3.0 SDK 동작과 일치 — `instanceof PermissionError` 분기가 mock에서도 동작한다 (#372).\n */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n const ErrorClass = permissionErrorMap[fnName];\n if (ErrorClass) {\n throw new ErrorClass();\n }\n // 매핑에 없는 fnName은 기반 클래스로 fallback (SDK 표면 밖의 경로 보호)\n throw new PermissionError({ methodName: fnName });\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};\nconst _fetchAlbumItemsWithPermission = withPermission(_fetchAlbumItems, 'photos');\nexport const fetchAlbumItems: typeof _fetchAlbumItemsWithPermission & {\n isSupported: () => boolean;\n} = Object.assign(_fetchAlbumItemsWithPermission, { isSupported: () => true });\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\n// 실기기(2.x×iOS) capture는 setClipboardText가 `undefined`가 아니라\n// `{ text: <설정한 문자열> }` 객체로 resolve됨을 보였다(devtools#770,\n// returnType: \"object\", valueKeys: [\"text\"]). 원본 SDK 타입 선언은 여전히\n// `Promise<void>`이므로 시그니처는 그대로 두고, 런타임 반환값만 실측과 동치시킨다.\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 // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측({text}) 캐스트\n return { text } as unknown as void;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측({text}) 캐스트\n return { text } as unknown as void;\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 * - @ait-co/polyfill 동시 사용 시 재귀 방지: polyfill이 navigator.vibrate를 override하고\n * 내부에서 mock의 generateHapticFeedback을 호출하므로 무한 재귀가 발생한다. polyfill이\n * 원본 vibrate를 BACKUP_KEY(Symbol.for('@ait-co/polyfill/vibrate.original'))에 저장하면\n * 그 원본을 직접 호출해 재귀를 끊는다.\n * - sdkCallLog에 🟡(partial)로 기록. params: { hapticType, vibrated: boolean }.\n * - 시그니처 불변 — __typecheck.ts의 Assert<Mock, Original> 통과.\n */\n\nimport { buildNativeError } from '../native-error.js';\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\n/**\n * navigator.vibrate를 안전하게 호출한다.\n *\n * @ait-co/polyfill/auto가 설치된 환경에서는 navigator.vibrate가 polyfill shim으로\n * override되어 있고, 그 shim은 내부적으로 mock의 generateHapticFeedback을 호출한다.\n * mock이 다시 navigator.vibrate(현재 = shim)를 호출하면 무한 재귀가 발생한다.\n * polyfill은 원본 vibrate를 BACKUP_KEY에 저장하므로 그쪽을 직접 호출한다.\n */\nconst POLYFILL_VIBRATE_BACKUP = Symbol.for('@ait-co/polyfill/vibrate.original');\n\nfunction callVibrate(pattern: VibratePattern): boolean {\n if (typeof navigator === 'undefined') return false;\n // polyfill이 설치되어 있으면 원본 vibrate를 직접 호출해 재귀를 방지한다.\n const nav = navigator as Navigator & Record<symbol, ((p: VibratePattern) => boolean) | undefined>;\n const original = POLYFILL_VIBRATE_BACKUP in nav ? nav[POLYFILL_VIBRATE_BACKUP] : null;\n if (typeof original === 'function') return original(pattern);\n // polyfill 없음 — 그냥 navigator.vibrate 호출.\n return typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;\n}\n\nexport async function generateHapticFeedback(options: { type: HapticFeedbackType }): Promise<void> {\n // 실기기(env3)는 알 수 없는 haptic type을 reject(errorCode: EXECUTION_ERROR)한다 —\n // mock은 과거 알 수 없는 type도 30ms fallback 패턴으로 조용히 resolve했다\n // (devtools#780, env1↔env3 capture diff 실측). 유효 판정은 SDK 타입 선언\n // (HapticFeedbackType, 10종 union)을 그대로 쓴다 — 이 mock의 HAPTIC_VIBRATE_PATTERN\n // 키 집합이 그 union과 정확히 일치하므로 별도 허용 목록을 새로 만들지 않는다.\n // `in`이 아니라 `Object.hasOwn`인 이유: `in`은 프로토타입 체인까지 보므로\n // `'constructor'`·`'toString'`·`'__proto__'` 같은 예약 이름이 유효 type으로\n // 통과해버린다 — 이 함수가 막으려는 바로 그 부류(실기기가 거부할 입력을 mock이\n // 조용히 수락)가 그 집합에서 재발한다. 타입 시그니처가 막아줄 것 같지만\n // MCP `call_sdk`는 타입 없는 인자를 그대로 실어 보내므로 런타임에 도달한다.\n // 이 throw는 결정적 입력-계약 위반이라 다이얼 뒤에 두지 않는다 — 던지는 error의\n // *shape*만 buildNativeError로 실기기 2.x envelope(name/code/userInfo/moduleName/\n // __isError)과 맞춘다(devtools#788, 손수 만든 `{errorCode}`가 env1↔env3\n // errorKeys 발산의 원인이었다).\n if (!Object.hasOwn(HAPTIC_VIBRATE_PATTERN, options.type)) {\n throw buildNativeError('EXECUTION_ERROR');\n }\n\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 = callVibrate(pattern);\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 // 빈 `data`는 native가 거부한다 — 실측(env3 run11, 2.x/iOS):\n // { data: '', fileName: '', mimeType: '' }\n // → rejected / Error / INVALID_DATA / moduleName 'RNFileSystem'\n // mock은 anchor를 만들어 click하기만 해서 무조건 resolve했고, 그 발산은\n // 시나리오 이름이 자동/manual 슈트에서 갈려 있어 커버리지 갭 뒤에 가려져\n // 있었다(sdk-example#313에서 키를 통일하며 드러남).\n //\n // 게이트를 `data`에만 거는 건 관측이 거기까지만 뒷받침하기 때문이다 —\n // 코드가 `INVALID_DATA`이고, 빈 fileName/mimeType 단독으로 무엇이 나오는지는\n // 관측이 없다. 근거 없이 조건을 넓히면 env1이 실기기에 없는 실패를 만든다.\n if (params.data === '') {\n throw buildNativeError('INVALID_DATA');\n }\n\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\n// 실기기(2.x×iOS) capture는 getCurrentLocation이 { coords, timestamp }만 반환하고\n// accessLocation은 포함하지 않음을 보였다(devtools#770). aitState.state.location에는\n// accessLocation을 계속 유지하되(상태 모델은 그대로), 이 함수의 반환값에서만 제외한다.\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n const { coords, timestamp } = buildLocation();\n return { coords, timestamp };\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 });\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 // 패널의 prompt 응답은 startUpdateLocation과 채널을 공유하므로 accessLocation을\n // 실어 온다. getCurrentLocation의 반환 shape는 모드와 무관해야 하므로(실기기엔\n // mock/web/prompt 구분이 없다) 여기서 걷어낸다 — mock 모드와 같은 층의 처리다.\n const { coords, timestamp } = await waitForPromptResponse<MockLocation>('location');\n return { coords, timestamp } as MockLocation;\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 */\nconst _openPDFViewerImpl = async (_params: OpenPDFViewerParams): Promise<OpenPDFViewerResult> => {\n // 실 SDK와 동일하게 비동기로 resolve한다.\n await Promise.resolve();\n return 'CLOSE';\n};\nexport const openPDFViewer: ((params: OpenPDFViewerParams) => Promise<OpenPDFViewerResult>) & {\n isSupported: () => boolean;\n} = Object.assign(_openPDFViewerImpl, { isSupported: () => true });\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 * Storage mock\n * localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리\n *\n * 실기기(2.x×iOS) capture는 `setItem`/`removeItem`/`clearItems` 세 메서드가\n * `undefined`가 아니라 `null`로 resolve됨을 보였다(devtools#770). 원본 SDK 타입\n * 선언은 여전히 `Promise<void>`이므로 시그니처는 그대로 두고, 런타임 반환값만\n * `null`로 캐스트해 실측과 동치시킨다 — Analytics·setClipboardText와 같은 처리(#775).\n * `getItem`은 이미 `string | null`이라 실측과 일치하므로 손대지 않는다.\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 // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return null as unknown as void;\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return null as unknown as void;\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 // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return null as unknown as void;\n },\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 * 화면/네비게이션/이벤트 mock\n */\n\nimport type { GraniteEvent, TdsEvent } from '@apps-in-toss/web-framework';\nimport { getNetworkStatusByMode } from '../device/index.js';\nimport { buildNativeError } from '../native-error.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\n// RFC 3986 scheme 문법(ALPHA *(ALPHA / DIGIT / \"+\" / \"-\" / \".\") \":\")만 확인한다 — 특정\n// scheme 이름(intoss 등)을 하드코딩하지 않아 애매한 케이스는 통과시킨다(보수적 규칙,\n// devtools#780). \"intoss://my-app\" 같은 mini-app 딥링크는 통과, \"/some/path\" 같은\n// scheme 없는 bare path는 거부.\nconst URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;\n\nexport async function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n // 실기기(env3)는 scheme 없는 bare path를 reject(code: EXECUTION_ERROR)한다 —\n // mock은 과거 어떤 문자열이든 조용히 resolve했다(devtools#780, env1↔env3 capture\n // diff 실측). string.resolve(path)로 만든 \"mock 링크\"가 유효한 mini-app 딥링크가\n // 아닌데도 성공한 것처럼 보이면 개발자가 dev에서 통과한 코드를 실기기에서 깨뜨린다.\n // 이 throw는 결정적 입력-계약 위반이라 다이얼 뒤에 두지 않는다 — 던지는 error의\n // *shape*만 buildNativeError로 실기기 2.x envelope(name/code/userInfo/moduleName/\n // __isError)과 맞춘다(devtools#788, 손수 만든 `{errorCode}`가 env1↔env3\n // errorKeys 발산의 원인이었다).\n if (!URI_SCHEME_PATTERN.test(path)) {\n throw buildNativeError('EXECUTION_ERROR');\n }\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\nconst _getServerTimeImpl = async (): Promise<number | undefined> => {\n return Date.now();\n};\nexport const getServerTime: (() => Promise<number | undefined>) & { isSupported: () => boolean } =\n Object.assign(_getServerTimeImpl, { isSupported: () => true });\n\n// --- 이벤트 시스템 ---\n\n/**\n * 현재 backEvent 구독자 수. graniteEvent.addEventListener('backEvent', …)가\n * 증가시키고, 반환된 cleanup이 감소시킨다. 호스트 back 메시지 처리 시 인터셉트\n * 여부를 판단하는 데 쓰인다.\n *\n * @internal 테스트 및 safe-area-bridge에서만 사용.\n */\nlet _backEventSubscriberCount = 0;\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEvent>(\n event: K,\n {\n onEvent,\n onError,\n }: {\n onEvent: GraniteEvent[K]['onEvent'];\n onError?: GraniteEvent[K]['onError'];\n options?: GraniteEvent[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\n // backEvent 구독자 카운터 관리\n if (event === 'backEvent') {\n _backEventSubscriberCount++;\n }\n\n let cleaned = false;\n return () => {\n if (cleaned) return; // 이중 호출 방지\n cleaned = true;\n window.removeEventListener(`__ait:${event}`, handler);\n if (event === 'backEvent') {\n _backEventSubscriberCount--;\n }\n };\n },\n};\n\n/**\n * 호스트 back 내비게이션을 처리한다.\n *\n * backEvent 구독자가 1명 이상이면 `window.dispatchEvent(new CustomEvent('__ait:backEvent'))`만\n * 발사한다 — 미니앱이 back을 가로채는(intercept) 채널이고 실제 토스 호스트와 동일한 시맨틱.\n * 구독자가 없으면 `history.back()`을 호출해 기본 브라우저 뒤로가기를 수행한다.\n *\n * env 1 패널의 back 버튼(`src/panel/viewport.ts` `aitState.trigger('backEvent')`)과\n * 동일한 경로를 거쳐 back 시맨틱의 단일 소유처를 navigation 모듈에 유지한다.\n */\nexport function dispatchHostBackNavigation(): void {\n if (_backEventSubscriberCount > 0) {\n window.dispatchEvent(new CustomEvent('__ait:backEvent'));\n } else {\n history.back();\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\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEvent>(\n event: K,\n {\n onEvent,\n }: {\n onEvent: TdsEvent[K]['onEvent'];\n onError?: TdsEvent[K]['onError'];\n options?: TdsEvent[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\n/**\n * @deprecated web-framework 3.0 에서 제거됨. 2.x 소비자 back-compat용으로 유지.\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/**\n * @deprecated `SafeAreaInsets.get()`을 쓸 것.\n *\n * 상류 SDK의 타입 선언은 `getSafeAreaInsets(): number`지만, 실기기(2.x×iOS)\n * capture는 이 함수가 숫자가 아니라 `SafeAreaInsets.get()`과 같은 객체\n * (`{ top, right, bottom, left }`)를 반환함을 보였다(devtools#770 —\n * `returnType: \"object\"`, `valueKeys: [\"top\",\"right\",\"bottom\",\"left\"]`).\n * 즉 선언과 런타임이 어긋나 있는 상류 타입 버그다.\n *\n * mock은 타입 선언이 아니라 **런타임 실측**을 재현해야 개발자가 env1에서 겪는\n * 동작이 실기기와 같아진다. 그래서 시그니처는 상류와 동일하게 `number`로 두되\n * (`__typecheck.ts`가 SDK 타입에 대해 계속 컴파일되도록) 반환값만 실측 객체로\n * 캐스트한다 — Analytics·setClipboardText·Storage와 같은 처리(#775).\n */\nexport function getSafeAreaInsets(): number {\n return { ...aitState.state.safeAreaInsets } as unknown as number;\n}\n","/**\n * env-2 postMessage bridges (#484, #510).\n *\n * In the AITC Sandbox PWA (env 2) the dev app runs inside the launcher's\n * full-viewport `<iframe>`. The launcher is the top-level document, so its\n * `env(safe-area-inset-*)` measurement is the ground truth for the real device\n * geometry. The framed page's mock would otherwise report a synthetic preset\n * value (e.g. top=54), which sdk-example then double-pads on top of a viewport\n * that already starts below the status bar — the env-2 \"dead band\" defect.\n *\n * This module installs receive-half listeners for two message types:\n *\n * 1. `ait:safe-area-insets` (#484): the launcher forwards its real env() insets\n * to the framed page on iframe load and resize/orientationchange. Validates the\n * envelope and writes real insets into the mock SafeAreaInsets state, firing the\n * subscribe path (see navigation/index.ts) so apps that subscribe re-read the\n * corrected values.\n *\n * 2. `ait:navigate-back` (#510): the launcher partner bar's `←` button posts this\n * command to the framed page. The receive half calls `dispatchHostBackNavigation()`\n * (navigation/index.ts): if backEvent subscribers are present, a `__ait:backEvent`\n * CustomEvent is dispatched (the mini-app intercept channel, matching the env-1\n * panel path); otherwise `history.back()` is called. No data other than `type` is\n * read from or written to the message — shape validation rejects anything that\n * carries extra fields with the wrong type. Apps that do not install this mock\n * (older builds) silently ignore the message (natural no-op).\n *\n * Origin policy: neither message type carries sensitive data, so we do NOT\n * restrict by origin — the launcher posts cross-origin from a *.trycloudflare.com\n * tunnel with targetOrigin '*'. Shape validation is still mandatory: a malformed\n * or out-of-range message is silently ignored so a stray postMessage can never\n * corrupt the mock state or trigger spurious navigation.\n *\n * Message-driven by design: env 1 (desktop browser, no launcher) never receives\n * these messages, so the panel preset stays authoritative there with zero special\n * casing here.\n */\n\nimport { dispatchHostBackNavigation } from './navigation/index.js';\nimport { aitState } from './state.js';\nimport type { SafeAreaInsets } from './types.js';\n\n/** The postMessage envelope the launcher posts to the framed dev app (inset forward). */\nexport const SAFE_AREA_INSETS_MESSAGE_TYPE = 'ait:safe-area-insets' as const;\n\n/**\n * The postMessage command the launcher partner bar's `←` button sends to the\n * framed dev app (#510). The framed page calls `history.back()` in response.\n *\n * Protocol: only `{ type: 'ait:navigate-back' }` is valid. No other fields are\n * read or acted on — extra fields are silently ignored by the shape guard.\n * Game variant never sends this message (back button is partner-bar-only).\n */\nexport const NAVIGATE_BACK_MESSAGE_TYPE = 'ait:navigate-back' as const;\n\n/**\n * The postMessage envelope the framed mini-app self-reports its webViewType\n * with (#580). The mini-app knows its own type from the build constant\n * `__WEB_VIEW_TYPE__` (`granite.config.ts`'s `webViewProps.type`, injected by\n * the devtools unplugin). The launcher is cross-origin so it cannot read that\n * constant directly — the mini-app posts it to `window.parent` once so the\n * launcher (env-2 PWA shell) switches to game mode automatically, with no\n * manual `?navBarType=game` URL edit.\n *\n * Direction: this is the SEND side's contract (posted from inside the iframe by\n * `src/in-app/attach.ts`). The launcher's receive half lives in\n * `e2e/fixture/launcher/Launcher.tsx` and mirrors the same value enum inline,\n * staying decoupled from the mock package internals — the same pattern the\n * other launcher message types follow.\n *\n * Value enum: only `'partner'` and `'game'` are valid. The SDK's deprecated\n * `'external'` alias of `partner` (web-framework 2.6.1) is mapped to `'partner'`\n * at the send site so the wire only ever carries the two shapes the launcher\n * emulates.\n */\nexport const WEB_VIEW_TYPE_MESSAGE_TYPE = 'ait:web-view-type' as const;\n\n/** The two webViewType shapes the launcher can emulate (#580). */\nexport type WebViewTypeValue = 'partner' | 'game';\n\n// Insets are CSS px; a real device tops out well under this. The bound rejects\n// nonsense (NaN/Infinity/negative/absurd) without being so tight it clips a\n// future large-notch device.\nconst MAX_INSET_PX = 200;\n\nfunction isValidInset(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= MAX_INSET_PX;\n}\n\n/**\n * Parse + validate a raw postMessage payload into a `SafeAreaInsets`, or return\n * null when it is not a well-formed `ait:safe-area-insets` message. Pure — unit\n * tested without a real MessageEvent.\n */\nexport function parseSafeAreaInsetsMessage(data: unknown): SafeAreaInsets | null {\n if (typeof data !== 'object' || data === null) return null;\n if ((data as { type?: unknown }).type !== SAFE_AREA_INSETS_MESSAGE_TYPE) return null;\n\n const insets = (data as { insets?: unknown }).insets;\n if (typeof insets !== 'object' || insets === null) return null;\n\n const { top, bottom, left, right } = insets as Record<string, unknown>;\n if (!isValidInset(top) || !isValidInset(bottom) || !isValidInset(left) || !isValidInset(right)) {\n return null;\n }\n return { top, bottom, left, right };\n}\n\n/**\n * Parse + validate a raw postMessage payload into a webViewType value\n * (`'partner'` | `'game'`), or return `null` when it is not a well-formed\n * `ait:web-view-type` message (#580). Pure — unit tested without a real\n * MessageEvent.\n *\n * Strict shape guard (the safety boundary for the cross-origin receive path):\n * the payload must be a non-null object whose `type` is exactly\n * {@link WEB_VIEW_TYPE_MESSAGE_TYPE} and whose `value` is exactly `'partner'`\n * or `'game'` (an enum allow-list). Anything else — a foreign type, a missing\n * or non-string value, the deprecated `'external'` alias, or any other string —\n * returns `null` so a stray postMessage can never flip the launcher's visual\n * mode. The send site is responsible for collapsing `'external'` → `'partner'`\n * before posting; the parser does NOT silently accept it.\n */\nexport function parseWebViewTypeMessage(data: unknown): WebViewTypeValue | null {\n if (typeof data !== 'object' || data === null) return null;\n if ((data as { type?: unknown }).type !== WEB_VIEW_TYPE_MESSAGE_TYPE) return null;\n const value = (data as { value?: unknown }).value;\n if (value === 'partner' || value === 'game') return value;\n return null;\n}\n\n/**\n * Apply forwarded insets to the mock state. Skips the write (and the resulting\n * subscribe notify) when nothing changed, so repeated identical messages from a\n * resize storm don't churn subscribers.\n */\nexport function applyForwardedSafeAreaInsets(insets: SafeAreaInsets): void {\n const current = aitState.state.safeAreaInsets;\n if (\n current.top === insets.top &&\n current.bottom === insets.bottom &&\n current.left === insets.left &&\n current.right === insets.right\n ) {\n return;\n }\n aitState.update({ safeAreaInsets: insets });\n}\n\n/**\n * The `id` attribute of the `<style>` element injected by\n * {@link applyEnv2Compensation}. Using a stable id makes the injection\n * idempotent — the element is reused on every subsequent message rather than\n * accumulating multiple `<style>` nodes.\n */\nexport const ENV2_COMPENSATION_STYLE_ID = 'ait-env2-safe-area-compensation';\n\n/**\n * The CSS text injected when the launcher forwards partner-mode insets\n * (`top === 0`). The negative margin pulls the mini-app document up by exactly\n * the device's status-bar inset so the launcher's own bar offset is not\n * double-counted.\n *\n * jsdom cannot evaluate `env()` expressions, so unit tests assert on this\n * string literal rather than computed geometry.\n */\nexport const ENV2_COMPENSATION_CSS = 'body { margin-top: calc(-1 * env(safe-area-inset-top)); }';\n\n/**\n * Inject or remove the env-2 safe-area compensation `<style>` based on the\n * forwarded insets received from the launcher.\n *\n * ### Why this is needed\n *\n * In env 2 (AITC Sandbox PWA) the launcher frames the mini-app in a\n * full-bleed `<iframe>` whose top edge is already positioned BELOW the\n * launcher's partner bar (at `env(safe-area-inset-top) + 54px`). However,\n * inside the cross-origin child iframe, CSS `env(safe-area-inset-top)` STILL\n * reports the full device status-bar inset (e.g. 62 px on an iPhone 15). Any\n * mini-app that pads itself by that CSS `env()` value ends up double-counting\n * the status-bar, producing a ≈62 px white dead-band between the partner bar\n * and the first content.\n *\n * The fix is a compensating negative margin on `body` inside the iframe. When\n * the launcher forwards partner insets (`top === 0`, meaning \"the iframe\n * already starts below the bar\"), we inject:\n *\n * ```css\n * body { margin-top: calc(-1 * env(safe-area-inset-top)); }\n * ```\n *\n * This cancels the duplicate padding without knowing the app's internal DOM\n * structure. The launcher's html/body `screen.height` force (#527) lives in the\n * LAUNCHER document (cross-origin) and never touches the mini-app document, so\n * margin and height force compose independently — they cannot interfere.\n *\n * ### Gating\n *\n * - **`top === 0`** (partner mode) → install the style (dead-band compensation).\n * - **`top > 0`** (game mode — launcher forwards the raw device inset for the\n * floating status-bar capsule) → remove any previously installed style so\n * `env()` is the authoritative clearance and nothing is double-removed.\n *\n * On every insets message (resize/orientation re-post) this function is called\n * so the gate is re-evaluated and the style toggled accordingly.\n *\n * ### env 1 / env 3 / env 4 no-regression\n *\n * This function is only called from the `message` listener installed by\n * {@link installSafeAreaInsetsBridge}. That listener fires only when the\n * launcher posts an `ait:safe-area-insets` message, which never happens in\n * env 1 (desktop browser, no launcher), env 3 (intoss-private WebView), or\n * env 4 (live relay) — they have no launcher posting to the iframe.\n *\n * @param top - The forwarded `top` inset value from the launcher message.\n */\nexport function applyEnv2Compensation(top: number): void {\n if (typeof document === 'undefined') return;\n\n const existing = document.getElementById(ENV2_COMPENSATION_STYLE_ID);\n\n if (top === 0) {\n // Partner mode: install the compensation style (idempotent — reuse if present).\n if (existing !== null) return; // already installed, nothing to do\n const style = document.createElement('style');\n style.id = ENV2_COMPENSATION_STYLE_ID;\n style.textContent = ENV2_COMPENSATION_CSS;\n document.head.appendChild(style);\n } else {\n // Game mode (or any non-zero top): remove the compensation if it was\n // previously installed so env() remains the authoritative clearance.\n if (existing !== null) existing.remove();\n }\n}\n\nlet installed = false;\n\n/**\n * Install the window `message` listener that receives forwarded insets. Safe to\n * call multiple times (idempotent) and a no-op outside a browser (SSR/jsdom\n * without a window). Imported for its side effect by the mock barrel so any\n * consumer that aliases `@apps-in-toss/web-framework` to the mock gets it wired.\n *\n * On each valid `ait:safe-area-insets` message:\n * 1. Writes the corrected insets into the mock `SafeAreaInsets` state\n * (existing #484 behaviour — for apps that still read SDK insets).\n * 2. Drives the env-2 dead-band compensation style via\n * {@link applyEnv2Compensation} — injects a `body { margin-top: calc(-1 *\n * env(safe-area-inset-top)) }` style when `top === 0` (partner mode) and\n * removes it when `top > 0` (game / full-bleed mode).\n */\nexport function installSafeAreaInsetsBridge(): void {\n if (installed || typeof window === 'undefined') return;\n installed = true;\n window.addEventListener('message', (event: MessageEvent) => {\n const insets = parseSafeAreaInsetsMessage(event.data);\n if (insets) {\n applyForwardedSafeAreaInsets(insets);\n applyEnv2Compensation(insets.top);\n }\n });\n}\n\n/**\n * Parse a raw postMessage payload as an `ait:navigate-back` command.\n *\n * Returns true when the payload is a well-formed navigate-back command\n * (`{ type: 'ait:navigate-back' }`), false otherwise. Pure — unit tested\n * without a real MessageEvent.\n *\n * Shape guard: only the `type` field is inspected; any extra fields are\n * ignored so future extensions do not break older receivers. The function\n * does NOT read any data field beyond `type` — no sensitive values, no host\n * disclosure (same principle as the insets bridge).\n */\nexport function isNavigateBackMessage(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false;\n return (data as { type?: unknown }).type === NAVIGATE_BACK_MESSAGE_TYPE;\n}\n\nlet navigateBackInstalled = false;\n\n/**\n * Install the window `message` listener that handles `ait:navigate-back`\n * commands (#510). When the launcher partner bar's `←` button is clicked it\n * posts `{ type: 'ait:navigate-back' }` to the framed dev app; this listener\n * calls `dispatchHostBackNavigation()` from the navigation module.\n *\n * Dispatch semantics: if there are any `graniteEvent.addEventListener('backEvent', …)`\n * subscribers the CustomEvent `__ait:backEvent` is fired (same path as the env-1\n * panel back button — the mini-app intercept channel). When there are no\n * subscribers `history.back()` is called as the fallback. Back semantics are\n * owned entirely by the navigation module; this bridge only delegates.\n *\n * Safe to call multiple times (idempotent) and a no-op outside a browser.\n * Installed together with the inset bridge by `installBridges()` so any consumer\n * of the mock barrel gets both wired automatically.\n *\n * No-op on apps that predate this bridge — the launcher posts the message but\n * older mocks simply have no listener (harmless).\n */\nexport function installNavigateBackBridge(): void {\n if (navigateBackInstalled || typeof window === 'undefined') return;\n navigateBackInstalled = true;\n window.addEventListener('message', (event: MessageEvent) => {\n if (isNavigateBackMessage(event.data)) {\n dispatchHostBackNavigation();\n }\n });\n}\n\n/**\n * Install both env-2 postMessage bridges in one call (#484 insets + #510\n * navigate-back). The mock barrel calls this at import time so consumers get\n * all bridges wired without any explicit setup.\n */\nexport function installBridges(): void {\n installSafeAreaInsetsBridge();\n installNavigateBackBridge();\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 type {\n LoadAdMobEvent,\n LoadAdMobOptions,\n LoadFullScreenAdEvent,\n LoadFullScreenAdOptions,\n ShowAdMobEvent,\n ShowAdMobOptions,\n ShowFullScreenAdEvent,\n ShowFullScreenAdOptions,\n} from '@apps-in-toss/web-framework';\nimport { buildNativeError } from '../native-error.js';\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 options: LoadAdMobOptions;\n onEvent: (event: LoadAdMobEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n setTimeout(() => {\n // 실패-모드 다이얼 (devtools#770): aitState.patch('failureModes',\n // { loadAdMob: 'PLACEMENT_ID_FETCH_FAILED' })로 실기기 프로비저닝 실패를\n // 재현한다. 미설정 시 forceNoFill/happy-load 기존 동작 그대로.\n const failureCode = aitState.state.failureModes.loadAdMob;\n if (failureCode) {\n args.onError(buildNativeError(failureCode));\n return;\n }\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({\n type: 'loaded',\n data: { responseInfo: { responseId: `mock-response-${args.options.adGroupId}` } },\n });\n }, 200);\n return () => {};\n },\n ),\n ),\n\n showAppsInTossAdMob: withIsSupported(\n observe(\n 'GoogleAdMob.showAppsInTossAdMob',\n 'faithful',\n (args: {\n options: ShowAdMobOptions;\n onEvent: (event: ShowAdMobEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n const { rewardUnitType, rewardAmount } = aitState.state.ads;\n setTimeout(\n () =>\n args.onEvent({\n type: 'userEarnedReward',\n data: { unitType: rewardUnitType, unitAmount: rewardAmount },\n }),\n 1000,\n );\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> => {\n // 실기기(env3)는 형식이 잘못된 adGroupId를 reject(code: INVALID_REQUEST)한다\n // — mock은 과거 어떤 값이든 조용히 boolean을 resolve했다(devtools#780, env1↔env3\n // capture diff 실측). SDK 타입 선언(GetCachedStatusAppsInTossAdmobParams)은\n // adGroupId를 필수 string으로 요구하지만, 이 mock은 필드 자체가 없는 호출은\n // 하위호환을 위해 계속 통과시킨다(기존 스모크 테스트가 `{}`로 호출) — \"값은\n // 있는데 빈 문자열/공백뿐\"만 명백히 잘못된 것으로 보고 거부한다. false-reject가\n // false-accept보다 나쁘므로 애매한 형식(길이·문자셋 제약 등)은 판정하지 않는다.\n // 이 throw는 결정적 입력-계약 위반이라 다이얼 뒤에 두지 않는다 — 던지는 error의\n // *shape*만 buildNativeError로 실기기 2.x envelope(name/code/userInfo/moduleName/\n // __isError)과 맞춘다(devtools#788, 손수 만든 `{errorCode}`가 env1↔env3\n // errorKeys 발산의 원인이었다).\n if (_options?.adGroupId !== undefined && _options.adGroupId.trim() === '') {\n throw buildNativeError('INVALID_REQUEST');\n }\n return aitState.state.ads.isLoaded;\n },\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 options: LoadFullScreenAdOptions;\n onEvent: (event: LoadFullScreenAdEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n setTimeout(() => {\n // 실패-모드 다이얼 (devtools#770): aitState.patch('failureModes',\n // { loadFullScreenAd: 'EXECUTION_ERROR' })로 실기기 프로비저닝 실패를 재현한다.\n // 미설정 시 forceNoFill/happy-load 기존 동작 그대로.\n const failureCode = aitState.state.failureModes.loadFullScreenAd;\n if (failureCode) {\n args.onError(buildNativeError(failureCode));\n return;\n }\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' });\n }, 200);\n return () => {};\n },\n ),\n);\n\nexport const showFullScreenAd = withIsSupported(\n observe(\n 'showFullScreenAd',\n 'faithful',\n (args: {\n options: ShowFullScreenAdOptions;\n onEvent: (event: ShowFullScreenAdEvent) => void;\n onError: (error: unknown) => void;\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'clicked' }), 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).\n//\n// 실기기(2.x×iOS) capture는 이 네 메서드가 `undefined`가 아니라 `null`로 resolve됨을 보였다\n// (devtools#770). 원본 SDK 타입 선언은 여전히 `Promise<void>`이므로 시그니처는 그대로 두고,\n// 런타임 반환값만 `null`로 캐스트해 실측과 동치시킨다.\nexport const Analytics = {\n screen: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'screen', params: params ?? {} });\n // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return Promise.resolve(null as unknown as void);\n },\n impression: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'impression', params: params ?? {} });\n // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return Promise.resolve(null as unknown as void);\n },\n click: (params?: LoggerParams): Promise<void> | undefined => {\n aitState.logAnalytics({ type: 'click', params: params ?? {} });\n // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return Promise.resolve(null as unknown as void);\n },\n};\n\nexport async function eventLog(params: {\n log_name: string;\n log_type: string;\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 // biome-ignore lint/suspicious/noConfusingVoidType: 원본 SDK 시그니처(Promise<void>) 유지 + 실측(null) 캐스트\n return null as unknown as void;\n}\n","/**\n * 인증/로그인 mock\n */\n\nimport { buildNativeError } from '../native-error.js';\nimport { aitState } from '../state.js';\n\nexport async function appLogin(): Promise<{\n authorizationCode: string;\n referrer: 'DEFAULT' | 'SANDBOX';\n}> {\n // 실패-모드 다이얼 (devtools#770): aitState.patch('failureModes', { appLogin: 'APP_LOGIN' })\n // 로 설정하면 실기기 프로비저닝 실패(APP_LOGIN)를 그대로 재현한다. 미설정 시 기존처럼\n // 항상 resolve (zero behavior change).\n const failureCode = aitState.state.failureModes.appLogin;\n if (failureCode) {\n throw buildNativeError(failureCode);\n }\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 // 실패-모드 다이얼 (devtools#783): aitState.patch('failureModes',\n // { getIsTossLoginIntegratedService: 'EXECUTION_ERROR' })로 실기기 실측(env3 run11,\n // 2.x/iOS `A1-awaited-is-boolean` → rejected/`Error`/`EXECUTION_ERROR`)을 재현한다.\n // 미설정 시 기존처럼 항상 resolve (zero behavior change).\n const failureCode = aitState.state.failureModes.getIsTossLoginIntegratedService;\n if (failureCode) {\n throw buildNativeError(failureCode);\n }\n\n return aitState.state.auth.isTossLoginIntegrated;\n}\n\n// SDK 3.0에서 getUserKeyForGame = typeof getAnonymousKey → Promise<{ hash: string; type: 'HASH' }>\n// 이전의 'INVALID_CATEGORY'|'ERROR'|undefined sentinel은 실제 SDK 타입에 없으므로 제거.\nexport async function getUserKeyForGame(): Promise<{ hash: string; type: 'HASH' }> {\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 * 게임/프로모션 mock\n */\n\nimport type {\n ContactsViralEvent,\n GrantPromotionRewardParams,\n GrantPromotionRewardResponse,\n} from '@apps-in-toss/web-framework';\nimport { aitState } from '../state.js';\n\n// SDK: grantPromotionReward(params: GrantPromotionRewardParams): Promise<GrantPromotionRewardResponse>\n//\n// 실기기(2.x×iOS) capture는 선언된 { key }가 아니라 { errorCode, message }\n// soft-failure shape를 보였다(devtools#770). 다만 그 캡처는 **미등록\n// promotionCode** 시나리오였다 — 즉 프로비저닝에 의존하는 실패지 이 API의\n// 무조건적 계약이 아니다. 그래서 기본값은 선언 타입대로 성공(`{ key }`)으로\n// 두고, soft-failure 재현은 실패-모드 다이얼(#777의 failureModes)에 붙인다.\n// 기본값을 실패로 뒤집으면 SDK가 선언한 성공 분기가 mock에서 영구히 도달\n// 불가능해지고, \"다이얼 미사용 시 zero behavior change\"라는 #770의 acceptance도\n// 깨진다. 다이얼 배선은 soft-resolve 모드(#789)로 구현됨 — #785 close.\nexport async function grantPromotionReward(\n params: GrantPromotionRewardParams,\n): Promise<GrantPromotionRewardResponse> {\n console.log('[@ait-co/devtools] grantPromotionReward:', params.params);\n if (aitState.state.failureModes.softResolve?.grantPromotionReward) {\n // env3 미등록 promotion soft-resolve 재현: { errorCode, message }.\n // 선언 타입 { key }와 다른 off-contract shape라 cast가 불가피하다 — 실기기가\n // SDK 선언보다 낙관적이지 않다는 사실 자체가 이 shape의 근거다. valueKeys=\n // ['errorCode','message']만 실측이고 문자열 값은 예시다(#789).\n return {\n errorCode: 'PROMOTION_NOT_FOUND',\n message: 'no active promotion',\n } as unknown as GrantPromotionRewardResponse;\n }\n return { key: `mock-reward-${Date.now()}` };\n}\n\n// SDK: grantPromotionRewardForGame = typeof grantPromotionReward\nexport async function grantPromotionRewardForGame(\n params: GrantPromotionRewardParams,\n): Promise<GrantPromotionRewardResponse> {\n console.log('[@ait-co/devtools] grantPromotionRewardForGame:', params.params);\n if (aitState.state.failureModes.softResolve?.grantPromotionRewardForGame) {\n // env3 미등록 promotion soft-resolve 재현: { errorCode, message }.\n // 선언 타입 { key }와 다른 off-contract shape라 cast가 불가피하다 — 실기기가\n // SDK 선언보다 낙관적이지 않다는 사실 자체가 이 shape의 근거다. valueKeys=\n // ['errorCode','message']만 실측이고 문자열 값은 예시다(#789).\n return {\n errorCode: 'PROMOTION_NOT_FOUND',\n message: 'no active promotion',\n } as unknown as GrantPromotionRewardResponse;\n }\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\n// 실기기(2.x×iOS) capture는 getGameCenterGameProfile이 { statusCode, gameSessionId,\n// nickname, profileImageUri } 4개 키로 resolve됨을 보였다(devtools#770) — 선언된 SDK\n// 타입엔 gameSessionId가 없으므로 시그니처는 그대로 두고 런타임 반환값만 캐스트한다.\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 gameSessionId: 'mock-session',\n nickname: profile.nickname,\n profileImageUri: profile.profileImageUri,\n } as unknown as { statusCode: 'SUCCESS'; nickname: string; profileImageUri: string };\n}\n\nexport async function openGameCenterLeaderboard(): Promise<void> {\n console.log('[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)');\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 const event: ContactsViralEvent = {\n type: 'close',\n data: { closeReason: 'noReward', sentRewardsCount: 0 },\n };\n params.onEvent(event);\n }, 500);\n return () => {};\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport type { IapProductListItem, IapSubscriptionInfoResult } from '@apps-in-toss/web-framework';\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: IapProductListItem[] }> {\n return {\n products: aitState.state.iap.products.map((p) => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })) as IapProductListItem[],\n };\n },\n\n // 실기기(2.x×iOS) capture는 getPendingOrders가 { orders }뿐 아니라 { orders, orderIds }\n // 2개 키로 resolve됨을 보였다(devtools#770) — 선언된 SDK 타입엔 orderIds가 없으므로\n // 시그니처는 그대로 두고 런타임 반환값만 캐스트한다.\n async getPendingOrders(): Promise<{\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n }> {\n const orders = [...aitState.state.iap.pendingOrders];\n return {\n orders,\n orderIds: orders.map((o) => o.orderId),\n } as unknown as {\n orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n };\n },\n\n // 실기기(2.x×iOS) capture는 getCompletedOrRefundedOrders가 nextKey 없이\n // { hasNext, orders } 2개 키로 resolve됨을 보였다(devtools#770).\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 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 // 실기기(2.x×iOS) capture는 getSubscriptionInfo가 { subscription } 없이 빈 객체\n // {}로 resolve됨을 보였다(devtools#770, valueKeys=[]). 다만 그 캡처는 **31146에\n // 구독이 프로비저닝되지 않은 상태**에서 얻은 것이다 — 즉 프로비저닝 의존 실패지 이\n // API의 무조건적 계약이 아니다(grantPromotionReward에서 되돌린 것과 같은 실패\n // 양상, devtools#786은 #778에서 이 되돌림만 누락됐던 잔여를 정정한다). 선언 타입\n // `IapSubscriptionInfoResult`의 subscription은 optional이 아니므로, 빈 객체를\n // 기본값으로 굳히면 SDK가 선언한 성공 분기가 mock에서 영구히 도달 불가능해진다.\n // 그래서 기본값은 선언 타입대로 성공 shape로 두고, 미프로비저닝 재현은 실패-모드\n // 다이얼에 붙인다 — soft-resolve 모드(#789)로 구현됨.\n async getSubscriptionInfo(_args: {\n params: { orderId: string };\n }): Promise<{ subscription: IapSubscriptionInfoResult }> {\n if (aitState.state.failureModes.softResolve?.getSubscriptionInfo) {\n // env3 미프로비저닝 구독 soft-resolve: 빈 객체 {} (valueKeys=[]). off-contract라 cast (#789).\n return {} as unknown as { subscription: IapSubscriptionInfoResult };\n }\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\n// 실기기(2.x×iOS) capture는 checkoutPayment가 전부 결제 **실패** 레코드였다\n// (devtools#770/#786) — I2-result-success-examined 시나리오조차\n// valueKeys=['false','reason']로 돌아왔다. env3는 성공 경로 shape를 한 번도\n// 보여준 적이 없으므로, 실패 shape(reason 포함)를 성공 분기에 일반화하는 건\n// 미측정 셀에 대한 근거 없는 추정이다(startUpdateLocation을 실측 없이 건드리지\n// 않은 것과 같은 이유). 게다가 그 일반화로 주장한 key-set 동치도 실제로는\n// 달성되지 않는다 — env3의 첫 키는 'success'가 아니라 'false'라 reason을 더해도\n// 여전히 어긋난다('false' 키 자체는 하네스 버그가 아니라 실기기 shape로 확정된\n// 별개 사안, 여기서 \"고치지\" 않는다). 그래서 성공 분기는 선언 타입대로\n// { success: true }만 반환한다. 실패 분기(reason 포함)는 실측대로 유지.\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 // requestTossPayPaysBilling도 checkoutPayment와 동일한 사정이다(devtools#770/#786)\n // — result-success-examined·native-billing-cancelled·happy-varied-token 시나리오\n // 전부 실패(valueKeys=['false','reason'])로 돌아왔다. env3가 성공 경로 shape를\n // 보여준 적이 없으므로 성공 분기에 reason을 얹는 건 근거 없는 일반화고, 그마저도\n // 첫 키가 'success'가 아닌 'false'라 주장한 key-set 동치를 달성하지 못한다.\n // 성공 분기는 선언 타입대로 { success: true }만 반환한다.\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 * 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 { buildNativeError } from './native-error.js';\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\nconst _requestNotificationAgreementImpl = (\n params: RequestNotificationAgreementOptions,\n): (() => void) => {\n let cancelled = false;\n\n Promise.resolve().then(async () => {\n if (cancelled) return;\n\n // 실패-모드 다이얼 (devtools#783): aitState.patch('failureModes',\n // { requestNotificationAgreement: '4000' })로 실기기 실측(env3 run11, 2.x/iOS —\n // happy-force-*/A1-empty-templateCode 시나리오 전부 rejected/`Error`/`4000`)을\n // 재현한다. 미설정 시 기존처럼 onEvent 경로 그대로 (zero behavior change).\n const failureCode = aitState.state.failureModes.requestNotificationAgreement;\n if (failureCode) {\n await params.onError(buildNativeError(failureCode));\n return;\n }\n\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};\nexport const requestNotificationAgreement: ((\n params: RequestNotificationAgreementOptions,\n) => () => void) & { isSupported: () => boolean } = Object.assign(\n _requestNotificationAgreementImpl,\n { isSupported: () => true },\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","/**\n * @ait-co/devtools/mock\n *\n * @apps-in-toss/web-framework의 모든 export를 mock으로 대체한다.\n * 번들러 alias로 원본 대신 이 모듈이 resolve된다.\n */\n\nimport { installBridges } from './safe-area-bridge.js';\n\n// env-2 postMessage bridges (#484 safe-area-insets, #510 navigate-back): wire\n// both receiver halves at import time so any consumer that aliases the SDK to\n// this mock gets real device geometry and back-navigation bridged automatically.\n// No-op outside a browser.\ninstallBridges();\n\n// --- 광고 ---\nexport { GoogleAdMob, loadFullScreenAd, showFullScreenAd, TossAds } from './ads/index.js';\n// --- 분석 ---\nexport { Analytics, eventLog } from './analytics/index.js';\n// --- 인증/로그인 ---\nexport {\n appLogin,\n appsInTossSignTossCert,\n getAnonymousKey,\n getIsTossLoginIntegratedService,\n getUserKeyForGame,\n} from './auth/index.js';\n// --- 디바이스 기능 ---\nexport {\n Accuracy,\n fetchAlbumItems,\n fetchAlbumPhotos,\n fetchContacts,\n generateHapticFeedback,\n getClipboardText,\n getCurrentLocation,\n getDefaultPlaceholderImages,\n openCamera,\n openPDFViewer,\n Storage,\n saveBase64Data,\n setClipboardText,\n startUpdateLocation,\n} from './device/index.js';\n// --- 게임/프로모션 ---\nexport {\n contactsViral,\n getGameCenterGameProfile,\n grantPromotionReward,\n grantPromotionRewardForGame,\n openGameCenterLeaderboard,\n submitGameCenterLeaderBoardScore,\n} from './game/index.js';\n// --- IAP / 결제 ---\nexport { checkoutPayment, IAP, requestTossPayPaysBilling } from './iap/index.js';\n// --- 화면/네비게이션/환경정보/이벤트 ---\nexport {\n appsInTossEvent,\n closeView,\n env,\n getAppsInTossGlobals,\n getDeviceId,\n getGroupId,\n getLocale,\n getNetworkStatus,\n getOperationalEnvironment,\n getPlatformOS,\n getSafeAreaInsets,\n getSchemeUri,\n getServerTime,\n getTossAppVersion,\n getTossShareLink,\n graniteEvent,\n isMinVersionSupported,\n onVisibilityChangedByTransparentServiceWeb,\n openURL,\n requestReview,\n SafeAreaInsets,\n setDeviceOrientation,\n setIosSwipeGestureEnabled,\n setScreenAwakeMode,\n setSecureScreen,\n share,\n tdsEvent,\n} from './navigation/index.js';\n// --- 알림 ---\nexport { requestNotificationAgreement } from './notification.js';\n// --- 파트너 ---\nexport { partner } from './partner/index.js';\n// --- 권한 (bridge-core 호환) ---\nexport {\n FetchAlbumPhotosPermissionError,\n FetchContactsPermissionError,\n GetClipboardTextPermissionError,\n GetCurrentLocationPermissionError,\n getPermission,\n OpenCameraPermissionError,\n openPermissionDialog,\n PermissionError,\n requestPermission,\n SetClipboardTextPermissionError,\n StartUpdateLocationPermissionError,\n} from './permissions.js';\nexport {\n deleteUserPreset,\n listUserPresets,\n saveUserPreset,\n} from './preset-store.js';\n// --- Mock state preset library ---\nexport {\n applyPreset,\n builtInPresets,\n captureCurrentState,\n type MockPreset,\n type MockPresetState,\n matchesPreset,\n} from './presets.js';\nexport type { WebViewTypeValue } from './safe-area-bridge.js';\n// --- env-2 postMessage bridges (#484 safe-area-insets, #510 navigate-back) ---\nexport {\n applyForwardedSafeAreaInsets,\n installBridges,\n installNavigateBackBridge,\n installSafeAreaInsetsBridge,\n isNavigateBackMessage,\n NAVIGATE_BACK_MESSAGE_TYPE,\n parseSafeAreaInsetsMessage,\n parseWebViewTypeMessage,\n SAFE_AREA_INSETS_MESSAGE_TYPE,\n WEB_VIEW_TYPE_MESSAGE_TYPE,\n} from './safe-area-bridge.js';\nexport type { AitDevtoolsState } from './state.js';\n// --- 상태 관리 (내부 + 외부 접근용) ---\nexport { aitState } from './state.js';\n// --- @apps-in-toss/types re-export 호환 ---\nexport type {\n AnalyticsLogEntry,\n DeviceApiMode,\n DeviceModes,\n HapticFeedbackType,\n IapNextResult,\n LocationCoords,\n MockContact,\n MockData,\n MockIapProduct,\n MockLocation,\n NetworkStatus,\n OperationalEnvironment,\n PermissionName,\n PermissionStatus,\n PlatformOS,\n Primitive,\n SafeAreaInsets as SafeAreaInsetsType,\n} from './types.js';\n"],"mappings":"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"}