@mpgd/platform 0.12.2 → 0.14.0
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PlatformCapabilities, PlatformGateway, PlatformTarget } from './index.js';
|
|
2
2
|
export declare const platformCapabilityKeys: readonly ["nativeIap", "nativeAds", "rewardedAds", "interstitialAds", "nativeLeaderboard", "remoteLeaderboard", "achievements", "cloudSave", "socialShare", "haptics", "localizedContent"];
|
|
3
|
-
export declare const optionalPlatformCapabilityKeys: readonly ["bannerAds"];
|
|
3
|
+
export declare const optionalPlatformCapabilityKeys: readonly ["bannerAds", "subscriptionIap"];
|
|
4
4
|
export interface PlatformGatewayCapabilityConformanceTransition {
|
|
5
5
|
readonly update: () => Promise<void> | void;
|
|
6
6
|
readonly expectedCapabilities: PlatformCapabilities;
|
|
@@ -27,7 +27,7 @@ export interface PlatformGatewayCapabilityConformanceReport {
|
|
|
27
27
|
* target-configured wrappers. Every read must return a complete boolean
|
|
28
28
|
* snapshot, a fresh object, and the provider's latest state.
|
|
29
29
|
* @evidence docs/specs/platform-capability-snapshots.md#snapshot-shape Checks each gateway read against the required boolean keys and expected provider state.
|
|
30
|
-
* @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #
|
|
30
|
+
* @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #a1834db Reviewed required booleans, optional banner/subscription defaults, ordered readiness comparison, and fresh nested rereads.
|
|
31
31
|
* @evidence docs/specs/platform-capability-snapshots.md#provider-transitions Checks a fresh provider read after the optional transition update.
|
|
32
32
|
* @evidenceReview docs/specs/platform-capability-snapshots.md#provider-transitions #839c0c8 Reviewed the changed-state guard and post-update snapshot assertion.
|
|
33
33
|
* @evidence docs/specs/platform-capability-snapshots.md#fixture-validation Rejects invalid fixture sets and returns the names of passing fixtures.
|
|
@@ -13,19 +13,40 @@ export const platformCapabilityKeys = Object.freeze([
|
|
|
13
13
|
]);
|
|
14
14
|
export const optionalPlatformCapabilityKeys = Object.freeze([
|
|
15
15
|
'bannerAds',
|
|
16
|
+
'subscriptionIap',
|
|
16
17
|
]);
|
|
17
18
|
const allowedPlatformCapabilityKeys = Object.freeze([
|
|
18
19
|
...platformCapabilityKeys,
|
|
19
20
|
...optionalPlatformCapabilityKeys,
|
|
20
21
|
]);
|
|
21
|
-
const allowedPlatformCapabilityKeySet = new Set(
|
|
22
|
+
const allowedPlatformCapabilityKeySet = new Set([
|
|
23
|
+
...allowedPlatformCapabilityKeys,
|
|
24
|
+
'providerAvailability',
|
|
25
|
+
]);
|
|
22
26
|
const optionalPlatformCapabilityKeySet = new Set(optionalPlatformCapabilityKeys);
|
|
27
|
+
const providerFeatureSet = new Set([
|
|
28
|
+
'nativeIap',
|
|
29
|
+
'subscriptionIap',
|
|
30
|
+
'rewardedAds',
|
|
31
|
+
'interstitialAds',
|
|
32
|
+
'bannerAds',
|
|
33
|
+
'nativeLeaderboard',
|
|
34
|
+
'identityUpgrade',
|
|
35
|
+
'pushNotifications',
|
|
36
|
+
]);
|
|
37
|
+
const providerAvailabilitySet = new Set([
|
|
38
|
+
'unsupported',
|
|
39
|
+
'configuration-required',
|
|
40
|
+
'action-required',
|
|
41
|
+
'temporarily-unavailable',
|
|
42
|
+
'available',
|
|
43
|
+
]);
|
|
23
44
|
/**
|
|
24
45
|
* Runs provider-neutral capability checks against real adapter gateways or
|
|
25
46
|
* target-configured wrappers. Every read must return a complete boolean
|
|
26
47
|
* snapshot, a fresh object, and the provider's latest state.
|
|
27
48
|
* @evidence docs/specs/platform-capability-snapshots.md#snapshot-shape Checks each gateway read against the required boolean keys and expected provider state.
|
|
28
|
-
* @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #
|
|
49
|
+
* @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #a1834db Reviewed required booleans, optional banner/subscription defaults, ordered readiness comparison, and fresh nested rereads.
|
|
29
50
|
* @evidence docs/specs/platform-capability-snapshots.md#provider-transitions Checks a fresh provider read after the optional transition update.
|
|
30
51
|
* @evidenceReview docs/specs/platform-capability-snapshots.md#provider-transitions #839c0c8 Reviewed the changed-state guard and post-update snapshot assertion.
|
|
31
52
|
* @evidence docs/specs/platform-capability-snapshots.md#fixture-validation Rejects invalid fixture sets and returns the names of passing fixtures.
|
|
@@ -60,10 +81,10 @@ export async function runPlatformGatewayCapabilityConformance(input) {
|
|
|
60
81
|
async function runFixture(fixture) {
|
|
61
82
|
// A fixture may accidentally share its expected object with the provider.
|
|
62
83
|
// Keep the oracle independent before tryMutateSnapshot probes isolation.
|
|
63
|
-
const initialExpected =
|
|
84
|
+
const initialExpected = cloneExpectedCapabilities(fixture.expectedCapabilities);
|
|
64
85
|
const transitionExpected = fixture.transition === undefined
|
|
65
86
|
? undefined
|
|
66
|
-
:
|
|
87
|
+
: cloneExpectedCapabilities(fixture.transition.expectedCapabilities);
|
|
67
88
|
assertEqual(fixture.gateway.target, fixture.expectedTarget, 'gateway target must match the fixture target');
|
|
68
89
|
const first = await fixture.gateway.getCapabilities();
|
|
69
90
|
assertCapabilitySnapshot(first, initialExpected);
|
|
@@ -93,6 +114,29 @@ function assertCapabilitySnapshot(actual, expected) {
|
|
|
93
114
|
}
|
|
94
115
|
assertEqual(optional ? (actual[key] ?? false) : actual[key], optional ? (expected[key] ?? false) : expected[key], `capability ${key} must match the expected provider state`);
|
|
95
116
|
}
|
|
117
|
+
assertProviderAvailabilityShape(actual.providerAvailability);
|
|
118
|
+
assertEqual(normalizedAvailability(actual.providerAvailability), normalizedAvailability(expected.providerAvailability), 'provider availability must match the expected provider state');
|
|
119
|
+
}
|
|
120
|
+
function assertProviderAvailabilityShape(value) {
|
|
121
|
+
if (value === undefined) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
assert(typeof value === 'object' && value !== null && !Array.isArray(value), 'provider availability must be a non-array record');
|
|
125
|
+
for (const [feature, state] of Object.entries(value)) {
|
|
126
|
+
assert(providerFeatureSet.has(feature)
|
|
127
|
+
&& providerAvailabilitySet.has(state), 'provider availability must contain only known features and readiness states');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function cloneExpectedCapabilities(value) {
|
|
131
|
+
return {
|
|
132
|
+
...value,
|
|
133
|
+
...(value.providerAvailability === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { providerAvailability: { ...value.providerAvailability } }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function normalizedAvailability(value) {
|
|
139
|
+
return JSON.stringify(Object.entries(value ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
96
140
|
}
|
|
97
141
|
function tryMutateSnapshot(snapshot) {
|
|
98
142
|
try {
|
|
@@ -101,10 +145,23 @@ function tryMutateSnapshot(snapshot) {
|
|
|
101
145
|
catch {
|
|
102
146
|
// Frozen snapshots already satisfy the isolation requirement.
|
|
103
147
|
}
|
|
148
|
+
if (snapshot.providerAvailability !== undefined) {
|
|
149
|
+
try {
|
|
150
|
+
const next = snapshot.providerAvailability.nativeIap === 'available'
|
|
151
|
+
? 'unsupported'
|
|
152
|
+
: 'available';
|
|
153
|
+
Reflect.set(snapshot.providerAvailability, 'nativeIap', next);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// A frozen nested readiness record already satisfies isolation.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
104
159
|
}
|
|
105
160
|
function hasCapabilityDifference(first, second) {
|
|
106
161
|
return allowedPlatformCapabilityKeys
|
|
107
|
-
.some((key) => (first[key] ?? false) !== (second[key] ?? false))
|
|
162
|
+
.some((key) => (first[key] ?? false) !== (second[key] ?? false))
|
|
163
|
+
|| normalizedAvailability(first.providerAvailability)
|
|
164
|
+
!== normalizedAvailability(second.providerAvailability);
|
|
108
165
|
}
|
|
109
166
|
function assert(condition, message) {
|
|
110
167
|
if (!condition) {
|
package/dist/index.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare function findAuthoritativePurchaseSettlement(input: {
|
|
|
83
83
|
readonly restore?: PurchaseRestoreResult;
|
|
84
84
|
}): PurchaseSettlement | null;
|
|
85
85
|
export interface RewardedAdResult {
|
|
86
|
-
readonly status: 'completed' | 'skipped' | 'unavailable' | 'failed';
|
|
86
|
+
readonly status: 'completed' | 'pending' | 'skipped' | 'unavailable' | 'failed';
|
|
87
87
|
readonly rewardGranted: boolean;
|
|
88
88
|
readonly ledgerEntryId?: string;
|
|
89
89
|
readonly evidence?: PlatformEvidenceEnvelope;
|
|
@@ -114,6 +114,8 @@ export interface CommerceAdapter {
|
|
|
114
114
|
export interface AdAdapter {
|
|
115
115
|
preload(input: {
|
|
116
116
|
readonly placementId: LogicalAdPlacementId;
|
|
117
|
+
/** Allows a shared provider preload method to enforce format readiness. */
|
|
118
|
+
readonly format?: 'rewarded' | 'interstitial' | 'banner';
|
|
117
119
|
}): Promise<void>;
|
|
118
120
|
showRewarded(input: {
|
|
119
121
|
readonly placementId: LogicalAdPlacementId;
|
|
@@ -161,6 +163,8 @@ export interface LeaderboardScoreInput {
|
|
|
161
163
|
readonly score: number;
|
|
162
164
|
readonly runId: string;
|
|
163
165
|
readonly submittedAt: string;
|
|
166
|
+
/** Optional target-selected route when native and remote backends coexist. */
|
|
167
|
+
readonly route?: 'native' | 'remote';
|
|
164
168
|
}
|
|
165
169
|
export interface LeaderboardSubmitResult {
|
|
166
170
|
readonly submitted: boolean;
|
|
@@ -170,10 +174,17 @@ export interface LeaderboardAdapter {
|
|
|
170
174
|
submitScore(input: LeaderboardScoreInput): Promise<LeaderboardSubmitResult>;
|
|
171
175
|
open(input?: {
|
|
172
176
|
readonly leaderboardId?: string;
|
|
177
|
+
readonly route?: 'native' | 'remote';
|
|
173
178
|
}): Promise<void>;
|
|
174
179
|
}
|
|
180
|
+
/** Readiness of an installed platform provider, independent of target policy. */
|
|
181
|
+
export type PlatformProviderAvailability = 'unsupported' | 'configuration-required' | 'action-required' | 'temporarily-unavailable' | 'available';
|
|
182
|
+
/** Existing capabilities and integrations annotated by optional providers. */
|
|
183
|
+
export type PlatformProviderFeature = 'nativeIap' | 'subscriptionIap' | 'rewardedAds' | 'interstitialAds' | 'bannerAds' | 'nativeLeaderboard' | 'identityUpgrade' | 'pushNotifications';
|
|
175
184
|
export interface PlatformCapabilities {
|
|
176
185
|
readonly nativeIap: boolean;
|
|
186
|
+
/** Optional for adapters published before subscription-specific support. */
|
|
187
|
+
readonly subscriptionIap?: boolean;
|
|
177
188
|
readonly nativeAds: boolean;
|
|
178
189
|
readonly rewardedAds: boolean;
|
|
179
190
|
readonly interstitialAds: boolean;
|
|
@@ -187,6 +198,8 @@ export interface PlatformCapabilities {
|
|
|
187
198
|
readonly socialShare: boolean;
|
|
188
199
|
readonly haptics: boolean;
|
|
189
200
|
readonly localizedContent: boolean;
|
|
201
|
+
/** Explains a false capability without claiming an unavailable SDK works. */
|
|
202
|
+
readonly providerAvailability?: Readonly<Partial<Record<PlatformProviderFeature, PlatformProviderAvailability>>>;
|
|
190
203
|
}
|
|
191
204
|
export interface PlayerIdentity {
|
|
192
205
|
readonly playerId: string;
|
|
@@ -267,9 +280,77 @@ export interface NotificationSubscriptionAdapter {
|
|
|
267
280
|
getStatus(topic: NotificationTopic): Promise<NotificationSubscriptionStatus>;
|
|
268
281
|
requestSubscription(topic: NotificationTopic): Promise<NotificationSubscriptionResult>;
|
|
269
282
|
}
|
|
283
|
+
/** The source of an app URL, not a claim that the URL is trusted. */
|
|
284
|
+
export interface NativeOpenUrlEvent {
|
|
285
|
+
readonly url: string;
|
|
286
|
+
readonly source: 'cold' | 'warm';
|
|
287
|
+
}
|
|
288
|
+
export interface NativeBackButtonEvent {
|
|
289
|
+
/** Whether the WebView history can navigate back, not the game's own scene stack. */
|
|
290
|
+
readonly canGoBack: boolean;
|
|
291
|
+
}
|
|
292
|
+
/** Full WebView viewport geometry in CSS pixels, before applying game-owned padding. */
|
|
293
|
+
export interface PlatformViewportInsets {
|
|
294
|
+
readonly top: number;
|
|
295
|
+
readonly right: number;
|
|
296
|
+
readonly bottom: number;
|
|
297
|
+
readonly left: number;
|
|
298
|
+
}
|
|
299
|
+
export interface PlatformViewportBounds {
|
|
300
|
+
readonly x: number;
|
|
301
|
+
readonly y: number;
|
|
302
|
+
readonly width: number;
|
|
303
|
+
readonly height: number;
|
|
304
|
+
}
|
|
305
|
+
/** An occupied game surface with stable identity, not an extra inset to sum. */
|
|
306
|
+
export interface PlatformViewportOccupiedSurface {
|
|
307
|
+
readonly surfaceId: string;
|
|
308
|
+
readonly edge: 'top' | 'right' | 'bottom' | 'left';
|
|
309
|
+
readonly bounds: PlatformViewportBounds;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Structurally matches `TargetViewportUsableAreaInput` in @mpgd/target-config
|
|
313
|
+
* (field names, CSS-pixel units, and edge union), so consumers can pass the
|
|
314
|
+
* same state to `resolveTargetViewportUsableArea` without conversion.
|
|
315
|
+
*/
|
|
316
|
+
export interface PlatformViewportState {
|
|
317
|
+
readonly width: number;
|
|
318
|
+
readonly height: number;
|
|
319
|
+
readonly safeAreaInsets: PlatformViewportInsets;
|
|
320
|
+
readonly systemBarInsets: PlatformViewportInsets;
|
|
321
|
+
readonly keyboardInsets: PlatformViewportInsets;
|
|
322
|
+
readonly occupiedSurfaces: readonly PlatformViewportOccupiedSurface[];
|
|
323
|
+
}
|
|
324
|
+
/** Optional native viewport source; consumers use one owner for canvas and DOM layout. */
|
|
325
|
+
export interface ViewportAdapter {
|
|
326
|
+
getState(): PlatformViewportState;
|
|
327
|
+
onChange(callback: (state: PlatformViewportState) => void): () => void;
|
|
328
|
+
dispose?(): void;
|
|
329
|
+
}
|
|
270
330
|
export interface LifecycleAdapter {
|
|
271
331
|
onPause(callback: () => void): () => void;
|
|
272
332
|
onResume(callback: () => void): () => void;
|
|
333
|
+
/**
|
|
334
|
+
* Android handlers run last-registered-first until one returns true. If none
|
|
335
|
+
* consumes the event, Capacitor navigates WebView history when possible or
|
|
336
|
+
* exits the app. The method's presence does not imply support on iOS: check
|
|
337
|
+
* the gateway target. Persist progress at checkpoints; back/exit callbacks
|
|
338
|
+
* are not guaranteed to run before process termination.
|
|
339
|
+
*/
|
|
340
|
+
onBackButton?(handler: (event: NativeBackButtonEvent) => boolean | Promise<boolean>): () => void;
|
|
341
|
+
/** Game links and OAuth redirects use separate callbacks. */
|
|
342
|
+
onGameUrlOpen?(callback: (event: NativeOpenUrlEvent) => void): () => void;
|
|
343
|
+
onOAuthRedirect?(callback: (event: NativeOpenUrlEvent) => void): () => void;
|
|
344
|
+
getInitialGameUrl?(): Promise<NativeOpenUrlEvent | null>;
|
|
345
|
+
getInitialOAuthRedirect?(): Promise<NativeOpenUrlEvent | null>;
|
|
346
|
+
/**
|
|
347
|
+
* Keep execution paused while external UI is open. Call the returned
|
|
348
|
+
* idempotent release function when it closes, including failure paths;
|
|
349
|
+
* otherwise execution remains paused.
|
|
350
|
+
*/
|
|
351
|
+
beginExternalActivity?(): () => void;
|
|
352
|
+
/** Remove only the native listeners owned by this adapter instance. */
|
|
353
|
+
dispose?(): Promise<void>;
|
|
273
354
|
}
|
|
274
355
|
export interface StorageLoadResult {
|
|
275
356
|
readonly value: unknown;
|
|
@@ -284,6 +365,19 @@ export interface StorageAdapter {
|
|
|
284
365
|
readonly value: unknown;
|
|
285
366
|
}): Promise<void>;
|
|
286
367
|
}
|
|
368
|
+
/** Device-protected opaque credentials; never backed by ordinary game storage. */
|
|
369
|
+
export interface SecureCredentialStore {
|
|
370
|
+
load(input: {
|
|
371
|
+
readonly key: string;
|
|
372
|
+
}): Promise<string | null>;
|
|
373
|
+
save(input: {
|
|
374
|
+
readonly key: string;
|
|
375
|
+
readonly value: string;
|
|
376
|
+
}): Promise<void>;
|
|
377
|
+
remove(input: {
|
|
378
|
+
readonly key: string;
|
|
379
|
+
}): Promise<void>;
|
|
380
|
+
}
|
|
287
381
|
export interface PlatformGateway {
|
|
288
382
|
readonly target: PlatformTarget;
|
|
289
383
|
getCapabilities(): Promise<PlatformCapabilities>;
|
|
@@ -292,7 +386,10 @@ export interface PlatformGateway {
|
|
|
292
386
|
readonly ads: AdAdapter;
|
|
293
387
|
readonly leaderboard: LeaderboardAdapter;
|
|
294
388
|
readonly lifecycle: LifecycleAdapter;
|
|
389
|
+
readonly viewport?: ViewportAdapter;
|
|
295
390
|
readonly storage: StorageAdapter;
|
|
391
|
+
/** Optional: missing native support must not fall back to plaintext storage. */
|
|
392
|
+
readonly secureCredentials?: SecureCredentialStore;
|
|
296
393
|
readonly presentation?: PresentationAdapter;
|
|
297
394
|
readonly sharing?: ShareAdapter;
|
|
298
395
|
readonly notifications?: NotificationSubscriptionAdapter;
|
package/package.json
CHANGED