@mpgd/game-services 0.30.5 → 0.32.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/README.md +101 -0
- package/dist/another-account.d.ts +65 -0
- package/dist/another-account.js +107 -0
- package/dist/app-store-verifier.js +24 -5
- package/dist/client.d.ts +16 -2
- package/dist/client.js +33 -5
- package/dist/commerce-reconciler.d.ts +89 -4
- package/dist/commerce-reconciler.js +467 -35
- package/dist/contract.d.ts +6 -6
- package/dist/evidence-verification.d.ts +14 -2
- package/dist/google-play-purchase.js +22 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/monetization-recovery.js +1 -1
- package/dist/orpc-error-compat.d.ts +42 -0
- package/dist/orpc-error-compat.js +102 -0
- package/dist/request-headers.d.ts +22 -0
- package/dist/request-headers.js +34 -1
- package/dist/runtime.d.ts +82 -2
- package/dist/runtime.js +363 -10
- package/dist/server.d.ts +31 -7
- package/dist/server.js +125 -15
- package/package.json +9 -9
package/dist/server.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
|
|
1
2
|
import { implement, ORPCError } from '@orpc/server';
|
|
2
|
-
import {
|
|
3
|
+
import { RPCHandler } from '@orpc/server/fetch';
|
|
4
|
+
import { RequestLimitHandlerPlugin } from '@orpc/server/plugins';
|
|
3
5
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
6
|
import { resolveAdPlacementPlatformId, resolveCatalogCurrencies, resolveProductPlatformId, } from '@mpgd/catalog';
|
|
5
7
|
import { gameServicesBackendEndpoints, } from './client.js';
|
|
6
8
|
import { resolveCommerceStoreId, } from './commerce-evidence-registry.js';
|
|
7
9
|
import { createPurchaseHolderEvidenceVerificationId, findEntitlementRevocationsChunked, findPurchaseWideRevocation, holderRevocationEvidenceId, isEntitlementGrantReversed, isRestoredAcrossPlayers, listPurchaseHolders, readPurchaseLedgerStatuses, readPurchaseVerificationId, RevocationKeyConflictError, revokeEntitlementGrant, } from './commerce-ledger.js';
|
|
8
10
|
import { assertPlayerDataErasureInput, createRetainedLedgerEntryId, detachRetainedLedgerPayload, isRetainedPlayerId, } from './account-deletion.js';
|
|
11
|
+
import { createPurchaseBoundToAnotherAccountBody, GameServicesPurchaseBoundToAnotherAccountError, isStoreAccountMismatchReason, purchaseBoundToAnotherAccountCode, purchaseBoundToAnotherAccountStatus, } from './another-account.js';
|
|
9
12
|
import { gameServicesContract } from './contract.js';
|
|
13
|
+
import { addLegacyOrpcErrorFlag } from './orpc-error-compat.js';
|
|
10
14
|
import { createRejectingGameServicesEvidenceVerifier, } from './evidence-verification.js';
|
|
11
15
|
import { assertClaimAdRewardRequest, assertClaimAdRewardResponse, assertEntitlementLedgerGrant, assertEntitlementLedgerNote, assertEntitlementLedgerResult, assertEntitlementLedgerRevocation, assertEntitlementRevocationTransaction, assertGameServicesDeploymentTarget, assertLeaderboardScoreTransaction, assertProductGrantTransaction, assertPurchaseGrantFinalization, assertRecordLeaderboardScoreRequest, assertRecordLeaderboardScoreResponse, assertVerifyPurchaseRequest, assertVerifyPurchaseResponse, } from './types.js';
|
|
12
16
|
import { resolvePurchaseRestoreSharingPolicy, } from './purchase-sharing.js';
|
|
@@ -405,6 +409,23 @@ export function createGameServicesBackend(input) {
|
|
|
405
409
|
: { purchaseGrantFinalizer }),
|
|
406
410
|
...(registry === undefined ? {} : { registry }),
|
|
407
411
|
}, async (verification) => {
|
|
412
|
+
if (verification instanceof GameServicesPurchaseBoundToAnotherAccountError) {
|
|
413
|
+
// Refused for this player, not decided for its account: tracked
|
|
414
|
+
// as a refusal with the mark, never as a pending purchase.
|
|
415
|
+
await analytics.track({
|
|
416
|
+
name: 'purchase_rejected',
|
|
417
|
+
properties: {
|
|
418
|
+
target: request.target,
|
|
419
|
+
store: resolveCommerceStoreId(request),
|
|
420
|
+
playerId: request.playerId,
|
|
421
|
+
productId: request.productId,
|
|
422
|
+
alreadyProcessed: false,
|
|
423
|
+
reason: verification.code,
|
|
424
|
+
boundToAnotherAccount: verification.reason,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
408
429
|
await analytics.track({
|
|
409
430
|
name: verification.verified ? 'purchase_granted'
|
|
410
431
|
: verification.disposition === 'pending' ? 'purchase_pending' : 'purchase_rejected',
|
|
@@ -519,6 +540,12 @@ export function createGameServicesBackendApiHandler(input) {
|
|
|
519
540
|
if (error instanceof GameServicesRequestValidationError) {
|
|
520
541
|
return errorResponse(400, 'INVALID_REQUEST', error.message);
|
|
521
542
|
}
|
|
543
|
+
if (error instanceof GameServicesPurchaseBoundToAnotherAccountError) {
|
|
544
|
+
return {
|
|
545
|
+
status: purchaseBoundToAnotherAccountStatus,
|
|
546
|
+
body: createPurchaseBoundToAnotherAccountBody(error.reason),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
522
549
|
reportInternalError(input.onInternalError, error);
|
|
523
550
|
return errorResponse(500, 'INTERNAL_ERROR');
|
|
524
551
|
}
|
|
@@ -571,6 +598,15 @@ export function createGameServicesRouter(backend, options = {}) {
|
|
|
571
598
|
if (error instanceof GameServicesRequestValidationError) {
|
|
572
599
|
throw new ORPCError('BAD_REQUEST', { message: error.message });
|
|
573
600
|
}
|
|
601
|
+
if (error instanceof GameServicesPurchaseBoundToAnotherAccountError) {
|
|
602
|
+
// A retryable answer, not an internal error: the code and reason reach the client.
|
|
603
|
+
// oRPC beta.46 errors carry no status: `gameServicesRpcErrorStatusMap`
|
|
604
|
+
// (used by `createGameServicesRpcFetchHandler`) maps the code to 409.
|
|
605
|
+
throw new ORPCError(purchaseBoundToAnotherAccountCode, {
|
|
606
|
+
message: error.message,
|
|
607
|
+
data: { reason: error.reason },
|
|
608
|
+
});
|
|
609
|
+
}
|
|
574
610
|
reportInternalError(options.onInternalError, error);
|
|
575
611
|
throw error;
|
|
576
612
|
}
|
|
@@ -610,16 +646,31 @@ export function createGameServicesRouter(backend, options = {}) {
|
|
|
610
646
|
}),
|
|
611
647
|
});
|
|
612
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* The RPC handler's error status map: oRPC's common codes and
|
|
651
|
+
* `PURCHASE_BOUND_TO_ANOTHER_ACCOUNT` at 409. oRPC 2.0.0-beta.46 derives the
|
|
652
|
+
* HTTP status of an error from its code through this map (an `ORPCError`
|
|
653
|
+
* carries no status of its own), so a host that mounts
|
|
654
|
+
* `createGameServicesRouter` in its own `RPCHandler` passes it as
|
|
655
|
+
* `errorStatusMap` to answer 409 too; without it the code still reaches the
|
|
656
|
+
* client, with status 500.
|
|
657
|
+
*/
|
|
658
|
+
export const gameServicesRpcErrorStatusMap = Object.freeze({
|
|
659
|
+
...COMMON_ERROR_STATUS_MAP,
|
|
660
|
+
[purchaseBoundToAnotherAccountCode]: purchaseBoundToAnotherAccountStatus,
|
|
661
|
+
});
|
|
613
662
|
export function createGameServicesRpcFetchHandler(router, options = {}) {
|
|
614
663
|
const rpcHandler = new RPCHandler(router, {
|
|
664
|
+
errorStatusMap: { ...gameServicesRpcErrorStatusMap },
|
|
615
665
|
plugins: [
|
|
616
|
-
new
|
|
666
|
+
new RequestLimitHandlerPlugin({
|
|
617
667
|
maxBodySize: resolveMaxBodyBytes(options.maxBodyBytes),
|
|
618
668
|
}),
|
|
619
669
|
],
|
|
620
670
|
});
|
|
621
671
|
const prefix = options.prefix ?? '/rpc';
|
|
622
672
|
const healthPath = options.healthPath ?? '/health';
|
|
673
|
+
const legacyOrpcErrorFlag = options.legacyOrpcErrorFlag ?? true;
|
|
623
674
|
return async (request) => {
|
|
624
675
|
const requestUrl = new URL(request.url);
|
|
625
676
|
if (requestUrl.pathname === healthPath) {
|
|
@@ -637,7 +688,11 @@ export function createGameServicesRpcFetchHandler(router, options = {}) {
|
|
|
637
688
|
if (!result.matched) {
|
|
638
689
|
return textResponse('Not Found', 404, options.corsHeaders);
|
|
639
690
|
}
|
|
640
|
-
|
|
691
|
+
// Error bodies keep the pre-beta.34 `inferable` flag so older game clients decode them.
|
|
692
|
+
const response = legacyOrpcErrorFlag
|
|
693
|
+
? await addLegacyOrpcErrorFlag(result.response)
|
|
694
|
+
: result.response;
|
|
695
|
+
return withCors(response, options.corsHeaders);
|
|
641
696
|
};
|
|
642
697
|
}
|
|
643
698
|
export function createGameServicesHttpFetchHandler(handler, options = {}) {
|
|
@@ -780,7 +835,21 @@ async function verifyPurchaseWithStore(input, context, onDecision) {
|
|
|
780
835
|
: { deploymentTarget: request.deploymentTarget }),
|
|
781
836
|
};
|
|
782
837
|
return withEntitlementRetryLock(retryIdentity, async () => {
|
|
783
|
-
|
|
838
|
+
let verification;
|
|
839
|
+
try {
|
|
840
|
+
verification = await verifyPurchaseForRetry(request, retryIdentity, context);
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
if (error instanceof GameServicesPurchaseBoundToAnotherAccountError) {
|
|
844
|
+
try {
|
|
845
|
+
await onDecision(error);
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
// Analytics is best-effort: a failed report never replaces the answer.
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
throw error;
|
|
852
|
+
}
|
|
784
853
|
await onDecision(verification);
|
|
785
854
|
return verification;
|
|
786
855
|
});
|
|
@@ -855,18 +924,27 @@ async function verifyPurchaseForRetry(request, retryIdentity, context) {
|
|
|
855
924
|
? routedEntry.entry.store
|
|
856
925
|
: resolveCommerceStoreId(request);
|
|
857
926
|
if (verification.status === 'restorable') {
|
|
858
|
-
// A verified restore of a purchase the store binds to another player.
|
|
927
|
+
// A verified restore of a purchase the store binds to another player, or to none.
|
|
859
928
|
return shareRestoredPurchase(request, {
|
|
860
929
|
product,
|
|
861
930
|
platformProductId,
|
|
862
931
|
verification,
|
|
863
932
|
verifiedStore,
|
|
864
|
-
refusal:
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
933
|
+
refusal: () => {
|
|
934
|
+
if (isStoreAccountMismatchReason(verification.reason)) {
|
|
935
|
+
// No share for this player: the purchase stays its account's,
|
|
936
|
+
// which can still restore it (RFC "Another account's purchase").
|
|
937
|
+
throw new GameServicesPurchaseBoundToAnotherAccountError(verification.reason);
|
|
938
|
+
}
|
|
939
|
+
// Bound to no account (`*_MISSING`) or a custom verifier's reason:
|
|
940
|
+
// no account can claim it, so the refusal is final.
|
|
941
|
+
return assertVerifyPurchaseResponse({
|
|
942
|
+
verified: false,
|
|
943
|
+
alreadyProcessed: false,
|
|
944
|
+
disposition: 'rejected',
|
|
945
|
+
reason: verification.reason,
|
|
946
|
+
});
|
|
947
|
+
},
|
|
870
948
|
}, context);
|
|
871
949
|
}
|
|
872
950
|
if (verification.status !== 'verified') {
|
|
@@ -876,6 +954,12 @@ async function verifyPurchaseForRetry(request, retryIdentity, context) {
|
|
|
876
954
|
if (racedExisting !== undefined) {
|
|
877
955
|
return completeExistingRetry(racedExisting);
|
|
878
956
|
}
|
|
957
|
+
if (verification.status === 'rejected' && verification.anotherAccount === true) {
|
|
958
|
+
// Another account's valid purchase: nothing is granted or recorded, and
|
|
959
|
+
// the answer is a retryable error, never a `rejected` receipt that would
|
|
960
|
+
// end the client's record and block its account's recovery.
|
|
961
|
+
throw new GameServicesPurchaseBoundToAnotherAccountError(verification.reason);
|
|
962
|
+
}
|
|
879
963
|
return assertVerifyPurchaseResponse({
|
|
880
964
|
verified: false,
|
|
881
965
|
alreadyProcessed: false,
|
|
@@ -985,6 +1069,22 @@ async function verifyPurchaseForRetry(request, retryIdentity, context) {
|
|
|
985
1069
|
finalization,
|
|
986
1070
|
});
|
|
987
1071
|
}
|
|
1072
|
+
if (product.type === 'consumable'
|
|
1073
|
+
&& routedEntry?.status === 'routed'
|
|
1074
|
+
&& routedEntry.entry.finish === 'client') {
|
|
1075
|
+
// This player's own grant of a consumable the client finishes
|
|
1076
|
+
// (StoreKit): the store still lists the transaction, so its finish
|
|
1077
|
+
// never happened or never reached the client's journal, and the
|
|
1078
|
+
// client's record of it is gone (journal retention), so it asks
|
|
1079
|
+
// under the derived key. The recorded grant is reported, never a
|
|
1080
|
+
// second one, so the client can finish the transaction instead of
|
|
1081
|
+
// the store re-delivering it on every launch.
|
|
1082
|
+
return assertVerifyPurchaseResponse({
|
|
1083
|
+
verified: true,
|
|
1084
|
+
ledgerEntryId: existingEvidence.ledgerEntryId,
|
|
1085
|
+
alreadyProcessed: true,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
988
1088
|
}
|
|
989
1089
|
return assertVerifyPurchaseResponse({
|
|
990
1090
|
verified: false,
|
|
@@ -1088,6 +1188,15 @@ function createPurchaseGrantPayload(request, input) {
|
|
|
1088
1188
|
evidenceVerifiedAt: input.verification.verifiedAt,
|
|
1089
1189
|
};
|
|
1090
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Whether the `share` policy applies: a non-consumable, asked as a restore
|
|
1193
|
+
* unless the store binds it to this player.
|
|
1194
|
+
*/
|
|
1195
|
+
function sharesRestoredPurchase(request, product, context, bindingMatches = false) {
|
|
1196
|
+
return context.restoreSharing.mode === 'share'
|
|
1197
|
+
&& (request.intent === 'restore' || bindingMatches)
|
|
1198
|
+
&& product.type === 'non_consumable';
|
|
1199
|
+
}
|
|
1091
1200
|
/**
|
|
1092
1201
|
* A verified restore of a non-consumable the store binds to another player
|
|
1093
1202
|
* (RFC "Restore sharing"). Under the `share` policy, for a request with
|
|
@@ -1102,10 +1211,8 @@ function createPurchaseGrantPayload(request, input) {
|
|
|
1102
1211
|
async function shareRestoredPurchase(request, input, context) {
|
|
1103
1212
|
const { store, restoreSharing: policy } = context;
|
|
1104
1213
|
const { product, verification } = input;
|
|
1105
|
-
if (
|
|
1106
|
-
|
|
1107
|
-
|| product.type !== 'non_consumable') {
|
|
1108
|
-
return input.refusal;
|
|
1214
|
+
if (!sharesRestoredPurchase(request, product, context, input.bindingMatches === true)) {
|
|
1215
|
+
return typeof input.refusal === 'function' ? input.refusal() : input.refusal;
|
|
1109
1216
|
}
|
|
1110
1217
|
const purchaseVerificationId = verification.verificationId;
|
|
1111
1218
|
const notGranted = (disposition, reason) => ({
|
|
@@ -1717,6 +1824,9 @@ function assertEvidenceVerificationDecision(decision) {
|
|
|
1717
1824
|
}
|
|
1718
1825
|
if (decision.status === 'rejected') {
|
|
1719
1826
|
assertNonEmptyDecisionString(decision.reason, 'reason');
|
|
1827
|
+
if (decision.anotherAccount !== undefined && decision.anotherAccount !== true) {
|
|
1828
|
+
throw new Error('Evidence verification anotherAccount must be true when present.');
|
|
1829
|
+
}
|
|
1720
1830
|
return decision;
|
|
1721
1831
|
}
|
|
1722
1832
|
throw new Error('Unknown evidence verification status.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/game-services",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -228,12 +228,12 @@
|
|
|
228
228
|
"dependencies": {
|
|
229
229
|
"@ait-kit/api-core": "0.6.0",
|
|
230
230
|
"@noble/secp256k1": "3.1.0",
|
|
231
|
-
"@orpc/client": "2.0.0-beta.
|
|
232
|
-
"@orpc/contract": "2.0.0-beta.
|
|
233
|
-
"@orpc/server": "2.0.0-beta.
|
|
234
|
-
"@mpgd/analytics": "0.5.
|
|
235
|
-
"@mpgd/platform": "0.
|
|
236
|
-
"@mpgd/catalog": "0.
|
|
231
|
+
"@orpc/client": "2.0.0-beta.46",
|
|
232
|
+
"@orpc/contract": "2.0.0-beta.46",
|
|
233
|
+
"@orpc/server": "2.0.0-beta.46",
|
|
234
|
+
"@mpgd/analytics": "0.5.7",
|
|
235
|
+
"@mpgd/platform": "0.34.0",
|
|
236
|
+
"@mpgd/catalog": "0.14.1"
|
|
237
237
|
},
|
|
238
238
|
"peerDependencies": {
|
|
239
239
|
"@apple/app-store-server-library": "^3.1.0"
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
"devDependencies": {
|
|
247
247
|
"@apple/app-store-server-library": "3.1.0",
|
|
248
248
|
"@opentelemetry/api": "1.9.0",
|
|
249
|
-
"ttsc": "0.
|
|
249
|
+
"ttsc": "0.34.2",
|
|
250
250
|
"typescript": "7.0.2",
|
|
251
251
|
"vitest": "^4.0.0"
|
|
252
252
|
},
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
"fix": "ttsc fix",
|
|
263
263
|
"build": "cd ../.. && node tools/run-ttsx.mjs tools/package/build-packages.ts @mpgd/game-services",
|
|
264
264
|
"test:packed-transport": "pnpm build && node test/runtime-transport-package-import.mjs && node test/reward-evidence-import.mjs",
|
|
265
|
-
"test:unit": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-progress.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/google-play-publisher-client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-store-server.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/microsoft-store-identity.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/notifications/service.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/service-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/client-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/scheduler.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/fcm.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/store-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/ait-functional-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/app-store-server-notifications.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 && node tools/run-ttsx.mjs packages/game-services/src/guest-session.test.ts && node tools/run-ttsx.mjs packages/game-services/src/development-session.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-reward-evidence.test.ts && node tools/run-ttsx.mjs packages/game-services/src/ad-reward-verifier-registry.test.ts && node tools/run-ttsx.mjs packages/game-services/src/oneplay-reward.test.ts && node tools/run-ttsx.mjs packages/game-services/src/oneplay-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-registry-routing.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-registry-server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-ledger-revocation.test.ts && node tools/run-ttsx.mjs packages/game-services/src/purchase-sharing.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-anonymous-auth.test.ts && node tools/run-ttsx.mjs packages/game-services/src/account-deletion.test.ts",
|
|
265
|
+
"test:unit": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-progress.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime-session.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/orpc-error-compat.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/google-play-publisher-client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-store-server.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/microsoft-store-identity.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/notifications/service.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/service-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/client-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/scheduler.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/fcm.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/store-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notifications/ait-functional-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/app-store-server-notifications.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 && node tools/run-ttsx.mjs packages/game-services/src/guest-session.test.ts && node tools/run-ttsx.mjs packages/game-services/src/development-session.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-reward-evidence.test.ts && node tools/run-ttsx.mjs packages/game-services/src/ad-reward-verifier-registry.test.ts && node tools/run-ttsx.mjs packages/game-services/src/oneplay-reward.test.ts && node tools/run-ttsx.mjs packages/game-services/src/oneplay-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-registry-routing.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-registry-server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-ledger-revocation.test.ts && node tools/run-ttsx.mjs packages/game-services/src/purchase-sharing.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-anonymous-auth.test.ts && node tools/run-ttsx.mjs packages/game-services/src/account-deletion.test.ts",
|
|
266
266
|
"test:recovery": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/monetization-recovery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-recovery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-reconciler.test.ts && node tools/run-ttsx.mjs packages/game-services/src/commerce-reconciler-conformance.test.ts",
|
|
267
267
|
"test": "pnpm test:unit && pnpm test:app-store-server && pnpm test:recovery && pnpm test:packed-transport",
|
|
268
268
|
"test:app-store-server": "vitest run"
|