@mpgd/game-services 0.14.0 → 0.15.1
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 +80 -0
- package/dist/admob-ssv-conformance.d.ts +2 -2
- package/dist/admob-ssv.d.ts +2 -2
- package/dist/app-store-verifier-conformance.d.ts +1 -1
- package/dist/app-store-verifier.d.ts +1 -1
- package/dist/apps-in-toss-evidence-verification-conformance.d.ts +2 -2
- package/dist/apps-in-toss-evidence-verification-conformance.js +2 -2
- package/dist/apps-in-toss-evidence-verification.d.ts +2 -2
- package/dist/apps-in-toss-partner-api.d.ts +1 -1
- package/dist/client.d.ts +7 -27
- package/dist/client.js +162 -147
- package/dist/contract.d.ts +1 -1
- package/dist/evidence-verification.d.ts +1 -1
- package/dist/google-play-purchase-conformance.d.ts +1 -1
- package/dist/google-play-purchase-conformance.js +1 -1
- package/dist/google-play-purchase.d.ts +1 -1
- package/dist/index.d.ts +16 -16
- package/dist/index.js +16 -16
- package/dist/microsoft-store-identity.d.ts +1 -1
- package/dist/microsoft-store-purchase.d.ts +1 -1
- package/dist/notification-delivery.js +1 -1
- package/dist/operation-progress.d.ts +47 -0
- package/dist/operation-progress.js +87 -0
- package/dist/operation-results.d.ts +21 -0
- package/dist/operation-results.js +1 -0
- package/dist/operations.d.ts +30 -0
- package/dist/operations.js +1 -0
- package/dist/progress-link.js +1 -1
- package/dist/server.d.ts +6 -6
- package/dist/server.js +9 -6
- package/dist/types.d.ts +2 -20
- package/dist/verified-leaderboard-conformance.d.ts +1 -1
- package/dist/verified-leaderboard-conformance.js +1 -1
- package/dist/verified-leaderboard-durability-conformance.d.ts +1 -1
- package/dist/verified-leaderboard-durability-conformance.js +1 -1
- package/package.json +8 -4
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Game services
|
|
2
|
+
|
|
3
|
+
Client, contract, backend, and store helpers for authoritative game services.
|
|
4
|
+
See [backend integration](../../docs/GAME_SERVICES_BACKEND.md) for the existing
|
|
5
|
+
verification and ledger boundaries.
|
|
6
|
+
|
|
7
|
+
## Optional operation progress
|
|
8
|
+
|
|
9
|
+
`purchase(input, options?)` and `claimRewardedAd(input, options?)` accept optional
|
|
10
|
+
`onProgress` observers. Existing one-argument calls and client doubles remain
|
|
11
|
+
compatible. Types are available from `@mpgd/game-services/client` and the package
|
|
12
|
+
root. No UI runtime or Phaser dependency is added.
|
|
13
|
+
|
|
14
|
+
Headless consumers can import `GameServicesOperationClient` and operation types
|
|
15
|
+
from `@mpgd/game-services/operations`. This entrypoint's declarations support
|
|
16
|
+
`lib: ["ES2022"]`, `types: []`, and `skipLibCheck: false`; they do not require
|
|
17
|
+
catalog validators, transport declarations, or DOM globals. Existing response
|
|
18
|
+
type exports from `@mpgd/game-services/types` remain compatible.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
await client.purchase(input, {
|
|
22
|
+
correlationId: 'view-operation-42',
|
|
23
|
+
onProgress: (event) => updateStatus(event),
|
|
24
|
+
onObserverError: (error) => reportUiObserverError(error),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Phase | Observed fact |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `platform-requested` | The platform API request is about to be attempted |
|
|
31
|
+
| `platform-result` | The platform API resolved; `status` retains its result code |
|
|
32
|
+
| `server-requested` | The request has been constructed and the backend verification/claim call is about to be attempted |
|
|
33
|
+
| `server-result` | The backend resolved; `accepted` mirrors verification or claim acceptance |
|
|
34
|
+
| `completed` | The local invocation returned a normal result; `status` equals that result's status |
|
|
35
|
+
| `exception` | The invocation threw; `at` identifies platform, server, or local processing |
|
|
36
|
+
|
|
37
|
+
Purchase and rewarded-ad progress are discriminated by `kind` and `phase`.
|
|
38
|
+
`sequence` starts at one per invocation and increases for each actual event.
|
|
39
|
+
Events are frozen and calls have independent observers, sequences, and optional
|
|
40
|
+
correlation IDs. Callback invocation order is deterministic; asynchronous callback
|
|
41
|
+
completion order belongs to the callback. There is no event history, polling,
|
|
42
|
+
retry, or cancellation mechanism.
|
|
43
|
+
|
|
44
|
+
A platform request is not proof that a native screen opened, and a server stage
|
|
45
|
+
is not proof that it closed. Native UI visibility cannot be inferred from these
|
|
46
|
+
events. Platform ad completion/reward flags, backend acceptance, and the final
|
|
47
|
+
service result remain separate. Progress is observation, never grant evidence.
|
|
48
|
+
|
|
49
|
+
Successful verified purchases and claimed ads normally emit all five non-error
|
|
50
|
+
stages. Cancelled, pending, failed, skipped, or unavailable platform results
|
|
51
|
+
complete without fabricating backend stages. Unsupported targets emit only the
|
|
52
|
+
local completed result, retaining existing rejected-result semantics. An
|
|
53
|
+
authoritative Microsoft Store completion emits no duplicate verification.
|
|
54
|
+
Verse8 purchases preserve the existing Agent8 grant restrictions and pending
|
|
55
|
+
semantics. Backend rejection is normal completion; an exception leaves the
|
|
56
|
+
outcome uncertain and does not mean the server denied a grant.
|
|
57
|
+
|
|
58
|
+
Events do not copy receipts, provider evidence, auth headers, player IDs, ledger
|
|
59
|
+
IDs, idempotency keys, raw responses, or provider exception details. A correlation
|
|
60
|
+
ID is caller-supplied UI/diagnostic metadata: use an opaque, non-sensitive value.
|
|
61
|
+
It is independent of the backend idempotency key and confers no grant authority.
|
|
62
|
+
|
|
63
|
+
Synchronous callback exceptions and rejected callback promises are isolated.
|
|
64
|
+
Observer promises are not awaited: slow or unsettled observers cannot delay a
|
|
65
|
+
platform call, verification, claim, or business result. Optional `onObserverError`
|
|
66
|
+
observes only observation failures; its own exceptions/rejections are consumed.
|
|
67
|
+
There is no forced global logging or transport. Original business exceptions
|
|
68
|
+
still reject the client promise unchanged. Existing analytics, purchase recovery,
|
|
69
|
+
and retry policy are not redesigned by this observer API.
|
|
70
|
+
|
|
71
|
+
The root `pnpm test` pipeline includes compiled client JS and public declaration
|
|
72
|
+
consumption checks alongside the client and target conformance suites.
|
|
73
|
+
|
|
74
|
+
All published entrypoints use explicit internal ESM module paths and are smoke-tested
|
|
75
|
+
with native Node imports, without a bundler or TypeScript runtime loader.
|
|
76
|
+
|
|
77
|
+
Headless consumers can import operation input/result/progress types and the two-method
|
|
78
|
+
`GameServicesOperationClient` port from `@mpgd/game-services/operations`. This entrypoint
|
|
79
|
+
has no runtime implementation and its declarations require no DOM or fetch globals.
|
|
80
|
+
The full client continues to re-export the same operation types for compatibility.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdPlacements } from '@mpgd/catalog';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { ClaimAdRewardRequest } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { ClaimAdRewardRequest } from './types.js';
|
|
4
4
|
export declare const adMobSsvConformanceChecks: readonly ['verified callback emits one transaction authority identity', 'missing callback remains pending', 'tampered signature fails closed', 'AdMob-decoded signed query bytes accept equivalent percent-encoding', 'missing and duplicate signed fields fail closed', 'unknown key fails closed', 'secp256k1 key feed entries verify', 'signed identity mismatch fails closed', 'timestamp age and future-skew boundaries fail closed', 'callbacks sharing one signed transaction preserve one authority identity'];
|
|
5
5
|
export interface AdMobSsvConformanceReport {
|
|
6
6
|
readonly checks: readonly string[];
|
package/dist/admob-ssv.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdPlacementEntry } from '@mpgd/catalog';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { ClaimAdRewardRequest } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { ClaimAdRewardRequest } from './types.js';
|
|
4
4
|
export declare const admobSsvCustomDataSchema = "mpgd.admob.ssv.binding.v1";
|
|
5
5
|
export declare const defaultAdMobSsvMaximumCallbackAgeMs = 86400000;
|
|
6
6
|
export declare const defaultAdMobSsvMaximumFutureSkewMs = 300000;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppStoreEnvironment, AppStoreInAppPurchaseType, AppStoreTransactionPayload } from './app-store-verifier';
|
|
1
|
+
import type { AppStoreEnvironment, AppStoreInAppPurchaseType, AppStoreTransactionPayload } from './app-store-verifier.js';
|
|
2
2
|
export interface CreateAppStoreTransactionConformanceFixtureInput {
|
|
3
3
|
readonly transactionId?: string;
|
|
4
4
|
readonly originalTransactionId?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
1
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
2
2
|
export type AppStoreEnvironment = 'Production' | 'Sandbox';
|
|
3
3
|
export declare const appStoreServerApiBaseUrls: Readonly<{
|
|
4
4
|
readonly Production: 'https://api.storekit.apple.com';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AppsInTossPurchaseAuthority, type AppsInTossPurchaseAuthorityInput, type AppsInTossPurchaseAuthorityResult, type AppsInTossRewardAuthority, type AppsInTossRewardAuthorityInput, type AppsInTossRewardAuthorityResult, type CreateAppsInTossProductionEvidenceVerifierInput } from './apps-in-toss-evidence-verification';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
1
|
+
import { type AppsInTossPurchaseAuthority, type AppsInTossPurchaseAuthorityInput, type AppsInTossPurchaseAuthorityResult, type AppsInTossRewardAuthority, type AppsInTossRewardAuthorityInput, type AppsInTossRewardAuthorityResult, type CreateAppsInTossProductionEvidenceVerifierInput } from './apps-in-toss-evidence-verification.js';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
3
|
export declare const appsInTossProductionEvidenceConformanceScenarios: readonly ['callback-only-fail-closed', 'purchase-product-grant-callback', 'purchase-authority-retry', 'purchase-pending-order-restore', 'purchase-authority-matching', 'purchase-timestamp-normalization', 'reward-authority-retry-and-replay', 'reward-timestamp-validation', 'authority-errors-and-reward-matching'];
|
|
4
4
|
export type AppsInTossProductionEvidenceConformanceScenario = (typeof appsInTossProductionEvidenceConformanceScenarios)[number];
|
|
5
5
|
export type CreateAppsInTossProductionEvidenceVerifier = (input?: CreateAppsInTossProductionEvidenceVerifierInput) => GameServicesEvidenceVerifier;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appsInTossPurchaseCallbackEvidenceSchema, createAppsInTossProductGrantCallback, createAppsInTossProductGrantVerificationPort, createAppsInTossProductionEvidenceVerifier, createAppsInTossPurchaseCallbackEvidence, createAppsInTossRewardCallbackEvidence, } from './apps-in-toss-evidence-verification';
|
|
2
|
-
import { createGameServicesBackend, createInMemoryGameServicesStore, InMemoryGameServicesStore, } from './server';
|
|
1
|
+
import { appsInTossPurchaseCallbackEvidenceSchema, createAppsInTossProductGrantCallback, createAppsInTossProductGrantVerificationPort, createAppsInTossProductionEvidenceVerifier, createAppsInTossPurchaseCallbackEvidence, createAppsInTossRewardCallbackEvidence, } from './apps-in-toss-evidence-verification.js';
|
|
2
|
+
import { createGameServicesBackend, createInMemoryGameServicesStore, InMemoryGameServicesStore, } from './server.js';
|
|
3
3
|
export const appsInTossProductionEvidenceConformanceScenarios = [
|
|
4
4
|
'callback-only-fail-closed',
|
|
5
5
|
'purchase-product-grant-callback',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LogicalProductId, PlatformEvidenceEnvelope } from '@mpgd/platform';
|
|
2
|
-
import type { GameServicesEvidenceVerifier } from './evidence-verification';
|
|
3
|
-
import type { VerifyPurchaseRequest, VerifyPurchaseResponse } from './types';
|
|
2
|
+
import type { GameServicesEvidenceVerifier } from './evidence-verification.js';
|
|
3
|
+
import type { VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
4
4
|
export declare const appsInTossPurchaseCallbackEvidenceSchema = "apps-in-toss.iap.callback.v1";
|
|
5
5
|
export declare const appsInTossRewardCallbackEvidenceSchema = "apps-in-toss.rewarded-ad.callback.v1";
|
|
6
6
|
/** Leaves five seconds of headroom inside the SDK's documented 30-second window. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NotificationTemplateData } from './notification-delivery';
|
|
1
|
+
import type { NotificationTemplateData } from './notification-delivery.js';
|
|
2
2
|
export declare const appsInTossPartnerApiBaseUrl = "https://apps-in-toss-api.toss.im";
|
|
3
3
|
export declare const defaultAppsInTossPartnerApiTimeoutMs = 10000;
|
|
4
4
|
/** Compatible with a Cloudflare mTLS certificate binding (`env.BINDING.fetch`). */
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type AnalyticsSink } from '@mpgd/analytics';
|
|
2
|
-
import type { LeaderboardScoreInput,
|
|
3
|
-
import type {
|
|
4
|
-
|
|
2
|
+
import type { LeaderboardScoreInput, PlatformGateway, PlatformTarget } from '@mpgd/platform';
|
|
3
|
+
import type { GameServicesOperationClient } from './operations.js';
|
|
4
|
+
export type { GameServicesOperationClient, GameServicesPurchaseInput, GameServicesPurchaseResult, GameServicesRewardedAdInput, GameServicesRewardedAdResult, } from './operations.js';
|
|
5
|
+
import type { GameServicesContractClient } from './contract.js';
|
|
6
|
+
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, GameServicesLedgerTarget, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
7
|
+
export type { GameServicesOperationKind, GameServicesOperationLocation, GameServicesOperationOptions, GameServicesOperationProgress, GameServicesPurchaseProgress, GameServicesRewardedAdProgress, } from './operation-progress.js';
|
|
5
8
|
export interface PurchaseVerificationApi {
|
|
6
9
|
verifyPurchase(input: VerifyPurchaseRequest): Promise<VerifyPurchaseResponse>;
|
|
7
10
|
}
|
|
@@ -64,9 +67,7 @@ export declare class GameServicesBackendError extends Error {
|
|
|
64
67
|
readonly body: unknown;
|
|
65
68
|
constructor(endpoint: GameServicesBackendEndpoint, status: number, body: unknown);
|
|
66
69
|
}
|
|
67
|
-
export interface GameServicesClient {
|
|
68
|
-
purchase(input: GameServicesPurchaseInput): Promise<GameServicesPurchaseResult>;
|
|
69
|
-
claimRewardedAd(input: GameServicesRewardedAdInput): Promise<GameServicesRewardedAdResult>;
|
|
70
|
+
export interface GameServicesClient extends GameServicesOperationClient {
|
|
70
71
|
submitLeaderboardScore(input: GameServicesLeaderboardInput): Promise<GameServicesLeaderboardResult>;
|
|
71
72
|
}
|
|
72
73
|
export interface CreateGameServicesClientInput {
|
|
@@ -79,27 +80,6 @@ export interface CreateGameServicesClientInput {
|
|
|
79
80
|
readonly analyticsSessionId?: string;
|
|
80
81
|
readonly now?: () => string;
|
|
81
82
|
}
|
|
82
|
-
export interface GameServicesPurchaseInput {
|
|
83
|
-
readonly productId: LogicalProductId;
|
|
84
|
-
readonly source: 'shop' | 'stage_fail' | 'result' | 'event';
|
|
85
|
-
readonly idempotencyKey: string;
|
|
86
|
-
}
|
|
87
|
-
export interface GameServicesPurchaseResult {
|
|
88
|
-
readonly status: 'granted' | 'cancelled' | 'pending' | 'failed' | 'rejected';
|
|
89
|
-
readonly purchase: PurchaseResult;
|
|
90
|
-
readonly verification?: VerifyPurchaseResponse;
|
|
91
|
-
readonly ledgerEntryId?: string;
|
|
92
|
-
}
|
|
93
|
-
export interface GameServicesRewardedAdInput {
|
|
94
|
-
readonly placementId: LogicalAdPlacementId;
|
|
95
|
-
readonly idempotencyKey: string;
|
|
96
|
-
}
|
|
97
|
-
export interface GameServicesRewardedAdResult {
|
|
98
|
-
readonly status: 'granted' | 'skipped' | 'unavailable' | 'failed' | 'rejected';
|
|
99
|
-
readonly reward: RewardedAdResult;
|
|
100
|
-
readonly claim?: ClaimAdRewardResponse;
|
|
101
|
-
readonly ledgerEntryId?: string;
|
|
102
|
-
}
|
|
103
83
|
export interface GameServicesLeaderboardInput extends LeaderboardScoreInput {
|
|
104
84
|
}
|
|
105
85
|
export interface GameServicesLeaderboardResult {
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createORPCClient } from '@orpc/client';
|
|
2
2
|
import { RPCLink } from '@orpc/client/fetch';
|
|
3
3
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
|
+
import { observeGameServicesOperation } from './operation-progress.js';
|
|
4
5
|
export const gameServicesBackendEndpoints = {
|
|
5
6
|
verifyPurchase: '/game-services/purchases/verify',
|
|
6
7
|
claimAdReward: '/game-services/ad-rewards/claim',
|
|
@@ -27,176 +28,190 @@ export function createGameServicesClient(input) {
|
|
|
27
28
|
...(input.analytics === undefined ? {} : { sink: input.analytics }),
|
|
28
29
|
});
|
|
29
30
|
return {
|
|
30
|
-
async purchase(purchaseInput) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
async purchase(purchaseInput, options) {
|
|
32
|
+
return observeGameServicesOperation('purchase', options, async (progress) => {
|
|
33
|
+
const target = input.target;
|
|
34
|
+
if (!isGameServicesCommerceTarget(target)) {
|
|
35
|
+
const purchase = {
|
|
36
|
+
status: 'failed',
|
|
37
|
+
entitlementIds: [],
|
|
38
|
+
};
|
|
39
|
+
await analytics.track({
|
|
40
|
+
name: 'purchase_rejected',
|
|
41
|
+
properties: {
|
|
42
|
+
productId: purchaseInput.productId,
|
|
43
|
+
status: purchase.status,
|
|
44
|
+
reason: 'unsupported_target',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
status: 'rejected',
|
|
49
|
+
purchase,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
progress.platformRequested();
|
|
53
|
+
const purchase = await input.gateway.commerce.purchase(purchaseInput);
|
|
54
|
+
progress.platformResult(purchase.status);
|
|
55
|
+
if (target === 'verse8') {
|
|
56
|
+
const status = purchase.status === 'completed' ? 'rejected' : purchase.status;
|
|
57
|
+
await analytics.track({
|
|
58
|
+
name: 'purchase_rejected',
|
|
59
|
+
properties: {
|
|
60
|
+
productId: purchaseInput.productId,
|
|
61
|
+
status,
|
|
62
|
+
reason: purchase.status === 'completed'
|
|
63
|
+
? 'verse8_grants_require_agent8_purchase_event'
|
|
64
|
+
: purchase.status === 'pending'
|
|
65
|
+
? 'agent8_purchase_event_pending'
|
|
66
|
+
: undefined,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
57
70
|
status,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
purchase,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (isAuthoritativeMicrosoftStoreCompletion(target, purchase)) {
|
|
75
|
+
const result = {
|
|
76
|
+
status: 'granted',
|
|
77
|
+
purchase,
|
|
78
|
+
ledgerEntryId: purchase.transactionId,
|
|
79
|
+
};
|
|
80
|
+
await analytics.track({
|
|
81
|
+
name: 'purchase_granted',
|
|
82
|
+
properties: {
|
|
83
|
+
productId: purchaseInput.productId,
|
|
84
|
+
status: result.status,
|
|
85
|
+
ledgerEntryId: purchase.transactionId,
|
|
86
|
+
alreadyProcessed: purchase.authoritativeGrant?.alreadyProcessed,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
if (purchase.status !== 'completed' || purchase.transactionId === undefined) {
|
|
92
|
+
await analytics.track({
|
|
93
|
+
name: 'purchase_rejected',
|
|
94
|
+
properties: {
|
|
95
|
+
productId: purchaseInput.productId,
|
|
96
|
+
status: purchase.status,
|
|
97
|
+
reason: purchaseRejectionReason(purchase),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
status: purchase.status === 'completed' ? 'rejected' : purchase.status,
|
|
102
|
+
purchase,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const verificationRequest = {
|
|
106
|
+
target,
|
|
107
|
+
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
108
|
+
? {}
|
|
109
|
+
: { deploymentTarget: input.deploymentTarget }),
|
|
110
|
+
playerId: input.playerId,
|
|
111
|
+
productId: purchaseInput.productId,
|
|
112
|
+
platformTransactionId: purchase.transactionId,
|
|
113
|
+
idempotencyKey: purchaseInput.idempotencyKey,
|
|
114
|
+
purchasedAt: now(),
|
|
115
|
+
...(purchase.evidence === undefined ? {} : { evidence: purchase.evidence }),
|
|
68
116
|
};
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
progress.serverRequested();
|
|
118
|
+
const verification = await input.backend.purchases.verifyPurchase(verificationRequest);
|
|
119
|
+
progress.serverResult(verification.verified);
|
|
71
120
|
const result = {
|
|
72
|
-
status: 'granted',
|
|
121
|
+
status: verification.verified ? 'granted' : 'rejected',
|
|
73
122
|
purchase,
|
|
74
|
-
|
|
123
|
+
verification,
|
|
124
|
+
...(verification.ledgerEntryId === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: { ledgerEntryId: verification.ledgerEntryId }),
|
|
75
127
|
};
|
|
76
128
|
await analytics.track({
|
|
77
|
-
name: 'purchase_granted',
|
|
129
|
+
name: verification.verified ? 'purchase_granted' : 'purchase_rejected',
|
|
78
130
|
properties: {
|
|
79
131
|
productId: purchaseInput.productId,
|
|
80
132
|
status: result.status,
|
|
81
|
-
ledgerEntryId:
|
|
82
|
-
alreadyProcessed:
|
|
133
|
+
ledgerEntryId: verification.ledgerEntryId,
|
|
134
|
+
alreadyProcessed: verification.alreadyProcessed,
|
|
135
|
+
reason: verification.reason,
|
|
83
136
|
},
|
|
84
137
|
});
|
|
85
138
|
return result;
|
|
86
|
-
}
|
|
87
|
-
if (purchase.status !== 'completed' || purchase.transactionId === undefined) {
|
|
88
|
-
await analytics.track({
|
|
89
|
-
name: 'purchase_rejected',
|
|
90
|
-
properties: {
|
|
91
|
-
productId: purchaseInput.productId,
|
|
92
|
-
status: purchase.status,
|
|
93
|
-
reason: purchaseRejectionReason(purchase),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
return {
|
|
97
|
-
status: purchase.status === 'completed' ? 'rejected' : purchase.status,
|
|
98
|
-
purchase,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
const verification = await input.backend.purchases.verifyPurchase({
|
|
102
|
-
target,
|
|
103
|
-
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
104
|
-
? {}
|
|
105
|
-
: { deploymentTarget: input.deploymentTarget }),
|
|
106
|
-
playerId: input.playerId,
|
|
107
|
-
productId: purchaseInput.productId,
|
|
108
|
-
platformTransactionId: purchase.transactionId,
|
|
109
|
-
idempotencyKey: purchaseInput.idempotencyKey,
|
|
110
|
-
purchasedAt: now(),
|
|
111
|
-
...(purchase.evidence === undefined ? {} : { evidence: purchase.evidence }),
|
|
112
139
|
});
|
|
113
|
-
const result = {
|
|
114
|
-
status: verification.verified ? 'granted' : 'rejected',
|
|
115
|
-
purchase,
|
|
116
|
-
verification,
|
|
117
|
-
...(verification.ledgerEntryId === undefined
|
|
118
|
-
? {}
|
|
119
|
-
: { ledgerEntryId: verification.ledgerEntryId }),
|
|
120
|
-
};
|
|
121
|
-
await analytics.track({
|
|
122
|
-
name: verification.verified ? 'purchase_granted' : 'purchase_rejected',
|
|
123
|
-
properties: {
|
|
124
|
-
productId: purchaseInput.productId,
|
|
125
|
-
status: result.status,
|
|
126
|
-
ledgerEntryId: verification.ledgerEntryId,
|
|
127
|
-
alreadyProcessed: verification.alreadyProcessed,
|
|
128
|
-
reason: verification.reason,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
return result;
|
|
132
140
|
},
|
|
133
|
-
async claimRewardedAd(rewardInput) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
async claimRewardedAd(rewardInput, options) {
|
|
142
|
+
return observeGameServicesOperation('rewarded-ad', options, async (progress) => {
|
|
143
|
+
const target = input.target;
|
|
144
|
+
if (!isGameServicesAdRewardTarget(target)) {
|
|
145
|
+
const reward = {
|
|
146
|
+
status: 'unavailable',
|
|
147
|
+
rewardGranted: false,
|
|
148
|
+
};
|
|
149
|
+
await analytics.track({
|
|
150
|
+
name: 'rewarded_ad_rejected',
|
|
151
|
+
properties: {
|
|
152
|
+
placementId: rewardInput.placementId,
|
|
153
|
+
status: reward.status,
|
|
154
|
+
rewardGranted: reward.rewardGranted,
|
|
155
|
+
reason: 'unsupported_target',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
status: 'rejected',
|
|
160
|
+
reward,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
progress.platformRequested();
|
|
164
|
+
const reward = await input.gateway.ads.showRewarded(rewardInput);
|
|
165
|
+
progress.platformResult(reward.status);
|
|
166
|
+
if (reward.status !== 'completed' || !reward.rewardGranted) {
|
|
167
|
+
await analytics.track({
|
|
168
|
+
name: 'rewarded_ad_rejected',
|
|
169
|
+
properties: {
|
|
170
|
+
placementId: rewardInput.placementId,
|
|
171
|
+
status: reward.status,
|
|
172
|
+
rewardGranted: reward.rewardGranted,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
status: reward.status === 'completed' ? 'rejected' : reward.status,
|
|
177
|
+
reward,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const claimRequest = {
|
|
181
|
+
target,
|
|
182
|
+
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
183
|
+
? {}
|
|
184
|
+
: { deploymentTarget: input.deploymentTarget }),
|
|
185
|
+
playerId: input.playerId,
|
|
186
|
+
placementId: rewardInput.placementId,
|
|
187
|
+
...(reward.ledgerEntryId === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { platformImpressionId: reward.ledgerEntryId }),
|
|
190
|
+
idempotencyKey: rewardInput.idempotencyKey,
|
|
191
|
+
completedAt: now(),
|
|
192
|
+
...(reward.evidence === undefined ? {} : { evidence: reward.evidence }),
|
|
139
193
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
rewardGranted: reward.rewardGranted,
|
|
146
|
-
reason: 'unsupported_target',
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
return {
|
|
150
|
-
status: 'rejected',
|
|
194
|
+
progress.serverRequested();
|
|
195
|
+
const claim = await input.backend.adRewards.claimAdReward(claimRequest);
|
|
196
|
+
progress.serverResult(claim.granted);
|
|
197
|
+
const result = {
|
|
198
|
+
status: claim.granted ? 'granted' : 'rejected',
|
|
151
199
|
reward,
|
|
200
|
+
claim,
|
|
201
|
+
...(claim.ledgerEntryId === undefined ? {} : { ledgerEntryId: claim.ledgerEntryId }),
|
|
152
202
|
};
|
|
153
|
-
}
|
|
154
|
-
const reward = await input.gateway.ads.showRewarded(rewardInput);
|
|
155
|
-
if (reward.status !== 'completed' || !reward.rewardGranted) {
|
|
156
203
|
await analytics.track({
|
|
157
|
-
name: 'rewarded_ad_rejected',
|
|
204
|
+
name: claim.granted ? 'rewarded_ad_granted' : 'rewarded_ad_rejected',
|
|
158
205
|
properties: {
|
|
159
206
|
placementId: rewardInput.placementId,
|
|
160
|
-
status:
|
|
161
|
-
|
|
207
|
+
status: result.status,
|
|
208
|
+
ledgerEntryId: claim.ledgerEntryId,
|
|
209
|
+
alreadyProcessed: claim.alreadyProcessed,
|
|
210
|
+
reason: claim.reason,
|
|
162
211
|
},
|
|
163
212
|
});
|
|
164
|
-
return
|
|
165
|
-
status: reward.status === 'completed' ? 'rejected' : reward.status,
|
|
166
|
-
reward,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
const claim = await input.backend.adRewards.claimAdReward({
|
|
170
|
-
target,
|
|
171
|
-
...(input.deploymentTarget === undefined || input.deploymentTarget === target
|
|
172
|
-
? {}
|
|
173
|
-
: { deploymentTarget: input.deploymentTarget }),
|
|
174
|
-
playerId: input.playerId,
|
|
175
|
-
placementId: rewardInput.placementId,
|
|
176
|
-
...(reward.ledgerEntryId === undefined
|
|
177
|
-
? {}
|
|
178
|
-
: { platformImpressionId: reward.ledgerEntryId }),
|
|
179
|
-
idempotencyKey: rewardInput.idempotencyKey,
|
|
180
|
-
completedAt: now(),
|
|
181
|
-
...(reward.evidence === undefined ? {} : { evidence: reward.evidence }),
|
|
182
|
-
});
|
|
183
|
-
const result = {
|
|
184
|
-
status: claim.granted ? 'granted' : 'rejected',
|
|
185
|
-
reward,
|
|
186
|
-
claim,
|
|
187
|
-
...(claim.ledgerEntryId === undefined ? {} : { ledgerEntryId: claim.ledgerEntryId }),
|
|
188
|
-
};
|
|
189
|
-
await analytics.track({
|
|
190
|
-
name: claim.granted ? 'rewarded_ad_granted' : 'rewarded_ad_rejected',
|
|
191
|
-
properties: {
|
|
192
|
-
placementId: rewardInput.placementId,
|
|
193
|
-
status: result.status,
|
|
194
|
-
ledgerEntryId: claim.ledgerEntryId,
|
|
195
|
-
alreadyProcessed: claim.alreadyProcessed,
|
|
196
|
-
reason: claim.reason,
|
|
197
|
-
},
|
|
213
|
+
return result;
|
|
198
214
|
});
|
|
199
|
-
return result;
|
|
200
215
|
},
|
|
201
216
|
async submitLeaderboardScore(scoreInput) {
|
|
202
217
|
const target = input.target;
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RouterContractClient } from '@orpc/contract';
|
|
2
|
-
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types';
|
|
2
|
+
import type { ClaimAdRewardRequest, ClaimAdRewardResponse, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types.js';
|
|
3
3
|
export interface GameServicesHealthResponse {
|
|
4
4
|
readonly ok: true;
|
|
5
5
|
readonly service: 'game-services';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdPlacements, ProductCatalog } from '@mpgd/catalog';
|
|
2
|
-
import type { ClaimAdRewardRequest, EntitlementLedgerPayload, PurchaseGrantFinalization, VerifyPurchaseRequest } from './types';
|
|
2
|
+
import type { ClaimAdRewardRequest, EntitlementLedgerPayload, PurchaseGrantFinalization, VerifyPurchaseRequest } from './types.js';
|
|
3
3
|
export type EvidenceVerificationDecision = {
|
|
4
4
|
readonly status: 'verified';
|
|
5
5
|
readonly verificationId: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GooglePlayAcknowledgementState, type GooglePlayConsumptionState, type GooglePlayPurchaseState } from './google-play-purchase';
|
|
1
|
+
import { type GooglePlayAcknowledgementState, type GooglePlayConsumptionState, type GooglePlayPurchaseState } from './google-play-purchase.js';
|
|
2
2
|
export declare const googlePlayProductPurchaseConformanceEvidence: Readonly<{
|
|
3
3
|
schema: "google-play.product-purchase.v2";
|
|
4
4
|
payload: Readonly<{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { googlePlayProductPurchaseEvidenceSchema, } from './google-play-purchase';
|
|
1
|
+
import { googlePlayProductPurchaseEvidenceSchema, } from './google-play-purchase.js';
|
|
2
2
|
export const googlePlayProductPurchaseConformanceEvidence = Object.freeze({
|
|
3
3
|
schema: googlePlayProductPurchaseEvidenceSchema,
|
|
4
4
|
payload: Object.freeze({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification';
|
|
1
|
+
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification.js';
|
|
2
2
|
export declare const googlePlayProductPurchaseEvidenceSchema = "google-play.product-purchase.v2";
|
|
3
3
|
export type GooglePlayPurchaseState = 'PURCHASED' | 'CANCELLED' | 'PENDING' | 'PURCHASE_STATE_UNSPECIFIED';
|
|
4
4
|
export type GooglePlayConsumptionState = 'CONSUMPTION_STATE_YET_TO_BE_CONSUMED' | 'CONSUMPTION_STATE_CONSUMED' | 'CONSUMPTION_STATE_UNSPECIFIED';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export * from './admob-ssv.js';
|
|
2
|
-
export * from './app-store-verifier';
|
|
3
|
-
export * from './apps-in-toss-evidence-verification';
|
|
4
|
-
export * from './apps-in-toss-partner-api';
|
|
5
|
-
export * from './client';
|
|
6
|
-
export * from './contract';
|
|
7
|
-
export * from './evidence-verification';
|
|
8
|
-
export * from './google-play-purchase';
|
|
9
|
-
export * from './microsoft-store-identity';
|
|
10
|
-
export * from './microsoft-store-purchase';
|
|
11
|
-
export * from './notification-delivery';
|
|
12
|
-
export * from './platform-order';
|
|
13
|
-
export * from './progress-link';
|
|
2
|
+
export * from './app-store-verifier.js';
|
|
3
|
+
export * from './apps-in-toss-evidence-verification.js';
|
|
4
|
+
export * from './apps-in-toss-partner-api.js';
|
|
5
|
+
export * from './client.js';
|
|
6
|
+
export * from './contract.js';
|
|
7
|
+
export * from './evidence-verification.js';
|
|
8
|
+
export * from './google-play-purchase.js';
|
|
9
|
+
export * from './microsoft-store-identity.js';
|
|
10
|
+
export * from './microsoft-store-purchase.js';
|
|
11
|
+
export * from './notification-delivery.js';
|
|
12
|
+
export * from './platform-order.js';
|
|
13
|
+
export * from './progress-link.js';
|
|
14
14
|
export * from './runtime.js';
|
|
15
|
-
export * from './server';
|
|
16
|
-
export * from './types';
|
|
17
|
-
export * from './verified-leaderboard';
|
|
18
|
-
export * from './verified-leaderboard-transport';
|
|
15
|
+
export * from './server.js';
|
|
16
|
+
export * from './types.js';
|
|
17
|
+
export * from './verified-leaderboard.js';
|
|
18
|
+
export * from './verified-leaderboard-transport.js';
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export * from './admob-ssv.js';
|
|
2
|
-
export * from './app-store-verifier';
|
|
3
|
-
export * from './apps-in-toss-evidence-verification';
|
|
4
|
-
export * from './apps-in-toss-partner-api';
|
|
5
|
-
export * from './client';
|
|
6
|
-
export * from './contract';
|
|
7
|
-
export * from './evidence-verification';
|
|
8
|
-
export * from './google-play-purchase';
|
|
9
|
-
export * from './microsoft-store-identity';
|
|
10
|
-
export * from './microsoft-store-purchase';
|
|
11
|
-
export * from './notification-delivery';
|
|
12
|
-
export * from './platform-order';
|
|
13
|
-
export * from './progress-link';
|
|
2
|
+
export * from './app-store-verifier.js';
|
|
3
|
+
export * from './apps-in-toss-evidence-verification.js';
|
|
4
|
+
export * from './apps-in-toss-partner-api.js';
|
|
5
|
+
export * from './client.js';
|
|
6
|
+
export * from './contract.js';
|
|
7
|
+
export * from './evidence-verification.js';
|
|
8
|
+
export * from './google-play-purchase.js';
|
|
9
|
+
export * from './microsoft-store-identity.js';
|
|
10
|
+
export * from './microsoft-store-purchase.js';
|
|
11
|
+
export * from './notification-delivery.js';
|
|
12
|
+
export * from './platform-order.js';
|
|
13
|
+
export * from './progress-link.js';
|
|
14
14
|
export * from './runtime.js';
|
|
15
|
-
export * from './server';
|
|
16
|
-
export * from './types';
|
|
17
|
-
export * from './verified-leaderboard';
|
|
18
|
-
export * from './verified-leaderboard-transport';
|
|
15
|
+
export * from './server.js';
|
|
16
|
+
export * from './types.js';
|
|
17
|
+
export * from './verified-leaderboard.js';
|
|
18
|
+
export * from './verified-leaderboard-transport.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MicrosoftStoreCollectionsCredentials } from './microsoft-store-purchase';
|
|
1
|
+
import type { MicrosoftStoreCollectionsCredentials } from './microsoft-store-purchase.js';
|
|
2
2
|
export declare const microsoftStoreIdentityCredentialsSchema: 'mpgd.microsoft-store.identity-credentials.v1';
|
|
3
3
|
export declare const microsoftStoreIdentityCredentialsRequestUrl: 'https://microsoft-store-authority.internal/v1/credentials';
|
|
4
4
|
export interface MicrosoftStoreIdentityCredentialsRequest {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification';
|
|
1
|
+
import type { GameServicesEvidenceVerifier, GameServicesPurchaseGrantFinalizer, VerifyPurchaseEvidenceInput } from './evidence-verification.js';
|
|
2
2
|
export declare const microsoftStoreDigitalGoodsEvidenceSchema: 'mpgd.microsoft-store.digital-goods.v1';
|
|
3
3
|
export declare const microsoftStoreCollectionsQueryUrl: 'https://collections.mp.microsoft.com/v9.0/collections/publisherQuery';
|
|
4
4
|
export declare const microsoftStoreCollectionsConsumeUrl: 'https://collections.mp.microsoft.com/v8.0/collections/consume';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PurchaseResult, RewardedAdResult } from '@mpgd/platform';
|
|
2
|
+
import type { GameServicesPurchaseResult, GameServicesRewardedAdResult } from './operations.js';
|
|
3
|
+
export type GameServicesOperationKind = 'purchase' | 'rewarded-ad';
|
|
4
|
+
export type GameServicesOperationLocation = 'local' | 'platform' | 'server';
|
|
5
|
+
type PlatformStatus<K extends GameServicesOperationKind> = K extends 'purchase' ? PurchaseResult['status'] : RewardedAdResult['status'];
|
|
6
|
+
type OperationResult<K extends GameServicesOperationKind> = K extends 'purchase' ? GameServicesPurchaseResult : GameServicesRewardedAdResult;
|
|
7
|
+
type ProgressStage<K extends GameServicesOperationKind> = {
|
|
8
|
+
readonly phase: 'platform-requested';
|
|
9
|
+
} | {
|
|
10
|
+
readonly phase: 'platform-result';
|
|
11
|
+
readonly status: PlatformStatus<K>;
|
|
12
|
+
} | {
|
|
13
|
+
readonly phase: 'server-requested';
|
|
14
|
+
} | {
|
|
15
|
+
readonly phase: 'server-result';
|
|
16
|
+
readonly accepted: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
readonly phase: 'completed';
|
|
19
|
+
readonly status: OperationResult<K>['status'];
|
|
20
|
+
} | {
|
|
21
|
+
readonly phase: 'exception';
|
|
22
|
+
readonly at: GameServicesOperationLocation;
|
|
23
|
+
};
|
|
24
|
+
type OperationProgress<K extends GameServicesOperationKind> = ProgressStage<K> & {
|
|
25
|
+
readonly kind: K;
|
|
26
|
+
/** Invocation-local event order, starting at one. */
|
|
27
|
+
readonly sequence: number;
|
|
28
|
+
/** Optional UI/diagnostic correlation, never an idempotency key or grant proof. */
|
|
29
|
+
readonly correlationId?: string;
|
|
30
|
+
};
|
|
31
|
+
export type GameServicesPurchaseProgress = OperationProgress<'purchase'>;
|
|
32
|
+
export type GameServicesRewardedAdProgress = OperationProgress<'rewarded-ad'>;
|
|
33
|
+
export type GameServicesOperationProgress = GameServicesPurchaseProgress | GameServicesRewardedAdProgress;
|
|
34
|
+
export interface GameServicesOperationOptions<TProgress = GameServicesOperationProgress> {
|
|
35
|
+
readonly onProgress?: (progress: TProgress) => void | Promise<void>;
|
|
36
|
+
readonly onObserverError?: (error: unknown) => void | Promise<void>;
|
|
37
|
+
readonly correlationId?: string;
|
|
38
|
+
}
|
|
39
|
+
interface OperationReporter<K extends GameServicesOperationKind> {
|
|
40
|
+
platformRequested(): void;
|
|
41
|
+
platformResult(status: PlatformStatus<K>): void;
|
|
42
|
+
serverRequested(): void;
|
|
43
|
+
serverResult(accepted: boolean): void;
|
|
44
|
+
}
|
|
45
|
+
/** Internal observation wrapper; it does not retry, cancel, verify, or grant anything. */
|
|
46
|
+
export declare function observeGameServicesOperation<K extends GameServicesOperationKind>(kind: K, options: GameServicesOperationOptions<OperationProgress<K>> | undefined, operation: (reporter: OperationReporter<K>) => Promise<OperationResult<K>>): Promise<OperationResult<K>>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Internal observation wrapper; it does not retry, cancel, verify, or grant anything. */
|
|
2
|
+
export async function observeGameServicesOperation(kind, options, operation) {
|
|
3
|
+
let onProgress;
|
|
4
|
+
let onObserverError;
|
|
5
|
+
let correlationId;
|
|
6
|
+
let sequence = 0;
|
|
7
|
+
let location = 'local';
|
|
8
|
+
function report(error) {
|
|
9
|
+
try {
|
|
10
|
+
if (typeof onObserverError === 'function') {
|
|
11
|
+
const result = onObserverError(error);
|
|
12
|
+
if (result !== undefined) {
|
|
13
|
+
void Promise.resolve(result).catch(() => { });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Diagnostic callbacks have no authority to change business results.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Capture observation options once. Even accessor failures stay outside the business flow.
|
|
22
|
+
try {
|
|
23
|
+
onObserverError = options?.onObserverError;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// There is no safe error observer to invoke if retrieving it failed.
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
onProgress = options?.onProgress;
|
|
30
|
+
const suppliedId = options?.correlationId;
|
|
31
|
+
if (typeof suppliedId === 'string') {
|
|
32
|
+
correlationId = suppliedId;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
report(error);
|
|
37
|
+
}
|
|
38
|
+
function emit(stage) {
|
|
39
|
+
sequence += 1;
|
|
40
|
+
if (typeof onProgress !== 'function') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const observer = onProgress;
|
|
44
|
+
const progress = Object.freeze({
|
|
45
|
+
...stage,
|
|
46
|
+
kind,
|
|
47
|
+
sequence,
|
|
48
|
+
...(correlationId === undefined ? {} : { correlationId }),
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const result = observer(progress);
|
|
52
|
+
if (result !== undefined) {
|
|
53
|
+
void Promise.resolve(result).catch(report);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
report(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const reporter = {
|
|
61
|
+
platformRequested() {
|
|
62
|
+
location = 'platform';
|
|
63
|
+
emit({ phase: 'platform-requested' });
|
|
64
|
+
},
|
|
65
|
+
platformResult(status) {
|
|
66
|
+
location = 'local';
|
|
67
|
+
emit({ phase: 'platform-result', status });
|
|
68
|
+
},
|
|
69
|
+
serverRequested() {
|
|
70
|
+
location = 'server';
|
|
71
|
+
emit({ phase: 'server-requested' });
|
|
72
|
+
},
|
|
73
|
+
serverResult(accepted) {
|
|
74
|
+
location = 'local';
|
|
75
|
+
emit({ phase: 'server-result', accepted });
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
const result = await operation(reporter);
|
|
80
|
+
emit({ phase: 'completed', status: result.status });
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
emit({ phase: 'exception', at: location });
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Shared response shapes with no schema, DOM or transport dependencies. */
|
|
2
|
+
export interface VerifyPurchaseResponse {
|
|
3
|
+
readonly verified: boolean;
|
|
4
|
+
readonly ledgerEntryId?: string;
|
|
5
|
+
readonly alreadyProcessed: boolean;
|
|
6
|
+
readonly reason?: string;
|
|
7
|
+
readonly finalization?: PurchaseGrantFinalization;
|
|
8
|
+
}
|
|
9
|
+
export type PurchaseGrantFinalizationAction = 'acknowledge' | 'consume' | 'finish' | 'complete';
|
|
10
|
+
export interface PurchaseGrantFinalization {
|
|
11
|
+
readonly status: 'completed' | 'pending';
|
|
12
|
+
readonly action?: PurchaseGrantFinalizationAction;
|
|
13
|
+
readonly alreadyCompleted: boolean;
|
|
14
|
+
readonly reason?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ClaimAdRewardResponse {
|
|
17
|
+
readonly granted: boolean;
|
|
18
|
+
readonly ledgerEntryId?: string;
|
|
19
|
+
readonly alreadyProcessed: boolean;
|
|
20
|
+
readonly reason?: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LogicalAdPlacementId, LogicalProductId, PurchaseResult, RewardedAdResult } from '@mpgd/platform';
|
|
2
|
+
import type { GameServicesOperationOptions, GameServicesPurchaseProgress, GameServicesRewardedAdProgress } from './operation-progress.js';
|
|
3
|
+
import type { ClaimAdRewardResponse, VerifyPurchaseResponse } from './operation-results.js';
|
|
4
|
+
export type { GameServicesOperationKind, GameServicesOperationLocation, GameServicesOperationOptions, GameServicesOperationProgress, GameServicesPurchaseProgress, GameServicesRewardedAdProgress, } from './operation-progress.js';
|
|
5
|
+
/** DOM-free public operation port, implemented by GameServicesClient. */
|
|
6
|
+
export interface GameServicesOperationClient {
|
|
7
|
+
purchase(input: GameServicesPurchaseInput, options?: GameServicesOperationOptions<GameServicesPurchaseProgress>): Promise<GameServicesPurchaseResult>;
|
|
8
|
+
claimRewardedAd(input: GameServicesRewardedAdInput, options?: GameServicesOperationOptions<GameServicesRewardedAdProgress>): Promise<GameServicesRewardedAdResult>;
|
|
9
|
+
}
|
|
10
|
+
export interface GameServicesPurchaseInput {
|
|
11
|
+
readonly productId: LogicalProductId;
|
|
12
|
+
readonly source: 'shop' | 'stage_fail' | 'result' | 'event';
|
|
13
|
+
readonly idempotencyKey: string;
|
|
14
|
+
}
|
|
15
|
+
export interface GameServicesPurchaseResult {
|
|
16
|
+
readonly status: 'granted' | 'cancelled' | 'pending' | 'failed' | 'rejected';
|
|
17
|
+
readonly purchase: PurchaseResult;
|
|
18
|
+
readonly verification?: VerifyPurchaseResponse;
|
|
19
|
+
readonly ledgerEntryId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface GameServicesRewardedAdInput {
|
|
22
|
+
readonly placementId: LogicalAdPlacementId;
|
|
23
|
+
readonly idempotencyKey: string;
|
|
24
|
+
}
|
|
25
|
+
export interface GameServicesRewardedAdResult {
|
|
26
|
+
readonly status: 'granted' | 'skipped' | 'unavailable' | 'failed' | 'rejected';
|
|
27
|
+
readonly reward: RewardedAdResult;
|
|
28
|
+
readonly claim?: ClaimAdRewardResponse;
|
|
29
|
+
readonly ledgerEntryId?: string;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/progress-link.js
CHANGED
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type AnalyticsSink } from '@mpgd/analytics';
|
|
2
2
|
import { type AdPlacements, type ProductCatalog } from '@mpgd/catalog';
|
|
3
|
-
import { type GameServicesBackendApi, type GameServicesBackendTransport, type GameServicesBackendTransportRequest, type GameServicesBackendTransportResponse } from './client';
|
|
4
|
-
import { type GameServicesHealthResponse } from './contract';
|
|
5
|
-
import { type GameServicesEvidenceVerifier, type GameServicesPurchaseGrantFinalizer } from './evidence-verification';
|
|
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';
|
|
3
|
+
import { type GameServicesBackendApi, type GameServicesBackendTransport, type GameServicesBackendTransportRequest, type GameServicesBackendTransportResponse } from './client.js';
|
|
4
|
+
import { type GameServicesHealthResponse } from './contract.js';
|
|
5
|
+
import { type GameServicesEvidenceVerifier, type GameServicesPurchaseGrantFinalizer } from './evidence-verification.js';
|
|
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.js';
|
|
7
7
|
type CorsHeaders = Record<string, string>;
|
|
8
8
|
export interface CreateGameServicesBackendInput {
|
|
9
9
|
readonly catalog: ProductCatalog;
|
|
@@ -113,5 +113,5 @@ export declare function createGameServicesRouter(backend: GameServicesBackendApi
|
|
|
113
113
|
};
|
|
114
114
|
export declare function createGameServicesRpcFetchHandler(router: ReturnType<typeof createGameServicesRouter>, options?: CreateGameServicesRpcFetchHandlerOptions): (request: Request) => Promise<Response>;
|
|
115
115
|
export declare function createGameServicesHttpFetchHandler(handler: GameServicesBackendApiHandler, options?: CreateGameServicesFetchHandlerOptions): (request: Request) => Promise<Response>;
|
|
116
|
-
export * from './notification-delivery';
|
|
117
|
-
export * from './progress-link';
|
|
116
|
+
export * from './notification-delivery.js';
|
|
117
|
+
export * from './progress-link.js';
|
package/dist/server.js
CHANGED
|
@@ -2,10 +2,10 @@ import { implement } from '@orpc/server';
|
|
|
2
2
|
import { RPCHandler } from '@orpc/server/fetch';
|
|
3
3
|
import { createAnalyticsReporter } from '@mpgd/analytics';
|
|
4
4
|
import { resolveAdPlacementPlatformId, resolveProductPlatformId, } from '@mpgd/catalog';
|
|
5
|
-
import { gameServicesBackendEndpoints, } from './client';
|
|
6
|
-
import { gameServicesContract } from './contract';
|
|
7
|
-
import { createRejectingGameServicesEvidenceVerifier, } from './evidence-verification';
|
|
8
|
-
import { assertClaimAdRewardRequest, assertClaimAdRewardResponse, assertEntitlementLedgerGrant, assertEntitlementLedgerResult, assertGameServicesDeploymentTarget, assertLeaderboardScoreTransaction, assertProductGrantTransaction, assertPurchaseGrantFinalization, assertRecordLeaderboardScoreRequest, assertRecordLeaderboardScoreResponse, assertVerifyPurchaseRequest, assertVerifyPurchaseResponse, } from './types';
|
|
5
|
+
import { gameServicesBackendEndpoints, } from './client.js';
|
|
6
|
+
import { gameServicesContract } from './contract.js';
|
|
7
|
+
import { createRejectingGameServicesEvidenceVerifier, } from './evidence-verification.js';
|
|
8
|
+
import { assertClaimAdRewardRequest, assertClaimAdRewardResponse, assertEntitlementLedgerGrant, assertEntitlementLedgerResult, assertGameServicesDeploymentTarget, assertLeaderboardScoreTransaction, assertProductGrantTransaction, assertPurchaseGrantFinalization, assertRecordLeaderboardScoreRequest, assertRecordLeaderboardScoreResponse, assertVerifyPurchaseRequest, assertVerifyPurchaseResponse, } from './types.js';
|
|
9
9
|
export class InMemoryGameServicesStore {
|
|
10
10
|
entitlementTransactionsByKey = new Map();
|
|
11
11
|
entitlementTransactionsByEvidence = new Map();
|
|
@@ -622,6 +622,9 @@ function createStoredPurchaseRequest(request, transaction) {
|
|
|
622
622
|
const purchasedAt = transaction.payload.purchasedAt;
|
|
623
623
|
return {
|
|
624
624
|
...request,
|
|
625
|
+
// Evidence recovery can arrive with another request key. Consume/release must use the
|
|
626
|
+
// original durable grant generation, never the retry's newly supplied key.
|
|
627
|
+
idempotencyKey: transaction.idempotencyKey,
|
|
625
628
|
...(typeof platformTransactionId === 'string' && platformTransactionId.length > 0
|
|
626
629
|
? { platformTransactionId }
|
|
627
630
|
: {}),
|
|
@@ -1157,5 +1160,5 @@ function applyCorsHeaders(headers, corsHeaders) {
|
|
|
1157
1160
|
headers.set(key, value);
|
|
1158
1161
|
}
|
|
1159
1162
|
}
|
|
1160
|
-
export * from './notification-delivery';
|
|
1161
|
-
export * from './progress-link';
|
|
1163
|
+
export * from './notification-delivery.js';
|
|
1164
|
+
export * from './progress-link.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ProductGrant } from '@mpgd/catalog';
|
|
2
2
|
import type { LeaderboardScoreInput, LogicalAdPlacementId, LogicalProductId, PlatformEvidenceEnvelope, PlatformTarget } from '@mpgd/platform';
|
|
3
|
+
import type { ClaimAdRewardResponse, PurchaseGrantFinalization, VerifyPurchaseResponse } from './operation-results.js';
|
|
4
|
+
export type { ClaimAdRewardResponse, PurchaseGrantFinalization, PurchaseGrantFinalizationAction, VerifyPurchaseResponse, } from './operation-results.js';
|
|
3
5
|
export type { PlatformEvidenceEnvelope } from '@mpgd/platform';
|
|
4
6
|
export type GameServicesStoreTarget = Extract<PlatformTarget, 'microsoft-store' | 'android' | 'ios' | 'ait'>;
|
|
5
7
|
export type GameServicesAdRewardTarget = Extract<PlatformTarget, 'android' | 'ios' | 'ait' | 'verse8'>;
|
|
@@ -21,20 +23,6 @@ export interface VerifyPurchaseRequest {
|
|
|
21
23
|
readonly purchasedAt: string;
|
|
22
24
|
readonly evidence?: PlatformEvidenceEnvelope;
|
|
23
25
|
}
|
|
24
|
-
export interface VerifyPurchaseResponse {
|
|
25
|
-
readonly verified: boolean;
|
|
26
|
-
readonly ledgerEntryId?: string;
|
|
27
|
-
readonly alreadyProcessed: boolean;
|
|
28
|
-
readonly reason?: string;
|
|
29
|
-
readonly finalization?: PurchaseGrantFinalization;
|
|
30
|
-
}
|
|
31
|
-
export type PurchaseGrantFinalizationAction = 'acknowledge' | 'consume' | 'finish' | 'complete';
|
|
32
|
-
export interface PurchaseGrantFinalization {
|
|
33
|
-
readonly status: 'completed' | 'pending';
|
|
34
|
-
readonly action?: PurchaseGrantFinalizationAction;
|
|
35
|
-
readonly alreadyCompleted: boolean;
|
|
36
|
-
readonly reason?: string;
|
|
37
|
-
}
|
|
38
26
|
export interface ClaimAdRewardRequest {
|
|
39
27
|
readonly target: GameServicesAdRewardTarget;
|
|
40
28
|
/** Deployment config key used to resolve game-owned platform placement identifiers. */
|
|
@@ -47,12 +35,6 @@ export interface ClaimAdRewardRequest {
|
|
|
47
35
|
readonly completedAt: string;
|
|
48
36
|
readonly evidence?: PlatformEvidenceEnvelope;
|
|
49
37
|
}
|
|
50
|
-
export interface ClaimAdRewardResponse {
|
|
51
|
-
readonly granted: boolean;
|
|
52
|
-
readonly ledgerEntryId?: string;
|
|
53
|
-
readonly alreadyProcessed: boolean;
|
|
54
|
-
readonly reason?: string;
|
|
55
|
-
}
|
|
56
38
|
export interface RecordLeaderboardScoreRequest extends LeaderboardScoreInput {
|
|
57
39
|
readonly target: GameServicesLeaderboardTarget;
|
|
58
40
|
readonly playerId: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type VerifiedLeaderboardService } from './verified-leaderboard';
|
|
1
|
+
import { type VerifiedLeaderboardService } from './verified-leaderboard.js';
|
|
2
2
|
export declare const verifiedLeaderboardConformanceScenarios: readonly ['first-selection-and-snapshot', 'best-selection-and-retry', 'deterministic-ties', 'identity-and-definition-conflicts', 'concurrent-idempotency', 'mutation-isolation', 'runtime-validation'];
|
|
3
3
|
export type VerifiedLeaderboardConformanceScenario = (typeof verifiedLeaderboardConformanceScenarios)[number];
|
|
4
4
|
export interface CreateVerifiedLeaderboardConformanceFixtureInput {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertRecordVerifiedLeaderboardAttemptResponse, assertVerifiedLeaderboardSnapshot, } from './verified-leaderboard';
|
|
1
|
+
import { assertRecordVerifiedLeaderboardAttemptResponse, assertVerifiedLeaderboardSnapshot, } from './verified-leaderboard.js';
|
|
2
2
|
export const verifiedLeaderboardConformanceScenarios = [
|
|
3
3
|
'first-selection-and-snapshot',
|
|
4
4
|
'best-selection-and-retry',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type VerifiedLeaderboardService } from './verified-leaderboard';
|
|
1
|
+
import { type VerifiedLeaderboardService } from './verified-leaderboard.js';
|
|
2
2
|
export declare const verifiedLeaderboardDurabilityConformanceScenarios: readonly ['replacement-after-interrupted-retained-write', 'concurrent-retry-after-interrupted-retained-write', 'snapshot-after-interrupted-retained-write'];
|
|
3
3
|
export type VerifiedLeaderboardDurabilityConformanceScenario = (typeof verifiedLeaderboardDurabilityConformanceScenarios)[number];
|
|
4
4
|
export interface CreateVerifiedLeaderboardDurabilityConformanceFixtureInput {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertRecordVerifiedLeaderboardAttemptResponse, assertVerifiedLeaderboardSnapshot, } from './verified-leaderboard';
|
|
1
|
+
import { assertRecordVerifiedLeaderboardAttemptResponse, assertVerifiedLeaderboardSnapshot, } from './verified-leaderboard.js';
|
|
2
2
|
export const verifiedLeaderboardDurabilityConformanceScenarios = [
|
|
3
3
|
'replacement-after-interrupted-retained-write',
|
|
4
4
|
'concurrent-retry-after-interrupted-retained-write',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/game-services",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -124,6 +124,10 @@
|
|
|
124
124
|
"./verified-leaderboard-durability-conformance": {
|
|
125
125
|
"types": "./dist/verified-leaderboard-durability-conformance.d.ts",
|
|
126
126
|
"default": "./dist/verified-leaderboard-durability-conformance.js"
|
|
127
|
+
},
|
|
128
|
+
"./operations": {
|
|
129
|
+
"types": "./dist/operations.d.ts",
|
|
130
|
+
"default": "./dist/operations.js"
|
|
127
131
|
}
|
|
128
132
|
},
|
|
129
133
|
"files": [
|
|
@@ -135,8 +139,8 @@
|
|
|
135
139
|
"@orpc/contract": "2.0.0-beta.14",
|
|
136
140
|
"@orpc/server": "2.0.0-beta.14",
|
|
137
141
|
"@mpgd/analytics": "0.3.12",
|
|
138
|
-
"@mpgd/
|
|
139
|
-
"@mpgd/
|
|
142
|
+
"@mpgd/catalog": "0.7.2",
|
|
143
|
+
"@mpgd/platform": "0.12.0"
|
|
140
144
|
},
|
|
141
145
|
"devDependencies": {
|
|
142
146
|
"ttsc": "0.18.4",
|
|
@@ -152,6 +156,6 @@
|
|
|
152
156
|
"lint": "ttsc --noEmit",
|
|
153
157
|
"format": "ttsc format",
|
|
154
158
|
"fix": "ttsc fix",
|
|
155
|
-
"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/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"
|
|
159
|
+
"test": "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/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"
|
|
156
160
|
}
|
|
157
161
|
}
|