@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.
package/dist/types.js CHANGED
@@ -1,18 +1,26 @@
1
+ import { assertOwnEnumerablePropertyLimit, gameServicesRequestLimits, toGameServicesRequestValidationError, } from './validation.js';
2
+ /**
3
+ * Validates an untrusted purchase verification request. Failures throw
4
+ * `GameServicesRequestValidationError` so transports can answer with a stable 400.
5
+ */
1
6
  export function assertVerifyPurchaseRequest(input) {
2
- assertRecord(input, 'VerifyPurchaseRequest');
3
- assertStoreTarget(input.target);
4
- assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
5
- assertNonEmptyString(input.playerId, 'playerId');
6
- assertNonEmptyString(input.productId, 'productId');
7
- assertNonEmptyString(input.platformTransactionId, 'platformTransactionId');
8
- assertNonEmptyString(input.idempotencyKey, 'idempotencyKey');
9
- assertNonEmptyString(input.purchasedAt, 'purchasedAt');
10
- assertOptionalEvidenceEnvelope(input.evidence);
11
- return input;
7
+ return validateRequest(() => {
8
+ assertRecord(input, 'VerifyPurchaseRequest');
9
+ assertStoreTarget(input.target);
10
+ assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
11
+ assertRequestString(input.playerId, 'playerId');
12
+ assertRequestString(input.productId, 'productId');
13
+ assertRequestString(input.platformTransactionId, 'platformTransactionId');
14
+ assertRequestString(input.idempotencyKey, 'idempotencyKey');
15
+ assertRequestString(input.purchasedAt, 'purchasedAt');
16
+ assertOptionalEvidenceEnvelope(input.evidence);
17
+ return input;
18
+ });
12
19
  }
13
20
  export function assertVerifyPurchaseResponse(input) {
14
21
  assertRecord(input, 'VerifyPurchaseResponse');
15
22
  assertBoolean(input.verified, 'verified');
23
+ assertOptionalNonGrantDisposition(input.disposition, input.verified);
16
24
  assertOptionalNonEmptyString(input.ledgerEntryId, 'ledgerEntryId');
17
25
  assertBoolean(input.alreadyProcessed, 'alreadyProcessed');
18
26
  assertOptionalNonEmptyString(input.reason, 'reason');
@@ -42,35 +50,49 @@ export function assertPurchaseGrantFinalization(input) {
42
50
  }
43
51
  return input;
44
52
  }
53
+ /**
54
+ * Validates an untrusted ad reward claim. Failures throw
55
+ * `GameServicesRequestValidationError` so transports can answer with a stable 400.
56
+ */
45
57
  export function assertClaimAdRewardRequest(input) {
46
- assertRecord(input, 'ClaimAdRewardRequest');
47
- assertAdRewardTarget(input.target);
48
- assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
49
- assertNonEmptyString(input.playerId, 'playerId');
50
- assertNonEmptyString(input.placementId, 'placementId');
51
- assertOptionalNonEmptyString(input.platformImpressionId, 'platformImpressionId');
52
- assertNonEmptyString(input.idempotencyKey, 'idempotencyKey');
53
- assertNonEmptyString(input.completedAt, 'completedAt');
54
- assertOptionalEvidenceEnvelope(input.evidence);
55
- return input;
58
+ return validateRequest(() => {
59
+ assertRecord(input, 'ClaimAdRewardRequest');
60
+ assertAdRewardTarget(input.target);
61
+ assertOptionalGameServicesDeploymentTarget(input.deploymentTarget);
62
+ assertRequestString(input.playerId, 'playerId');
63
+ assertRequestString(input.placementId, 'placementId');
64
+ assertOptionalRequestString(input.platformImpressionId, 'platformImpressionId');
65
+ assertRequestString(input.idempotencyKey, 'idempotencyKey');
66
+ assertRequestString(input.completedAt, 'completedAt');
67
+ assertOptionalEvidenceEnvelope(input.evidence);
68
+ return input;
69
+ });
56
70
  }
