@mpgd/game-services 0.16.0 → 0.17.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.
@@ -1,6 +1,8 @@
1
1
  /** Shared response shapes with no schema, DOM or transport dependencies. */
2
2
  export interface VerifyPurchaseResponse {
3
3
  readonly verified: boolean;
4
+ /** Explicit non-grant decision; older servers may omit this field. */
5
+ readonly disposition?: 'pending' | 'rejected';
4
6
  readonly ledgerEntryId?: string;
5
7
  readonly alreadyProcessed: boolean;
6
8
  readonly reason?: string;
@@ -15,6 +17,8 @@ export interface PurchaseGrantFinalization {
15
17
  }
16
18
  export interface ClaimAdRewardResponse {
17
19
  readonly granted: boolean;
20
+ /** Explicit non-grant decision; older servers may omit this field. */
21
+ readonly disposition?: 'pending' | 'rejected';
18
22
  readonly ledgerEntryId?: string;
19
23
  readonly alreadyProcessed: boolean;
20
24
  readonly reason?: string;
@@ -23,7 +23,7 @@ export interface GameServicesRewardedAdInput {
23
23
  readonly idempotencyKey: string;
24
24
  }
25
25
  export interface GameServicesRewardedAdResult {
26
- readonly status: 'granted' | 'skipped' | 'unavailable' | 'failed' | 'rejected';
26
+ readonly status: 'granted' | 'pending' | 'skipped' | 'unavailable' | 'failed' | 'rejected';
27
27
  readonly reward: RewardedAdResult;
28
28
  readonly claim?: ClaimAdRewardResponse;
29
29
  readonly ledgerEntryId?: string;
package/dist/runtime.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { AnalyticsSink } from '@mpgd/analytics';
2
2
  import type { PlatformGateway, PlatformTarget } from '@mpgd/platform';
3
3
  import { type GameServicesBackendApi, type GameServicesBackendTransport, type GameServicesClient, type GameServicesHeaderResolver } from './client.js';
4
4
  import type { GameServicesLedgerTarget } from './types.js';
5
+ import { type MonetizationOperationStore, type RecoverableMonetizationClient } from './monetization-recovery.js';
5
6
  export type GameServicesAuthorityMode = 'production' | 'non-production';
6
7
  export type GameServicesRuntimeMode = 'disabled' | 'local' | 'http' | 'orpc';
7
8
  export type GameServicesDisabledReason = 'unsupported_target' | 'missing_authoritative_backend' | 'invalid_authoritative_backend' | 'local_backend_not_allowed' | 'local_backend_unavailable';
@@ -11,6 +12,7 @@ export interface GameServicesRuntime {
11
12
  readonly baseUrl?: string;
12
13
  readonly target?: GameServicesLedgerTarget;
13
14
  readonly client?: GameServicesClient;
15
+ readonly monetizationRecovery?: RecoverableMonetizationClient;
14
16
  }
15
17
  interface CreateGameServicesRuntimeCommonInput {
16
18
  readonly gateway: PlatformGateway;
@@ -28,6 +30,8 @@ interface CreateGameServicesRuntimeCommonInput {
28
30
  readonly analytics?: AnalyticsSink;
29
31
  readonly analyticsSessionId?: string;
30
32
  readonly now?: () => string;
33
+ /** Durable, compare-and-swap journal for purchase and rewarded-ad recovery. */
34
+ readonly operationStore?: MonetizationOperationStore;
31
35
  }
32
36
  export type CreateGameServicesRuntimeInput = CreateGameServicesRuntimeCommonInput & ({
33
37
  readonly transport?: 'http';
package/dist/runtime.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createGameServicesClient, createGameServicesFetchBackendTransport, createGameServicesHttpBackendApi, createGameServicesOrpcBackendApi, createGameServicesOrpcClient, resolveGameServicesRequestHeaders, } from './client.js';
2
+ import { createRecoverableMonetizationClient, } from './monetization-recovery.js';
2
3
  export function createGameServicesRuntime(input) {
3
4
  assertAuthorityMode(input.authorityMode);
4
5
  assertTransport(input.transport);
@@ -47,24 +48,38 @@ export function createGameServicesRuntime(input) {
47
48
  mode = 'local';
48
49
  backend = input.localBackend;
49
50
  }
51
+ const clientInput = {
52
+ gateway: input.gateway,
53
+ backend,
54
+ playerId: input.playerId,
55
+ target,
56
+ ...(input.deploymentTarget === undefined
57
+ ? {}
58
+ : { deploymentTarget: input.deploymentTarget }),
59
+ ...(input.analytics === undefined ? {} : { analytics: input.analytics }),
60
+ ...(input.analyticsSessionId === undefined
61
+ ? {}
62
+ : { analyticsSessionId: input.analyticsSessionId }),
63
+ ...(input.now === undefined ? {} : { now: input.now }),
64
+ };
65
+ const client = createGameServicesClient(clientInput);
66
+ const monetizationRecovery = input.operationStore === undefined
67
+ ? undefined
68
+ : createRecoverableMonetizationClient({ ...clientInput, operationStore: input.operationStore });
69
+ let routedClient = client;
70
+ if (monetizationRecovery !== undefined) {
71
+ routedClient = {
72
+ ...client,
73
+ purchase: monetizationRecovery.purchase,
74
+ claimRewardedAd: monetizationRecovery.claimRewardedAd,
75
+ };
76
+ }
50
77
  return {
51
78
  mode,
52
79
  ...(baseUrl === undefined ? {} : { baseUrl }),
53
80
  target,
54
- client: createGameServicesClient({
55
- gateway: input.gateway,
56
- backend,
57
- playerId: input.playerId,
58
- target,
59
- ...(input.deploymentTarget === undefined
60
- ? {}
61
- : { deploymentTarget: input.deploymentTarget }),
62
- ...(input.analytics === undefined ? {} : { analytics: input.analytics }),
63
- ...(input.analyticsSessionId === undefined
64
- ? {}
65
- : { analyticsSessionId: input.analyticsSessionId }),
66
- ...(input.now === undefined ? {} : { now: input.now }),
67
- }),
81
+ client: routedClient,
82
+ ...(monetizationRecovery === undefined ? {} : { monetizationRecovery }),
68
83
  };
69
84
  }
70
85
  function withRuntimeHeaders(transport, input) {
package/dist/server.d.ts CHANGED
@@ -4,6 +4,7 @@ import { type GameServicesBackendApi, type GameServicesBackendTransport, type Ga
4
4
  import { type GameServicesHealthResponse } from './contract.js';
5
5
  import { type GameServicesEvidenceVerifier, type GameServicesPurchaseGrantFinalizer } from './evidence-verification.js';
6
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
+ export { gameServicesRequestLimits, GameServicesRequestValidationError } from './validation.js';
7
8
  type CorsHeaders = Record<string, string>;
8
9
  export interface CreateGameServicesBackendInput {
9
10
  readonly catalog: ProductCatalog;
@@ -19,14 +20,26 @@ export interface CreateGameServicesBackendInput {
19
20
  readonly evidenceVerificationTimeoutMs?: number;
20
21
  readonly purchaseGrantFinalizationTimeoutMs?: number;
21
22
  readonly purchaseGrantFinalizer?: GameServicesPurchaseGrantFinalizer;
23
+ /**
24
+ * Receives unexpected (non-validation) failures such as store errors before the
25
+ * transport answers with the generic `INTERNAL_ERROR` code. Defaults to `console.error`.
26
+ * The hook is diagnostics only: if it throws or returns a rejecting promise, the failure
27
+ * is logged and the generic error response is still returned.
28
+ */
29
+ readonly onInternalError?: GameServicesInternalErrorHandler;
22
30
  }
31
+ export type GameServicesInternalErrorHandler = (error: unknown) => void;
23
32
  export type GameServicesDeploymentTargetBindings = Readonly<Partial<Record<GameServicesAdRewardTarget | GameServicesStoreTarget, string>>>;
24
33
  export interface GameServicesBackendApiHandler {
25
34
  readonly version?: string;
26
35
  handle(request: GameServicesBackendTransportRequest): Promise<GameServicesBackendTransportResponse>;
27
36
  }
37
+ /** Stable error codes emitted by the backend API handler and the HTTP fetch handler. */
38
+ export type GameServicesBackendErrorCode = 'INVALID_REQUEST' | 'INVALID_JSON' | 'PAYLOAD_TOO_LARGE' | 'METHOD_NOT_ALLOWED' | 'UNKNOWN_ENDPOINT' | 'INTERNAL_ERROR';
28
39
  export interface GameServicesBackendErrorResponse {
29
- readonly error: string;
40
+ readonly error: GameServicesBackendErrorCode;
41
+ /** Present only for `INVALID_REQUEST`; never carries internal failure details. */
42
+ readonly message?: string;
30
43
  }
31
44
  export interface GameServicesStore {
32
45
  recordEntitlementGrant(input: EntitlementLedgerGrant): Promise<EntitlementLedgerResult>;
@@ -57,6 +70,11 @@ export interface CreateGameServicesFetchHandlerOptions {
57
70
  readonly corsHeaders?: CorsHeaders;
58
71
  readonly healthPath?: string;
59
72
  readonly version?: string;
73
+ /** Request bodies above this byte size are rejected with 413. */
74
+ readonly maxBodyBytes?: number;
75
+ }
76
+ export interface CreateGameServicesRouterOptions {
77
+ readonly onInternalError?: GameServicesInternalErrorHandler;
60
78
  }
61
79
  export interface CreateGameServicesRpcFetchHandlerOptions extends CreateGameServicesFetchHandlerOptions {
62
80
  readonly prefix?: string;
@@ -99,7 +117,7 @@ export declare function createInMemoryGameServicesStore(): InMemoryGameServicesS
99
117
  export declare function createGameServicesBackend(input: CreateGameServicesBackendInput): GameServicesBackendApi;
100
118
  export declare function createGameServicesBackendApiHandler(input: CreateGameServicesBackendInput): GameServicesBackendApiHandler;
101
119
  export declare function createInProcessGameServicesBackendTransport(handler: GameServicesBackendApiHandler): GameServicesBackendTransport;
102
- export declare function createGameServicesRouter(backend: GameServicesBackendApi): {
120
+ export declare function createGameServicesRouter(backend: GameServicesBackendApi, options?: CreateGameServicesRouterOptions): {
103
121
  health: import("@orpc/server").ImplementedProcedure<GameServicesOrpcContext & object, object, import("@orpc/contract").Schema<void, void>, import("@orpc/contract").Schema<GameServicesHealthResponse, GameServicesHealthResponse>, object>;
104
122
  commerce: {
105
123
  verifyPurchase: import("@orpc/server").ImplementedProcedure<GameServicesOrpcContext & object, object, import("@orpc/contract").Schema<VerifyPurchaseRequest, VerifyPurchaseRequest>, import("@orpc/contract").Schema<VerifyPurchaseResponse, VerifyPurchaseResponse>, object>;