@mpgd/game-services 0.19.0 → 0.20.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 +16 -0
- package/dist/app-store-server.d.ts +12 -0
- package/dist/app-store-server.js +119 -0
- package/dist/google-play-publisher-client.js +4 -2
- package/dist/google-play-purchase.d.ts +9 -2
- package/dist/google-play-purchase.js +12 -14
- package/dist/oneplay-purchase-client.js +4 -2
- package/dist/server.js +22 -1
- package/dist/types.js +5 -2
- package/package.json +21 -6
package/README.md
CHANGED
|
@@ -4,6 +4,22 @@ Client, contract, backend, and store helpers for authoritative game services.
|
|
|
4
4
|
See [backend integration](../../docs/GAME_SERVICES_BACKEND.md) for the existing
|
|
5
5
|
verification and ledger boundaries.
|
|
6
6
|
|
|
7
|
+
## App Store server-side transaction signatures
|
|
8
|
+
|
|
9
|
+
`@mpgd/game-services/app-store-server` exports
|
|
10
|
+
`createAppleSignedTransactionVerifier`, an `AppStoreSignedTransactionVerifier`
|
|
11
|
+
over the optional `@apple/app-store-server-library@3.1.0` peer. It verifies the
|
|
12
|
+
App Store JWS certificate chain and signature before exposing any decoded
|
|
13
|
+
one-time purchase field, keeps Apple's retryable certificate-status failures
|
|
14
|
+
pending (`AppStoreDependencyUnavailableError`), and rejects invalid signatures,
|
|
15
|
+
subscription payloads, and oversized transactions. Supply DER-encoded Apple
|
|
16
|
+
root certificates, the bundle ID, environment, and production App Apple ID,
|
|
17
|
+
and pass the result as `signedTransactionVerifier` to
|
|
18
|
+
`createAppStoreGameServicesEvidenceVerifier`. Backend only: the peer is not
|
|
19
|
+
installed by game clients, and no other subpath imports it. It moved here from
|
|
20
|
+
`@mpgd/adapter-capacitor/app-store-server`, which stays as a deprecated
|
|
21
|
+
re-export for one release.
|
|
22
|
+
|
|
7
23
|
## Registered rewarded-ad evidence
|
|
8
24
|
|
|
9
25
|
`createGameServicesClient`, `createRecoverableMonetizationClient`, and
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AppStoreEnvironment, type AppStoreSignedTransactionVerifier } from './app-store-verifier.js';
|
|
2
|
+
export interface CreateAppleSignedTransactionVerifierOptions {
|
|
3
|
+
/** DER-encoded Apple root certificates, supplied by the server operator. */
|
|
4
|
+
readonly rootCertificates: readonly Uint8Array[];
|
|
5
|
+
readonly environment: AppStoreEnvironment;
|
|
6
|
+
readonly bundleId: string;
|
|
7
|
+
/** Required by Apple's verifier in production. */
|
|
8
|
+
readonly appAppleId?: number;
|
|
9
|
+
readonly enableOnlineChecks?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Verify the App Store JWS before exposing any decoded purchase fields. */
|
|
12
|
+
export declare function createAppleSignedTransactionVerifier(options: CreateAppleSignedTransactionVerifierOptions): AppStoreSignedTransactionVerifier;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Environment, SignedDataVerifier, VerificationException, VerificationStatus, } from '@apple/app-store-server-library';
|
|
2
|
+
import { AppStoreDependencyUnavailableError, } from './app-store-verifier.js';
|
|
3
|
+
/** Verify the App Store JWS before exposing any decoded purchase fields. */
|
|
4
|
+
export function createAppleSignedTransactionVerifier(options) {
|
|
5
|
+
if (!Array.isArray(options.rootCertificates)
|
|
6
|
+
|| options.rootCertificates.length === 0
|
|
7
|
+
|| options.rootCertificates.some((certificate) => !(certificate instanceof Uint8Array)
|
|
8
|
+
|| certificate.length === 0)) {
|
|
9
|
+
throw new TypeError('Apple root certificates are required.');
|
|
10
|
+
}
|
|
11
|
+
if (options.environment !== 'Production' && options.environment !== 'Sandbox') {
|
|
12
|
+
throw new TypeError('App Store environment must be Production or Sandbox.');
|
|
13
|
+
}
|
|
14
|
+
if (options.bundleId.trim() === '') {
|
|
15
|
+
throw new TypeError('App Store bundle ID is required.');
|
|
16
|
+
}
|
|
17
|
+
if (options.environment === 'Production'
|
|
18
|
+
&& (!Number.isSafeInteger(options.appAppleId) || (options.appAppleId ?? 0) <= 0)) {
|
|
19
|
+
throw new TypeError('Production App Store verification requires appAppleId.');
|
|
20
|
+
}
|
|
21
|
+
if (options.environment === 'Production' && options.enableOnlineChecks === false) {
|
|
22
|
+
throw new TypeError('Production App Store verification requires online certificate checks.');
|
|
23
|
+
}
|
|
24
|
+
const verifier = new SignedDataVerifier(options.rootCertificates.map((certificate) => Buffer.from(certificate)), options.enableOnlineChecks ?? true, options.environment === 'Production' ? Environment.PRODUCTION : Environment.SANDBOX, options.bundleId, options.appAppleId);
|
|
25
|
+
return {
|
|
26
|
+
async verifyAndDecode(input) {
|
|
27
|
+
if (input.environment !== options.environment || input.bundleId !== options.bundleId) {
|
|
28
|
+
return { status: 'rejected', reason: 'APP_STORE_VERIFIER_SCOPE_MISMATCH' };
|
|
29
|
+
}
|
|
30
|
+
if (typeof input.signedTransaction !== 'string'
|
|
31
|
+
|| input.signedTransaction.length === 0
|
|
32
|
+
|| input.signedTransaction.length > 512 * 1024) {
|
|
33
|
+
return { status: 'rejected', reason: 'APP_STORE_SIGNED_TRANSACTION_INVALID' };
|
|
34
|
+
}
|
|
35
|
+
input.signal.throwIfAborted();
|
|
36
|
+
let decoded;
|
|
37
|
+
try {
|
|
38
|
+
decoded = await waitForVerification(verifier.verifyAndDecodeTransaction(input.signedTransaction), input.signal);
|
|
39
|
+
input.signal.throwIfAborted();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (input.signal.aborted) {
|
|
43
|
+
throw input.signal.reason;
|
|
44
|
+
}
|
|
45
|
+
if (error instanceof VerificationException) {
|
|
46
|
+
if (error.status === VerificationStatus.RETRYABLE_VERIFICATION_FAILURE) {
|
|
47
|
+
throw new AppStoreDependencyUnavailableError('Apple signature checks are temporarily unavailable.', error);
|
|
48
|
+
}
|
|
49
|
+
return { status: 'rejected', reason: 'APP_STORE_SIGNATURE_INVALID' };
|
|
50
|
+
}
|
|
51
|
+
// Only Apple's explicit retryable status is a transient dependency
|
|
52
|
+
// failure. Unexpected adapter/library faults must reach the caller.
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const payload = decodeOneTimeTransaction(decoded);
|
|
56
|
+
return payload === undefined
|
|
57
|
+
? { status: 'rejected', reason: 'APP_STORE_SIGNED_TRANSACTION_INVALID' }
|
|
58
|
+
: { status: 'verified', payload };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function waitForVerification(operation, signal) {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
let onAbort;
|
|
65
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
66
|
+
onAbort = () => reject(signal.reason ?? new Error('Verification was aborted.'));
|
|
67
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
68
|
+
if (signal.aborted) {
|
|
69
|
+
onAbort();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
return await Promise.race([operation, aborted]);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
if (onAbort !== undefined) {
|
|
77
|
+
signal.removeEventListener('abort', onAbort);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function decodeOneTimeTransaction(decoded) {
|
|
82
|
+
if (!nonEmpty(decoded.transactionId)
|
|
83
|
+
|| !nonEmpty(decoded.originalTransactionId)
|
|
84
|
+
|| !nonEmpty(decoded.bundleId)
|
|
85
|
+
|| !nonEmpty(decoded.productId)
|
|
86
|
+
|| !validTimestamp(decoded.purchaseDate)
|
|
87
|
+
|| !validTimestamp(decoded.signedDate)
|
|
88
|
+
|| (decoded.environment !== 'Production' && decoded.environment !== 'Sandbox')
|
|
89
|
+
|| (decoded.type !== 'Consumable' && decoded.type !== 'Non-Consumable')
|
|
90
|
+
|| (decoded.quantity !== undefined
|
|
91
|
+
&& (!Number.isSafeInteger(decoded.quantity) || decoded.quantity <= 0))
|
|
92
|
+
|| (decoded.appAccountToken !== undefined && !nonEmpty(decoded.appAccountToken))
|
|
93
|
+
|| (decoded.revocationDate !== undefined && !validTimestamp(decoded.revocationDate))
|
|
94
|
+
|| (decoded.expiresDate !== undefined && !validTimestamp(decoded.expiresDate))
|
|
95
|
+
|| (decoded.isUpgraded !== undefined && typeof decoded.isUpgraded !== 'boolean')) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
transactionId: decoded.transactionId,
|
|
100
|
+
originalTransactionId: decoded.originalTransactionId,
|
|
101
|
+
bundleId: decoded.bundleId,
|
|
102
|
+
productId: decoded.productId,
|
|
103
|
+
purchaseDate: decoded.purchaseDate,
|
|
104
|
+
signedDate: decoded.signedDate,
|
|
105
|
+
environment: decoded.environment,
|
|
106
|
+
type: decoded.type,
|
|
107
|
+
...(decoded.quantity === undefined ? {} : { quantity: decoded.quantity }),
|
|
108
|
+
...(decoded.appAccountToken === undefined ? {} : { appAccountToken: decoded.appAccountToken }),
|
|
109
|
+
...(decoded.revocationDate === undefined ? {} : { revocationDate: decoded.revocationDate }),
|
|
110
|
+
...(decoded.expiresDate === undefined ? {} : { expiresDate: decoded.expiresDate }),
|
|
111
|
+
...(decoded.isUpgraded === undefined ? {} : { isUpgraded: decoded.isUpgraded }),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function nonEmpty(value) {
|
|
115
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
116
|
+
}
|
|
117
|
+
function validTimestamp(value) {
|
|
118
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
119
|
+
}
|
|
@@ -15,7 +15,8 @@ export function createGooglePlayPublisherClient(options) {
|
|
|
15
15
|
response = await requestFetch(`${publisherOrigin}${path}`, {
|
|
16
16
|
method,
|
|
17
17
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
18
|
-
redirect: 'error'
|
|
18
|
+
// Workers reject redirect: 'error'; a manual 3xx/opaque redirect is never ok and fails below.
|
|
19
|
+
redirect: 'manual',
|
|
19
20
|
signal,
|
|
20
21
|
});
|
|
21
22
|
}
|
|
@@ -26,7 +27,8 @@ export function createGooglePlayPublisherClient(options) {
|
|
|
26
27
|
// Fetch errors can contain the request URL (including the purchase token).
|
|
27
28
|
throw new Error('Google Play Publisher request failed.');
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
// Also reject a redirect followed by an injected fetch that ignores redirect: 'manual'.
|
|
31
|
+
if (!response.ok || response.redirected) {
|
|
30
32
|
await discardBody(response);
|
|
31
33
|
// Do not forward an error body or URL: both can contain purchase credentials.
|
|
32
34
|
throw new Error(`Google Play Publisher request failed (HTTP ${response.status}).`);
|
|
@@ -38,5 +38,12 @@ export interface GooglePlayProductPurchaseBoundary extends GameServicesPurchaseG
|
|
|
38
38
|
verifyPurchase(input: VerifyPurchaseEvidenceInput): ReturnType<GameServicesEvidenceVerifier['verifyPurchase']>;
|
|
39
39
|
}
|
|
40
40
|
export declare function createGooglePlayProductPurchaseBoundary(input: CreateGooglePlayProductPurchaseBoundaryInput): GooglePlayProductPurchaseBoundary;
|
|
41
|
-
/**
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* A non-secret transaction ID for purchases whose Google order ID is absent or
|
|
43
|
+
* delayed: `play-token-sha256:<hex>` of the purchase token.
|
|
44
|
+
*
|
|
45
|
+
* @deprecated Use `createGooglePlayTokenPurchaseKey` from `@mpgd/platform/commerce`;
|
|
46
|
+
* this alias is byte-for-byte the same helper (same key, same `TypeError` for
|
|
47
|
+
* an invalid token) and will be removed in the next major version.
|
|
48
|
+
*/
|
|
49
|
+
export declare const createGooglePlayTokenTransactionId: (purchaseToken: string) => Promise<string>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createGooglePlayTokenPurchaseKey } from '@mpgd/platform/commerce';
|
|
1
2
|
export const googlePlayProductPurchaseEvidenceSchema = 'google-play.product-purchase.v2';
|
|
2
3
|
export function createGooglePlayProductPurchaseBoundary(input) {
|
|
3
4
|
const packageName = assertIdentifier(input.packageName, 'packageName', 256);
|
|
@@ -45,7 +46,7 @@ export function createGooglePlayProductPurchaseBoundary(input) {
|
|
|
45
46
|
purchaseToken,
|
|
46
47
|
expectedProductId: verificationInput.platformProductId,
|
|
47
48
|
expectedProductType: verificationInput.product.type,
|
|
48
|
-
orderMatch: createGooglePlayObservedOrderMatch(verificationInput.request.platformTransactionId, await
|
|
49
|
+
orderMatch: createGooglePlayObservedOrderMatch(verificationInput.request.platformTransactionId, await createGooglePlayTokenPurchaseKey(purchaseToken)),
|
|
49
50
|
accountBinding,
|
|
50
51
|
allowConsumed: false,
|
|
51
52
|
signal: verificationInput.signal,
|
|
@@ -143,7 +144,7 @@ async function finalizeGooglePlayPurchase(input, packageName, purchaseToken, pro
|
|
|
143
144
|
purchaseToken,
|
|
144
145
|
expectedProductId: finalizationInput.platformProductId,
|
|
145
146
|
expectedProductType: productType,
|
|
146
|
-
orderMatch: createGooglePlayFinalizationOrderMatch(verifiedContext, finalizationInput.request.platformTransactionId, await
|
|
147
|
+
orderMatch: createGooglePlayFinalizationOrderMatch(verifiedContext, finalizationInput.request.platformTransactionId, await createGooglePlayTokenPurchaseKey(purchaseToken)),
|
|
147
148
|
accountBinding,
|
|
148
149
|
allowConsumed: true,
|
|
149
150
|
signal: finalizationInput.signal,
|
|
@@ -380,18 +381,15 @@ function createGooglePlayObservedOrderMatch(observedTransactionId, tokenTransact
|
|
|
380
381
|
? { mode: 'token-only' }
|
|
381
382
|
: { mode: 'if-present', orderId: observedTransactionId };
|
|
382
383
|
}
|
|
383
|
-
/**
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const digest = new Uint8Array(await subtle.digest('SHA-256', new TextEncoder().encode(purchaseToken)));
|
|
393
|
-
return `play-token-sha256:${[...digest].map((value) => value.toString(16).padStart(2, '0')).join('')}`;
|
|
394
|
-
}
|
|
384
|
+
/**
|
|
385
|
+
* A non-secret transaction ID for purchases whose Google order ID is absent or
|
|
386
|
+
* delayed: `play-token-sha256:<hex>` of the purchase token.
|
|
387
|
+
*
|
|
388
|
+
* @deprecated Use `createGooglePlayTokenPurchaseKey` from `@mpgd/platform/commerce`;
|
|
389
|
+
* this alias is byte-for-byte the same helper (same key, same `TypeError` for
|
|
390
|
+
* an invalid token) and will be removed in the next major version.
|
|
391
|
+
*/
|
|
392
|
+
export const createGooglePlayTokenTransactionId = createGooglePlayTokenPurchaseKey;
|
|
395
393
|
function readGooglePlayVerifiedContext(payload, packageName, productId, purchaseTokenDigest) {
|
|
396
394
|
if (payload?.googlePlayPackageName !== packageName
|
|
397
395
|
|| payload.googlePlayProductId !== productId
|
|
@@ -17,10 +17,12 @@ export function createOnePlayPurchaseClient(input) {
|
|
|
17
17
|
try {
|
|
18
18
|
const response = await requestFetch(`${origin}${path}`, {
|
|
19
19
|
...init,
|
|
20
|
-
redirect: 'error'
|
|
20
|
+
// Workers reject redirect: 'error'; a manual 3xx/opaque redirect is never ok and fails below.
|
|
21
|
+
redirect: 'manual',
|
|
21
22
|
signal,
|
|
22
23
|
});
|
|
23
|
-
|
|
24
|
+
// Also reject a redirect followed by an injected fetch that ignores redirect: 'manual'.
|
|
25
|
+
if (!response.ok || response.redirected) {
|
|
24
26
|
if (response.status === 401) {
|
|
25
27
|
cached = undefined;
|
|
26
28
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { implement, ORPCError } from '@orpc/server';
|
|
2
2
|
import { BodyLimitHandlerPlugin, RPCHandler } from '@orpc/server/fetch';
|
|
3
3
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
|
-
import { resolveAdPlacementPlatformId, resolveProductPlatformId, } from '@mpgd/catalog';
|
|
4
|
+
import { resolveAdPlacementPlatformId, resolveCatalogCurrencies, resolveProductPlatformId, } from '@mpgd/catalog';
|
|
5
5
|
import { gameServicesBackendEndpoints, } from './client.js';
|
|
6
6
|
import { gameServicesContract } from './contract.js';
|
|
7
7
|
import { createRejectingGameServicesEvidenceVerifier, } from './evidence-verification.js';
|
|
@@ -127,6 +127,7 @@ export function createInMemoryGameServicesStore() {
|
|
|
127
127
|
return new InMemoryGameServicesStore();
|
|
128
128
|
}
|
|
129
129
|
export function createGameServicesBackend(input) {
|
|
130
|
+
assertDeclaredCatalogCurrencies(input.catalog, input.placements);
|
|
130
131
|
const store = input.store ?? createInMemoryGameServicesStore();
|
|
131
132
|
const now = input.now ?? (() => new Date().toISOString());
|
|
132
133
|
const version = input.version ?? '0.0.0';
|
|
@@ -223,6 +224,26 @@ export function createGameServicesBackend(input) {
|
|
|
223
224
|
},
|
|
224
225
|
};
|
|
225
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Every grant and reward the backend can write comes from the catalog or the
|
|
229
|
+
* placements, so an undeclared currency is a configuration error at startup
|
|
230
|
+
* rather than a rejected ledger write on the first purchase.
|
|
231
|
+
*/
|
|
232
|
+
function assertDeclaredCatalogCurrencies(catalog, placements) {
|
|
233
|
+
const currencies = resolveCatalogCurrencies(catalog);
|
|
234
|
+
for (const product of catalog.products) {
|
|
235
|
+
if (product.grant.type === 'currency' && !currencies.has(product.grant.currency)) {
|
|
236
|
+
throw new Error(`Product ${product.id} grants undeclared currency ${product.grant.currency}; `
|
|
237
|
+
+ `declared currencies: ${[...currencies].join(', ')}.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const placement of placements.placements) {
|
|
241
|
+
if (placement.reward?.type === 'currency' && !currencies.has(placement.reward.currency)) {
|
|
242
|
+
throw new Error(`Ad placement ${placement.id} rewards undeclared currency ${placement.reward.currency}; `
|
|
243
|
+
+ `declared currencies: ${[...currencies].join(', ')}.`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
226
247
|
export function createGameServicesBackendApiHandler(input) {
|
|
227
248
|
const backend = createGameServicesBackend(input);
|
|
228
249
|
return {
|
package/dist/types.js
CHANGED
|
@@ -270,8 +270,11 @@ function assertLedgerSource(input) {
|
|
|
270
270
|
function assertProductGrant(input) {
|
|
271
271
|
assertRecord(input, 'grant');
|
|
272
272
|
if (input.type === 'currency') {
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
// The currency list is open; the backend checks it against the catalog's
|
|
274
|
+
// declared currencies when it is constructed.
|
|
275
|
+
assertNonEmptyString(input.currency, 'grant.currency');
|
|
276
|
+
if (input.currency.trim() !== input.currency) {
|
|
277
|
+
throw new Error('grant.currency must be a trimmed currency code.');
|
|
275
278
|
}
|
|
276
279
|
assertFiniteNumber(input.amount, 'grant.amount');
|
|
277
280
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/game-services",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"types": "./dist/app-store-recovery.d.ts",
|
|
58
58
|
"default": "./dist/app-store-recovery.js"
|
|
59
59
|
},
|
|
60
|
+
"./app-store-server": {
|
|
61
|
+
"types": "./dist/app-store-server.d.ts",
|
|
62
|
+
"default": "./dist/app-store-server.js"
|
|
63
|
+
},
|
|
60
64
|
"./app-store-verifier-conformance": {
|
|
61
65
|
"types": "./dist/app-store-verifier-conformance.d.ts",
|
|
62
66
|
"default": "./dist/app-store-verifier-conformance.js"
|
|
@@ -186,14 +190,24 @@
|
|
|
186
190
|
"@orpc/client": "2.0.0-beta.14",
|
|
187
191
|
"@orpc/contract": "2.0.0-beta.14",
|
|
188
192
|
"@orpc/server": "2.0.0-beta.14",
|
|
189
|
-
"@mpgd/analytics": "0.4.
|
|
190
|
-
"@mpgd/
|
|
191
|
-
"@mpgd/
|
|
193
|
+
"@mpgd/analytics": "0.4.5",
|
|
194
|
+
"@mpgd/platform": "0.19.0",
|
|
195
|
+
"@mpgd/catalog": "0.9.0"
|
|
196
|
+
},
|
|
197
|
+
"peerDependencies": {
|
|
198
|
+
"@apple/app-store-server-library": "^3.1.0"
|
|
199
|
+
},
|
|
200
|
+
"peerDependenciesMeta": {
|
|
201
|
+
"@apple/app-store-server-library": {
|
|
202
|
+
"optional": true
|
|
203
|
+
}
|
|
192
204
|
},
|
|
193
205
|
"devDependencies": {
|
|
206
|
+
"@apple/app-store-server-library": "3.1.0",
|
|
194
207
|
"@opentelemetry/api": "1.9.0",
|
|
195
208
|
"ttsc": "0.30.4",
|
|
196
|
-
"typescript": "7.0.2"
|
|
209
|
+
"typescript": "7.0.2",
|
|
210
|
+
"vitest": "^4.0.0"
|
|
197
211
|
},
|
|
198
212
|
"main": "./dist/index.js",
|
|
199
213
|
"types": "./dist/index.d.ts",
|
|
@@ -209,6 +223,7 @@
|
|
|
209
223
|
"test:packed-transport": "pnpm build && node test/runtime-transport-package-import.mjs && node test/reward-evidence-import.mjs",
|
|
210
224
|
"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/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/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 && node tools/run-ttsx.mjs packages/game-services/src/guest-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",
|
|
211
225
|
"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",
|
|
212
|
-
"test": "pnpm test:unit && pnpm test:recovery && pnpm test:packed-transport"
|
|
226
|
+
"test": "pnpm test:unit && pnpm test:app-store-server && pnpm test:recovery && pnpm test:packed-transport",
|
|
227
|
+
"test:app-store-server": "vitest run"
|
|
213
228
|
}
|
|
214
229
|
}
|