@mpgd/game-services 0.29.0 → 0.30.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.
@@ -461,22 +461,49 @@ export function createCommerceReconciler(input) {
461
461
  trigger();
462
462
  scheduleRun();
463
463
  };
464
- const readAvailable = async (capabilitiesNow) => {
464
+ /**
465
+ * Reads the store's available purchases into the inbox. `restore` is the
466
+ * player's Restore Purchases read only: the store also lists owned
467
+ * non-consumables its recovery list hides (acknowledged on Play, bound to
468
+ * another guest's token on StoreKit), so a reinstall or a new device can
469
+ * restore them. Startup and resume recovery never set it, so an
470
+ * acknowledged durable is not sent again on every launch.
471
+ */
472
+ const readAvailable = async (capabilitiesNow, restore = false) => {
465
473
  if (!capabilitiesNow.lifecycle.availablePurchases) {
466
474
  return true;
467
475
  }
468
- try {
476
+ const read = async (withRestore) => {
469
477
  const available = await provider.getAvailablePurchases({
470
478
  includePending: capabilitiesNow.lifecycle.pending,
479
+ ...(withRestore ? { restore: true } : {}),
471
480
  });
472
481
  for (const purchase of available) {
473
482
  inbox.push({ type: 'purchaseUpdated', purchase });
474
483
  }
484
+ };
485
+ try {
486
+ await read(restore);
475
487
  return true;
476
488
  }
477
489
  catch (error) {
490
+ if (!restore) {
491
+ fail('available', error);
492
+ return false;
493
+ }
494
+ // A failed restore read still makes the recovery read, so Restore
495
+ // Purchases never finds less than a recovery pass: a bridge host built
496
+ // before `restore` rejects the field, and a store may fail only the
497
+ // wider read. The restore failure is still reported.
478
498
  fail('available', error);
479
- return false;
499
+ try {
500
+ await read(false);
501
+ return true;
502
+ }
503
+ catch (fallbackError) {
504
+ fail('available', fallbackError);
505
+ return false;
506
+ }
480
507
  }
481
508
  };
482
509
  const buildRequest = (record) => {
@@ -852,7 +879,8 @@ export function createCommerceReconciler(input) {
852
879
  // read again until it succeeds, so a purchase that predates the
853
880
  // subscription is not missed for the session.
854
881
  if (capabilitiesNow !== undefined && (!capabilitiesNow.lifecycle.events || initialScanPending)) {
855
- initialScanPending = !(await readAvailable(capabilitiesNow)) && capabilitiesNow.lifecycle.events;
882
+ initialScanPending = !(await readAvailable(capabilitiesNow, options.restore === true))
883
+ && capabilitiesNow.lifecycle.events;
856
884
  }
857
885
  // An event for a product whose store call is still running waits for that
858
886
  // call to journal the store answer: matched then, it is never journaled
@@ -1297,11 +1325,18 @@ export function createCommerceReconciler(input) {
1297
1325
  await loadRecords();
1298
1326
  }
1299
1327
  const capabilitiesNow = await readCapabilities(true);
1300
- // A polled provider is read by the pass itself.
1328
+ // A polled provider is read by the pass itself. Either read is a
1329
+ // restore read: owned non-consumables the store's recovery list
1330
+ // hides (a reinstall, a new device) reach the backend as restores.
1331
+ let restoreRead = false;
1301
1332
  if (capabilitiesNow !== undefined && capabilitiesNow.lifecycle.events) {
1302
- await readAvailable(capabilitiesNow);
1333
+ restoreRead = await readAvailable(capabilitiesNow, true);
1334
+ if (restoreRead) {
1335
+ // This read covers a start-time read that failed.
1336
+ initialScanPending = false;
1337
+ }
1303
1338
  }
1304
- await pass({ explicit: true, refreshCapabilities: false });
1339
+ await pass({ explicit: true, refreshCapabilities: false, restore: !restoreRead });
1305
1340
  });
1306
1341
  await runUntilIdle();
1307
1342
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/game-services",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Client, contract, server, store, and test helpers for authoritative mpgd game services.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -231,9 +231,9 @@
231
231
  "@orpc/client": "2.0.0-beta.14",
232
232
  "@orpc/contract": "2.0.0-beta.14",
233
233
  "@orpc/server": "2.0.0-beta.14",
234
- "@mpgd/analytics": "0.5.2",
235
- "@mpgd/platform": "0.29.0",
236
- "@mpgd/catalog": "0.11.0"
234
+ "@mpgd/catalog": "0.11.1",
235
+ "@mpgd/platform": "0.30.0",
236
+ "@mpgd/analytics": "0.5.3"
237
237
  },
238
238
  "peerDependencies": {
239
239
  "@apple/app-store-server-library": "^3.1.0"