@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.
- package/dist/apps-in-toss-partner-api.d.ts +49 -0
- package/dist/apps-in-toss-partner-api.js +190 -8
- package/dist/client.d.ts +1 -0
- package/dist/client.js +45 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/microsoft-store-purchase.d.ts +131 -0
- package/dist/microsoft-store-purchase.js +835 -0
- package/dist/notification-delivery.js +1 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +12 -2
- package/dist/server.d.ts +5 -2
- package/dist/server.js +87 -7
- package/dist/types.d.ts +8 -3
- package/dist/types.js +37 -3
- package/package.json +9 -5
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({
|
|
22
|
+
? createGameServicesOrpcBackendApi(createGameServicesOrpcClient({
|
|
23
|
+
url: baseUrl,
|
|
24
|
+
...(input.headers === undefined ? {} : { headers: input.headers }),
|
|
25
|
+
}))
|
|
23
26
|
: createGameServicesHttpBackendApi({
|
|
24
|
-
transport: createGameServicesFetchBackendTransport({
|
|
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
|
|
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.
|
|
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.
|
|
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 !== '
|
|
130
|
-
|
|
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.
|
|
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.
|
|
130
|
-
"@mpgd/
|
|
131
|
-
"@mpgd/
|
|
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
|
}
|