@mpgd/cli 0.47.0 → 0.48.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/command-launcher.d.ts +13 -0
- package/dist/command-launcher.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +126 -7
- package/dist/microsoft-store-submission.js +11 -6
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +2121 -350
- package/dist/windows-host/Core/BridgeCommand.cs +17 -0
- package/dist/windows-host/Core/Mpgd.MicrosoftStore.Host.csproj +9 -0
- package/dist/windows-host/Core/NativeBridgeContracts.cs +33 -0
- package/dist/windows-host/Core/NativeBridgeHost.cs +162 -0
- package/dist/windows-host/Core/NativeBridgeOrigin.cs +19 -0
- package/dist/windows-host/Core/NativeBridgeProtocol.cs +335 -0
- package/dist/windows-host/Core/NativeBridgeRequestGate.cs +76 -0
- package/dist/windows-host/Core/NativeBridgeResult.cs +65 -0
- package/dist/windows-host/README.md +118 -0
- package/dist/windows-host/WinUI/DispatcherQueueBridgeDispatcher.cs +39 -0
- package/dist/windows-host/WinUI/WebView2DocumentTransport.cs +46 -0
- package/dist/windows-host/WinUI/WebView2NativeBridge.cs +185 -0
- package/dist/windows-host/WinUI/WindowStoreContext.cs +20 -0
- package/dist/windows-native-tools.d.ts +4 -0
- package/dist/windows-native-tools.js +21 -0
- package/native-tools/windows-msix/WindowsMsixAcceptance.psm1 +333 -0
- package/package.json +24 -16
- package/templates/lite-overlay/.agents/skills/use-mpgd-kit/SKILL.md +22 -0
- package/templates/lite-overlay/AGENTS.md +27 -0
- package/templates/lite-overlay/README.md +37 -0
- package/templates/lite-overlay/agent/acceptance.md +27 -0
- package/templates/lite-overlay/agent/brief.md +17 -0
- package/templates/lite-overlay/apps/target-devvit/README.md +134 -0
- package/templates/lite-overlay/docs/MPGD_KIT_WORKFLOWS.md +37 -0
- package/templates/lite-overlay/src/lite/tapGame.ts +77 -0
- package/templates/lite-overlay/src/platform/devvitEntrypoint.ts +116 -0
- package/templates/lite-overlay/src/platform/devvitInlineMode.css +146 -0
- package/templates/lite-overlay/src/runtime/createGame.ts +57 -0
- package/templates/phaser-game/README.md +5 -3
- package/templates/phaser-game/apps/target-ait/package.json +2 -2
- package/templates/phaser-game/apps/target-ait/src/main.ts +5 -5
- package/templates/phaser-game/apps/target-devvit/src/server/bridge.ts +137 -18
- package/templates/phaser-game/package.json +1 -1
- package/templates/phaser-game/pnpm-workspace.yaml +2 -0
- package/templates/phaser-game/src/env.d.ts +2 -0
- package/templates/phaser-game/src/platform/buildGateways/ait.ts +1 -1
- package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +9 -4
- package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +2 -5
- package/templates/phaser-game/src/platform/buildGateways/reddit.ts +1 -1
- package/templates/phaser-game/src/platform/buildGateways/verse8.ts +1 -2
- package/templates/phaser-game/src/platform/verse8Agent8Client.ts +5 -6
- package/templates/phaser-game/vite.shared.ts +40 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
bridgeEventProtocol,
|
|
2
3
|
bridgeStorageLoadProtocol,
|
|
3
4
|
createBridgeError,
|
|
4
5
|
createCommerceV2UnavailableBridgeError,
|
|
6
|
+
createNotificationsV2UnavailableBridgeError,
|
|
7
|
+
isBridgeNotificationsV2Method,
|
|
5
8
|
type BridgeRequest,
|
|
6
9
|
type BridgeResponse,
|
|
7
10
|
type BridgeStorageLoadData,
|
|
@@ -90,10 +93,15 @@ export function createDevvitBridgeHandler(
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
return async function handleBridgeRequest(input: BridgeRequest): Promise<BridgeResponse> {
|
|
96
|
+
// Notifications v2 arrives with the Devvit provider (P3), which serves the
|
|
97
|
+
// game-rendered opt-in from this bridge; until then every v2 method
|
|
98
|
+
// answers the dedicated code, and the v1 `notifications.*` answers stay.
|
|
99
|
+
if (isBridgeNotificationsV2Method(input.method)) {
|
|
100
|
+
return createNotificationsV2UnavailableBridgeError(input, 'Devvit server bridge');
|
|
101
|
+
}
|
|
93
102
|
switch (input.method) {
|
|
94
103
|
case 'runtime.getCapabilities':
|
|
95
104
|
return ok(input, {
|
|
96
|
-
nativeIap: false,
|
|
97
105
|
nativeAds: false,
|
|
98
106
|
rewardedAds: false,
|
|
99
107
|
interstitialAds: false,
|
|
@@ -104,6 +112,7 @@ export function createDevvitBridgeHandler(
|
|
|
104
112
|
socialShare: false,
|
|
105
113
|
haptics: false,
|
|
106
114
|
localizedContent: true,
|
|
115
|
+
commerce: await readCommerceCapabilities(input, dependencies.commerce),
|
|
107
116
|
});
|
|
108
117
|
|
|
109
118
|
case 'identity.getPlayer': {
|
|
@@ -164,21 +173,6 @@ export function createDevvitBridgeHandler(
|
|
|
164
173
|
case 'notifications.requestSubscription':
|
|
165
174
|
return ok(input, 'unavailable');
|
|
166
175
|
|
|
167
|
-
case 'commerce.getProducts':
|
|
168
|
-
case 'commerce.getEntitlements':
|
|
169
|
-
return ok(input, []);
|
|
170
|
-
|
|
171
|
-
case 'commerce.purchase':
|
|
172
|
-
return ok(input, {
|
|
173
|
-
status: 'cancelled',
|
|
174
|
-
entitlementIds: [],
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
case 'commerce.restore':
|
|
178
|
-
return ok(input, {
|
|
179
|
-
restoredEntitlements: [],
|
|
180
|
-
});
|
|
181
|
-
|
|
182
176
|
case 'ads.preload':
|
|
183
177
|
return ok(input, {});
|
|
184
178
|
|
|
@@ -211,8 +205,9 @@ export function createDevvitBridgeHandler(
|
|
|
211
205
|
case 'storage.save':
|
|
212
206
|
return saveStorage(input, dependencies, storageKeyNamespace, warn);
|
|
213
207
|
|
|
214
|
-
// Commerce v2
|
|
215
|
-
// plugin; the checkout itself opens in the webview.
|
|
208
|
+
// Commerce v2: served by the configured handler over the payments
|
|
209
|
+
// plugin; the checkout itself opens in the webview. The removed v1
|
|
210
|
+
// `commerce.*` methods fall to UNSUPPORTED_METHOD below.
|
|
216
211
|
case 'commerce.v2.getCapabilities':
|
|
217
212
|
case 'commerce.v2.fetchProducts':
|
|
218
213
|
case 'commerce.v2.requestPurchase':
|
|
@@ -233,6 +228,130 @@ export function createDevvitBridgeHandler(
|
|
|
233
228
|
};
|
|
234
229
|
}
|
|
235
230
|
|
|
231
|
+
/**
|
|
232
|
+
* The all-false commerce capability block of a server that sells nothing
|
|
233
|
+
* (`createUnsupportedCommerceCapabilities()` of `@mpgd/platform`, kept equal by
|
|
234
|
+
* the bridge test). A fresh object on every read.
|
|
235
|
+
*/
|
|
236
|
+
function createUnsupportedCommerceBlock() {
|
|
237
|
+
return {
|
|
238
|
+
purchase: { consumable: false, durable: false, subscription: false, quantity: false },
|
|
239
|
+
catalog: { products: false, offers: false, localizedPrice: false },
|
|
240
|
+
lifecycle: {
|
|
241
|
+
availablePurchases: false,
|
|
242
|
+
pending: false,
|
|
243
|
+
restore: false,
|
|
244
|
+
events: false,
|
|
245
|
+
finish: 'none',
|
|
246
|
+
},
|
|
247
|
+
authority: { evidence: 'client-token', accountBinding: 'optional' },
|
|
248
|
+
availability: 'unsupported',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const commerceBlockShape = {
|
|
253
|
+
purchase: {
|
|
254
|
+
consumable: 'boolean',
|
|
255
|
+
durable: 'boolean',
|
|
256
|
+
subscription: 'boolean',
|
|
257
|
+
quantity: 'boolean',
|
|
258
|
+
},
|
|
259
|
+
catalog: { products: 'boolean', offers: 'boolean', localizedPrice: 'boolean' },
|
|
260
|
+
lifecycle: {
|
|
261
|
+
availablePurchases: 'boolean',
|
|
262
|
+
pending: 'boolean',
|
|
263
|
+
restore: 'boolean',
|
|
264
|
+
events: 'boolean',
|
|
265
|
+
finish: ['client', 'server', 'host', 'none'],
|
|
266
|
+
},
|
|
267
|
+
authority: {
|
|
268
|
+
evidence: ['client-token', 'host-callback', 'adapter-settled', 'server-push'],
|
|
269
|
+
accountBinding: ['required', 'optional', 'host'],
|
|
270
|
+
},
|
|
271
|
+
} as const;
|
|
272
|
+
const commerceAvailabilities: readonly unknown[] = [
|
|
273
|
+
'available',
|
|
274
|
+
'unsupported',
|
|
275
|
+
'configuration-required',
|
|
276
|
+
'action-required',
|
|
277
|
+
'temporarily-unavailable',
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
function isPlainRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
281
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function hasExactKeys(value: Readonly<Record<string, unknown>>, keys: readonly string[]): boolean {
|
|
285
|
+
const own = Object.keys(value);
|
|
286
|
+
return own.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A fresh copy of a well-formed commerce capability block (the exact keys and
|
|
291
|
+
* values `PlatformCapabilities.commerce` allows), or undefined for anything
|
|
292
|
+
* else, so a malformed handler answer never reaches the snapshot.
|
|
293
|
+
*/
|
|
294
|
+
function copyCommerceBlock(value: unknown): Record<string, unknown> | undefined {
|
|
295
|
+
if (!isPlainRecord(value)
|
|
296
|
+
|| !hasExactKeys(value, [...Object.keys(commerceBlockShape), 'availability'])
|
|
297
|
+
|| !commerceAvailabilities.includes(value.availability)) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
const copy: Record<string, unknown> = { availability: value.availability };
|
|
301
|
+
for (const [section, fields] of Object.entries(commerceBlockShape)) {
|
|
302
|
+
const source = value[section];
|
|
303
|
+
if (!isPlainRecord(source) || !hasExactKeys(source, Object.keys(fields))) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const sectionCopy: Record<string, unknown> = {};
|
|
307
|
+
for (const [field, rule] of Object.entries(fields)) {
|
|
308
|
+
const fieldValue = source[field];
|
|
309
|
+
const valid = rule === 'boolean'
|
|
310
|
+
? typeof fieldValue === 'boolean'
|
|
311
|
+
: (rule as readonly unknown[]).includes(fieldValue);
|
|
312
|
+
if (!valid) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
sectionCopy[field] = fieldValue;
|
|
316
|
+
}
|
|
317
|
+
copy[section] = sectionCopy;
|
|
318
|
+
}
|
|
319
|
+
return copy;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The `commerce` block of the server snapshot: what the configured commerce
|
|
324
|
+
* handler reports for this server, otherwise the unsupported block. It fails
|
|
325
|
+
* closed: a missing handler, a handler that throws or answers an error, or an
|
|
326
|
+
* answer without a well-formed capability block sells nothing. The webview
|
|
327
|
+
* gateway owns the block it finally reports (its provider's, or the
|
|
328
|
+
* unsupported one).
|
|
329
|
+
*/
|
|
330
|
+
async function readCommerceCapabilities(
|
|
331
|
+
input: BridgeRequest,
|
|
332
|
+
handler: DevvitBridgeCommerceHandler | undefined,
|
|
333
|
+
): Promise<unknown> {
|
|
334
|
+
if (handler === undefined) {
|
|
335
|
+
return createUnsupportedCommerceBlock();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const response = await handler.request({
|
|
340
|
+
...input,
|
|
341
|
+
method: 'commerce.v2.getCapabilities',
|
|
342
|
+
payload: { protocol: bridgeEventProtocol },
|
|
343
|
+
});
|
|
344
|
+
const data: unknown = response.ok ? response.data : undefined;
|
|
345
|
+
const capabilities = typeof data === 'object' && data !== null
|
|
346
|
+
? (data as { readonly capabilities?: unknown }).capabilities
|
|
347
|
+
: undefined;
|
|
348
|
+
|
|
349
|
+
return copyCommerceBlock(capabilities) ?? createUnsupportedCommerceBlock();
|
|
350
|
+
} catch {
|
|
351
|
+
return createUnsupportedCommerceBlock();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
236
355
|
async function loadStorage(
|
|
237
356
|
input: BridgeRequest,
|
|
238
357
|
dependencies: DevvitBridgeHandlerDependencies,
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@ait-co/polyfill": "^0.1.19",
|
|
59
|
-
"@apps-in-toss/web-framework": "3.
|
|
59
|
+
"@apps-in-toss/web-framework": "3.6.0",
|
|
60
60
|
"@capacitor/app": "8.1.1",
|
|
61
61
|
"@capacitor/core": "8.5.2",
|
|
62
62
|
"@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
declare const __APP_TARGET__: string;
|
|
4
4
|
declare const __MPGD_CONFIG_TARGET__: string;
|
|
5
|
+
/** The Apps in Toss app name `intoss://` share links carry; see `vite.shared.ts`. */
|
|
6
|
+
declare const __MPGD_AIT_APP_NAME__: string;
|
|
5
7
|
declare const __MPGD_PLATFORM_TARGET__:
|
|
6
8
|
| import('@mpgd/target-config').EffectivePlatformTargetMetadata
|
|
7
9
|
| undefined;
|
|
@@ -8,7 +8,7 @@ import type { RuntimeConfig } from '../runtimeDetector';
|
|
|
8
8
|
* host side: the wrapper (`apps/target-ait`) installs the IAP products from
|
|
9
9
|
* the catalog's `platformProductIds.ait` entries and serves the six
|
|
10
10
|
* `commerce.v2.*` methods plus the `commerceV2` event channel, so the gateway
|
|
11
|
-
* exposes `
|
|
11
|
+
* exposes `commerce` whenever the installed bridge has that channel. The
|
|
12
12
|
* host's grant callback only confirms through the game backend; nothing is
|
|
13
13
|
* granted from this side.
|
|
14
14
|
*/
|
|
@@ -5,15 +5,20 @@ import type { RuntimeConfig } from '../runtimeDetector';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Local Apps in Toss playtest. The sandbox bridge keeps its build-id gate: only
|
|
8
|
-
* the `ait-sandbox` build (`pnpm dev:ait`) gets the
|
|
8
|
+
* the `ait-sandbox` build (`pnpm dev:ait`) gets the commerce event channel and
|
|
9
9
|
* the evidence-only v2 stub; any other build id gets no channel, so the
|
|
10
|
-
* gateway offers no `
|
|
11
|
-
* `COMMERCE_V2_UNAVAILABLE`.
|
|
10
|
+
* gateway offers no `commerce` provider and the v2 methods answer
|
|
11
|
+
* `COMMERCE_V2_UNAVAILABLE`. Every build answers the `notifications.v2.*`
|
|
12
|
+
* methods with `NOTIFICATIONS_V2_UNAVAILABLE` until the Apps in Toss
|
|
13
|
+
* notifications provider lands.
|
|
12
14
|
*/
|
|
13
15
|
export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
|
|
14
16
|
return createAitPlatformGateway({
|
|
15
17
|
appVersion: runtime.appVersion,
|
|
16
18
|
buildId: runtime.buildId,
|
|
17
|
-
fallbackBridge: createAitSandboxBridge({
|
|
19
|
+
fallbackBridge: createAitSandboxBridge({
|
|
20
|
+
appName: __MPGD_AIT_APP_NAME__,
|
|
21
|
+
buildId: runtime.buildId,
|
|
22
|
+
}),
|
|
18
23
|
});
|
|
19
24
|
}
|
|
@@ -41,8 +41,8 @@ function resolveMicrosoftStoreProducts(target: string): readonly MicrosoftStoreC
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Commerce
|
|
45
|
-
* installed as `
|
|
44
|
+
* Commerce over the Digital Goods path (`createMicrosoftStoreCommerceProvider`)
|
|
45
|
+
* installed as `gateway.commerce` with its capability block. The purchase authority stays
|
|
46
46
|
* `configuration-required` until the game points it at its backend: the
|
|
47
47
|
* authority's ledger entry is the only settlement, and the reconciler still
|
|
48
48
|
* verifies it through game-services before anything is granted.
|
|
@@ -64,9 +64,6 @@ export async function createBuildGateway(runtime: RuntimeConfig): Promise<Platfo
|
|
|
64
64
|
async verifyAndGrant() {
|
|
65
65
|
return { status: 'failed' };
|
|
66
66
|
},
|
|
67
|
-
async getEntitlements() {
|
|
68
|
-
return [];
|
|
69
|
-
},
|
|
70
67
|
},
|
|
71
68
|
});
|
|
72
69
|
return withMicrosoftStoreCommerceProvider(base, commerce);
|
|
@@ -20,7 +20,7 @@ const productCatalog = productCatalogJson as ProductCatalog;
|
|
|
20
20
|
* which serves it only after the Devvit server opted in
|
|
21
21
|
* (`permissions.payments` plus the same catalog SKUs), and the grant comes
|
|
22
22
|
* from the fulfillment handler's ledger entry. Without SKUs the gateway
|
|
23
|
-
* offers no `
|
|
23
|
+
* offers no `commerce` provider at all.
|
|
24
24
|
*/
|
|
25
25
|
export async function createBuildGateway(runtime: RuntimeConfig): Promise<PlatformGateway> {
|
|
26
26
|
const commerce = createCommerceOptions();
|
|
@@ -18,7 +18,7 @@ import { resolveStarterVerse8Agent8Client } from '../verse8Agent8Client';
|
|
|
18
18
|
const productCatalog = productCatalogJson as ProductCatalog;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Verse8 gateway. VXShop and the commerce
|
|
21
|
+
* Verse8 gateway. VXShop and the commerce provider are installed only when
|
|
22
22
|
* the game provides its authenticated Agent8 client (`verse8Agent8Client.ts`)
|
|
23
23
|
* and the catalog names VXShop product ids (`platformProductIds.verse8`):
|
|
24
24
|
* `vxShop.purchases` reads the purchases the Agent8 server recorded, which is
|
|
@@ -58,7 +58,6 @@ function createVxShopOptions(runtime: RuntimeConfig): Verse8CommerceOptions | un
|
|
|
58
58
|
return {
|
|
59
59
|
purchaseEventAuthority: 'agent8-server',
|
|
60
60
|
products,
|
|
61
|
-
loadEntitlements: async () => (await agent8.getCommerceSnapshot()).entitlements,
|
|
62
61
|
purchases: {
|
|
63
62
|
listPurchases: () => agent8.listPurchases(),
|
|
64
63
|
},
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import type { Verse8RecordedPurchase } from '@mpgd/adapter-verse8';
|
|
2
|
-
import type { Entitlement } from '@mpgd/platform';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* The game-owned Agent8 remote client the Verse8 gateway reads commerce state
|
|
6
|
-
* through:
|
|
5
|
+
* through: one authenticated server method exposed for the sender's own
|
|
7
6
|
* account (see docs/VERSE8_COMMERCE.md, "Iframe client boundary"). The
|
|
8
7
|
* starter installs no Agent8 browser SDK, so `resolveStarterVerse8Agent8Client`
|
|
9
8
|
* answers `undefined` until the game provides one; VXShop then stays off and
|
|
10
|
-
* the gateway offers no `
|
|
9
|
+
* the gateway offers no `commerce` provider and sells nothing. The client only
|
|
11
10
|
* reads: `$onItemPurchased` on the Agent8 server is the sole grant path.
|
|
11
|
+
* Entitlements are not read from the client: they come from the backend
|
|
12
|
+
* ledger through game-services.
|
|
12
13
|
*/
|
|
13
14
|
export interface StarterVerse8Agent8Client {
|
|
14
|
-
/** Server-owned balances and entitlements (`getSnapshot` of the Agent8 commerce service). */
|
|
15
|
-
getCommerceSnapshot(): Promise<{ readonly entitlements: readonly Entitlement[] }>;
|
|
16
15
|
/** The purchases `$onItemPurchased` recorded (`listPurchases` of the Agent8 commerce service). */
|
|
17
16
|
listPurchases(): Promise<readonly Verse8RecordedPurchase[]>;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
/** Return the game's authenticated Agent8 client here to enable VXShop and commerce
|
|
19
|
+
/** Return the game's authenticated Agent8 client here to enable VXShop and commerce on Verse8. */
|
|
21
20
|
export function resolveStarterVerse8Agent8Client(): StarterVerse8Agent8Client | undefined {
|
|
22
21
|
return undefined;
|
|
23
22
|
}
|
|
@@ -17,6 +17,8 @@ interface RuntimePlatformTargetMetadata {
|
|
|
17
17
|
readonly crazyGamesLaunch?: 'basic' | 'full';
|
|
18
18
|
readonly authoritativeGameServices?: boolean;
|
|
19
19
|
readonly integrations?: Record<string, unknown>;
|
|
20
|
+
/** An Apps in Toss target's `metadata.appName`, for the sandbox bridge only. */
|
|
21
|
+
readonly aitAppName?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// Sandbox gateways self-complete purchases and rewards without any backend
|
|
@@ -100,7 +102,10 @@ export function createGameViteSharedConfig<SharedPluginOption = PluginOption>(
|
|
|
100
102
|
__APP_TARGET__: JSON.stringify(appTarget),
|
|
101
103
|
__MPGD_CONFIG_TARGET__: JSON.stringify(configTarget),
|
|
102
104
|
__MPGD_PLATFORM_TARGET__:
|
|
103
|
-
platformTarget === undefined
|
|
105
|
+
platformTarget === undefined
|
|
106
|
+
? 'undefined'
|
|
107
|
+
: JSON.stringify(toEffectivePlatformTargetMetadata(platformTarget)),
|
|
108
|
+
__MPGD_AIT_APP_NAME__: JSON.stringify(resolveAitAppName(platformTarget)),
|
|
104
109
|
__MPGD_TARGET_CONFIG_MATRIX__:
|
|
105
110
|
runtimeTargetConfigMatrix === undefined
|
|
106
111
|
? 'undefined'
|
|
@@ -264,6 +269,8 @@ function readRuntimePlatformTarget(
|
|
|
264
269
|
throw new Error(`Platform target ${configTarget} authoritativeGameServices must be a boolean.`);
|
|
265
270
|
}
|
|
266
271
|
|
|
272
|
+
const aitAppName = readAitAppName(target);
|
|
273
|
+
|
|
267
274
|
return {
|
|
268
275
|
kind: target.kind,
|
|
269
276
|
adapter: target.adapter,
|
|
@@ -272,9 +279,41 @@ function readRuntimePlatformTarget(
|
|
|
272
279
|
: { authoritativeGameServices: target.authoritativeGameServices }),
|
|
273
280
|
...(target.crazyGamesLaunch === undefined ? {} : { crazyGamesLaunch: target.crazyGamesLaunch as 'basic' | 'full' }),
|
|
274
281
|
...(target.integrations === undefined ? {} : { integrations: target.integrations }),
|
|
282
|
+
...(aitAppName === undefined ? {} : { aitAppName }),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function toEffectivePlatformTargetMetadata(
|
|
287
|
+
input: RuntimePlatformTargetMetadata,
|
|
288
|
+
): Omit<RuntimePlatformTargetMetadata, 'aitAppName'> {
|
|
289
|
+
return {
|
|
290
|
+
kind: input.kind,
|
|
291
|
+
adapter: input.adapter,
|
|
292
|
+
...(input.authoritativeGameServices === undefined
|
|
293
|
+
? {}
|
|
294
|
+
: { authoritativeGameServices: input.authoritativeGameServices }),
|
|
295
|
+
...(input.crazyGamesLaunch === undefined ? {} : { crazyGamesLaunch: input.crazyGamesLaunch }),
|
|
296
|
+
...(input.integrations === undefined ? {} : { integrations: input.integrations }),
|
|
275
297
|
};
|
|
276
298
|
}
|
|
277
299
|
|
|
300
|
+
/**
|
|
301
|
+
* The app name `intoss://` share links must carry in the Apps in Toss sandbox
|
|
302
|
+
* bridge, the one the wrapper's host bridge uses: `MPGD_AIT_APP_NAME`, else
|
|
303
|
+
* the target's `metadata.appName`, else the game name.
|
|
304
|
+
*/
|
|
305
|
+
function resolveAitAppName(platformTarget: RuntimePlatformTargetMetadata | undefined): string {
|
|
306
|
+
return process.env.MPGD_AIT_APP_NAME?.trim() || platformTarget?.aitAppName || '__GAME_NAME__';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function readAitAppName(target: Record<string, unknown>): string | undefined {
|
|
310
|
+
const appName = target.kind === 'apps-in-toss' && isRecord(target.metadata)
|
|
311
|
+
? target.metadata.appName
|
|
312
|
+
: undefined;
|
|
313
|
+
|
|
314
|
+
return typeof appName === 'string' && appName.trim().length > 0 ? appName.trim() : undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
278
317
|
function isRecord(input: unknown): input is Record<string, unknown> {
|
|
279
318
|
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
280
319
|
}
|