@mpgd/game-services 0.13.1 → 0.15.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.
Files changed (34) hide show
  1. package/README.md +74 -0
  2. package/dist/admob-ssv-conformance.d.ts +2 -2
  3. package/dist/admob-ssv.d.ts +2 -2
  4. package/dist/app-store-verifier-conformance.d.ts +1 -1
  5. package/dist/app-store-verifier.d.ts +1 -1
  6. package/dist/apps-in-toss-evidence-verification-conformance.d.ts +2 -2
  7. package/dist/apps-in-toss-evidence-verification-conformance.js +2 -2
  8. package/dist/apps-in-toss-evidence-verification.d.ts +2 -2
  9. package/dist/apps-in-toss-partner-api.d.ts +1 -1
  10. package/dist/client.d.ts +7 -27
  11. package/dist/client.js +162 -147
  12. package/dist/contract.d.ts +1 -1
  13. package/dist/evidence-verification.d.ts +1 -1
  14. package/dist/google-play-purchase-conformance.d.ts +1 -1
  15. package/dist/google-play-purchase-conformance.js +1 -1
  16. package/dist/google-play-purchase.d.ts +1 -1
  17. package/dist/index.d.ts +16 -15
  18. package/dist/index.js +16 -15
  19. package/dist/microsoft-store-identity.d.ts +39 -0
  20. package/dist/microsoft-store-identity.js +304 -0
  21. package/dist/microsoft-store-purchase.d.ts +1 -1
  22. package/dist/notification-delivery.js +3 -1
  23. package/dist/operation-progress.d.ts +47 -0
  24. package/dist/operation-progress.js +87 -0
  25. package/dist/operations.d.ts +30 -0
  26. package/dist/operations.js +1 -0
  27. package/dist/progress-link.js +1 -1
  28. package/dist/server.d.ts +6 -6
  29. package/dist/server.js +9 -6
  30. package/dist/verified-leaderboard-conformance.d.ts +1 -1
  31. package/dist/verified-leaderboard-conformance.js +1 -1
  32. package/dist/verified-leaderboard-durability-conformance.d.ts +1 -1
  33. package/dist/verified-leaderboard-durability-conformance.js +1 -1
  34. package/package.json +13 -5
