From ac6860b7b96980d2b1fa79dc503c103d47972a61 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 22:25:55 +0900 Subject: [PATCH 1/7] feat(ait): add production host bridge --- .sampo/changesets/ait-production-host.md | 14 + README.md | 10 +- adapters/ait/package.json | 13 + adapters/ait/src/host.test.ts | 287 ++++ adapters/ait/src/host.ts | 695 ++++++++ adapters/ait/src/index.ts | 18 +- adapters/ait/src/lifecycle.ts | 45 + apps/target-ait/README.md | 30 +- apps/target-ait/package.json | 5 +- apps/target-ait/src/aitBridge.test.ts | 200 --- apps/target-ait/src/aitBridge.ts | 518 ------ apps/target-ait/src/aitIdentity.test.ts | 77 - apps/target-ait/src/aitIdentity.ts | 103 -- apps/target-ait/src/bridgeTypes.ts | 40 - apps/target-ait/src/main.ts | 35 +- apps/target-ait/tsconfig.bundle.json | 2 + apps/target-ait/tsconfig.test.json | 8 +- apps/target-ait/vite.config.ts | 49 + examples/phaser-starter/mpgd.targets.json | 1 + .../cli/src/production-target-readiness.ts | 4 +- packages/cli/templates/phaser-game/README.md | 6 + .../templates/phaser-game/mpgd.targets.json | 1 + packages/target-config/src/effective.ts | 60 +- packages/target-config/src/runtime.ts | 61 +- packages/target-config/targets.json | 2 +- .../target-config/test/target-config-smoke.ts | 29 + pnpm-lock.yaml | 1516 +++++++++++++++-- tools/smoke/adapter-effective-config.ts | 114 +- tools/smoke/effective-target-config.ts | 41 +- tools/smoke/production-target-readiness.ts | 21 +- tools/target/effective-config.ts | 3 + tools/target/platform-targets.ts | 7 + tools/target/schemas.ts | 2 + tsconfig.base.json | 1 + 34 files changed, 2864 insertions(+), 1154 deletions(-) create mode 100644 .sampo/changesets/ait-production-host.md create mode 100644 adapters/ait/src/host.test.ts create mode 100644 adapters/ait/src/host.ts create mode 100644 adapters/ait/src/lifecycle.ts delete mode 100644 apps/target-ait/src/aitBridge.test.ts delete mode 100644 apps/target-ait/src/aitBridge.ts delete mode 100644 apps/target-ait/src/aitIdentity.test.ts delete mode 100644 apps/target-ait/src/aitIdentity.ts delete mode 100644 apps/target-ait/src/bridgeTypes.ts diff --git a/.sampo/changesets/ait-production-host.md b/.sampo/changesets/ait-production-host.md new file mode 100644 index 00000000..1be704fb --- /dev/null +++ b/.sampo/changesets/ait-production-host.md @@ -0,0 +1,14 @@ +--- +npm/@mpgd/adapter-ait: minor (Added) +npm/@mpgd/target-config: minor (Changed) +npm/@mpgd/cli: minor (Added) +--- + +Add a reusable Apps in Toss production host bridge backed by the official anonymous identity, +Storage, Ads 2.0, sharing, lifecycle, and Game Center APIs. Rewarded ads now forward callback +evidence without issuing demo grants, and commerce stays fail-closed until a game installs its +own verified purchase flow. + +Allow game targets to opt out of authoritative game services. Opted-out targets disable IAP and +ad features in their effective configuration and no longer require a production backend URL, +while targets that enable authoritative grants keep the public HTTPS backend release gate. diff --git a/README.md b/README.md index e12e439e..13e37cab 100644 --- a/README.md +++ b/README.md @@ -423,10 +423,12 @@ for target overrides, native staging, and the Apps in Toss console URL gate. ## Apps in Toss -The Apps in Toss target currently uses SDK 2.x compatible `granite.config.ts` -and `ait build` scripts. Its wrapper resolves the game-specific player id with -`getUserKeyForGame`; Toss app 5.232.0 or newer and a game-category mini-app are -required outside the explicit plain-browser mock command. SDK 3.x keeps the feature interface compatible but +The Apps in Toss target uses SDK 2.x-compatible `granite.config.ts` and `ait +build` scripts. The reusable production host resolves a stable mini-app-scoped +player id with `getAnonymousKey`, persists gateway state with native `Storage`, +and delegates sharing, Game Center, and Ads 2.0 to the official SDK. Purchases +stay fail-closed, and rewarded-ad callbacks remain evidence until a game-owned +authority verifies them. SDK 3.x keeps the feature interface compatible but renames the config file to `apps-in-toss.config.ts`, so that migration should be handled as a dedicated follow-up. diff --git a/adapters/ait/package.json b/adapters/ait/package.json index a4782eb3..3be71bc1 100644 --- a/adapters/ait/package.json +++ b/adapters/ait/package.json @@ -28,6 +28,10 @@ ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" + }, + "./host": { + "types": "./dist/host.d.ts", + "default": "./dist/host.js" } }, "files": [ @@ -44,7 +48,16 @@ "@mpgd/bridge": "workspace:*", "@mpgd/platform": "workspace:*" }, + "peerDependencies": { + "@apps-in-toss/web-framework": ">=2.10.4 <3" + }, + "peerDependenciesMeta": { + "@apps-in-toss/web-framework": { + "optional": true + } + }, "devDependencies": { + "@apps-in-toss/web-framework": "^2.10.4", "ttsc": "0.18.4", "typescript": "7.0.2", "vitest": "^4.0.0" diff --git a/adapters/ait/src/host.test.ts b/adapters/ait/src/host.test.ts new file mode 100644 index 00000000..b6c669c9 --- /dev/null +++ b/adapters/ait/src/host.test.ts @@ -0,0 +1,287 @@ +import { describe, expect, it } from 'vitest'; + +import type { BridgeRequest } from '@mpgd/bridge'; + +import { createAitHostBridge, shareIntent, type AitHostDependencies } from './host'; + +describe('AIT production host bridge', () => { + it('uses the native anonymous identity and persistent string storage', async () => { + const values = new Map(); + const bridge = createAitHostBridge({ + dependencies: createDependencies({ + identityProvider: async () => ({ type: 'HASH', hash: ' player-1 ' }), + storage: { + getItem: async (key) => values.get(key) ?? null, + setItem: async (key, value) => { + values.set(key, value); + }, + }, + }), + }); + + await expect(request(bridge, 'identity.getSession', {})).resolves.toEqual({ + identityLevel: 'platform-anonymous', + playerId: 'player-1', + trustLevel: 'platform-asserted', + }); + await request(bridge, 'storage.save', { key: 'save:v1', value: { hints: 3 } }); + expect(values.get('save:v1')).toBe('{"hints":3}'); + await expect(request(bridge, 'storage.load', { key: 'save:v1' })).resolves.toEqual({ + __mpgdBridgeProtocol: 'mpgd.storage.load.v1', + found: true, + value: { hints: 3 }, + }); + + const firstLoad = await request(bridge, 'storage.load', { key: 'save:v1' }) as { + value: { hints: number }; + }; + firstLoad.value.hints = -1; + await expect(request(bridge, 'storage.load', { key: 'save:v1' })).resolves.toMatchObject({ + value: { hints: 3 }, + }); + }); + + it('fails closed when the native anonymous identity is invalid', async () => { + const bridge = createAitHostBridge({ + dependencies: createDependencies({ identityProvider: async () => ({ type: 'HASH' }) }), + }); + + await expect(request(bridge, 'identity.getPlayer', {})).rejects.toThrow( + 'AIT user identity is unavailable.', + ); + await expect(request(bridge, 'identity.getSession', {})).resolves.toEqual({ + identityLevel: 'guest', + trustLevel: 'local', + }); + }); + + it('fails closed for commerce and unconfigured ads', async () => { + const bridge = createAitHostBridge({ dependencies: createDependencies() }); + + await expect(request(bridge, 'runtime.getCapabilities', {})).resolves.toMatchObject({ + nativeIap: false, + nativeAds: false, + rewardedAds: false, + interstitialAds: false, + }); + await expect(request(bridge, 'commerce.getProducts', {})).resolves.toEqual([]); + await expect(request(bridge, 'commerce.purchase', { productId: 'HINT_PACK_5' })).resolves + .toEqual({ status: 'failed', entitlementIds: [] }); + await expect(request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-1', + })).resolves.toEqual({ status: 'unavailable', rewardGranted: false }); + }); + + it('grants a configured rewarded ad only after userEarnedReward and dismissal', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + const dependencies = createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies, + }); + + const preload = request(bridge, 'ads.preload', { placementId: 'SUDOKU_HINT_REWARDED' }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await expect(preload).resolves.toEqual({}); + + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-correlation-1', + }); + showCallbacks?.onEvent({ + type: 'userEarnedReward', + data: { unitType: 'hint', unitAmount: 1 }, + }); + showCallbacks?.onEvent({ type: 'dismissed' }); + + await expect(reward).resolves.toEqual({ + status: 'completed', + rewardGranted: true, + ledgerEntryId: 'reward-correlation-1', + evidence: { + schema: 'apps-in-toss.rewarded-ad.callback.v1', + payload: { + event: 'user-earned-reward', + correlationId: 'reward-correlation-1', + placementId: 'ait-ad-group-1', + }, + }, + }); + }); + + it('does not grant a reward when the ad is dismissed without the reward event', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { placementId: 'SUDOKU_HINT_REWARDED' }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-correlation-2', + }); + showCallbacks?.onEvent({ type: 'dismissed' }); + + await expect(reward).resolves.toEqual({ status: 'skipped', rewardGranted: false }); + }); + + it('delegates supported Game Center score submission and opening', async () => { + const submittedScores: string[] = []; + let openCount = 0; + const bridge = createAitHostBridge({ + dependencies: createDependencies({ + submitGameCenterLeaderBoardScore: async ({ score }) => { + submittedScores.push(score); + return { statusCode: 'SUCCESS' }; + }, + openGameCenterLeaderboard: async () => { + openCount += 1; + }, + }), + }); + + await expect(request(bridge, 'leaderboard.submitScore', { score: 42 })).resolves.toEqual({ + submitted: true, + }); + await expect(request(bridge, 'leaderboard.open', {})).resolves.toEqual({}); + expect(submittedScores).toEqual(['42']); + expect(openCount).toBe(1); + }); +}); + +describe('AIT sharing', () => { + it('converts an HTTPS game path to the app-owned intoss deep link', async () => { + const paths: string[] = []; + const messages: string[] = []; + const result = await shareIntent( + { + text: "Try today's challenge.", + deepLink: 'https://game.example/daily?challengeToken=signed-token#result', + previewImageUrl: 'https://game.example/daily.png', + }, + { + appName: 'ttokdoku', + getTossShareLink: async (path) => { + paths.push(path); + return 'https://toss.im/_ul/daily'; + }, + share: async ({ message }) => { + messages.push(message); + }, + }, + ); + + expect(result).toEqual({ status: 'shared', completion: 'presented' }); + expect(paths).toEqual([ + 'intoss://ttokdoku/daily?challengeToken=signed-token#result', + ]); + expect(messages).toEqual(["Try today's challenge.\nhttps://toss.im/_ul/daily"]); + }); + + it('rejects unsafe links and preserves native share cancellation', async () => { + const dependencies = { + appName: 'ttokdoku', + getTossShareLink: async () => 'https://toss.im/_ul/daily', + share: async () => {}, + }; + + await expect(shareIntent({ text: 'Unsafe', deepLink: 'javascript:alert(1)' }, dependencies)) + .resolves.toEqual({ status: 'unavailable' }); + await expect(shareIntent( + { text: 'Cancelled', deepLink: '/daily' }, + { + ...dependencies, + share: async () => { + throw { name: 'AbortError' }; + }, + }, + )).resolves.toEqual({ status: 'cancelled' }); + }); +}); + +type LoadAdCallbacks = Parameters[0]; +type ShowAdCallbacks = Parameters[0]; + +function createDependencies( + overrides: Partial = {}, +): AitHostDependencies { + const unsupportedAd = Object.assign(() => () => {}, { isSupported: () => false }); + + return { + identityProvider: async () => ({ type: 'HASH', hash: 'test-player' }), + storage: { + getItem: async () => null, + setItem: async () => {}, + }, + getTossShareLink: async () => 'https://toss.im/test', + share: async () => {}, + isMinVersionSupported: () => true, + loadFullScreenAd: unsupportedAd, + showFullScreenAd: unsupportedAd, + openGameCenterLeaderboard: async () => {}, + submitGameCenterLeaderBoardScore: async () => ({ statusCode: 'SUCCESS' }), + ...overrides, + } as AitHostDependencies; +} + +async function request( + bridge: ReturnType, + method: BridgeRequest['method'], + payload: unknown, +): Promise { + const response = await bridge.request({ + id: `${method}:test`, + method, + payload, + meta: { + target: 'ait', + appVersion: '1.0.0', + buildId: 'test', + sentAt: '2026-07-19T00:00:00.000Z', + }, + } satisfies BridgeRequest); + + if (!response.ok) { + throw new Error(response.error.message); + } + + return response.data; +} diff --git a/adapters/ait/src/host.ts b/adapters/ait/src/host.ts new file mode 100644 index 00000000..93a69340 --- /dev/null +++ b/adapters/ait/src/host.ts @@ -0,0 +1,695 @@ +import { + getAnonymousKey, + getTossShareLink, + isMinVersionSupported, + loadFullScreenAd, + openGameCenterLeaderboard, + share, + showFullScreenAd, + Storage, + submitGameCenterLeaderBoardScore, +} from '@apps-in-toss/web-framework'; + +import { + bridgeStorageLoadProtocol, + type BridgeRequest, + type BridgeResponse, + type BridgeStorageLoadData, +} from '@mpgd/bridge'; +import type { LaunchEntry, LaunchIntent, ShareResult } from '@mpgd/platform'; + +import type { GamePlatformBridge } from './index.js'; +import { dispatchAitLifecycleEvent } from './lifecycle.js'; + +const defaultAdTimeoutMs = 60_000; +const rewardedAdEvidenceSchema = 'apps-in-toss.rewarded-ad.callback.v1'; +const launchEntries = new Set([ + 'home', + 'daily', + 'practice', + 'free-play', + 'continue', + 'leaderboard', + 'friend-challenge', +]); + +export type AitIdentityProvider = () => Promise; + +export interface AitHostDependencies { + readonly identityProvider: AitIdentityProvider; + readonly storage: Pick; + readonly getTossShareLink: typeof getTossShareLink; + readonly share: typeof share; + readonly isMinVersionSupported: typeof isMinVersionSupported; + readonly loadFullScreenAd: typeof loadFullScreenAd; + readonly showFullScreenAd: typeof showFullScreenAd; + readonly openGameCenterLeaderboard: typeof openGameCenterLeaderboard; + readonly submitGameCenterLeaderBoardScore: typeof submitGameCenterLeaderBoardScore; +} + +export interface InstallAitHostBridgeOptions { + readonly appName?: string; + readonly adGroupIds?: Readonly>; + readonly adPlacementTypes?: Readonly>; + readonly adTimeoutMs?: number; + readonly dependencies?: Partial; +} + +const defaultDependencies: AitHostDependencies = { + identityProvider: getAnonymousKey, + storage: Storage, + getTossShareLink, + share, + isMinVersionSupported, + loadFullScreenAd, + showFullScreenAd, + openGameCenterLeaderboard, + submitGameCenterLeaderBoardScore, +}; + +export function installAitHostBridge(options: InstallAitHostBridgeOptions = {}): GamePlatformBridge { + const bridge = createAitHostBridge(options); + (globalThis as { __GAME_PLATFORM_BRIDGE__?: GamePlatformBridge }).__GAME_PLATFORM_BRIDGE__ = bridge; + return bridge; +} + +export function createAitHostBridge( + options: InstallAitHostBridgeOptions = {}, +): GamePlatformBridge { + const dependencies = { ...defaultDependencies, ...options.dependencies }; + const appName = normalizeAppName(options.appName ?? 'mpgd-kit'); + const adGroupIds = normalizeAdGroupIds(options.adGroupIds); + const adPlacementTypes = normalizeAdPlacementTypes(options.adPlacementTypes); + const loadedAdGroupIds = new Set(); + const adTimeoutMs = normalizeTimeout(options.adTimeoutMs); + + return { + async request(input) { + try { + return await handleRequest(parseBridgeRequest(input)); + } catch (error) { + return createBridgeError( + input.id, + 'AIT_BRIDGE_REQUEST_FAILED', + errorMessage(error), + true, + ); + } + }, + }; + + async function handleRequest(request: BridgeRequest): Promise { + switch (request.method) { + case 'runtime.getCapabilities': { + const adsSupported = dependencies.loadFullScreenAd.isSupported() + && dependencies.showFullScreenAd.isSupported(); + const rewardedAds = adsSupported + && hasConfiguredAdType(adGroupIds, adPlacementTypes, 'rewarded'); + const interstitialAds = adsSupported + && hasConfiguredAdType(adGroupIds, adPlacementTypes, 'interstitial'); + + return ok(request, { + nativeIap: false, + nativeAds: rewardedAds || interstitialAds, + rewardedAds, + interstitialAds, + nativeLeaderboard: isGameCenterSupported(dependencies), + achievements: false, + cloudSave: false, + socialShare: true, + haptics: false, + localizedContent: true, + }); + } + + case 'identity.getPlayer': { + const player = await resolveAitIdentity(dependencies.identityProvider); + return player === null + ? createBridgeError( + request.id, + 'AIT_IDENTITY_UNAVAILABLE', + 'AIT user identity is unavailable.', + ) + : ok(request, player); + } + + case 'identity.getSession': { + const player = await resolveAitIdentity(dependencies.identityProvider); + return ok( + request, + player === null + ? { identityLevel: 'guest', trustLevel: 'local' } + : { + identityLevel: 'platform-anonymous', + playerId: player.playerId, + trustLevel: 'platform-asserted', + }, + ); + } + + case 'identity.requestUpgrade': + return ok(request, { status: 'unavailable', reloadExpected: false }); + + case 'presentation.getLaunchIntent': + return ok(request, getLaunchIntent()); + + case 'presentation.requestGameSurface': + return ok(request, 'already-fullscreen'); + + case 'share.share': + return ok(request, await shareIntent(request.payload, { + appName, + getTossShareLink: dependencies.getTossShareLink, + share: dependencies.share, + })); + + case 'share.readInboundShare': + return ok(request, readInboundShare()); + + case 'notifications.getStatus': + return ok(request, 'unsupported'); + + case 'notifications.requestSubscription': + return ok(request, 'unavailable'); + + // IAP must be installed with game-owned server verification. Never return demo grants. + case 'commerce.getProducts': + return ok(request, []); + + case 'commerce.purchase': + return ok(request, { status: 'failed', entitlementIds: [] }); + + case 'commerce.restore': + return ok(request, { restoredEntitlements: [] }); + + case 'commerce.getEntitlements': + return ok(request, []); + + case 'ads.preload': { + const placementId = readPlacementId(request.payload); + const adGroupId = adGroupIds.get(placementId); + + if ( + adGroupId === undefined + || adPlacementTypes.get(placementId) === undefined + || !dependencies.loadFullScreenAd.isSupported() + ) { + return createBridgeError( + request.id, + 'AIT_AD_UNAVAILABLE', + `AIT ad placement is unavailable: ${placementId}`, + ); + } + + await preloadAdGroup(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs); + return ok(request, {}); + } + + case 'ads.showRewarded': { + const placementId = readPlacementId(request.payload); + const adGroupId = adGroupIds.get(placementId); + + if ( + adGroupId === undefined + || adPlacementTypes.get(placementId) !== 'rewarded' + || !dependencies.showFullScreenAd.isSupported() + || !loadedAdGroupIds.has(adGroupId) + ) { + return ok(request, { status: 'unavailable', rewardGranted: false }); + } + + const correlationId = readIdempotencyKey(request.payload, request.id); + const result = await showRewardedAd(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs); + + return ok( + request, + result.rewardGranted + ? { + ...result, + // game-services forwards this as platformImpressionId and compares it + // with the native callback correlationId during authority verification. + ledgerEntryId: correlationId, + evidence: { + schema: rewardedAdEvidenceSchema, + payload: { + event: 'user-earned-reward', + correlationId, + placementId: adGroupId, + }, + }, + } + : result, + ); + } + + case 'ads.showInterstitial': { + const placementId = readPlacementId(request.payload); + const adGroupId = adGroupIds.get(placementId); + + if ( + adGroupId === undefined + || adPlacementTypes.get(placementId) !== 'interstitial' + || !dependencies.showFullScreenAd.isSupported() + || !loadedAdGroupIds.has(adGroupId) + ) { + return ok(request, { status: 'unavailable' }); + } + + return ok( + request, + await showInterstitialAd(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs), + ); + } + + case 'leaderboard.submitScore': { + if (!isGameCenterSupported(dependencies)) { + return ok(request, { submitted: false }); + } + + const score = readFiniteScore(request.payload); + const result = await dependencies.submitGameCenterLeaderBoardScore({ + score: String(score), + }); + return ok(request, { submitted: result?.statusCode === 'SUCCESS' }); + } + + case 'leaderboard.open': { + if (!isGameCenterSupported(dependencies)) { + return ok(request, {}); + } + + dispatchAitLifecycleEvent('pause'); + try { + await dependencies.openGameCenterLeaderboard(); + } finally { + dispatchAitLifecycleEvent('resume'); + } + return ok(request, {}); + } + + case 'storage.load': { + const key = readStorageKey(request.payload); + const serialized = await dependencies.storage.getItem(key); + const data: BridgeStorageLoadData = serialized === null + ? { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false } + : { + __mpgdBridgeProtocol: bridgeStorageLoadProtocol, + found: true, + value: JSON.parse(serialized) as unknown, + }; + return ok(request, data); + } + + case 'storage.save': { + const key = readStorageKey(request.payload); + const value = readPayloadRecord(request.payload).value; + const serialized = JSON.stringify(value); + + if (serialized === undefined) { + throw new TypeError('Storage values must be JSON-serializable.'); + } + + await dependencies.storage.setItem(key, serialized); + return ok(request, {}); + } + + default: + return createBridgeError( + request.id, + 'UNSUPPORTED_METHOD', + `Unsupported AIT bridge method: ${request.method}`, + ); + } + } +} + +interface AitShareDependencies { + readonly appName: string; + readonly getTossShareLink: typeof getTossShareLink; + readonly share: typeof share; +} + +export async function shareIntent( + payload: unknown, + dependencies: AitShareDependencies, +): Promise { + const intent = readPayloadRecord(payload); + + if (typeof intent.text !== 'string' || typeof intent.deepLink !== 'string') { + return { status: 'unavailable' }; + } + + const deepLink = toAitDeepLink(intent.deepLink, dependencies.appName); + + if (deepLink === undefined) { + return { status: 'unavailable' }; + } + + try { + const tossLink = await dependencies.getTossShareLink( + deepLink, + typeof intent.previewImageUrl === 'string' && intent.previewImageUrl.startsWith('https://') + ? intent.previewImageUrl + : undefined, + ); + await dependencies.share({ message: `${intent.text}\n${tossLink}` }); + return { status: 'shared', completion: 'presented' }; + } catch (error) { + return isAbortError(error) ? { status: 'cancelled' } : { status: 'unavailable' }; + } +} + +async function resolveAitIdentity( + provider: AitIdentityProvider, +): Promise<{ readonly playerId: string } | null> { + let result: unknown; + + try { + result = await provider(); + } catch { + return null; + } + + if (!isRecord(result) || result.type !== 'HASH') { + return null; + } + + const hash = typeof result.hash === 'string' ? result.hash.trim() : ''; + return hash.length === 0 ? null : { playerId: hash }; +} + +async function preloadAdGroup( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + timeoutMs: number, +): Promise { + if (loaded.has(adGroupId)) { + return; + } + + await new Promise((resolve, reject) => { + let cleanup = (): void => {}; + const finish = once((error?: unknown) => { + globalThis.clearTimeout(timer); + cleanup(); + if (error === undefined) { + loaded.add(adGroupId); + resolve(); + } else { + reject(error); + } + }); + const timer = globalThis.setTimeout( + () => finish(new Error(`Timed out loading AIT ad group ${adGroupId}.`)), + timeoutMs, + ); + + cleanup = dependencies.loadFullScreenAd({ + options: { adGroupId }, + onEvent: (event) => { + if (event.type === 'loaded') { + finish(); + } + }, + onError: finish, + }); + }); +} + +async function showRewardedAd( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + timeoutMs: number, +): Promise<{ readonly status: 'completed' | 'skipped' | 'failed'; readonly rewardGranted: boolean }> { + let rewardGranted = false; + const status = await showAd(dependencies, adGroupId, loaded, timeoutMs, (eventType) => { + if (eventType === 'userEarnedReward') { + rewardGranted = true; + } + }); + + return { + status: status === 'shown' && rewardGranted + ? 'completed' + : status === 'failed' + ? 'failed' + : 'skipped', + rewardGranted, + }; +} + +async function showInterstitialAd( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + timeoutMs: number, +): Promise<{ readonly status: 'shown' | 'skipped' | 'unavailable' }> { + const status = await showAd(dependencies, adGroupId, loaded, timeoutMs); + return { status: status === 'failed' ? 'skipped' : status }; +} + +function showAd( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + timeoutMs: number, + observe?: (eventType: string) => void, +): Promise<'shown' | 'failed'> { + loaded.delete(adGroupId); + dispatchAitLifecycleEvent('pause'); + + return new Promise((resolve) => { + let cleanup = (): void => {}; + const finish = once((status: 'shown' | 'failed') => { + globalThis.clearTimeout(timer); + cleanup(); + dispatchAitLifecycleEvent('resume'); + resolve(status); + }); + const timer = globalThis.setTimeout(() => finish('failed'), timeoutMs); + + cleanup = dependencies.showFullScreenAd({ + options: { adGroupId }, + onEvent: (event) => { + observe?.(event.type); + if (event.type === 'dismissed') { + finish('shown'); + } else if (event.type === 'failedToShow') { + finish('failed'); + } + }, + onError: () => finish('failed'), + }); + }); +} + +function getLaunchIntent(): LaunchIntent { + const params = inboundSearchParams(); + const challengeToken = nonEmptyParam(params.get('challengeToken')); + const puzzleId = nonEmptyParam(params.get('puzzleId')); + const requestedEntry = nonEmptyParam(params.get('entry')); + const entry = requestedEntry !== undefined && launchEntries.has(requestedEntry as LaunchEntry) + ? requestedEntry as LaunchEntry + : challengeToken === undefined + ? 'home' + : 'friend-challenge'; + + return { + entry, + ...(puzzleId === undefined ? {} : { puzzleId }), + ...(challengeToken === undefined ? {} : { referralToken: challengeToken }), + }; +} + +function readInboundShare(): { readonly puzzleId?: string; readonly challengeToken?: string } | null { + const params = inboundSearchParams(); + const puzzleId = nonEmptyParam(params.get('puzzleId')); + const challengeToken = nonEmptyParam(params.get('challengeToken')); + return puzzleId === undefined && challengeToken === undefined + ? null + : { + ...(puzzleId === undefined ? {} : { puzzleId }), + ...(challengeToken === undefined ? {} : { challengeToken }), + }; +} + +function inboundSearchParams(): URLSearchParams { + const params = new URLSearchParams(globalThis.location?.search ?? ''); + const nested = params.get('queryParams'); + + if (nested !== null) { + try { + const parsed = JSON.parse(nested) as unknown; + if (isRecord(parsed)) { + for (const [key, value] of Object.entries(parsed)) { + if (typeof value === 'string' && !params.has(key)) { + params.set(key, value); + } + } + } + } catch { + // Deep-link payloads are untrusted; malformed nested query data is ignored. + } + } + + return params; +} + +function toAitDeepLink(input: string, appName: string): string | undefined { + if (input.startsWith('//')) { + return undefined; + } + + if (input.startsWith('/')) { + const baseUrl = new URL('https://mpgd.invalid'); + const parsed = new URL(input, baseUrl); + return parsed.origin === baseUrl.origin + ? `intoss://${appName}${parsed.pathname}${parsed.search}${parsed.hash}` + : undefined; + } + + try { + const parsed = new URL(input); + if (parsed.protocol === 'intoss:') { + return parsed.hostname === appName ? input : undefined; + } + return parsed.protocol === 'http:' || parsed.protocol === 'https:' + ? `intoss://${appName}${parsed.pathname}${parsed.search}${parsed.hash}` + : undefined; + } catch { + return undefined; + } +} + +function isGameCenterSupported(dependencies: AitHostDependencies): boolean { + return dependencies.isMinVersionSupported({ android: '5.221.0', ios: '5.221.0' }); +} + +function normalizeAdGroupIds( + input: Readonly> | undefined, +): ReadonlyMap { + return new Map( + Object.entries(input ?? {}) + .map(([placementId, adGroupId]) => [placementId.trim(), adGroupId.trim()] as const) + .filter(([placementId, adGroupId]) => placementId.length > 0 && adGroupId.length > 0), + ); +} + +function hasConfiguredAdType( + adGroupIds: ReadonlyMap, + adPlacementTypes: ReadonlyMap, + type: 'rewarded' | 'interstitial', +): boolean { + return [...adGroupIds.keys()].some((placementId) => adPlacementTypes.get(placementId) === type); +} + +function normalizeAdPlacementTypes( + input: Readonly> | undefined, +): ReadonlyMap { + return new Map( + Object.entries(input ?? {}) + .map(([placementId, type]) => [placementId.trim(), type] as const) + .filter(([placementId]) => placementId.length > 0), + ); +} + +function normalizeAppName(value: string): string { + const normalized = value.trim(); + if (!/^[A-Za-z0-9-]+$/u.test(normalized)) { + throw new Error('AIT appName must contain only letters, numbers, and hyphens.'); + } + return normalized; +} + +function normalizeTimeout(value: number | undefined): number { + if (value === undefined) { + return defaultAdTimeoutMs; + } + if (!Number.isFinite(value) || value <= 0) { + throw new Error('AIT ad timeout must be a positive finite number.'); + } + return value; +} + +function readStorageKey(payload: unknown): string { + const key = readPayloadRecord(payload).key; + if (typeof key !== 'string' || key.length === 0) { + throw new TypeError('AIT storage key must be a non-empty string.'); + } + return key; +} + +function readPlacementId(payload: unknown): string { + const placementId = readPayloadRecord(payload).placementId; + if (typeof placementId !== 'string' || placementId.length === 0) { + throw new TypeError('AIT ad placementId must be a non-empty string.'); + } + return placementId; +} + +function readIdempotencyKey(payload: unknown, fallback: string): string { + const value = readPayloadRecord(payload).idempotencyKey; + return typeof value === 'string' && value.length > 0 ? value : fallback; +} + +function readFiniteScore(payload: unknown): number { + const score = readPayloadRecord(payload).score; + if (typeof score !== 'number' || !Number.isFinite(score)) { + throw new TypeError('AIT leaderboard score must be a finite number.'); + } + return score; +} + +function readPayloadRecord(payload: unknown): Readonly> { + return isRecord(payload) ? payload : {}; +} + +function parseBridgeRequest(input: BridgeRequest): BridgeRequest { + if (typeof input.id !== 'string' || input.id.length === 0 || typeof input.method !== 'string') { + throw new TypeError('Bridge request id and method are required.'); + } + return input; +} + +function ok(request: BridgeRequest, data: unknown): BridgeResponse { + return { id: request.id, ok: true, data }; +} + +function createBridgeError( + id: string, + code: string, + message: string, + retryable = false, +): BridgeResponse { + return { id, ok: false, error: { code, message, retryable } }; +} + +function once( + callback: (...args: Arguments) => void, +): (...args: Arguments) => void { + let called = false; + return (...args) => { + if (!called) { + called = true; + callback(...args); + } + }; +} + +function nonEmptyParam(value: string | null): string | undefined { + return value === null || value.length === 0 ? undefined : value; +} + +function isAbortError(error: unknown): boolean { + return isRecord(error) && error.name === 'AbortError'; +} + +function isRecord(value: unknown): value is Readonly> { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/adapters/ait/src/index.ts b/adapters/ait/src/index.ts index 4a998122..32ffcdf3 100644 --- a/adapters/ait/src/index.ts +++ b/adapters/ait/src/index.ts @@ -19,6 +19,8 @@ import type { ShareResult, } from '@mpgd/platform'; +import { createAitLifecycleAdapter } from './lifecycle.js'; + export interface GamePlatformBridge { request(input: BridgeRequest): Promise; } @@ -96,14 +98,7 @@ export function createAitPlatformGateway(input: { submitScore: (payload) => request('leaderboard.submitScore', payload), open: (payload) => request('leaderboard.open', payload ?? {}), }, - lifecycle: { - onPause() { - return () => {}; - }, - onResume() { - return () => {}; - }, - }, + lifecycle: createAitLifecycleAdapter(), storage: { async load(payload) { return decodeBridgeStorageLoadData(await request('storage.load', payload)); @@ -113,6 +108,13 @@ export function createAitPlatformGateway(input: { }; } +export { + aitLifecyclePauseEvent, + aitLifecycleResumeEvent, + createAitLifecycleAdapter, + dispatchAitLifecycleEvent, +} from './lifecycle.js'; + function getBridge(): GamePlatformBridge | undefined { return (globalThis as { __GAME_PLATFORM_BRIDGE__?: GamePlatformBridge }).__GAME_PLATFORM_BRIDGE__; } diff --git a/adapters/ait/src/lifecycle.ts b/adapters/ait/src/lifecycle.ts new file mode 100644 index 00000000..da7d5321 --- /dev/null +++ b/adapters/ait/src/lifecycle.ts @@ -0,0 +1,45 @@ +import type { LifecycleAdapter } from '@mpgd/platform'; + +export const aitLifecyclePauseEvent = 'mpgd:ait:pause'; +export const aitLifecycleResumeEvent = 'mpgd:ait:resume'; + +export function createAitLifecycleAdapter(): LifecycleAdapter { + return { + onPause(callback) { + return subscribeToAitLifecycle('pause', callback); + }, + onResume(callback) { + return subscribeToAitLifecycle('resume', callback); + }, + }; +} + +export function dispatchAitLifecycleEvent(type: 'pause' | 'resume'): void { + globalThis.dispatchEvent?.( + new Event(type === 'pause' ? aitLifecyclePauseEvent : aitLifecycleResumeEvent), + ); +} + +function subscribeToAitLifecycle(type: 'pause' | 'resume', callback: () => void): () => void { + const eventName = type === 'pause' ? aitLifecyclePauseEvent : aitLifecycleResumeEvent; + const handleCustomEvent = (): void => callback(); + const handleVisibilityChange = (): void => { + if (globalThis.document?.visibilityState === (type === 'pause' ? 'hidden' : 'visible')) { + callback(); + } + }; + const handlePageTransition = (): void => callback(); + + globalThis.addEventListener?.(eventName, handleCustomEvent); + globalThis.document?.addEventListener('visibilitychange', handleVisibilityChange); + globalThis.addEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition); + + return () => { + globalThis.removeEventListener?.(eventName, handleCustomEvent); + globalThis.document?.removeEventListener('visibilitychange', handleVisibilityChange); + globalThis.removeEventListener?.( + type === 'pause' ? 'pagehide' : 'pageshow', + handlePageTransition, + ); + }; +} diff --git a/apps/target-ait/README.md b/apps/target-ait/README.md index db12e9c5..36fab8ba 100644 --- a/apps/target-ait/README.md +++ b/apps/target-ait/README.md @@ -6,8 +6,11 @@ Current scope: - SDK 2.x-compatible `granite.config.ts` - `ait build` script -- SDK-backed game identity through `getUserKeyForGame` +- SDK-backed anonymous game identity through `getAnonymousKey` +- persistent progress through the native `Storage` API +- SDK-backed sharing and Ads 2.0 callbacks - Game Center leaderboard bridge methods +- fail-closed commerce until a game installs verified purchase callbacks - no iframe embedding The game build is copied into `public/game` by `pnpm build:ait`. The wrapper @@ -17,14 +20,17 @@ The target build also mirrors the copied game's `assets` directory to `public/assets` so root-absolute runtime asset requests still resolve during local devtools, phone tunnel, and wrapper-web sessions without iframe embedding. -The production bridge maps the game-specific `getUserKeyForGame()` hash to -`PlatformGateway.identity.getPlayer()`. Toss app 5.232.0 or newer is required, -and the mini-app must be registered as a game. The `dev:plain` script enables a -local identity provider explicitly with `VITE_MPGD_AIT_MOCK_IDENTITY=1`; release -builds never use that fixed local player id. Sandbox results are still provided -by the Apps in Toss SDK and should be verified with a QR test in the Toss app. +The production bridge maps the stable mini-app-scoped `getAnonymousKey()` hash +to `PlatformGateway.identity.getPlayer()` and serializes gateway storage values +through the native `Storage` API. The `dev:plain` script enables a local identity +provider explicitly with `VITE_MPGD_AIT_MOCK_IDENTITY=1`; release builds never +use that fixed local player id. Game Center requires Toss app 5.221.0 or newer. +SDK results must still be verified with a QR test in the Toss app. -Purchase and rewarded-ad callbacks remain evidence only. Use the public +Purchases remain unavailable in the reference host. Rewarded Ads 2.0 must be +preloaded and only return completion after the native `userEarnedReward` event +and dismissal; dismissal by itself never grants. The returned callback envelope +remains evidence only. Use the public `@mpgd/game-services/apps-in-toss-evidence-verification` boundary with a partner-server authority before granting catalog products or rewards. The generic `createGameServicesClient().purchase()` path runs too late for the SDK @@ -32,8 +38,7 @@ callback; wire `createAppsInTossProductGrantCallback()` directly into `processProductGrant` with an abort-aware verification port that enforces the helper's 25-second ledger deadline. Construct that nominal port with `createAppsInTossProductGrantVerificationPort()`; the legacy one-argument -backend API is intentionally not accepted. The current gateway bridge does not -synthesize purchase or reward evidence from a completed result. The purchase authority is +backend API is intentionally not accepted. The purchase authority is responsible for authenticated Toss-login identity and the mTLS order-status lookup; the reward authority validates a game-issued correlation id because the official `userEarnedReward` event has no impression id. The game-owned reward @@ -44,3 +49,8 @@ See [Apps in Toss Production Evidence](../../docs/APPS_IN_TOSS_PRODUCTION_EVIDENCE.md) for pending-order restoration, product-grant completion, sandbox scenarios, and runtime-only credential requirements. + +Game targets that set `authoritativeGameServices: false` keep native identity, +storage, sharing, and Game Center while their effective config disables IAP and +ads. Set the flag to `true`, add app-owned product/ad ids, and configure the +public HTTPS game-services backend before enabling authoritative grants. diff --git a/apps/target-ait/package.json b/apps/target-ait/package.json index e0f9d3dc..e0df5fb6 100644 --- a/apps/target-ait/package.json +++ b/apps/target-ait/package.json @@ -12,14 +12,15 @@ "ait:deploy": "ait deploy --api-key \"$AIT_API_KEY\"", "devtools:mcp": "pnpm exec devtools-mcp", "devtools:mcp:mobile": "pnpm exec devtools-mcp --target=mobile", - "test": "cd ../.. && node tools/run-ttsx.mjs --project apps/target-ait/tsconfig.test.json apps/target-ait/src/aitIdentity.test.ts && node tools/run-ttsx.mjs --project apps/target-ait/tsconfig.test.json apps/target-ait/src/aitBridge.test.ts", + "test": "pnpm --dir ../../adapters/ait test", "check": "ttsc --noEmit", "lint": "ttsc --noEmit", "format": "ttsc format", "fix": "ttsc fix" }, "dependencies": { - "@apps-in-toss/web-framework": "^2.10.4" + "@apps-in-toss/web-framework": "^2.10.4", + "@mpgd/adapter-ait": "workspace:*" }, "devDependencies": { "@ait-co/devtools": "^0.1.137", diff --git a/apps/target-ait/src/aitBridge.test.ts b/apps/target-ait/src/aitBridge.test.ts deleted file mode 100644 index 013eb6a2..00000000 --- a/apps/target-ait/src/aitBridge.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -import assert from 'node:assert/strict'; - -import { installAitBridge, shareIntent, type AitShareDependencies } from './aitBridge'; -import type { BridgeResponse } from './bridgeTypes'; - -const sharePayload = { - text: 'Try today\'s challenge.', - deepLink: 'intoss://mpgd-kit/daily', - previewImageUrl: 'https://game.example/daily.png', -}; -const abortError = { name: 'AbortError' }; - -const cancelledWhileCreatingLink = await shareIntent(sharePayload, { - appName: 'mpgd-kit', - async getTossShareLink() { - throw abortError; - }, - async share() { - throw new Error('share should not run after link cancellation'); - }, -}); -assert.deepEqual(cancelledWhileCreatingLink, { status: 'cancelled' }); - -const cancelledWhileSharing = await shareIntent(sharePayload, { - appName: 'mpgd-kit', - async getTossShareLink() { - return 'https://toss.im/_ul/daily'; - }, - async share() { - throw abortError; - }, -}); -assert.deepEqual(cancelledWhileSharing, { status: 'cancelled' }); - -let warningCount = 0; -const originalConsoleWarn = console.warn; - -try { - console.warn = () => { - warningCount += 1; - }; - const unavailable = await shareIntent( - sharePayload, - { - appName: 'mpgd-kit', - async getTossShareLink() { - throw new Error('bridge unavailable'); - }, - async share() {}, - } satisfies AitShareDependencies, - ); - - assert.deepEqual(unavailable, { status: 'unavailable' }); -} finally { - console.warn = originalConsoleWarn; -} - -assert.equal(warningCount, 1, 'ordinary share errors should retain warning behavior'); - -const generatedPaths: string[] = []; -const sharedFromHttps = await shareIntent( - { - ...sharePayload, - deepLink: 'https://game.example/daily?challengeToken=signed-token#result', - }, - { - appName: 'mpgd-kit', - async getTossShareLink(path) { - generatedPaths.push(path); - return 'https://toss.im/_ul/daily'; - }, - async share() {}, - }, -); - -assert.deepEqual(sharedFromHttps, { status: 'shared' }); -assert.deepEqual(generatedPaths, [ - 'intoss://mpgd-kit/daily?challengeToken=signed-token#result', -]); - -const invalidDeepLink = await shareIntent( - { - ...sharePayload, - deepLink: 'javascript:alert(1)', - }, - { - appName: 'mpgd-kit', - async getTossShareLink() { - throw new Error('invalid deep links must not reach the Toss link provider'); - }, - async share() {}, - }, -); - -assert.deepEqual(invalidDeepLink, { status: 'unavailable' }); - -const protocolRelativeDeepLink = await shareIntent( - { - ...sharePayload, - deepLink: '//other.example/daily', - }, - { - appName: 'mpgd-kit', - async getTossShareLink() { - throw new Error('protocol-relative links must not reach the Toss link provider'); - }, - async share() {}, - }, -); - -assert.deepEqual(protocolRelativeDeepLink, { status: 'unavailable' }); - -const bridgeHost = globalThis as { - __GAME_PLATFORM_BRIDGE__?: { - request(input: unknown): Promise; - }; -}; -try { - installAitBridge(); - const installedBridge = bridgeHost.__GAME_PLATFORM_BRIDGE__; - - if (installedBridge === undefined) { - throw new Error('Expected the Apps in Toss bridge to be installed.'); - } - - const savedValue = { - progress: { coins: 7 }, - inventory: ['seed'], - }; - await installedBridge.request({ - id: 'storage-save-1', - method: 'storage.save', - payload: { key: 'mutation-isolation:v1', value: savedValue }, - meta: { - target: 'ait', - appVersion: '1.0.0', - buildId: 'ait-bridge-test', - sentAt: '2026-07-16T00:00:00.000Z', - }, - }); - savedValue.progress.coins = -1; - savedValue.inventory.push('mutated-after-save'); - - const firstLoad = await installedBridge.request({ - id: 'storage-load-1', - method: 'storage.load', - payload: { key: 'mutation-isolation:v1' }, - meta: { - target: 'ait', - appVersion: '1.0.0', - buildId: 'ait-bridge-test', - sentAt: '2026-07-16T00:00:01.000Z', - }, - }); - assert(firstLoad.ok, 'Expected the Apps in Toss storage load to succeed.'); - - const firstLoadedValue = firstLoad.data as { - readonly __mpgdBridgeProtocol: 'mpgd.storage.load.v1'; - readonly found: true; - readonly value: { - progress: { coins: number }; - inventory: string[]; - }; - }; - assert.deepEqual(firstLoadedValue, { - __mpgdBridgeProtocol: 'mpgd.storage.load.v1', - found: true, - value: { - progress: { coins: 7 }, - inventory: ['seed'], - }, - }); - firstLoadedValue.value.progress.coins = -2; - firstLoadedValue.value.inventory.push('mutated-after-load'); - - const secondLoad = await installedBridge.request({ - id: 'storage-load-2', - method: 'storage.load', - payload: { key: 'mutation-isolation:v1' }, - meta: { - target: 'ait', - appVersion: '1.0.0', - buildId: 'ait-bridge-test', - sentAt: '2026-07-16T00:00:02.000Z', - }, - }); - assert(secondLoad.ok, 'Expected the Apps in Toss storage load to succeed.'); - assert.deepEqual(secondLoad.data, { - __mpgdBridgeProtocol: 'mpgd.storage.load.v1', - found: true, - value: { - progress: { coins: 7 }, - inventory: ['seed'], - }, - }); -} finally { - Reflect.deleteProperty(bridgeHost, '__GAME_PLATFORM_BRIDGE__'); -} - -console.log('Apps in Toss bridge tests passed.'); diff --git a/apps/target-ait/src/aitBridge.ts b/apps/target-ait/src/aitBridge.ts deleted file mode 100644 index a491e830..00000000 --- a/apps/target-ait/src/aitBridge.ts +++ /dev/null @@ -1,518 +0,0 @@ -import { - getTossShareLink, - getUserKeyForGame, - isMinVersionSupported, - openGameCenterLeaderboard, - share, - submitGameCenterLeaderBoardScore, -} from '@apps-in-toss/web-framework'; - -// The AIT packager treats workspace dependencies as runtime packages. Keep this -// shared contract as a source-only type import so it is erased before packaging. -import type { - LaunchEntry, - LaunchIntent, - ShareResult, -} from '../../../packages/platform/src/index'; - -declare const __MPGD_AIT_APP_NAME__: string; - -import { - resolveAitGameIdentity, - type AitGameUserKeyProvider, -} from './aitIdentity'; -import { - bridgeStorageLoadProtocol, - type BridgeRequest, - type BridgeResponse, - type BridgeStorageLoadData, -} from './bridgeTypes'; - -const storage = new Map(); -const launchEntries = new Set([ - 'home', - 'daily', - 'practice', - 'free-play', - 'continue', - 'leaderboard', - 'friend-challenge', -]); -const defaultAitShareDependencies: AitShareDependencies = { - appName: typeof __MPGD_AIT_APP_NAME__ === 'string' ? __MPGD_AIT_APP_NAME__ : 'mpgd-kit', - getTossShareLink, - share, -}; - -export interface AitShareDependencies { - readonly appName: string; - readonly getTossShareLink: typeof getTossShareLink; - readonly share: typeof share; -} - -export interface InstallAitBridgeOptions { - readonly getUserKeyForGame?: AitGameUserKeyProvider; -} - -export function installAitBridge(options: InstallAitBridgeOptions = {}): void { - const gameUserKeyProvider = options.getUserKeyForGame ?? getUserKeyForGame; - const globalBridgeHost = globalThis as { - __GAME_PLATFORM_BRIDGE__?: { - request(input: unknown): Promise; - }; - }; - - globalBridgeHost.__GAME_PLATFORM_BRIDGE__ = { - async request(input: unknown): Promise { - const request = parseBridgeRequest(input); - - switch (request.method) { - case 'runtime.getCapabilities': - return { - id: request.id, - ok: true, - data: { - nativeIap: true, - nativeAds: true, - rewardedAds: true, - interstitialAds: true, - nativeLeaderboard: isGameCenterSupported(), - achievements: false, - cloudSave: false, - socialShare: true, - haptics: true, - localizedContent: true, - }, - }; - - case 'identity.getPlayer': { - const identity = await resolveAitGameIdentity(gameUserKeyProvider); - - if (!identity.ok) { - return { - id: request.id, - ok: false, - error: identity.error, - }; - } - - return { - id: request.id, - ok: true, - data: identity.player, - }; - } - - case 'identity.getSession': - return ok(request, await getIdentitySession(gameUserKeyProvider)); - - case 'identity.requestUpgrade': - return ok(request, { - status: 'unavailable', - reloadExpected: false, - }); - - case 'presentation.getLaunchIntent': - return ok(request, getLaunchIntent()); - - case 'presentation.requestGameSurface': - return ok(request, 'already-fullscreen'); - - case 'share.share': - return ok(request, await shareIntent(request.payload)); - - case 'share.readInboundShare': - return ok(request, readInboundShare()); - - case 'notifications.getStatus': - return ok(request, 'configuration-required'); - - case 'notifications.requestSubscription': - return ok(request, 'unavailable'); - - case 'commerce.getProducts': - return { - id: request.id, - ok: true, - data: [ - { - id: 'COINS_100', - type: 'consumable', - title: '100 Coins', - description: 'Adds 100 demo coins.', - price: { - formatted: '₩1,100', - currencyCode: 'KRW', - }, - }, - ], - }; - - case 'commerce.purchase': - return { - id: request.id, - ok: true, - data: { - status: 'completed', - transactionId: `ait-mock-${request.id}`, - entitlementIds: ['COINS_100'], - }, - }; - - case 'commerce.restore': - return { - id: request.id, - ok: true, - data: { - restoredEntitlements: [], - }, - }; - - case 'commerce.getEntitlements': - return { - id: request.id, - ok: true, - data: [], - }; - - case 'ads.preload': - return { - id: request.id, - ok: true, - data: {}, - }; - - case 'ads.showRewarded': - return { - id: request.id, - ok: true, - data: { - status: 'completed', - rewardGranted: true, - ledgerEntryId: `ait-reward-${request.id}`, - }, - }; - - case 'ads.showInterstitial': - return { - id: request.id, - ok: true, - data: { - status: 'shown', - }, - }; - - case 'leaderboard.submitScore': { - const payload = request.payload as { readonly score: number }; - const result = await submitGameCenterLeaderBoardScore({ - score: String(payload.score), - }); - - return { - id: request.id, - ok: true, - data: { - submitted: result?.statusCode === 'SUCCESS', - }, - }; - } - - case 'leaderboard.open': - await openGameCenterLeaderboard(); - return { - id: request.id, - ok: true, - data: {}, - }; - - case 'storage.load': { - const payload = request.payload as { readonly key?: string }; - const value = payload.key === undefined ? undefined : storage.get(payload.key); - - return { - id: request.id, - ok: true, - data: - value === undefined - ? ({ - __mpgdBridgeProtocol: bridgeStorageLoadProtocol, - found: false, - } satisfies BridgeStorageLoadData) - : ({ - __mpgdBridgeProtocol: bridgeStorageLoadProtocol, - found: true, - value: cloneJsonValue(value), - } satisfies BridgeStorageLoadData), - }; - } - - case 'storage.save': { - const payload = request.payload as { readonly key?: string; readonly value?: unknown }; - - if (payload.key !== undefined) { - storage.set(payload.key, cloneJsonValue(payload.value)); - } - - return { - id: request.id, - ok: true, - data: {}, - }; - } - - default: - return createBridgeError( - request.id, - 'UNSUPPORTED_METHOD', - `Unsupported AIT bridge method: ${request.method}`, - ); - } - }, - }; -} - -function cloneJsonValue(value: unknown): unknown { - const serialized = JSON.stringify(value); - - if (serialized === undefined) { - throw new TypeError('Storage values must be JSON-serializable.'); - } - - return JSON.parse(serialized) as unknown; -} - -function parseBridgeRequest(input: unknown): BridgeRequest { - if (typeof input !== 'object' || input === null) { - throw new TypeError('Bridge request must be an object.'); - } - - const request = input as Partial; - - if (typeof request.id !== 'string' || typeof request.method !== 'string') { - throw new TypeError('Bridge request id and method are required.'); - } - - return request as BridgeRequest; -} - -function createBridgeError( - id: string, - code: string, - message: string, - retryable = false, -): BridgeResponse { - return { - id, - ok: false, - error: { - code, - message, - retryable, - }, - }; -} - -function ok(request: BridgeRequest, data: unknown): BridgeResponse { - return { - id: request.id, - ok: true, - data, - }; -} - -async function getIdentitySession( - gameUserKeyProvider: AitGameUserKeyProvider, -): Promise<{ - readonly identityLevel: 'guest' | 'platform-anonymous'; - readonly playerId?: string; - readonly trustLevel: 'local' | 'platform-asserted'; -}> { - const identity = await resolveAitGameIdentity(gameUserKeyProvider); - - if (!identity.ok) { - return { - identityLevel: 'guest', - trustLevel: 'local', - }; - } - - return { - identityLevel: 'platform-anonymous', - playerId: identity.player.playerId, - trustLevel: 'platform-asserted', - }; -} - -function getLaunchIntent(): LaunchIntent { - const params = inboundSearchParams(); - const challengeToken = nonEmptyParam(params.get('challengeToken')); - const puzzleId = nonEmptyParam(params.get('puzzleId')); - const requestedEntry = nonEmptyParam(params.get('entry')); - let entry: LaunchEntry; - - if ( - requestedEntry !== undefined - && launchEntries.has(requestedEntry as LaunchEntry) - ) { - entry = requestedEntry as LaunchEntry; - } else if (challengeToken === undefined) { - entry = 'home'; - } else { - entry = 'friend-challenge'; - } - - return { - entry, - ...(puzzleId === undefined ? {} : { puzzleId }), - ...(challengeToken === undefined ? {} : { referralToken: challengeToken }), - }; -} - -export async function shareIntent( - payload: unknown, - dependencies: AitShareDependencies = defaultAitShareDependencies, -): Promise { - if (typeof payload !== 'object' || payload === null) { - return { status: 'unavailable' }; - } - - const intent = payload as { - readonly text?: unknown; - readonly deepLink?: unknown; - readonly previewImageUrl?: unknown; - }; - - if (typeof intent.text !== 'string' || typeof intent.deepLink !== 'string') { - return { status: 'unavailable' }; - } - - const aitDeepLink = toAitDeepLink(intent.deepLink, dependencies.appName); - - if (aitDeepLink === undefined) { - return { status: 'unavailable' }; - } - - try { - const tossLink = await dependencies.getTossShareLink( - aitDeepLink, - typeof intent.previewImageUrl === 'string' && intent.previewImageUrl.startsWith('https://') - ? intent.previewImageUrl - : undefined, - ); - await dependencies.share({ message: `${intent.text}\n${tossLink}` }); - return { status: 'shared' }; - } catch (error) { - if (isAbortError(error)) { - return { status: 'cancelled' }; - } - - console.warn('AIT share failed; returning unavailable.', error); - return { status: 'unavailable' }; - } -} - -function readInboundShare(): { - readonly puzzleId?: string; - readonly challengeToken?: string; -} | null { - const params = inboundSearchParams(); - const puzzleId = nonEmptyParam(params.get('puzzleId')); - const challengeToken = nonEmptyParam(params.get('challengeToken')); - - return puzzleId === undefined && challengeToken === undefined - ? null - : { - ...(puzzleId === undefined ? {} : { puzzleId }), - ...(challengeToken === undefined ? {} : { challengeToken }), - }; -} - -function inboundSearchParams(): URLSearchParams { - const params = new URLSearchParams(globalThis.location?.search ?? ''); - const nested = params.get('queryParams'); - - if (nested === null) { - return params; - } - - try { - const parsed = JSON.parse(nested) as unknown; - - if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { - for (const [key, value] of Object.entries(parsed)) { - if (typeof value === 'string' && !params.has(key)) { - params.set(key, value); - } - } - } - } catch { - // Deep-link payloads are untrusted; malformed nested query data is ignored. - } - - return params; -} - -function nonEmptyParam(value: string | null): string | undefined { - return value === null || value.length === 0 ? undefined : value; -} - -function toAitDeepLink(input: string, appNameInput: string): string | undefined { - const appName = appNameInput.trim(); - - if (!/^[A-Za-z0-9-]+$/u.test(appName)) { - return undefined; - } - - if (input.startsWith('//')) { - return undefined; - } - - if (input.startsWith('/')) { - const baseUrl = new URL('https://mpgd.invalid'); - const parsed = new URL(input, baseUrl); - - if (parsed.origin !== baseUrl.origin) { - return undefined; - } - - return `intoss://${appName}${parsed.pathname}${parsed.search}${parsed.hash}`; - } - - let parsed: URL; - - try { - parsed = new URL(input); - } catch { - return undefined; - } - - if (parsed.protocol === 'intoss:') { - return parsed.hostname === appName ? input : undefined; - } - - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - return undefined; - } - - return `intoss://${appName}${parsed.pathname}${parsed.search}${parsed.hash}`; -} - -function isAbortError(error: unknown): boolean { - try { - return ( - typeof error === 'object' - && error !== null - && 'name' in error - && error.name === 'AbortError' - ); - } catch { - return false; - } -} - -function isGameCenterSupported(): boolean { - return isMinVersionSupported({ - android: '5.221.0', - ios: '5.221.0', - }); -} diff --git a/apps/target-ait/src/aitIdentity.test.ts b/apps/target-ait/src/aitIdentity.test.ts deleted file mode 100644 index 69a85cbf..00000000 --- a/apps/target-ait/src/aitIdentity.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import assert from 'node:assert/strict'; - -import { resolveAitGameIdentity } from './aitIdentity'; - -const player = await resolveAitGameIdentity(async () => ({ - type: 'HASH', - hash: 'ait-game-user-hash', -})); - -assert.deepEqual(player, { - ok: true, - player: { - playerId: 'ait-game-user-hash', - }, -}); - -const trimmedPlayer = await resolveAitGameIdentity(async () => ({ - type: 'HASH', - hash: ' ait-game-user-hash ', -})); -assert.deepEqual(trimmedPlayer, { - ok: true, - player: { - playerId: 'ait-game-user-hash', - }, -}); - -const unsupported = await resolveAitGameIdentity(async () => undefined); -assert.deepEqual(unsupported, { - ok: false, - error: { - code: 'AIT_GAME_IDENTITY_UNSUPPORTED', - message: 'Apps in Toss game identity requires Toss app 5.232.0 or newer.', - retryable: false, - }, -}); - -const invalidCategory = await resolveAitGameIdentity(async () => 'INVALID_CATEGORY'); -assert.equal(invalidCategory.ok, false); -assert.equal( - invalidCategory.ok ? undefined : invalidCategory.error.code, - 'AIT_GAME_IDENTITY_INVALID_CATEGORY', -); - -const sdkFailure = await resolveAitGameIdentity(async () => 'ERROR'); -assert.equal(sdkFailure.ok, false); -assert.equal(sdkFailure.ok ? undefined : sdkFailure.error.retryable, true); - -for (const invalidResult of [ - null, - {}, - { type: 'HASH' }, - { type: 'HASH', hash: '' }, - { type: 'HASH', hash: ' ' }, - { type: 'OTHER', hash: 'ait-game-user-hash' }, -]) { - const invalid = await resolveAitGameIdentity(async () => invalidResult); - assert.equal(invalid.ok, false); - assert.equal( - invalid.ok ? undefined : invalid.error.code, - 'AIT_GAME_IDENTITY_INVALID_RESPONSE', - ); -} - -const thrown = await resolveAitGameIdentity(async () => { - throw new Error('bridge unavailable'); -}); -assert.deepEqual(thrown, { - ok: false, - error: { - code: 'AIT_GAME_IDENTITY_REQUEST_FAILED', - message: 'Apps in Toss game identity request failed: bridge unavailable', - retryable: true, - }, -}); - -console.log('Apps in Toss game identity tests passed.'); diff --git a/apps/target-ait/src/aitIdentity.ts b/apps/target-ait/src/aitIdentity.ts deleted file mode 100644 index f4b10ced..00000000 --- a/apps/target-ait/src/aitIdentity.ts +++ /dev/null @@ -1,103 +0,0 @@ -export type AitGameUserKeyProvider = () => Promise; - -const AIT_SDK_INVALID_CATEGORY = 'INVALID_CATEGORY' as const; -const AIT_SDK_ERROR = 'ERROR' as const; - -export type AitGameIdentityResolution = - | { - readonly ok: true; - readonly player: { - readonly playerId: string; - }; - } - | { - readonly ok: false; - readonly error: { - readonly code: string; - readonly message: string; - readonly retryable: boolean; - }; - }; - -export async function resolveAitGameIdentity( - getUserKeyForGame: AitGameUserKeyProvider, -): Promise { - let result: unknown; - - try { - result = await getUserKeyForGame(); - } catch (error) { - return failure( - 'AIT_GAME_IDENTITY_REQUEST_FAILED', - `Apps in Toss game identity request failed: ${errorMessage(error)}`, - true, - ); - } - - if (result === undefined) { - return failure( - 'AIT_GAME_IDENTITY_UNSUPPORTED', - 'Apps in Toss game identity requires Toss app 5.232.0 or newer.', - false, - ); - } - - if (result === AIT_SDK_INVALID_CATEGORY) { - return failure( - 'AIT_GAME_IDENTITY_INVALID_CATEGORY', - 'Apps in Toss game identity is only available to game mini-apps.', - false, - ); - } - - if (result === AIT_SDK_ERROR) { - return failure( - 'AIT_GAME_IDENTITY_FAILED', - 'Apps in Toss could not resolve the game user identity.', - true, - ); - } - - if (!isGameUserHash(result)) { - return failure( - 'AIT_GAME_IDENTITY_INVALID_RESPONSE', - 'Apps in Toss returned an invalid game user identity.', - false, - ); - } - - return { - ok: true, - player: { - playerId: result.hash.trim(), - }, - }; -} - -function isGameUserHash(input: unknown): input is { readonly type: 'HASH'; readonly hash: string } { - if (typeof input !== 'object' || input === null) { - return false; - } - - const value = input as { readonly type?: unknown; readonly hash?: unknown }; - return value.type === 'HASH' && typeof value.hash === 'string' && value.hash.trim().length > 0; -} - -function failure( - code: string, - message: string, - retryable: boolean, -): AitGameIdentityResolution { - return { - ok: false, - error: { - code, - message, - retryable, - }, - }; -} - -function errorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} diff --git a/apps/target-ait/src/bridgeTypes.ts b/apps/target-ait/src/bridgeTypes.ts deleted file mode 100644 index 5c451e79..00000000 --- a/apps/target-ait/src/bridgeTypes.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface BridgeRequest { - readonly id: string; - readonly method: string; - readonly payload: unknown; - readonly meta?: { - readonly target?: string; - readonly appVersion?: string; - readonly buildId?: string; - readonly sentAt?: string; - }; -} - -export type BridgeResponse = - | { - readonly id: string; - readonly ok: true; - readonly data: unknown; - } - | { - readonly id: string; - readonly ok: false; - readonly error: { - readonly code: string; - readonly message: string; - readonly retryable: boolean; - }; - }; - -export const bridgeStorageLoadProtocol = 'mpgd.storage.load.v1' as const; - -export type BridgeStorageLoadData = - | { - readonly __mpgdBridgeProtocol: typeof bridgeStorageLoadProtocol; - readonly found: false; - } - | { - readonly __mpgdBridgeProtocol: typeof bridgeStorageLoadProtocol; - readonly found: true; - readonly value: unknown; - }; diff --git a/apps/target-ait/src/main.ts b/apps/target-ait/src/main.ts index 46e9463b..7137f145 100644 --- a/apps/target-ait/src/main.ts +++ b/apps/target-ait/src/main.ts @@ -1,5 +1,11 @@ import './styles.css'; -import { installAitBridge, type InstallAitBridgeOptions } from './aitBridge'; +import { installAitHostBridge, type InstallAitHostBridgeOptions } from '@mpgd/adapter-ait/host'; + +declare const __MPGD_AIT_APP_NAME__: string; +declare const __MPGD_AIT_AD_GROUP_IDS__: Readonly>; +declare const __MPGD_AIT_AD_PLACEMENT_TYPES__: Readonly< + Record +>; const gameBundleBasePath = '/game/'; const gameBundleIndexPath = `${gameBundleBasePath}index.html`; @@ -14,7 +20,12 @@ const safeScriptAttributes = new Set([ 'type', ]); -installAitBridge(identityBridgeOptions()); +installAitHostBridge({ + appName: __MPGD_AIT_APP_NAME__, + adGroupIds: __MPGD_AIT_AD_GROUP_IDS__, + adPlacementTypes: __MPGD_AIT_AD_PLACEMENT_TYPES__, + ...identityBridgeOptions(), +}); const app = document.querySelector('#app'); @@ -22,16 +33,18 @@ if (app !== null) { void loadGameBundle(app); } -function identityBridgeOptions(): InstallAitBridgeOptions { +function identityBridgeOptions(): Pick { if (import.meta.env.VITE_MPGD_AIT_MOCK_IDENTITY !== '1') { return {}; } return { - getUserKeyForGame: async () => ({ - type: 'HASH', - hash: 'ait-local-player', - }), + dependencies: { + identityProvider: async () => ({ + type: 'HASH', + hash: 'ait-local-player', + }), + }, }; } @@ -213,7 +226,9 @@ function rewriteAndValidateGamePath(path: string, originalUrl: string): string { : path; const resolvedUrl = new URL(normalizedPath, `${window.location.origin}${gameBundleBasePath}`); - if (resolvedUrl.origin !== window.location.origin || !resolvedUrl.pathname.startsWith(gameBundleBasePath)) { + if (resolvedUrl.origin !== window.location.origin || !resolvedUrl.pathname.startsWith( + gameBundleBasePath, + )) { throw new Error(`Game bundle asset URL escapes the bundle base path: ${originalUrl}`); } @@ -331,7 +346,9 @@ function isGameBundleRuntimeError(event: ErrorEvent): boolean { try { const sourceUrl = new URL(event.filename, window.location.href); - return sourceUrl.origin === window.location.origin && sourceUrl.pathname.startsWith(gameBundleBasePath); + return sourceUrl.origin === window.location.origin && sourceUrl.pathname.startsWith( + gameBundleBasePath, + ); } catch { return false; } diff --git a/apps/target-ait/tsconfig.bundle.json b/apps/target-ait/tsconfig.bundle.json index 72868e83..55268543 100644 --- a/apps/target-ait/tsconfig.bundle.json +++ b/apps/target-ait/tsconfig.bundle.json @@ -8,6 +8,8 @@ "src/**/*.ts", "vite.config.ts", "granite.config.ts", + "../../adapters/ait/src/host.ts", + "../../adapters/ait/src/lifecycle.ts", "../../packages/bridge/src/**/*.ts" ] } diff --git a/apps/target-ait/tsconfig.test.json b/apps/target-ait/tsconfig.test.json index 341b48c5..becbd9ad 100644 --- a/apps/target-ait/tsconfig.test.json +++ b/apps/target-ait/tsconfig.test.json @@ -6,10 +6,8 @@ "rootDir": "../.." }, "include": [ - "src/aitBridge.ts", - "src/aitBridge.test.ts", - "src/aitIdentity.ts", - "src/aitIdentity.test.ts", - "src/bridgeTypes.ts" + "../../adapters/ait/src/host.ts", + "../../adapters/ait/src/host.test.ts", + "../../adapters/ait/src/lifecycle.ts" ] } diff --git a/apps/target-ait/vite.config.ts b/apps/target-ait/vite.config.ts index 39c18b20..f07ba334 100644 --- a/apps/target-ait/vite.config.ts +++ b/apps/target-ait/vite.config.ts @@ -1,3 +1,5 @@ +import { readFileSync } from 'node:fs'; + import aitDevtools from '@ait-co/devtools/unplugin'; import ttsc from '@ttsc/unplugin/vite'; import { defineConfig } from 'vite'; @@ -9,10 +11,13 @@ const aitDevtoolsTunnel = isTruthyEnv(process.env.AIT_TUNNEL) ? { cdp: isTruthyEnv(process.env.AIT_TUNNEL_CDP) } : false; const aitAppName = process.env.MPGD_AIT_APP_NAME?.trim() || 'mpgd-kit'; +const aitAdConfig = readAitAdConfig(process.env.MPGD_AD_PLACEMENTS_FILE); export default defineConfig(({ command, isPreview }) => ({ define: { __MPGD_AIT_APP_NAME__: JSON.stringify(aitAppName), + __MPGD_AIT_AD_GROUP_IDS__: JSON.stringify(aitAdConfig.adGroupIds), + __MPGD_AIT_AD_PLACEMENT_TYPES__: JSON.stringify(aitAdConfig.adPlacementTypes), }, plugins: [ ...(command === 'serve' && !isPreview && process.env.MPGD_AIT_DEVTOOLS !== '0' @@ -29,3 +34,47 @@ export default defineConfig(({ command, isPreview }) => ({ emptyOutDir: true, }, })); + +function readAitAdConfig(path: string | undefined): { + readonly adGroupIds: Readonly>; + readonly adPlacementTypes: Readonly>; +} { + if (path === undefined || path.trim().length === 0) { + return { adGroupIds: {}, adPlacementTypes: {} }; + } + + const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + + if (!isRecord(parsed) || !Array.isArray(parsed.placements)) { + throw new Error(`AIT ad placements file is invalid: ${path}`); + } + + const adGroupIds: Record = {}; + const adPlacementTypes: Record = {}; + + for (const placement of parsed.placements) { + if ( + !isRecord(placement) + || typeof placement.id !== 'string' + || (placement.type !== 'rewarded' && placement.type !== 'interstitial') + ) { + throw new Error(`AIT ad placement entry is invalid: ${path}`); + } + + const platformPlacementIds = placement.platformPlacementIds; + const adGroupId = isRecord(platformPlacementIds) && typeof platformPlacementIds.ait === 'string' + ? platformPlacementIds.ait.trim() + : ''; + + if (adGroupId.length > 0) { + adGroupIds[placement.id] = adGroupId; + adPlacementTypes[placement.id] = placement.type; + } + } + + return { adGroupIds, adPlacementTypes }; +} + +function isRecord(value: unknown): value is Readonly> { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/examples/phaser-starter/mpgd.targets.json b/examples/phaser-starter/mpgd.targets.json index e1080a12..fd94f859 100644 --- a/examples/phaser-starter/mpgd.targets.json +++ b/examples/phaser-starter/mpgd.targets.json @@ -44,6 +44,7 @@ "gameApp": ".", "wrapperApp": "../../apps/target-ait", "adapter": "ait", + "authoritativeGameServices": false, "icon": { "profile": "ait" }, "webDir": "../../apps/target-ait/public/game", "artifact": ".ait" diff --git a/packages/cli/src/production-target-readiness.ts b/packages/cli/src/production-target-readiness.ts index a2165905..64403c18 100644 --- a/packages/cli/src/production-target-readiness.ts +++ b/packages/cli/src/production-target-readiness.ts @@ -98,7 +98,9 @@ export function assertProductionTargetReadiness( } } - assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target); + if (targetConfig.authoritativeGameServices !== false) { + assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target); + } } function readTargets(targetsFile: string): Record { diff --git a/packages/cli/templates/phaser-game/README.md b/packages/cli/templates/phaser-game/README.md index 6beec8b2..9a521478 100644 --- a/packages/cli/templates/phaser-game/README.md +++ b/packages/cli/templates/phaser-game/README.md @@ -282,6 +282,12 @@ game uses permission-gated APIs such as clipboard or geolocation, declare the matching permissions in the game-owned Apps in Toss `granite.config.ts` before submission. +The generated AIT target starts with `authoritativeGameServices: false`. Native +anonymous identity, persistent Storage, sharing, and Game Center remain +available, while IAP and ads are removed from the effective game configuration. +Only switch the flag to `true` after adding app-owned catalog/ad ids and a public +HTTPS game-services backend that verifies purchase or reward evidence. + ## Target Ownership Notes - Browser preview is fully game-owned and writes to `artifacts/web-preview`. diff --git a/packages/cli/templates/phaser-game/mpgd.targets.json b/packages/cli/templates/phaser-game/mpgd.targets.json index 0121cbfa..e5308c5e 100644 --- a/packages/cli/templates/phaser-game/mpgd.targets.json +++ b/packages/cli/templates/phaser-game/mpgd.targets.json @@ -44,6 +44,7 @@ "gameApp": ".", "wrapperApp": "${MPGD_KIT_PATH}/apps/target-ait", "adapter": "ait", + "authoritativeGameServices": false, "icon": { "profile": "ait" }, "webDir": "${MPGD_KIT_PATH}/apps/target-ait/public/game", "artifact": ".ait" diff --git a/packages/target-config/src/effective.ts b/packages/target-config/src/effective.ts index d36f2804..53274a4b 100644 --- a/packages/target-config/src/effective.ts +++ b/packages/target-config/src/effective.ts @@ -26,6 +26,7 @@ export type EffectiveAvailabilityReason = export interface EffectivePlatformTargetMetadata { readonly kind: string; readonly adapter: string; + readonly authoritativeGameServices?: boolean; readonly artifact?: string; readonly output?: string; readonly webDir?: string; @@ -130,15 +131,16 @@ export const defaultLeaderboardId = 'default'; export function createEffectiveTargetConfig( input: CreateEffectiveTargetConfigInput, ): EffectiveTargetConfig { + const config = resolveAuthoritativeGameServicesConfig(input.config, input.platformTarget); const products = input.catalog.products.map((product) => - createEffectiveProductConfig(input.target, input.config, product), + createEffectiveProductConfig(input.target, config, product), ); const placements = input.adPlacements.placements.map((placement) => - createEffectiveAdPlacementConfig(input.target, input.config, placement), + createEffectiveAdPlacementConfig(input.target, config, placement), ); - const leaderboardEnabled = input.config.features.leaderboard; + const leaderboardEnabled = config.features.leaderboard; const integrations = normalizeTargetIntegrationConfig({ - ...input.config.integrations, + ...config.integrations, ...input.platformTarget?.integrations, }); @@ -149,12 +151,12 @@ export function createEffectiveTargetConfig( adPlacements: input.adPlacements.version, }), target: input.target, - runtime: input.config.runtime, - release: input.config.release, - features: input.config.features, - capabilities: input.config.capabilities, + runtime: config.runtime, + release: config.release, + features: config.features, + capabilities: config.capabilities, integrations, - policy: input.config.policy, + policy: config.policy, sources: { targetConfig: input.targetConfigVersion, productCatalog: input.catalog.version, @@ -167,27 +169,51 @@ export function createEffectiveTargetConfig( }), }, monetization: { - iap: input.config.monetization.iap, + iap: config.monetization.iap, products, }, ads: { - rewardedAds: input.config.monetization.rewardedAds, - interstitialAds: input.config.monetization.interstitialAds, + rewardedAds: config.monetization.rewardedAds, + interstitialAds: config.monetization.interstitialAds, placements, }, leaderboard: { - native: input.config.leaderboard.native, + native: config.leaderboard.native, enabled: leaderboardEnabled, reason: leaderboardEnabled ? 'available' : 'target-disabled', ...(leaderboardEnabled ? { defaultLeaderboardId } : {}), }, storage: { - support: input.config.capabilities.storage, - enabled: input.config.capabilities.storage !== 'none', + support: config.capabilities.storage, + enabled: config.capabilities.storage !== 'none', }, localization: { - enabled: input.config.capabilities.localization, - fallbackLocale: input.config.localization.fallbackLocale, + enabled: config.capabilities.localization, + fallbackLocale: config.localization.fallbackLocale, + }, + }; +} + +function resolveAuthoritativeGameServicesConfig( + config: TargetConfig, + platformTarget: EffectivePlatformTargetMetadata | undefined, +): TargetConfig { + if (platformTarget?.authoritativeGameServices !== false) { + return config; + } + + return { + ...config, + features: { + ...config.features, + iap: false, + rewardedAds: false, + interstitialAds: false, + }, + monetization: { + iap: false, + rewardedAds: false, + interstitialAds: false, }, }; } diff --git a/packages/target-config/src/runtime.ts b/packages/target-config/src/runtime.ts index 8d97d7a2..e4f05396 100644 --- a/packages/target-config/src/runtime.ts +++ b/packages/target-config/src/runtime.ts @@ -340,16 +340,34 @@ export function createTargetRuntimeSnapshot(input: { readonly gateway?: PlatformGateway; }): TargetRuntimeSnapshot { const configTarget = input.configTarget ?? targetConfigKeyForPlatform(input.target); + const availabilityConfig = input.effectiveConfig === undefined + ? input.config + : { + ...input.config, + features: input.effectiveConfig.features, + }; const features = { - iap: getFeatureAvailability('iap', input.config, input.capabilities), - rewardedAds: getFeatureAvailability('rewardedAds', input.config, input.capabilities), + iap: getFeatureAvailability('iap', availabilityConfig, input.capabilities), + rewardedAds: getFeatureAvailability( + 'rewardedAds', + availabilityConfig, + input.capabilities, + ), interstitialAds: getFeatureAvailability( 'interstitialAds', - input.config, + availabilityConfig, + input.capabilities, + ), + leaderboard: getFeatureAvailability( + 'leaderboard', + availabilityConfig, + input.capabilities, + ), + localization: getFeatureAvailability( + 'localization', + availabilityConfig, input.capabilities, ), - leaderboard: getFeatureAvailability('leaderboard', input.config, input.capabilities), - localization: getFeatureAvailability('localization', input.config, input.capabilities), } satisfies Record; const integrationConfig = normalizeTargetIntegrationConfig( input.effectiveConfig?.integrations ?? input.config.integrations, @@ -396,6 +414,12 @@ export function withTargetAvailability( config: TargetConfig, options: TargetAvailabilityOptions = {}, ): TargetConfiguredGateway { + const availabilityConfig = options.effectiveConfig === undefined + ? config + : { + ...config, + features: options.effectiveConfig.features, + }; const getGatewayCapabilities = (): Promise => ( gateway.getCapabilities() ); @@ -442,7 +466,7 @@ export function withTargetAvailability( }; const notifications = notificationsAvailable ? gatewayNotifications : undefined; const isIapAvailable = async (): Promise => ( - config.features.iap && (await getGatewayCapabilities()).nativeIap + availabilityConfig.features.iap && (await getGatewayCapabilities()).nativeIap ); const isAdPlacementAllowed = ( placementId: string, @@ -455,22 +479,25 @@ export function withTargetAvailability( } return expectedType === 'rewarded' - ? config.features.rewardedAds - : config.features.interstitialAds; + ? availabilityConfig.features.rewardedAds + : availabilityConfig.features.interstitialAds; }; const canPreloadAdPlacement = (placementId: string): boolean => { const actualType = options.resolveAdPlacementType?.(placementId); if (actualType === 'rewarded') { - return config.features.rewardedAds; + return availabilityConfig.features.rewardedAds; } if (actualType === 'interstitial') { - return config.features.interstitialAds; + return availabilityConfig.features.interstitialAds; } - return config.features.rewardedAds || config.features.interstitialAds; + return ( + availabilityConfig.features.rewardedAds || + availabilityConfig.features.interstitialAds + ); }; return { @@ -492,13 +519,19 @@ export function withTargetAvailability( ...(options.effectiveConfig === undefined ? {} : { effectiveConfig: options.effectiveConfig }), - capabilities: applyTargetConfigToCapabilities(await getGatewayCapabilities(), config), + capabilities: applyTargetConfigToCapabilities( + await getGatewayCapabilities(), + availabilityConfig, + ), adPlacements: options.adPlacements ?? [], gateway, }); }, async getCapabilities() { - return applyTargetConfigToCapabilities(await getGatewayCapabilities(), config); + return applyTargetConfigToCapabilities( + await getGatewayCapabilities(), + availabilityConfig, + ); }, commerce: { async getProducts() { @@ -556,7 +589,7 @@ export function withTargetAvailability( return gateway.ads.showInterstitial(input); }, }, - leaderboard: config.features.leaderboard + leaderboard: availabilityConfig.features.leaderboard ? gateway.leaderboard : { async submitScore() { diff --git a/packages/target-config/targets.json b/packages/target-config/targets.json index 84f203c7..f644bd4f 100644 --- a/packages/target-config/targets.json +++ b/packages/target-config/targets.json @@ -226,7 +226,7 @@ "localization": true }, "capabilities": { - "storage": "none", + "storage": "native", "localization": true }, "localization": { diff --git a/packages/target-config/test/target-config-smoke.ts b/packages/target-config/test/target-config-smoke.ts index d5227db6..9f015333 100644 --- a/packages/target-config/test/target-config-smoke.ts +++ b/packages/target-config/test/target-config-smoke.ts @@ -470,6 +470,18 @@ const androidTargetOverrideEffectiveConfig = createEffectiveTargetConfig({ }, }, }); +const androidWithoutAuthorityEffectiveConfig = createEffectiveTargetConfig({ + target: 'android', + targetConfigVersion: targetConfigMatrix.version, + config: androidConfig, + catalog: productCatalog, + adPlacements, + platformTarget: { + kind: 'capacitor-android', + adapter: 'capacitor', + authoritativeGameServices: false, + }, +}); const androidTargetOverrideGateway = withTargetAvailability(gateway, androidConfig, { effectiveConfig: androidTargetOverrideEffectiveConfig, resolveAdPlacementType, @@ -482,6 +494,23 @@ const androidTargetOverrideRuntime = await androidTargetOverrideGateway.getTarge const androidRuntime = await androidGateway.getTargetRuntime(); assertEqual(androidRuntime.presentationMode, 'fullscreen'); +assertEqual(androidWithoutAuthorityEffectiveConfig.features.iap, false); +assertEqual(androidWithoutAuthorityEffectiveConfig.features.rewardedAds, false); +assertEqual(androidWithoutAuthorityEffectiveConfig.features.interstitialAds, false); +assertEqual(androidWithoutAuthorityEffectiveConfig.monetization.iap, false); +assertEqual(androidWithoutAuthorityEffectiveConfig.ads.rewardedAds, false); +assertEqual(androidWithoutAuthorityEffectiveConfig.ads.interstitialAds, false); +assertEqual( + getEffectiveProductConfig(androidWithoutAuthorityEffectiveConfig, 'COINS_100')?.reason, + 'target-disabled', +); +assertEqual( + getEffectiveAdPlacementConfig( + androidWithoutAuthorityEffectiveConfig, + 'CONTINUE_AFTER_FAIL', + )?.reason, + 'target-disabled', +); assertEqual(androidEffectiveConfig.integrations.identityUpgrade, 'available'); assertDeepEqual(androidTargetOverrideEffectiveConfig.integrations, { identityUpgrade: 'available', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f19650d7..0dda843d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: specifier: workspace:* version: link:../../packages/platform devDependencies: + '@apps-in-toss/web-framework': + specifier: ^2.10.4 + version: 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) ttsc: specifier: 0.18.4 version: 0.18.4 @@ -107,7 +110,7 @@ importers: version: 7.0.2 vitest: specifier: ^4.0.0 - version: 4.1.9(@types/node@24.13.2)(jsdom@24.1.0)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.9(@types/node@24.13.2)(jsdom@24.1.0)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) adapters/browser: dependencies: @@ -263,14 +266,17 @@ importers: dependencies: '@apps-in-toss/web-framework': specifier: ^2.10.4 - version: 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(e611cf729dbb950b6f4fac8c975cae5e))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + version: 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + '@mpgd/adapter-ait': + specifier: workspace:* + version: link:../../adapters/ait devDependencies: '@ait-co/devtools': specifier: ^0.1.137 - version: 0.1.137(e37433a29189582f9e5ed8d493f25fe2) + version: 0.1.137(197a08e79f5a0dea7c9c567c0dba765e) '@apps-in-toss/cli': specifier: ^2.10.4 - version: 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + version: 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) '@ttsc/unplugin': specifier: 0.18.4 version: 0.18.4 @@ -285,7 +291,7 @@ importers: version: 7.0.2 vite: specifier: ^8.1.3 - version: 8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) + version: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) apps/target-devvit: dependencies: @@ -9819,7 +9825,7 @@ packages: snapshots: - '@ait-co/devtools@0.1.137(e37433a29189582f9e5ed8d493f25fe2)': + '@ait-co/devtools@0.1.137(197a08e79f5a0dea7c9c567c0dba765e)': dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@vitest/expect': 4.1.9 @@ -9831,10 +9837,10 @@ snapshots: esbuild: 0.28.1 qrcode: 1.5.4 qrcode-terminal: 0.12.0 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) ws: 8.21.0 optionalDependencies: - '@apps-in-toss/web-framework': 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(e611cf729dbb950b6f4fac8c975cae5e))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + '@apps-in-toss/web-framework': 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) transitivePeerDependencies: - '@cfworker/json-schema' - '@farmfe/core' @@ -9865,10 +9871,19 @@ snapshots: '@apps-in-toss/ait-format-proto': 1.0.0 fflate: 0.8.3 - '@apps-in-toss/analytics@2.10.4(31182b0508a682442d7b825ff4d08582)': + '@apps-in-toss/analytics@2.10.4(517315b53d0d70e3b66b55bebacce804)': + dependencies: + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@types/react': 19.2.17 + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + + '@apps-in-toss/analytics@2.10.4(9c282825ce6d3d717af57bc67c854205)': dependencies: '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) '@types/react': 19.2.17 brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) react: 18.2.0 @@ -9876,10 +9891,50 @@ snapshots: '@apps-in-toss/bridge-core@2.10.4': {} - '@apps-in-toss/cli@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + '@apps-in-toss/cli@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + dependencies: + '@apps-in-toss/ait-format': 1.0.0 + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@clack/prompts': 0.10.1 + '@granite-js/utils': 1.0.20 + '@sentry/cli': 2.58.6 + clipanion: 4.0.0-rc.4(typanion@3.14.0) + dedent: 1.7.2 + execa: 9.3.0 + fast-glob: 3.3.3 + jscodeshift: 17.3.0(@babel/preset-env@7.28.5(@babel/core@7.23.9)) + source-map: 0.8.0-beta.0 + unzipper: 0.12.5 + uuidv7: 1.2.1 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - '@dotlottie/react-player' + - '@lottiefiles/react-lottie-player' + - '@react-navigation/native' + - '@swc/helpers' + - '@types/node' + - babel-plugin-macros + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react + - react-native + - react-native-b4a + - react-native-safe-area-context + - react-native-screens + - react-native-windows + - supports-color + - typanion + - typescript + - utf-8-validate + + '@apps-in-toss/cli@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) '@clack/prompts': 0.10.1 '@granite-js/utils': 1.0.20 @@ -9916,10 +9971,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/cli@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + '@apps-in-toss/cli@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) '@clack/prompts': 0.10.1 '@granite-js/utils': 1.0.20 @@ -9956,17 +10011,17 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/framework@2.10.4(048871f9d8af3dec25a486cf5e3b350e)': + '@apps-in-toss/framework@2.10.4(db61d165d46a8a47fb7719835ebd892e)': dependencies: - '@apps-in-toss/analytics': 2.10.4(31182b0508a682442d7b825ff4d08582) - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/native-modules': 2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/analytics': 2.10.4(9c282825ce6d3d717af57bc67c854205) + '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/native-modules': 2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@apps-in-toss/types': 2.10.4 '@apps-in-toss/user-scripts': 2.10.4 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) - '@toss/tds-react-native': 2.0.3(e611cf729dbb950b6f4fac8c975cae5e) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@toss/tds-react-native': 2.0.3(febdec7493c1b8afdc2c391c9f1f45ad) '@types/react': 19.2.17 brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) es-hangul: 2.3.8 @@ -9994,21 +10049,128 @@ snapshots: - typescript - utf-8-validate - ? '@apps-in-toss/native-modules@2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' + '@apps-in-toss/framework@2.10.4(fe6e5424b707ceb9cb0a963285f7648e)': + dependencies: + '@apps-in-toss/analytics': 2.10.4(517315b53d0d70e3b66b55bebacce804) + '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/native-modules': 2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/types': 2.10.4 + '@apps-in-toss/user-scripts': 2.10.4 + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@toss/tds-react-native': 2.0.3(d7d036f8004849fb2ad75cccc074de3d) + '@types/react': 19.2.17 + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + es-hangul: 2.3.8 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - '@dotlottie/react-player' + - '@lottiefiles/react-lottie-player' + - '@react-navigation/native' + - '@swc/helpers' + - '@types/node' + - babel-plugin-macros + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react-native-b4a + - react-native-safe-area-context + - react-native-screens + - react-native-windows + - supports-color + - typanion + - typescript + - utf-8-validate + + ? '@apps-in-toss/native-modules@2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + : dependencies: + '@apps-in-toss/types': 2.10.4 + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + es-toolkit: 1.49.0 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + + ? '@apps-in-toss/native-modules@2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' : dependencies: '@apps-in-toss/types': 2.10.4 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) es-toolkit: 1.49.0 react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) - '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@babel/runtime': 7.29.7 '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/utils': 1.0.20 + '@react-native-async-storage/async-storage-1.18.2': '@react-native-async-storage/async-storage@1.18.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))' + '@react-native-community/blur-4.3.2': '@react-native-community/blur@4.3.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + '@react-native/codegen-0.72.6': '@react-native/codegen@0.72.6(@babel/preset-env@7.28.5(@babel/core@7.23.9))' + '@react-native/js-polyfills-0.72.1': '@react-native/js-polyfills@0.72.1' + '@react-native/normalize-colors-0.72.0': '@react-native/normalize-colors@0.72.0' + '@react-navigation/elements-1.3.9': '@react-navigation/elements@1.3.9(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + '@react-navigation/native-6.0.13': '@react-navigation/native@6.0.13(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + '@react-navigation/native-stack-6.9.0': '@react-navigation/native-stack@6.9.0(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + '@shopify/flash-list-1.6.2': '@shopify/flash-list@1.6.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + assert: 2.1.0 + browserify-zlib: 0.2.0 + buffer: 6.0.3 + enhanced-resolve: 5.24.1 + es-toolkit: 1.49.0 + events: 3.3.0 + https-browserify: 1.0.0 + lottie-react-native-6.4.0: lottie-react-native@6.4.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + path-browserify: 1.0.1 + react-18.2.0: react@18.2.0 + react-dom-18.2.0: react-dom@18.2.0(react@18.2.0) + react-native-0.72.6: react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-fast-image-8.6.3: react-native-fast-image@8.6.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-gesture-handler-2.8.0: react-native-gesture-handler@2.8.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-pager-view-6.1.2: react-native-pager-view@6.1.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-safe-area-context-4.7.4: react-native-safe-area-context@4.7.4(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-screens-3.27.0: react-native-screens@3.27.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-svg-13.14.0: react-native-svg@13.14.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-video-6.0.0-alpha.6: react-native-video@6.0.0-alpha.6 + react-native-webview-13.6.3: react-native-webview@13.6.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react18-use: 0.4.1(react@18.2.0) + stream-browserify: 3.0.0 + stream-http: 3.2.0 + url: 0.11.0 + use-effect-event: 2.0.3(react@18.2.0) + util: 0.12.5 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - '@dotlottie/react-player' + - '@lottiefiles/react-lottie-player' + - '@react-navigation/native' + - '@swc/helpers' + - '@types/node' + - bufferutil + - encoding + - react + - react-native + - react-native-safe-area-context + - react-native-screens + - react-native-windows + - supports-color + - typescript + - utf-8-validate + + '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + dependencies: + '@babel/runtime': 7.29.7 + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 '@react-native-async-storage/async-storage-1.18.2': '@react-native-async-storage/async-storage@1.18.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))' '@react-native-community/blur-4.3.2': '@react-native-community/blur@4.3.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' '@react-native/codegen-0.72.6': '@react-native/codegen@0.72.6(@babel/preset-env@7.28.5(@babel/core@7.29.7))' @@ -10063,10 +10225,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@babel/runtime': 7.29.7 - '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/utils': 1.0.20 '@react-native-async-storage/async-storage-1.18.2': '@react-native-async-storage/async-storage@1.18.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))' '@react-native-community/blur-4.3.2': '@react-native-community/blur@4.3.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' @@ -10122,10 +10284,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugins@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugins@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) @@ -10158,13 +10320,49 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugins@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugins@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) - '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) - '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 + archiver: 7.0.1 + connect: 3.7.0 + esbuild: 0.25.5 + execa: 9.3.0 + picocolors: 1.1.1 + uuidv7: 1.2.1 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - '@dotlottie/react-player' + - '@lottiefiles/react-lottie-player' + - '@react-navigation/native' + - '@swc/helpers' + - '@types/node' + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react + - react-native + - react-native-b4a + - react-native-safe-area-context + - react-native-screens + - react-native-windows + - supports-color + - typescript + - utf-8-validate + + '@apps-in-toss/plugins@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + dependencies: + '@apps-in-toss/ait-format': 1.0.0 + '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/utils': 1.0.20 archiver: 7.0.1 connect: 3.7.0 @@ -10219,21 +10417,74 @@ snapshots: - supports-color - typescript - '@apps-in-toss/web-framework@2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(e611cf729dbb950b6f4fac8c975cae5e))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': + '@apps-in-toss/web-framework@2.10.4(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': dependencies: '@apps-in-toss/bridge-core': 2.10.4 - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/framework': 2.10.4(048871f9d8af3dec25a486cf5e3b350e) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/framework': 2.10.4(fe6e5424b707ceb9cb0a963285f7648e) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@apps-in-toss/web-analytics': 2.10.4(@apps-in-toss/web-bridge@2.10.4) '@apps-in-toss/web-bridge': 2.10.4 '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) '@babel/core': 7.23.9 '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.17)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) - '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) - '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/utils': 1.0.20 + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + transitivePeerDependencies: + - '@babel/preset-env' + - '@babel/runtime' + - '@dotlottie/react-player' + - '@emnapi/core' + - '@emnapi/runtime' + - '@lottiefiles/react-lottie-player' + - '@microsoft/api-extractor' + - '@react-native-masked-view/masked-view' + - '@react-navigation/native' + - '@swc/helpers' + - '@toss/tds-react-native' + - '@types/node' + - '@types/react' + - babel-plugin-macros + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - jiti + - node-notifier + - postcss + - react + - react-native + - react-native-b4a + - react-native-safe-area-context + - react-native-screens + - react-native-windows + - supports-color + - ts-node + - tsx + - typanion + - typescript + - utf-8-validate + - yaml + + '@apps-in-toss/web-framework@2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': + dependencies: + '@apps-in-toss/bridge-core': 2.10.4 + '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/framework': 2.10.4(db61d165d46a8a47fb7719835ebd892e) + '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-analytics': 2.10.4(@apps-in-toss/web-bridge@2.10.4) + '@apps-in-toss/web-bridge': 2.10.4 + '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@babel/core': 7.23.9 + '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.23)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) '@granite-js/utils': 1.0.20 brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) transitivePeerDependencies: @@ -10316,15 +10567,15 @@ snapshots: '@babel/core@7.28.5': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.28.5) '@babel/helpers': 7.29.7 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@8.1.1) @@ -10356,8 +10607,8 @@ snapshots: '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -10477,7 +10728,7 @@ snapshots: '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@babel/helper-globals@7.29.7': {} @@ -10610,12 +10861,20 @@ snapshots: '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10632,6 +10891,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10642,6 +10906,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10652,6 +10921,15 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.23.9) + transitivePeerDependencies: + - supports-color + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10670,6 +10948,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10871,6 +11157,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10974,7 +11264,12 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.29.7 @@ -10984,6 +11279,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11129,6 +11429,12 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11156,6 +11462,15 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.23.9) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11231,6 +11546,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11247,6 +11570,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11341,6 +11672,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11353,6 +11690,11 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11363,6 +11705,12 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11375,6 +11723,11 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11385,6 +11738,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.23.9) + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11401,6 +11762,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11411,6 +11777,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11421,24 +11792,24 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.23.9)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.28.5) + + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.23.9) - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -11496,6 +11867,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11521,6 +11897,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11546,6 +11927,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11586,6 +11975,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-modules-systemjs@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-modules-systemjs@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11606,6 +12005,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11640,6 +12047,11 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11650,6 +12062,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11660,6 +12077,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11670,6 +12092,17 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.23.9) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11716,6 +12149,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11726,6 +12164,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11757,6 +12203,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11773,6 +12227,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11897,6 +12360,11 @@ snapshots: '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11907,6 +12375,12 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11919,6 +12393,11 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12034,6 +12513,11 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12077,6 +12561,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12087,6 +12576,12 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12117,6 +12612,12 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12129,6 +12630,82 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 + '@babel/preset-env@7.28.5(@babel/core@7.23.9)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9) + '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.9) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-modules-systemjs': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-spread': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.23.9) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.9) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.23.9) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.23.9) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.23.9) + core-js-compat: 3.49.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/preset-env@7.28.5(@babel/core@7.28.5)': dependencies: '@babel/compat-data': 7.29.7 @@ -12288,6 +12865,13 @@ snapshots: '@babel/helper-validator-option': 7.29.7 '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.9)': + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 7.29.7 + esutils: 2.0.3 + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -12351,9 +12935,9 @@ snapshots: '@babel/template@7.27.2': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@babel/template@7.29.7': dependencies: @@ -12363,12 +12947,12 @@ snapshots: '@babel/traverse@7.28.5': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -13305,6 +13889,13 @@ snapshots: path-to-regexp: 6.3.0 reusify: 1.1.0 + '@granite-js/blur-view@1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@types/react': 19.2.17 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@granite-js/blur-view@1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -13345,16 +13936,67 @@ snapshots: - utf-8-validate - yaml + '@granite-js/cli@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.23)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0)': + dependencies: + '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 + '@inquirer/prompts': 7.10.1(@types/node@24.13.2) + '@shopify/semaphore': 3.1.0 + chalk: 4.1.2 + clipanion: 4.0.0-rc.4(typanion@3.14.0) + connect: 3.7.0 + debug: 4.4.3(supports-color@8.1.1) + enquirer: 2.4.1 + es-toolkit: 1.49.0 + execa: 5.1.1 + ora: 5.4.1 + typanion: 3.14.0 + zod: 3.24.4 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@microsoft/api-extractor' + - '@swc/helpers' + - '@types/node' + - bufferutil + - encoding + - jiti + - postcss + - supports-color + - tsx + - typescript + - utf-8-validate + - yaml + + '@granite-js/image@1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@granite-js/image@1.0.20(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) - '@granite-js/jest@1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))': + '@granite-js/jest@1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))': dependencies: - '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) jest: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - node-notifier + - supports-color + - ts-node + + '@granite-js/jest@1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))': + dependencies: + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + jest: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) transitivePeerDependencies: - '@types/node' @@ -13363,6 +14005,11 @@ snapshots: - supports-color - ts-node + '@granite-js/lottie@1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@granite-js/lottie@1.0.20(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 @@ -13479,6 +14126,29 @@ snapshots: - utf-8-validate - yaml + '@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@granite-js/image': 1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/lottie': 1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/video': 1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-native-async-storage/async-storage': 2.2.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0)) + '@react-native-community/blur': 4.4.1(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/elements': 2.9.14(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/native-stack': 7.14.10(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@shopify/flash-list': 2.2.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-gesture-handler: 2.30.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-pager-view: 7.0.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-screens: 4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-svg: 15.15.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-webview: 13.16.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.29.7 @@ -13520,39 +14190,118 @@ snapshots: - '@types/node' - typescript + '@granite-js/plugin-core@1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2)': + dependencies: + '@granite-js/utils': 1.0.20 + '@swc/core': 1.15.8(@swc/helpers@0.5.23) + '@types/babel__core': 7.20.5 + '@types/connect': 3.4.38 + '@types/ws': 8.18.1 + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 5.1.0(@types/node@24.13.2)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + es-toolkit: 1.49.0 + esbuild: 0.25.12 + fastify: 4.14.0 + zod: 4.4.3 + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - typescript + '@granite-js/plugin-micro-frontend@1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2)': dependencies: '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/utils': 1.0.20 es-toolkit: 1.49.0 - picocolors: 1.1.1 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - typescript + + '@granite-js/plugin-micro-frontend@1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2)': + dependencies: + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 + es-toolkit: 1.49.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - typescript + + '@granite-js/plugin-sentry@1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2)': + dependencies: + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 + '@sentry/cli': 2.58.6 + es-toolkit: 1.49.0 + execa: 5.1.1 + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - encoding + - supports-color + - typescript + + '@granite-js/plugin-sentry@1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2)': + dependencies: + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/utils': 1.0.20 + '@sentry/cli': 2.58.6 + es-toolkit: 1.49.0 + execa: 5.1.1 transitivePeerDependencies: - '@swc/helpers' - '@types/node' + - encoding + - supports-color - typescript - '@granite-js/plugin-sentry@1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2)': + '@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0)': dependencies: + '@granite-js/blur-view': 1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.17)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/jest': 1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) - '@granite-js/utils': 1.0.20 - '@sentry/cli': 2.58.6 + '@granite-js/style-utils': 1.0.20(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/video': 1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@standard-schema/spec': 1.1.0 + '@types/react': 19.2.17 + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) es-toolkit: 1.49.0 - execa: 5.1.1 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-url-polyfill: 3.0.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0)) transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@microsoft/api-extractor' - '@swc/helpers' - '@types/node' + - babel-plugin-macros + - bufferutil - encoding + - jiti + - node-notifier + - postcss - supports-color + - ts-node + - tsx - typescript + - utf-8-validate + - yaml - '@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0)': + '@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0)': dependencies: '@granite-js/blur-view': 1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.17)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) - '@granite-js/jest': 1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.23)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/jest': 1.0.20(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@types/node@24.13.2)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) + '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/style-utils': 1.0.20(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@granite-js/video': 1.0.20(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@standard-schema/spec': 1.1.0 @@ -13581,6 +14330,12 @@ snapshots: - utf-8-validate - yaml + '@granite-js/style-utils@1.0.20(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@types/react': 19.2.17 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@granite-js/style-utils@1.0.20(@types/react@19.2.17)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@types/react': 19.2.17 @@ -13591,6 +14346,11 @@ snapshots: dependencies: yauzl: 3.4.0 + '@granite-js/video@1.0.20(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@granite-js/video@1.0.20(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 @@ -14149,6 +14909,41 @@ snapshots: - supports-color - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.13.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/create-cache-key-function@29.7.0': dependencies: '@jest/types': 29.6.3 @@ -14605,21 +15400,41 @@ snapshots: '@protobufjs/utf8@1.1.1': {} + '@react-native-async-storage/async-storage@1.18.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))': + dependencies: + merge-options: 3.0.4 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-native-async-storage/async-storage@1.18.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))': dependencies: merge-options: 3.0.4 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + '@react-native-async-storage/async-storage@2.2.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))': + dependencies: + merge-options: 3.0.4 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-native-async-storage/async-storage@2.2.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))': dependencies: merge-options: 3.0.4 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + '@react-native-community/blur@4.3.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-native-community/blur@4.3.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + '@react-native-community/blur@4.4.1(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-native-community/blur@4.4.1(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 @@ -14856,7 +15671,7 @@ snapshots: '@react-native/babel-plugin-codegen@0.84.0(@babel/core@7.28.5)': dependencies: - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.7 '@react-native/codegen': 0.84.0(@babel/core@7.28.5) transitivePeerDependencies: - '@babel/core' @@ -14876,7 +15691,7 @@ snapshots: '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.28.5) @@ -14900,6 +15715,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@react-native/codegen@0.72.6(@babel/preset-env@7.28.5(@babel/core@7.23.9))': + dependencies: + '@babel/parser': 7.29.7 + '@babel/preset-env': 7.28.5(@babel/core@7.23.9) + flow-parser: 0.206.0 + jscodeshift: 0.14.0(@babel/preset-env@7.28.5(@babel/core@7.23.9)) + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + '@react-native/codegen@0.72.6(@babel/preset-env@7.28.5(@babel/core@7.29.7))': dependencies: '@babel/parser': 7.29.7 @@ -14910,6 +15735,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@react-native/codegen@0.72.8(@babel/preset-env@7.28.5(@babel/core@7.23.9))': + dependencies: + '@babel/parser': 7.29.7 + '@babel/preset-env': 7.28.5(@babel/core@7.23.9) + flow-parser: 0.206.0 + glob: 7.2.3 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.28.5(@babel/core@7.23.9)) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + '@react-native/codegen@0.72.8(@babel/preset-env@7.28.5(@babel/core@7.29.7))': dependencies: '@babel/parser': 7.29.7 @@ -14926,7 +15764,7 @@ snapshots: '@react-native/codegen@0.84.0(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.7 hermes-parser: 0.32.0 invariant: 2.2.4 nullthrows: 1.1.1 @@ -14970,6 +15808,12 @@ snapshots: '@react-native/normalize-colors@0.72.0': {} + '@react-native/virtualized-lists@0.72.8(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-native/virtualized-lists@0.72.8(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))': dependencies: invariant: 2.2.4 @@ -14998,6 +15842,13 @@ snapshots: use-latest-callback: 0.2.6(react@18.2.0) use-sync-external-store: 1.6.0(react@18.2.0) + '@react-navigation/elements@1.3.9(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/elements@1.3.9(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -15005,6 +15856,16 @@ snapshots: react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@react-navigation/elements@2.9.14(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + color: 4.2.3 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + use-latest-callback: 0.2.6(react@18.2.0) + use-sync-external-store: 1.6.0(react@18.2.0) + '@react-navigation/elements@2.9.14(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -15015,6 +15876,16 @@ snapshots: use-latest-callback: 0.2.6(react@18.2.0) use-sync-external-store: 1.6.0(react@18.2.0) + '@react-navigation/native-stack@6.9.0(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/elements': 1.3.9(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-screens: 4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + warn-once: 0.1.1 + '@react-navigation/native-stack@6.9.0(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/elements': 1.3.9(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -15025,6 +15896,20 @@ snapshots: react-native-screens: 4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) warn-once: 0.1.1 + '@react-navigation/native-stack@7.14.10(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/elements': 2.9.14(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + color: 4.2.3 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + react-native-screens: 4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + sf-symbols-typescript: 2.2.0 + warn-once: 0.1.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@react-navigation/native-stack@7.14.10(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/elements': 2.9.14(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -15039,6 +15924,15 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' + '@react-navigation/native@6.0.13(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/core': 6.4.17(react@18.2.0) + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.15 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-navigation/native@6.0.13(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/core': 6.4.17(react@18.2.0) @@ -15048,6 +15942,16 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + '@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-navigation/core': 7.21.3(react@18.2.0) + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.15 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + use-latest-callback: 0.2.6(react@18.2.0) + '@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-navigation/core': 7.21.3(react@18.2.0) @@ -15079,6 +15983,15 @@ snapshots: '@react-spring/types': 10.0.4 react: 18.2.0 + '@react-spring/native@10.0.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@react-spring/animated': 10.0.4(react@18.2.0) + '@react-spring/core': 10.0.4(react@18.2.0) + '@react-spring/shared': 10.0.4(react@18.2.0) + '@react-spring/types': 10.0.4 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@react-spring/native@10.0.3(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@react-spring/animated': 10.0.4(react@18.2.0) @@ -15281,6 +16194,14 @@ snapshots: - encoding - supports-color + '@shopify/flash-list@1.6.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@babel/runtime': 7.29.7 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + recyclerlistview: 4.2.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + tslib: 2.4.0 + '@shopify/flash-list@1.6.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.29.7 @@ -15289,6 +16210,12 @@ snapshots: recyclerlistview: 4.2.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) tslib: 2.4.0 + '@shopify/flash-list@2.2.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)': + dependencies: + '@babel/runtime': 7.29.7 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + '@shopify/flash-list@2.2.2(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.29.7 @@ -15412,7 +16339,6 @@ snapshots: '@swc/core-win32-ia32-msvc': 1.15.8 '@swc/core-win32-x64-msvc': 1.15.8 '@swc/helpers': 0.5.23 - optional: true '@swc/counter@0.1.3': {} @@ -15445,10 +16371,32 @@ snapshots: '@toss/tds-easings@0.0.1': {} - '@toss/tds-react-native@2.0.3(e611cf729dbb950b6f4fac8c975cae5e)': + '@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d)': + dependencies: + '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@react-spring/native': 10.0.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@react-stately/radio': 3.12.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@toss/tds-color-utils': 0.0.2 + '@toss/tds-colors': 0.1.0 + '@toss/tds-easings': 0.0.1 + '@toss/tds-spring-easing': 0.0.1 + '@toss/tds-typography': 0.0.3 + '@toss/utils': 1.6.1 + '@types/node': 22.15.0 + brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + culori: 4.0.2 + es-hangul: 2.3.8 + hex-to-rgba: 2.0.1 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + transitivePeerDependencies: + - react-dom + + '@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad)': dependencies: '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) + '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) '@react-spring/native': 10.0.3(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@react-stately/radio': 3.12.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@toss/tds-color-utils': 0.0.2 @@ -15734,6 +16682,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) + '@vitest/mocker@4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 @@ -16121,7 +17077,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.23.9) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.23.9) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.23.9) @@ -16154,7 +17110,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.28.5) @@ -16187,7 +17143,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) @@ -16330,6 +17286,12 @@ snapshots: ts-morph: 27.0.2 typescript: 5.9.3 + brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + brick-codegen: 0.5.2 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: brick-codegen: 0.5.2 @@ -16757,6 +17719,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -18215,6 +19192,25 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.3 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-config@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)): dependencies: '@babel/core': 7.29.7 @@ -18246,6 +19242,37 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)): + dependencies: + '@babel/core': 7.29.7 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.7) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.13.2 + ts-node: 10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -18494,12 +19521,24 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)): + jest@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest@29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2)) + jest-cli: 29.7.0(@types/node@24.13.2)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -18543,6 +19582,31 @@ snapshots: jsc-safe-url@0.2.4: {} + jscodeshift@0.14.0(@babel/preset-env@7.28.5(@babel/core@7.23.9)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/preset-env': 7.28.5(@babel/core@7.23.9) + '@babel/preset-flow': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/register': 7.29.7(@babel/core@7.29.7) + babel-core: 7.0.0-bridge.0(@babel/core@7.29.7) + chalk: 4.1.2 + flow-parser: 0.321.0 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + neo-async: 2.6.2 + node-dir: 0.1.17 + recast: 0.21.5 + temp: 0.8.4 + write-file-atomic: 2.4.3 + transitivePeerDependencies: + - supports-color + jscodeshift@0.14.0(@babel/preset-env@7.28.5(@babel/core@7.29.7)): dependencies: '@babel/core': 7.29.7 @@ -18568,6 +19632,31 @@ snapshots: transitivePeerDependencies: - supports-color + jscodeshift@17.3.0(@babel/preset-env@7.28.5(@babel/core@7.23.9)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/preset-flow': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/register': 7.29.7(@babel/core@7.29.7) + flow-parser: 0.321.0 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + neo-async: 2.6.2 + picocolors: 1.1.1 + recast: 0.23.12 + tmp: 0.2.7 + write-file-atomic: 5.0.1 + optionalDependencies: + '@babel/preset-env': 7.28.5(@babel/core@7.23.9) + transitivePeerDependencies: + - supports-color + jscodeshift@17.3.0(@babel/preset-env@7.28.5(@babel/core@7.29.7)): dependencies: '@babel/core': 7.29.7 @@ -18876,6 +19965,11 @@ snapshots: dependencies: js-tokens: 4.0.0 + lottie-react-native@6.4.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + lottie-react-native@6.4.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 @@ -18936,7 +20030,7 @@ snapshots: metro-babel-transformer@0.72.3: dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.7 hermes-parser: 0.8.0 metro-source-map: 0.72.3 nullthrows: 1.1.1 @@ -18945,7 +20039,7 @@ snapshots: metro-babel-transformer@0.76.8: dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.7 hermes-parser: 0.12.0 nullthrows: 1.1.1 transitivePeerDependencies: @@ -19088,7 +20182,7 @@ snapshots: '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.28.5) @@ -19105,7 +20199,7 @@ snapshots: '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.28.5) - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 react-refresh: 0.4.3 transitivePeerDependencies: - supports-color @@ -19132,7 +20226,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.23.9) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.23.9) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.23.9) @@ -19148,7 +20242,7 @@ snapshots: '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.23.9) '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.23.9) - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.23.9) react-refresh: 0.4.3 transitivePeerDependencies: @@ -19176,7 +20270,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.28.5) @@ -19192,7 +20286,7 @@ snapshots: '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.28.5) '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.28.5) - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.5) react-refresh: 0.4.3 transitivePeerDependencies: @@ -19220,7 +20314,7 @@ snapshots: '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) @@ -19236,7 +20330,7 @@ snapshots: '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) react-refresh: 0.4.3 transitivePeerDependencies: @@ -19280,7 +20374,7 @@ snapshots: metro-runtime@0.72.3: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.7 react-refresh: 0.4.3 metro-runtime@0.76.8: @@ -19290,8 +20384,8 @@ snapshots: metro-source-map@0.72.3: dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 invariant: 2.2.4 metro-symbolicate: 0.72.3 nullthrows: 1.1.1 @@ -19338,31 +20432,31 @@ snapshots: metro-transform-plugins@0.72.3: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color metro-transform-plugins@0.76.8: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color metro-transform-worker@0.76.8: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 - '@babel/types': 7.28.5 - babel-preset-fbjs: 3.4.0(@babel/core@7.28.5) + '@babel/types': 7.29.7 + babel-preset-fbjs: 3.4.0(@babel/core@7.29.7) metro: 0.76.8 metro-babel-transformer: 0.76.8 metro-cache: 0.76.8 @@ -19378,13 +20472,13 @@ snapshots: metro@0.76.8: dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 accepts: 1.3.8 async: 3.2.6 chalk: 4.1.2 @@ -19409,7 +20503,7 @@ snapshots: metro-inspector-proxy: 0.76.8 metro-minify-terser: 0.76.8 metro-minify-uglify: 0.76.8 - metro-react-native-babel-preset: 0.76.8(@babel/core@7.28.5) + metro-react-native-babel-preset: 0.76.8(@babel/core@7.29.7) metro-resolver: 0.76.8 metro-runtime: 0.76.8 metro-source-map: 0.76.8 @@ -20078,11 +21172,24 @@ snapshots: react-is@19.2.7: {} + react-native-fast-image@8.6.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-fast-image@8.6.3(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-gesture-handler@2.30.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-gesture-handler@2.30.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: '@egjs/hammerjs': 2.0.17 @@ -20091,6 +21198,16 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-gesture-handler@2.8.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + lodash: 4.18.1 + prop-types: 15.8.1 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-gesture-handler@2.8.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: '@egjs/hammerjs': 2.0.17 @@ -20101,26 +21218,53 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-pager-view@6.1.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-pager-view@6.1.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-pager-view@7.0.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-pager-view@7.0.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-safe-area-context@4.7.4(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context@4.7.4(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-screens@3.27.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-freeze: 1.0.4(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + warn-once: 0.1.1 + react-native-screens@3.27.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 @@ -20128,6 +21272,13 @@ snapshots: react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) warn-once: 0.1.1 + react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-freeze: 1.0.4(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + warn-once: 0.1.1 + react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 @@ -20135,6 +21286,13 @@ snapshots: react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) warn-once: 0.1.1 + react-native-svg@13.14.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + css-select: 5.2.2 + css-tree: 1.1.3 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-svg@13.14.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: css-select: 5.2.2 @@ -20142,6 +21300,14 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-svg@15.15.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + css-select: 5.2.2 + css-tree: 1.1.3 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + warn-once: 0.1.1 + react-native-svg@15.15.3(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: css-select: 5.2.2 @@ -20150,6 +21316,11 @@ snapshots: react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) warn-once: 0.1.1 + react-native-url-polyfill@3.0.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0)): + dependencies: + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + whatwg-url-without-unicode: 8.0.0-3 + react-native-url-polyfill@3.0.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0)): dependencies: react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) @@ -20161,6 +21332,13 @@ snapshots: keymirror: 0.1.1 prop-types: 15.8.1 + react-native-webview@13.16.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + escape-string-regexp: 4.0.0 + invariant: 2.2.4 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-webview@13.16.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: escape-string-regexp: 4.0.0 @@ -20168,6 +21346,13 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native-webview@13.6.3(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + escape-string-regexp: 2.0.0 + invariant: 2.2.4 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + react-native-webview@13.6.3(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: escape-string-regexp: 2.0.0 @@ -20175,6 +21360,53 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) + react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native-community/cli': 11.3.7(@babel/core@7.23.9) + '@react-native-community/cli-platform-android': 11.3.7 + '@react-native-community/cli-platform-ios': 11.3.7 + '@react-native/assets-registry': 0.72.0 + '@react-native/codegen': 0.72.8(@babel/preset-env@7.28.5(@babel/core@7.23.9)) + '@react-native/gradle-plugin': 0.72.11 + '@react-native/js-polyfills': 0.72.1 + '@react-native/normalize-colors': 0.72.0 + '@react-native/virtualized-lists': 0.72.8(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0)) + abort-controller: 3.0.0 + anser: 1.4.10 + base64-js: 1.5.1 + deprecated-react-native-prop-types: 4.1.0 + event-target-shim: 5.0.1 + flow-enums-runtime: 0.0.5 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + jsc-android: 250231.0.0 + memoize-one: 5.2.1 + metro-runtime: 0.76.8 + metro-source-map: 0.76.8 + mkdirp: 0.5.6 + nullthrows: 1.1.1 + pretty-format: 26.6.2 + promise: 8.3.0 + react: 18.2.0 + react-devtools-core: 4.28.5 + react-refresh: 0.4.3 + react-shallow-renderer: 16.15.0(react@18.2.0) + regenerator-runtime: 0.13.11 + scheduler: 0.24.0-canary-efb381bbf-20230505 + stacktrace-parser: 0.1.11 + use-sync-external-store: 1.6.0(react@18.2.0) + whatwg-fetch: 3.6.20 + ws: 6.2.4 + yargs: 17.7.3 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0): dependencies: '@jest/create-cache-key-function': 29.7.0 @@ -20350,6 +21582,14 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + recyclerlistview@4.2.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0): + dependencies: + lodash.debounce: 4.0.8 + prop-types: 15.8.1 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) + ts-object-utils: 0.0.5 + recyclerlistview@4.2.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0): dependencies: lodash.debounce: 4.0.8 @@ -21379,7 +22619,7 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)): + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.4 @@ -21388,7 +22628,7 @@ snapshots: esbuild: 0.28.1 rolldown: 1.1.4 rollup: 4.62.2 - vite: 8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) + vite: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) untildify@4.0.0: {} @@ -21496,6 +22736,34 @@ snapshots: terser: 5.48.0 yaml: 2.9.0 + vitest@4.1.9(@types/node@24.13.2)(jsdom@24.1.0)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + es-module-lexer: 2.3.0 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.3(@types/node@24.13.2)(esbuild@0.27.7)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.2 + jsdom: 24.1.0 + transitivePeerDependencies: + - msw + vitest@4.1.9(@types/node@24.13.2)(jsdom@24.1.0)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 diff --git a/tools/smoke/adapter-effective-config.ts b/tools/smoke/adapter-effective-config.ts index 142ecdf3..1b1d063e 100644 --- a/tools/smoke/adapter-effective-config.ts +++ b/tools/smoke/adapter-effective-config.ts @@ -1,5 +1,5 @@ import type { BridgeMethod, BridgeRequest, BridgeResponse } from '@mpgd/bridge'; -import type { AdPlacements } from '@mpgd/catalog'; +import type { AdPlacements, ProductCatalog } from '@mpgd/catalog'; import type { PlatformCapabilities, PlatformGateway } from '@mpgd/platform'; import { createAitPlatformGateway } from '../../adapters/ait/src/index'; @@ -8,6 +8,7 @@ import { createCapacitorPlatformGateway } from '../../adapters/capacitor/src/ind import { createDevvitPlatformGateway } from '../../adapters/devvit/src/index'; import { createVerse8PlatformGateway } from '../../adapters/verse8/src/index'; import { + createEffectiveTargetConfig, getEffectiveAdPlacementConfig, getEffectiveProductConfig, type EffectiveTargetConfig, @@ -26,6 +27,7 @@ const targetConfigMatrix = readJsonFile( ) as TargetConfigMatrix; const effectiveConfigMatrix = loadEffectiveTargetConfigMatrix() as EffectiveTargetConfigMatrix; +const productCatalog = readJsonFile('packages/catalog/catalog.json') as ProductCatalog; const adPlacements = readJsonFile('packages/catalog/placements.json') as AdPlacements; const targetAdPlacements = adPlacements.placements.map((placement) => ({ id: placement.id, @@ -231,35 +233,114 @@ async function verifyAitAdapter(): Promise { assertEqual(effectiveConfig.target, 'ait', 'ait effective target'); assertEqual( getEffectiveProductConfig(effectiveConfig, 'COINS_100')?.enabled, - true, - 'ait product should be enabled', + false, + 'ait product should be disabled without authoritative game services', ); assertEqual( getEffectiveAdPlacementConfig(effectiveConfig, 'CONTINUE_AFTER_FAIL')?.enabled, + false, + 'ait rewarded placement should be disabled without authoritative game services', + ); + + assertDeepEqual( + await gateway.commerce.purchase({ + productId: 'COINS_100', + source: 'shop', + idempotencyKey: 'ait-disabled-purchase', + }), + { + status: 'cancelled', + entitlementIds: [], + }, + 'ait purchase should fail closed without authoritative game services', + ); + assertDeepEqual( + await gateway.ads.showRewarded({ + placementId: 'CONTINUE_AFTER_FAIL', + idempotencyKey: 'ait-disabled-reward', + }), + { + status: 'unavailable', + rewardGranted: false, + }, + 'ait reward should fail closed without authoritative game services', + ); + await gateway.leaderboard.submitScore({ + leaderboardId: 'default', + score: 1, + runId: 'ait-native-leaderboard-run', + submittedAt: new Date().toISOString(), + }); + + assertDeepEqual( + bridge.methods.slice(1), + ['leaderboard.submitScore'], + 'ait adapter should preserve native leaderboard while filtering grants', + ); + + const authoritativeBridge = createRecordingBridge('ait'); + const aitConfig = getTargetConfig(targetConfigMatrix, 'ait'); + const authoritativeEffectiveConfig = createEffectiveTargetConfig({ + target: 'ait', + targetConfigVersion: targetConfigMatrix.version, + config: aitConfig, + catalog: productCatalog, + adPlacements, + platformTarget: { + kind: 'apps-in-toss', + adapter: 'ait', + authoritativeGameServices: true, + }, + }); + const authoritativeGateway = wrapGateway( + 'ait', + createAitPlatformGateway({ + appVersion: '1.0.0', + buildId: 'build-ait-authoritative', + bridge: authoritativeBridge, + }), + authoritativeEffectiveConfig, + ); + const authoritativeRuntime = await authoritativeGateway.getTargetRuntime(); + const authoritativeConfig = requireEffectiveConfig( + authoritativeRuntime.effectiveConfig, + 'ait-authoritative', + ); + + assertEqual( + getEffectiveProductConfig(authoritativeConfig, 'COINS_100')?.enabled, true, - 'ait rewarded placement should be enabled', + 'authoritative ait product should be enabled', + ); + assertEqual( + getEffectiveAdPlacementConfig( + authoritativeConfig, + 'CONTINUE_AFTER_FAIL', + )?.enabled, + true, + 'authoritative ait rewarded placement should be enabled', ); - await gateway.commerce.purchase({ + await authoritativeGateway.commerce.purchase({ productId: 'COINS_100', source: 'shop', - idempotencyKey: 'ait-parity-purchase', + idempotencyKey: 'ait-authoritative-purchase', }); - await gateway.ads.showRewarded({ + await authoritativeGateway.ads.showRewarded({ placementId: 'CONTINUE_AFTER_FAIL', - idempotencyKey: 'ait-parity-reward', + idempotencyKey: 'ait-authoritative-reward', }); - await gateway.leaderboard.submitScore({ + await authoritativeGateway.leaderboard.submitScore({ leaderboardId: 'default', score: 1, - runId: 'ait-parity-run', + runId: 'ait-authoritative-run', submittedAt: new Date().toISOString(), }); assertDeepEqual( - bridge.methods.slice(1), + authoritativeBridge.methods.slice(1), enabledActionMethods, - 'ait adapter should delegate enabled actions', + 'authoritative ait adapter should delegate enabled actions', ); } @@ -329,9 +410,14 @@ function requireEffectiveConfig( return config; } -function wrapGateway(configTarget: string, gateway: PlatformGateway) { +function wrapGateway( + configTarget: string, + gateway: PlatformGateway, + effectiveConfigOverride?: EffectiveTargetConfig, +) { const config = getTargetConfig(targetConfigMatrix, configTarget); - const effectiveConfig = effectiveConfigMatrix.targets[configTarget]; + const effectiveConfig = + effectiveConfigOverride ?? effectiveConfigMatrix.targets[configTarget]; if (effectiveConfig === undefined) { throw new Error(`Missing effective target config for ${configTarget}.`); diff --git a/tools/smoke/effective-target-config.ts b/tools/smoke/effective-target-config.ts index ec8f6113..da3f53b1 100644 --- a/tools/smoke/effective-target-config.ts +++ b/tools/smoke/effective-target-config.ts @@ -115,6 +115,18 @@ function verifyGameOwnedIntegrationOverrides(): void { notifications: 'not-a-readiness-state', }); const invalidInputs = [ + { + description: 'a non-boolean authoritative game-services flag', + config: { + targets: { + reddit: { + ...target, + authoritativeGameServices: 'false', + }, + }, + }, + expectedMessage: 'authoritativeGameServices must be a boolean', + }, { description: 'an invalid availability state', config: invalidPlatformTargets, @@ -406,6 +418,28 @@ function verifyEffectiveConfig(target: string, config: EffectiveTargetConfig): v return; } + if (target === 'ait') { + assertEqual( + config.monetization.products.every((product) => !product.enabled), + true, + 'ait products should stay disabled until app-owned product ids and authority are configured', + ); + assertEqual( + config.ads.placements.every((placement) => !placement.enabled), + true, + 'ait ads should stay disabled until app-owned ad group ids are configured', + ); + assertEqual(config.leaderboard.enabled, true, 'ait leaderboard should be enabled'); + assertEqual(config.leaderboard.native, true, 'ait should use Game Center leaderboard'); + assertEqual( + config.leaderboard.defaultLeaderboardId, + 'default', + 'ait leaderboard id should be stable', + ); + assertEqual(config.storage.support, 'native', 'ait should use native Storage'); + return; + } + assertEqual( config.monetization.products.every((product) => product.enabled), true, @@ -422,12 +456,7 @@ function verifyEffectiveConfig(target: string, config: EffectiveTargetConfig): v 'default', `${target} leaderboard id should be stable`, ); - - if (target === 'ait') { - assertEqual(config.storage.support, 'none', 'ait should not rely on native storage'); - } else { - assertEqual(config.storage.support, 'native', `${target} should use native storage`); - } + assertEqual(config.storage.support, 'native', `${target} should use native storage`); } function assertEqual(actual: T, expected: T, message: string): void { diff --git a/tools/smoke/production-target-readiness.ts b/tools/smoke/production-target-readiness.ts index 6a756def..13635949 100644 --- a/tools/smoke/production-target-readiness.ts +++ b/tools/smoke/production-target-readiness.ts @@ -17,7 +17,11 @@ try { mkdirSync(outsideRoot, { recursive: true }); writeTargets({ - ait: { wrapperApp: 'apps/target-ait', webDir: 'apps/target-ait/public/game' }, + ait: { + wrapperApp: 'apps/target-ait', + webDir: 'apps/target-ait/public/game', + authoritativeGameServices: false, + }, android: { shellApp: 'apps/mobile-capacitor', webDir: 'apps/mobile-capacitor/www' }, ios: { shellApp: 'apps/mobile-capacitor', webDir: 'apps/mobile-capacitor/www' }, }); @@ -32,6 +36,13 @@ try { }); } + assertProductionTargetReadiness({ + target: 'ait', + profile: 'production', + targetsFile, + gameRoot, + }); + for (const target of ['android', 'ios']) { const nativePlatformDir = join(gameRoot, 'apps', 'mobile-capacitor', target); @@ -60,6 +71,14 @@ try { { target: 'ait', profile: ' production ', gameServicesUrl: publicBackend }, 'without surrounding whitespace', ); + + writeTargets({ + ait: { + wrapperApp: 'apps/target-ait', + webDir: 'apps/target-ait/public/game', + authoritativeGameServices: true, + }, + }); expectReadinessError( { target: 'ait', profile: 'production' }, 'requires VITE_MPGD_GAME_SERVICES_URL', diff --git a/tools/target/effective-config.ts b/tools/target/effective-config.ts index 73712fe8..a0e39aca 100644 --- a/tools/target/effective-config.ts +++ b/tools/target/effective-config.ts @@ -240,6 +240,9 @@ function toEffectivePlatformTargetMetadata( const base = { kind: config.kind, adapter: config.adapter, + ...(config.authoritativeGameServices === undefined + ? {} + : { authoritativeGameServices: config.authoritativeGameServices }), ...(config.integrations === undefined ? {} : { integrations: config.integrations }), }; diff --git a/tools/target/platform-targets.ts b/tools/target/platform-targets.ts index 6dfe0d67..93ec2c51 100644 --- a/tools/target/platform-targets.ts +++ b/tools/target/platform-targets.ts @@ -88,6 +88,7 @@ function assertPlatformTargetConfigShape( assertTargetKind(input.kind, target); assertString(input.gameApp, `${target}.gameApp`); assertString(input.adapter, `${target}.adapter`); + assertOptionalBoolean(input.authoritativeGameServices, `${target}.authoritativeGameServices`); assertTargetIntegrations(input.integrations, target); assertTargetIcon(input.icon, target); @@ -204,6 +205,12 @@ function assertString(input: unknown, label: string): asserts input is string { } } +function assertOptionalBoolean(input: unknown, label: string): void { + if (input !== undefined && typeof input !== 'boolean') { + throw new Error(`${label} must be a boolean when provided.`); + } +} + function assertTargetKind(input: unknown, target: string): asserts input is PlatformTargetConfig['kind'] { if ( input !== 'web' diff --git a/tools/target/schemas.ts b/tools/target/schemas.ts index ecdaa761..ca8a7066 100644 --- a/tools/target/schemas.ts +++ b/tools/target/schemas.ts @@ -14,6 +14,8 @@ export interface BaseTargetConfig { readonly gameApp: string; readonly adapter: string; readonly metadata?: TargetReleaseMetadata; + /** Required when purchases or rewarded grants use a game-owned authority. */ + readonly authoritativeGameServices?: boolean; readonly integrations?: Partial; readonly icon?: TargetIconConfig; } diff --git a/tsconfig.base.json b/tsconfig.base.json index 707a8e38..07d60702 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -23,6 +23,7 @@ "@mpgd/adapter-devvit/surface": ["./adapters/devvit/src/surface.ts"], "@mpgd/adapter-devvit/view-mode": ["./adapters/devvit/src/view-mode.ts"], "@mpgd/adapter-devvit/web": ["./adapters/devvit/src/web.ts"], + "@mpgd/adapter-ait/host": ["./adapters/ait/src/host.ts"], "@mpgd/adapter-verse8/agent8": ["./adapters/verse8/src/agent8.ts"], "@mpgd/adapter-verse8/server": ["./adapters/verse8/src/server.ts"], "@mpgd/catalog/*": ["./packages/catalog/*"], From ea431facdef74263cd43a667e03bf9e396e54248 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 22:42:36 +0900 Subject: [PATCH 2/7] fix(ait): harden lifecycle and ad state --- adapters/ait/src/host.test.ts | 71 +++++++++++++++++++++ adapters/ait/src/host.ts | 82 +++++++++++++++---------- adapters/ait/src/lifecycle.test.ts | 43 +++++++++++++ adapters/ait/src/lifecycle.ts | 37 +++++++---- packages/target-config/src/effective.ts | 15 +++-- packages/target-config/src/runtime.ts | 32 ++++++---- 6 files changed, 215 insertions(+), 65 deletions(-) create mode 100644 adapters/ait/src/lifecycle.test.ts diff --git a/adapters/ait/src/host.test.ts b/adapters/ait/src/host.test.ts index b6c669c9..991b40b0 100644 --- a/adapters/ait/src/host.test.ts +++ b/adapters/ait/src/host.test.ts @@ -55,6 +55,22 @@ describe('AIT production host bridge', () => { }); }); + it('treats corrupted native storage as a missing save', async () => { + const bridge = createAitHostBridge({ + dependencies: createDependencies({ + storage: { + getItem: async () => '{not-valid-json', + setItem: async () => {}, + }, + }), + }); + + await expect(request(bridge, 'storage.load', { key: 'save:v1' })).resolves.toEqual({ + __mpgdBridgeProtocol: 'mpgd.storage.load.v1', + found: false, + }); + }); + it('fails closed for commerce and unconfigured ads', async () => { const bridge = createAitHostBridge({ dependencies: createDependencies() }); @@ -163,6 +179,61 @@ describe('AIT production host bridge', () => { await expect(reward).resolves.toEqual({ status: 'skipped', rewardGranted: false }); }); + it('consumes a preloaded ad before awaiting the native show result', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let showCount = 0; + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCount += 1; + showCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { placementId: 'SUDOKU_HINT_REWARDED' }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + + const firstShow = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-concurrent-1', + }); + const secondShow = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-concurrent-2', + }); + + await expect(secondShow).resolves.toEqual({ + status: 'unavailable', + rewardGranted: false, + }); + showCallbacks?.onEvent({ + type: 'userEarnedReward', + data: { unitType: 'hint', unitAmount: 1 }, + }); + showCallbacks?.onEvent({ type: 'dismissed' }); + await expect(firstShow).resolves.toMatchObject({ + status: 'completed', + rewardGranted: true, + }); + expect(showCount).toBe(1); + }); + it('delegates supported Game Center score submission and opening', async () => { const submittedScores: string[] = []; let openCount = 0; diff --git a/adapters/ait/src/host.ts b/adapters/ait/src/host.ts index 93a69340..f37484bf 100644 --- a/adapters/ait/src/host.ts +++ b/adapters/ait/src/host.ts @@ -213,13 +213,13 @@ export function createAitHostBridge( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'rewarded' || !dependencies.showFullScreenAd.isSupported() - || !loadedAdGroupIds.has(adGroupId) + || !consumeLoadedAd(adGroupId, loadedAdGroupIds) ) { return ok(request, { status: 'unavailable', rewardGranted: false }); } const correlationId = readIdempotencyKey(request.payload, request.id); - const result = await showRewardedAd(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs); + const result = await showRewardedAd(dependencies, adGroupId, adTimeoutMs); return ok( request, @@ -250,15 +250,12 @@ export function createAitHostBridge( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'interstitial' || !dependencies.showFullScreenAd.isSupported() - || !loadedAdGroupIds.has(adGroupId) + || !consumeLoadedAd(adGroupId, loadedAdGroupIds) ) { return ok(request, { status: 'unavailable' }); } - return ok( - request, - await showInterstitialAd(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs), - ); + return ok(request, await showInterstitialAd(dependencies, adGroupId, adTimeoutMs)); } case 'leaderboard.submitScore': { @@ -290,14 +287,7 @@ export function createAitHostBridge( case 'storage.load': { const key = readStorageKey(request.payload); const serialized = await dependencies.storage.getItem(key); - const data: BridgeStorageLoadData = serialized === null - ? { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false } - : { - __mpgdBridgeProtocol: bridgeStorageLoadProtocol, - found: true, - value: JSON.parse(serialized) as unknown, - }; - return ok(request, data); + return ok(request, decodeStoredValue(serialized)); } case 'storage.save': { @@ -420,22 +410,26 @@ async function preloadAdGroup( async function showRewardedAd( dependencies: AitHostDependencies, adGroupId: string, - loaded: Set, timeoutMs: number, ): Promise<{ readonly status: 'completed' | 'skipped' | 'failed'; readonly rewardGranted: boolean }> { let rewardGranted = false; - const status = await showAd(dependencies, adGroupId, loaded, timeoutMs, (eventType) => { + const status = await showAd(dependencies, adGroupId, timeoutMs, (eventType) => { if (eventType === 'userEarnedReward') { rewardGranted = true; } }); + let resultStatus: 'completed' | 'skipped' | 'failed'; + + if (status === 'shown' && rewardGranted) { + resultStatus = 'completed'; + } else if (status === 'failed') { + resultStatus = 'failed'; + } else { + resultStatus = 'skipped'; + } return { - status: status === 'shown' && rewardGranted - ? 'completed' - : status === 'failed' - ? 'failed' - : 'skipped', + status: resultStatus, rewardGranted, }; } @@ -443,21 +437,18 @@ async function showRewardedAd( async function showInterstitialAd( dependencies: AitHostDependencies, adGroupId: string, - loaded: Set, timeoutMs: number, ): Promise<{ readonly status: 'shown' | 'skipped' | 'unavailable' }> { - const status = await showAd(dependencies, adGroupId, loaded, timeoutMs); + const status = await showAd(dependencies, adGroupId, timeoutMs); return { status: status === 'failed' ? 'skipped' : status }; } function showAd( dependencies: AitHostDependencies, adGroupId: string, - loaded: Set, timeoutMs: number, observe?: (eventType: string) => void, ): Promise<'shown' | 'failed'> { - loaded.delete(adGroupId); dispatchAitLifecycleEvent('pause'); return new Promise((resolve) => { @@ -490,11 +481,15 @@ function getLaunchIntent(): LaunchIntent { const challengeToken = nonEmptyParam(params.get('challengeToken')); const puzzleId = nonEmptyParam(params.get('puzzleId')); const requestedEntry = nonEmptyParam(params.get('entry')); - const entry = requestedEntry !== undefined && launchEntries.has(requestedEntry as LaunchEntry) - ? requestedEntry as LaunchEntry - : challengeToken === undefined - ? 'home' - : 'friend-challenge'; + let entry: LaunchEntry; + + if (requestedEntry !== undefined && launchEntries.has(requestedEntry as LaunchEntry)) { + entry = requestedEntry as LaunchEntry; + } else if (challengeToken === undefined) { + entry = 'home'; + } else { + entry = 'friend-challenge'; + } return { entry, @@ -577,6 +572,31 @@ function normalizeAdGroupIds( ); } +function consumeLoadedAd(adGroupId: string, loaded: Set): boolean { + if (!loaded.has(adGroupId)) { + return false; + } + + loaded.delete(adGroupId); + return true; +} + +function decodeStoredValue(serialized: string | null): BridgeStorageLoadData { + if (serialized === null) { + return { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false }; + } + + try { + return { + __mpgdBridgeProtocol: bridgeStorageLoadProtocol, + found: true, + value: JSON.parse(serialized) as unknown, + }; + } catch { + return { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false }; + } +} + function hasConfiguredAdType( adGroupIds: ReadonlyMap, adPlacementTypes: ReadonlyMap, diff --git a/adapters/ait/src/lifecycle.test.ts b/adapters/ait/src/lifecycle.test.ts new file mode 100644 index 00000000..67643ed1 --- /dev/null +++ b/adapters/ait/src/lifecycle.test.ts @@ -0,0 +1,43 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { aitLifecyclePauseEvent, createAitLifecycleAdapter } from './lifecycle'; + +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); +}); + +describe('AIT lifecycle adapter', () => { + it('deduplicates one native transition across custom, visibility, and page events', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-07-19T00:00:00.000Z')); + const globalEvents = new EventTarget(); + const documentEvents = new EventTarget(); + Object.defineProperty(documentEvents, 'visibilityState', { + configurable: true, + value: 'hidden', + }); + vi.stubGlobal('addEventListener', globalEvents.addEventListener.bind(globalEvents)); + vi.stubGlobal('removeEventListener', globalEvents.removeEventListener.bind(globalEvents)); + vi.stubGlobal('document', documentEvents as unknown as Document); + + const callback = vi.fn(); + const unsubscribe = createAitLifecycleAdapter().onPause(callback); + + globalEvents.dispatchEvent(new Event(aitLifecyclePauseEvent)); + documentEvents.dispatchEvent(new Event('visibilitychange')); + globalEvents.dispatchEvent(new Event('pagehide')); + expect(callback).toHaveBeenCalledTimes(1); + + vi.advanceTimersByTime(501); + globalEvents.dispatchEvent(new Event('pagehide')); + expect(callback).toHaveBeenCalledTimes(2); + + vi.stubGlobal('document', new EventTarget() as unknown as Document); + unsubscribe(); + vi.advanceTimersByTime(501); + documentEvents.dispatchEvent(new Event('visibilitychange')); + globalEvents.dispatchEvent(new Event('pagehide')); + expect(callback).toHaveBeenCalledTimes(2); + }); +}); diff --git a/adapters/ait/src/lifecycle.ts b/adapters/ait/src/lifecycle.ts index da7d5321..727417ee 100644 --- a/adapters/ait/src/lifecycle.ts +++ b/adapters/ait/src/lifecycle.ts @@ -2,6 +2,7 @@ import type { LifecycleAdapter } from '@mpgd/platform'; export const aitLifecyclePauseEvent = 'mpgd:ait:pause'; export const aitLifecycleResumeEvent = 'mpgd:ait:resume'; +const lifecycleEventDedupeWindowMs = 500; export function createAitLifecycleAdapter(): LifecycleAdapter { return { @@ -21,25 +22,35 @@ export function dispatchAitLifecycleEvent(type: 'pause' | 'resume'): void { } function subscribeToAitLifecycle(type: 'pause' | 'resume', callback: () => void): () => void { + const target = globalThis; + const documentTarget = target.document; const eventName = type === 'pause' ? aitLifecyclePauseEvent : aitLifecycleResumeEvent; - const handleCustomEvent = (): void => callback(); + let lastFiredAt = Number.NEGATIVE_INFINITY; + const fire = (): void => { + const now = Date.now(); + + if (now - lastFiredAt < lifecycleEventDedupeWindowMs) { + return; + } + + lastFiredAt = now; + callback(); + }; + const handleCustomEvent = (): void => fire(); const handleVisibilityChange = (): void => { - if (globalThis.document?.visibilityState === (type === 'pause' ? 'hidden' : 'visible')) { - callback(); + if (documentTarget?.visibilityState === (type === 'pause' ? 'hidden' : 'visible')) { + fire(); } }; - const handlePageTransition = (): void => callback(); + const handlePageTransition = (): void => fire(); - globalThis.addEventListener?.(eventName, handleCustomEvent); - globalThis.document?.addEventListener('visibilitychange', handleVisibilityChange); - globalThis.addEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition); + target.addEventListener?.(eventName, handleCustomEvent); + documentTarget?.addEventListener('visibilitychange', handleVisibilityChange); + target.addEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition); return () => { - globalThis.removeEventListener?.(eventName, handleCustomEvent); - globalThis.document?.removeEventListener('visibilitychange', handleVisibilityChange); - globalThis.removeEventListener?.( - type === 'pause' ? 'pagehide' : 'pageshow', - handlePageTransition, - ); + target.removeEventListener?.(eventName, handleCustomEvent); + documentTarget?.removeEventListener('visibilitychange', handleVisibilityChange); + target.removeEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition); }; } diff --git a/packages/target-config/src/effective.ts b/packages/target-config/src/effective.ts index 53274a4b..f7aea9cc 100644 --- a/packages/target-config/src/effective.ts +++ b/packages/target-config/src/effective.ts @@ -127,6 +127,11 @@ export interface CreateEffectiveTargetConfigMatrixInput { } export const defaultLeaderboardId = 'default'; +const disabledAuthoritativeMonetization = { + iap: false, + rewardedAds: false, + interstitialAds: false, +} as const; export function createEffectiveTargetConfig( input: CreateEffectiveTargetConfigInput, @@ -206,15 +211,9 @@ function resolveAuthoritativeGameServicesConfig( ...config, features: { ...config.features, - iap: false, - rewardedAds: false, - interstitialAds: false, - }, - monetization: { - iap: false, - rewardedAds: false, - interstitialAds: false, + ...disabledAuthoritativeMonetization, }, + monetization: disabledAuthoritativeMonetization, }; } diff --git a/packages/target-config/src/runtime.ts b/packages/target-config/src/runtime.ts index e4f05396..1538083e 100644 --- a/packages/target-config/src/runtime.ts +++ b/packages/target-config/src/runtime.ts @@ -340,14 +340,13 @@ export function createTargetRuntimeSnapshot(input: { readonly gateway?: PlatformGateway; }): TargetRuntimeSnapshot { const configTarget = input.configTarget ?? targetConfigKeyForPlatform(input.target); - const availabilityConfig = input.effectiveConfig === undefined - ? input.config - : { - ...input.config, - features: input.effectiveConfig.features, - }; + const availabilityConfig = resolveAvailabilityConfig(input.config, input.effectiveConfig); const features = { - iap: getFeatureAvailability('iap', availabilityConfig, input.capabilities), + iap: getFeatureAvailability( + 'iap', + availabilityConfig, + input.capabilities, + ), rewardedAds: getFeatureAvailability( 'rewardedAds', availabilityConfig, @@ -414,12 +413,7 @@ export function withTargetAvailability( config: TargetConfig, options: TargetAvailabilityOptions = {}, ): TargetConfiguredGateway { - const availabilityConfig = options.effectiveConfig === undefined - ? config - : { - ...config, - features: options.effectiveConfig.features, - }; + const availabilityConfig = resolveAvailabilityConfig(config, options.effectiveConfig); const getGatewayCapabilities = (): Promise => ( gateway.getCapabilities() ); @@ -602,6 +596,18 @@ export function withTargetAvailability( }; } +function resolveAvailabilityConfig( + config: TargetConfig, + effectiveConfig: EffectiveTargetConfig | undefined, +): TargetConfig { + return effectiveConfig === undefined + ? config + : { + ...config, + features: effectiveConfig.features, + }; +} + export function isTargetConfiguredGateway( gateway: PlatformGateway, ): gateway is TargetConfiguredGateway { From cdeb8926a29b66195d7dbdf06e79c09690b2be73 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 22:47:30 +0900 Subject: [PATCH 3/7] chore(ait): clarify storage recovery --- adapters/ait/src/host.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adapters/ait/src/host.ts b/adapters/ait/src/host.ts index f37484bf..d9a66742 100644 --- a/adapters/ait/src/host.ts +++ b/adapters/ait/src/host.ts @@ -438,7 +438,7 @@ async function showInterstitialAd( dependencies: AitHostDependencies, adGroupId: string, timeoutMs: number, -): Promise<{ readonly status: 'shown' | 'skipped' | 'unavailable' }> { +): Promise<{ readonly status: 'shown' | 'skipped' }> { const status = await showAd(dependencies, adGroupId, timeoutMs); return { status: status === 'failed' ? 'skipped' : status }; } @@ -593,6 +593,8 @@ function decodeStoredValue(serialized: string | null): BridgeStorageLoadData { value: JSON.parse(serialized) as unknown, }; } catch { + // Malformed native state is treated as absent so the game can recover with + // its versioned defaults instead of entering a permanent load-error loop. return { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false }; } } From 784fb94386042e9bfead1f519649afcbd561a115 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 22:57:30 +0900 Subject: [PATCH 4/7] test(ait): improve production diagnostics --- apps/target-ait/vite.config.ts | 8 +++++++- tools/smoke/adapter-effective-config.ts | 16 ++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/target-ait/vite.config.ts b/apps/target-ait/vite.config.ts index f07ba334..1b3a63d1 100644 --- a/apps/target-ait/vite.config.ts +++ b/apps/target-ait/vite.config.ts @@ -43,7 +43,13 @@ function readAitAdConfig(path: string | undefined): { return { adGroupIds: {}, adPlacementTypes: {} }; } - const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + let parsed: unknown; + + try { + parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + } catch { + throw new Error(`AIT ad placements file is unreadable or invalid JSON: ${path}`); + } if (!isRecord(parsed) || !Array.isArray(parsed.placements)) { throw new Error(`AIT ad placements file is invalid: ${path}`); diff --git a/tools/smoke/adapter-effective-config.ts b/tools/smoke/adapter-effective-config.ts index 1b1d063e..fb47b1e7 100644 --- a/tools/smoke/adapter-effective-config.ts +++ b/tools/smoke/adapter-effective-config.ts @@ -265,12 +265,16 @@ async function verifyAitAdapter(): Promise { }, 'ait reward should fail closed without authoritative game services', ); - await gateway.leaderboard.submitScore({ - leaderboardId: 'default', - score: 1, - runId: 'ait-native-leaderboard-run', - submittedAt: new Date().toISOString(), - }); + assertDeepEqual( + await gateway.leaderboard.submitScore({ + leaderboardId: 'default', + score: 1, + runId: 'ait-native-leaderboard-run', + submittedAt: new Date().toISOString(), + }), + { submitted: true }, + 'ait leaderboard submission should succeed with native delegation', + ); assertDeepEqual( bridge.methods.slice(1), From d48055d3987977784fea2c3f572fb0b5ae175620 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 23:25:25 +0900 Subject: [PATCH 5/7] fix(graph): follow AIT host bridge parsing --- tools/graph/requests/bridge-contracts.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/graph/requests/bridge-contracts.json b/tools/graph/requests/bridge-contracts.json index d8b8788f..c2c36569 100644 --- a/tools/graph/requests/bridge-contracts.json +++ b/tools/graph/requests/bridge-contracts.json @@ -1,11 +1,11 @@ { "name": "bridge-contracts", - "description": "Bridge protocol validators, fixtures, and wrapper parsing flow.", - "expectedFiles": ["packages/bridge/", "apps/target-ait/"], + "description": "Bridge protocol validators, fixtures, and Apps in Toss host parsing flow.", + "expectedFiles": ["packages/bridge/", "adapters/ait/"], "tsconfig": "tsconfig.graph.json", - "question": "What are the central bridge protocol validator, fixture, and wrapper parsing flows?", - "draft": "Use a tour because the bridge contract crosses protocol validators, fixtures, and Apps in Toss wrapper parsing.", - "review": "A bridge contract question should use a focused graph tour over validators, fixtures, and wrapper parsing.", + "question": "What are the central bridge protocol validator, fixture, and Apps in Toss host parsing flows?", + "draft": "Use a tour because the bridge contract crosses protocol validators, fixtures, and Apps in Toss host parsing.", + "review": "A bridge contract question should use a focused graph tour over validators, fixtures, and Apps in Toss host parsing.", "request": { "type": "tour", "query": "assertBridgeRequest assertBridgeResponse BridgeRequest BridgeResponse bridge protocol fixture invalid valid parseBridgeRequest", From 606ab0692f9a959e09517bad6861476fd7d1c477 Mon Sep 17 00:00:00 2001 From: imjlk Date: Sun, 19 Jul 2026 23:39:35 +0900 Subject: [PATCH 6/7] fix(ait): align production host contracts --- .sampo/changesets/ait-production-host.md | 8 +- README.md | 6 +- adapters/ait/src/host.test.ts | 528 +++++++++++++++- adapters/ait/src/host.ts | 566 +++++++++++++++--- apps/target-ait/README.md | 7 +- examples/phaser-starter/vite.shared.ts | 13 + .../cli/templates/phaser-game/vite.shared.ts | 13 + packages/target-config/targets.json | 2 +- tools/smoke/game-owned-catalog.ts | 1 + 9 files changed, 1045 insertions(+), 99 deletions(-) diff --git a/.sampo/changesets/ait-production-host.md b/.sampo/changesets/ait-production-host.md index 1be704fb..2488f6f9 100644 --- a/.sampo/changesets/ait-production-host.md +++ b/.sampo/changesets/ait-production-host.md @@ -4,10 +4,10 @@ npm/@mpgd/target-config: minor (Changed) npm/@mpgd/cli: minor (Added) --- -Add a reusable Apps in Toss production host bridge backed by the official anonymous identity, -Storage, Ads 2.0, sharing, lifecycle, and Game Center APIs. Rewarded ads now forward callback -evidence without issuing demo grants, and commerce stays fail-closed until a game installs its -own verified purchase flow. +Add a reusable Apps in Toss production host bridge backed by the official game identity, +Storage, Ads 2.0, sharing, lifecycle, and Game Center APIs. Rewarded ads load before display and +forward callback evidence without issuing demo grants, while commerce stays fail-closed until a +game installs its own verified purchase flow. Allow game targets to opt out of authoritative game services. Opted-out targets disable IAP and ad features in their effective configuration and no longer require a production backend URL, diff --git a/README.md b/README.md index 13e37cab..3c2b7968 100644 --- a/README.md +++ b/README.md @@ -424,9 +424,9 @@ for target overrides, native staging, and the Apps in Toss console URL gate. ## Apps in Toss The Apps in Toss target uses SDK 2.x-compatible `granite.config.ts` and `ait -build` scripts. The reusable production host resolves a stable mini-app-scoped -player id with `getAnonymousKey`, persists gateway state with native `Storage`, -and delegates sharing, Game Center, and Ads 2.0 to the official SDK. Purchases +build` scripts. The reusable production host resolves a stable game-scoped +player id with the game-only `getUserKeyForGame`, persists gateway state with +native `Storage`, and delegates sharing, Game Center, and Ads 2.0 to the official SDK. Purchases stay fail-closed, and rewarded-ad callbacks remain evidence until a game-owned authority verifies them. SDK 3.x keeps the feature interface compatible but renames the config file to `apps-in-toss.config.ts`, so that migration should be diff --git a/adapters/ait/src/host.test.ts b/adapters/ait/src/host.test.ts index 991b40b0..265a4d1d 100644 --- a/adapters/ait/src/host.test.ts +++ b/adapters/ait/src/host.test.ts @@ -1,11 +1,11 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import type { BridgeRequest } from '@mpgd/bridge'; import { createAitHostBridge, shareIntent, type AitHostDependencies } from './host'; describe('AIT production host bridge', () => { - it('uses the native anonymous identity and persistent string storage', async () => { + it('uses the native game identity and persistent string storage', async () => { const values = new Map(); const bridge = createAitHostBridge({ dependencies: createDependencies({ @@ -41,7 +41,7 @@ describe('AIT production host bridge', () => { }); }); - it('fails closed when the native anonymous identity is invalid', async () => { + it('fails closed when the native game identity is invalid', async () => { const bridge = createAitHostBridge({ dependencies: createDependencies({ identityProvider: async () => ({ type: 'HASH' }) }), }); @@ -89,9 +89,62 @@ describe('AIT production host bridge', () => { })).resolves.toEqual({ status: 'unavailable', rewardGranted: false }); }); + it('returns a protocol error for a malformed runtime bridge call', async () => { + const bridge = createAitHostBridge({ dependencies: createDependencies() }); + const response: unknown = await Reflect.apply(bridge.request, bridge, [null]); + const legacyResponse: unknown = await Reflect.apply(bridge.request, bridge, [{ + id: 'legacy-request', + method: 'runtime.getCapabilities', + payload: {}, + }]); + + expect(response).toMatchObject({ + id: 'ait-invalid-request', + ok: false, + error: { + code: 'AIT_BRIDGE_REQUEST_FAILED', + retryable: true, + }, + }); + expect(legacyResponse).toMatchObject({ + id: 'legacy-request', + ok: false, + error: { code: 'AIT_BRIDGE_REQUEST_FAILED' }, + }); + }); + + it('treats a configured preload as a no-op when Ads 2.0 is unsupported', async () => { + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies(), + }); + + await expect(request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + })).resolves.toEqual({}); + await expect(request(bridge, 'ads.preload', { + placementId: 'UNKNOWN_PLACEMENT', + })).rejects.toThrow('AIT ad placement is unavailable: UNKNOWN_PLACEMENT'); + expect(warning).toHaveBeenCalledOnce(); + expect(warning).toHaveBeenCalledWith( + 'AIT full-screen ads are not supported; configured preload is a no-op.', + 'SUDOKU_HINT_REWARDED', + ); + } finally { + warning.mockRestore(); + } + }); + it('grants a configured rewarded ad only after userEarnedReward and dismissal', async () => { let loadCallbacks: LoadAdCallbacks | undefined; let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); const dependencies = createDependencies({ loadFullScreenAd: Object.assign( (callbacks: LoadAdCallbacks) => { @@ -103,6 +156,7 @@ describe('AIT production host bridge', () => { showFullScreenAd: Object.assign( (callbacks: ShowAdCallbacks) => { showCallbacks = callbacks; + markShowRegistered(); return () => {}; }, { isSupported: () => true }, @@ -122,6 +176,7 @@ describe('AIT production host bridge', () => { placementId: 'SUDOKU_HINT_REWARDED', idempotencyKey: 'reward-correlation-1', }); + await showRegistered; showCallbacks?.onEvent({ type: 'userEarnedReward', data: { unitType: 'hint', unitAmount: 1 }, @@ -143,9 +198,474 @@ describe('AIT production host bridge', () => { }); }); + it('preserves an earned reward during a long displayed ad', async () => { + vi.useFakeTimers(); + try { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + adTimeoutMs: 50, + adDisplayStartTimeoutMs: 100, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + markShowRegistered(); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-long-display', + }); + await showRegistered; + showCallbacks?.onEvent({ type: 'show' }); + await vi.advanceTimersByTimeAsync(10 * 60_000); + showCallbacks?.onEvent({ + type: 'userEarnedReward', + data: { unitType: 'hint', unitAmount: 1 }, + }); + showCallbacks?.onEvent({ type: 'dismissed' }); + + await expect(reward).resolves.toMatchObject({ + status: 'completed', + rewardGranted: true, + ledgerEntryId: 'reward-long-display', + }); + } finally { + vi.useRealTimers(); + } + }); + + it('reports a long displayed interstitial after native dismissal', async () => { + vi.useFakeTimers(); + try { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_BREAK_INTERSTITIAL: 'ait-ad-group-interstitial' }, + adPlacementTypes: { SUDOKU_BREAK_INTERSTITIAL: 'interstitial' }, + adTimeoutMs: 50, + adDisplayStartTimeoutMs: 100, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + markShowRegistered(); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_BREAK_INTERSTITIAL', + }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + + const interstitial = request(bridge, 'ads.showInterstitial', { + placementId: 'SUDOKU_BREAK_INTERSTITIAL', + }); + await showRegistered; + showCallbacks?.onEvent({ type: 'show' }); + await vi.advanceTimersByTimeAsync(10 * 60_000); + showCallbacks?.onEvent({ type: 'dismissed' }); + + await expect(interstitial).resolves.toEqual({ status: 'shown' }); + } finally { + vi.useRealTimers(); + } + }); + + it('recovers a rewarded ad when the native terminal callback is omitted', async () => { + vi.useFakeTimers(); + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + adTimeoutMs: 50, + adMaximumDisplayMs: 100, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + markShowRegistered(); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-missing-dismissal', + }); + await showRegistered; + showCallbacks?.onEvent({ type: 'show' }); + showCallbacks?.onEvent({ + type: 'userEarnedReward', + data: { unitType: 'hint', unitAmount: 1 }, + }); + await vi.advanceTimersByTimeAsync(100); + + await expect(reward).resolves.toMatchObject({ + status: 'completed', + rewardGranted: true, + ledgerEntryId: 'reward-missing-dismissal', + }); + expect(warning).toHaveBeenCalledWith( + 'AIT full-screen ad omitted its terminal callback; recovering the game lifecycle.', + 'ait-ad-group-1', + ); + } finally { + warning.mockRestore(); + vi.useRealTimers(); + } + }); + + it('keeps the requested-to-display wait inside the total show timeout', async () => { + vi.useFakeTimers(); + try { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + adTimeoutMs: 50, + adDisplayStartTimeoutMs: 100, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + markShowRegistered(); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const preload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + loadCallbacks?.onEvent({ type: 'loaded' }); + await preload; + + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-total-timeout', + }); + await showRegistered; + await vi.advanceTimersByTimeAsync(40); + showCallbacks?.onEvent({ type: 'requested' }); + await vi.advanceTimersByTimeAsync(10); + + await expect(reward).resolves.toEqual({ status: 'failed', rewardGranted: false }); + } finally { + vi.useRealTimers(); + } + }); + + it('treats a shared explicit preload failure as non-fatal and logs it once', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const firstPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + const secondPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + loadCallbacks?.onError(new Error('native load failed')); + + await expect(firstPreload).resolves.toEqual({}); + await expect(secondPreload).resolves.toEqual({}); + expect(warning).toHaveBeenCalledTimes(1); + } finally { + warning.mockRestore(); + } + }); + + it('serializes native loads for different full-screen ad groups', async () => { + const loads: Array<{ + readonly adGroupId: string; + readonly callbacks: LoadAdCallbacks; + }> = []; + const bridge = createAitHostBridge({ + adGroupIds: { + SUDOKU_HINT_REWARDED: 'ait-ad-group-rewarded', + SUDOKU_BREAK_INTERSTITIAL: 'ait-ad-group-interstitial', + }, + adPlacementTypes: { + SUDOKU_HINT_REWARDED: 'rewarded', + SUDOKU_BREAK_INTERSTITIAL: 'interstitial', + }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loads.push({ adGroupId: callbacks.options?.adGroupId ?? 'missing-ad-group', callbacks }); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const rewardedPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + const interstitialPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_BREAK_INTERSTITIAL', + }); + + expect(loads.map(({ adGroupId }) => adGroupId)).toEqual(['ait-ad-group-rewarded']); + loads[0]?.callbacks.onEvent({ type: 'loaded' }); + await rewardedPreload; + await vi.waitFor(() => { + expect(loads.map(({ adGroupId }) => adGroupId)).toEqual([ + 'ait-ad-group-rewarded', + 'ait-ad-group-interstitial', + ]); + }); + loads[1]?.callbacks.onEvent({ type: 'loaded' }); + + await expect(interstitialPreload).resolves.toEqual({}); + }); + + it('does not let a hung native load extend the next group timeout', async () => { + vi.useFakeTimers(); + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const nativeLoadGroups: string[] = []; + const bridge = createAitHostBridge({ + adGroupIds: { + SUDOKU_HINT_REWARDED: 'ait-ad-group-rewarded', + SUDOKU_BREAK_INTERSTITIAL: 'ait-ad-group-interstitial', + }, + adPlacementTypes: { + SUDOKU_HINT_REWARDED: 'rewarded', + SUDOKU_BREAK_INTERSTITIAL: 'interstitial', + }, + adTimeoutMs: 50, + adLoadQueueTimeoutMs: 20, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + nativeLoadGroups.push(callbacks.options?.adGroupId ?? 'missing-ad-group'); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const rewardedPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + }); + const interstitialPreload = request(bridge, 'ads.preload', { + placementId: 'SUDOKU_BREAK_INTERSTITIAL', + }); + await vi.advanceTimersByTimeAsync(20); + + await expect(interstitialPreload).resolves.toEqual({}); + expect(nativeLoadGroups).toEqual(['ait-ad-group-rewarded']); + expect(warning).toHaveBeenCalledOnce(); + + await vi.advanceTimersByTimeAsync(30); + await expect(rewardedPreload).resolves.toEqual({}); + expect(warning).toHaveBeenCalledTimes(2); + } finally { + warning.mockRestore(); + vi.useRealTimers(); + } + }); + + it('loads a configured rewarded ad before a direct show request', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + (callbacks: ShowAdCallbacks) => { + showCallbacks = callbacks; + markShowRegistered(); + return () => {}; + }, + { isSupported: () => true }, + ), + }), + }); + + const reward = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-direct-show', + }); + + expect(loadCallbacks).toBeDefined(); + expect(showCallbacks).toBeUndefined(); + loadCallbacks?.onEvent({ type: 'loaded' }); + await showRegistered; + showCallbacks?.onEvent({ + type: 'userEarnedReward', + data: { unitType: 'hint', unitAmount: 1 }, + }); + showCallbacks?.onEvent({ type: 'dismissed' }); + + await expect(reward).resolves.toMatchObject({ + status: 'completed', + rewardGranted: true, + ledgerEntryId: 'reward-direct-show', + }); + }); + + it('logs one diagnostic when concurrent shows share a failed ad load', async () => { + let loadCallbacks: LoadAdCallbacks | undefined; + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const bridge = createAitHostBridge({ + adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, + adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, + dependencies: createDependencies({ + loadFullScreenAd: Object.assign( + (callbacks: LoadAdCallbacks) => { + loadCallbacks = callbacks; + return () => {}; + }, + { isSupported: () => true }, + ), + showFullScreenAd: Object.assign( + () => () => {}, + { isSupported: () => true }, + ), + }), + }); + + const firstShow = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-load-failure-1', + }); + const secondShow = request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-load-failure-2', + }); + loadCallbacks?.onError(new Error('native load failed')); + + await expect(firstShow).resolves.toEqual({ + status: 'unavailable', + rewardGranted: false, + }); + await expect(secondShow).resolves.toEqual({ + status: 'unavailable', + rewardGranted: false, + }); + expect(warning).toHaveBeenCalledTimes(1); + } finally { + warning.mockRestore(); + } + }); + it('does not grant a reward when the ad is dismissed without the reward event', async () => { let loadCallbacks: LoadAdCallbacks | undefined; let showCallbacks: ShowAdCallbacks | undefined; + let markShowRegistered = (): void => {}; + const showRegistered = new Promise((resolve) => { + markShowRegistered = resolve; + }); const bridge = createAitHostBridge({ adGroupIds: { SUDOKU_HINT_REWARDED: 'ait-ad-group-1' }, adPlacementTypes: { SUDOKU_HINT_REWARDED: 'rewarded' }, @@ -160,6 +680,7 @@ describe('AIT production host bridge', () => { showFullScreenAd: Object.assign( (callbacks: ShowAdCallbacks) => { showCallbacks = callbacks; + markShowRegistered(); return () => {}; }, { isSupported: () => true }, @@ -174,6 +695,7 @@ describe('AIT production host bridge', () => { placementId: 'SUDOKU_HINT_REWARDED', idempotencyKey: 'reward-correlation-2', }); + await showRegistered; showCallbacks?.onEvent({ type: 'dismissed' }); await expect(reward).resolves.toEqual({ status: 'skipped', rewardGranted: false }); diff --git a/adapters/ait/src/host.ts b/adapters/ait/src/host.ts index d9a66742..af84a583 100644 --- a/adapters/ait/src/host.ts +++ b/adapters/ait/src/host.ts @@ -1,6 +1,6 @@ import { - getAnonymousKey, getTossShareLink, + getUserKeyForGame, isMinVersionSupported, loadFullScreenAd, openGameCenterLeaderboard, @@ -11,6 +11,7 @@ import { } from '@apps-in-toss/web-framework'; import { + assertBridgeRequest, bridgeStorageLoadProtocol, type BridgeRequest, type BridgeResponse, @@ -22,6 +23,10 @@ import type { GamePlatformBridge } from './index.js'; import { dispatchAitLifecycleEvent } from './lifecycle.js'; const defaultAdTimeoutMs = 60_000; +const defaultAdLoadQueueTimeoutMs = 5_000; +const defaultAdDisplayStartTimeoutMs = 60_000; +const defaultAdMaximumDisplayMs = 30 * 60_000; +const invalidBridgeRequestId = 'ait-invalid-request'; const rewardedAdEvidenceSchema = 'apps-in-toss.rewarded-ad.callback.v1'; const launchEntries = new Set([ 'home', @@ -51,12 +56,19 @@ export interface InstallAitHostBridgeOptions { readonly appName?: string; readonly adGroupIds?: Readonly>; readonly adPlacementTypes?: Readonly>; + /** Maximum total wait for the native show request before display is observed. */ readonly adTimeoutMs?: number; + /** Maximum wait to acquire the process-wide native full-screen load slot. */ + readonly adLoadQueueTimeoutMs?: number; + /** Upper bound for the requested-to-display portion of the total show timeout. */ + readonly adDisplayStartTimeoutMs?: number; + /** Last-resort cleanup when the native SDK omits its terminal display callback. */ + readonly adMaximumDisplayMs?: number; readonly dependencies?: Partial; } const defaultDependencies: AitHostDependencies = { - identityProvider: getAnonymousKey, + identityProvider: getUserKeyForGame, storage: Storage, getTossShareLink, share, @@ -81,7 +93,17 @@ export function createAitHostBridge( const adGroupIds = normalizeAdGroupIds(options.adGroupIds); const adPlacementTypes = normalizeAdPlacementTypes(options.adPlacementTypes); const loadedAdGroupIds = new Set(); + const loadingAdGroups = new Map>(); + const activeAdGroupIds = new Set(); + let warnedUnsupportedAdPreload = false; const adTimeoutMs = normalizeTimeout(options.adTimeoutMs); + const adLoadQueueTimeoutMs = normalizeLoadQueueTimeout(options.adLoadQueueTimeoutMs); + const adDisplayStartTimeoutMs = normalizeDisplayStartTimeout(options.adDisplayStartTimeoutMs); + const adMaximumDisplayMs = normalizeMaximumDisplayTimeout(options.adMaximumDisplayMs); + const adLoadCoordinator: AitAdLoadCoordinator = { + active: undefined, + waitTimeoutMs: adLoadQueueTimeoutMs, + }; return { async request(input) { @@ -89,7 +111,7 @@ export function createAitHostBridge( return await handleRequest(parseBridgeRequest(input)); } catch (error) { return createBridgeError( - input.id, + readBridgeRequestId(input), 'AIT_BRIDGE_REQUEST_FAILED', errorMessage(error), true, @@ -188,12 +210,9 @@ export function createAitHostBridge( case 'ads.preload': { const placementId = readPlacementId(request.payload); const adGroupId = adGroupIds.get(placementId); + const placementType = adPlacementTypes.get(placementId); - if ( - adGroupId === undefined - || adPlacementTypes.get(placementId) === undefined - || !dependencies.loadFullScreenAd.isSupported() - ) { + if (adGroupId === undefined || placementType === undefined) { return createBridgeError( request.id, 'AIT_AD_UNAVAILABLE', @@ -201,7 +220,26 @@ export function createAitHostBridge( ); } - await preloadAdGroup(dependencies, adGroupId, loadedAdGroupIds, adTimeoutMs); + if (!dependencies.loadFullScreenAd.isSupported()) { + if (!warnedUnsupportedAdPreload) { + warnedUnsupportedAdPreload = true; + console.warn( + 'AIT full-screen ads are not supported; configured preload is a no-op.', + placementId, + ); + } + return ok(request, {}); + } + + await preloadAdGroupWithDiagnostics( + dependencies, + adGroupId, + loadedAdGroupIds, + loadingAdGroups, + adLoadCoordinator, + adTimeoutMs, + placementType, + ); return ok(request, {}); } @@ -212,34 +250,53 @@ export function createAitHostBridge( if ( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'rewarded' + || !dependencies.loadFullScreenAd.isSupported() || !dependencies.showFullScreenAd.isSupported() - || !consumeLoadedAd(adGroupId, loadedAdGroupIds) ) { return ok(request, { status: 'unavailable', rewardGranted: false }); } - const correlationId = readIdempotencyKey(request.payload, request.id); - const result = await showRewardedAd(dependencies, adGroupId, adTimeoutMs); - - return ok( - request, - result.rewardGranted - ? { - ...result, - // game-services forwards this as platformImpressionId and compares it - // with the native callback correlationId during authority verification. - ledgerEntryId: correlationId, - evidence: { - schema: rewardedAdEvidenceSchema, - payload: { - event: 'user-earned-reward', - correlationId, - placementId: adGroupId, + const shown = await withLoadedAdSlot( + dependencies, + adGroupId, + loadedAdGroupIds, + loadingAdGroups, + adLoadCoordinator, + activeAdGroupIds, + adTimeoutMs, + 'rewarded', + async () => { + const correlationId = readIdempotencyKey(request.payload, request.id); + const result = await showRewardedAd( + dependencies, + adGroupId, + adTimeoutMs, + adDisplayStartTimeoutMs, + adMaximumDisplayMs, + ); + + return result.rewardGranted + ? { + ...result, + // game-services forwards this as platformImpressionId and compares it + // with the native callback correlationId during authority verification. + ledgerEntryId: correlationId, + evidence: { + schema: rewardedAdEvidenceSchema, + payload: { + event: 'user-earned-reward', + correlationId, + placementId: adGroupId, + }, }, - }, - } - : result, + } + : result; + }, ); + if (!shown.acquired) { + return ok(request, { status: 'unavailable', rewardGranted: false }); + } + return ok(request, shown.value); } case 'ads.showInterstitial': { @@ -249,13 +306,31 @@ export function createAitHostBridge( if ( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'interstitial' + || !dependencies.loadFullScreenAd.isSupported() || !dependencies.showFullScreenAd.isSupported() - || !consumeLoadedAd(adGroupId, loadedAdGroupIds) ) { return ok(request, { status: 'unavailable' }); } - return ok(request, await showInterstitialAd(dependencies, adGroupId, adTimeoutMs)); + const showInterstitial = () => showInterstitialAd( + dependencies, + adGroupId, + adTimeoutMs, + adDisplayStartTimeoutMs, + adMaximumDisplayMs, + ); + const shown = await withLoadedAdSlot( + dependencies, + adGroupId, + loadedAdGroupIds, + loadingAdGroups, + adLoadCoordinator, + activeAdGroupIds, + adTimeoutMs, + 'interstitial', + showInterstitial, + ); + return ok(request, shown.acquired ? shown.value : { status: 'unavailable' }); } case 'leaderboard.submitScore': { @@ -372,15 +447,28 @@ async function preloadAdGroup( dependencies: AitHostDependencies, adGroupId: string, loaded: Set, + loading: Map>, + coordinator: AitAdLoadCoordinator, timeoutMs: number, ): Promise { if (loaded.has(adGroupId)) { return; } - await new Promise((resolve, reject) => { + const existing = loading.get(adGroupId); + if (existing !== undefined) { + await existing; + return; + } + + const startNativeLoad = (): Promise => new Promise((resolve, reject) => { let cleanup = (): void => {}; - const finish = once((error?: unknown) => { + let settled = false; + const finish = (error?: unknown): void => { + if (settled) { + return; + } + settled = true; globalThis.clearTimeout(timer); cleanup(); if (error === undefined) { @@ -389,35 +477,232 @@ async function preloadAdGroup( } else { reject(error); } - }); + }; const timer = globalThis.setTimeout( () => finish(new Error(`Timed out loading AIT ad group ${adGroupId}.`)), timeoutMs, ); - cleanup = dependencies.loadFullScreenAd({ - options: { adGroupId }, - onEvent: (event) => { - if (event.type === 'loaded') { - finish(); - } - }, - onError: finish, - }); + try { + const unregister = dependencies.loadFullScreenAd({ + options: { adGroupId }, + onEvent: (event) => { + if (event.type === 'loaded') { + finish(); + } + }, + onError: finish, + }); + cleanup = unregister; + if (settled) { + cleanup(); + } + } catch (error) { + finish(error); + } }); + const pending = runSerializedAdLoad(coordinator, adGroupId, startNativeLoad); + loading.set(adGroupId, pending); + + try { + await pending; + } finally { + // Clean up this load attempt. The identity check also protects a future retry + // implementation from deleting a replacement promise. + if (loading.get(adGroupId) === pending) { + loading.delete(adGroupId); + } + } +} + +interface AitAdLoadCoordinator { + active: { readonly promise: Promise } | undefined; + readonly waitTimeoutMs: number; +} + +async function runSerializedAdLoad( + coordinator: AitAdLoadCoordinator, + adGroupId: string, + startNativeLoad: () => Promise, +): Promise { + const waitDeadline = Date.now() + coordinator.waitTimeoutMs; + + while (coordinator.active !== undefined) { + const remainingWaitMs = waitDeadline - Date.now(); + if (remainingWaitMs <= 0) { + throw new Error( + `Timed out waiting for the AIT ad load slot: ${adGroupId} (queue deadline exceeded).`, + ); + } + await waitForAdLoadSettlement(coordinator.active.promise, remainingWaitMs, adGroupId); + } + + // Some deployed Toss runtimes lose callbacks when different groups begin + // loading together. Acquire the process-wide native boundary synchronously; + // the actual native load still receives its complete timeout budget. + const pending = startNativeLoad(); + coordinator.active = { promise: pending }; + try { + await pending; + } finally { + if (coordinator.active?.promise === pending) { + coordinator.active = undefined; + } + } +} + +function waitForAdLoadSettlement( + active: Promise, + timeoutMs: number, + adGroupId: string, +): Promise { + return new Promise((resolve, reject) => { + let settled = false; + const finish = (error?: Error): void => { + if (settled) { + return; + } + settled = true; + globalThis.clearTimeout(timer); + if (error === undefined) { + resolve(); + } else { + reject(error); + } + }; + const timer = globalThis.setTimeout( + () => finish(new Error( + `Timed out waiting for the AIT ad load slot: ${adGroupId} (active load wait expired).`, + )), + timeoutMs, + ); + void active.then( + () => finish(), + () => finish(), + ); + }); +} + +async function acquireLoadedAdSlot( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + loading: Map>, + coordinator: AitAdLoadCoordinator, + active: Set, + timeoutMs: number, + placementType: 'rewarded' | 'interstitial', +): Promise { + if (active.has(adGroupId)) { + return false; + } + + const loadedSuccessfully = await preloadAdGroupWithDiagnostics( + dependencies, + adGroupId, + loaded, + loading, + coordinator, + timeoutMs, + placementType, + ); + if (!loadedSuccessfully) { + return false; + } + + // Re-check after the asynchronous load so concurrent callers cannot consume + // or display the same native ad slot twice. + if (active.has(adGroupId) || !consumeLoadedAd(adGroupId, loaded)) { + return false; + } + + active.add(adGroupId); + return true; +} + +async function preloadAdGroupWithDiagnostics( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + loading: Map>, + coordinator: AitAdLoadCoordinator, + timeoutMs: number, + placementType: 'rewarded' | 'interstitial', +): Promise { + // The first caller enters preloadAdGroup synchronously and publishes its promise + // before yielding. Remember that ownership so concurrent waiters do not emit the + // same diagnostic for one native load failure. + const ownsLoadAttempt = !loaded.has(adGroupId) && !loading.has(adGroupId); + + try { + await preloadAdGroup(dependencies, adGroupId, loaded, loading, coordinator, timeoutMs); + return true; + } catch (error) { + // Preloading is opportunistic. A later show request may retry, so keep + // gameplay available while retaining one diagnostic per native attempt. + if (ownsLoadAttempt) { + console.warn(`Failed to preload ${placementType} AIT ad group.`, adGroupId, error); + } + return false; + } +} + +type LoadedAdSlotResult = + | { readonly acquired: false } + | { readonly acquired: true; readonly value: Value }; + +async function withLoadedAdSlot( + dependencies: AitHostDependencies, + adGroupId: string, + loaded: Set, + loading: Map>, + coordinator: AitAdLoadCoordinator, + active: Set, + timeoutMs: number, + placementType: 'rewarded' | 'interstitial', + display: () => Promise, +): Promise> { + const acquired = await acquireLoadedAdSlot( + dependencies, + adGroupId, + loaded, + loading, + coordinator, + active, + timeoutMs, + placementType, + ); + if (!acquired) { + return { acquired: false }; + } + + try { + return { acquired: true, value: await display() }; + } finally { + active.delete(adGroupId); + } } async function showRewardedAd( dependencies: AitHostDependencies, adGroupId: string, timeoutMs: number, + displayStartTimeoutMs: number, + maximumDisplayMs: number, ): Promise<{ readonly status: 'completed' | 'skipped' | 'failed'; readonly rewardGranted: boolean }> { let rewardGranted = false; - const status = await showAd(dependencies, adGroupId, timeoutMs, (eventType) => { - if (eventType === 'userEarnedReward') { - rewardGranted = true; - } - }); + const status = await showAd( + dependencies, + adGroupId, + timeoutMs, + displayStartTimeoutMs, + maximumDisplayMs, + (eventType) => { + if (eventType === 'userEarnedReward') { + rewardGranted = true; + } + }, + ); let resultStatus: 'completed' | 'skipped' | 'failed'; if (status === 'shown' && rewardGranted) { @@ -438,8 +723,16 @@ async function showInterstitialAd( dependencies: AitHostDependencies, adGroupId: string, timeoutMs: number, + displayStartTimeoutMs: number, + maximumDisplayMs: number, ): Promise<{ readonly status: 'shown' | 'skipped' }> { - const status = await showAd(dependencies, adGroupId, timeoutMs); + const status = await showAd( + dependencies, + adGroupId, + timeoutMs, + displayStartTimeoutMs, + maximumDisplayMs, + ); return { status: status === 'failed' ? 'skipped' : status }; } @@ -447,32 +740,105 @@ function showAd( dependencies: AitHostDependencies, adGroupId: string, timeoutMs: number, + displayStartTimeoutMs: number, + maximumDisplayMs: number, observe?: (eventType: string) => void, ): Promise<'shown' | 'failed'> { dispatchAitLifecycleEvent('pause'); return new Promise((resolve) => { + const startedAt = Date.now(); let cleanup = (): void => {}; - const finish = once((status: 'shown' | 'failed') => { - globalThis.clearTimeout(timer); + let settled = false; + let maximumDisplayTimeoutArmed = false; + let adTimer: ReturnType | undefined; + const clearAdTimer = (): void => { + if (adTimer !== undefined) { + globalThis.clearTimeout(adTimer); + adTimer = undefined; + } + }; + const finish = (status: 'shown' | 'failed'): void => { + if (settled) { + return; + } + settled = true; + clearAdTimer(); cleanup(); dispatchAitLifecycleEvent('resume'); resolve(status); - }); - const timer = globalThis.setTimeout(() => finish('failed'), timeoutMs); - - cleanup = dependencies.showFullScreenAd({ - options: { adGroupId }, - onEvent: (event) => { - observe?.(event.type); - if (event.type === 'dismissed') { - finish('shown'); - } else if (event.type === 'failedToShow') { - finish('failed'); - } - }, - onError: () => finish('failed'), - }); + }; + const armDisplayStartTimeout = (): void => { + clearAdTimer(); + const elapsedMs = Math.max(0, Date.now() - startedAt); + const remainingMs = Math.max(0, timeoutMs - elapsedMs); + adTimer = globalThis.setTimeout( + () => finish('failed'), + Math.min(displayStartTimeoutMs, remainingMs), + ); + }; + const armMaximumDisplayTimeout = (): void => { + if (maximumDisplayTimeoutArmed) { + return; + } + maximumDisplayTimeoutArmed = true; + clearAdTimer(); + adTimer = globalThis.setTimeout(() => { + console.warn( + 'AIT full-screen ad omitted its terminal callback; recovering the game lifecycle.', + adGroupId, + ); + finish('shown'); + }, maximumDisplayMs); + }; + const failShow = (error?: unknown): void => { + if (settled) { + return; + } + console.warn( + 'Failed to show AIT full-screen ad.', + adGroupId, + error ?? 'unknown native error', + ); + finish('failed'); + }; + adTimer = globalThis.setTimeout(() => finish('failed'), timeoutMs); + + try { + const unregister = dependencies.showFullScreenAd({ + options: { adGroupId }, + onEvent: (event) => { + observe?.(event.type); + switch (event.type) { + case 'requested': + armDisplayStartTimeout(); + break; + case 'show': + case 'impression': + case 'clicked': + case 'userEarnedReward': + // Native terminal callbacks remain authoritative. A long, one-shot + // recovery timeout prevents a broken SDK callback from deadlocking + // the game forever without treating ordinary end-card dwell as failure. + armMaximumDisplayTimeout(); + break; + case 'dismissed': + finish('shown'); + break; + case 'failedToShow': + finish('failed'); + break; + } + }, + onError: failShow, + }); + cleanup = unregister; + if (settled) { + cleanup(); + } + } catch (error) { + failShow(error); + } }); } @@ -626,11 +992,47 @@ function normalizeAppName(value: string): string { } function normalizeTimeout(value: number | undefined): number { + return normalizePositiveTimeout( + value, + defaultAdTimeoutMs, + 'AIT ad timeout must be a positive finite number.', + ); +} + +function normalizeLoadQueueTimeout(value: number | undefined): number { + return normalizePositiveTimeout( + value, + defaultAdLoadQueueTimeoutMs, + 'AIT ad load queue timeout must be a positive finite number.', + ); +} + +function normalizeDisplayStartTimeout(value: number | undefined): number { + return normalizePositiveTimeout( + value, + defaultAdDisplayStartTimeoutMs, + 'AIT ad display start timeout must be a positive finite number.', + ); +} + +function normalizeMaximumDisplayTimeout(value: number | undefined): number { + return normalizePositiveTimeout( + value, + defaultAdMaximumDisplayMs, + 'AIT ad maximum display timeout must be a positive finite number.', + ); +} + +function normalizePositiveTimeout( + value: number | undefined, + defaultValue: number, + message: string, +): number { if (value === undefined) { - return defaultAdTimeoutMs; + return defaultValue; } if (!Number.isFinite(value) || value <= 0) { - throw new Error('AIT ad timeout must be a positive finite number.'); + throw new Error(message); } return value; } @@ -668,11 +1070,17 @@ function readPayloadRecord(payload: unknown): Readonly> return isRecord(payload) ? payload : {}; } -function parseBridgeRequest(input: BridgeRequest): BridgeRequest { - if (typeof input.id !== 'string' || input.id.length === 0 || typeof input.method !== 'string') { - throw new TypeError('Bridge request id and method are required.'); +function parseBridgeRequest(input: unknown): BridgeRequest { + // The production host deliberately accepts only the current bridge protocol. + // Legacy partial requests are rejected rather than silently inventing metadata. + return assertBridgeRequest(input); +} + +function readBridgeRequestId(input: unknown): string { + if (!isRecord(input) || typeof input.id !== 'string' || input.id.length === 0) { + return invalidBridgeRequestId; } - return input; + return input.id; } function ok(request: BridgeRequest, data: unknown): BridgeResponse { @@ -688,18 +1096,6 @@ function createBridgeError( return { id, ok: false, error: { code, message, retryable } }; } -function once( - callback: (...args: Arguments) => void, -): (...args: Arguments) => void { - let called = false; - return (...args) => { - if (!called) { - called = true; - callback(...args); - } - }; -} - function nonEmptyParam(value: string | null): string | undefined { return value === null || value.length === 0 ? undefined : value; } diff --git a/apps/target-ait/README.md b/apps/target-ait/README.md index 36fab8ba..a2fafad9 100644 --- a/apps/target-ait/README.md +++ b/apps/target-ait/README.md @@ -6,7 +6,7 @@ Current scope: - SDK 2.x-compatible `granite.config.ts` - `ait build` script -- SDK-backed anonymous game identity through `getAnonymousKey` +- SDK-backed anonymous game identity through `getUserKeyForGame` - persistent progress through the native `Storage` API - SDK-backed sharing and Ads 2.0 callbacks - Game Center leaderboard bridge methods @@ -20,11 +20,12 @@ The target build also mirrors the copied game's `assets` directory to `public/assets` so root-absolute runtime asset requests still resolve during local devtools, phone tunnel, and wrapper-web sessions without iframe embedding. -The production bridge maps the stable mini-app-scoped `getAnonymousKey()` hash +The production bridge maps the stable game-scoped `getUserKeyForGame()` hash to `PlatformGateway.identity.getPlayer()` and serializes gateway storage values through the native `Storage` API. The `dev:plain` script enables a local identity provider explicitly with `VITE_MPGD_AIT_MOCK_IDENTITY=1`; release builds never -use that fixed local player id. Game Center requires Toss app 5.221.0 or newer. +use that fixed local player id. Game identity requires Toss app 5.232.0 or newer, +while Game Center requires Toss app 5.221.0 or newer. SDK results must still be verified with a QR test in the Toss app. Purchases remain unavailable in the reference host. Rewarded Ads 2.0 must be diff --git a/examples/phaser-starter/vite.shared.ts b/examples/phaser-starter/vite.shared.ts index 3771123f..dfd77b56 100644 --- a/examples/phaser-starter/vite.shared.ts +++ b/examples/phaser-starter/vite.shared.ts @@ -7,6 +7,7 @@ import type { UserConfig } from 'vite'; interface RuntimePlatformTargetMetadata { readonly kind: string; readonly adapter: string; + readonly authoritativeGameServices?: boolean; readonly integrations?: Record; } @@ -174,9 +175,21 @@ function readRuntimePlatformTarget( throw new Error(`Platform target ${configTarget} integrations must be an object.`); } + if ( + target.authoritativeGameServices !== undefined + && typeof target.authoritativeGameServices !== 'boolean' + ) { + throw new Error( + `Platform target ${configTarget} authoritativeGameServices must be a boolean.`, + ); + } + return { kind: target.kind, adapter: target.adapter, + ...(target.authoritativeGameServices === undefined + ? {} + : { authoritativeGameServices: target.authoritativeGameServices }), ...(target.integrations === undefined ? {} : { integrations: target.integrations }), }; } diff --git a/packages/cli/templates/phaser-game/vite.shared.ts b/packages/cli/templates/phaser-game/vite.shared.ts index 3771123f..dfd77b56 100644 --- a/packages/cli/templates/phaser-game/vite.shared.ts +++ b/packages/cli/templates/phaser-game/vite.shared.ts @@ -7,6 +7,7 @@ import type { UserConfig } from 'vite'; interface RuntimePlatformTargetMetadata { readonly kind: string; readonly adapter: string; + readonly authoritativeGameServices?: boolean; readonly integrations?: Record; } @@ -174,9 +175,21 @@ function readRuntimePlatformTarget( throw new Error(`Platform target ${configTarget} integrations must be an object.`); } + if ( + target.authoritativeGameServices !== undefined + && typeof target.authoritativeGameServices !== 'boolean' + ) { + throw new Error( + `Platform target ${configTarget} authoritativeGameServices must be a boolean.`, + ); + } + return { kind: target.kind, adapter: target.adapter, + ...(target.authoritativeGameServices === undefined + ? {} + : { authoritativeGameServices: target.authoritativeGameServices }), ...(target.integrations === undefined ? {} : { integrations: target.integrations }), }; } diff --git a/packages/target-config/targets.json b/packages/target-config/targets.json index f644bd4f..cf607229 100644 --- a/packages/target-config/targets.json +++ b/packages/target-config/targets.json @@ -1,5 +1,5 @@ { - "version": "2026-07-16.2", + "version": "2026-07-19.1", "targets": { "web-preview": { "runtime": "web-preview", diff --git a/tools/smoke/game-owned-catalog.ts b/tools/smoke/game-owned-catalog.ts index 8a8c832a..96a79367 100644 --- a/tools/smoke/game-owned-catalog.ts +++ b/tools/smoke/game-owned-catalog.ts @@ -450,6 +450,7 @@ async function assertVitePlatformTargetMetadata(): Promise { const expectedMetadata = { kind: 'apps-in-toss', adapter: 'ait', + authoritativeGameServices: false, integrations: { presentation: 'disabled', presentationMode: 'inline-expanded', From 9d384f6472889444de716f49cbb589c3e0607877 Mon Sep 17 00:00:00 2001 From: imjlk Date: Mon, 20 Jul 2026 00:39:19 +0900 Subject: [PATCH 7/7] feat(ait): scaffold game-owned wrappers --- .sampo/changesets/ait-production-host.md | 7 + README.md | 9 +- adapters/ait/package.json | 13 +- adapters/ait/src/ad-config.test.ts | 54 +++ adapters/ait/src/ad-config.ts | 54 +++ adapters/ait/src/wrapper-url.test.ts | 33 ++ adapters/ait/src/wrapper-url.ts | 65 +++ adapters/ait/src/wrapper.ts | 331 +++++++++++++++ apps/target-ait/granite.config.ts | 4 +- apps/target-ait/package.json | 6 +- apps/target-ait/src/main.ts | 387 +----------------- apps/target-ait/tsconfig.bundle.json | 2 + apps/target-ait/vite.config.ts | 40 +- docs/AGENTIC_GAME_WORKFLOW.md | 7 +- docs/GAME_DEVELOPMENT.md | 25 +- packages/cli/templates/phaser-game/README.md | 44 +- .../apps/target-ait/granite.config.ts | 35 ++ .../phaser-game/apps/target-ait/index.html | 15 + .../phaser-game/apps/target-ait/package.json | 33 ++ .../phaser-game/apps/target-ait/src/main.ts | 34 ++ .../apps/target-ait/src/styles.css | 33 ++ .../phaser-game/apps/target-ait/tsconfig.json | 8 + .../apps/target-ait/vite.config.ts | 53 +++ packages/cli/templates/phaser-game/gitignore | 6 + .../templates/phaser-game/mpgd.targets.json | 10 +- .../cli/templates/phaser-game/package.json | 14 +- pnpm-lock.yaml | 200 ++++----- pnpm-workspace.yaml | 1 + tools/validate-starter-workflow.ts | 239 +++++++++++ tsconfig.base.json | 3 + 30 files changed, 1197 insertions(+), 568 deletions(-) create mode 100644 adapters/ait/src/ad-config.test.ts create mode 100644 adapters/ait/src/ad-config.ts create mode 100644 adapters/ait/src/wrapper-url.test.ts create mode 100644 adapters/ait/src/wrapper-url.ts create mode 100644 adapters/ait/src/wrapper.ts create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/granite.config.ts create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/index.html create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/package.json create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/src/main.ts create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/src/styles.css create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/tsconfig.json create mode 100644 packages/cli/templates/phaser-game/apps/target-ait/vite.config.ts diff --git a/.sampo/changesets/ait-production-host.md b/.sampo/changesets/ait-production-host.md index 2488f6f9..087565ce 100644 --- a/.sampo/changesets/ait-production-host.md +++ b/.sampo/changesets/ait-production-host.md @@ -12,3 +12,10 @@ game installs its own verified purchase flow. Allow game targets to opt out of authoritative game services. Opted-out targets disable IAP and ad features in their effective configuration and no longer require a production backend URL, while targets that enable authoritative grants keep the public HTTPS backend release gate. + +Scaffold each generated game with its own Apps in Toss wrapper, Granite configuration, and +`@ait-co/devtools` workflow. The wrapper reuses the adapter's exported bundle loader while +keeping app identity, console state, icons, and review metadata owned by the game repository. +Expose a shared AIT ad-placement extractor for wrapper builds, preserve configuration parse +causes, and publish package metadata that the official `.ait` dependency collector can resolve +from both installed packages and Kit workspaces. diff --git a/README.md b/README.md index 3c2b7968..e416d049 100644 --- a/README.md +++ b/README.md @@ -249,11 +249,10 @@ pnpm mpgd target smoke-all --targets-file ./mpgd.targets.json --targets web,micr Generated Phaser starters own their Reddit Devvit app root in `apps/target-devvit`; run `pnpm devvit:init` once after login before live -playtest/upload. Apps in Toss and Capacitor targets currently use kit reference -shells for smoke builds, but release artifacts and manifests are copied back -under the game app's `artifacts/` and `release-output/` directories. Production -app metadata should still move into game-owned wrappers before store or Toss -submission. +playtest/upload. They also own an Apps in Toss wrapper in `apps/target-ait`, so +app identity, console state, community devtools, icons, and review metadata stay +with the game. Capacitor targets continue to use kit reference shells for smoke +builds until a game creates production-owned Android and iOS shells. Optional Microsoft Store support is modeled as a PWA/web target, not a separate native SDK adapter. `pnpm build:microsoft-store` builds the Phaser game with the diff --git a/adapters/ait/package.json b/adapters/ait/package.json index 3be71bc1..25724553 100644 --- a/adapters/ait/package.json +++ b/adapters/ait/package.json @@ -32,7 +32,16 @@ "./host": { "types": "./dist/host.d.ts", "default": "./dist/host.js" - } + }, + "./wrapper": { + "types": "./dist/wrapper.d.ts", + "default": "./dist/wrapper.js" + }, + "./ad-config": { + "types": "./dist/ad-config.d.ts", + "default": "./dist/ad-config.js" + }, + "./package.json": "./package.json" }, "files": [ "dist" @@ -57,7 +66,7 @@ } }, "devDependencies": { - "@apps-in-toss/web-framework": "^2.10.4", + "@apps-in-toss/web-framework": "^2.10.6", "ttsc": "0.18.4", "typescript": "7.0.2", "vitest": "^4.0.0" diff --git a/adapters/ait/src/ad-config.test.ts b/adapters/ait/src/ad-config.test.ts new file mode 100644 index 00000000..c733d913 --- /dev/null +++ b/adapters/ait/src/ad-config.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; + +import { extractAitAdBridgeConfig } from './ad-config'; + +describe('extractAitAdBridgeConfig', () => { + it('maps configured AIT placements and omits placements without an AIT group', () => { + expect(extractAitAdBridgeConfig({ + version: '1', + placements: [ + { + id: 'HINT_REWARDED', + type: 'rewarded', + reward: { type: 'currency', currency: 'coin', amount: 1 }, + frequencyCap: { cooldownSeconds: 0 }, + platformPlacementIds: { ait: ' rewarded-group ' }, + }, + { + id: 'RESULT_INTERSTITIAL', + type: 'interstitial', + frequencyCap: { cooldownSeconds: 60 }, + platformPlacementIds: { android: 'android-only' }, + }, + ], + })).toEqual({ + adGroupIds: { HINT_REWARDED: 'rewarded-group' }, + adPlacementTypes: { HINT_REWARDED: 'rewarded' }, + }); + }); + + it('rejects malformed placement entries with their source label', () => { + expect(() => extractAitAdBridgeConfig({ + placements: [{ id: 'BROKEN', type: 'banner' }], + }, '/tmp/placements.json')).toThrow( + 'AIT ad placement entry at index 0 is invalid: /tmp/placements.json', + ); + }); + + it('rejects duplicate logical placement IDs before mapping AIT groups', () => { + const placement = { + id: 'HINT_REWARDED', + type: 'rewarded', + reward: { type: 'currency', currency: 'coin', amount: 1 }, + frequencyCap: { cooldownSeconds: 0 }, + platformPlacementIds: { ait: 'rewarded-group' }, + } as const; + + expect(() => extractAitAdBridgeConfig({ + version: '1', + placements: [placement, placement], + }, 'placements.json')).toThrow( + 'Duplicate AIT ad placement ID "HINT_REWARDED" at index 1: placements.json', + ); + }); +}); diff --git a/adapters/ait/src/ad-config.ts b/adapters/ait/src/ad-config.ts new file mode 100644 index 00000000..06559423 --- /dev/null +++ b/adapters/ait/src/ad-config.ts @@ -0,0 +1,54 @@ +export interface AitAdBridgeConfig { + readonly adGroupIds: Readonly>; + readonly adPlacementTypes: Readonly>; +} + +export function extractAitAdBridgeConfig( + input: unknown, + sourceLabel = 'ad placements', +): AitAdBridgeConfig { + if (!isRecord(input) || !Array.isArray(input.placements)) { + throw new Error(`AIT ad placements are invalid: ${sourceLabel}`); + } + + const placements: readonly unknown[] = input.placements; + const adGroupIds: Record = {}; + const adPlacementTypes: Record = {}; + const placementIds = new Set(); + + for (const [placementIndex, placement] of placements.entries()) { + if ( + !isRecord(placement) + || typeof placement.id !== 'string' + || (placement.type !== 'rewarded' && placement.type !== 'interstitial') + ) { + throw new Error( + `AIT ad placement entry at index ${placementIndex} is invalid: ${sourceLabel}`, + ); + } + + if (placementIds.has(placement.id)) { + throw new Error( + `Duplicate AIT ad placement ID "${placement.id}" at index ` + + `${placementIndex}: ${sourceLabel}`, + ); + } + placementIds.add(placement.id); + + const platformPlacementIds = placement.platformPlacementIds; + const adGroupId = isRecord(platformPlacementIds) && typeof platformPlacementIds.ait === 'string' + ? platformPlacementIds.ait.trim() + : ''; + + if (adGroupId.length > 0) { + adGroupIds[placement.id] = adGroupId; + adPlacementTypes[placement.id] = placement.type; + } + } + + return { adGroupIds, adPlacementTypes }; +} + +function isRecord(value: unknown): value is Readonly> { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/adapters/ait/src/wrapper-url.test.ts b/adapters/ait/src/wrapper-url.test.ts new file mode 100644 index 00000000..3ff74824 --- /dev/null +++ b/adapters/ait/src/wrapper-url.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; + +import { rewriteAitGameBundleUrl } from './wrapper-url'; + +const origin = 'https://ttokdoku.example'; + +describe('rewriteAitGameBundleUrl', () => { + it.each([ + ['assets/main.js', '/game/assets/main.js'], + ['/assets/main.js', '/game/assets/main.js'], + ['/game/assets/main.js', '/game/assets/main.js'], + ['styles/main.css?theme=dark#sheet', '/game/styles/main.css?theme=dark#sheet'], + [`${origin}/game/assets/main.js?v=1#entry`, '/game/assets/main.js?v=1#entry'], + [`${origin}/assets/main.js`, '/game/assets/main.js'], + ])('rewrites %s inside the game bundle root', (input, expected) => { + expect(rewriteAitGameBundleUrl(input, origin)).toBe(expected); + }); + + it.each([ + '', + '../escape.js', + '../../escape.js', + '//cdn.example/main.js', + 'https://evil.example/main.js', + 'javascript:alert(1)', + '%2e%2e/escape.js', + '%2fescape.js', + '%5cescape.js', + '%252e%252e/escape.js', + ])('rejects unsafe asset URL %s', (input) => { + expect(() => rewriteAitGameBundleUrl(input, origin)).toThrow(); + }); +}); diff --git a/adapters/ait/src/wrapper-url.ts b/adapters/ait/src/wrapper-url.ts new file mode 100644 index 00000000..7aa2a4db --- /dev/null +++ b/adapters/ait/src/wrapper-url.ts @@ -0,0 +1,65 @@ +export const aitGameBundleBasePath = '/game/'; + +export function rewriteAitGameBundleUrl(url: string, origin: string): string { + const trimmedUrl = url.trim(); + + if (trimmedUrl.length === 0) { + throw new Error('Game bundle asset URL must not be empty.'); + } + + if (trimmedUrl.startsWith('//')) { + throw new Error('Protocol-relative game bundle asset URLs are not supported.'); + } + + if (/^[a-z][a-z0-9+.-]*:/i.test(trimmedUrl)) { + let parsedUrl: URL; + + try { + parsedUrl = new URL(trimmedUrl); + } catch { + throw new Error(`Invalid game bundle asset URL: ${trimmedUrl}`); + } + + if ( + (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') + || parsedUrl.origin !== origin + ) { + throw new Error(`External game bundle asset URL is not supported: ${parsedUrl.protocol}`); + } + + return rewriteAitGameBundleUrl( + `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`, + origin, + ); + } + + return rewriteAndValidateGamePath(trimmedUrl, trimmedUrl, origin); +} + +function rewriteAndValidateGamePath(path: string, originalUrl: string, origin: string): string { + rejectEncodedTraversal(path, originalUrl); + + const normalizedPath = path.startsWith('/') && !path.startsWith(aitGameBundleBasePath) + ? path.slice(1) + : path; + const resolvedUrl = new URL(normalizedPath, `${origin}${aitGameBundleBasePath}`); + + if (resolvedUrl.origin !== origin || !resolvedUrl.pathname.startsWith(aitGameBundleBasePath)) { + throw new Error(`Game bundle asset URL escapes the bundle base path: ${originalUrl}`); + } + + return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`; +} + +function rejectEncodedTraversal(path: string, originalUrl: string): void { + const rawPath = path.toLowerCase(); + + if ( + rawPath.includes('%25') + || rawPath.includes('%2e') + || rawPath.includes('%2f') + || rawPath.includes('%5c') + ) { + throw new Error(`Game bundle asset URL contains encoded path traversal: ${originalUrl}`); + } +} diff --git a/adapters/ait/src/wrapper.ts b/adapters/ait/src/wrapper.ts new file mode 100644 index 00000000..c4285a90 --- /dev/null +++ b/adapters/ait/src/wrapper.ts @@ -0,0 +1,331 @@ +import { aitGameBundleBasePath, rewriteAitGameBundleUrl } from './wrapper-url'; + +const gameBundleBasePath = aitGameBundleBasePath; +const gameBundleIndexPath = `${gameBundleBasePath}index.html`; +const gameAssetAttribute = 'data-mpgd-ait-game-asset'; +const resourceLoadTimeoutMs = 15_000; +const safeScriptAttributes = new Set([ + 'crossorigin', + 'integrity', + 'nomodule', + 'nonce', + 'referrerpolicy', + 'type', +]); + +export function mountAitGameBundle(app: HTMLElement): Promise { + return loadGameBundle(app); +} + +async function loadGameBundle(app: HTMLElement): Promise { + try { + const bundleDocument = await fetchGameBundleDocument(); + const gameMount = bundleDocument.querySelector('#game'); + + if (gameMount === null) { + throw new Error('Game bundle mount element was not found.'); + } + + const scripts = Array.from(bundleDocument.querySelectorAll('script')); + + if (scripts.length === 0) { + throw new Error('Game bundle script was not found.'); + } + + const links = Array.from(bundleDocument.querySelectorAll('link[href]')); + await Promise.all(links.map((link) => appendBundleLink(link))); + + document.title = bundleDocument.title || document.title; + app.replaceChildren(createGameMount(gameMount)); + + await appendBundleScripts(scripts); + } catch (error) { + // Already-executed scripts cannot be un-run; this only removes injected DOM nodes. + removeInjectedGameAssets(); + renderMissingBundleShell(app, error); + } +} + +async function fetchGameBundleDocument(): Promise { + const controller = new AbortController(); + const timer = window.setTimeout(() => { + controller.abort(); + }, resourceLoadTimeoutMs); + + try { + const response = await fetch(gameBundleIndexPath, { + cache: 'no-store', + signal: controller.signal, + }); + + if (!response.ok) { + throw new Error(`Game bundle request failed with ${response.status}.`); + } + + const contentType = response.headers.get('Content-Type') ?? ''; + + // The wrapper only runs over HTTP(S), where Vite and the AIT container both declare HTML. + // Missing metadata fails closed before any URLs from the response are injected. + if (!contentType.toLowerCase().startsWith('text/html')) { + throw new Error(`Game bundle returned unexpected Content-Type: ${contentType}.`); + } + + const html = await response.text(); + return new DOMParser().parseFromString(html, 'text/html'); + } catch (error) { + if (controller.signal.aborted) { + throw new Error(`Timed out loading ${gameBundleIndexPath}.`); + } + + throw error; + } finally { + window.clearTimeout(timer); + } +} + +async function appendBundleLink(source: HTMLLinkElement): Promise { + const rel = source.getAttribute('rel'); + + if (rel !== 'stylesheet' && rel !== 'modulepreload') { + return; + } + + const href = source.getAttribute('href'); + + if (href === null) { + return; + } + + const rewrittenHref = rewriteGameBundleUrl(href); + + if (hasMarkedElement('link', rewrittenHref)) { + return; + } + + const link = document.createElement('link'); + link.setAttribute(gameAssetAttribute, 'true'); + copyAttribute(source, link, 'as'); + copyAttribute(source, link, 'crossorigin'); + copyAttribute(source, link, 'integrity'); + copyAttribute(source, link, 'media'); + copyAttribute(source, link, 'nonce'); + copyAttribute(source, link, 'referrerpolicy'); + copyAttribute(source, link, 'type'); + link.rel = rel; + link.setAttribute('href', rewrittenHref); + document.head.appendChild(link); + + if (rel === 'stylesheet') { + await waitForElementLoad(link, rewrittenHref); + } +} + +async function appendBundleScripts(scripts: readonly HTMLScriptElement[]): Promise { + const runtimeErrorTrap = createRuntimeErrorTrap(); + + try { + for (const script of scripts) { + const scriptPromise = appendBundleScript(script); + // The runtime trap can win while this resource is still loading. Mark its eventual + // rejection as handled without changing which error wins the race. + void scriptPromise.catch(() => undefined); + await Promise.race([scriptPromise, runtimeErrorTrap.promise]); + await waitForNextMacrotask(); + runtimeErrorTrap.throwIfCaught(); + } + } finally { + runtimeErrorTrap.dispose(); + } +} + +async function appendBundleScript(source: HTMLScriptElement): Promise { + const src = source.getAttribute('src'); + + if (src === null) { + throw new Error('Inline scripts are not supported in AIT wrapper game bundles.'); + } + + const rewrittenSrc = rewriteGameBundleUrl(src); + + if (hasMarkedElement('script', rewrittenSrc)) { + return; + } + + const script = document.createElement('script'); + script.setAttribute(gameAssetAttribute, 'true'); + copyScriptAttributes(source, script); + script.async = false; + script.setAttribute('src', rewrittenSrc); + const loadPromise = waitForElementLoad(script, rewrittenSrc); + document.body.appendChild(script); + await loadPromise; +} + +function rewriteGameBundleUrl(url: string): string { + return rewriteAitGameBundleUrl(url, window.location.origin); +} + +function createGameMount(source: Element): HTMLElement { + const tagName = source.tagName.toLowerCase() === 'main' ? 'main' : 'div'; + const mount = document.createElement(tagName); + + for (const attribute of source.attributes) { + const attributeName = attribute.name.toLowerCase(); + + if ( + attributeName === 'class' || + attributeName === 'role' || + attributeName.startsWith('aria-') || + attributeName.startsWith('data-') + ) { + mount.setAttribute(attribute.name, attribute.value); + } + } + + mount.id = 'game'; + return mount; +} + +function copyAttribute(source: Element, target: Element, attributeName: string): void { + const value = source.getAttribute(attributeName); + + if (value !== null) { + target.setAttribute(attributeName, value); + } +} + +function copyScriptAttributes(source: HTMLScriptElement, target: HTMLScriptElement): void { + for (const attribute of source.attributes) { + if (safeScriptAttributes.has(attribute.name)) { + target.setAttribute(attribute.name, attribute.value); + } + } +} + +function hasMarkedElement(tagName: 'link' | 'script', url: string): boolean { + const attributeName = tagName === 'link' ? 'href' : 'src'; + const elements = document.querySelectorAll(`${tagName}[${gameAssetAttribute}]`); + + return Array.from(elements).some((element) => element.getAttribute(attributeName) === url); +} + +function removeInjectedGameAssets(): void { + document.querySelectorAll(`[${gameAssetAttribute}]`).forEach((element) => element.remove()); +} + +interface RuntimeErrorTrap { + readonly promise: Promise; + throwIfCaught(): void; + dispose(): void; +} + +function createRuntimeErrorTrap(): RuntimeErrorTrap { + let caughtError: Error | null = null; + let rejectPromise: (error: Error) => void = () => {}; + const promise = new Promise((_resolve, reject) => { + rejectPromise = reject; + }); + promise.catch(() => undefined); + + const catchError = (error: Error): void => { + if (caughtError === null) { + caughtError = error; + rejectPromise(error); + } + }; + const onError = (event: ErrorEvent): void => { + if (!isGameBundleRuntimeError(event)) { + return; + } + + catchError(event.error instanceof Error ? event.error : new Error(event.message)); + }; + + window.addEventListener('error', onError); + + return { + promise, + throwIfCaught() { + if (caughtError !== null) { + throw caughtError; + } + }, + dispose() { + window.removeEventListener('error', onError); + }, + }; +} + +function isGameBundleRuntimeError(event: ErrorEvent): boolean { + if (event.filename.length === 0) { + return false; + } + + try { + const sourceUrl = new URL(event.filename, window.location.href); + return sourceUrl.origin === window.location.origin && sourceUrl.pathname.startsWith( + gameBundleBasePath, + ); + } catch { + return false; + } +} + +async function waitForNextMacrotask(): Promise { + await new Promise((resolve) => { + window.setTimeout(() => resolve(), 0); + }); +} + +async function waitForElementLoad( + element: HTMLLinkElement | HTMLScriptElement, + url: string, +): Promise { + await new Promise((resolve, reject) => { + const controller = new AbortController(); + let timer = 0; + const settle = (callback: () => void): void => { + window.clearTimeout(timer); + controller.abort(); + callback(); + }; + timer = window.setTimeout(() => { + settle(() => reject(new Error(`Timed out loading ${url}.`))); + }, resourceLoadTimeoutMs); + element.addEventListener('load', () => { + settle(resolve); + }, { once: true, signal: controller.signal }); + element.addEventListener('error', () => { + settle(() => reject(new Error(`Failed to load ${url}.`))); + }, { + once: true, + signal: controller.signal, + }); + }); +} + +function renderMissingBundleShell(app: HTMLElement, error: unknown): void { + const detail = error instanceof Error ? error.message : String(error); + const section = document.createElement('section'); + section.className = 'shell'; + + const title = document.createElement('h1'); + title.textContent = 'MPGD AIT Target'; + + const guide = document.createElement('p'); + guide.append('Bridge installed. Run '); + const command = document.createElement('code'); + command.textContent = 'pnpm build:ait'; + guide.append(command, ' from a generated game to copy its bundle into '); + const outputPath = document.createElement('code'); + outputPath.textContent = 'public/game'; + guide.append(outputPath, '.'); + + const errorMessage = document.createElement('p'); + const errorCode = document.createElement('code'); + errorCode.textContent = detail; + errorMessage.append(errorCode); + + section.append(title, guide, errorMessage); + app.replaceChildren(section); +} diff --git a/apps/target-ait/granite.config.ts b/apps/target-ait/granite.config.ts index f1a1d9f9..5f4921ed 100644 --- a/apps/target-ait/granite.config.ts +++ b/apps/target-ait/granite.config.ts @@ -25,7 +25,9 @@ const config: AppsInTossWebConfig = { }, }; -export default defineConfig(config) as AppsInTossWebConfig; +const definedConfig: AppsInTossWebConfig = defineConfig(config); + +export default definedConfig; function readEnvString(input: string | undefined): string | undefined { const trimmed = input?.trim(); diff --git a/apps/target-ait/package.json b/apps/target-ait/package.json index e0df5fb6..4243fcd5 100644 --- a/apps/target-ait/package.json +++ b/apps/target-ait/package.json @@ -19,12 +19,12 @@ "fix": "ttsc fix" }, "dependencies": { - "@apps-in-toss/web-framework": "^2.10.4", + "@apps-in-toss/web-framework": "^2.10.6", "@mpgd/adapter-ait": "workspace:*" }, "devDependencies": { - "@ait-co/devtools": "^0.1.137", - "@apps-in-toss/cli": "^2.10.4", + "@ait-co/devtools": "^0.1.138", + "@apps-in-toss/cli": "^2.10.6", "@ttsc/unplugin": "0.18.4", "@types/node": "^24.0.0", "ttsc": "0.18.4", diff --git a/apps/target-ait/src/main.ts b/apps/target-ait/src/main.ts index 7137f145..4a49be5e 100644 --- a/apps/target-ait/src/main.ts +++ b/apps/target-ait/src/main.ts @@ -1,5 +1,7 @@ import './styles.css'; + import { installAitHostBridge, type InstallAitHostBridgeOptions } from '@mpgd/adapter-ait/host'; +import { mountAitGameBundle } from '@mpgd/adapter-ait/wrapper'; declare const __MPGD_AIT_APP_NAME__: string; declare const __MPGD_AIT_AD_GROUP_IDS__: Readonly>; @@ -7,19 +9,6 @@ declare const __MPGD_AIT_AD_PLACEMENT_TYPES__: Readonly< Record >; -const gameBundleBasePath = '/game/'; -const gameBundleIndexPath = `${gameBundleBasePath}index.html`; -const gameAssetAttribute = 'data-mpgd-ait-game-asset'; -const resourceLoadTimeoutMs = 15_000; -const safeScriptAttributes = new Set([ - 'crossorigin', - 'integrity', - 'nomodule', - 'nonce', - 'referrerpolicy', - 'type', -]); - installAitHostBridge({ appName: __MPGD_AIT_APP_NAME__, adGroupIds: __MPGD_AIT_AD_GROUP_IDS__, @@ -27,10 +16,12 @@ installAitHostBridge({ ...identityBridgeOptions(), }); -const app = document.querySelector('#app'); +const app = document.querySelector('#app'); if (app !== null) { - void loadGameBundle(app); + void mountAitGameBundle(app).catch((error: unknown) => { + console.error('AIT game bundle mount failed unexpectedly.', error); + }); } function identityBridgeOptions(): Pick { @@ -47,369 +38,3 @@ function identityBridgeOptions(): Pick { - try { - const bundleDocument = await fetchGameBundleDocument(); - const gameMount = bundleDocument.querySelector('#game'); - - if (gameMount === null) { - throw new Error('Game bundle mount element was not found.'); - } - - const scripts = Array.from(bundleDocument.querySelectorAll('script')); - - if (scripts.length === 0) { - throw new Error('Game bundle script was not found.'); - } - - const links = Array.from(bundleDocument.querySelectorAll('link[href]')); - await Promise.all(links.map((link) => appendBundleLink(link))); - - document.title = bundleDocument.title || document.title; - app.replaceChildren(createGameMount(gameMount)); - - await appendBundleScripts(scripts); - } catch (error) { - // Already-executed scripts cannot be un-run; this only removes injected DOM nodes. - removeInjectedGameAssets(); - renderMissingBundleShell(app, error); - } -} - -async function fetchGameBundleDocument(): Promise { - const controller = new AbortController(); - const timer = window.setTimeout(() => { - controller.abort(); - }, resourceLoadTimeoutMs); - - try { - const response = await fetch(gameBundleIndexPath, { - cache: 'no-store', - signal: controller.signal, - }); - - if (!response.ok) { - throw new Error(`Game bundle request failed with ${response.status}.`); - } - - const contentType = response.headers.get('Content-Type') ?? ''; - - if (contentType.length > 0 && !contentType.includes('text/html')) { - throw new Error(`Game bundle returned unexpected Content-Type: ${contentType}.`); - } - - const html = await response.text(); - return new DOMParser().parseFromString(html, 'text/html'); - } catch (error) { - if (controller.signal.aborted) { - throw new Error(`Timed out loading ${gameBundleIndexPath}.`); - } - - throw error; - } finally { - window.clearTimeout(timer); - } -} - -async function appendBundleLink(source: HTMLLinkElement): Promise { - const rel = source.getAttribute('rel'); - - if (rel !== 'stylesheet' && rel !== 'modulepreload') { - return; - } - - const href = source.getAttribute('href'); - - if (href === null) { - return; - } - - const rewrittenHref = rewriteGameBundleUrl(href); - - if (hasMarkedElement('link', rewrittenHref)) { - return; - } - - const link = document.createElement('link'); - link.setAttribute(gameAssetAttribute, 'true'); - copyAttribute(source, link, 'as'); - copyAttribute(source, link, 'crossorigin'); - copyAttribute(source, link, 'integrity'); - copyAttribute(source, link, 'media'); - copyAttribute(source, link, 'nonce'); - copyAttribute(source, link, 'referrerpolicy'); - link.rel = rel; - link.setAttribute('href', rewrittenHref); - document.head.appendChild(link); - - if (rel === 'stylesheet') { - await waitForElementLoad(link, rewrittenHref); - } -} - -async function appendBundleScripts(scripts: readonly HTMLScriptElement[]): Promise { - const runtimeErrorTrap = createRuntimeErrorTrap(); - - try { - for (const script of scripts) { - await Promise.race([appendBundleScript(script), runtimeErrorTrap.promise]); - await waitForNextMacrotask(); - runtimeErrorTrap.throwIfCaught(); - } - } finally { - runtimeErrorTrap.dispose(); - } -} - -async function appendBundleScript(source: HTMLScriptElement): Promise { - const src = source.getAttribute('src'); - - if (src === null) { - throw new Error('Inline scripts are not supported in AIT wrapper game bundles.'); - } - - const rewrittenSrc = rewriteGameBundleUrl(src); - - if (hasMarkedElement('script', rewrittenSrc)) { - return; - } - - const script = document.createElement('script'); - script.setAttribute(gameAssetAttribute, 'true'); - copyScriptAttributes(source, script); - script.async = false; - script.setAttribute('src', rewrittenSrc); - const loadPromise = waitForElementLoad(script, rewrittenSrc); - document.body.appendChild(script); - await loadPromise; -} - -function rewriteGameBundleUrl(url: string): string { - const trimmedUrl = url.trim(); - - if (trimmedUrl.length === 0) { - throw new Error('Game bundle asset URL must not be empty.'); - } - - if (trimmedUrl.startsWith('//')) { - throw new Error('Protocol-relative game bundle asset URLs are not supported.'); - } - - if (/^[a-z][a-z0-9+.-]*:/i.test(trimmedUrl)) { - let parsedUrl: URL; - - try { - parsedUrl = new URL(trimmedUrl); - } catch { - throw new Error(`Invalid game bundle asset URL: ${trimmedUrl}`); - } - - if ( - (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') || - parsedUrl.origin !== window.location.origin - ) { - throw new Error(`External game bundle asset URL is not supported: ${parsedUrl.protocol}`); - } - - return rewriteGameBundleUrl(`${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`); - } - - return rewriteAndValidateGamePath(trimmedUrl, trimmedUrl); -} - -function rewriteAndValidateGamePath(path: string, originalUrl: string): string { - rejectEncodedTraversal(path, originalUrl); - - const normalizedPath = path.startsWith('/') && !path.startsWith(gameBundleBasePath) - ? path.slice(1) - : path; - const resolvedUrl = new URL(normalizedPath, `${window.location.origin}${gameBundleBasePath}`); - - if (resolvedUrl.origin !== window.location.origin || !resolvedUrl.pathname.startsWith( - gameBundleBasePath, - )) { - throw new Error(`Game bundle asset URL escapes the bundle base path: ${originalUrl}`); - } - - return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`; -} - -function rejectEncodedTraversal(path: string, originalUrl: string): void { - const rawPath = path.toLowerCase(); - - if ( - rawPath.includes('%25') || - rawPath.includes('%2e') || - rawPath.includes('%2f') || - rawPath.includes('%5c') - ) { - throw new Error(`Game bundle asset URL contains encoded path traversal: ${originalUrl}`); - } -} - -function createGameMount(source: Element): HTMLElement { - const tagName = source.tagName.toLowerCase() === 'main' ? 'main' : 'div'; - const mount = document.createElement(tagName); - - for (const attribute of source.attributes) { - const attributeName = attribute.name.toLowerCase(); - - if ( - attributeName === 'class' || - attributeName === 'role' || - attributeName.startsWith('aria-') || - attributeName.startsWith('data-') - ) { - mount.setAttribute(attribute.name, attribute.value); - } - } - - mount.id = 'game'; - return mount; -} - -function copyAttribute(source: Element, target: Element, attributeName: string): void { - const value = source.getAttribute(attributeName); - - if (value !== null) { - target.setAttribute(attributeName, value); - } -} - -function copyScriptAttributes(source: HTMLScriptElement, target: HTMLScriptElement): void { - for (const attribute of source.attributes) { - if (safeScriptAttributes.has(attribute.name)) { - target.setAttribute(attribute.name, attribute.value); - } - } -} - -function hasMarkedElement(tagName: 'link' | 'script', url: string): boolean { - const attributeName = tagName === 'link' ? 'href' : 'src'; - const elements = document.querySelectorAll(`${tagName}[${gameAssetAttribute}]`); - - return Array.from(elements).some((element) => element.getAttribute(attributeName) === url); -} - -function removeInjectedGameAssets(): void { - document.querySelectorAll(`[${gameAssetAttribute}]`).forEach((element) => element.remove()); -} - -interface RuntimeErrorTrap { - readonly promise: Promise; - throwIfCaught(): void; - dispose(): void; -} - -function createRuntimeErrorTrap(): RuntimeErrorTrap { - let caughtError: Error | null = null; - let rejectPromise: (error: Error) => void = () => {}; - const promise = new Promise((_resolve, reject) => { - rejectPromise = reject; - }); - promise.catch(() => undefined); - - const catchError = (error: Error): void => { - if (caughtError === null) { - caughtError = error; - rejectPromise(error); - } - }; - const onError = (event: ErrorEvent): void => { - if (!isGameBundleRuntimeError(event)) { - return; - } - - catchError(event.error instanceof Error ? event.error : new Error(event.message)); - }; - - window.addEventListener('error', onError); - - return { - promise, - throwIfCaught() { - if (caughtError !== null) { - throw caughtError; - } - }, - dispose() { - window.removeEventListener('error', onError); - }, - }; -} - -function isGameBundleRuntimeError(event: ErrorEvent): boolean { - if (event.filename.length === 0) { - return false; - } - - try { - const sourceUrl = new URL(event.filename, window.location.href); - return sourceUrl.origin === window.location.origin && sourceUrl.pathname.startsWith( - gameBundleBasePath, - ); - } catch { - return false; - } -} - -async function waitForNextMacrotask(): Promise { - await new Promise((resolve) => { - window.setTimeout(() => resolve(), 0); - }); -} - -async function waitForElementLoad( - element: HTMLLinkElement | HTMLScriptElement, - url: string, - timeoutMs = resourceLoadTimeoutMs, -): Promise { - await new Promise((resolve, reject) => { - const controller = new AbortController(); - let timer = 0; - const settle = (callback: () => void): void => { - window.clearTimeout(timer); - controller.abort(); - callback(); - }; - timer = window.setTimeout(() => { - settle(() => reject(new Error(`Timed out loading ${url}.`))); - }, timeoutMs); - element.addEventListener('load', () => { - settle(resolve); - }, { once: true, signal: controller.signal }); - element.addEventListener('error', () => { - settle(() => reject(new Error(`Failed to load ${url}.`))); - }, { - once: true, - signal: controller.signal, - }); - }); -} - -function renderMissingBundleShell(app: HTMLDivElement, error: unknown): void { - const detail = error instanceof Error ? error.message : String(error); - const section = document.createElement('section'); - section.className = 'shell'; - - const title = document.createElement('h1'); - title.textContent = 'MPGD AIT Target'; - - const guide = document.createElement('p'); - guide.append('Bridge installed. Run '); - const command = document.createElement('code'); - command.textContent = 'pnpm build:ait'; - guide.append(command, ' from a generated game to copy its bundle into '); - const outputPath = document.createElement('code'); - outputPath.textContent = 'public/game'; - guide.append(outputPath, '.'); - - const errorMessage = document.createElement('p'); - const errorCode = document.createElement('code'); - errorCode.textContent = detail; - errorMessage.append(errorCode); - - section.append(title, guide, errorMessage); - app.replaceChildren(section); -} diff --git a/apps/target-ait/tsconfig.bundle.json b/apps/target-ait/tsconfig.bundle.json index 55268543..be5599bf 100644 --- a/apps/target-ait/tsconfig.bundle.json +++ b/apps/target-ait/tsconfig.bundle.json @@ -9,7 +9,9 @@ "vite.config.ts", "granite.config.ts", "../../adapters/ait/src/host.ts", + "../../adapters/ait/src/ad-config.ts", "../../adapters/ait/src/lifecycle.ts", + "../../adapters/ait/src/wrapper.ts", "../../packages/bridge/src/**/*.ts" ] } diff --git a/apps/target-ait/vite.config.ts b/apps/target-ait/vite.config.ts index 1b3a63d1..054f9db1 100644 --- a/apps/target-ait/vite.config.ts +++ b/apps/target-ait/vite.config.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import aitDevtools from '@ait-co/devtools/unplugin'; +import { extractAitAdBridgeConfig } from '@mpgd/adapter-ait/ad-config'; import ttsc from '@ttsc/unplugin/vite'; import { defineConfig } from 'vite'; @@ -47,40 +48,11 @@ function readAitAdConfig(path: string | undefined): { try { parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; - } catch { - throw new Error(`AIT ad placements file is unreadable or invalid JSON: ${path}`); + } catch (error) { + throw new Error(`AIT ad placements file is unreadable or invalid JSON: ${path}`, { + cause: error, + }); } - if (!isRecord(parsed) || !Array.isArray(parsed.placements)) { - throw new Error(`AIT ad placements file is invalid: ${path}`); - } - - const adGroupIds: Record = {}; - const adPlacementTypes: Record = {}; - - for (const placement of parsed.placements) { - if ( - !isRecord(placement) - || typeof placement.id !== 'string' - || (placement.type !== 'rewarded' && placement.type !== 'interstitial') - ) { - throw new Error(`AIT ad placement entry is invalid: ${path}`); - } - - const platformPlacementIds = placement.platformPlacementIds; - const adGroupId = isRecord(platformPlacementIds) && typeof platformPlacementIds.ait === 'string' - ? platformPlacementIds.ait.trim() - : ''; - - if (adGroupId.length > 0) { - adGroupIds[placement.id] = adGroupId; - adPlacementTypes[placement.id] = placement.type; - } - } - - return { adGroupIds, adPlacementTypes }; -} - -function isRecord(value: unknown): value is Readonly> { - return typeof value === 'object' && value !== null && !Array.isArray(value); + return extractAitAdBridgeConfig(parsed, path); } diff --git a/docs/AGENTIC_GAME_WORKFLOW.md b/docs/AGENTIC_GAME_WORKFLOW.md index feb8569b..a1f5fcaf 100644 --- a/docs/AGENTIC_GAME_WORKFLOW.md +++ b/docs/AGENTIC_GAME_WORKFLOW.md @@ -20,9 +20,10 @@ The repository separates four concerns: - Optional targets may add a generated target skill. Selecting or initializing Microsoft Store adds `.agents/skills/release-microsoft-store` together with its PWA build and evidence workflow. -- Generated games own their Reddit Devvit app root in `apps/target-devvit`. - Apps in Toss and Capacitor shells remain kit reference shells for smoke builds - until a game creates production-owned wrappers and app metadata. +- Generated games own their Reddit Devvit app root in `apps/target-devvit` and + their Apps in Toss wrapper in `apps/target-ait`. Capacitor shells remain kit + references for smoke builds until a game creates production-owned Android and + iOS shells and app metadata. Generated game projects remain private by default. The public packages are the generator and reusable SDK surfaces. New publishable packages should be added diff --git a/docs/GAME_DEVELOPMENT.md b/docs/GAME_DEVELOPMENT.md index d92741a1..73ce4c33 100644 --- a/docs/GAME_DEVELOPMENT.md +++ b/docs/GAME_DEVELOPMENT.md @@ -156,11 +156,11 @@ Recommended starter composition: Generated games own their Reddit Devvit app root in `apps/target-devvit`. Run `pnpm devvit:login`, `pnpm devvit:init`, and `pnpm devvit:playtest` from the game root when you are ready to create the Reddit-side app record and test it. -The starter still uses kit reference shells for Apps in Toss and Capacitor -artifact smoke checks. The final smoke artifacts are copied back under the -game app's `artifacts/` and `release-output/` directories, but copy or create -game-owned shells before real Toss, App Store, or Google Play submission -metadata is needed. +The starter owns its Apps in Toss wrapper in `apps/target-ait`, including app +identity, Granite configuration, community devtools, and console metadata. It +still uses kit reference Capacitor shells for Android and iOS artifact smoke +checks. Copy or create game-owned mobile shells before App Store or Google Play +submission metadata is needed. Use the kit CLI for generated target builds because it resolves `${MPGD_KIT_PATH}` tokens in the game's `mpgd.targets.json` before invoking the @@ -171,12 +171,15 @@ pnpm mpgd target build-all --targets-file examples/my-game/mpgd.targets.json --t pnpm mpgd target smoke-all --targets-file examples/my-game/mpgd.targets.json --targets web,microsoft-store,verse8,ait,reddit --kit-path . ``` -Use `staging` while an AIT or Capacitor entry points at a kit reference wrapper -or shell. Production AIT, Android, and iOS builds fail closed unless the -wrapper/shell resolves to a dedicated directory inside the game root and -`VITE_MPGD_GAME_SERVICES_URL` is a public HTTPS URL without credentials. -Canonical path validation blocks symbolic-link escapes; localhost and literal -private or reserved IP addresses are also rejected. +Use `staging` while a Capacitor entry points at a kit reference shell. +Production AIT, Android, and iOS builds fail closed unless their wrapper or +shell resolves to a dedicated directory inside the game root. An AIT target +with `authoritativeGameServices: false` keeps native identity, storage, sharing, +and Game Center while disabling IAP and ads, so it does not require a backend +URL. Enabling authoritative AIT grants, or producing Android and iOS releases, +requires `VITE_MPGD_GAME_SERVICES_URL` to be a public HTTPS URL without +credentials. Canonical path validation blocks symbolic-link escapes; localhost +and literal private or reserved IP addresses are also rejected. For a private sibling game repo, run the same commands from the game repo or kit checkout and pass an absolute or relative `--targets-file` plus `--kit-path`. diff --git a/packages/cli/templates/phaser-game/README.md b/packages/cli/templates/phaser-game/README.md index 9a521478..c1e5dc5a 100644 --- a/packages/cli/templates/phaser-game/README.md +++ b/packages/cli/templates/phaser-game/README.md @@ -65,18 +65,24 @@ pnpm --dir ../mpgd-kit mpgd target build-all \ into `.mpgd.targets.generated.json` before calling the kit's existing target build and smoke scripts. -The starter's Apps in Toss and Capacitor entries intentionally point at kit -reference wrappers, so their reusable validation builds use the `staging` -profile. Before an AIT, Android, or iOS production build, copy or create the -wrapper/shell under this game directory, update `mpgd.targets.json`, and set -`VITE_MPGD_GAME_SERVICES_URL` to a public HTTPS endpoint without embedded -credentials. Production preflight resolves real paths, rejects wrappers or -shells outside the game root (including symbolic-link escapes), and blocks -localhost plus literal private or reserved IP addresses. +The starter owns its Apps in Toss wrapper under `apps/target-ait`. With +`authoritativeGameServices: false`, production keeps native identity, storage, +sharing, and Game Center while disabling IAP and ads; no game-services URL is +required for that fail-closed first release. To enable purchases or ads later, +set the authority flag to `true`, add app-owned product or ad IDs, and configure +`VITE_MPGD_GAME_SERVICES_URL` to a public HTTPS verifier without embedded +credentials. + +The Capacitor entries still intentionally point at kit reference shells, so +their reusable validation builds use the `staging` profile. Before an Android +or iOS production build, copy or create the shell under this game directory and +update `mpgd.targets.json`. Production preflight resolves real paths, rejects +wrappers or shells outside the game root (including symbolic-link escapes), and +blocks localhost plus literal private or reserved IP addresses. `mpgd target doctor` verifies that release manifest artifact paths stay under -this game directory, even when a build used the kit reference Apps in Toss or -Capacitor wrappers. +this game directory, including non-production builds that use kit reference +Capacitor shells. Optional targets can add their own config, scripts, runtime wiring, and release skill. To enable Microsoft Store after creation, run @@ -224,8 +230,8 @@ pnpm build:ait pnpm smoke:ait ``` -The kit reference wrapper includes the Apps in Toss community devtools package -for SDK mock and device debugging while the wrapper is still kit-owned: +The game-owned wrapper includes the Apps in Toss community devtools package for +SDK mocks and device debugging: ```sh pnpm ait:wrapper:dev @@ -235,7 +241,7 @@ pnpm ait:devtools:mcp pnpm ait:devtools:mcp:mobile ``` -`ait:wrapper:dev` runs the kit wrapper with the community Vite plugin enabled. +`ait:wrapper:dev` runs `apps/target-ait` with the community Vite plugin enabled. It loads the last game bundle copied by `pnpm build:ait` from the wrapper's `public/game` directory, so run `pnpm build:ait` again after game changes before opening a wrapper devtools session. Use `ait:wrapper:dev:plain` to disable the @@ -266,8 +272,8 @@ pnpm ait:console:deploy -- release-output/ait/YOUR_APP.ait `ait:console:register` creates the mini-app and writes its `miniAppId` back to that file. Before registration, add the real logo, thumbnail, and screenshot files referenced by `aitcc.yaml` under `./assets/`. Use `pnpm build:ait` for the -kit wrapper smoke loop, and run `pnpm build:ait:package` before console deploys -so `release-output/ait/` contains the `.ait` bundle you pass after `--`. +game-owned wrapper smoke loop, and run `pnpm build:ait:package` before console +deploys so `release-output/ait/` contains the `.ait` bundle you pass after `--`. The starter also installs `@apps-in-toss/web-framework` and awaits `install()` from `@ait-co/polyfill` only when `__APP_TARGET__` is `ait`. In the @@ -362,10 +368,10 @@ HTTPS game-services backend that verifies purchase or reward evidence. - Reddit Devvit is game-owned in `apps/target-devvit`. Its Redis-backed storage bridge fails closed on identity, provider, serialization, and quota errors; it never switches progress to browser `localStorage`. -- Apps in Toss currently uses the kit reference wrapper at - `${MPGD_KIT_PATH}/apps/target-ait` for smoke packaging. Before a real Toss - submission, create a game-owned wrapper/config so the app name, CLI state, and - review metadata are not shared with the kit demo. +- Apps in Toss is game-owned in `apps/target-ait`. The wrapper keeps the app + name, console state, review metadata, and `@ait-co/devtools` development + integration local to the game while reusing `@mpgd/adapter-ait` for the + production bridge and game-bundle loader. - Android and iOS currently use the kit reference Capacitor shell at `${MPGD_KIT_PATH}/apps/mobile-capacitor`. That is useful for local artifact checks, and final artifacts are copied back to this game's `release-output/` diff --git a/packages/cli/templates/phaser-game/apps/target-ait/granite.config.ts b/packages/cli/templates/phaser-game/apps/target-ait/granite.config.ts new file mode 100644 index 00000000..82b45284 --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/granite.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, type AppsInTossWebConfig } from '@apps-in-toss/web-framework/config'; + +const config: AppsInTossWebConfig = { + appName: readEnvString(process.env.MPGD_AIT_APP_NAME) ?? '__GAME_NAME__', + brand: { + displayName: readEnvString(process.env.MPGD_AIT_DISPLAY_NAME) ?? '__GAME_TITLE__', + primaryColor: readEnvString(process.env.MPGD_AIT_PRIMARY_COLOR) ?? '#101820', + icon: readEnvString(process.env.MPGD_AIT_BRAND_ICON_URL) ?? 'generated/console-icon.png', + }, + permissions: [], + web: { + host: 'localhost', + port: 5173, + commands: { + dev: 'pnpm dev', + build: 'pnpm build', + }, + }, + webViewProps: { + bounces: false, + pullToRefreshEnabled: false, + overScrollMode: 'never', + allowsBackForwardNavigationGestures: false, + allowsInlineMediaPlayback: true, + }, +}; + +const definedConfig: AppsInTossWebConfig = defineConfig(config); + +export default definedConfig; + +function readEnvString(input: string | undefined): string | undefined { + const trimmed = input?.trim(); + return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed; +} diff --git a/packages/cli/templates/phaser-game/apps/target-ait/index.html b/packages/cli/templates/phaser-game/apps/target-ait/index.html new file mode 100644 index 00000000..c1f9906d --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/index.html @@ -0,0 +1,15 @@ + + + + + + __GAME_TITLE__ + + +
+ + + diff --git a/packages/cli/templates/phaser-game/apps/target-ait/package.json b/packages/cli/templates/phaser-game/apps/target-ait/package.json new file mode 100644 index 00000000..5f76d129 --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/package.json @@ -0,0 +1,33 @@ +{ + "name": "__PACKAGE_NAME__-target-ait", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "dev:plain": "MPGD_AIT_DEVTOOLS=0 VITE_MPGD_AIT_MOCK_IDENTITY=1 vite --host 0.0.0.0", + "dev:phone": "AIT_TUNNEL=1 AIT_TUNNEL_CDP=1 vite --host 0.0.0.0", + "build": "vite build", + "ait:build": "ait build", + "ait:deploy": "ait deploy --api-key \"$AIT_API_KEY\"", + "devtools:mcp": "pnpm exec devtools-mcp", + "devtools:mcp:mobile": "pnpm exec devtools-mcp --target=mobile", + "check": "ttsc --noEmit -p tsconfig.json", + "lint": "ttsc --noEmit -p tsconfig.json", + "format": "ttsc format", + "fix": "ttsc fix" + }, + "dependencies": { + "@apps-in-toss/web-framework": "^2.10.6", + "@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__" + }, + "devDependencies": { + "@ait-co/devtools": "^0.1.138", + "@apps-in-toss/cli": "^2.10.6", + "@ttsc/unplugin": "0.18.4", + "@types/node": "^24.0.0", + "ttsc": "0.18.4", + "typescript": "7.0.2", + "vite": "^8.1.3" + } +} diff --git a/packages/cli/templates/phaser-game/apps/target-ait/src/main.ts b/packages/cli/templates/phaser-game/apps/target-ait/src/main.ts new file mode 100644 index 00000000..32bf65dd --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/src/main.ts @@ -0,0 +1,34 @@ +import './styles.css'; + +import { installAitHostBridge, type InstallAitHostBridgeOptions } from '@mpgd/adapter-ait/host'; +import { mountAitGameBundle } from '@mpgd/adapter-ait/wrapper'; + +declare const __MPGD_AIT_APP_NAME__: string; +declare const __MPGD_AIT_AD_GROUP_IDS__: Readonly>; +declare const __MPGD_AIT_AD_PLACEMENT_TYPES__: Readonly< + Record +>; + +installAitHostBridge({ + appName: __MPGD_AIT_APP_NAME__, + adGroupIds: __MPGD_AIT_AD_GROUP_IDS__, + adPlacementTypes: __MPGD_AIT_AD_PLACEMENT_TYPES__, + ...identityBridgeOptions(), +}); + +const app = document.querySelector('#app'); +if (app !== null) { + void mountAitGameBundle(app).catch((error: unknown) => { + console.error('AIT game bundle mount failed unexpectedly.', error); + }); +} + +function identityBridgeOptions(): Pick { + return import.meta.env.VITE_MPGD_AIT_MOCK_IDENTITY === '1' + ? { + dependencies: { + identityProvider: async () => ({ type: 'HASH', hash: 'ait-local-player' }), + }, + } + : {}; +} diff --git a/packages/cli/templates/phaser-game/apps/target-ait/src/styles.css b/packages/cli/templates/phaser-game/apps/target-ait/src/styles.css new file mode 100644 index 00000000..cfc51926 --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/src/styles.css @@ -0,0 +1,33 @@ +html, +body, +#app { + width: 100%; + height: 100%; + margin: 0; +} + +body { + background: #101820; + color: #f8fafc; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.shell { + min-height: 100%; + display: grid; + place-content: center; + gap: 12px; + padding: 24px; + box-sizing: border-box; + text-align: center; +} + +h1, +p { + margin: 0; +} + +code { + color: #fff7ad; +} diff --git a/packages/cli/templates/phaser-game/apps/target-ait/tsconfig.json b/packages/cli/templates/phaser-game/apps/target-ait/tsconfig.json new file mode 100644 index 00000000..2926ad84 --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["vite/client", "node"], + "noEmit": true + }, + "include": ["src/**/*.ts", "vite.config.ts", "granite.config.ts"] +} diff --git a/packages/cli/templates/phaser-game/apps/target-ait/vite.config.ts b/packages/cli/templates/phaser-game/apps/target-ait/vite.config.ts new file mode 100644 index 00000000..ca8b557a --- /dev/null +++ b/packages/cli/templates/phaser-game/apps/target-ait/vite.config.ts @@ -0,0 +1,53 @@ +import { readFileSync } from 'node:fs'; + +import aitDevtools from '@ait-co/devtools/unplugin'; +import { extractAitAdBridgeConfig } from '@mpgd/adapter-ait/ad-config'; +import ttsc from '@ttsc/unplugin/vite'; +import { defineConfig } from 'vite'; + +const isTruthyEnv = (value: string | undefined): boolean => + value !== undefined && value !== '' && value !== '0' && value.toLowerCase() !== 'false'; +const aitDevtoolsTunnel = isTruthyEnv(process.env.AIT_TUNNEL) + ? { cdp: isTruthyEnv(process.env.AIT_TUNNEL_CDP) } + : false; +const aitAppName = process.env.MPGD_AIT_APP_NAME?.trim() || '__GAME_NAME__'; +const aitAdConfig = readAitAdConfig(process.env.MPGD_AD_PLACEMENTS_FILE); + +export default defineConfig(({ command, isPreview }) => ({ + define: { + __MPGD_AIT_APP_NAME__: JSON.stringify(aitAppName), + __MPGD_AIT_AD_GROUP_IDS__: JSON.stringify(aitAdConfig.adGroupIds), + __MPGD_AIT_AD_PLACEMENT_TYPES__: JSON.stringify(aitAdConfig.adPlacementTypes), + }, + plugins: [ + ...(command === 'serve' && !isPreview && process.env.MPGD_AIT_DEVTOOLS !== '0' + ? [aitDevtools.vite({ mcp: true, tunnel: aitDevtoolsTunnel })] + : []), + ttsc({ project: 'tsconfig.json', plugins: false }), + ], + build: { + target: 'es2022', + outDir: 'dist', + emptyOutDir: true, + }, +})); + +function readAitAdConfig(path: string | undefined): { + readonly adGroupIds: Readonly>; + readonly adPlacementTypes: Readonly>; +} { + if (path === undefined || path.trim().length === 0) { + return { adGroupIds: {}, adPlacementTypes: {} }; + } + + let parsed: unknown; + try { + parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + } catch (error) { + throw new Error(`AIT ad placements file is unreadable or invalid JSON: ${path}`, { + cause: error, + }); + } + + return extractAitAdBridgeConfig(parsed, path); +} diff --git a/packages/cli/templates/phaser-game/gitignore b/packages/cli/templates/phaser-game/gitignore index 5b73f034..24388ccf 100644 --- a/packages/cli/templates/phaser-game/gitignore +++ b/packages/cli/templates/phaser-game/gitignore @@ -1,6 +1,12 @@ node_modules/ dist/ apps/*/dist/ +apps/target-ait/.ait_relay +apps/target-ait/.ait_urls +apps/target-ait/.granite/ +apps/target-ait/generated/ +apps/target-ait/public/assets/ +apps/target-ait/public/game/ apps/target-devvit/generated/ artifacts/ release-input/ diff --git a/packages/cli/templates/phaser-game/mpgd.targets.json b/packages/cli/templates/phaser-game/mpgd.targets.json index e5308c5e..01cf68e9 100644 --- a/packages/cli/templates/phaser-game/mpgd.targets.json +++ b/packages/cli/templates/phaser-game/mpgd.targets.json @@ -42,11 +42,17 @@ "ait": { "kind": "apps-in-toss", "gameApp": ".", - "wrapperApp": "${MPGD_KIT_PATH}/apps/target-ait", + "wrapperApp": "apps/target-ait", "adapter": "ait", "authoritativeGameServices": false, + "metadata": { + "appName": "__GAME_NAME__", + "displayName": "__GAME_TITLE__", + "primaryColor": "#101820", + "sdkMajor": 2 + }, "icon": { "profile": "ait" }, - "webDir": "${MPGD_KIT_PATH}/apps/target-ait/public/game", + "webDir": "apps/target-ait/public/game", "artifact": ".ait" }, "reddit": { diff --git a/packages/cli/templates/phaser-game/package.json b/packages/cli/templates/phaser-game/package.json index f399c5e3..2537fb02 100644 --- a/packages/cli/templates/phaser-game/package.json +++ b/packages/cli/templates/phaser-game/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "dev": "__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0", - "dev:ait": "APP_TARGET=ait APP_VERSION=0.0.0-dev BUILD_ID=ait-sandbox sh -c '__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0'", + "dev:ait": "APP_TARGET=ait MPGD_CONFIG_TARGET=ait MPGD_PLATFORM_TARGETS_FILE=./mpgd.targets.json APP_VERSION=0.0.0-dev BUILD_ID=ait-sandbox sh -c '__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0'", "build": "__WORKSPACE_I18N_BUILD_PREFIX__vite build", "accept": "node ./tools/run-game-acceptance.mjs", "icons:generate": "pnpm exec mpgd game icons generate . --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}", @@ -17,11 +17,11 @@ "build:ait": "pnpm exec mpgd target build ait staging --variant wrapper --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}", "build:ait:package": "pnpm exec mpgd target build ait production --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}", "smoke:ait": "pnpm exec mpgd target smoke ait --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}", - "ait:wrapper:dev": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run dev", - "ait:wrapper:dev:plain": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run dev:plain", - "ait:wrapper:dev:phone": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run dev:phone", - "ait:devtools:mcp": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run devtools:mcp", - "ait:devtools:mcp:mobile": "pnpm --dir ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}/apps/target-ait run devtools:mcp:mobile", + "ait:wrapper:dev": "pnpm --dir apps/target-ait run dev", + "ait:wrapper:dev:plain": "pnpm --dir apps/target-ait run dev:plain", + "ait:wrapper:dev:phone": "pnpm --dir apps/target-ait run dev:phone", + "ait:devtools:mcp": "pnpm --dir apps/target-ait run devtools:mcp", + "ait:devtools:mcp:mobile": "pnpm --dir apps/target-ait run devtools:mcp:mobile", "ait:console:login": "aitcc login", "ait:console:whoami": "aitcc whoami", "ait:console:init": "aitcc app init", @@ -58,7 +58,7 @@ }, "dependencies": { "@ait-co/polyfill": "^0.1.17", - "@apps-in-toss/web-framework": "^2.10.4", + "@apps-in-toss/web-framework": "^2.10.6", "@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__", "@mpgd/adapter-browser": "__MPGD_DEPENDENCY_VERSION_ADAPTER_BROWSER__", "@mpgd/adapter-capacitor": "__MPGD_DEPENDENCY_VERSION_ADAPTER_CAPACITOR__", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0dda843d..13618b8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,8 +100,8 @@ importers: version: link:../../packages/platform devDependencies: '@apps-in-toss/web-framework': - specifier: ^2.10.4 - version: 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + specifier: ^2.10.6 + version: 2.10.6(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) ttsc: specifier: 0.18.4 version: 0.18.4 @@ -265,18 +265,18 @@ importers: apps/target-ait: dependencies: '@apps-in-toss/web-framework': - specifier: ^2.10.4 - version: 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + specifier: ^2.10.6 + version: 2.10.6(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) '@mpgd/adapter-ait': specifier: workspace:* version: link:../../adapters/ait devDependencies: '@ait-co/devtools': - specifier: ^0.1.137 - version: 0.1.137(197a08e79f5a0dea7c9c567c0dba765e) + specifier: ^0.1.138 + version: 0.1.138(ddacffd1360e345338f8069a3f016ad5) '@apps-in-toss/cli': - specifier: ^2.10.4 - version: 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + specifier: ^2.10.6 + version: 2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) '@ttsc/unplugin': specifier: 0.18.4 version: 0.18.4 @@ -858,8 +858,8 @@ importers: packages: - '@ait-co/devtools@0.1.137': - resolution: {integrity: sha512-HMf5bDscKkto1ZqIT5Iu5HtvGXDQfJ/lrqo0a6hNpLEfQtD6fSGeyD/PjwUbG5v7Erh5lDVORnhAt31qRSPl5Q==} + '@ait-co/devtools@0.1.138': + resolution: {integrity: sha512-JQw8HmS+iY+gWwSffQBunpiL8BQijspKEq34KS4IP9dbiOscHL8/T1ArOTHRcVDeqQ3jdM7WuqCwbdOTQKRf+Q==} engines: {node: '>=24'} hasBin: true peerDependencies: @@ -879,8 +879,8 @@ packages: resolution: {integrity: sha512-1no0CXOhPEeSCrGduT37KMmCqA6irhowXa0S7AaPHb4RQSHGioR3wvvD7HHBCaUriMYs0EvtqRJfOgYd0ycKlQ==} engines: {node: '>=24'} - '@apps-in-toss/analytics@2.10.4': - resolution: {integrity: sha512-ZiuWcw2mLyP4PBHiVx8kuKULcM4DleiZcvBcnd8UxWzqFNcgZ355m1T15FOAlRHuIx+/wSePHKr48pgvs7mABQ==} + '@apps-in-toss/analytics@2.10.6': + resolution: {integrity: sha512-9dUIDnELDTG+S+IUUU2HUUoMm/AOu0OBDtoDHq3mm2sn7LWzoDH2RPFQCLO3YXXvDbxcpo/7iJpTWEz1InE8uA==} peerDependencies: '@granite-js/native': '*' '@granite-js/react-native': '*' @@ -889,14 +889,14 @@ packages: react: '*' react-native: '*' - '@apps-in-toss/bridge-core@2.10.4': - resolution: {integrity: sha512-5cnRcgeO3MRHuorXx/pYsXlhGNEio/x1bnTwBQGOeoZoKpzpH/L73P3RXE0puOHzaQ3JfD4b8q/lAm6etIX3kg==} + '@apps-in-toss/bridge-core@2.10.6': + resolution: {integrity: sha512-r/aaiMwg0GYjetiELD2NyvX1VG+OELuHUFIlNN8GGW3YOFlIkx4C1B2BoscjAWazqJpNdbpKlH2JzXrYy5KHzA==} - '@apps-in-toss/cli@2.10.4': - resolution: {integrity: sha512-kFoMVX3h3TCtyayMtGBYQNdoSkSqaARMuF8dzFJOf7NNLHnZ2dqUf/tyRmtaoZAaaafshw8iS4J2e6uEhtrwqQ==} + '@apps-in-toss/cli@2.10.6': + resolution: {integrity: sha512-fBouLfQTJklJvQc36kFZKQtfVpYa4D1JdYXGiV/S81OfpK6tOeOMBo8qaRsF5MN5y2zBNx4WyejTUWiejmxzBg==} - '@apps-in-toss/framework@2.10.4': - resolution: {integrity: sha512-Q3iQ+H28SHX6f/cSTtZAGCK9MVlw0E15mVcH+bDN53zsCFegNZOPcUcpLYg2+nrZ+wpV/B5ScT/yvUCWRXeVEw==} + '@apps-in-toss/framework@2.10.6': + resolution: {integrity: sha512-pSj4BTnMON6H+MIjQI8W1Mwl+Wkoak02Fz/W45ha6eFmmCmoyUr/r3P9zRtqgCINu1N4J0vfT4HqKz6BntrWQg==} hasBin: true peerDependencies: '@granite-js/native': '*' @@ -907,39 +907,39 @@ packages: react: '*' react-native: '*' - '@apps-in-toss/native-modules@2.10.4': - resolution: {integrity: sha512-0w3aQ2Q6+eqOfgDEKzL32FicCQOdk8cwnPTvdkIm3WGnq/H45r1/yleu4QE6SkP2Adz6fL2AfiutFu7oVyAlAg==} + '@apps-in-toss/native-modules@2.10.6': + resolution: {integrity: sha512-SDZGXN8qHfpK+OoE9Uv6ZNGxHzugxauVIZM1OFaKUCFSqyKjlDal+0Dt6zVIDtK8TtmeMqoodkG3cymu/XuEuQ==} peerDependencies: '@granite-js/native': '*' '@granite-js/react-native': '*' react: '*' react-native: '*' - '@apps-in-toss/plugin-compat@2.10.4': - resolution: {integrity: sha512-MnkqKgsf3gQObMPAMe9PmZLlzWYNrxS712Qdy98dR/TFmbtuk4gX4OmDjIcjW16M14QJHk1t7NwyGOwQ7dDT7A==} + '@apps-in-toss/plugin-compat@2.10.6': + resolution: {integrity: sha512-ijB5NcNwbzkA1X0z+C9g+2U8uUIgpmtIrPqqerON3UKYOBwscrdsb8liFNaH+iQ12oFP+fcY2/N56pJKqSP8zQ==} - '@apps-in-toss/plugins@2.10.4': - resolution: {integrity: sha512-iR1/GckJuD1zad47neXya4Y3FViJF/M+EY1pT/3CXZjM3eoyvBxITtNeOD/p3fh90RRm5E50iKoWNJsHnhmN/A==} + '@apps-in-toss/plugins@2.10.6': + resolution: {integrity: sha512-VZl7kqQ6TLL9qI2hFCMuTNhTFmHHTVK1OjCWuVEl9Wyqx96H9E2RcmdaK3S1Gc5Y3DiaokU9p/42MBDmHFFi/g==} - '@apps-in-toss/types@2.10.4': - resolution: {integrity: sha512-GMODZlV7xjUYKd7nPMMAVSStfwIOwiC4v6SIcTgKlqfdTvF+9X52/hiAY2UMdCAlxWO8lZD9uHchp3fC+CvY6w==} + '@apps-in-toss/types@2.10.6': + resolution: {integrity: sha512-V08aGu3cg/m4boBsEs4if2lzY5MpUKTEi5whgKegOfpUhDeH+etwigYjqSKpZ0uwiaANoh8ySG5H5/2vRWFUDg==} - '@apps-in-toss/user-scripts@2.10.4': - resolution: {integrity: sha512-sACtAnBrk1pgow+m7JjtjhU3TBXGHZfyehdy7K7H/gOLYNRtoUAzf9kAgCT6QJepKvidejst8ULJr3pxdfYT9A==} + '@apps-in-toss/user-scripts@2.10.6': + resolution: {integrity: sha512-3IYs+V2aLefiI2TRvJ8rlxb32Ebn59IVfnOtX1sCIWNdPZ5bqAUGN4Gkxi+z5/LPlZxmxDV1+1rVs3BhMQKU7Q==} - '@apps-in-toss/web-analytics@2.10.4': - resolution: {integrity: sha512-9K3Ytd9yyOLhVuQvFQxHI369pjzv+kSV8yq3t+oHUhgjPyocNEyL6dQfKkf6uwJMf8XEvGuXIo+QFib3HJoA4w==} + '@apps-in-toss/web-analytics@2.10.6': + resolution: {integrity: sha512-iWssmVc7EEFG9KQVwGaVIb9u2Z7ZkjLNWMJx9ZTBNs+rFXPpe/FOHXN9AAtk8TKcR4WQ1dXMedOu5cgbzcUVNw==} peerDependencies: '@apps-in-toss/web-bridge': '*' - '@apps-in-toss/web-bridge@2.10.4': - resolution: {integrity: sha512-P336Zd6k6qt6hUp6hDYaqevXk8i8QepMOlHsDcj+wAxc5xsZ0BLxkmV76E8WD/MiWKH8IQYeVuU8k2OCuaORcQ==} + '@apps-in-toss/web-bridge@2.10.6': + resolution: {integrity: sha512-APPHdwfof58KSHboHsxBEF/R8DZauNFkpd43ew+d363PtdJL6EOQ87DDn1CewmvWoQuXqV2kvcZkq6+Rl6NZwQ==} - '@apps-in-toss/web-config@2.10.4': - resolution: {integrity: sha512-w9onw/MND1DunbSmhQHZWkhMvFeZ3Y3NinY6ysSBPm9BGe3xMoJZPt71on5cuptGSWaKjmbh68X87V9wK14dSw==} + '@apps-in-toss/web-config@2.10.6': + resolution: {integrity: sha512-O8cfRrXNww3VLEYzP/y5cAbyHX/e2pVlwGSckBvo09LTxz1b2/xXeRNbcTh85w/Feg876L2QG9SqvEOq5D5W6g==} - '@apps-in-toss/web-framework@2.10.4': - resolution: {integrity: sha512-ZHjKRcJBlHpnAC8aAzVLexf20izBbUqlMjiimFy3M4OWIglVxvgxTb8pmiWkLS6xoPkmbU3g4porNAa76U1O3w==} + '@apps-in-toss/web-framework@2.10.6': + resolution: {integrity: sha512-LYYhRr0tXgUOkYgQOXFyXsIqML0lGjPzBqTE3Mazu56mCN5xB6oNp6yaOpUCIr/+vh/gMvr7YT3lDXpLUrAbCA==} hasBin: true '@asamuzakjp/css-color@3.2.0': @@ -9825,7 +9825,7 @@ packages: snapshots: - '@ait-co/devtools@0.1.137(197a08e79f5a0dea7c9c567c0dba765e)': + '@ait-co/devtools@0.1.138(ddacffd1360e345338f8069a3f016ad5)': dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@vitest/expect': 4.1.9 @@ -9840,7 +9840,7 @@ snapshots: unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) ws: 8.21.0 optionalDependencies: - '@apps-in-toss/web-framework': 2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) + '@apps-in-toss/web-framework': 2.10.6(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0) transitivePeerDependencies: - '@cfworker/json-schema' - '@farmfe/core' @@ -9871,7 +9871,7 @@ snapshots: '@apps-in-toss/ait-format-proto': 1.0.0 fflate: 0.8.3 - '@apps-in-toss/analytics@2.10.4(517315b53d0d70e3b66b55bebacce804)': + '@apps-in-toss/analytics@2.10.6(517315b53d0d70e3b66b55bebacce804)': dependencies: '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) @@ -9880,7 +9880,7 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) - '@apps-in-toss/analytics@2.10.4(9c282825ce6d3d717af57bc67c854205)': + '@apps-in-toss/analytics@2.10.6(9c282825ce6d3d717af57bc67c854205)': dependencies: '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) @@ -9889,13 +9889,13 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) - '@apps-in-toss/bridge-core@2.10.4': {} + '@apps-in-toss/bridge-core@2.10.6': {} - '@apps-in-toss/cli@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + '@apps-in-toss/cli@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-config': 2.10.6(@types/node@24.13.2)(typescript@7.0.2) '@clack/prompts': 0.10.1 '@granite-js/utils': 1.0.20 '@sentry/cli': 2.58.6 @@ -9931,11 +9931,11 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/cli@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + '@apps-in-toss/cli@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-config': 2.10.6(@types/node@24.13.2)(typescript@7.0.2) '@clack/prompts': 0.10.1 '@granite-js/utils': 1.0.20 '@sentry/cli': 2.58.6 @@ -9971,11 +9971,11 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/cli@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': + '@apps-in-toss/cli@2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-config': 2.10.6(@types/node@24.13.2)(typescript@7.0.2) '@clack/prompts': 0.10.1 '@granite-js/utils': 1.0.20 '@sentry/cli': 2.58.6 @@ -10011,14 +10011,14 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/framework@2.10.4(db61d165d46a8a47fb7719835ebd892e)': + '@apps-in-toss/framework@2.10.6(db61d165d46a8a47fb7719835ebd892e)': dependencies: - '@apps-in-toss/analytics': 2.10.4(9c282825ce6d3d717af57bc67c854205) - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/native-modules': 2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/types': 2.10.4 - '@apps-in-toss/user-scripts': 2.10.4 + '@apps-in-toss/analytics': 2.10.6(9c282825ce6d3d717af57bc67c854205) + '@apps-in-toss/cli': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/native-modules': 2.10.6(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/types': 2.10.6 + '@apps-in-toss/user-scripts': 2.10.6 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) '@toss/tds-react-native': 2.0.3(febdec7493c1b8afdc2c391c9f1f45ad) @@ -10049,14 +10049,14 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/framework@2.10.4(fe6e5424b707ceb9cb0a963285f7648e)': + '@apps-in-toss/framework@2.10.6(fe6e5424b707ceb9cb0a963285f7648e)': dependencies: - '@apps-in-toss/analytics': 2.10.4(517315b53d0d70e3b66b55bebacce804) - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/native-modules': 2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/types': 2.10.4 - '@apps-in-toss/user-scripts': 2.10.4 + '@apps-in-toss/analytics': 2.10.6(517315b53d0d70e3b66b55bebacce804) + '@apps-in-toss/cli': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/native-modules': 2.10.6(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/types': 2.10.6 + '@apps-in-toss/user-scripts': 2.10.6 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) '@toss/tds-react-native': 2.0.3(d7d036f8004849fb2ad75cccc074de3d) @@ -10087,9 +10087,9 @@ snapshots: - typescript - utf-8-validate - ? '@apps-in-toss/native-modules@2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' + ? '@apps-in-toss/native-modules@2.10.6(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)' : dependencies: - '@apps-in-toss/types': 2.10.4 + '@apps-in-toss/types': 2.10.6 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0) @@ -10097,9 +10097,9 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0) - ? '@apps-in-toss/native-modules@2.10.4(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' + ? '@apps-in-toss/native-modules@2.10.6(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@granite-js/react-native@1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)' : dependencies: - '@apps-in-toss/types': 2.10.4 + '@apps-in-toss/types': 2.10.6 '@granite-js/native': 1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) '@granite-js/react-native': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@granite-js/native@1.0.20(@babel/runtime@7.29.7)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(@types/react@19.2.17)(brick-module@0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(jiti@1.21.7)(postcss@8.5.16)(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typescript@7.0.2)(yaml@2.9.0) brick-module: 0.5.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0) @@ -10107,7 +10107,7 @@ snapshots: react: 18.2.0 react-native: 0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0) - '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugin-compat@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@babel/runtime': 7.29.7 '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) @@ -10166,7 +10166,7 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugin-compat@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@babel/runtime': 7.29.7 '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) @@ -10225,7 +10225,7 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugin-compat@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugin-compat@2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@babel/runtime': 7.29.7 '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) @@ -10284,10 +10284,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugins@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugins@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugin-compat': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.17)(@types/node@24.13.2)(typescript@7.0.2) @@ -10320,10 +10320,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugins@2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugins@2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugin-compat': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) @@ -10356,10 +10356,10 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/plugins@2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': + '@apps-in-toss/plugins@2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2)': dependencies: '@apps-in-toss/ait-format': 1.0.0 - '@apps-in-toss/plugin-compat': 2.10.4(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/plugin-compat': 2.10.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) '@granite-js/plugin-core': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-micro-frontend': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) '@granite-js/plugin-sentry': 1.0.20(@swc/helpers@0.5.23)(@types/node@24.13.2)(typescript@7.0.2) @@ -10392,19 +10392,19 @@ snapshots: - typescript - utf-8-validate - '@apps-in-toss/types@2.10.4': {} + '@apps-in-toss/types@2.10.6': {} - '@apps-in-toss/user-scripts@2.10.4': {} + '@apps-in-toss/user-scripts@2.10.6': {} - '@apps-in-toss/web-analytics@2.10.4(@apps-in-toss/web-bridge@2.10.4)': + '@apps-in-toss/web-analytics@2.10.6(@apps-in-toss/web-bridge@2.10.6)': dependencies: - '@apps-in-toss/web-bridge': 2.10.4 + '@apps-in-toss/web-bridge': 2.10.6 - '@apps-in-toss/web-bridge@2.10.4': + '@apps-in-toss/web-bridge@2.10.6': dependencies: - '@apps-in-toss/types': 2.10.4 + '@apps-in-toss/types': 2.10.6 - '@apps-in-toss/web-config@2.10.4(@types/node@24.13.2)(typescript@7.0.2)': + '@apps-in-toss/web-config@2.10.6(@types/node@24.13.2)(typescript@7.0.2)': dependencies: '@babel/core': 7.23.9 '@babel/traverse': 7.29.7 @@ -10417,15 +10417,15 @@ snapshots: - supports-color - typescript - '@apps-in-toss/web-framework@2.10.4(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': + '@apps-in-toss/web-framework@2.10.6(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@toss/tds-react-native@2.0.3(d7d036f8004849fb2ad75cccc074de3d))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.17))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': dependencies: - '@apps-in-toss/bridge-core': 2.10.4 - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/framework': 2.10.4(fe6e5424b707ceb9cb0a963285f7648e) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/web-analytics': 2.10.4(@apps-in-toss/web-bridge@2.10.4) - '@apps-in-toss/web-bridge': 2.10.4 - '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/bridge-core': 2.10.6 + '@apps-in-toss/cli': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/framework': 2.10.6(fe6e5424b707ceb9cb0a963285f7648e) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.17)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.23.9))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-analytics': 2.10.6(@apps-in-toss/web-bridge@2.10.6) + '@apps-in-toss/web-bridge': 2.10.6 + '@apps-in-toss/web-config': 2.10.6(@types/node@24.13.2)(typescript@7.0.2) '@babel/core': 7.23.9 '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.17)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) @@ -10470,15 +10470,15 @@ snapshots: - utf-8-validate - yaml - '@apps-in-toss/web-framework@2.10.4(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': + '@apps-in-toss/web-framework@2.10.6(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@toss/tds-react-native@2.0.3(febdec7493c1b8afdc2c391c9f1f45ad))(@types/node@24.13.2)(@types/react@19.2.17)(jiti@1.21.7)(postcss@8.5.16)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(ts-node@10.9.2(@swc/core@1.15.8(@swc/helpers@0.5.23))(@types/node@24.13.2)(typescript@7.0.2))(typanion@3.14.0)(typescript@7.0.2)(yaml@2.9.0)': dependencies: - '@apps-in-toss/bridge-core': 2.10.4 - '@apps-in-toss/cli': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) - '@apps-in-toss/framework': 2.10.4(db61d165d46a8a47fb7719835ebd892e) - '@apps-in-toss/plugins': 2.10.4(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) - '@apps-in-toss/web-analytics': 2.10.4(@apps-in-toss/web-bridge@2.10.4) - '@apps-in-toss/web-bridge': 2.10.4 - '@apps-in-toss/web-config': 2.10.4(@types/node@24.13.2)(typescript@7.0.2) + '@apps-in-toss/bridge-core': 2.10.6 + '@apps-in-toss/cli': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typanion@3.14.0)(typescript@7.0.2) + '@apps-in-toss/framework': 2.10.6(db61d165d46a8a47fb7719835ebd892e) + '@apps-in-toss/plugins': 2.10.6(@babel/core@7.23.9)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(@react-navigation/native@7.2.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(@swc/helpers@0.5.23)(@types/node@24.13.2)(react-native-safe-area-context@5.6.2(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native-screens@4.23.0(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0))(react-native@0.72.6(@babel/core@7.29.7)(@babel/preset-env@7.28.5(@babel/core@7.29.7))(react@18.2.0))(react@18.2.0)(typescript@7.0.2) + '@apps-in-toss/web-analytics': 2.10.6(@apps-in-toss/web-bridge@2.10.6) + '@apps-in-toss/web-bridge': 2.10.6 + '@apps-in-toss/web-config': 2.10.6(@types/node@24.13.2)(typescript@7.0.2) '@babel/core': 7.23.9 '@granite-js/cli': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@swc/helpers@0.5.23)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) '@granite-js/mpack': 1.0.20(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@1.21.7)(postcss@8.5.16)(typescript@7.0.2)(yaml@2.9.0) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f398eb07..dff260e9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -62,3 +62,4 @@ minimumReleaseAgeExclude: - '@devvit/web-view-scripts@0.13.8' - '@devvit/web@0.13.8' - devvit@0.13.8 + - '@ait-co/devtools@0.1.138' diff --git a/tools/validate-starter-workflow.ts b/tools/validate-starter-workflow.ts index c3a05aca..10bb9c57 100644 --- a/tools/validate-starter-workflow.ts +++ b/tools/validate-starter-workflow.ts @@ -185,6 +185,7 @@ if (manifest !== null) { } validatePhaserTemplateAITPolyfill(); +validatePhaserTemplateAITWrapper(); validatePhaserTemplateAITConsoleCli(); validatePhaserTemplateDevvitPostOperations(); validatePhaserTemplateDevvitViewModes(); @@ -854,6 +855,244 @@ function validatePhaserTemplateAITConsoleCli(): void { } } +function validatePhaserTemplateAITWrapper(): void { + const templateRoot = 'packages/cli/templates/phaser-game'; + const wrapperRoot = `${templateRoot}/apps/target-ait`; + const wrapperPackagePath = `${wrapperRoot}/package.json`; + const wrapperMainPath = `${wrapperRoot}/src/main.ts`; + const wrapperVitePath = `${wrapperRoot}/vite.config.ts`; + const wrapperGranitePath = `${wrapperRoot}/granite.config.ts`; + const targetsPath = `${templateRoot}/mpgd.targets.json`; + const rootPackagePath = `${templateRoot}/package.json`; + const gitignorePath = `${templateRoot}/gitignore`; + const adapterPackagePath = 'adapters/ait/package.json'; + const baseTsconfigPath = 'tsconfig.base.json'; + + for (const path of [ + wrapperPackagePath, + wrapperMainPath, + wrapperVitePath, + wrapperGranitePath, + targetsPath, + rootPackagePath, + gitignorePath, + adapterPackagePath, + baseTsconfigPath, + ]) { + if (!existsSync(path)) { + failures.push(`${path}: required for the game-owned AIT wrapper flow.`); + } + } + + if (existsSync(adapterPackagePath)) { + const packageJson = readJson(adapterPackagePath) as { + readonly exports?: Record; + } | null; + + if (packageJson !== null) { + assertEqual( + packageJson.exports?.['./package.json'], + './package.json', + `${adapterPackagePath}: exports[./package.json]`, + ); + + for (const [subpath, basename] of [ + ['./ad-config', 'ad-config'], + ['./host', 'host'], + ['./wrapper', 'wrapper'], + ] as const) { + const exported = packageJson.exports?.[subpath]; + + if (!isJsonObject(exported)) { + failures.push(`${adapterPackagePath}: exports[${subpath}] must be an object.`); + continue; + } + + assertEqual( + exported.types, + `./dist/${basename}.d.ts`, + `${adapterPackagePath}: exports[${subpath}].types`, + ); + assertEqual( + exported.default, + `./dist/${basename}.js`, + `${adapterPackagePath}: exports[${subpath}].default`, + ); + } + } + } + + if (existsSync(baseTsconfigPath)) { + const tsconfig = readJson(baseTsconfigPath) as { + readonly compilerOptions?: { + readonly paths?: Record; + }; + } | null; + + if (tsconfig !== null) { + assertIncludes( + tsconfig.compilerOptions?.paths?.['@mpgd/adapter-ait/package.json'], + './adapters/ait/package.json', + `${baseTsconfigPath}: compilerOptions.paths[@mpgd/adapter-ait/package.json]`, + ); + } + } + + if (existsSync(wrapperPackagePath)) { + const packageJson = readJson(wrapperPackagePath) as { + readonly dependencies?: Record; + readonly devDependencies?: Record; + readonly scripts?: Record; + } | null; + + if (packageJson !== null) { + assertString( + packageJson.dependencies?.['@mpgd/adapter-ait'], + `${wrapperPackagePath}: dependencies.@mpgd/adapter-ait`, + ); + assertString( + packageJson.dependencies?.['@apps-in-toss/web-framework'], + `${wrapperPackagePath}: dependencies.@apps-in-toss/web-framework`, + ); + assertString( + packageJson.devDependencies?.['@ait-co/devtools'], + `${wrapperPackagePath}: devDependencies.@ait-co/devtools`, + ); + assertEqual( + packageJson.scripts?.['devtools:mcp'], + 'pnpm exec devtools-mcp', + `${wrapperPackagePath}: devtools:mcp`, + ); + assertEqual( + packageJson.scripts?.['devtools:mcp:mobile'], + 'pnpm exec devtools-mcp --target=mobile', + `${wrapperPackagePath}: devtools:mcp:mobile`, + ); + } + } + + if (existsSync(wrapperMainPath)) { + const content = readText(wrapperMainPath); + for (const requiredText of [ + "from '@mpgd/adapter-ait/host'", + "from '@mpgd/adapter-ait/wrapper'", + 'installAitHostBridge({', + 'mountAitGameBundle(app)', + ]) { + assertIncludesText(content, requiredText, wrapperMainPath); + } + } + + if (existsSync(wrapperVitePath)) { + const content = readText(wrapperVitePath); + for (const requiredText of [ + "from '@ait-co/devtools/unplugin'", + "from '@mpgd/adapter-ait/ad-config'", + 'aitDevtools.vite({ mcp: true', + '__MPGD_AIT_AD_GROUP_IDS__', + '__MPGD_AIT_AD_PLACEMENT_TYPES__', + ]) { + assertIncludesText(content, requiredText, wrapperVitePath); + } + } + + if (existsSync(wrapperGranitePath)) { + const content = readText(wrapperGranitePath); + for (const requiredText of ['__GAME_NAME__', '__GAME_TITLE__', 'generated/console-icon.png']) { + assertIncludesText(content, requiredText, wrapperGranitePath); + } + } + + if (existsSync(targetsPath)) { + const targets = readJson(targetsPath) as { + readonly targets?: { + readonly ait?: { + readonly wrapperApp?: unknown; + readonly webDir?: unknown; + readonly metadata?: { + readonly appName?: unknown; + readonly displayName?: unknown; + readonly primaryColor?: unknown; + readonly sdkMajor?: unknown; + }; + }; + }; + } | null; + + if (targets !== null) { + assertEqual( + targets.targets?.ait?.wrapperApp, + 'apps/target-ait', + `${targetsPath}: targets.ait.wrapperApp`, + ); + assertEqual( + targets.targets?.ait?.webDir, + 'apps/target-ait/public/game', + `${targetsPath}: targets.ait.webDir`, + ); + assertEqual( + targets.targets?.ait?.metadata?.appName, + '__GAME_NAME__', + `${targetsPath}: targets.ait.metadata.appName`, + ); + assertEqual( + targets.targets?.ait?.metadata?.displayName, + '__GAME_TITLE__', + `${targetsPath}: targets.ait.metadata.displayName`, + ); + assertEqual( + targets.targets?.ait?.metadata?.primaryColor, + '#101820', + `${targetsPath}: targets.ait.metadata.primaryColor`, + ); + if (targets.targets?.ait?.metadata?.sdkMajor !== 2) { + failures.push(`${targetsPath}: targets.ait.metadata.sdkMajor must be 2.`); + } + } + } + + if (existsSync(rootPackagePath)) { + const packageJson = readJson(rootPackagePath) as { + readonly scripts?: Record; + } | null; + + if (packageJson !== null) { + for (const [scriptName, scriptValue] of Object.entries({ + 'dev:ait': + "APP_TARGET=ait MPGD_CONFIG_TARGET=ait MPGD_PLATFORM_TARGETS_FILE=./mpgd.targets.json APP_VERSION=0.0.0-dev BUILD_ID=ait-sandbox sh -c '__WORKSPACE_I18N_BUILD_PREFIX__vite --host 0.0.0.0'", + 'ait:wrapper:dev': 'pnpm --dir apps/target-ait run dev', + 'ait:wrapper:dev:plain': 'pnpm --dir apps/target-ait run dev:plain', + 'ait:wrapper:dev:phone': 'pnpm --dir apps/target-ait run dev:phone', + 'ait:devtools:mcp': 'pnpm --dir apps/target-ait run devtools:mcp', + 'ait:devtools:mcp:mobile': 'pnpm --dir apps/target-ait run devtools:mcp:mobile', + })) { + assertEqual( + packageJson.scripts?.[scriptName], + scriptValue, + `${rootPackagePath}: ${scriptName}`, + ); + } + } + } + + if (existsSync(gitignorePath)) { + const ignoredPaths = new Set(readText(gitignorePath).split(/\r?\n/u)); + + for (const ignoredPath of [ + 'apps/target-ait/.ait_relay', + 'apps/target-ait/.ait_urls', + 'apps/target-ait/.granite/', + 'apps/target-ait/generated/', + 'apps/target-ait/public/assets/', + 'apps/target-ait/public/game/', + ]) { + if (!ignoredPaths.has(ignoredPath)) { + failures.push(`${gitignorePath}: must ignore ${ignoredPath}.`); + } + } + } +} + function validatePhaserTemplateDevvitPostOperations(): void { const kitPackagePath = 'apps/target-devvit/package.json'; const kitStorePath = 'apps/target-devvit/src/server/postOperationStore.ts'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 07d60702..aebefe4a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -23,7 +23,10 @@ "@mpgd/adapter-devvit/surface": ["./adapters/devvit/src/surface.ts"], "@mpgd/adapter-devvit/view-mode": ["./adapters/devvit/src/view-mode.ts"], "@mpgd/adapter-devvit/web": ["./adapters/devvit/src/web.ts"], + "@mpgd/adapter-ait/package.json": ["./adapters/ait/package.json"], + "@mpgd/adapter-ait/ad-config": ["./adapters/ait/src/ad-config.ts"], "@mpgd/adapter-ait/host": ["./adapters/ait/src/host.ts"], + "@mpgd/adapter-ait/wrapper": ["./adapters/ait/src/wrapper.ts"], "@mpgd/adapter-verse8/agent8": ["./adapters/verse8/src/agent8.ts"], "@mpgd/adapter-verse8/server": ["./adapters/verse8/src/server.ts"], "@mpgd/catalog/*": ["./packages/catalog/*"],