@mpgd/target-config 0.11.0 → 0.13.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.
@@ -39,6 +39,7 @@ export interface EffectiveMonetizationConfig {
39
39
  readonly products: readonly EffectiveProductConfig[];
40
40
  }
41
41
  export interface EffectiveAdsConfig {
42
+ readonly bannerAds: boolean;
42
43
  readonly rewardedAds: boolean;
43
44
  readonly interstitialAds: boolean;
44
45
  readonly placements: readonly EffectiveAdPlacementConfig[];
package/dist/effective.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { resolveAdPlacementPlatformId, resolveProductPlatformId, } from '@mpgd/catalog';
2
- import { assertTargetIntegrationRuntimeBounds, } from './runtime.js';
2
+ import { adPlacementFeatureFor, assertTargetIntegrationRuntimeBounds, } from './runtime.js';
3
3
  export const defaultLeaderboardId = 'default';
4
4
  const disabledAuthoritativeMonetization = {
5
5
  iap: false,
6
+ bannerAds: false,
6
7
  rewardedAds: false,
7
8
  interstitialAds: false,
8
9
  };
@@ -44,6 +45,7 @@ export function createEffectiveTargetConfig(input) {
44
45
  products,
45
46
  },
46
47
  ads: {
48
+ bannerAds: config.monetization.bannerAds === true,
47
49
  rewardedAds: config.monetization.rewardedAds,
48
50
  interstitialAds: config.monetization.interstitialAds,
49
51
  placements,
@@ -126,12 +128,13 @@ function effectiveProductReason(runtime, targetEnabled, product, platformProduct
126
128
  if (runtime === 'verse8-web' && product.grant.type === 'resource') {
127
129
  return 'capability-unsupported';
128
130
  }
131
+ if (runtime === 'microsoft-store-pwa' && product.type !== 'consumable') {
132
+ return 'capability-unsupported';
133
+ }
129
134
  return effectiveItemReason(targetEnabled, platformProductId);
130
135
  }
131
136
  function createEffectiveAdPlacementConfig(target, config, placement) {
132
- const featureEnabled = isRewardedPlacement(placement)
133
- ? config.features.rewardedAds
134
- : config.features.interstitialAds;
137
+ const featureEnabled = config.features[adPlacementFeatureFor(placement.type)] === true;
135
138
  const platformPlacementId = resolveAdPlacementPlatformId(placement, target);
136
139
  const reason = effectiveItemReason(featureEnabled, platformPlacementId);
137
140
  return {
@@ -153,6 +156,3 @@ function effectiveItemReason(targetEnabled, platformId) {
153
156
  function effectiveTargetConfigVersion(input) {
154
157
  return `${input.targetConfig}+catalog.${input.productCatalog}+ads.${input.adPlacements}`;
155
158
  }
156
- function isRewardedPlacement(placement) {
157
- return placement.type === 'rewarded';
158
- }
package/dist/index.js CHANGED
@@ -179,7 +179,12 @@ export const assertEffectiveTargetConfigMatrix = (() => {
179
179
  expected: "(number & ExclusiveMinimum<0> & Maximum<1.7976931348623157e+308>)",
180
180
  value: input.amount
181
181
  }, _errorFactory));
182
- const _ao14 = (input, _path, _exceptionable = true) => _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable) && ((Array.isArray(input.placements) || _assertGuard_1._assertGuard(_exceptionable, {
182
+ const _ao14 = (input, _path, _exceptionable = true) => ("boolean" === typeof input.bannerAds || _assertGuard_1._assertGuard(_exceptionable, {
183
+ method: "typia.createAssert",
184
+ path: _path + ".bannerAds",
185
+ expected: "boolean",
186
+ value: input.bannerAds
187
+ }, _errorFactory)) && _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable) && ((Array.isArray(input.placements) || _assertGuard_1._assertGuard(_exceptionable, {
183
188
  method: "typia.createAssert",
184
189
  path: _path + ".placements",
185
190
  expected: "ReadonlyArray<EffectiveAdPlacementConfig>",
@@ -200,10 +205,10 @@ export const assertEffectiveTargetConfigMatrix = (() => {
200
205
  expected: "ReadonlyArray<EffectiveAdPlacementConfig>",
201
206
  value: input.placements
202
207
  }, _errorFactory));
203
- const _ao15 = (input, _path, _exceptionable = true) => _ap5(input, _path, true && _exceptionable) && ("interstitial" === input.type || "rewarded" === input.type || _assertGuard_1._assertGuard(_exceptionable, {
208
+ const _ao15 = (input, _path, _exceptionable = true) => _ap5(input, _path, true && _exceptionable) && ("banner" === input.type || "interstitial" === input.type || "rewarded" === input.type || _assertGuard_1._assertGuard(_exceptionable, {
204
209
  method: "typia.createAssert",
205
210
  path: _path + ".type",
206
- expected: "(\"interstitial\" | \"rewarded\")",
211
+ expected: "(\"banner\" | \"interstitial\" | \"rewarded\")",
207
212
  value: input.type
208
213
  }, _errorFactory)) && (undefined === input.reward || ("object" === typeof input.reward && null !== input.reward || _assertGuard_1._assertGuard(_exceptionable, {
209
214
  method: "typia.createAssert",
@@ -409,7 +414,12 @@ export const assertEffectiveTargetConfigMatrix = (() => {
409
414
  expected: "(\"app-store\" | \"apps-in-toss\" | \"devvit\" | \"google-play\" | \"microsoft-store\" | \"verse8\" | \"web\" | \"web-preview\")",
410
415
  value: input.profile
411
416
  }, _errorFactory);
412
- const _ao4 = (input, _path, _exceptionable = true) => _ap1(input, _path, true && _exceptionable) && _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable) && ("boolean" === typeof input.leaderboard || _assertGuard_1._assertGuard(_exceptionable, {
417
+ const _ao4 = (input, _path, _exceptionable = true) => _ap1(input, _path, true && _exceptionable) && (undefined === input.bannerAds || "boolean" === typeof input.bannerAds || _assertGuard_1._assertGuard(_exceptionable, {
418
+ method: "typia.createAssert",
419
+ path: _path + ".bannerAds",
420
+ expected: "(boolean | undefined)",
421
+ value: input.bannerAds
422
+ }, _errorFactory)) && _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable) && ("boolean" === typeof input.leaderboard || _assertGuard_1._assertGuard(_exceptionable, {
413
423
  method: "typia.createAssert",
414
424
  path: _path + ".leaderboard",
415
425
  expected: "boolean",
@@ -564,8 +574,8 @@ export const assertEffectiveTargetConfigMatrix = (() => {
564
574
  const _io11 = input => _ip8(input) && _ip9(input) && _ip10(input);
565
575
  const _io12 = input => "entitlement" === input.type && "string" === typeof input.entitlement;
566
576
  const _io13 = input => "resource" === input.type && ("string" === typeof input.resource && 1 <= input.resource.length) && ("number" === typeof input.amount && (0 < input.amount && input.amount <= 1.7976931348623157e+308));
567
- const _io14 = input => _ip2(input) && _ip3(input) && (Array.isArray(input.placements) && input.placements.every(elem => "object" === typeof elem && null !== elem && _io15(elem)));
568
- const _io15 = input => _ip5(input) && ("interstitial" === input.type || "rewarded" === input.type) && (undefined === input.reward || "object" === typeof input.reward && null !== input.reward && _iu1(input.reward)) && ("object" === typeof input.frequencyCap && null !== input.frequencyCap && _io18(input.frequencyCap)) && _ip6(input) && _ip7(input) && (undefined === input.platformPlacementId || "string" === typeof input.platformPlacementId);
577
+ const _io14 = input => "boolean" === typeof input.bannerAds && _ip2(input) && _ip3(input) && (Array.isArray(input.placements) && input.placements.every(elem => "object" === typeof elem && null !== elem && _io15(elem)));
578
+ const _io15 = input => _ip5(input) && ("banner" === input.type || "interstitial" === input.type || "rewarded" === input.type) && (undefined === input.reward || "object" === typeof input.reward && null !== input.reward && _iu1(input.reward)) && ("object" === typeof input.frequencyCap && null !== input.frequencyCap && _io18(input.frequencyCap)) && _ip6(input) && _ip7(input) && (undefined === input.platformPlacementId || "string" === typeof input.platformPlacementId);
569
579
  const _io16 = input => "continue" === input.type && _ip10(input);
570
580
  const _io17 = input => _ip8(input) && _ip10(input) && _ip9(input);
571
581
  const _io18 = input => "number" === typeof input.cooldownSeconds && (undefined === input.maxPerSession || "number" === typeof input.maxPerSession) && (undefined === input.minStageInterval || "number" === typeof input.minStageInterval);
@@ -574,7 +584,7 @@ export const assertEffectiveTargetConfigMatrix = (() => {
574
584
  const _io20 = input => ("local" === input.support || "native" === input.support || "none" === input.support) && _ip6(input);
575
585
  const _io21 = input => _ip6(input) && ("en" === input.fallbackLocale || "ko" === input.fallbackLocale);
576
586
  const _io3 = input => "app-store" === input.profile || "apps-in-toss" === input.profile || "devvit" === input.profile || "google-play" === input.profile || "microsoft-store" === input.profile || "verse8" === input.profile || "web" === input.profile || "web-preview" === input.profile;
577
- const _io4 = input => _ip1(input) && _ip2(input) && _ip3(input) && "boolean" === typeof input.leaderboard && _ip4(input);
587
+ const _io4 = input => _ip1(input) && (undefined === input.bannerAds || "boolean" === typeof input.bannerAds) && _ip2(input) && _ip3(input) && "boolean" === typeof input.leaderboard && _ip4(input);
578
588
  const _io5 = input => ("local" === input.storage || "native" === input.storage || "none" === input.storage) && _ip4(input);
579
589
  const _io6 = input => ("approval-required" === input.identityUpgrade || "available" === input.identityUpgrade || "configuration-required" === input.identityUpgrade || "disabled" === input.identityUpgrade || "unsupported" === input.identityUpgrade) && ("approval-required" === input.presentation || "available" === input.presentation || "configuration-required" === input.presentation || "disabled" === input.presentation || "unsupported" === input.presentation) && ("approval-required" === input.sharing || "available" === input.sharing || "configuration-required" === input.sharing || "disabled" === input.sharing || "unsupported" === input.sharing) && ("approval-required" === input.inboundShare || "available" === input.inboundShare || "configuration-required" === input.inboundShare || "disabled" === input.inboundShare || "unsupported" === input.inboundShare) && ("approval-required" === input.notifications || "available" === input.notifications || "configuration-required" === input.notifications || "disabled" === input.notifications || "unsupported" === input.notifications) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
580
590
  const _io7 = input => "boolean" === typeof input.externalPaymentAllowed && "boolean" === typeof input.remoteExecutableCodeAllowed && "boolean" === typeof input.installOtherAppCTAAllowed && "boolean" === typeof input.requiresStoreReview && "boolean" === typeof input.requiresAitReview;
@@ -620,9 +630,10 @@ export const assertEffectiveTargetConfigMatrix = (() => {
620
630
  })();
621
631
  export const assertTargetConfigMatrix = (() => {
622
632
  const _ip0 = input => "boolean" === typeof input["iap"];
623
- const _ip1 = input => "boolean" === typeof input["rewardedAds"];
624
- const _ip2 = input => "boolean" === typeof input["interstitialAds"];
625
- const _ip3 = input => "boolean" === typeof input["localization"];
633
+ const _ip1 = input => undefined === input["bannerAds"] || "boolean" === typeof input["bannerAds"];
634
+ const _ip2 = input => "boolean" === typeof input["rewardedAds"];
635
+ const _ip3 = input => "boolean" === typeof input["interstitialAds"];
636
+ const _ip4 = input => "boolean" === typeof input["localization"];
626
637
  const _ae0 = "(\"apps-in-toss\" | \"capacitor-android\" | \"capacitor-ios\" | \"devvit-web\" | \"microsoft-store-pwa\" | \"verse8-web\" | \"web\" | \"web-preview\")";
627
638
  const _ap0 = (input, _path, _exceptionable = true) => "boolean" === typeof input["iap"] || _assertGuard_1._assertGuard(_exceptionable, {
628
639
  method: "typia.createAssert",
@@ -630,19 +641,25 @@ export const assertTargetConfigMatrix = (() => {
630
641
  expected: "boolean",
631
642
  value: input["iap"]
632
643
  }, _errorFactory);
633
- const _ap1 = (input, _path, _exceptionable = true) => "boolean" === typeof input["rewardedAds"] || _assertGuard_1._assertGuard(_exceptionable, {
644
+ const _ap1 = (input, _path, _exceptionable = true) => undefined === input["bannerAds"] || "boolean" === typeof input["bannerAds"] || _assertGuard_1._assertGuard(_exceptionable, {
645
+ method: "typia.createAssert",
646
+ path: _path + ".bannerAds",
647
+ expected: "(boolean | undefined)",
648
+ value: input["bannerAds"]
649
+ }, _errorFactory);
650
+ const _ap2 = (input, _path, _exceptionable = true) => "boolean" === typeof input["rewardedAds"] || _assertGuard_1._assertGuard(_exceptionable, {
634
651
  method: "typia.createAssert",
635
652
  path: _path + ".rewardedAds",
636
653
  expected: "boolean",
637
654
  value: input["rewardedAds"]
638
655
  }, _errorFactory);
639
- const _ap2 = (input, _path, _exceptionable = true) => "boolean" === typeof input["interstitialAds"] || _assertGuard_1._assertGuard(_exceptionable, {
656
+ const _ap3 = (input, _path, _exceptionable = true) => "boolean" === typeof input["interstitialAds"] || _assertGuard_1._assertGuard(_exceptionable, {
640
657
  method: "typia.createAssert",
641
658
  path: _path + ".interstitialAds",
642
659
  expected: "boolean",
643
660
  value: input["interstitialAds"]
644
661
  }, _errorFactory);
645
- const _ap3 = (input, _path, _exceptionable = true) => "boolean" === typeof input["localization"] || _assertGuard_1._assertGuard(_exceptionable, {
662
+ const _ap4 = (input, _path, _exceptionable = true) => "boolean" === typeof input["localization"] || _assertGuard_1._assertGuard(_exceptionable, {
646
663
  method: "typia.createAssert",
647
664
  path: _path + ".localization",
648
665
  expected: "boolean",
@@ -797,25 +814,25 @@ export const assertTargetConfigMatrix = (() => {
797
814
  expected: "(TargetIntegrationConfig | undefined)",
798
815
  value: input.integrations
799
816
  }, _errorFactory));
800
- const _ao3 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && _ap1(input, _path, true && _exceptionable) && _ap2(input, _path, true && _exceptionable) && ("boolean" === typeof input.leaderboard || _assertGuard_1._assertGuard(_exceptionable, {
817
+ const _ao3 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && _ap1(input, _path, true && _exceptionable) && _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable) && ("boolean" === typeof input.leaderboard || _assertGuard_1._assertGuard(_exceptionable, {
801
818
  method: "typia.createAssert",
802
819
  path: _path + ".leaderboard",
803
820
  expected: "boolean",
804
821
  value: input.leaderboard
805
- }, _errorFactory)) && _ap3(input, _path, true && _exceptionable);
822
+ }, _errorFactory)) && _ap4(input, _path, true && _exceptionable);
806
823
  const _ao4 = (input, _path, _exceptionable = true) => ("local" === input.storage || "native" === input.storage || "none" === input.storage || _assertGuard_1._assertGuard(_exceptionable, {
807
824
  method: "typia.createAssert",
808
825
  path: _path + ".storage",
809
826
  expected: "(\"local\" | \"native\" | \"none\")",
810
827
  value: input.storage
811
- }, _errorFactory)) && _ap3(input, _path, true && _exceptionable);
828
+ }, _errorFactory)) && _ap4(input, _path, true && _exceptionable);
812
829
  const _ao5 = (input, _path, _exceptionable = true) => "en" === input.fallbackLocale || "ko" === input.fallbackLocale || _assertGuard_1._assertGuard(_exceptionable, {
813
830
  method: "typia.createAssert",
814
831
  path: _path + ".fallbackLocale",
815
832
  expected: "(\"en\" | \"ko\")",
816
833
  value: input.fallbackLocale
817
834
  }, _errorFactory);
818
- const _ao6 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && _ap1(input, _path, true && _exceptionable) && _ap2(input, _path, true && _exceptionable);
835
+ const _ao6 = (input, _path, _exceptionable = true) => _ap0(input, _path, true && _exceptionable) && _ap1(input, _path, true && _exceptionable) && _ap2(input, _path, true && _exceptionable) && _ap3(input, _path, true && _exceptionable);
819
836
  const _ao7 = (input, _path, _exceptionable = true) => "boolean" === typeof input.native || _assertGuard_1._assertGuard(_exceptionable, {
820
837
  method: "typia.createAssert",
821
838
  path: _path + ".native",
@@ -863,10 +880,10 @@ export const assertTargetConfigMatrix = (() => {
863
880
  });
864
881
  const _io10 = input => ("approval-required" === input.identityUpgrade || "available" === input.identityUpgrade || "configuration-required" === input.identityUpgrade || "disabled" === input.identityUpgrade || "unsupported" === input.identityUpgrade) && ("approval-required" === input.presentation || "available" === input.presentation || "configuration-required" === input.presentation || "disabled" === input.presentation || "unsupported" === input.presentation) && ("approval-required" === input.sharing || "available" === input.sharing || "configuration-required" === input.sharing || "disabled" === input.sharing || "unsupported" === input.sharing) && ("approval-required" === input.inboundShare || "available" === input.inboundShare || "configuration-required" === input.inboundShare || "disabled" === input.inboundShare || "unsupported" === input.inboundShare) && ("approval-required" === input.notifications || "available" === input.notifications || "configuration-required" === input.notifications || "disabled" === input.notifications || "unsupported" === input.notifications) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
865
882
  const _io2 = input => ("apps-in-toss" === input.runtime || "capacitor-android" === input.runtime || "capacitor-ios" === input.runtime || "devvit-web" === input.runtime || "microsoft-store-pwa" === input.runtime || "verse8-web" === input.runtime || "web" === input.runtime || "web-preview" === input.runtime) && ("object" === typeof input.features && null !== input.features && _io3(input.features)) && ("object" === typeof input.capabilities && null !== input.capabilities && _io4(input.capabilities)) && ("object" === typeof input.localization && null !== input.localization && _io5(input.localization)) && ("object" === typeof input.monetization && null !== input.monetization && _io6(input.monetization)) && ("object" === typeof input.leaderboard && null !== input.leaderboard && _io7(input.leaderboard)) && ("object" === typeof input.release && null !== input.release && _io8(input.release)) && ("object" === typeof input.policy && null !== input.policy && _io9(input.policy)) && (undefined === input.integrations || "object" === typeof input.integrations && null !== input.integrations && _io10(input.integrations));
866
- const _io3 = input => _ip0(input) && _ip1(input) && _ip2(input) && "boolean" === typeof input.leaderboard && _ip3(input);
867
- const _io4 = input => ("local" === input.storage || "native" === input.storage || "none" === input.storage) && _ip3(input);
883
+ const _io3 = input => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input) && "boolean" === typeof input.leaderboard && _ip4(input);
884
+ const _io4 = input => ("local" === input.storage || "native" === input.storage || "none" === input.storage) && _ip4(input);
868
885
  const _io5 = input => "en" === input.fallbackLocale || "ko" === input.fallbackLocale;
869
- const _io6 = input => _ip0(input) && _ip1(input) && _ip2(input);
886
+ const _io6 = input => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input);
870
887
  const _io7 = input => "boolean" === typeof input.native;
871
888
  const _io8 = input => "app-store" === input.profile || "apps-in-toss" === input.profile || "devvit" === input.profile || "google-play" === input.profile || "microsoft-store" === input.profile || "verse8" === input.profile || "web" === input.profile || "web-preview" === input.profile;
872
889
  const _io9 = input => "boolean" === typeof input.externalPaymentAllowed && "boolean" === typeof input.remoteExecutableCodeAllowed && "boolean" === typeof input.installOtherAppCTAAllowed && "boolean" === typeof input.requiresStoreReview && "boolean" === typeof input.requiresAitReview;
package/dist/runtime.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { MpgdLocale } from '@mpgd/i18n';
2
2
  import type { PlatformCapabilities, PlatformGateway } from '@mpgd/platform';
3
3
  import type { EffectiveTargetConfig } from './effective.js';
4
- export type PlatformFeature = 'iap' | 'rewardedAds' | 'interstitialAds' | 'leaderboard' | 'localization';
5
- export type AdPlacementType = 'rewarded' | 'interstitial';
4
+ export type PlatformFeature = 'iap' | 'bannerAds' | 'rewardedAds' | 'interstitialAds' | 'leaderboard' | 'localization';
5
+ export type AdPlacementType = 'rewarded' | 'interstitial' | 'banner';
6
6
  type PlatformConfigTarget = PlatformGateway['target'];
7
7
  export type TargetRuntimeKind = 'web' | 'web-preview' | 'microsoft-store-pwa' | 'capacitor-android' | 'capacitor-ios' | 'apps-in-toss' | 'devvit-web' | 'verse8-web';
8
8
  export type ReleaseProfile = 'web' | 'web-preview' | 'microsoft-store' | 'google-play' | 'app-store' | 'apps-in-toss' | 'devvit' | 'verse8';
@@ -20,6 +20,8 @@ export interface TargetIntegrationConfig {
20
20
  }
21
21
  export interface TargetFeatureConfig {
22
22
  readonly iap: boolean;
23
+ /** Optional for target matrices authored before inline banner support. */
24
+ readonly bannerAds?: boolean;
23
25
  readonly rewardedAds: boolean;
24
26
  readonly interstitialAds: boolean;
25
27
  readonly leaderboard: boolean;
@@ -34,6 +36,8 @@ export interface TargetLocalizationConfig {
34
36
  }
35
37
  export interface TargetMonetizationConfig {
36
38
  readonly iap: boolean;
39
+ /** Optional for target matrices authored before inline banner support. */
40
+ readonly bannerAds?: boolean;
37
41
  readonly rewardedAds: boolean;
38
42
  readonly interstitialAds: boolean;
39
43
  }
@@ -112,7 +116,8 @@ export interface TargetConfiguredGateway extends PlatformGateway {
112
116
  readonly effectiveConfig?: EffectiveTargetConfig;
113
117
  getTargetRuntime(): Promise<TargetRuntimeSnapshot>;
114
118
  }
115
- export declare const platformFeatures: readonly ["iap", "rewardedAds", "interstitialAds", "leaderboard", "localization"];
119
+ export declare const platformFeatures: readonly ["iap", "bannerAds", "rewardedAds", "interstitialAds", "leaderboard", "localization"];
120
+ export declare function adPlacementFeatureFor(type: AdPlacementType): PlatformFeature;
116
121
  export declare const targetIntegrations: readonly ["identityUpgrade", "presentation", "sharing", "inboundShare", "notifications"];
117
122
  type CompleteValueList<Union, Values extends readonly Union[]> = Exclude<Union, Values[number]> extends never ? Values : never;
118
123
  declare const integrationAvailabilityStateValues: readonly ["available", "disabled", "approval-required", "configuration-required", "unsupported"];
package/dist/runtime.js CHANGED
@@ -1,10 +1,19 @@
1
+ const adPlacementFeatureByType = {
2
+ rewarded: 'rewardedAds',
3
+ interstitial: 'interstitialAds',
4
+ banner: 'bannerAds',
5
+ };
1
6
  export const platformFeatures = [
2
7
  'iap',
8
+ 'bannerAds',
3
9
  'rewardedAds',
4
10
  'interstitialAds',
5
11
  'leaderboard',
6
12
  'localization',
7
13
  ];
14
+ export function adPlacementFeatureFor(type) {
15
+ return adPlacementFeatureByType[type];
16
+ }
8
17
  export const targetIntegrations = [
9
18
  'identityUpgrade',
10
19
  'presentation',
@@ -100,22 +109,27 @@ export function getTargetConfig(matrix, target) {
100
109
  return config;
101
110
  }
102
111
  export function isPlatformFeatureEnabled(config, feature) {
103
- return config.features[feature];
112
+ return config.features[feature] === true;
104
113
  }
105
114
  export function applyTargetConfigToCapabilities(capabilities, config) {
115
+ const bannerAds = capabilities.bannerAds === true && config.features.bannerAds === true;
116
+ const rewardedAds = capabilities.rewardedAds && config.features.rewardedAds;
117
+ const interstitialAds = capabilities.interstitialAds && config.features.interstitialAds;
106
118
  return {
107
119
  ...capabilities,
108
120
  nativeIap: capabilities.nativeIap && config.features.iap,
109
121
  nativeAds: capabilities.nativeAds &&
110
- (config.features.rewardedAds || config.features.interstitialAds),
111
- rewardedAds: capabilities.rewardedAds && config.features.rewardedAds,
112
- interstitialAds: capabilities.interstitialAds && config.features.interstitialAds,
122
+ (bannerAds || rewardedAds || interstitialAds),
123
+ bannerAds,
124
+ rewardedAds,
125
+ interstitialAds,
113
126
  nativeLeaderboard: capabilities.nativeLeaderboard && config.features.leaderboard,
127
+ remoteLeaderboard: capabilities.remoteLeaderboard && config.features.leaderboard,
114
128
  localizedContent: capabilities.localizedContent && config.features.localization,
115
129
  };
116
130
  }
117
131
  export function getFeatureAvailability(feature, config, capabilities) {
118
- const targetEnabled = config.features[feature];
132
+ const targetEnabled = config.features[feature] === true;
119
133
  const capabilitySupported = isFeatureCapabilitySupported(feature, capabilities);
120
134
  const enabled = targetEnabled && capabilitySupported;
121
135
  return {
@@ -139,6 +153,7 @@ export function createTargetRuntimeSnapshot(input) {
139
153
  const availabilityConfig = resolveAvailabilityConfig(input.config, input.effectiveConfig);
140
154
  const features = {
141
155
  iap: getFeatureAvailability('iap', availabilityConfig, input.capabilities),
156
+ bannerAds: getFeatureAvailability('bannerAds', availabilityConfig, input.capabilities),
142
157
  rewardedAds: getFeatureAvailability('rewardedAds', availabilityConfig, input.capabilities),
143
158
  interstitialAds: getFeatureAvailability('interstitialAds', availabilityConfig, input.capabilities),
144
159
  leaderboard: getFeatureAvailability('leaderboard', availabilityConfig, input.capabilities),
@@ -160,7 +175,7 @@ export function createTargetRuntimeSnapshot(input) {
160
175
  features,
161
176
  integrations,
162
177
  adPlacements: (input.adPlacements ?? []).map((placement) => {
163
- const feature = placement.type === 'rewarded' ? 'rewardedAds' : 'interstitialAds';
178
+ const feature = adPlacementFeatureFor(placement.type);
164
179
  const availability = features[feature];
165
180
  return {
166
181
  id: placement.id,
@@ -206,25 +221,28 @@ export function withTargetAvailability(gateway, config, options = {}) {
206
221
  };
207
222
  const notifications = notificationsAvailable ? gatewayNotifications : undefined;
208
223
  const isIapAvailable = async () => (availabilityConfig.features.iap && (await getGatewayCapabilities()).nativeIap);
224
+ const isLeaderboardAvailable = async () => {
225
+ if (!availabilityConfig.features.leaderboard) {
226
+ return false;
227
+ }
228
+ const capabilities = await getGatewayCapabilities();
229
+ return capabilities.nativeLeaderboard || capabilities.remoteLeaderboard;
230
+ };
209
231
  const isAdPlacementAllowed = (placementId, expectedType) => {
210
232
  const actualType = options.resolveAdPlacementType?.(placementId);
211
233
  if (actualType !== undefined && actualType !== expectedType) {
212
234
  return false;
213
235
  }
214
- return expectedType === 'rewarded'
215
- ? availabilityConfig.features.rewardedAds
216
- : availabilityConfig.features.interstitialAds;
236
+ return availabilityConfig.features[adPlacementFeatureFor(expectedType)] === true;
217
237
  };
218
238
  const canPreloadAdPlacement = (placementId) => {
219
239
  const actualType = options.resolveAdPlacementType?.(placementId);
220
- if (actualType === 'rewarded') {
221
- return availabilityConfig.features.rewardedAds;
222
- }
223
- if (actualType === 'interstitial') {
224
- return availabilityConfig.features.interstitialAds;
240
+ if (actualType !== undefined) {
241
+ return availabilityConfig.features[adPlacementFeatureFor(actualType)] === true;
225
242
  }
226
243
  return (availabilityConfig.features.rewardedAds ||
227
- availabilityConfig.features.interstitialAds);
244
+ availabilityConfig.features.interstitialAds ||
245
+ availabilityConfig.features.bannerAds === true);
228
246
  };
229
247
  return {
230
248
  ...gatewayWithoutIntegrations,
@@ -302,17 +320,34 @@ export function withTargetAvailability(gateway, config, options = {}) {
302
320
  }
303
321
  return gateway.ads.showInterstitial(input);
304
322
  },
323
+ async mountBanner(input) {
324
+ if (!isAdPlacementAllowed(input.placementId, 'banner')
325
+ || gateway.ads.mountBanner === undefined) {
326
+ return { status: 'unavailable' };
327
+ }
328
+ return gateway.ads.mountBanner(input);
329
+ },
330
+ async unmountBanner(input) {
331
+ if (gateway.ads.unmountBanner !== undefined) {
332
+ await gateway.ads.unmountBanner(input);
333
+ }
334
+ },
305
335
  },
306
- leaderboard: availabilityConfig.features.leaderboard
307
- ? gateway.leaderboard
308
- : {
309
- async submitScore() {
336
+ leaderboard: {
337
+ async submitScore(input) {
338
+ if (!await isLeaderboardAvailable()) {
310
339
  return {
311
340
  submitted: false,
312
341
  };
313
- },
314
- async open() { },
342
+ }
343
+ return gateway.leaderboard.submitScore(input);
315
344
  },
345
+ async open(input) {
346
+ if (await isLeaderboardAvailable()) {
347
+ await gateway.leaderboard.open(input);
348
+ }
349
+ },
350
+ },
316
351
  };
317
352
  }
318
353
  function resolveAvailabilityConfig(config, effectiveConfig) {
@@ -330,12 +365,14 @@ function isFeatureCapabilitySupported(feature, capabilities) {
330
365
  switch (feature) {
331
366
  case 'iap':
332
367
  return capabilities.nativeIap;
368
+ case 'bannerAds':
369
+ return capabilities.bannerAds === true;
333
370
  case 'rewardedAds':
334
371
  return capabilities.rewardedAds;
335
372
  case 'interstitialAds':
336
373
  return capabilities.interstitialAds;
337
374
  case 'leaderboard':
338
- return capabilities.nativeLeaderboard;
375
+ return capabilities.nativeLeaderboard || capabilities.remoteLeaderboard;
339
376
  case 'localization':
340
377
  return capabilities.localizedContent;
341
378
  }
@@ -15,6 +15,10 @@ export type TargetViewportMeasurementSource = 'container' | 'visual-viewport' |
15
15
  export type TargetViewportControlPlacement = 'bottom' | 'side';
16
16
  /** Recommended placement for secondary game panels. */
17
17
  export type TargetViewportPanelPlacement = 'below' | 'side' | 'drawer';
18
+ /** Adaptive shell arrangement for a game surface and its surrounding UI. */
19
+ export type TargetViewportCompositionMode = 'side-rails' | 'bottom-controls' | 'compact-portrait';
20
+ /** Optional expanded-screen arrangement requested by a game. */
21
+ export type TargetViewportExpandedLayout = 'none' | 'side-rails';
18
22
  /** Width thresholds used to classify target viewports. */
19
23
  export interface TargetViewportBreakpoints {
20
24
  readonly compactMaxWidth: number;
@@ -109,6 +113,36 @@ export interface TargetViewportSnapshot extends TargetViewportPlan {
109
113
  readonly safeArea: TargetViewportSafeArea;
110
114
  }
111
115
  /**
116
+ * Input for resolving a concrete adaptive game shell from an existing viewport
117
+ * snapshot. The aspect ratio is expressed as width divided by height.
118
+ */
119
+ export interface TargetViewportCompositionInput {
120
+ readonly viewport: Pick<TargetViewportSnapshot, 'layout' | 'safeArea'>;
121
+ readonly gameAspectRatio: number;
122
+ readonly expandedLayout?: TargetViewportExpandedLayout;
123
+ readonly minRailWidth?: number;
124
+ }
125
+ interface TargetViewportCompositionBounds {
126
+ readonly contentBounds: TargetViewportBounds;
127
+ readonly gameBounds: TargetViewportBounds;
128
+ }
129
+ /** Concrete safe-area-relative geometry for an adaptive game shell. */
130
+ export type TargetViewportComposition = TargetViewportCompositionBounds & ({
131
+ readonly mode: 'side-rails';
132
+ readonly leftRailBounds: TargetViewportBounds;
133
+ readonly rightRailBounds: TargetViewportBounds;
134
+ readonly primaryControls: 'side';
135
+ readonly secondaryPanels: 'side';
136
+ } | {
137
+ readonly mode: 'bottom-controls';
138
+ readonly primaryControls: 'bottom';
139
+ readonly secondaryPanels: 'below';
140
+ } | {
141
+ readonly mode: 'compact-portrait';
142
+ readonly primaryControls: 'bottom';
143
+ readonly secondaryPanels: 'drawer';
144
+ });
145
+ /**
112
146
  * The minimal browser style surface needed to read CSS safe-area custom
113
147
  * properties. Keeping this structural avoids a runtime DOM dependency in the
114
148
  * target-config package.
@@ -151,6 +185,8 @@ export declare const defaultTargetViewportSafeAreaCssVariables: {
151
185
  readonly bottom: '--mpgd-safe-area-bottom';
152
186
  readonly left: '--mpgd-safe-area-left';
153
187
  };
188
+ /** Default minimum width for each expanded-layout side rail. */
189
+ export declare const defaultTargetViewportCompositionMinRailWidth = 160;
154
190
  /** Map a configured target runtime to its viewport host shell. */
155
191
  export declare function targetViewportShellForRuntime(runtime: TargetRuntimeKind): TargetViewportShell;
156
192
  /** Resolve a viewport shell from the runtime field of a target config. */
@@ -171,6 +207,15 @@ export declare function resolveTargetViewportPlan(input: TargetViewportInput, br
171
207
  */
172
208
  export declare function resolveTargetViewportSnapshot(input: TargetViewportSnapshotInput, breakpoints?: TargetViewportBreakpoints): TargetViewportSnapshot;
173
209
  /**
210
+ * Resolve an adaptive game shell inside the snapshot's safe content bounds.
211
+ *
212
+ * A landscape viewport uses side rails only when the requested game aspect and
213
+ * both minimum rails fit at full content height. Portrait viewports remain
214
+ * full-bleed so phone shells do not inherit desktop letterboxing. Intermediate
215
+ * layouts keep the full game bounds and move persistent controls to the bottom.
216
+ */
217
+ export declare function resolveTargetViewportComposition(input: TargetViewportCompositionInput): TargetViewportComposition;
218
+ /**
174
219
  * Resolve safe-area insets and content bounds for a given layout.
175
220
  *
176
221
  * Insets are clamped sequentially (top before bottom, left before right) so
@@ -185,3 +230,4 @@ export declare function resolveTargetViewportSafeArea(layout: Pick<TargetViewpor
185
230
  * the same full-viewport layout as before.
186
231
  */
187
232
  export declare function readTargetViewportSafeAreaInsets(style: TargetViewportComputedStyle, variables?: TargetViewportSafeAreaCssVariables): TargetViewportSafeAreaInsets;
233
+ export {};
package/dist/viewport.js CHANGED
@@ -26,6 +26,8 @@ export const defaultTargetViewportSafeAreaCssVariables = {
26
26
  bottom: '--mpgd-safe-area-bottom',
27
27
  left: '--mpgd-safe-area-left',
28
28
  };
29
+ /** Default minimum width for each expanded-layout side rail. */
30
+ export const defaultTargetViewportCompositionMinRailWidth = 160;
29
31
  const targetViewportOrientationPolicyModeDescriptors = {
30
32
  responsive: {
31
33
  preferredOrientation: undefined,
@@ -147,6 +149,80 @@ export function resolveTargetViewportSnapshot(input, breakpoints = defaultTarget
147
149
  safeArea: resolveTargetViewportSafeArea(plan.layout, input.safeAreaInsets),
148
150
  };
149
151
  }
152
+ /**
153
+ * Resolve an adaptive game shell inside the snapshot's safe content bounds.
154
+ *
155
+ * A landscape viewport uses side rails only when the requested game aspect and
156
+ * both minimum rails fit at full content height. Portrait viewports remain
157
+ * full-bleed so phone shells do not inherit desktop letterboxing. Intermediate
158
+ * layouts keep the full game bounds and move persistent controls to the bottom.
159
+ */
160
+ export function resolveTargetViewportComposition(input) {
161
+ const gameAspectRatio = normalizeViewportAspectRatio(input.gameAspectRatio);
162
+ const minRailWidth = normalizeViewportMinimum(input.minRailWidth ?? defaultTargetViewportCompositionMinRailWidth, 'minRailWidth');
163
+ const contentBounds = { ...input.viewport.safeArea.contentBounds };
164
+ const portrait = input.viewport.layout.orientation === 'portrait';
165
+ if (portrait) {
166
+ if (input.viewport.layout.sizeClass === 'compact') {
167
+ return {
168
+ mode: 'compact-portrait',
169
+ contentBounds,
170
+ gameBounds: contentBounds,
171
+ primaryControls: 'bottom',
172
+ secondaryPanels: 'drawer',
173
+ };
174
+ }
175
+ return {
176
+ mode: 'bottom-controls',
177
+ contentBounds,
178
+ gameBounds: contentBounds,
179
+ primaryControls: 'bottom',
180
+ secondaryPanels: 'below',
181
+ };
182
+ }
183
+ const requestedSideRails = input.expandedLayout === 'side-rails';
184
+ const minimumGameWidth = contentBounds.width > 0 && contentBounds.height > 0 ? 1 : 0;
185
+ const desiredGameWidth = Math.max(minimumGameWidth, Math.round(contentBounds.height * gameAspectRatio));
186
+ const fittedGameWidth = Math.min(contentBounds.width, desiredGameWidth);
187
+ const availableRailWidth = (contentBounds.width - fittedGameWidth) / 2;
188
+ if (requestedSideRails &&
189
+ input.viewport.layout.sizeClass === 'expanded' &&
190
+ desiredGameWidth <= contentBounds.width &&
191
+ availableRailWidth >= minRailWidth) {
192
+ const gameBounds = {
193
+ x: contentBounds.x + availableRailWidth,
194
+ y: contentBounds.y,
195
+ width: fittedGameWidth,
196
+ height: contentBounds.height,
197
+ };
198
+ return {
199
+ mode: 'side-rails',
200
+ contentBounds,
201
+ gameBounds,
202
+ leftRailBounds: {
203
+ x: contentBounds.x,
204
+ y: contentBounds.y,
205
+ width: availableRailWidth,
206
+ height: contentBounds.height,
207
+ },
208
+ rightRailBounds: {
209
+ x: gameBounds.x + gameBounds.width,
210
+ y: contentBounds.y,
211
+ width: availableRailWidth,
212
+ height: contentBounds.height,
213
+ },
214
+ primaryControls: 'side',
215
+ secondaryPanels: 'side',
216
+ };
217
+ }
218
+ return {
219
+ mode: 'bottom-controls',
220
+ contentBounds,
221
+ gameBounds: contentBounds,
222
+ primaryControls: 'bottom',
223
+ secondaryPanels: 'below',
224
+ };
225
+ }
150
226
  /**
151
227
  * Resolve safe-area insets and content bounds for a given layout.
152
228
  *
@@ -246,6 +322,20 @@ function normalizeViewportInset(value, name) {
246
322
  }
247
323
  return Math.round(value);
248
324
  }
325
+ /** Validate a non-negative CSS-pixel minimum without discarding fractions. */
326
+ function normalizeViewportMinimum(value, name) {
327
+ if (!Number.isFinite(value) || value < 0) {
328
+ throw new Error(`Viewport ${name} must be a non-negative finite number.`);
329
+ }
330
+ return value;
331
+ }
332
+ /** Validate a finite positive width-to-height aspect ratio. */
333
+ function normalizeViewportAspectRatio(value) {
334
+ if (!Number.isFinite(value) || value <= 0) {
335
+ throw new Error('Viewport gameAspectRatio must be a positive finite number.');
336
+ }
337
+ return value;
338
+ }
249
339
  /** Parse a non-negative CSS pixel custom-property value. */
250
340
  function readTargetViewportCssPixels(style, property) {
251
341
  const value = style.getPropertyValue(property).trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/target-config",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Target-specific platform feature availability and effective config helpers for mpgd.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,9 +37,9 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "typia": "13.0.2",
40
- "@mpgd/catalog": "0.5.2",
41
- "@mpgd/i18n": "0.5.4",
42
- "@mpgd/platform": "0.8.0"
40
+ "@mpgd/catalog": "0.7.0",
41
+ "@mpgd/i18n": "0.5.6",
42
+ "@mpgd/platform": "0.10.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "ttsc": "0.18.4",
package/targets.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026-07-19.1",
2
+ "version": "2026-08-12",
3
3
  "targets": {
4
4
  "web-preview": {
5
5
  "runtime": "web-preview",
@@ -47,10 +47,10 @@
47
47
  "microsoft-store": {
48
48
  "runtime": "microsoft-store-pwa",
49
49
  "features": {
50
- "iap": false,
50
+ "iap": true,
51
51
  "rewardedAds": false,
52
52
  "interstitialAds": false,
53
- "leaderboard": false,
53
+ "leaderboard": true,
54
54
  "localization": true
55
55
  },
56
56
  "capabilities": {
@@ -61,7 +61,7 @@
61
61
  "fallbackLocale": "en"
62
62
  },
63
63
  "monetization": {
64
- "iap": false,
64
+ "iap": true,
65
65
  "rewardedAds": false,
66
66
  "interstitialAds": false
67
67
  },
@@ -220,6 +220,7 @@
220
220
  "runtime": "apps-in-toss",
221
221
  "features": {
222
222
  "iap": true,
223
+ "bannerAds": true,
223
224
  "rewardedAds": true,
224
225
  "interstitialAds": true,
225
226
  "leaderboard": true,
@@ -234,6 +235,7 @@
234
235
  },
235
236
  "monetization": {
236
237
  "iap": true,
238
+ "bannerAds": true,
237
239
  "rewardedAds": true,
238
240
  "interstitialAds": true
239
241
  },