@mpgd/adapter-ait 0.12.7 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ad-provider.d.ts +18 -0
- package/dist/ad-provider.js +295 -0
- package/dist/host.d.ts +1 -1
- package/dist/host.js +57 -193
- package/dist/index.d.ts +3 -0
- package/dist/index.js +43 -1
- package/dist/lifecycle.d.ts +1 -0
- package/dist/lifecycle.js +81 -34
- package/package.json +6 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AdProvider, type AdShowInput, type AdShowResult } from '@mpgd/platform/ads';
|
|
2
|
+
import type { AitHostDependencies } from './host.js';
|
|
3
|
+
import type { AitAdPlacementType } from './ad-config.js';
|
|
4
|
+
/** SDK event interpretation only. Game execution and backend grants are separate owners. */
|
|
5
|
+
export declare function createAitAdProvider(input: {
|
|
6
|
+
readonly dependencies: Pick<AitHostDependencies, 'showFullScreenAd'>;
|
|
7
|
+
readonly groups: ReadonlyMap<string, string>;
|
|
8
|
+
readonly types: ReadonlyMap<string, AitAdPlacementType>;
|
|
9
|
+
readonly isSupported: () => boolean;
|
|
10
|
+
readonly prepare: (group: string) => Promise<void>;
|
|
11
|
+
readonly consume: (group: string) => boolean;
|
|
12
|
+
readonly requestTimeoutMs: number;
|
|
13
|
+
readonly startTimeoutMs: number;
|
|
14
|
+
readonly displayTimeoutMs: number;
|
|
15
|
+
readonly onLegacyPhase?: (active: boolean) => void;
|
|
16
|
+
}): AdProvider & {
|
|
17
|
+
showLegacy(request: AdShowInput): Promise<AdShowResult>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { adProtocol, adProtocolVersion, assertAdPresentationEvent, assertAdShowInput, assertAdShowResult, } from '@mpgd/platform/ads';
|
|
2
|
+
const nativeOwners = new WeakMap();
|
|
3
|
+
/** SDK event interpretation only. Game execution and backend grants are separate owners. */
|
|
4
|
+
export function createAitAdProvider(input) {
|
|
5
|
+
const id = 'apps-in-toss-ads';
|
|
6
|
+
const sdk = input.dependencies.showFullScreenAd;
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
function availability(request) {
|
|
9
|
+
if (!input.isSupported()) {
|
|
10
|
+
return { state: 'unsupported', reason: 'unsupported' };
|
|
11
|
+
}
|
|
12
|
+
if (input.types.has(request.placementId) && input.types.get(request.placementId) !== request.format) {
|
|
13
|
+
return {
|
|
14
|
+
state: 'unsupported',
|
|
15
|
+
reason: 'unsupported',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (!input.groups.has(request.placementId) || !input.types.has(request.placementId)) {
|
|
19
|
+
return {
|
|
20
|
+
state: 'configuration-required',
|
|
21
|
+
reason: 'configuration-required',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const owner = nativeOwners.get(sdk);
|
|
25
|
+
if (owner?.state === 'unknown') {
|
|
26
|
+
return {
|
|
27
|
+
state: 'action-required',
|
|
28
|
+
reason: 'action-required',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (owner !== undefined) {
|
|
32
|
+
return { state: 'temporarily-unavailable', reason: 'busy' };
|
|
33
|
+
}
|
|
34
|
+
return { state: 'available' };
|
|
35
|
+
}
|
|
36
|
+
async function show(supplied, legacy) {
|
|
37
|
+
const request = assertAdShowInput(supplied);
|
|
38
|
+
if (request.idempotencyKey.length > 256 || /[\p{Cc}\p{Cf}]/u.test(request.idempotencyKey)) {
|
|
39
|
+
throw new TypeError('AIT ad correlation must contain 1 to 256 visible characters.');
|
|
40
|
+
}
|
|
41
|
+
const makeResult = (state) => assertAdShowResult({ providerId: id, invocationId: request.invocationId, format: request.format, ...state });
|
|
42
|
+
const noStart = (outcome, reason) => makeResult({
|
|
43
|
+
outcome,
|
|
44
|
+
presentation: 'not-started',
|
|
45
|
+
eligibility: request.format === 'rewarded' ? 'not-earned' : 'not-applicable',
|
|
46
|
+
...(reason === undefined ? {} : { reason }),
|
|
47
|
+
});
|
|
48
|
+
const available = availability(request);
|
|
49
|
+
if (available.state !== 'available') {
|
|
50
|
+
return noStart('unavailable', available.reason);
|
|
51
|
+
}
|
|
52
|
+
const group = input.groups.get(request.placementId);
|
|
53
|
+
if (group === undefined) {
|
|
54
|
+
return noStart('unavailable', 'configuration-required');
|
|
55
|
+
}
|
|
56
|
+
const owned = { state: 'requested' };
|
|
57
|
+
nativeOwners.set(sdk, owned);
|
|
58
|
+
const legacyPhase = (active) => {
|
|
59
|
+
if (!legacy) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
input.onLegacyPhase?.(active);
|
|
64
|
+
}
|
|
65
|
+
catch { /* Isolate lifecycle observation. */ }
|
|
66
|
+
};
|
|
67
|
+
const release = () => {
|
|
68
|
+
if (nativeOwners.get(sdk) === owned) {
|
|
69
|
+
nativeOwners.delete(sdk);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
await input.prepare(group);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
release();
|
|
77
|
+
return noStart('failed', 'transient-failure');
|
|
78
|
+
}
|
|
79
|
+
if (!input.consume(group)) {
|
|
80
|
+
release();
|
|
81
|
+
return noStart('failed', 'transient-failure');
|
|
82
|
+
}
|
|
83
|
+
let started = false;
|
|
84
|
+
let closed = false;
|
|
85
|
+
let earned;
|
|
86
|
+
let sequence = 0;
|
|
87
|
+
let cleanup;
|
|
88
|
+
let cleaned = false;
|
|
89
|
+
let timer;
|
|
90
|
+
let displayTimerArmed = false;
|
|
91
|
+
let delivered = false;
|
|
92
|
+
let resolve;
|
|
93
|
+
const result = new Promise((done) => {
|
|
94
|
+
resolve = done;
|
|
95
|
+
});
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
const emit = (event) => {
|
|
98
|
+
const safe = assertAdPresentationEvent({
|
|
99
|
+
...event,
|
|
100
|
+
providerId: id,
|
|
101
|
+
invocationId: request.invocationId,
|
|
102
|
+
sequence: ++sequence,
|
|
103
|
+
});
|
|
104
|
+
for (const listener of [...listeners]) {
|
|
105
|
+
try {
|
|
106
|
+
void Promise.resolve(listener(safe)).catch(() => undefined);
|
|
107
|
+
}
|
|
108
|
+
catch { /* Keep native terminal observation alive. */ }
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const clean = () => {
|
|
112
|
+
if (cleaned || cleanup === undefined || !closed || request.format === 'rewarded' && earned === undefined) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
cleaned = true;
|
|
116
|
+
try {
|
|
117
|
+
cleanup();
|
|
118
|
+
}
|
|
119
|
+
catch { /* Cleanup does not change native closure. */ }
|
|
120
|
+
};
|
|
121
|
+
const finish = (value) => {
|
|
122
|
+
if (!delivered) {
|
|
123
|
+
delivered = true;
|
|
124
|
+
resolve(value);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const eligibility = () => {
|
|
128
|
+
if (request.format === 'interstitial') {
|
|
129
|
+
return 'not-applicable';
|
|
130
|
+
}
|
|
131
|
+
return earned === undefined ? 'unknown' : 'eligible';
|
|
132
|
+
};
|
|
133
|
+
const uncertain = () => {
|
|
134
|
+
if (closed || owned.state === 'unknown') {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
owned.state = 'unknown';
|
|
138
|
+
emit({ type: 'unknown' });
|
|
139
|
+
finish(makeResult({
|
|
140
|
+
outcome: 'pending',
|
|
141
|
+
presentation: 'unknown',
|
|
142
|
+
eligibility: eligibility(),
|
|
143
|
+
reason: 'outcome-unknown',
|
|
144
|
+
...(earned === undefined ? {} : { evidence: earned }),
|
|
145
|
+
}));
|
|
146
|
+
};
|
|
147
|
+
const close = (failed) => {
|
|
148
|
+
if (closed) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
closed = true;
|
|
152
|
+
if (timer !== undefined) {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
}
|
|
155
|
+
release();
|
|
156
|
+
legacyPhase(false);
|
|
157
|
+
emit(failed ? { type: 'failed', reason: 'transient-failure' } : { type: 'closed' });
|
|
158
|
+
if (failed && !started && earned === undefined) {
|
|
159
|
+
finish(noStart('failed', 'transient-failure'));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
finish(makeResult({
|
|
163
|
+
outcome: failed ? 'failed' : 'shown',
|
|
164
|
+
presentation: 'closed',
|
|
165
|
+
eligibility: eligibility(),
|
|
166
|
+
...(failed ? { reason: 'transient-failure' } : {}),
|
|
167
|
+
...(earned === undefined ? {} : { evidence: earned }),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
// Failure before presentation proves no earning is forthcoming. After a
|
|
171
|
+
// started presentation, retain the scoped callback for a delayed reward.
|
|
172
|
+
if (failed && !started && earned === undefined) {
|
|
173
|
+
cleaned = true;
|
|
174
|
+
if (cleanup !== undefined) {
|
|
175
|
+
try {
|
|
176
|
+
cleanup();
|
|
177
|
+
}
|
|
178
|
+
catch { /* Isolate cleanup. */ }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
clean();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const startedEvent = () => {
|
|
186
|
+
if (closed) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!started) {
|
|
190
|
+
started = true;
|
|
191
|
+
owned.state = 'open';
|
|
192
|
+
emit({ type: 'started' });
|
|
193
|
+
}
|
|
194
|
+
if (!displayTimerArmed) {
|
|
195
|
+
displayTimerArmed = true;
|
|
196
|
+
if (timer !== undefined) {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
}
|
|
199
|
+
timer = setTimeout(uncertain, input.displayTimeoutMs);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
legacyPhase(true);
|
|
203
|
+
emit({ type: 'requested' });
|
|
204
|
+
timer = setTimeout(uncertain, input.requestTimeoutMs);
|
|
205
|
+
try {
|
|
206
|
+
cleanup = sdk({
|
|
207
|
+
options: { adGroupId: group },
|
|
208
|
+
onEvent(event) {
|
|
209
|
+
switch (event.type) {
|
|
210
|
+
case 'requested':
|
|
211
|
+
if (!closed && !started) {
|
|
212
|
+
if (timer !== undefined) {
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
}
|
|
215
|
+
timer = setTimeout(uncertain, Math.min(input.startTimeoutMs, Math.max(0, input.requestTimeoutMs - (Date.now() - startedAt))));
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'show':
|
|
219
|
+
case 'impression':
|
|
220
|
+
case 'clicked':
|
|
221
|
+
startedEvent();
|
|
222
|
+
break;
|
|
223
|
+
case 'userEarnedReward':
|
|
224
|
+
if (request.format !== 'rewarded' || earned !== undefined || cleaned) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (!closed) {
|
|
228
|
+
startedEvent();
|
|
229
|
+
}
|
|
230
|
+
earned = { schema: 'apps-in-toss.rewarded-ad.callback.v1', payload: { event: 'user-earned-reward', correlationId: request.idempotencyKey, placementId: group } };
|
|
231
|
+
emit({ type: 'reward-earned', evidence: earned });
|
|
232
|
+
clean();
|
|
233
|
+
break;
|
|
234
|
+
case 'dismissed':
|
|
235
|
+
close(false);
|
|
236
|
+
break;
|
|
237
|
+
case 'failedToShow':
|
|
238
|
+
close(true);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
// A bridge error does not prove the native UI never opened.
|
|
243
|
+
onError() { uncertain(); },
|
|
244
|
+
});
|
|
245
|
+
if (cleaned) {
|
|
246
|
+
try {
|
|
247
|
+
cleanup();
|
|
248
|
+
}
|
|
249
|
+
catch { /* Isolate synchronous terminal cleanup. */ }
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
clean();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
uncertain();
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
id,
|
|
262
|
+
protocol: adProtocol,
|
|
263
|
+
protocolVersion: adProtocolVersion,
|
|
264
|
+
rewardSignal: 'delayed',
|
|
265
|
+
getAvailability: async (request) => availability(request),
|
|
266
|
+
async preload(request) {
|
|
267
|
+
const available = availability(request);
|
|
268
|
+
if (available.state !== 'available') {
|
|
269
|
+
return {
|
|
270
|
+
status: 'unavailable',
|
|
271
|
+
...(available.reason === undefined ? {} : { reason: available.reason }),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const group = input.groups.get(request.placementId);
|
|
275
|
+
if (group === undefined) {
|
|
276
|
+
return { status: 'unavailable', reason: 'configuration-required' };
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
await input.prepare(group);
|
|
280
|
+
return { status: 'ready' };
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return { status: 'failed', reason: 'transient-failure' };
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
show: (request) => show(request, false),
|
|
287
|
+
showLegacy: (request) => show(request, true),
|
|
288
|
+
subscribe(listener) {
|
|
289
|
+
listeners.add(listener);
|
|
290
|
+
return () => {
|
|
291
|
+
listeners.delete(listener);
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -149,7 +149,7 @@ export interface InstallAitHostBridgeOptions {
|
|
|
149
149
|
readonly adLoadQueueTimeoutMs?: number;
|
|
150
150
|
/** Upper bound for the requested-to-display portion of the total show timeout. */
|
|
151
151
|
readonly adDisplayStartTimeoutMs?: number;
|
|
152
|
-
/**
|
|
152
|
+
/** Caller wait limit; a missing native terminal callback keeps presentation unknown. */
|
|
153
153
|
readonly adMaximumDisplayMs?: number;
|
|
154
154
|
/** Maximum wait for an inline banner to render or report no-fill/failure. */
|
|
155
155
|
readonly bannerRenderTimeoutMs?: number;
|
package/dist/host.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getTossShareLink, getUserKeyForGame, grantPromotionRewardForGame as grantPromotionReward, IAP, isMinVersionSupported, loadFullScreenAd, openGameCenterLeaderboard, requestNotificationAgreement, share, showFullScreenAd, Storage, submitGameCenterLeaderBoardScore, TossAds, } from '@apps-in-toss/web-framework';
|
|
2
2
|
import { assertBridgeRequest, bridgeStorageLoadProtocol, } from '@mpgd/bridge';
|
|
3
|
+
import { assertAdShowInput, toAdAdapter } from '@mpgd/platform/ads';
|
|
4
|
+
import { createAitAdProvider } from './ad-provider.js';
|
|
3
5
|
import { dispatchAitLifecycleEvent } from './lifecycle.js';
|
|
4
6
|
const defaultAdTimeoutMs = 60_000;
|
|
5
7
|
const defaultAdLoadQueueTimeoutMs = 5_000;
|
|
@@ -13,7 +15,6 @@ const defaultIapPurchaseSessionTimeoutMs = 30 * 60_000;
|
|
|
13
15
|
/** A provider response is untrusted input; keep restore work bounded per launch. */
|
|
14
16
|
const maximumPendingIapOrders = 20;
|
|
15
17
|
const invalidBridgeRequestId = 'ait-invalid-request';
|
|
16
|
-
const rewardedAdEvidenceSchema = 'apps-in-toss.rewarded-ad.callback.v1';
|
|
17
18
|
const iapEvidenceSchema = 'apps-in-toss.iap.callback.v1';
|
|
18
19
|
const minimumPromotionTossAppVersion = '5.232.0';
|
|
19
20
|
/**
|
|
@@ -115,7 +116,6 @@ export function createAitHostBridge(options = {}) {
|
|
|
115
116
|
const notificationSubscriptions = new Set();
|
|
116
117
|
const loadedAdGroupIds = new Set();
|
|
117
118
|
const loadingAdGroups = new Map();
|
|
118
|
-
const activeAdGroupIds = new Set();
|
|
119
119
|
const activeBannerAttachments = new Map();
|
|
120
120
|
const bannerMountOwners = new Map();
|
|
121
121
|
const pendingBannerMountSettlements = new Map();
|
|
@@ -131,10 +131,20 @@ export function createAitHostBridge(options = {}) {
|
|
|
131
131
|
tone: options.bannerAppearance?.tone ?? 'blackAndWhite',
|
|
132
132
|
variant: options.bannerAppearance?.variant ?? 'expanded',
|
|
133
133
|
};
|
|
134
|
-
const adLoadCoordinator =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
const adLoadCoordinator = createAdLoadCoordinator(dependencies.loadFullScreenAd, adLoadQueueTimeoutMs);
|
|
135
|
+
const nativeAds = createAitAdProvider({
|
|
136
|
+
dependencies,
|
|
137
|
+
groups: adGroupIds,
|
|
138
|
+
types: adPlacementTypes,
|
|
139
|
+
isSupported: () => areFullScreenAdsSupported(dependencies),
|
|
140
|
+
prepare: (group) => preloadAdGroup(dependencies, group, loadedAdGroupIds, loadingAdGroups, adLoadCoordinator, adTimeoutMs),
|
|
141
|
+
consume: (group) => consumeLoadedAd(group, loadedAdGroupIds),
|
|
142
|
+
requestTimeoutMs: adTimeoutMs,
|
|
143
|
+
startTimeoutMs: adDisplayStartTimeoutMs,
|
|
144
|
+
displayTimeoutMs: adMaximumDisplayMs,
|
|
145
|
+
onLegacyPhase: (active) => dispatchAitLifecycleEvent(active ? 'pause' : 'resume'),
|
|
146
|
+
});
|
|
147
|
+
const legacyAds = toAdAdapter({ ...nativeAds, show: nativeAds.showLegacy });
|
|
138
148
|
const enqueueCompletedIapAttemptRetention = (storage, storageKey) => {
|
|
139
149
|
const scheduled = completedIapAttemptRetention.then(async () => {
|
|
140
150
|
await retainCompletedAitIapPurchaseAttempt(storage, storageKey, iapProductGrantTimeoutMs);
|
|
@@ -145,6 +155,13 @@ export function createAitHostBridge(options = {}) {
|
|
|
145
155
|
return next;
|
|
146
156
|
};
|
|
147
157
|
return {
|
|
158
|
+
advertising: {
|
|
159
|
+
id: nativeAds.id,
|
|
160
|
+
protocol: nativeAds.protocol,
|
|
161
|
+
protocolVersion: nativeAds.protocolVersion,
|
|
162
|
+
rewardSignal: nativeAds.rewardSignal,
|
|
163
|
+
subscribe: (listener) => nativeAds.subscribe(listener),
|
|
164
|
+
},
|
|
148
165
|
async request(input) {
|
|
149
166
|
try {
|
|
150
167
|
return await handleRequest(parseBridgeRequest(input));
|
|
@@ -377,54 +394,24 @@ export function createAitHostBridge(options = {}) {
|
|
|
377
394
|
await preloadAdGroupWithDiagnostics(dependencies, adGroupId, loadedAdGroupIds, loadingAdGroups, adLoadCoordinator, adTimeoutMs, placementType);
|
|
378
395
|
return ok(request, {});
|
|
379
396
|
}
|
|
380
|
-
case 'ads.
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return ok(request, { status: 'unavailable', rewardGranted: false });
|
|
388
|
-
}
|
|
389
|
-
const shown = await withLoadedAdSlot(dependencies, adGroupId, loadedAdGroupIds, loadingAdGroups, adLoadCoordinator, activeAdGroupIds, adTimeoutMs, 'rewarded', async () => {
|
|
390
|
-
const result = await showRewardedAd(dependencies, adGroupId, adTimeoutMs, adDisplayStartTimeoutMs, adMaximumDisplayMs);
|
|
391
|
-
return result.rewardGranted
|
|
392
|
-
? {
|
|
393
|
-
...result,
|
|
394
|
-
// The correlationId is client-supplied evidence, not a native receipt:
|
|
395
|
-
// the Apps in Toss show call only carries the adGroupId, so nothing
|
|
396
|
-
// native echoes it back. game-services forwards it as
|
|
397
|
-
// platformImpressionId; the backend rewardAuthority must verify it
|
|
398
|
-
// (and the rest of this evidence) before granting any reward.
|
|
399
|
-
ledgerEntryId: correlationId,
|
|
400
|
-
evidence: {
|
|
401
|
-
schema: rewardedAdEvidenceSchema,
|
|
402
|
-
payload: {
|
|
403
|
-
event: 'user-earned-reward',
|
|
404
|
-
correlationId,
|
|
405
|
-
placementId: adGroupId,
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
}
|
|
409
|
-
: result;
|
|
397
|
+
case 'ads.getAvailability':
|
|
398
|
+
case 'ads.prepare': {
|
|
399
|
+
const payload = readPayloadRecord(request.payload);
|
|
400
|
+
const query = assertAdShowInput({
|
|
401
|
+
...payload,
|
|
402
|
+
invocationId: 'host-query',
|
|
403
|
+
idempotencyKey: 'host-query',
|
|
410
404
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return ok(request, shown.value);
|
|
415
|
-
}
|
|
416
|
-
case 'ads.showInterstitial': {
|
|
417
|
-
const placementId = readPlacementId(request.payload);
|
|
418
|
-
const adGroupId = adGroupIds.get(placementId);
|
|
419
|
-
if (adGroupId === undefined
|
|
420
|
-
|| adPlacementTypes.get(placementId) !== 'interstitial'
|
|
421
|
-
|| !areFullScreenAdsSupported(dependencies)) {
|
|
422
|
-
return ok(request, { status: 'unavailable' });
|
|
423
|
-
}
|
|
424
|
-
const showInterstitial = () => showInterstitialAd(dependencies, adGroupId, adTimeoutMs, adDisplayStartTimeoutMs, adMaximumDisplayMs);
|
|
425
|
-
const shown = await withLoadedAdSlot(dependencies, adGroupId, loadedAdGroupIds, loadingAdGroups, adLoadCoordinator, activeAdGroupIds, adTimeoutMs, 'interstitial', showInterstitial);
|
|
426
|
-
return ok(request, shown.acquired ? shown.value : { status: 'unavailable' });
|
|
405
|
+
return ok(request, request.method === 'ads.prepare'
|
|
406
|
+
? await nativeAds.preload(query)
|
|
407
|
+
: await nativeAds.getAvailability(query));
|
|
427
408
|
}
|
|
409
|
+
case 'ads.show':
|
|
410
|
+
return ok(request, await nativeAds.show(assertAdShowInput(request.payload)));
|
|
411
|
+
case 'ads.showRewarded':
|
|
412
|
+
return ok(request, await legacyAds.showRewarded({ placementId: readPlacementId(request.payload), idempotencyKey: readRewardedAdCorrelationId(request.payload, request.id) }));
|
|
413
|
+
case 'ads.showInterstitial':
|
|
414
|
+
return ok(request, await legacyAds.showInterstitial?.({ placementId: readPlacementId(request.payload) }));
|
|
428
415
|
case 'ads.mountBanner': {
|
|
429
416
|
const placementId = readPlacementId(request.payload);
|
|
430
417
|
const surfaceId = readBannerSurfaceId(request.payload);
|
|
@@ -838,6 +825,24 @@ async function preloadAdGroup(dependencies, adGroupId, loaded, loading, coordina
|
|
|
838
825
|
}
|
|
839
826
|
}
|
|
840
827
|
}
|
|
828
|
+
const nativeAdLoadScopes = new WeakMap();
|
|
829
|
+
function createAdLoadCoordinator(sdk, waitTimeoutMs) {
|
|
830
|
+
let shared = nativeAdLoadScopes.get(sdk);
|
|
831
|
+
if (shared === undefined) {
|
|
832
|
+
shared = { active: undefined };
|
|
833
|
+
nativeAdLoadScopes.set(sdk, shared);
|
|
834
|
+
}
|
|
835
|
+
const owned = shared;
|
|
836
|
+
return {
|
|
837
|
+
get active() {
|
|
838
|
+
return owned.active;
|
|
839
|
+
},
|
|
840
|
+
set active(value) {
|
|
841
|
+
owned.active = value;
|
|
842
|
+
},
|
|
843
|
+
waitTimeoutMs,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
841
846
|
async function runSerializedAdLoad(coordinator, adGroupId, startNativeLoad) {
|
|
842
847
|
const waitDeadline = Date.now() + coordinator.waitTimeoutMs;
|
|
843
848
|
while (coordinator.active !== undefined) {
|
|
@@ -881,22 +886,6 @@ function waitForAdLoadSettlement(active, timeoutMs, adGroupId) {
|
|
|
881
886
|
void active.then(() => finish(), () => finish());
|
|
882
887
|
});
|
|
883
888
|
}
|
|
884
|
-
async function acquireLoadedAdSlot(dependencies, adGroupId, loaded, loading, coordinator, active, timeoutMs, placementType) {
|
|
885
|
-
if (active.has(adGroupId)) {
|
|
886
|
-
return false;
|
|
887
|
-
}
|
|
888
|
-
const loadedSuccessfully = await preloadAdGroupWithDiagnostics(dependencies, adGroupId, loaded, loading, coordinator, timeoutMs, placementType);
|
|
889
|
-
if (!loadedSuccessfully) {
|
|
890
|
-
return false;
|
|
891
|
-
}
|
|
892
|
-
// Re-check after the asynchronous load so concurrent callers cannot consume
|
|
893
|
-
// or display the same native ad slot twice.
|
|
894
|
-
if (active.has(adGroupId) || !consumeLoadedAd(adGroupId, loaded)) {
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
active.add(adGroupId);
|
|
898
|
-
return true;
|
|
899
|
-
}
|
|
900
889
|
async function preloadAdGroupWithDiagnostics(dependencies, adGroupId, loaded, loading, coordinator, timeoutMs, placementType) {
|
|
901
890
|
// The first caller enters preloadAdGroup synchronously and publishes its promise
|
|
902
891
|
// before yielding. Remember that ownership so concurrent waiters do not emit the
|
|
@@ -915,131 +904,6 @@ async function preloadAdGroupWithDiagnostics(dependencies, adGroupId, loaded, lo
|
|
|
915
904
|
return false;
|
|
916
905
|
}
|
|
917
906
|
}
|
|
918
|
-
async function withLoadedAdSlot(dependencies, adGroupId, loaded, loading, coordinator, active, timeoutMs, placementType, display) {
|
|
919
|
-
const acquired = await acquireLoadedAdSlot(dependencies, adGroupId, loaded, loading, coordinator, active, timeoutMs, placementType);
|
|
920
|
-
if (!acquired) {
|
|
921
|
-
return { acquired: false };
|
|
922
|
-
}
|
|
923
|
-
try {
|
|
924
|
-
return { acquired: true, value: await display() };
|
|
925
|
-
}
|
|
926
|
-
finally {
|
|
927
|
-
active.delete(adGroupId);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
async function showRewardedAd(dependencies, adGroupId, timeoutMs, displayStartTimeoutMs, maximumDisplayMs) {
|
|
931
|
-
let rewardGranted = false;
|
|
932
|
-
const status = await showAd(dependencies, adGroupId, timeoutMs, displayStartTimeoutMs, maximumDisplayMs, (eventType) => {
|
|
933
|
-
if (eventType === 'userEarnedReward') {
|
|
934
|
-
rewardGranted = true;
|
|
935
|
-
}
|
|
936
|
-
});
|
|
937
|
-
let resultStatus;
|
|
938
|
-
if (status === 'shown' && rewardGranted) {
|
|
939
|
-
resultStatus = 'completed';
|
|
940
|
-
}
|
|
941
|
-
else if (status === 'failed') {
|
|
942
|
-
resultStatus = 'failed';
|
|
943
|
-
}
|
|
944
|
-
else {
|
|
945
|
-
resultStatus = 'skipped';
|
|
946
|
-
}
|
|
947
|
-
return {
|
|
948
|
-
status: resultStatus,
|
|
949
|
-
rewardGranted,
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
async function showInterstitialAd(dependencies, adGroupId, timeoutMs, displayStartTimeoutMs, maximumDisplayMs) {
|
|
953
|
-
const status = await showAd(dependencies, adGroupId, timeoutMs, displayStartTimeoutMs, maximumDisplayMs);
|
|
954
|
-
return { status: status === 'failed' ? 'skipped' : status };
|
|
955
|
-
}
|
|
956
|
-
function showAd(dependencies, adGroupId, timeoutMs, displayStartTimeoutMs, maximumDisplayMs, observe) {
|
|
957
|
-
dispatchAitLifecycleEvent('pause');
|
|
958
|
-
return new Promise((resolve) => {
|
|
959
|
-
const startedAt = Date.now();
|
|
960
|
-
let cleanup = () => { };
|
|
961
|
-
let settled = false;
|
|
962
|
-
let maximumDisplayTimeoutArmed = false;
|
|
963
|
-
let adTimer;
|
|
964
|
-
const clearAdTimer = () => {
|
|
965
|
-
if (adTimer !== undefined) {
|
|
966
|
-
globalThis.clearTimeout(adTimer);
|
|
967
|
-
adTimer = undefined;
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
const finish = (status) => {
|
|
971
|
-
if (settled) {
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
settled = true;
|
|
975
|
-
clearAdTimer();
|
|
976
|
-
cleanup();
|
|
977
|
-
dispatchAitLifecycleEvent('resume');
|
|
978
|
-
resolve(status);
|
|
979
|
-
};
|
|
980
|
-
const armDisplayStartTimeout = () => {
|
|
981
|
-
clearAdTimer();
|
|
982
|
-
const elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
983
|
-
const remainingMs = Math.max(0, timeoutMs - elapsedMs);
|
|
984
|
-
adTimer = globalThis.setTimeout(() => finish('failed'), Math.min(displayStartTimeoutMs, remainingMs));
|
|
985
|
-
};
|
|
986
|
-
const armMaximumDisplayTimeout = () => {
|
|
987
|
-
if (maximumDisplayTimeoutArmed) {
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
maximumDisplayTimeoutArmed = true;
|
|
991
|
-
clearAdTimer();
|
|
992
|
-
adTimer = globalThis.setTimeout(() => {
|
|
993
|
-
console.warn('AIT full-screen ad omitted its terminal callback; recovering the game lifecycle.', adGroupId);
|
|
994
|
-
finish('shown');
|
|
995
|
-
}, maximumDisplayMs);
|
|
996
|
-
};
|
|
997
|
-
const failShow = (error) => {
|
|
998
|
-
if (settled) {
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
console.warn('Failed to show AIT full-screen ad.', adGroupId, error ?? 'unknown native error');
|
|
1002
|
-
finish('failed');
|
|
1003
|
-
};
|
|
1004
|
-
adTimer = globalThis.setTimeout(() => finish('failed'), timeoutMs);
|
|
1005
|
-
try {
|
|
1006
|
-
const unregister = dependencies.showFullScreenAd({
|
|
1007
|
-
options: { adGroupId },
|
|
1008
|
-
onEvent: (event) => {
|
|
1009
|
-
observe?.(event.type);
|
|
1010
|
-
switch (event.type) {
|
|
1011
|
-
case 'requested':
|
|
1012
|
-
armDisplayStartTimeout();
|
|
1013
|
-
break;
|
|
1014
|
-
case 'show':
|
|
1015
|
-
case 'impression':
|
|
1016
|
-
case 'clicked':
|
|
1017
|
-
case 'userEarnedReward':
|
|
1018
|
-
// Native terminal callbacks remain authoritative. A long, one-shot
|
|
1019
|
-
// recovery timeout prevents a broken SDK callback from deadlocking
|
|
1020
|
-
// the game forever without treating ordinary end-card dwell as failure.
|
|
1021
|
-
armMaximumDisplayTimeout();
|
|
1022
|
-
break;
|
|
1023
|
-
case 'dismissed':
|
|
1024
|
-
finish('shown');
|
|
1025
|
-
break;
|
|
1026
|
-
case 'failedToShow':
|
|
1027
|
-
finish('failed');
|
|
1028
|
-
break;
|
|
1029
|
-
}
|
|
1030
|
-
},
|
|
1031
|
-
onError: failShow,
|
|
1032
|
-
});
|
|
1033
|
-
cleanup = unregister;
|
|
1034
|
-
if (settled) {
|
|
1035
|
-
cleanup();
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
catch (error) {
|
|
1039
|
-
failShow(error);
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
907
|
/**
|
|
1044
908
|
* Launch and inbound-share parameters arrive from untrusted deep links. Bound
|
|
1045
909
|
* each forwarded value and the nested `queryParams` JSON so a hostile link
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
|
|
2
2
|
import { type PlatformGateway } from '@mpgd/platform';
|
|
3
|
+
import { type AdProvider } from '@mpgd/platform/ads';
|
|
3
4
|
export interface GamePlatformBridge {
|
|
4
5
|
request(input: BridgeRequest): Promise<BridgeResponse>;
|
|
6
|
+
/** Native observations are scoped to this host; SDK commands still use request envelopes. */
|
|
7
|
+
readonly advertising?: Pick<AdProvider, 'id' | 'protocol' | 'protocolVersion' | 'rewardSignal' | 'subscribe'>;
|
|
5
8
|
}
|
|
6
9
|
export interface AitSandboxStorage {
|
|
7
10
|
readonly getItem: (key: string) => string | null;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,48 @@
|
|
|
1
|
-
import { bridgeStorageLoadProtocol, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
1
|
+
import { assertBridgeResponse, bridgeStorageLoadProtocol, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
2
2
|
import { PlatformOperationError, } from '@mpgd/platform';
|
|
3
|
+
import { assertAdAvailability, assertAdPreparationResult, assertAdPresentationEvent, assertAdShowInput, assertAdShowResult, toAdAdapter, } from '@mpgd/platform/ads';
|
|
3
4
|
import { createAitLifecycleAdapter } from './lifecycle.js';
|
|
4
5
|
export function createAitPlatformGateway(input) {
|
|
6
|
+
const advertisingBridge = input.bridge ?? getBridge() ?? input.fallbackBridge;
|
|
7
|
+
const advertising = advertisingBridge?.advertising;
|
|
8
|
+
async function requestAdvertising(method, payload) {
|
|
9
|
+
if (advertisingBridge === undefined) {
|
|
10
|
+
throw new PlatformOperationError({
|
|
11
|
+
code: 'AIT_BRIDGE_NOT_INSTALLED',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const id = crypto.randomUUID();
|
|
15
|
+
const response = assertBridgeResponse(await advertisingBridge.request({ id, method, payload, meta: { target: 'ait', appVersion: input.appVersion, buildId: input.buildId, sentAt: new Date().toISOString() } }));
|
|
16
|
+
if (response.id !== id) {
|
|
17
|
+
throw new TypeError('Advertising response belongs to another bridge request.');
|
|
18
|
+
}
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new PlatformOperationError(response.error);
|
|
21
|
+
}
|
|
22
|
+
return response.data;
|
|
23
|
+
}
|
|
24
|
+
const provider = advertising === undefined
|
|
25
|
+
? undefined
|
|
26
|
+
: {
|
|
27
|
+
id: advertising.id,
|
|
28
|
+
protocol: advertising.protocol,
|
|
29
|
+
protocolVersion: advertising.protocolVersion,
|
|
30
|
+
rewardSignal: advertising.rewardSignal,
|
|
31
|
+
getAvailability: async (payload) => assertAdAvailability(await requestAdvertising('ads.getAvailability', payload)),
|
|
32
|
+
preload: async (payload) => assertAdPreparationResult(await requestAdvertising('ads.prepare', payload)),
|
|
33
|
+
show: async (supplied) => {
|
|
34
|
+
const request = assertAdShowInput(supplied);
|
|
35
|
+
const result = assertAdShowResult(await requestAdvertising('ads.show', request));
|
|
36
|
+
if (result.providerId !== advertising.id || result.invocationId !== request.invocationId || result.format !== request.format) {
|
|
37
|
+
throw new TypeError('Advertising result belongs to another invocation.');
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
},
|
|
41
|
+
subscribe: (listener) => advertising.subscribe((event) => listener(assertAdPresentationEvent(event))),
|
|
42
|
+
};
|
|
43
|
+
if (provider !== undefined) {
|
|
44
|
+
toAdAdapter(provider);
|
|
45
|
+
}
|
|
5
46
|
async function request(method, payload) {
|
|
6
47
|
const bridge = input.bridge ?? getBridge() ?? input.fallbackBridge;
|
|
7
48
|
if (bridge === undefined) {
|
|
@@ -60,6 +101,7 @@ export function createAitPlatformGateway(input) {
|
|
|
60
101
|
getEntitlements: () => request('commerce.getEntitlements', {}),
|
|
61
102
|
},
|
|
62
103
|
ads: {
|
|
104
|
+
...(provider === undefined ? {} : { provider }),
|
|
63
105
|
preload: (payload) => request('ads.preload', payload),
|
|
64
106
|
showRewarded: (payload) => request('ads.showRewarded', payload),
|
|
65
107
|
showInterstitial: (payload) => request('ads.showInterstitial', payload),
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LifecycleAdapter } from '@mpgd/platform';
|
|
2
2
|
export declare const aitLifecyclePauseEvent = "mpgd:ait:pause";
|
|
3
3
|
export declare const aitLifecycleResumeEvent = "mpgd:ait:resume";
|
|
4
|
+
/** Legacy SDK presentation, page lifetime, and visibility are independent owners. */
|
|
4
5
|
export declare function createAitLifecycleAdapter(): LifecycleAdapter;
|
|
5
6
|
export declare function dispatchAitLifecycleEvent(type: 'pause' | 'resume'): void;
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,45 +1,92 @@
|
|
|
1
1
|
export const aitLifecyclePauseEvent = 'mpgd:ait:pause';
|
|
2
2
|
export const aitLifecycleResumeEvent = 'mpgd:ait:resume';
|
|
3
|
-
|
|
3
|
+
/** Legacy SDK presentation, page lifetime, and visibility are independent owners. */
|
|
4
4
|
export function createAitLifecycleAdapter() {
|
|
5
|
-
return {
|
|
6
|
-
onPause(callback) {
|
|
7
|
-
return subscribeToAitLifecycle('pause', callback);
|
|
8
|
-
},
|
|
9
|
-
onResume(callback) {
|
|
10
|
-
return subscribeToAitLifecycle('resume', callback);
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
export function dispatchAitLifecycleEvent(type) {
|
|
15
|
-
globalThis.dispatchEvent?.(new Event(type === 'pause' ? aitLifecyclePauseEvent : aitLifecycleResumeEvent));
|
|
16
|
-
}
|
|
17
|
-
function subscribeToAitLifecycle(type, callback) {
|
|
18
5
|
const target = globalThis;
|
|
19
6
|
const documentTarget = target.document;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
const pauses = new Set();
|
|
8
|
+
const resumes = new Set();
|
|
9
|
+
let nativePaused = false;
|
|
10
|
+
let pageActive = true;
|
|
11
|
+
let attached = false;
|
|
12
|
+
let active = documentTarget?.visibilityState !== 'hidden';
|
|
13
|
+
const notify = (callback) => {
|
|
14
|
+
try {
|
|
15
|
+
void Promise.resolve(callback()).catch(() => undefined);
|
|
26
16
|
}
|
|
27
|
-
|
|
28
|
-
callback();
|
|
17
|
+
catch { /* Keep lifecycle observation alive. */ }
|
|
29
18
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
19
|
+
function reconcile() {
|
|
20
|
+
const next = !nativePaused && pageActive && documentTarget?.visibilityState !== 'hidden';
|
|
21
|
+
if (next === active) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
active = next;
|
|
25
|
+
for (const callback of [...(active ? resumes : pauses)]) {
|
|
26
|
+
notify(callback);
|
|
34
27
|
}
|
|
28
|
+
}
|
|
29
|
+
const nativePause = () => {
|
|
30
|
+
nativePaused = true;
|
|
31
|
+
reconcile();
|
|
32
|
+
};
|
|
33
|
+
const nativeResume = () => {
|
|
34
|
+
nativePaused = false;
|
|
35
|
+
reconcile();
|
|
35
36
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
target.addEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition);
|
|
40
|
-
return () => {
|
|
41
|
-
target.removeEventListener?.(eventName, handleCustomEvent);
|
|
42
|
-
documentTarget?.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
43
|
-
target.removeEventListener?.(type === 'pause' ? 'pagehide' : 'pageshow', handlePageTransition);
|
|
37
|
+
const pageHide = () => {
|
|
38
|
+
pageActive = false;
|
|
39
|
+
reconcile();
|
|
44
40
|
};
|
|
41
|
+
const pageShow = () => {
|
|
42
|
+
pageActive = true;
|
|
43
|
+
reconcile();
|
|
44
|
+
};
|
|
45
|
+
function attach() {
|
|
46
|
+
if (attached) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
attached = true;
|
|
50
|
+
target.addEventListener?.(aitLifecyclePauseEvent, nativePause);
|
|
51
|
+
target.addEventListener?.(aitLifecycleResumeEvent, nativeResume);
|
|
52
|
+
target.addEventListener?.('pagehide', pageHide);
|
|
53
|
+
target.addEventListener?.('pageshow', pageShow);
|
|
54
|
+
documentTarget?.addEventListener('visibilitychange', reconcile);
|
|
55
|
+
}
|
|
56
|
+
function detach() {
|
|
57
|
+
if (!attached) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
attached = false;
|
|
61
|
+
target.removeEventListener?.(aitLifecyclePauseEvent, nativePause);
|
|
62
|
+
target.removeEventListener?.(aitLifecycleResumeEvent, nativeResume);
|
|
63
|
+
target.removeEventListener?.('pagehide', pageHide);
|
|
64
|
+
target.removeEventListener?.('pageshow', pageShow);
|
|
65
|
+
documentTarget?.removeEventListener('visibilitychange', reconcile);
|
|
66
|
+
}
|
|
67
|
+
function subscribe(callbacks, callback) {
|
|
68
|
+
callbacks.add(callback);
|
|
69
|
+
attach();
|
|
70
|
+
if (callbacks === pauses && !active) {
|
|
71
|
+
notify(callback);
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
callbacks.delete(callback);
|
|
75
|
+
if (pauses.size + resumes.size === 0) {
|
|
76
|
+
detach();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
onPause: (callback) => subscribe(pauses, callback),
|
|
82
|
+
onResume: (callback) => subscribe(resumes, callback),
|
|
83
|
+
async dispose() {
|
|
84
|
+
pauses.clear();
|
|
85
|
+
resumes.clear();
|
|
86
|
+
detach();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function dispatchAitLifecycleEvent(type) {
|
|
91
|
+
globalThis.dispatchEvent?.(new Event(type === 'pause' ? aitLifecyclePauseEvent : aitLifecycleResumeEvent));
|
|
45
92
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-ait",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Apps in Toss WebView platform adapter for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"dist"
|
|
60
60
|
],
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@mpgd/bridge": "0.
|
|
63
|
-
"@mpgd/platform": "0.
|
|
62
|
+
"@mpgd/bridge": "0.11.0",
|
|
63
|
+
"@mpgd/platform": "0.16.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@apps-in-toss/web-framework": ">=3.0.0 <4"
|
|
@@ -71,10 +71,12 @@
|
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
+
"@types/node": "^24.0.0",
|
|
74
75
|
"@apps-in-toss/web-framework": "3.5.0",
|
|
75
76
|
"ttsc": "0.30.4",
|
|
76
77
|
"typescript": "7.0.2",
|
|
77
|
-
"vitest": "^4.0.0"
|
|
78
|
+
"vitest": "^4.0.0",
|
|
79
|
+
"@mpgd/game-runtime": "0.4.0"
|
|
78
80
|
},
|
|
79
81
|
"main": "./dist/index.js",
|
|
80
82
|
"types": "./dist/index.d.ts",
|