package/README.md ADDED
@@ -0,0 +1,74 @@
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
+ ```ts
15
+ await client.purchase(input, {
16
+ correlationId: 'view-operation-42',
17
+ onProgress: (event) => updateStatus(event),
18
+ onObserverError: (error) => reportUiObserverError(error),
19
+ });
20
+ ```
21
+
22
+ | Phase | Observed fact |
23
+ | --- | --- |
24
+ | `platform-requested` | The platform API request is about to be attempted |
25
+ | `platform-result` | The platform API resolved; `status` retains its result code |
26
+ | `server-requested` | The request has been constructed and the backend verification/claim call is about to be attempted |
27
+ | `server-result` | The backend resolved; `accepted` mirrors verification or claim acceptance |
28
+ | `completed` | The local invocation returned a normal result; `status` equals that result's status |
29
+ | `exception` | The invocation threw; `at` identifies platform, server, or local processing |
30
+
31
+ Purchase and rewarded-ad progress are discriminated by `kind` and `phase`.
32
+ `sequence` starts at one per invocation and increases for each actual event.
33
+ Events are frozen and calls have independent observers, sequences, and optional
34
+ correlation IDs. Callback invocation order is deterministic; asynchronous callback
35
+ completion order belongs to the callback. There is no event history, polling,
36
+ retry, or cancellation mechanism.
37
+
38
+ A platform request is not proof that a native screen opened, and a server stage
39
+ is not proof that it closed. Native UI visibility cannot be inferred from these
40
+ events. Platform ad completion/reward flags, backend acceptance, and the final
41
+ service result remain separate. Progress is observation, never grant evidence.
42
+
43
+ Successful verified purchases and claimed ads normally emit all five non-error
44
+ stages. Cancelled, pending, failed, skipped, or unavailable platform results
45
+ complete without fabricating backend stages. Unsupported targets emit only the
46
+ local completed result, retaining existing rejected-result semantics. An
47
+ authoritative Microsoft Store completion emits no duplicate verification.
48
+ Verse8 purchases preserve the existing Agent8 grant restrictions and pending
49
+ semantics. Backend rejection is normal completion; an exception leaves the
50
+ outcome uncertain and does not mean the server denied a grant.
51
+
52
+ Events do not copy receipts, provider evidence, auth headers, player IDs, ledger
53
+ IDs, idempotency keys, raw responses, or provider exception details. A correlation
54
+ ID is caller-supplied UI/diagnostic metadata: use an opaque, non-sensitive value.
55
+ It is independent of the backend idempotency key and confers no grant authority.
56
+
57
+ Synchronous callback exceptions and rejected callback promises are isolated.
58
+ Observer promises are not awaited: slow or unsettled observers cannot delay a
59
+ platform call, verification, claim, or business result. Optional `onObserverError`
60
+ observes only observation failures; its own exceptions/rejections are consumed.
61
+ There is no forced global logging or transport. Original business exceptions
62
+ still reject the client promise unchanged. Existing analytics, purchase recovery,
63
+ and retry policy are not redesigned by this observer API.
64
+
65
+ The root `pnpm test` pipeline includes compiled client JS and public declaration
66
+ consumption checks alongside the client and target conformance suites.
67
+
68
+ All published entrypoints use explicit internal ESM module paths and are smoke-tested
69
+ with native Node imports, without a bundler or TypeScript runtime loader.
70
+
71
+ Headless consumers can import operation input/result/progress types and the two-method
72
+ `GameServicesOperationClient` port from `@mpgd/game-services/operations`. This entrypoint
73
+ has no runtime implementation and its declarations require no DOM or fetch globals.
74
+ 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[];
@@ -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, LogicalAdPlacementId, LogicalProductId, PlatformGateway, PlatformTarget, PurchaseResult, RewardedAdResult } from '@mpgd/platform';
3
- import type { GameServicesContractClient } from './contract';
4
- import type { ClaimAdRewardRequest, ClaimAdRewardResponse, GameServicesLedgerTarget, RecordLeaderboardScoreRequest, RecordLeaderboardScoreResponse, VerifyPurchaseRequest, VerifyPurchaseResponse } from './types';
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
- const target = input.target;
32
- if (!isGameServicesCommerceTarget(target)) {
33
- const purchase = {
34
- status: 'failed',
35
- entitlementIds: [],
36
- };
37
- await analytics.track({
38
- name: 'purchase_rejected',
39
- properties: {
40
- productId: purchaseInput.productId,
41
- status: purchase.status,
42
- reason: 'unsupported_target',
43
- },
44
- });
45
- return {
46
- status: 'rejected',
47
- purchase,
48
- };
49
- }
50
- const purchase = await input.gateway.commerce.purchase(purchaseInput);
51
- if (target === 'verse8') {
52
- const status = purchase.status === 'completed' ? 'rejected' : purchase.status;
53
- await analytics.track({
54
- name: 'purchase_rejected',
55
- properties: {
56
- productId: purchaseInput.productId,
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
- reason: purchase.status === 'completed'
59
- ? 'verse8_grants_require_agent8_purchase_event'
60
- : purchase.status === 'pending'
61
- ? 'agent8_purchase_event_pending'
62
- : undefined,
63
- },
64
- });
65
- return {
66
- status,
67
- purchase,
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
- if (isAuthoritativeMicrosoftStoreCompletion(target, purchase)) {
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
- ledgerEntryId: purchase.transactionId,
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: purchase.transactionId,
82
- alreadyProcessed: purchase.authoritativeGrant?.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
- const target = input.target;
135
- if (!isGameServicesAdRewardTarget(target)) {
136
- const reward = {
137
- status: 'unavailable',
138
- rewardGranted: false,
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
- await analytics.track({
141
- name: 'rewarded_ad_rejected',
142
- properties: {
143
- placementId: rewardInput.placementId,
144
- status: reward.status,
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: reward.status,
161
- rewardGranted: reward.rewardGranted,
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;
@@ -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,17 +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-purchase';
10
- export * from './notification-delivery';
11
- export * from './platform-order';
12
- 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';
13
14
  export * from './runtime.js';
14
- export * from './server';
15
- export * from './types';
16
- export * from './verified-leaderboard';
17
- 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';