Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .sampo/changesets/ait-host-capability-support.md
Original file line number Diff line number Diff line change
@@ -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.
54 changes: 54 additions & 0 deletions adapters/ait/src/host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 20 additions & 7 deletions adapters/ait/src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ export function createAitHostBridge(
async function handleRequest(request: BridgeRequest): Promise<BridgeResponse> {
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
Expand Down Expand Up @@ -220,7 +219,7 @@ export function createAitHostBridge(
);
}

if (!dependencies.loadFullScreenAd.isSupported()) {
if (!isCapabilitySupported(() => dependencies.loadFullScreenAd.isSupported())) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate preloads on both full-screen ad APIs

When loadFullScreenAd.isSupported() returns true but showFullScreenAd.isSupported() is missing or false, this branch still calls loadFullScreenAd and reports preload success even though runtime.getCapabilities and the show calls fail closed via areFullScreenAdsSupported. In that partial-support compatibility case, preloading can invoke the native Ads 2.0 load path on a host that cannot display the ad; use the same combined support check here so configured preloads are also fail-closed until both APIs report support.

Useful? React with 👍 / 👎.

if (!warnedUnsupportedAdPreload) {
warnedUnsupportedAdPreload = true;
console.warn(
Expand Down Expand Up @@ -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 });
}
Expand Down Expand Up @@ -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' });
}
Expand Down Expand Up @@ -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<Record<string, string>> | undefined,
): ReadonlyMap<string, string> {
Expand Down