57
71
  export function assertClaimAdRewardResponse(input) {
58
72
  assertRecord(input, 'ClaimAdRewardResponse');
59
73
  assertBoolean(input.granted, 'granted');
74
+ assertOptionalNonGrantDisposition(input.disposition, input.granted);
60
75
  assertOptionalNonEmptyString(input.ledgerEntryId, 'ledgerEntryId');
61
76
  assertBoolean(input.alreadyProcessed, 'alreadyProcessed');
62
77
  assertOptionalNonEmptyString(input.reason, 'reason');
63
78
  return input;
64
79
  }
80
+ /**
81
+ * Validates an untrusted leaderboard score submission. Failures throw
82
+ * `GameServicesRequestValidationError` so transports can answer with a stable 400.
83
+ */
65
84
  export function assertRecordLeaderboardScoreRequest(input) {
85
+ return validateRequest(() => validateRecordLeaderboardScoreRequest(input));
86
+ }
87
+ function validateRecordLeaderboardScoreRequest(input) {
66
88
  assertRecord(input, 'RecordLeaderboardScoreRequest');
67
89
  assertLeaderboardTarget(input.target);
68
- assertNonEmptyString(input.playerId, 'playerId');
69
- assertNonEmptyString(input.leaderboardId, 'leaderboardId');
90
+ assertRequestString(input.playerId, 'playerId');
91
+ assertRequestString(input.leaderboardId, 'leaderboardId');
70
92
  assertFiniteNumber(input.score, 'score');
71
- assertNonEmptyString(input.runId, 'runId');
72
- assertNonEmptyString(input.submittedAt, 'submittedAt');
73
- assertOptionalNonEmptyString(input.platformSubmissionId, 'platformSubmissionId');
93
+ assertRequestString(input.runId, 'runId');
94
+ assertRequestString(input.submittedAt, 'submittedAt');
95
+ assertOptionalRequestString(input.platformSubmissionId, 'platformSubmissionId');
74
96
  return input;
75
97
  }
