@ait-co/devtools 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/mock/auth/index.ts","../../src/mock/navigation/index.ts","../../src/mock/proxy.ts","../../src/mock/permissions.ts","../../src/mock/device/index.ts","../../src/mock/iap/index.ts","../../src/mock/ads/index.ts","../../src/mock/game/index.ts","../../src/mock/analytics/index.ts","../../src/mock/partner/index.ts"],"sourcesContent":["/**\n * 인증/로그인 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport function appLogin(): Promise<{ authorizationCode: string; referrer: 'DEFAULT' | 'SANDBOX' }> {\n return Promise.resolve({\n authorizationCode: `mock-auth-${crypto.randomUUID()}`,\n referrer: aitState.state.environment === 'toss' ? 'DEFAULT' : 'SANDBOX',\n });\n}\n\nexport function getIsTossLoginIntegratedService(): Promise<boolean | undefined> {\n return Promise.resolve(aitState.state.auth.isTossLoginIntegrated);\n}\n\nexport function getUserKeyForGame(): Promise<{ hash: string; type: 'HASH' } | 'INVALID_CATEGORY' | 'ERROR' | undefined> {\n if (!aitState.state.auth.userKeyHash) return Promise.resolve(undefined);\n return Promise.resolve({ hash: aitState.state.auth.userKeyHash, type: 'HASH' });\n}\n\nexport interface AppsInTossSignTossCertParams {\n txId: string;\n}\n\nexport function appsInTossSignTossCert(_params: AppsInTossSignTossCertParams): Promise<void> {\n console.log('[ait-devtools] appsInTossSignTossCert called (no-op in mock)');\n return Promise.resolve();\n}\n","/**\n * 화면/네비게이션/이벤트 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport function closeView(): Promise<void> {\n console.log('[ait-devtools] closeView called');\n window.history.back();\n return Promise.resolve();\n}\n\nexport function openURL(url: string): Promise<void> {\n console.log('[ait-devtools] openURL:', url);\n window.open(url, '_blank');\n return Promise.resolve();\n}\n\nexport function share(message: { message: string }): Promise<void> {\n if (navigator.share) {\n return navigator.share({ text: message.message }).then(() => {});\n }\n console.log('[ait-devtools] share:', message.message);\n return Promise.resolve();\n}\n\nexport function getTossShareLink(path: string, _ogImageUrl?: string): Promise<string> {\n return Promise.resolve(`https://toss.im/share/mock${path}`);\n}\n\nexport function setIosSwipeGestureEnabled(_options: { isEnabled: boolean }): Promise<void> {\n console.log('[ait-devtools] setIosSwipeGestureEnabled:', _options.isEnabled);\n return Promise.resolve();\n}\n\nexport function setDeviceOrientation(_options: { type: 'portrait' | 'landscape' }): Promise<void> {\n console.log('[ait-devtools] setDeviceOrientation:', _options.type);\n return Promise.resolve();\n}\n\nexport function setScreenAwakeMode(options: { enabled: boolean }): Promise<{ enabled: boolean }> {\n console.log('[ait-devtools] setScreenAwakeMode:', options.enabled);\n return Promise.resolve({ enabled: options.enabled });\n}\n\nexport function setSecureScreen(options: { enabled: boolean }): Promise<{ enabled: boolean }> {\n console.log('[ait-devtools] setSecureScreen:', options.enabled);\n return Promise.resolve({ enabled: options.enabled });\n}\n\nexport function requestReview(): Promise<void> {\n console.log('[ait-devtools] requestReview called');\n return Promise.resolve();\n}\n(requestReview as unknown as { isSupported: () => boolean }).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: {\n android: string;\n ios: string;\n}): 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 function getNetworkStatus(): Promise<NetworkStatus> {\n return Promise.resolve(aitState.state.networkStatus);\n}\n\ntype NetworkStatus = 'OFFLINE' | 'WIFI' | '2G' | '3G' | '4G' | '5G' | 'WWAN' | 'UNKNOWN';\n\nexport function getServerTime(): Promise<number | undefined> {\n return Promise.resolve(Date.now());\n}\n(getServerTime as unknown as { isSupported: () => boolean }).isSupported = () => true;\n\n// --- 이벤트 시스템 ---\n\ninterface GraniteEventMap {\n backEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: void };\n homeEvent: { onEvent: () => void; onError?: (error: Error) => void; options?: void };\n}\n\nexport const graniteEvent = {\n addEventListener<K extends keyof GraniteEventMap>(\n event: K,\n { onEvent, onError }: {\n onEvent: GraniteEventMap[K]['onEvent'];\n onError?: GraniteEventMap[K]['onError'];\n options?: GraniteEventMap[K]['options'];\n },\n ): () => void {\n const handler = () => {\n try { onEvent(); }\n catch (e) { onError?.(e instanceof Error ? e : new Error(String(e))); }\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport const appsInTossEvent = {\n addEventListener<K extends string>(\n _event: K,\n _handlers: { onEvent: (...args: unknown[]) => void; onError?: (error: Error) => void; options?: unknown },\n ): () => void {\n return () => {};\n },\n};\n\ninterface TdsEventMap {\n navigationAccessoryEvent: { onEvent: (data: { id: string }) => void; onError?: (error: Error) => void; options: undefined };\n}\n\nexport const tdsEvent = {\n addEventListener<K extends keyof TdsEventMap>(\n event: K,\n { onEvent }: {\n onEvent: TdsEventMap[K]['onEvent'];\n onError?: TdsEventMap[K]['onError'];\n options?: TdsEventMap[K]['options'];\n },\n ): () => void {\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n onEvent(detail);\n };\n window.addEventListener(`__ait:${event}`, handler);\n return () => window.removeEventListener(`__ait:${event}`, handler);\n },\n};\n\nexport function onVisibilityChangedByTransparentServiceWeb(eventParams: {\n options: { callbackId: string };\n onEvent: (isVisible: boolean) => void;\n onError: (error: unknown) => void;\n}): () => void {\n const handler = () => eventParams.onEvent(!document.hidden);\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n}\n\n// --- env / globals ---\n\nexport const env = {\n getDeploymentId: () => aitState.state.deploymentId,\n};\n\nexport function getAppsInTossGlobals() {\n return {\n deploymentId: aitState.state.deploymentId,\n brandDisplayName: aitState.state.brand.displayName,\n brandIcon: aitState.state.brand.icon,\n brandPrimaryColor: aitState.state.brand.primaryColor,\n };\n}\n\n// --- SafeAreaInsets ---\n\ntype SafeAreaInsetsValue = { top: number; bottom: number; left: number; right: number };\ntype SafeAreaInsetsSubscribeHandler = { onEvent: (data: SafeAreaInsetsValue) => void };\n\nexport const SafeAreaInsets = {\n get: (): SafeAreaInsetsValue => ({ ...aitState.state.safeAreaInsets }),\n // NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.\n // 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.\n subscribe: ({ onEvent }: SafeAreaInsetsSubscribeHandler): (() => void) => {\n return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));\n },\n};\n\n/** @deprecated */\nexport function getSafeAreaInsets(): number {\n return aitState.state.safeAreaInsets.top;\n}\n","/**\n * 미구현 API용 Proxy fallback\n * SDK에 새 메서드가 추가되었을 때 크래시 없이 경고만 출력한다.\n */\n\nconst WARNED = new Set<string>();\n\n/** 테스트에서 WARNED 캐시를 초기화할 때 사용 */\nexport function resetWarned(): void {\n WARNED.clear();\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: string) {\n if (prop in target) return target[prop];\n if (typeof prop === 'symbol') return undefined;\n\n if (!WARNED.has(`${moduleName}.${prop}`)) {\n console.warn(\n `[ait-devtools] ${moduleName}.${prop} is not mocked yet. Returning no-op. ` +\n `Please update ait-devtools or file an issue.`,\n );\n WARNED.add(`${moduleName}.${prop}`);\n }\n return async () => undefined;\n },\n }) as T;\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState, type PermissionName, type PermissionStatus } from './state.js';\n\nexport function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return Promise.resolve(aitState.state.permissions[name]);\n}\n\nexport function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return Promise.resolve('allowed');\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return Promise.resolve('allowed');\n}\n\nexport function requestPermission(permission: { name: PermissionName; access: string }): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & { getPermission: () => Promise<PermissionStatus>; openPermissionDialog: () => Promise<'allowed' | 'denied'> } {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(`[ait-devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`);\n }\n}\n","/**\n * 디바이스 기능 mock\n * Storage, Location, Camera, Photos, Contacts, Clipboard, Haptic\n */\n\nimport { aitState, type MockLocation } from '../state.js';\nimport { createMockProxy } from '../proxy.js';\nimport { withPermission, checkPermission } from '../permissions.js';\n\n// --- Storage ---\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 keys.forEach(k => localStorage.removeItem(k));\n },\n});\n\n// --- Location ---\n\nenum Accuracy { Lowest = 1, Low = 2, Balanced = 3, High = 4, Highest = 5, BestForNavigation = 6 }\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\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n return buildLocation();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction _startUpdateLocation(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n // 약간의 jitter를 추가해서 현실감 있게\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\nexport const startUpdateLocation = Object.assign(_startUpdateLocation, {\n getPermission: () => withPermission(_getCurrentLocation, 'geolocation').getPermission(),\n openPermissionDialog: () => withPermission(_getCurrentLocation, 'geolocation').openPermissionDialog(),\n});\n\n// --- Camera ---\n\nconst _openCamera = async (options?: { base64?: boolean; maxWidth?: number }): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n\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 input.onchange = () => {\n const file = input.files?.[0];\n if (!file) { reject(new Error('No file selected')); return; }\n const reader = new FileReader();\n reader.onload = () => {\n resolve({\n id: crypto.randomUUID(),\n dataUri: reader.result as string,\n });\n };\n reader.readAsDataURL(file);\n };\n input.click();\n });\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nconst _fetchAlbumPhotos = async (options?: { maxCount?: number; maxWidth?: number; base64?: boolean }): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n\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 input.onchange = async () => {\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) { reject(new Error('No files selected')); return; }\n const results = await Promise.all(\n files.map(file => new Promise<{ id: string; dataUri: string }>((res) => {\n const reader = new FileReader();\n reader.onload = () => res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.readAsDataURL(file);\n })),\n );\n resolve(results);\n };\n input.click();\n });\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Contacts ---\n\nconst _fetchContacts = async (options: { size: number; offset: number; query?: { contains?: string } }) => {\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(c => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));\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// --- Clipboard ---\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\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 await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n\n// --- Haptic Feedback ---\n\nexport function generateHapticFeedback(options: { type: string }): Promise<void> {\n console.log(`[ait-devtools] haptic: ${options.type}`);\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n return Promise.resolve();\n}\n\n// --- Save Base64 ---\n\nexport function saveBase64Data(params: { data: string; fileName: string; mimeType: string }): 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 return Promise.resolve();\n}\n","/**\n * IAP (인앱결제) mock\n */\n\nimport { aitState } from '../state.js';\nimport { createMockProxy } from '../proxy.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: { orderId: string; subscriptionId?: 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 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: { orderId: string; subscriptionId?: string }) => 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: [...aitState.state.iap.completedOrders, {\n orderId: result.orderId,\n sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\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(e => console.error('[ait-devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {\n handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch(e => console.error('[ait-devtools] IAP unexpected error:', e));\n return () => {};\n },\n\n async getProductItemList(): Promise<{ products: unknown[] }> {\n return {\n products: aitState.state.iap.products.map(p => ({\n ...p,\n ...(p.type === 'SUBSCRIPTION' ? { renewalCycle: p.renewalCycle ?? 'MONTHLY' } : {}),\n })),\n };\n },\n\n async getPendingOrders(): Promise<{ orders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }> }> {\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(o => o.orderId === args.params.orderId);\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 = [...aitState.state.iap.completedOrders, {\n orderId: order.orderId,\n sku: order.sku,\n status: 'COMPLETED' as const,\n date: new Date().toISOString(),\n }];\n aitState.patch('iap', { pendingOrders, completedOrders });\n }\n return true;\n },\n\n async getSubscriptionInfo(_args: { params: { orderId: string } }) {\n return {\n subscription: {\n catalogId: 1,\n status: 'ACTIVE',\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n isAutoRenew: true,\n gracePeriodExpiresAt: null,\n isAccessible: true,\n },\n };\n },\n});\n\n// --- TossPay ---\n\nexport function checkoutPayment(options: { params: { payToken: string } }): Promise<{ success: boolean; reason?: string }> {\n const { nextResult, failReason } = aitState.state.payment;\n console.log('[ait-devtools] checkoutPayment:', options.params.payToken);\n\n return new Promise(resolve => {\n setTimeout(() => {\n if (nextResult === 'success') {\n resolve({ success: true });\n } else {\n resolve({ success: false, reason: failReason || 'Mock payment failed' });\n }\n }, 300);\n });\n}\n","/**\n * 광고 mock (GoogleAdMob, TossAds, FullScreenAd)\n */\n\nimport { aitState } from '../state.js';\nimport { createMockProxy } from '../proxy.js';\n\nfunction withIsSupported<T extends (...args: never[]) => unknown>(fn: T): T & { isSupported: () => boolean } {\n (fn as T & { isSupported: () => boolean }).isSupported = () => true;\n return fn as T & { isSupported: () => boolean };\n}\n\n// --- Google AdMob ---\n\nexport const GoogleAdMob = createMockProxy('GoogleAdMob', {\n loadAppsInTossAdMob: withIsSupported((args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n setTimeout(() => {\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n }),\n\n showAppsInTossAdMob: withIsSupported((args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n }): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'requested' }), 50);\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'impression' }), 150);\n setTimeout(() => {\n args.onEvent({ type: 'userEarnedReward', data: { unitType: 'coins', unitAmount: 10 } });\n }, 1000);\n setTimeout(() => {\n args.onEvent({ type: 'dismissed' });\n aitState.patch('ads', { isLoaded: false });\n }, 1500);\n return () => {};\n }),\n\n isAppsInTossAdMobLoaded: withIsSupported(\n async (_options: { adGroupId?: string }): Promise<boolean> => aitState.state.ads.isLoaded,\n ),\n});\n\n// --- TossAds ---\n\nexport const TossAds = createMockProxy('TossAds', {\n initialize: withIsSupported((_options: unknown) => {\n console.log('[ait-devtools] TossAds.initialize (mock)');\n }),\n attach: withIsSupported((_adGroupId: string, target: string | HTMLElement, _options?: unknown) => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText = 'background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;';\n placeholder.textContent = '[ait-devtools] TossAds Placeholder';\n el.appendChild(placeholder);\n }\n }),\n attachBanner: withIsSupported((_adGroupId: string, target: string | HTMLElement, _options?: unknown) => {\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (el) {\n const placeholder = document.createElement('div');\n placeholder.style.cssText = 'background:#f0f0f0;border:1px dashed #999;padding:12px;text-align:center;color:#666;font-size:12px;';\n placeholder.textContent = '[ait-devtools] Banner Ad Placeholder';\n el.appendChild(placeholder);\n }\n return { destroy: () => {} };\n }),\n destroy: withIsSupported((_slotId: string) => {}),\n destroyAll: withIsSupported(() => {}),\n});\n\n// --- FullScreen Ad ---\n\nexport const loadFullScreenAd = withIsSupported((args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n}): (() => void) => {\n setTimeout(() => {\n aitState.patch('ads', { isLoaded: true });\n args.onEvent({ type: 'loaded', data: { adGroupId: args.options?.adGroupId } });\n }, 200);\n return () => {};\n});\n\nexport const showFullScreenAd = withIsSupported((args: {\n onEvent: (data: { type: string; data?: unknown }) => void;\n onError: (error: Error) => void;\n options?: { adGroupId?: string };\n}): (() => void) => {\n if (!aitState.state.ads.isLoaded) {\n args.onError(new Error('Ad not loaded'));\n return () => {};\n }\n setTimeout(() => args.onEvent({ type: 'show' }), 100);\n setTimeout(() => args.onEvent({ type: 'dismissed' }), 1500);\n return () => {};\n});\n","/**\n * 게임/프로모션 mock\n */\n\nimport { aitState } from '../state.js';\n\nexport function grantPromotionReward(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[ait-devtools] grantPromotionReward:', params.params);\n return Promise.resolve({ key: `mock-reward-${Date.now()}` });\n}\n\nexport function grantPromotionRewardForGame(params: {\n params: { promotionCode: string; amount: number };\n}): Promise<{ key: string } | { errorCode: string; message: string } | 'ERROR' | undefined> {\n console.log('[ait-devtools] grantPromotionRewardForGame:', params.params);\n return Promise.resolve({ key: `mock-reward-${Date.now()}` });\n}\n\nexport function submitGameCenterLeaderBoardScore(params: {\n score: string;\n}): Promise<{ statusCode: 'SUCCESS' | 'LEADERBOARD_NOT_FOUND' | 'PROFILE_NOT_FOUND' | 'UNPARSABLE_SCORE' } | undefined> {\n aitState.patch('game', {\n leaderboardScores: [...aitState.state.game.leaderboardScores, { score: params.score, timestamp: Date.now() }],\n });\n return Promise.resolve({ statusCode: 'SUCCESS' });\n}\n\nexport 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 Promise.resolve({ statusCode: 'PROFILE_NOT_FOUND' });\n return Promise.resolve({\n statusCode: 'SUCCESS',\n nickname: profile.nickname,\n profileImageUri: profile.profileImageUri,\n });\n}\n\nexport function openGameCenterLeaderboard(): Promise<void> {\n console.log('[ait-devtools] openGameCenterLeaderboard (no-op in browser)');\n return Promise.resolve();\n}\n\ninterface ContactsViralEvent {\n type: string;\n data: Record<string, unknown>;\n}\n\nexport function contactsViral(params: {\n options: { moduleId: string };\n onEvent: (event: ContactsViralEvent) => void;\n onError: (error: unknown) => void;\n}): () => void {\n setTimeout(() => {\n params.onEvent({\n type: 'close',\n data: {\n closeReason: 'noReward',\n sentRewardsCount: 0,\n },\n });\n }, 500);\n return () => {};\n}\n","/**\n * 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\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 function eventLog(params: {\n log_name: string;\n log_type: 'debug' | 'info' | 'warn' | 'error' | 'event' | 'screen' | 'impression' | 'click';\n params: Record<string, Primitive>;\n}): Promise<void> {\n aitState.logAnalytics({ type: params.log_type, params: { log_name: params.log_name, ...params.params } });\n return Promise.resolve();\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-devtools] partner.addAccessoryButton:', options);\n },\n async removeAccessoryButton(): Promise<void> {\n console.log('[ait-devtools] partner.removeAccessoryButton');\n },\n};\n"],"mappings":"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","names":["Accuracy"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ait-devtools Floating Panel
3
+ *
4
+ * import 하면 자동으로 페이지에 DevTools 패널을 마운트한다.
5
+ * 외부 의존성 없이 vanilla DOM으로 구현.
6
+ */
7
+ declare function mount(): void;
8
+
9
+ export { mount };
@@ -0,0 +1,528 @@
1
+ import {
2
+ aitState
3
+ } from "../chunk-YYIIG3JT.js";
4
+
5
+ // src/panel/styles.ts
6
+ var PANEL_STYLES = (
7
+ /* css */
8
+ `
9
+ .ait-panel-toggle {
10
+ position: fixed;
11
+ bottom: 16px;
12
+ right: 16px;
13
+ z-index: 99999;
14
+ width: 48px;
15
+ height: 48px;
16
+ border-radius: 50%;
17
+ background: #3182F6;
18
+ border: none;
19
+ cursor: pointer;
20
+ box-shadow: 0 2px 12px rgba(0,0,0,0.2);
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ font-size: 20px;
25
+ color: white;
26
+ transition: transform 0.15s;
27
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
28
+ }
29
+ .ait-panel-toggle:hover {
30
+ transform: scale(1.1);
31
+ }
32
+
33
+ .ait-panel {
34
+ position: fixed;
35
+ bottom: 72px;
36
+ right: 16px;
37
+ z-index: 99998;
38
+ width: 360px;
39
+ max-height: 520px;
40
+ background: #1a1a2e;
41
+ border-radius: 12px;
42
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
43
+ font-family: -apple-system, BlinkMacSystemFont, 'Pretendard', sans-serif;
44
+ font-size: 13px;
45
+ color: #e0e0e0;
46
+ overflow: hidden;
47
+ display: none;
48
+ }
49
+ .ait-panel.open {
50
+ display: flex;
51
+ flex-direction: column;
52
+ }
53
+
54
+ .ait-panel-header {
55
+ padding: 12px 16px;
56
+ background: #16213e;
57
+ font-weight: 600;
58
+ font-size: 14px;
59
+ display: flex;
60
+ justify-content: space-between;
61
+ align-items: center;
62
+ border-bottom: 1px solid #2a2a4a;
63
+ }
64
+ .ait-panel-header span {
65
+ color: #3182F6;
66
+ }
67
+
68
+ .ait-panel-tabs {
69
+ display: flex;
70
+ background: #16213e;
71
+ border-bottom: 1px solid #2a2a4a;
72
+ overflow-x: auto;
73
+ scrollbar-width: none;
74
+ }
75
+ .ait-panel-tabs::-webkit-scrollbar { display: none; }
76
+
77
+ .ait-panel-tab {
78
+ padding: 8px 12px;
79
+ font-size: 12px;
80
+ color: #888;
81
+ cursor: pointer;
82
+ white-space: nowrap;
83
+ border-bottom: 2px solid transparent;
84
+ background: none;
85
+ border-top: none;
86
+ border-left: none;
87
+ border-right: none;
88
+ font-family: inherit;
89
+ }
90
+ .ait-panel-tab:hover {
91
+ color: #bbb;
92
+ }
93
+ .ait-panel-tab.active {
94
+ color: #3182F6;
95
+ border-bottom-color: #3182F6;
96
+ }
97
+
98
+ .ait-panel-body {
99
+ padding: 12px 16px;
100
+ overflow-y: auto;
101
+ max-height: 400px;
102
+ flex: 1;
103
+ }
104
+
105
+ .ait-section {
106
+ margin-bottom: 16px;
107
+ }
108
+ .ait-section-title {
109
+ font-size: 11px;
110
+ text-transform: uppercase;
111
+ color: #666;
112
+ margin-bottom: 8px;
113
+ letter-spacing: 0.5px;
114
+ }
115
+
116
+ .ait-row {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ margin-bottom: 6px;
121
+ }
122
+ .ait-row label {
123
+ color: #aaa;
124
+ font-size: 12px;
125
+ }
126
+
127
+ .ait-select {
128
+ background: #2a2a4a;
129
+ color: #e0e0e0;
130
+ border: 1px solid #3a3a5a;
131
+ border-radius: 4px;
132
+ padding: 4px 8px;
133
+ font-size: 12px;
134
+ font-family: inherit;
135
+ cursor: pointer;
136
+ }
137
+
138
+ .ait-input {
139
+ background: #2a2a4a;
140
+ color: #e0e0e0;
141
+ border: 1px solid #3a3a5a;
142
+ border-radius: 4px;
143
+ padding: 4px 8px;
144
+ font-size: 12px;
145
+ width: 100px;
146
+ font-family: inherit;
147
+ }
148
+
149
+ .ait-btn {
150
+ background: #3182F6;
151
+ color: white;
152
+ border: none;
153
+ border-radius: 4px;
154
+ padding: 6px 12px;
155
+ font-size: 12px;
156
+ cursor: pointer;
157
+ font-family: inherit;
158
+ }
159
+ .ait-btn:hover {
160
+ background: #1b6ef3;
161
+ }
162
+ .ait-btn-sm {
163
+ padding: 4px 8px;
164
+ font-size: 11px;
165
+ }
166
+ .ait-btn-danger {
167
+ background: #e74c3c;
168
+ }
169
+ .ait-btn-danger:hover {
170
+ background: #c0392b;
171
+ }
172
+
173
+ .ait-log-entry {
174
+ font-family: 'SF Mono', 'Menlo', monospace;
175
+ font-size: 11px;
176
+ padding: 3px 0;
177
+ border-bottom: 1px solid #2a2a4a;
178
+ color: #aaa;
179
+ }
180
+ .ait-log-entry .ait-log-type {
181
+ color: #3182F6;
182
+ font-weight: 600;
183
+ margin-right: 6px;
184
+ }
185
+ .ait-log-entry .ait-log-time {
186
+ color: #555;
187
+ margin-right: 6px;
188
+ }
189
+
190
+ .ait-storage-row {
191
+ font-family: 'SF Mono', 'Menlo', monospace;
192
+ font-size: 11px;
193
+ display: flex;
194
+ gap: 8px;
195
+ padding: 4px 0;
196
+ border-bottom: 1px solid #2a2a4a;
197
+ }
198
+ .ait-storage-key {
199
+ color: #e8a87c;
200
+ min-width: 80px;
201
+ word-break: break-all;
202
+ }
203
+ .ait-storage-value {
204
+ color: #95e6cb;
205
+ flex: 1;
206
+ word-break: break-all;
207
+ }
208
+ `
209
+ );
210
+
211
+ // src/panel/index.ts
212
+ var TABS = [
213
+ { id: "env", label: "Environment" },
214
+ { id: "permissions", label: "Permissions" },
215
+ { id: "location", label: "Location" },
216
+ { id: "iap", label: "IAP" },
217
+ { id: "events", label: "Events" },
218
+ { id: "analytics", label: "Analytics" },
219
+ { id: "storage", label: "Storage" }
220
+ ];
221
+ function h(tag, attrs, ...children) {
222
+ const el = document.createElement(tag);
223
+ if (attrs) {
224
+ for (const [k, v] of Object.entries(attrs)) {
225
+ if (k === "className") el.className = v;
226
+ else el.setAttribute(k, v);
227
+ }
228
+ }
229
+ for (const child of children) {
230
+ el.append(typeof child === "string" ? document.createTextNode(child) : child);
231
+ }
232
+ return el;
233
+ }
234
+ function selectRow(label, options, value, onChange) {
235
+ const select = h("select", { className: "ait-select" });
236
+ for (const opt of options) {
237
+ const option = h("option", { value: opt }, opt);
238
+ if (opt === value) option.selected = true;
239
+ select.appendChild(option);
240
+ }
241
+ select.addEventListener("change", () => onChange(select.value));
242
+ return h("div", { className: "ait-row" }, h("label", {}, label), select);
243
+ }
244
+ function inputRow(label, value, onChange) {
245
+ const input = h("input", { className: "ait-input", value });
246
+ input.addEventListener("change", () => onChange(input.value));
247
+ return h("div", { className: "ait-row" }, h("label", {}, label), input);
248
+ }
249
+ function renderEnvTab() {
250
+ const s = aitState.state;
251
+ const container = h("div");
252
+ container.append(
253
+ h(
254
+ "div",
255
+ { className: "ait-section" },
256
+ h("div", { className: "ait-section-title" }, "Platform"),
257
+ selectRow("OS", ["ios", "android"], s.platform, (v) => aitState.update({ platform: v })),
258
+ inputRow("App Version", s.appVersion, (v) => aitState.update({ appVersion: v })),
259
+ selectRow("Environment", ["toss", "sandbox"], s.environment, (v) => aitState.update({ environment: v })),
260
+ inputRow("Locale", s.locale, (v) => aitState.update({ locale: v }))
261
+ ),
262
+ h(
263
+ "div",
264
+ { className: "ait-section" },
265
+ h("div", { className: "ait-section-title" }, "Network"),
266
+ selectRow("Status", ["WIFI", "4G", "5G", "3G", "2G", "OFFLINE", "WWAN", "UNKNOWN"], s.networkStatus, (v) => aitState.update({ networkStatus: v }))
267
+ ),
268
+ h(
269
+ "div",
270
+ { className: "ait-section" },
271
+ h("div", { className: "ait-section-title" }, "Safe Area Insets"),
272
+ inputRow("Top", String(s.safeAreaInsets.top), (v) => aitState.patch("safeAreaInsets", { top: Number(v) })),
273
+ inputRow("Bottom", String(s.safeAreaInsets.bottom), (v) => aitState.patch("safeAreaInsets", { bottom: Number(v) }))
274
+ )
275
+ );
276
+ return container;
277
+ }
278
+ function renderPermissionsTab() {
279
+ const s = aitState.state;
280
+ const container = h("div");
281
+ const names = ["camera", "photos", "geolocation", "clipboard", "contacts", "microphone"];
282
+ const statuses = ["allowed", "denied", "notDetermined"];
283
+ container.append(
284
+ h(
285
+ "div",
286
+ { className: "ait-section" },
287
+ h("div", { className: "ait-section-title" }, "Device Permissions"),
288
+ ...names.map(
289
+ (name) => selectRow(name, statuses, s.permissions[name], (v) => {
290
+ aitState.patch("permissions", { [name]: v });
291
+ })
292
+ )
293
+ )
294
+ );
295
+ return container;
296
+ }
297
+ function renderLocationTab() {
298
+ const s = aitState.state;
299
+ const container = h("div");
300
+ container.append(
301
+ h(
302
+ "div",
303
+ { className: "ait-section" },
304
+ h("div", { className: "ait-section-title" }, "Current Location"),
305
+ inputRow("Latitude", String(s.location.coords.latitude), (v) => {
306
+ const coords = { ...s.location.coords, latitude: Number(v) };
307
+ aitState.patch("location", { coords });
308
+ }),
309
+ inputRow("Longitude", String(s.location.coords.longitude), (v) => {
310
+ const coords = { ...s.location.coords, longitude: Number(v) };
311
+ aitState.patch("location", { coords });
312
+ }),
313
+ inputRow("Accuracy", String(s.location.coords.accuracy), (v) => {
314
+ const coords = { ...s.location.coords, accuracy: Number(v) };
315
+ aitState.patch("location", { coords });
316
+ })
317
+ )
318
+ );
319
+ return container;
320
+ }
321
+ function renderIapTab() {
322
+ const s = aitState.state;
323
+ const container = h("div");
324
+ const results = ["success", "USER_CANCELED", "INVALID_PRODUCT_ID", "PAYMENT_PENDING", "NETWORK_ERROR", "ITEM_ALREADY_OWNED", "INTERNAL_ERROR"];
325
+ container.append(
326
+ h(
327
+ "div",
328
+ { className: "ait-section" },
329
+ h("div", { className: "ait-section-title" }, "IAP Simulator"),
330
+ selectRow("Next Purchase Result", results, s.iap.nextResult, (v) => {
331
+ aitState.patch("iap", { nextResult: v });
332
+ })
333
+ ),
334
+ h(
335
+ "div",
336
+ { className: "ait-section" },
337
+ h("div", { className: "ait-section-title" }, "TossPay"),
338
+ selectRow("Next Payment Result", ["success", "fail"], s.payment.nextResult, (v) => {
339
+ aitState.patch("payment", { nextResult: v });
340
+ })
341
+ ),
342
+ h(
343
+ "div",
344
+ { className: "ait-section" },
345
+ h("div", { className: "ait-section-title" }, `Completed Orders (${s.iap.completedOrders.length})`),
346
+ ...s.iap.completedOrders.slice(-5).map(
347
+ (o) => h(
348
+ "div",
349
+ { className: "ait-log-entry" },
350
+ h("span", { className: "ait-log-type" }, o.status),
351
+ `${o.sku} (${o.orderId.slice(-8)})`
352
+ )
353
+ )
354
+ )
355
+ );
356
+ return container;
357
+ }
358
+ function renderEventsTab() {
359
+ const container = h("div");
360
+ const backBtn = h("button", { className: "ait-btn" }, "Trigger Back Event");
361
+ backBtn.addEventListener("click", () => aitState.trigger("backEvent"));
362
+ const homeBtn = h("button", { className: "ait-btn" }, "Trigger Home Event");
363
+ homeBtn.addEventListener("click", () => aitState.trigger("homeEvent"));
364
+ container.append(
365
+ h(
366
+ "div",
367
+ { className: "ait-section" },
368
+ h("div", { className: "ait-section-title" }, "Navigation Events"),
369
+ h("div", { className: "ait-row" }, backBtn, homeBtn)
370
+ ),
371
+ h(
372
+ "div",
373
+ { className: "ait-section" },
374
+ h("div", { className: "ait-section-title" }, "Login"),
375
+ selectRow("Logged In", ["true", "false"], String(aitState.state.auth.isLoggedIn), (v) => {
376
+ aitState.patch("auth", { isLoggedIn: v === "true" });
377
+ }),
378
+ selectRow("Toss Login Integrated", ["true", "false"], String(aitState.state.auth.isTossLoginIntegrated), (v) => {
379
+ aitState.patch("auth", { isTossLoginIntegrated: v === "true" });
380
+ })
381
+ )
382
+ );
383
+ return container;
384
+ }
385
+ function renderAnalyticsTab() {
386
+ const container = h("div");
387
+ const logs = aitState.state.analyticsLog;
388
+ const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "Clear");
389
+ clearBtn.addEventListener("click", () => {
390
+ aitState.state.analyticsLog.length = 0;
391
+ refreshPanel();
392
+ });
393
+ container.append(
394
+ h(
395
+ "div",
396
+ { className: "ait-section" },
397
+ h(
398
+ "div",
399
+ { className: "ait-row" },
400
+ h("div", { className: "ait-section-title" }, `Analytics Log (${logs.length})`),
401
+ clearBtn
402
+ ),
403
+ ...logs.slice(-30).reverse().map((entry) => {
404
+ const time = new Date(entry.timestamp).toLocaleTimeString("ko-KR", { hour12: false });
405
+ return h(
406
+ "div",
407
+ { className: "ait-log-entry" },
408
+ h("span", { className: "ait-log-time" }, time),
409
+ h("span", { className: "ait-log-type" }, entry.type),
410
+ JSON.stringify(entry.params)
411
+ );
412
+ })
413
+ )
414
+ );
415
+ return container;
416
+ }
417
+ function renderStorageTab() {
418
+ const container = h("div");
419
+ const prefix = "__ait_storage:";
420
+ const entries = [];
421
+ for (let i = 0; i < localStorage.length; i++) {
422
+ const key = localStorage.key(i);
423
+ if (key?.startsWith(prefix)) {
424
+ entries.push([key.slice(prefix.length), localStorage.getItem(key) ?? ""]);
425
+ }
426
+ }
427
+ const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "Clear All");
428
+ clearBtn.addEventListener("click", () => {
429
+ entries.forEach(([key]) => localStorage.removeItem(prefix + key));
430
+ refreshPanel();
431
+ });
432
+ container.append(
433
+ h(
434
+ "div",
435
+ { className: "ait-section" },
436
+ h(
437
+ "div",
438
+ { className: "ait-row" },
439
+ h("div", { className: "ait-section-title" }, `Storage (${entries.length} items)`),
440
+ clearBtn
441
+ ),
442
+ entries.length === 0 ? h("div", { style: "color:#555;font-size:12px" }, "No items in storage") : h(
443
+ "div",
444
+ {},
445
+ ...entries.map(
446
+ ([key, value]) => h(
447
+ "div",
448
+ { className: "ait-storage-row" },
449
+ h("span", { className: "ait-storage-key" }, key),
450
+ h("span", { className: "ait-storage-value" }, value.length > 100 ? value.slice(0, 100) + "..." : value)
451
+ )
452
+ )
453
+ )
454
+ )
455
+ );
456
+ return container;
457
+ }
458
+ var TAB_RENDERERS = {
459
+ env: renderEnvTab,
460
+ permissions: renderPermissionsTab,
461
+ location: renderLocationTab,
462
+ iap: renderIapTab,
463
+ events: renderEventsTab,
464
+ analytics: renderAnalyticsTab,
465
+ storage: renderStorageTab
466
+ };
467
+ var currentTab = "env";
468
+ var panelEl = null;
469
+ var bodyEl = null;
470
+ var tabsEl = null;
471
+ function refreshPanel() {
472
+ if (!bodyEl || !tabsEl) return;
473
+ bodyEl.innerHTML = "";
474
+ bodyEl.appendChild(TAB_RENDERERS[currentTab]());
475
+ tabsEl.querySelectorAll(".ait-panel-tab").forEach((el) => {
476
+ el.classList.toggle("active", el.getAttribute("data-tab") === currentTab);
477
+ });
478
+ }
479
+ function mount() {
480
+ if (typeof document === "undefined") return;
481
+ if (document.querySelector(".ait-panel-toggle")) return;
482
+ const style = document.createElement("style");
483
+ style.textContent = PANEL_STYLES;
484
+ document.head.appendChild(style);
485
+ const toggle = h("button", { className: "ait-panel-toggle", title: "AIT DevTools" }, "AIT");
486
+ let isOpen = false;
487
+ panelEl = h("div", { className: "ait-panel" });
488
+ const header = h(
489
+ "div",
490
+ { className: "ait-panel-header" },
491
+ h("span", {}, "AIT DevTools"),
492
+ h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v${aitState.state.appVersion}`)
493
+ );
494
+ tabsEl = h("div", { className: "ait-panel-tabs" });
495
+ for (const tab of TABS) {
496
+ const tabEl = h("button", { className: "ait-panel-tab", "data-tab": tab.id }, tab.label);
497
+ tabEl.addEventListener("click", () => {
498
+ currentTab = tab.id;
499
+ refreshPanel();
500
+ });
501
+ tabsEl.appendChild(tabEl);
502
+ }
503
+ bodyEl = h("div", { className: "ait-panel-body" });
504
+ panelEl.append(header, tabsEl, bodyEl);
505
+ document.body.append(panelEl, toggle);
506
+ toggle.addEventListener("click", () => {
507
+ isOpen = !isOpen;
508
+ panelEl.classList.toggle("open", isOpen);
509
+ if (isOpen) refreshPanel();
510
+ });
511
+ aitState.subscribe(() => {
512
+ if (isOpen && (currentTab === "analytics" || currentTab === "storage")) {
513
+ refreshPanel();
514
+ }
515
+ });
516
+ refreshPanel();
517
+ }
518
+ if (typeof document !== "undefined") {
519
+ if (document.readyState === "loading") {
520
+ document.addEventListener("DOMContentLoaded", mount);
521
+ } else {
522
+ mount();
523
+ }
524
+ }
525
+ export {
526
+ mount
527
+ };
528
+ //# sourceMappingURL=index.js.map