@mpgd/target-config 0.15.2 → 0.17.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.
package/dist/runtime.js CHANGED
@@ -5,10 +5,13 @@ const adPlacementFeatureByType = {
5
5
  };
6
6
  export const platformFeatures = [
7
7
  'iap',
8
+ 'subscriptions',
8
9
  'bannerAds',
9
10
  'rewardedAds',
10
11
  'interstitialAds',
11
12
  'leaderboard',
13
+ 'nativeLeaderboard',
14
+ 'remoteLeaderboard',
12
15
  'localization',
13
16
  ];
14
17
  export function adPlacementFeatureFor(type) {
@@ -55,7 +58,9 @@ const integrationAvailabilityStateValues = [
55
58
  'available',
56
59
  'disabled',
57
60
  'approval-required',
61
+ 'action-required',
58
62
  'configuration-required',
63
+ 'temporarily-unavailable',
59
64
  'unsupported',
60
65
  ];
61
66
  export const integrationAvailabilityStates = integrationAvailabilityStateValues;
@@ -129,7 +134,18 @@ export function getTargetConfig(matrix, target) {
129
134
  return config;
130
135
  }
131
136
  export function isPlatformFeatureEnabled(config, feature) {
132
- return config.features[feature] === true;
137
+ switch (feature) {
138
+ case 'subscriptions':
139
+ return config.features.iap && config.features.subscriptions === true;
140
+ case 'nativeLeaderboard':
141
+ return config.features.leaderboard
142
+ && (config.features.nativeLeaderboard ?? config.leaderboard.native);
143
+ case 'remoteLeaderboard':
144
+ return config.features.leaderboard
145
+ && (config.features.remoteLeaderboard ?? true);
146
+ default:
147
+ return config.features[feature] === true;
148
+ }
133
149
  }
134
150
  export function applyTargetConfigToCapabilities(capabilities, config) {
135
151
  const bannerAds = capabilities.bannerAds === true && config.features.bannerAds === true;
@@ -138,46 +154,61 @@ export function applyTargetConfigToCapabilities(capabilities, config) {
138
154
  return {
139
155
  ...capabilities,
140
156
  nativeIap: capabilities.nativeIap && config.features.iap,
157
+ ...(capabilities.subscriptionIap === undefined
158
+ ? {}
159
+ : { subscriptionIap: capabilities.subscriptionIap && isPlatformFeatureEnabled(config, 'subscriptions') }),
141
160
  nativeAds: capabilities.nativeAds &&
142
161
  (bannerAds || rewardedAds || interstitialAds),
143
162
  bannerAds,
144
163
  rewardedAds,
145
164
  interstitialAds,
146
- nativeLeaderboard: capabilities.nativeLeaderboard && config.features.leaderboard,
147
- remoteLeaderboard: capabilities.remoteLeaderboard && config.features.leaderboard,
165
+ nativeLeaderboard: capabilities.nativeLeaderboard && isPlatformFeatureEnabled(config, 'nativeLeaderboard'),
166
+ remoteLeaderboard: capabilities.remoteLeaderboard && isPlatformFeatureEnabled(config, 'remoteLeaderboard'),
148
167
  localizedContent: capabilities.localizedContent && config.features.localization,
149
168
  };
150
169
  }
151
170
  export function getFeatureAvailability(feature, config, capabilities) {
152
- const targetEnabled = config.features[feature] === true;
153
- const capabilitySupported = isFeatureCapabilitySupported(feature, capabilities);
154
- const enabled = targetEnabled && capabilitySupported;
171
+ const targetEnabled = isPlatformFeatureEnabled(config, feature);
172
+ const capabilitySupported = isFeatureCapabilitySupported(feature, capabilities, config);
173
+ const providerState = getFeatureProviderAvailability(feature, capabilities, config);
174
+ const enabled = targetEnabled && capabilitySupported && (providerState === undefined || providerState === 'available');
155
175
  return {
156
176
  feature,
157
177
  enabled,
158
178
  targetEnabled,
159
179
  capabilitySupported,
160
- reason: enabled ? 'available' : targetEnabled ? 'capability-unsupported' : 'target-disabled',
180
+ reason: !targetEnabled
181
+ ? 'target-disabled'
182
+ : enabled
183
+ ? 'available'
184
+ : providerState === 'configuration-required'
185
+ || providerState === 'action-required'
186
+ || providerState === 'temporarily-unavailable'
187
+ ? providerState
188
+ : 'capability-unsupported',
161
189
  };
162
190
  }
163
- export function getIntegrationAvailability(integration, config, gateway) {
191
+ export function getIntegrationAvailability(integration, config, gateway, capabilities) {
164
192
  const integrations = normalizeTargetIntegrationConfig(config.integrations);
165
- return createIntegrationAvailability(integration, integrations[integration], gateway);
193
+ return createIntegrationAvailability(integration, integrations[integration], gateway, capabilities);
166
194
  }
167
195
  export function createTargetRuntimeSnapshot(input) {
168
196
  const configTarget = input.configTarget ?? targetConfigKeyForPlatform(input.target);
169
197
  const availabilityConfig = resolveAvailabilityConfig(input.config, input.effectiveConfig);
170
198
  const features = {
171
199
  iap: getFeatureAvailability('iap', availabilityConfig, input.capabilities),
200
+ subscriptions: getFeatureAvailability('subscriptions', availabilityConfig, input.capabilities),
172
201
  bannerAds: getFeatureAvailability('bannerAds', availabilityConfig, input.capabilities),
173
202
  rewardedAds: getFeatureAvailability('rewardedAds', availabilityConfig, input.capabilities),
174
203
  interstitialAds: getFeatureAvailability('interstitialAds', availabilityConfig, input.capabilities),
175
204
  leaderboard: getFeatureAvailability('leaderboard', availabilityConfig, input.capabilities),
205
+ nativeLeaderboard: getFeatureAvailability('nativeLeaderboard', availabilityConfig, input.capabilities),
206
+ remoteLeaderboard: getFeatureAvailability('remoteLeaderboard', availabilityConfig, input.capabilities),
176
207
  localization: getFeatureAvailability('localization', availabilityConfig, input.capabilities),
177
208
  };
178
209
  const integrationConfig = normalizeTargetIntegrationConfig(input.effectiveConfig?.integrations ?? input.config.integrations);
179
210
  const integrationEntries = targetIntegrations.map((integration) => {
180
- const availability = createIntegrationAvailability(integration, integrationConfig[integration], input.gateway);
211
+ const availability = createIntegrationAvailability(integration, integrationConfig[integration], input.gateway, input.capabilities);
181
212
  return [integration, availability];
182
213
  });
183
214
  const integrations = Object.fromEntries(integrationEntries);
@@ -208,13 +239,16 @@ export function withTargetAvailability(gateway, config, options = {}) {
208
239
  const configTarget = options.configTarget ?? targetConfigKeyForPlatform(gateway.target);
209
240
  const { identity: gatewayIdentity, presentation: gatewayPresentation, sharing: gatewaySharing, notifications: gatewayNotifications, ...gatewayWithoutIntegrations } = gateway;
210
241
  const integrations = normalizeTargetIntegrationConfig(options.effectiveConfig?.integrations ?? config.integrations);
211
- const isIntegrationAvailable = (integration) => (createIntegrationAvailability(integration, integrations[integration], gateway).state
212
- === 'available');
213
- const identityUpgradeAvailable = isIntegrationAvailable('identityUpgrade');
214
- const presentationAvailable = isIntegrationAvailable('presentation');
215
- const sharingAvailable = isIntegrationAvailable('sharing');
216
- const inboundShareAvailable = isIntegrationAvailable('inboundShare');
217
- const notificationsAvailable = isIntegrationAvailable('notifications');
242
+ const isIntegrationCallable = (integration) => {
243
+ const state = createIntegrationAvailability(integration, integrations[integration], gateway).state;
244
+ return state === 'available' || (state === 'action-required'
245
+ && (integration === 'identityUpgrade' || integration === 'notifications'));
246
+ };
247
+ const identityUpgradeAvailable = isIntegrationCallable('identityUpgrade');
248
+ const presentationAvailable = isIntegrationCallable('presentation');
249
+ const sharingAvailable = isIntegrationCallable('sharing');
250
+ const inboundShareAvailable = isIntegrationCallable('inboundShare');
251
+ const notificationsAvailable = isIntegrationCallable('notifications');
218
252
  const getIdentitySession = gatewayIdentity.getSession?.bind(gatewayIdentity);
219
253
  const requestIdentityUpgrade = gatewayIdentity.requestUpgrade?.bind(gatewayIdentity);
220
254
  const shareOutbound = gatewaySharing?.share?.bind(gatewaySharing);
@@ -236,13 +270,32 @@ export function withTargetAvailability(gateway, config, options = {}) {
236
270
  ...(exposeInboundShare ? { readInboundShare } : {}),
237
271
  };
238
272
  const notifications = notificationsAvailable ? gatewayNotifications : undefined;
239
- const isIapAvailable = async () => (availabilityConfig.features.iap && (await getGatewayCapabilities()).nativeIap);
240
- const isLeaderboardAvailable = async () => {
273
+ const getIapAvailability = async () => {
274
+ const oneTimeEnabled = availabilityConfig.features.iap;
275
+ const subscriptionsEnabled = isPlatformFeatureEnabled(availabilityConfig, 'subscriptions');
276
+ if (!oneTimeEnabled && !subscriptionsEnabled) {
277
+ return { oneTime: false, subscriptions: false };
278
+ }
279
+ const capabilities = await getGatewayCapabilities();
280
+ return {
281
+ oneTime: oneTimeEnabled && capabilities.nativeIap,
282
+ subscriptions: subscriptionsEnabled && capabilities.subscriptionIap === true,
283
+ };
284
+ };
285
+ const getLeaderboardRoute = async () => {
241
286
  if (!availabilityConfig.features.leaderboard) {
242
- return false;
287
+ return null;
243
288
  }
244
289
  const capabilities = await getGatewayCapabilities();
245
- return capabilities.nativeLeaderboard || capabilities.remoteLeaderboard;
290
+ if (capabilities.nativeLeaderboard
291
+ && isPlatformFeatureEnabled(availabilityConfig, 'nativeLeaderboard')) {
292
+ return 'native';
293
+ }
294
+ if (capabilities.remoteLeaderboard
295
+ && isPlatformFeatureEnabled(availabilityConfig, 'remoteLeaderboard')) {
296
+ return 'remote';
297
+ }
298
+ return null;
246
299
  };
247
300
  const isAdPlacementAllowed = (placementId, expectedType) => {
248
301
  const actualType = options.resolveAdPlacementType?.(placementId);
@@ -251,14 +304,19 @@ export function withTargetAvailability(gateway, config, options = {}) {
251
304
  }
252
305
  return availabilityConfig.features[adPlacementFeatureFor(expectedType)] === true;
253
306
  };
254
- const canPreloadAdPlacement = (placementId) => {
255
- const actualType = options.resolveAdPlacementType?.(placementId);
307
+ const canPreloadAdPlacement = (placementId, requestedFormat) => {
308
+ const resolvedType = options.resolveAdPlacementType?.(placementId);
309
+ if (resolvedType !== undefined && requestedFormat !== undefined
310
+ && resolvedType !== requestedFormat) {
311
+ return false;
312
+ }
313
+ const actualType = resolvedType ?? requestedFormat;
256
314
  if (actualType !== undefined) {
257
- return availabilityConfig.features[adPlacementFeatureFor(actualType)] === true;
315
+ return actualType !== 'banner'
316
+ && availabilityConfig.features[adPlacementFeatureFor(actualType)] === true;
258
317
  }
259
318
  return (availabilityConfig.features.rewardedAds ||
260
- availabilityConfig.features.interstitialAds ||
261
- availabilityConfig.features.bannerAds === true);
319
+ availabilityConfig.features.interstitialAds);
262
320
  };
263
321
  return {
264
322
  ...gatewayWithoutIntegrations,
@@ -289,10 +347,29 @@ export function withTargetAvailability(gateway, config, options = {}) {
289
347
  },
290
348
  commerce: {
291
349
  async getProducts() {
292
- return await isIapAvailable() ? gateway.commerce.getProducts() : [];
350
+ const available = await getIapAvailability();
351
+ if (!available.oneTime && !available.subscriptions) {
352
+ return [];
353
+ }
354
+ return (await gateway.commerce.getProducts()).filter((product) => product.type === 'subscription' ? available.subscriptions : available.oneTime);
293
355
  },
294
356
  async purchase(input) {
295
- if (!await isIapAvailable()) {
357
+ const available = await getIapAvailability();
358
+ const configuredProduct = options.effectiveConfig?.monetization.products.find((product) => product.id === input.productId);
359
+ let productType = configuredProduct?.type;
360
+ if (productType === undefined
361
+ && options.effectiveConfig === undefined
362
+ && available.oneTime !== available.subscriptions) {
363
+ productType = (await gateway.commerce.getProducts()).find((product) => product.id === input.productId)?.type;
364
+ }
365
+ // When only one purchase route is available, unknown product types
366
+ // must not fall through to that route. An effective catalog is binding.
367
+ const allowed = productType === 'subscription'
368
+ ? available.subscriptions
369
+ : productType === undefined
370
+ ? available.oneTime && available.subscriptions
371
+ : available.oneTime;
372
+ if (!allowed || (options.effectiveConfig !== undefined && configuredProduct?.enabled !== true)) {
296
373
  return {
297
374
  status: 'cancelled',
298
375
  entitlementIds: [],
@@ -301,7 +378,8 @@ export function withTargetAvailability(gateway, config, options = {}) {
301
378
  return gateway.commerce.purchase(input);
302
379
  },
303
380
  async restore() {
304
- if (!await isIapAvailable() || gateway.commerce.restore === undefined) {
381
+ const available = await getIapAvailability();
382
+ if ((!available.oneTime && !available.subscriptions) || gateway.commerce.restore === undefined) {
305
383
  return {
306
384
  restoredEntitlements: [],
307
385
  };
@@ -309,13 +387,17 @@ export function withTargetAvailability(gateway, config, options = {}) {
309
387
  return gateway.commerce.restore();
310
388
  },
311
389
  async getEntitlements() {
312
- return await isIapAvailable() ? gateway.commerce.getEntitlements() : [];
390
+ const available = await getIapAvailability();
391
+ return available.oneTime || available.subscriptions
392
+ ? gateway.commerce.getEntitlements()
393
+ : [];
313
394
  },
314
395
  },
315
396
  ads: {
316
397
  async preload(input) {
317
- if (canPreloadAdPlacement(input.placementId)) {
318
- await gateway.ads.preload(input);
398
+ if (canPreloadAdPlacement(input.placementId, input.format)) {
399
+ const format = options.resolveAdPlacementType?.(input.placementId) ?? input.format;
400
+ await gateway.ads.preload(format === undefined ? input : { ...input, format });
319
401
  }
320
402
  },
321
403
  async showRewarded(input) {
@@ -351,16 +433,22 @@ export function withTargetAvailability(gateway, config, options = {}) {
351
433
  },
352
434
  leaderboard: {
353
435
  async submitScore(input) {
354
- if (!await isLeaderboardAvailable()) {
436
+ const route = await getLeaderboardRoute();
437
+ if (route === null) {
355
438
  return {
356
439
  submitted: false,
357
440
  };
358
441
  }
359
- return gateway.leaderboard.submitScore(input);
442
+ return gateway.leaderboard.submitScore(gateway.target === 'android' || gateway.target === 'ios'
443
+ ? { ...input, route }
444
+ : input);
360
445
  },
361
446
  async open(input) {
362
- if (await isLeaderboardAvailable()) {
363
- await gateway.leaderboard.open(input);
447
+ const route = await getLeaderboardRoute();
448
+ if (route !== null) {
449
+ await gateway.leaderboard.open(gateway.target === 'android' || gateway.target === 'ios'
450
+ ? { ...input, route }
451
+ : input);
364
452
  }
365
453
  },
366
454
  },
@@ -377,10 +465,12 @@ function resolveAvailabilityConfig(config, effectiveConfig) {
377
465
  export function isTargetConfiguredGateway(gateway) {
378
466
  return typeof gateway.getTargetRuntime === 'function';
379
467
  }
380
- function isFeatureCapabilitySupported(feature, capabilities) {
468
+ function isFeatureCapabilitySupported(feature, capabilities, config) {
381
469
  switch (feature) {
382
470
  case 'iap':
383
471
  return capabilities.nativeIap;
472
+ case 'subscriptions':
473
+ return capabilities.subscriptionIap === true;
384
474
  case 'bannerAds':
385
475
  return capabilities.bannerAds === true;
386
476
  case 'rewardedAds':
@@ -388,11 +478,45 @@ function isFeatureCapabilitySupported(feature, capabilities) {
388
478
  case 'interstitialAds':
389
479
  return capabilities.interstitialAds;
390
480
  case 'leaderboard':
391
- return capabilities.nativeLeaderboard || capabilities.remoteLeaderboard;
481
+ return (capabilities.nativeLeaderboard && isPlatformFeatureEnabled(config, 'nativeLeaderboard')) || (capabilities.remoteLeaderboard && isPlatformFeatureEnabled(config, 'remoteLeaderboard'));
482
+ case 'nativeLeaderboard':
483
+ return capabilities.nativeLeaderboard;
484
+ case 'remoteLeaderboard':
485
+ return capabilities.remoteLeaderboard;
392
486
  case 'localization':
393
487
  return capabilities.localizedContent;
394
488
  }
395
489
  }
490
+ function getFeatureProviderAvailability(feature, capabilities, config) {
491
+ const states = capabilities.providerAvailability;
492
+ if (states === undefined) {
493
+ return undefined;
494
+ }
495
+ switch (feature) {
496
+ case 'iap':
497
+ return states.nativeIap;
498
+ case 'subscriptions':
499
+ return states.subscriptionIap;
500
+ case 'rewardedAds':
501
+ return states.rewardedAds;
502
+ case 'interstitialAds':
503
+ return states.interstitialAds;
504
+ case 'bannerAds':
505
+ return states.bannerAds;
506
+ case 'nativeLeaderboard':
507
+ return states.nativeLeaderboard;
508
+ case 'leaderboard':
509
+ // A game-owned remote leaderboard remains usable without a native provider.
510
+ return capabilities.remoteLeaderboard && isPlatformFeatureEnabled(config, 'remoteLeaderboard')
511
+ ? 'available'
512
+ : isPlatformFeatureEnabled(config, 'nativeLeaderboard')
513
+ ? states.nativeLeaderboard
514
+ : undefined;
515
+ case 'remoteLeaderboard':
516
+ case 'localization':
517
+ return undefined;
518
+ }
519
+ }
396
520
  function isIntegrationAdapterSupported(integration, gateway) {
397
521
  if (gateway === undefined) {
398
522
  return false;
@@ -412,11 +536,23 @@ function isIntegrationAdapterSupported(integration, gateway) {
412
536
  && typeof gateway.notifications?.requestSubscription === 'function');
413
537
  }
414
538
  }
415
- function createIntegrationAvailability(integration, configuredState, gateway) {
539
+ function createIntegrationAvailability(integration, configuredState, gateway, capabilities) {
416
540
  const adapterSupported = isIntegrationAdapterSupported(integration, gateway);
541
+ const providerState = integration === 'identityUpgrade'
542
+ ? capabilities?.providerAvailability?.identityUpgrade
543
+ : integration === 'notifications'
544
+ ? capabilities?.providerAvailability?.pushNotifications
545
+ : undefined;
546
+ const state = configuredState === 'disabled' || configuredState === 'unsupported'
547
+ ? configuredState
548
+ : !adapterSupported || providerState === 'unsupported'
549
+ ? 'unsupported'
550
+ : providerState !== undefined && providerState !== 'available'
551
+ ? providerState
552
+ : configuredState;
417
553
  return {
418
554
  integration,
419
- state: adapterSupported ? configuredState : 'unsupported',
555
+ state,
420
556
  configuredState,
421
557
  adapterSupported,
422
558
  };
@@ -106,6 +106,27 @@ export interface TargetViewportSafeArea {
106
106
  readonly contentBounds: TargetViewportBounds;
107
107
  }
108
108
  /**
109
+ * A named host-owned surface in full-viewport CSS-pixel coordinates. The
110
+ * effective inset extends from `edge` to the surface's far bound, so any gap
111
+ * between a floating surface and that edge is conservatively treated as used.
112
+ */
113
+ export interface TargetViewportOccupiedSurface {
114
+ readonly surfaceId: string;
115
+ readonly edge: 'top' | 'right' | 'bottom' | 'left';
116
+ readonly bounds: TargetViewportBounds;
117
+ }
118
+ /** Independent occlusion measurements. Overlap is combined, never summed. */
119
+ export interface TargetViewportUsableAreaInput {
120
+ readonly safeAreaInsets?: Partial<TargetViewportSafeAreaInsets>;
121
+ readonly systemBarInsets?: Partial<TargetViewportSafeAreaInsets>;
122
+ readonly keyboardInsets?: Partial<TargetViewportSafeAreaInsets>;
123
+ readonly occupiedSurfaces?: readonly TargetViewportOccupiedSurface[];
124
+ }
125
+ /** Remaining geometry shared by a canvas and any DOM overlays. */
126
+ export interface TargetViewportUsableArea extends TargetViewportSafeArea {
127
+ readonly occupiedSurfaceIds: readonly string[];
128
+ }
129
+ /**
109
130
  * A viewport plan enriched with concrete safe-area geometry. It is assignable
110
131
  * to `TargetViewportPlan`, so existing scene code can adopt it incrementally.
111
132
  */
@@ -206,6 +227,67 @@ export declare function resolveTargetViewportPlan(input: TargetViewportInput, br
206
227
  * render persistent DOM controls or position Phaser HUD elements themselves.
207
228
  */
208
229
  export declare function resolveTargetViewportSnapshot(input: TargetViewportSnapshotInput, breakpoints?: TargetViewportBreakpoints): TargetViewportSnapshot;
230
+ /** A raw viewport size reported by a host surface, before a plan validates it. */
231
+ export interface TargetViewportMeasurement {
232
+ readonly width: number;
233
+ readonly height: number;
234
+ readonly source: TargetViewportMeasurementSource;
235
+ }
236
+ /** A surface that reports its layout box, such as the element that mounts the game. */
237
+ export interface TargetViewportMeasurableElement {
238
+ getBoundingClientRect(): {
239
+ readonly width: number;
240
+ readonly height: number;
241
+ };
242
+ }
243
+ /**
244
+ * Host surfaces for {@link measureTargetViewport}. Structural types keep the helper usable in
245
+ * tests and non-DOM hosts; a browser passes the game container, `window.visualViewport` and
246
+ * `window`.
247
+ */
248
+ export interface TargetViewportMeasurementSources {
249
+ readonly container?: TargetViewportMeasurableElement | null;
250
+ readonly visualViewport?: {
251
+ readonly width: number;
252
+ readonly height: number;
253
+ } | null;
254
+ readonly window?: {
255
+ readonly innerWidth: number;
256
+ readonly innerHeight: number;
257
+ } | null;
258
+ }
259
+ /** Inputs for {@link waitForTargetViewportMeasurement}. */
260
+ export interface TargetViewportMeasurementWait {
261
+ /** Returns a measurement, or `null` while the surface has no usable size. */
262
+ readonly measure: () => TargetViewportMeasurement | null;
263
+ /**
264
+ * Calls the listener whenever the surface may have resized or become visible and returns an
265
+ * unsubscribe function. Browsers typically combine window and `visualViewport` `resize`,
266
+ * document `visibilitychange` and a `ResizeObserver` on the game container.
267
+ */
268
+ readonly subscribe: (listener: () => void) => () => void;
269
+ /** Stops waiting; the promise then rejects with the signal's reason. */
270
+ readonly signal?: AbortSignal;
271
+ }
272
+ /** Whether a size can produce a viewport plan: both sides finite and at least one CSS pixel. */
273
+ export declare function isMeasurableTargetViewport(size: {
274
+ readonly width: number;
275
+ readonly height: number;
276
+ }): boolean;
277
+ /**
278
+ * Measure the first host surface with a usable area: the game container, then the visual
279
+ * viewport, then the window. Returns `null` while every surface is still zero-sized, as in a
280
+ * hidden iframe, a collapsed embed or a background tab before layout. Resolve a viewport plan
281
+ * only from a measurement; {@link waitForTargetViewportMeasurement} waits for the first one.
282
+ */
283
+ export declare function measureTargetViewport(sources: TargetViewportMeasurementSources): TargetViewportMeasurement | null;
284
+ /**
285
+ * Resolve with the first usable measurement, so a game that boots inside a zero-sized surface
286
+ * starts once the host lays it out instead of failing viewport validation. Resolves immediately
287
+ * when the surface is already measurable and unsubscribes as soon as it settles. A measurement
288
+ * that throws, including the first one, rejects the returned promise.
289
+ */
290
+ export declare function waitForTargetViewportMeasurement(input: TargetViewportMeasurementWait): Promise<TargetViewportMeasurement>;
209
291
  /**
210
292
  * Resolve an adaptive game shell inside the snapshot's safe content bounds.
211
293
  *
@@ -225,6 +307,14 @@ export declare function resolveTargetViewportComposition(input: TargetViewportCo
225
307
  */
226
308
  export declare function resolveTargetViewportSafeArea(layout: Pick<TargetViewportLayout, 'width' | 'height'>, insets?: Partial<TargetViewportSafeAreaInsets>): TargetViewportSafeArea;
227
309
  /**
310
+ * Resolve all edge occlusions in one CSS-pixel coordinate space. System bars
311
+ * and CSS safe-area measurements can describe the same pixels, as can a banner
312
+ * and the home-indicator inset. Taking the maximum intrusion per edge avoids
313
+ * subtracting those regions twice. A surface's bounds are absolute viewport
314
+ * coordinates, not a banner height to add to another inset.
315
+ */
316
+ export declare function resolveTargetViewportUsableArea(layout: Pick<TargetViewportLayout, 'width' | 'height'>, input?: TargetViewportUsableAreaInput): TargetViewportUsableArea;
317
+ /**
228
318
  * Read the starter's computed CSS variables. Unknown or non-pixel values are
229
319
  * intentionally treated as zero so browser shells without CSS env support keep
230
320
  * the same full-viewport layout as before.