diff --git a/.sampo/changesets/ait-host-capability-support.md b/.sampo/changesets/ait-host-capability-support.md new file mode 100644 index 00000000..af8a2cc7 --- /dev/null +++ b/.sampo/changesets/ait-host-capability-support.md @@ -0,0 +1,7 @@ +--- +npm/@mpgd/adapter-ait: patch (Fixed) +--- + +Treat missing Apps in Toss Ads 2.0 support constants as an unsupported capability instead of +failing the game wrapper during startup. Preload and display requests remain fail-closed until +both native full-screen ad APIs report support. diff --git a/adapters/ait/src/host.test.ts b/adapters/ait/src/host.test.ts index 265a4d1d..4e119989 100644 --- a/adapters/ait/src/host.test.ts +++ b/adapters/ait/src/host.test.ts @@ -138,6 +138,60 @@ describe('AIT production host bridge', () => { } }); + it('treats missing Ads 2.0 support constants as unsupported without blocking startup', async () => { + const warning = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const diagnostic = vi.spyOn(console, 'debug').mockImplementation(() => {}); + const missingSupportConstant = Object.assign( + () => () => {}, + { + isSupported: () => { + throw new Error('native support constant is unavailable'); + }, + }, + ); + + try { + const bridge = createAitHostBridge({ + adGroupIds: { + SUDOKU_HINT_REWARDED: 'ait-ad-group-1', + SUDOKU_BREAK_INTERSTITIAL: 'ait-ad-group-2', + }, + adPlacementTypes: { + SUDOKU_HINT_REWARDED: 'rewarded', + SUDOKU_BREAK_INTERSTITIAL: 'interstitial', + }, + dependencies: createDependencies({ + loadFullScreenAd: missingSupportConstant, + showFullScreenAd: missingSupportConstant, + }), + }); + + await expect(request(bridge, 'runtime.getCapabilities', {})).resolves.toMatchObject({ + nativeAds: false, + rewardedAds: false, + interstitialAds: false, + }); + await expect(request(bridge, 'ads.preload', { + placementId: 'SUDOKU_HINT_REWARDED', + })).resolves.toEqual({}); + await expect(request(bridge, 'ads.showRewarded', { + placementId: 'SUDOKU_HINT_REWARDED', + idempotencyKey: 'reward-1', + })).resolves.toEqual({ status: 'unavailable', rewardGranted: false }); + await expect(request(bridge, 'ads.showInterstitial', { + placementId: 'SUDOKU_BREAK_INTERSTITIAL', + })).resolves.toEqual({ status: 'unavailable' }); + expect(warning).toHaveBeenCalledOnce(); + expect(diagnostic).toHaveBeenCalledWith( + 'AIT capability support check failed; disabling the feature.', + expect.objectContaining({ message: 'native support constant is unavailable' }), + ); + } finally { + diagnostic.mockRestore(); + warning.mockRestore(); + } + }); + it('grants a configured rewarded ad only after userEarnedReward and dismissal', async () => { let loadCallbacks: LoadAdCallbacks | undefined; let showCallbacks: ShowAdCallbacks | undefined; diff --git a/adapters/ait/src/host.ts b/adapters/ait/src/host.ts index af84a583..64d1f8e8 100644 --- a/adapters/ait/src/host.ts +++ b/adapters/ait/src/host.ts @@ -123,8 +123,7 @@ export function createAitHostBridge( async function handleRequest(request: BridgeRequest): Promise { switch (request.method) { case 'runtime.getCapabilities': { - const adsSupported = dependencies.loadFullScreenAd.isSupported() - && dependencies.showFullScreenAd.isSupported(); + const adsSupported = areFullScreenAdsSupported(dependencies); const rewardedAds = adsSupported && hasConfiguredAdType(adGroupIds, adPlacementTypes, 'rewarded'); const interstitialAds = adsSupported @@ -220,7 +219,7 @@ export function createAitHostBridge( ); } - if (!dependencies.loadFullScreenAd.isSupported()) { + if (!isCapabilitySupported(() => dependencies.loadFullScreenAd.isSupported())) { if (!warnedUnsupportedAdPreload) { warnedUnsupportedAdPreload = true; console.warn( @@ -250,8 +249,7 @@ export function createAitHostBridge( if ( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'rewarded' - || !dependencies.loadFullScreenAd.isSupported() - || !dependencies.showFullScreenAd.isSupported() + || !areFullScreenAdsSupported(dependencies) ) { return ok(request, { status: 'unavailable', rewardGranted: false }); } @@ -306,8 +304,7 @@ export function createAitHostBridge( if ( adGroupId === undefined || adPlacementTypes.get(placementId) !== 'interstitial' - || !dependencies.loadFullScreenAd.isSupported() - || !dependencies.showFullScreenAd.isSupported() + || !areFullScreenAdsSupported(dependencies) ) { return ok(request, { status: 'unavailable' }); } @@ -928,6 +925,22 @@ function isGameCenterSupported(dependencies: AitHostDependencies): boolean { return dependencies.isMinVersionSupported({ android: '5.221.0', ios: '5.221.0' }); } +function areFullScreenAdsSupported(dependencies: AitHostDependencies): boolean { + return isCapabilitySupported(() => dependencies.loadFullScreenAd.isSupported()) + && isCapabilitySupported(() => dependencies.showFullScreenAd.isSupported()); +} + +function isCapabilitySupported(check: () => boolean): boolean { + try { + return check() === true; + } catch (error) { + // Older hosts and local wrappers may not expose a native support constant yet. + // Missing capability metadata must disable the feature instead of blocking startup. + console.debug('AIT capability support check failed; disabling the feature.', error); + return false; + } +} + function normalizeAdGroupIds( input: Readonly> | undefined, ): ReadonlyMap {