@mpgd/game-services 0.11.0 → 0.12.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.
@@ -8,6 +8,7 @@ const platformTargets = new Set([
8
8
  'verse8',
9
9
  'telegram',
10
10
  'tauri',
11
+ 'microsoft-store',
11
12
  ]);
12
13
  const notificationTopics = new Set([
13
14
  'daily-ready',
package/dist/runtime.d.ts CHANGED
@@ -17,8 +17,11 @@ export interface CreateGameServicesRuntimeInput {
17
17
  readonly playerId: string;
18
18
  readonly authorityMode: GameServicesAuthorityMode;
19
19
  readonly target?: PlatformTarget | string;
20
+ readonly deploymentTarget?: string;
20
21
  readonly baseUrl?: string;
21
22
  readonly transport?: 'http' | 'orpc';
23
+ /** Static headers sent with every authoritative game-services request. */
24
+ readonly headers?: Record<string, string>;
22
25
  readonly allowLocalBackend?: boolean;
23
26
  readonly localBackend?: GameServicesBackendApi;
24
27
  readonly analytics?: AnalyticsSink;
package/dist/runtime.js CHANGED
@@ -19,9 +19,15 @@ export function createGameServicesRuntime(input) {
19
19
  }
20
20
  mode = input.transport === 'orpc' ? 'orpc' : 'http';
21
21
  backend = mode === 'orpc'
22
- ? createGameServicesOrpcBackendApi(createGameServicesOrpcClient({ url: baseUrl }))
22
+ ? createGameServicesOrpcBackendApi(createGameServicesOrpcClient({
23
+ url: baseUrl,
24
+ ...(input.headers === undefined ? {} : { headers: input.headers }),
25
+ }))
23
26
  : createGameServicesHttpBackendApi({
24
- transport: createGameServicesFetchBackendTransport({ baseUrl }),
27
+ transport: createGameServicesFetchBackendTransport({
28
+ baseUrl,
29
+ ...(input.headers === undefined ? {} : { headers: input.headers }),
30
+ }),
25
31
  });
26
32
  }
27
33
  else {
@@ -46,6 +52,9 @@ export function createGameServicesRuntime(input) {
46
52
  backend,
47
53
  playerId: input.playerId,
48
54
  target,
55
+ ...(input.deploymentTarget === undefined
56
+ ? {}
57
+ : { deploymentTarget: input.deploymentTarget }),
49
58
  ...(input.analytics === undefined ? {} : { analytics: input.analytics }),
50
59
  ...(input.analyticsSessionId === undefined
51
60
  ? {}
@@ -56,6 +65,7 @@ export function createGameServicesRuntime(input) {
56
65
  }
57
66
  export function resolveGameServicesLedgerTarget(target) {
58
67
  if (target === 'browser'
68
+ || target === 'microsoft-store'
59
69
  || target === 'android'
60
70
  || target === 'ios'
61
71
  || target === 'ait'
package/dist/server.d.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import { type AnalyticsSink } from '@mpgd/analytics';
2
- import type { AdPlacements, ProductCatalog } from '@mpgd/catalog';
2
+ import { type AdPlacements, type ProductCatalog } from '@mpgd/catalog';
3
3
  import { type GameServicesBackendApi, type GameServicesBackendTransport, type GameServicesBackendTransportRequest, type GameServicesBackendTransportResponse } from './client';
4
4
  import { type GameServicesHealthResponse } from './contract';
5
5
  import { type GameServicesEvidenceVerifier, type GameServicesPurchaseGrantFinalizer } from './evidence-verification';
6
- import { type ClaimAdRewardRequest, type ClaimAdRewardResponse, type EntitlementLedgerGrant, type EntitlementLedgerResult, type LeaderboardScoreTransaction, type ProductGrantTransaction, type RecordLeaderboardScoreRequest, type RecordLeaderboardScoreResponse, type VerifyPurchaseRequest, type VerifyPurchaseResponse } from './types';
6
+ import { type ClaimAdRewardRequest, type ClaimAdRewardResponse, type EntitlementLedgerGrant, type EntitlementLedgerResult, type GameServicesAdRewardTarget, type GameServicesStoreTarget, type LeaderboardScoreTransaction, type ProductGrantTransaction, type RecordLeaderboardScoreRequest, type RecordLeaderboardScoreResponse, type VerifyPurchaseRequest, type VerifyPurchaseResponse } from './types';
7
7
  type CorsHeaders = Record<string, string>;
8
8
  export interface CreateGameServicesBackendInput {
9
9
  readonly catalog: ProductCatalog;
10
10
  readonly placements: AdPlacements;
11
+ /** Trusted deployment-config bindings keyed by the request's platform target. */
12
+ readonly deploymentTargetBindings?: GameServicesDeploymentTargetBindings;
11
13
  readonly store?: GameServicesStore;
12
14
  readonly analytics?: AnalyticsSink;
13
15
  readonly analyticsSessionId?: string;
@@ -18,6 +20,7 @@ export interface CreateGameServicesBackendInput {
18
20
  readonly purchaseGrantFinalizationTimeoutMs?: number;
19
21
  readonly purchaseGrantFinalizer?: GameServicesPurchaseGrantFinalizer;
20
22
  }
23
+ export type GameServicesDeploymentTargetBindings = Readonly<Partial<Record<GameServicesAdRewardTarget | GameServicesStoreTarget, string>>>;
21
24
  export interface GameServicesBackendApiHandler {
22
25
  readonly version?: string;
23
26
  handle(request: GameServicesBackendTransportRequest): Promise<GameServicesBackendTransportResponse>;
package/dist/server.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { implement } from '@orpc/server';
2
2
  import { RPCHandler } from '@orpc/server/fetch';
3
3
  import { createAnalyticsReporter } from '@mpgd/analytics';
4
+ import { resolveAdPlacementPlatformId, resolveProductPlatformId, } from '@mpgd/catalog';
4
5
  import { gameServicesBackendEndpoints, } from './client';
5
6
  import { gameServicesContract } from './contract';
6
7
  import { createRejectingGameServicesEvidenceVerifier, } from './evidence-verification';
7
- import { assertClaimAdRewardRequest, assertClaimAdRewardResponse, assertEntitlementLedgerGrant, assertEntitlementLedgerResult, assertLeaderboardScoreTransaction, assertProductGrantTransaction, assertPurchaseGrantFinalization, assertRecordLeaderboardScoreRequest, assertRecordLeaderboardScoreResponse, assertVerifyPurchaseRequest, assertVerifyPurchaseResponse, } from './types';
8
+ import { assertClaimAdRewardRequest, assertClaimAdRewardResponse, assertEntitlementLedgerGrant, assertEntitlementLedgerResult, assertGameServicesDeploymentTarget, assertLeaderboardScoreTransaction, assertProductGrantTransaction, assertPurchaseGrantFinalization, assertRecordLeaderboardScoreRequest, assertRecordLeaderboardScoreResponse, assertVerifyPurchaseRequest, assertVerifyPurchaseResponse, } from './types';
8
9
  export class InMemoryGameServicesStore {
9
10
  entitlementTransactionsByKey = new Map();
10
11
  entitlementTransactionsByEvidence = new Map();
@@ -130,6 +131,7 @@ export function createGameServicesBackend(input) {
130
131
  const evidenceVerifier = input.evidenceVerifier ?? createRejectingGameServicesEvidenceVerifier();
131
132
  const evidenceVerificationTimeoutMs = resolveEvidenceVerificationTimeout(input.evidenceVerificationTimeoutMs);
132
133
  const purchaseGrantFinalizationTimeoutMs = resolvePurchaseGrantFinalizationTimeout(input.purchaseGrantFinalizationTimeoutMs);
134
+ const deploymentTargetBindings = resolveDeploymentTargetBindings(input.deploymentTargetBindings);
133
135
  const analytics = createAnalyticsReporter({
134
136
  target: 'server',
135
137
  sessionId: input.analyticsSessionId ?? 'game-services',
@@ -147,6 +149,7 @@ export function createGameServicesBackend(input) {
147
149
  evidenceVerifier,
148
150
  evidenceVerificationTimeoutMs,
149
151
  purchaseGrantFinalizationTimeoutMs,
152
+ deploymentTargetBindings,
150
153
  ...(input.purchaseGrantFinalizer === undefined
151
154
  ? {}
152
155
  : { purchaseGrantFinalizer: input.purchaseGrantFinalizer }),
@@ -177,6 +180,7 @@ export function createGameServicesBackend(input) {
177
180
  now,
178
181
  evidenceVerifier,
179
182
  evidenceVerificationTimeoutMs,
183
+ deploymentTargetBindings,
180
184
  });
181
185
  await analytics.track({
182
186
  name: claim.granted ? 'rewarded_ad_granted' : 'rewarded_ad_rejected',
@@ -313,14 +317,45 @@ export function createGameServicesHttpFetchHandler(handler, options = {}) {
313
317
  }
314
318
  };
315
319
  }
320
+ const gameServicesDeploymentTargetPlatforms = new Set([
321
+ 'microsoft-store',
322
+ 'android',
323
+ 'ios',
324
+ 'ait',
325
+ 'verse8',
326
+ ]);
327
+ function resolveDeploymentTargetBindings(input) {
328
+ const bindings = {};
329
+ for (const [platformTarget, deploymentTarget] of Object.entries(input ?? {})) {
330
+ if (!gameServicesDeploymentTargetPlatforms.has(platformTarget)) {
331
+ throw new Error(`Unsupported deployment target binding platform: ${platformTarget}.`);
332
+ }
333
+ assertGameServicesDeploymentTarget(deploymentTarget);
334
+ bindings[platformTarget] = deploymentTarget;
335
+ }
336
+ return bindings;
337
+ }
338
+ function bindRequestDeploymentTarget(request, bindings) {
339
+ const deploymentTarget = bindings[request.target] ?? request.target;
340
+ if (request.deploymentTarget !== undefined
341
+ && request.deploymentTarget !== deploymentTarget) {
342
+ throw new Error(`deploymentTarget must match the backend binding for ${request.target}.`);
343
+ }
344
+ return deploymentTarget === request.target || request.deploymentTarget === deploymentTarget
345
+ ? request
346
+ : { ...request, deploymentTarget };
347
+ }
316
348
  async function verifyPurchaseWithStore(input, context) {
317
- const request = assertVerifyPurchaseRequest(input);
349
+ const request = bindRequestDeploymentTarget(assertVerifyPurchaseRequest(input), context.deploymentTargetBindings);
318
350
  const retryIdentity = {
319
351
  source: 'purchase',
320
352
  playerId: request.playerId,
321
353
  idempotencyKey: request.idempotencyKey,
322
354
  grantId: request.productId,
323
355
  target: request.target,
356
+ ...(request.deploymentTarget === undefined
357
+ ? {}
358
+ : { deploymentTarget: request.deploymentTarget }),
324
359
  };
325
360
  const completeExistingRetry = async (transaction) => {
326
361
  if (!matchesEntitlementRetry(transaction, retryIdentity)) {
@@ -350,7 +385,7 @@ async function verifyPurchaseWithStore(input, context) {
350
385
  reason: 'UNKNOWN_PRODUCT',
351
386
  });
352
387
  }
353
- const platformProductId = product.platformProductIds[request.target];
388
+ const platformProductId = resolveProductPlatformId(product, request.deploymentTarget ?? request.target);
354
389
  if (platformProductId === undefined) {
355
390
  return assertVerifyPurchaseResponse({
356
391
  verified: false,
@@ -394,6 +429,9 @@ async function verifyPurchaseWithStore(input, context) {
394
429
  payload: {
395
430
  ...verificationPayload,
396
431
  target: request.target,
432
+ ...(request.deploymentTarget === undefined
433
+ ? {}
434
+ : { deploymentTarget: request.deploymentTarget }),
397
435
  productId: request.productId,
398
436
  productType: product.type,
399
437
  platformProductId,
@@ -408,6 +446,30 @@ async function verifyPurchaseWithStore(input, context) {
408
446
  },
409
447
  });
410
448
  if (grant.status === 'evidence_already_processed') {
449
+ const existingEvidence = await findEntitlementTransactionByEvidenceVerificationId(context.store, { source: 'purchase', evidenceVerificationId: verification.verificationId });
450
+ if (existingEvidence !== undefined
451
+ && matchesPurchaseEvidenceRecovery(existingEvidence, {
452
+ playerId: request.playerId,
453
+ grantId: request.productId,
454
+ target: request.target,
455
+ ...(request.deploymentTarget === undefined
456
+ ? {}
457
+ : { deploymentTarget: request.deploymentTarget }),
458
+ evidenceVerificationId: verification.verificationId,
459
+ })) {
460
+ const finalization = await finalizeExistingPurchaseGrant(request, existingEvidence, context);
461
+ // A different idempotency key is evidence replay unless a configured finalizer can resume
462
+ // the provider operation. Keep generic stores fail-closed while allowing Store consume
463
+ // recovery to report the already-recorded grant.
464
+ if (finalization !== undefined) {
465
+ return assertVerifyPurchaseResponse({
466
+ verified: true,
467
+ ledgerEntryId: existingEvidence.ledgerEntryId,
468
+ alreadyProcessed: true,
469
+ finalization,
470
+ });
471
+ }
472
+ }
411
473
  return assertVerifyPurchaseResponse({
412
474
  verified: false,
413
475
  alreadyProcessed: false,
@@ -569,13 +631,16 @@ function createStoredPurchaseRequest(request, transaction) {
569
631
  };
570
632
  }
571
633
  async function claimAdRewardWithStore(input, context) {
572
- const request = assertClaimAdRewardRequest(input);
634
+ const request = bindRequestDeploymentTarget(assertClaimAdRewardRequest(input), context.deploymentTargetBindings);
573
635
  const retryIdentity = {
574
636
  source: 'ad_reward',
575
637
  playerId: request.playerId,
576
638
  idempotencyKey: request.idempotencyKey,
577
639
  grantId: request.placementId,
578
640
  target: request.target,
641
+ ...(request.deploymentTarget === undefined
642
+ ? {}
643
+ : { deploymentTarget: request.deploymentTarget }),
579
644
  };
580
645
  const existing = await findEntitlementTransactionByIdempotency(context.store, retryIdentity);
581
646
  if (existing !== undefined) {
@@ -600,7 +665,7 @@ async function claimAdRewardWithStore(input, context) {
600
665
  reason: placement === undefined ? 'UNKNOWN_PLACEMENT' : 'NOT_REWARDED_PLACEMENT',
601
666
  });
602
667
  }
603
- const platformPlacementId = placement.platformPlacementIds[request.target];
668
+ const platformPlacementId = resolveAdPlacementPlatformId(placement, request.deploymentTarget ?? request.target);
604
669
  const verification = await verifyEvidence((signal) => {
605
670
  return context.evidenceVerifier.verifyAdReward({
606
671
  request,
@@ -622,6 +687,9 @@ async function claimAdRewardWithStore(input, context) {
622
687
  const payload = {
623
688
  ...verification.payload,
624
689
  target: request.target,
690
+ ...(request.deploymentTarget === undefined
691
+ ? {}
692
+ : { deploymentTarget: request.deploymentTarget }),
625
693
  placementId: request.placementId,
626
694
  rewardType: placement.reward.type,
627
695
  amount: placement.reward.amount,
@@ -904,11 +972,23 @@ async function recordedEntitlementMatchesRetry(store, result, identity) {
904
972
  return transaction !== undefined && matchesEntitlementRetry(transaction, identity);
905
973
  }
906
974
  function matchesEntitlementRetry(transaction, identity) {
975
+ return matchesEntitlementTransactionIdentity(transaction, identity)
976
+ && transaction.idempotencyKey === identity.idempotencyKey;
977
+ }
978
+ function matchesPurchaseEvidenceRecovery(transaction, identity) {
979
+ return matchesEntitlementTransactionIdentity(transaction, {
980
+ ...identity,
981
+ source: 'purchase',
982
+ })
983
+ && (transaction.evidenceVerificationId
984
+ ?? transaction.payload.evidenceVerificationId) === identity.evidenceVerificationId;
985
+ }
986
+ function matchesEntitlementTransactionIdentity(transaction, identity) {
907
987
  return transaction.source === identity.source
908
988
  && transaction.playerId === identity.playerId
909
- && transaction.idempotencyKey === identity.idempotencyKey
910
989
  && transaction.grantId === identity.grantId
911
- && transaction.payload.target === identity.target;
990
+ && transaction.payload.target === identity.target
991
+ && transaction.payload.deploymentTarget === identity.deploymentTarget;
912
992
  }
913
993
  function resolveEvidenceVerificationTimeout(timeoutMs) {
914
994
  const resolved = timeoutMs ?? 10000;
package/dist/types.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { ProductGrant } from '@mpgd/catalog';
2
2
  import type { LeaderboardScoreInput, LogicalAdPlacementId, LogicalProductId, PlatformEvidenceEnvelope, PlatformTarget } from '@mpgd/platform';
3
3
  export type { PlatformEvidenceEnvelope } from '@mpgd/platform';
4
- export type GameServicesStoreTarget = Extract<PlatformTarget, 'android' | 'ios' | 'ait'>;
4
+ export type GameServicesStoreTarget = Extract<PlatformTarget, 'microsoft-store' | 'android' | 'ios' | 'ait'>;
5
5
  export type GameServicesAdRewardTarget = Extract<PlatformTarget, 'android' | 'ios' | 'ait' | 'verse8'>;
6
- export type GameServicesLeaderboardTarget = Extract<PlatformTarget, 'browser' | 'android' | 'ios' | 'ait' | 'reddit'>;
7
- export type GameServicesLedgerTarget = Extract<PlatformTarget, 'browser' | 'android' | 'ios' | 'ait' | 'reddit' | 'verse8'>;
6
+ export type GameServicesLeaderboardTarget = Extract<PlatformTarget, 'browser' | 'microsoft-store' | 'android' | 'ios' | 'ait' | 'reddit'>;
7
+ export type GameServicesLedgerTarget = Extract<PlatformTarget, 'browser' | 'microsoft-store' | 'android' | 'ios' | 'ait' | 'reddit' | 'verse8'>;
8
8
  export type PurchaseIdempotencyKey = string;
9
9
  export type AdRewardIdempotencyKey = string;
10
10
  export type EntitlementIdempotencyKey = PurchaseIdempotencyKey | AdRewardIdempotencyKey;
@@ -12,6 +12,8 @@ export type EntitlementLedgerSource = 'purchase' | 'ad_reward' | 'admin';
12
12
  export type EntitlementLedgerPayload = Record<string, string | number | boolean>;
13
13
  export interface VerifyPurchaseRequest {
14
14
  readonly target: GameServicesStoreTarget;
15
+ /** Deployment config key used to resolve game-owned platform product identifiers. */
16
+ readonly deploymentTarget?: string;
15
17
  readonly playerId: string;
16
18
  readonly productId: LogicalProductId;
17
19
  readonly platformTransactionId: string;
@@ -35,6 +37,8 @@ export interface PurchaseGrantFinalization {
35
37
  }
36
38
  export interface ClaimAdRewardRequest {
37
39
  readonly target: GameServicesAdRewardTarget;
40
+ /** Deployment config key used to resolve game-owned platform placement identifiers. */
41
+ readonly deploymentTarget?: string;
38
42
  readonly playerId: string;
39
43
  readonly placementId: LogicalAdPlacementId;
40
44
  /** Platform evidence identity; AIT uses the game-issued reward correlation identifier. */
@@ -100,3 +104,4 @@ export declare function assertEntitlementLedgerGrant(input: EntitlementLedgerGra
100
104
  export declare function assertEntitlementLedgerResult(input: EntitlementLedgerResult): EntitlementLedgerResult;
101
105
  export declare function assertProductGrantTransaction(input: ProductGrantTransaction): ProductGrantTransaction;
102
106
  export declare function assertLeaderboardScoreTransaction(input: LeaderboardScoreTransaction): LeaderboardScoreTransaction;
107
+ export declare function assertGameServicesDeploymentTarget(input: unknown): asserts input is string;
package/dist/types.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export function assertVerifyPurchaseRequest(input) {
2
2
  assertRecord(input, 'VerifyPurchaseRequest');
3
3
  assertStoreTarget(input.target);
4
+ assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
4
5
  assertNonEmptyString(input.playerId, 'playerId');
5
6
  assertNonEmptyString(input.productId, 'productId');
6
7
  assertNonEmptyString(input.platformTransactionId, 'platformTransactionId');
@@ -44,6 +45,7 @@ export function assertPurchaseGrantFinalization(input) {
44
45
  export function assertClaimAdRewardRequest(input) {
45
46
  assertRecord(input, 'ClaimAdRewardRequest');
46
47
  assertAdRewardTarget(input.target);
48
+ assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
47
49
  assertNonEmptyString(input.playerId, 'playerId');
48
50
  assertNonEmptyString(input.placementId, 'placementId');
49
51
  assertOptionalNonEmptyString(input.platformImpressionId, 'platformImpressionId');
@@ -126,8 +128,11 @@ function assertRecord(input, label) {
126
128
  }
127
129
  }
128
130
  function assertStoreTarget(input) {
129
- if (input !== 'android' && input !== 'ios' && input !== 'ait') {
130
- throw new Error('target must be android, ios, or ait.');
131
+ if (input !== 'microsoft-store'
132
+ && input !== 'android'
133
+ && input !== 'ios'
134
+ && input !== 'ait') {
135
+ throw new Error('target must be microsoft-store, android, ios, or ait.');
131
136
  }
132
137
  }
133
138
  function assertAdRewardTarget(input) {
@@ -135,13 +140,42 @@ function assertAdRewardTarget(input) {
135
140
  throw new Error('target must be android, ios, ait, or verse8.');
136
141
  }
137
142
  }
143
+ export function assertGameServicesDeploymentTarget(input) {
144
+ if (typeof input !== 'string'
145
+ || input.length > 64
146
+ || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(input)
147
+ || reservedDeploymentTargetNames.has(input)) {
148
+ throw new Error('deploymentTarget must use lowercase kebab-case and avoid reserved names.');
149
+ }
150
+ }
151
+ function assertOptionalGameServicesDeploymentTarget(input) {
152
+ if (input === undefined) {
153
+ return;
154
+ }
155
+ assertGameServicesDeploymentTarget(input);
156
+ }
157
+ const reservedDeploymentTargetNames = new Set([
158
+ 'aux',
159
+ 'browser',
160
+ 'con',
161
+ 'devvit',
162
+ 'index',
163
+ 'msstore',
164
+ 'nul',
165
+ 'prn',
166
+ 'constructor',
167
+ 'prototype',
168
+ ...Array.from({ length: 9 }, (_, index) => `com${String(index + 1)}`),
169
+ ...Array.from({ length: 9 }, (_, index) => `lpt${String(index + 1)}`),
170
+ ]);
138
171
  function assertLeaderboardTarget(input) {
139
172
  if (input !== 'browser'
173
+ && input !== 'microsoft-store'
140
174
  && input !== 'android'
141
175
  && input !== 'ios'
142
176
  && input !== 'ait'
143
177
  && input !== 'reddit') {
144
- throw new Error('target must be browser, android, ios, ait, or reddit.');
178
+ throw new Error('target must be browser, microsoft-store, android, ios, ait, or reddit.');
145
179
  }
146
180
  }
147
181
  function assertNonEmptyString(input, label) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/game-services",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -73,6 +73,10 @@
73
73
  "types": "./dist/google-play-purchase.d.ts",
74
74
  "default": "./dist/google-play-purchase.js"
75
75
  },
76
+ "./microsoft-store-purchase": {
77
+ "types": "./dist/microsoft-store-purchase.d.ts",
78
+ "default": "./dist/microsoft-store-purchase.js"
79
+ },
76
80
  "./google-play-purchase-conformance": {
77
81
  "types": "./dist/google-play-purchase-conformance.d.ts",
78
82
  "default": "./dist/google-play-purchase-conformance.js"
@@ -126,9 +130,9 @@
126
130
  "@orpc/client": "2.0.0-beta.14",
127
131
  "@orpc/contract": "2.0.0-beta.14",
128
132
  "@orpc/server": "2.0.0-beta.14",
129
- "@mpgd/catalog": "0.5.1",
130
- "@mpgd/analytics": "0.3.8",
131
- "@mpgd/platform": "0.8.0"
133
+ "@mpgd/catalog": "0.6.0",
134
+ "@mpgd/platform": "0.9.0",
135
+ "@mpgd/analytics": "0.3.9"
132
136
  },
133
137
  "devDependencies": {
134
138
  "ttsc": "0.18.4",
@@ -144,6 +148,6 @@
144
148
  "lint": "ttsc --noEmit",
145
149
  "format": "ttsc format",
146
150
  "fix": "ttsc fix",
147
- "test": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime.test.ts && node tools/run-ttsx.mjs packages/game-services/src/types.test.ts && node tools/run-ttsx.mjs packages/game-services/src/server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/platform-order.test.ts && node tools/run-ttsx.mjs packages/game-services/src/progress-link.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notification-delivery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/admob-ssv.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-verifier.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-evidence-verification-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-partner-api.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-durability-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-transport.test.ts"
151
+ "test": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime.test.ts && node tools/run-ttsx.mjs packages/game-services/src/types.test.ts && node tools/run-ttsx.mjs packages/game-services/src/server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/microsoft-store-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/platform-order.test.ts && node tools/run-ttsx.mjs packages/game-services/src/progress-link.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notification-delivery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/admob-ssv.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-verifier.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-evidence-verification-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-partner-api.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-durability-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-transport.test.ts"
148
152
  }
149
153
  }