76
98
  export function assertRecordLeaderboardScoreResponse(input) {
@@ -117,7 +139,7 @@ export function assertProductGrantTransaction(input) {
117
139
  return input;
118
140
  }
119
141
  export function assertLeaderboardScoreTransaction(input) {
120
- assertRecordLeaderboardScoreRequest(input);
142
+ validateRecordLeaderboardScoreRequest(input);
121
143
  assertNonEmptyString(input.ledgerEntryId, 'ledgerEntryId');
122
144
  assertNonEmptyString(input.recordedAt, 'recordedAt');
123
145
  return input;
@@ -178,11 +200,31 @@ function assertLeaderboardTarget(input) {
178
200
  throw new Error('target must be browser, microsoft-store, android, ios, ait, or reddit.');
179
201
  }
180
202
  }
203
+ function validateRequest(validate) {
204
+ try {
205
+ return validate();
206
+ }
207
+ catch (error) {
208
+ throw toGameServicesRequestValidationError(error);
209
+ }
210
+ }
181
211
  function assertNonEmptyString(input, label) {
182
212
  if (typeof input !== 'string' || input.length === 0) {
183
213
  throw new Error(`${label} must be a non-empty string.`);
184
214
  }
185
215
  }
216
+ /** Client-supplied identifiers feed ledger keys, so they are bounded as well as non-empty. */
217
+ function assertRequestString(input, label) {
218
+ assertNonEmptyString(input, label);
219
+ if (input.length > gameServicesRequestLimits.maxStringLength) {
220
+ throw new Error(`${label} must not exceed ${String(gameServicesRequestLimits.maxStringLength)} characters.`);
221
+ }
222
+ }
223
+ function assertOptionalRequestString(input, label) {
224
+ if (input !== undefined) {
225
+ assertRequestString(input, label);
226
+ }
227
+ }
186
228
  function assertOptionalNonEmptyString(input, label) {
187
229
  if (input !== undefined) {
188
230
  assertNonEmptyString(input, label);
@@ -198,6 +240,17 @@ function assertBoolean(input, label) {
198
240
  throw new Error(`${label} must be a boolean.`);
199
241
  }
200
242
  }
243
+ function assertOptionalNonGrantDisposition(input, granted) {
244
+ if (input === undefined) {
245
+ return;
246
+ }
247
+ if (input !== 'pending' && input !== 'rejected') {
248
+ throw new Error('disposition must be pending or rejected.');
249
+ }
250
+ if (granted) {
251
+ throw new Error('A granted result cannot have a non-grant disposition.');
252
+ }
253
+ }
201
254
  function assertPurchaseGrantFinalizationAction(input) {
202
255
  if (input !== 'acknowledge'
203
256
  && input !== 'consume'
@@ -248,6 +301,7 @@ function assertOptionalEvidenceEnvelope(input) {
248
301
  return;
249
302
  }
250
303
  assertRecord(input, 'evidence');
251
- assertNonEmptyString(input.schema, 'evidence.schema');
304
+ assertRequestString(input.schema, 'evidence.schema');
252
305
  assertPayload(input.payload);
306
+ assertOwnEnumerablePropertyLimit(input.payload, gameServicesRequestLimits.maxEvidencePayloadEntries, 'evidence.payload');
253
307
  }
@@ -1 +1,19 @@
1
+ /** Hard limits applied to untrusted client requests before they reach ledger keys. */
2
+ export declare const gameServicesRequestLimits: {
3
+ /** Maximum length of any identifier or timestamp string in a client request. */
4
+ readonly maxStringLength: 256;
5
+ /** Maximum number of entries accepted inside a platform evidence payload. */
6
+ readonly maxEvidencePayloadEntries: 64;
7
+ /** Maximum request body size accepted by the HTTP and oRPC fetch handlers. */
8
+ readonly maxBodyBytes: number;
9
+ };
10
+ /**
11
+ * A client-supplied request failed validation. Handlers map this error to a 400 response
12
+ * with the stable `INVALID_REQUEST` code; every other error is treated as internal.
13
+ */
14
+ export declare class GameServicesRequestValidationError extends Error {
15
+ readonly code = "INVALID_REQUEST";
16
+ constructor(message: string);
17
+ }
18
+ export declare function toGameServicesRequestValidationError(error: unknown): GameServicesRequestValidationError;
1
19
  export declare function assertOwnEnumerablePropertyLimit(input: Record<string, unknown>, maximum: number, label: string): void;
@@ -1,3 +1,29 @@
1
+ /** Hard limits applied to untrusted client requests before they reach ledger keys. */
2
+ export const gameServicesRequestLimits = {
3
+ /** Maximum length of any identifier or timestamp string in a client request. */
4
+ maxStringLength: 256,
5
+ /** Maximum number of entries accepted inside a platform evidence payload. */
6
+ maxEvidencePayloadEntries: 64,
7
+ /** Maximum request body size accepted by the HTTP and oRPC fetch handlers. */
8
+ maxBodyBytes: 64 * 1024,
9
+ };
10
+ /**
11
+ * A client-supplied request failed validation. Handlers map this error to a 400 response
12
+ * with the stable `INVALID_REQUEST` code; every other error is treated as internal.
13
+ */
14
+ export class GameServicesRequestValidationError extends Error {
15
+ code = 'INVALID_REQUEST';
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = 'GameServicesRequestValidationError';
19
+ }
20
+ }
21
+ export function toGameServicesRequestValidationError(error) {
22
+ if (error instanceof GameServicesRequestValidationError) {
23
+ return error;
24
+ }
25
+ return new GameServicesRequestValidationError(error instanceof Error ? error.message : 'Request validation failed.');
26
+ }
1
27
  export function assertOwnEnumerablePropertyLimit(input, maximum, label) {
2
28
  let propertyCount = 0;
3
29
  for (const key in input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/game-services",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "default": "./dist/index.js"
31
31
  },
32
+ "./admob-client-reward": {
33
+ "types": "./dist/admob-client-reward.d.ts",
34
+ "default": "./dist/admob-client-reward.js"
35
+ },
32
36
  "./admob-ssv": {
33
37
  "types": "./dist/admob-ssv.d.ts",
34
38
  "default": "./dist/admob-ssv.js"
@@ -41,6 +45,10 @@
41
45
  "types": "./dist/app-store-verifier.d.ts",
42
46
  "default": "./dist/app-store-verifier.js"
43
47
  },
48
+ "./app-store-recovery": {
49
+ "types": "./dist/app-store-recovery.d.ts",
50
+ "default": "./dist/app-store-recovery.js"
51
+ },
44
52
  "./app-store-verifier-conformance": {
45
53
  "types": "./dist/app-store-verifier-conformance.d.ts",
46
54
  "default": "./dist/app-store-verifier-conformance.js"
@@ -73,6 +81,10 @@
73
81
  "types": "./dist/google-play-purchase.d.ts",
74
82
  "default": "./dist/google-play-purchase.js"
75
83
  },
84
+ "./google-play-publisher-client": {
85
+ "types": "./dist/google-play-publisher-client.d.ts",
86
+ "default": "./dist/google-play-publisher-client.js"
87
+ },
76
88
  "./guest-session": {
77
89
  "types": "./dist/guest-session.d.ts",
78
90
  "default": "./dist/guest-session.js"
@@ -142,9 +154,9 @@
142
154
  "@orpc/client": "2.0.0-beta.14",
143
155
  "@orpc/contract": "2.0.0-beta.14",
144
156
  "@orpc/server": "2.0.0-beta.14",
145
- "@mpgd/analytics": "0.3.15",
146
- "@mpgd/platform": "0.13.0",
147
- "@mpgd/catalog": "0.7.5"
157
+ "@mpgd/analytics": "0.4.0",
158
+ "@mpgd/catalog": "0.7.6",
159
+ "@mpgd/platform": "0.14.0"
148
160
  },
149
161
  "devDependencies": {
150
162
  "@opentelemetry/api": "1.9.0",
@@ -163,7 +175,8 @@
163
175
  "fix": "ttsc fix",
164
176
  "build": "cd ../.. && node tools/run-ttsx.mjs tools/package/build-packages.ts @mpgd/game-services",
165
177
  "test:packed-transport": "pnpm build && node test/runtime-transport-package-import.mjs",
166
- "test:unit": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-progress.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime.test.ts && node tools/run-ttsx.mjs packages/game-services/src/types.test.ts && node tools/run-ttsx.mjs packages/game-services/src/server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/microsoft-store-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/microsoft-store-identity.test.ts && node tools/run-ttsx.mjs packages/game-services/src/platform-order.test.ts && node tools/run-ttsx.mjs packages/game-services/src/progress-link.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notification-delivery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/admob-ssv.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-verifier.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-evidence-verification-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-partner-api.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-durability-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-transport.test.ts && node tools/run-ttsx.mjs packages/game-services/src/guest-session.test.ts",
167
- "test": "pnpm test:unit && pnpm test:packed-transport"
178
+ "test:unit": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/client-progress.test.ts && node tools/run-ttsx.mjs packages/game-services/src/runtime.test.ts && node tools/run-ttsx.mjs packages/game-services/src/types.test.ts && node tools/run-ttsx.mjs packages/game-services/src/server.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/google-play-publisher-client.test.ts && node tools/run-ttsx.mjs packages/game-services/src/microsoft-store-purchase.test.ts && node tools/run-ttsx.mjs packages/game-services/src/microsoft-store-identity.test.ts && node tools/run-ttsx.mjs packages/game-services/src/platform-order.test.ts && node tools/run-ttsx.mjs packages/game-services/src/progress-link.test.ts && node tools/run-ttsx.mjs packages/game-services/src/notification-delivery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/admob-ssv.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-verifier.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-evidence-verification-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/apps-in-toss-partner-api.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-durability-conformance.test.ts && node tools/run-ttsx.mjs packages/game-services/src/verified-leaderboard-transport.test.ts && node tools/run-ttsx.mjs packages/game-services/src/guest-session.test.ts",
179
+ "test:recovery": "cd ../.. && node tools/run-ttsx.mjs packages/game-services/src/monetization-recovery.test.ts && node tools/run-ttsx.mjs packages/game-services/src/app-store-recovery.test.ts",
180
+ "test": "pnpm test:unit && pnpm test:recovery && pnpm test:packed-transport"
168
181
  }
169
182
  }