@mdbase-dev/sdk 0.1.0-beta.178 → 0.1.0-beta.179

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.
@@ -15691,12 +15691,12 @@ function appReleaseTrust() {
15691
15691
  // src/application/asset-pins.json
15692
15692
  var asset_pins_default = {
15693
15693
  runtime: {
15694
- sha256: "5f296cb57d8c1da1a9710f91a893efece2340ff893583b088feb531a16ced2ef",
15695
- bytes: 3849226,
15696
- maxBytes: 3849226,
15694
+ sha256: "be17475e2ca164642fb5b29b4bf003c03ad9a8dbacd8aebc135dad7e91b73585",
15695
+ bytes: 3849256,
15696
+ maxBytes: 3849256,
15697
15697
  source: {
15698
15698
  repository: "mdbase-dev/mdbase-connect",
15699
- commit: "61dd7650312f04e942de51536b6441142c79ec48"
15699
+ commit: "323d30aff40fc6f3f172d44f60cd7e67bb17d977"
15700
15700
  }
15701
15701
  },
15702
15702
  sqlite: {
@@ -17790,6 +17790,23 @@ var AppProtectedInstallationSignIn = class _AppProtectedInstallationSignIn {
17790
17790
  }
17791
17791
  /** Current approved/member cloud-copy metadata only; never readiness, keying,
17792
17792
  * or permission inferred from this response or retained consent snapshots. */
17793
+ /** The kept listing narrowed to this installation's consent. File actions
17794
+ * are never kept, so every row has none until the next read. */
17795
+ keptCollections() {
17796
+ if (this.state.collectionListing === void 0) return null;
17797
+ return Object.freeze(collectionListing(this.state.collectionListing).filter((c) => this.state.collectionIds.includes(c.collectionId) && this.modeConsented(c)).map((c) => Object.freeze({ ...c, fileActions: Object.freeze([]) })));
17798
+ }
17799
+ /** The approved collections as this installation last read them, or null
17800
+ * when no listing is kept. No request. It can be out of date in every way a
17801
+ * list can; `listApprovedCollections` is still the authority. */
17802
+ cachedApprovedCollections(installation) {
17803
+ this.consentGuard(installation)();
17804
+ try {
17805
+ return this.keptCollections();
17806
+ } catch {
17807
+ return null;
17808
+ }
17809
+ }
17793
17810
  listApprovedCollections(installation) {
17794
17811
  const check3 = this.consentGuard(installation);
17795
17812
  return this.exclusive(async () => {
@@ -17806,7 +17823,7 @@ var AppProtectedInstallationSignIn = class _AppProtectedInstallationSignIn {
17806
17823
  } catch (error4) {
17807
17824
  check3();
17808
17825
  if (error4 instanceof AppInstallationSignInError && error4.reason === "unavailable" && !error4.server && !nonOkResponses.has(error4) && this.state.collectionListing !== void 0)
17809
- return Object.freeze(collectionListing(this.state.collectionListing).filter((c) => this.state.collectionIds.includes(c.collectionId) && this.modeConsented(c)).map((c) => Object.freeze({ ...c, fileActions: Object.freeze([]) })));
17826
+ return this.keptCollections();
17810
17827
  if (error4 instanceof AppInstallationSignInError && (error4.server || nonOkResponses.has(error4)) && this.state.collectionListing !== void 0) {
17811
17828
  const state2 = { ...this.state };
17812
17829
  delete state2.collectionListing;
@@ -36932,6 +36949,15 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
36932
36949
  return null;
36933
36950
  }
36934
36951
  }
36952
+ /** For a collection opened before its log: has the reopened replica its
36953
+ * keys back from the log's control prefix? `failed` is what a waited open
36954
+ * refuses as `recovery_required`. */
36955
+ keyringSettled() {
36956
+ if (!this.native) throw fail15("binding");
36957
+ const observation = this.native.observations();
36958
+ if (observation.requiresReopen || observation.keyringRebuildFailed) return "failed";
36959
+ return observation.keyringRebuilding ? "pending" : "settled";
36960
+ }
36935
36961
  /** Local writes not yet confirmed by the log (native sync status). */
36936
36962
  pendingWrites() {
36937
36963
  try {
@@ -37016,16 +37042,16 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37016
37042
  * behind can finish its catch-up turn; without a pump it is fixed.
37017
37043
  * No status-based admission, write retry, key override or private grant. */
37018
37044
  waitForVerifiedRead(options) {
37019
- const { timeoutMs = 15e3, cold = false } = options;
37045
+ const { timeoutMs = 15e3, cold = false, local = false } = options;
37020
37046
  const signal = this.operationSignal(options.signal);
37021
37047
  this.checkCollection(signal);
37022
- if (!this.native || !this.collectionScope || this.adoptedEndpoint === null || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 15e3 || typeof cold !== "boolean" || cold && !this.pump)
37048
+ if (!this.native || !this.collectionScope || this.adoptedEndpoint === null || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 15e3 || typeof cold !== "boolean" || typeof local !== "boolean" || cold && (local || !this.pump))
37023
37049
  throw fail15("binding");
37024
37050
  if (this.readWork)
37025
37051
  return this.readWork.then(() => {
37026
37052
  this.checkCollection(signal);
37027
37053
  });
37028
- const work2 = this.verifyRead(signal, timeoutMs, cold, options.onProgress);
37054
+ const work2 = this.verifyRead(signal, timeoutMs, cold, local, options.onProgress);
37029
37055
  this.readWork = work2;
37030
37056
  void work2.finally(() => {
37031
37057
  if (this.readWork === work2) this.readWork = null;
@@ -37033,8 +37059,9 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37033
37059
  });
37034
37060
  return work2;
37035
37061
  }
37036
- async verifyRead(signal, timeoutMs, cold, onProgress) {
37062
+ async verifyRead(signal, timeoutMs, cold, local, onProgress) {
37037
37063
  const ctrl = new AbortController(), abort = () => ctrl.abort();
37064
+ const pump = () => local ? null : this.pump;
37038
37065
  this.readAbort = ctrl;
37039
37066
  signal.addEventListener("abort", abort, { once: true });
37040
37067
  let timer = null;
@@ -37057,7 +37084,7 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37057
37084
  };
37058
37085
  const observe = () => {
37059
37086
  this.checkCollection(signal);
37060
- const refusal2 = cold ? this.pump.terminalProblem : null;
37087
+ const refusal2 = cold ? pump().terminalProblem : null;
37061
37088
  if (isAppServerProblem(refusal2))
37062
37089
  throw new AppHttpLogError(appServerOutcome(refusal2) ?? "unavailable", refusal2);
37063
37090
  const observation = this.native.observations();
@@ -37068,14 +37095,14 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37068
37095
  };
37069
37096
  this.readVerified = false;
37070
37097
  try {
37071
- if (this.pump) {
37098
+ if (pump()) {
37072
37099
  const progress2 = () => {
37073
37100
  const observation = observe();
37074
37101
  return [
37075
37102
  observation.status.confirmedThrough,
37076
37103
  observation.status.installing?.done ?? 0,
37077
37104
  observation.status.installing?.total ?? 0,
37078
- this.pump.readProgress
37105
+ pump().readProgress
37079
37106
  ];
37080
37107
  };
37081
37108
  let high = progress2(), lastProgress = Date.now();
@@ -37089,7 +37116,7 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37089
37116
  deferred2 = null;
37090
37117
  }
37091
37118
  high = next.map((value2, i) => Math.max(value2, high[i]));
37092
- const active = this.pump.responseProgress();
37119
+ const active = pump().responseProgress();
37093
37120
  const ids = new Set(active.map((body) => body.id));
37094
37121
  for (const id4 of bodies.keys()) if (!ids.has(id4)) bodies.delete(id4);
37095
37122
  for (const body of active) {
@@ -37134,7 +37161,8 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37134
37161
  let delay = 250;
37135
37162
  while (!ctrl.signal.aborted) {
37136
37163
  this.native.tick();
37137
- if (this.pump) await Promise.race([this.pump.pump(), aborted]);
37164
+ const turn = pump();
37165
+ if (turn) await Promise.race([turn.pump(), aborted]);
37138
37166
  this.checkCollection(signal);
37139
37167
  const observation = observe();
37140
37168
  if (cold && initialSyncWait(observation.status) !== null) {
@@ -37153,7 +37181,7 @@ var AppWebCloudCopyHost = class _AppWebCloudCopyHost {
37153
37181
  return;
37154
37182
  } catch (error4) {
37155
37183
  this.checkCollection(signal);
37156
- if (!(error4 instanceof MdbaseError) || error4.code !== "unavailable" || !this.pump || ctrl.signal.aborted)
37184
+ if (!(error4 instanceof MdbaseError) || error4.code !== "unavailable" || !pump() || ctrl.signal.aborted)
37157
37185
  throw error4;
37158
37186
  }
37159
37187
  await waitForOpenRetry(delay, ctrl.signal);
@@ -37787,6 +37815,7 @@ var AppWebSignInSession = class {
37787
37815
  attested = false;
37788
37816
  acknowledged = false;
37789
37817
  consentPending = false;
37818
+ listCached = false;
37790
37819
  fileAccess = null;
37791
37820
  freshApproval = false;
37792
37821
  /** The window this session's approval (or file access request) is shown in. */
@@ -37980,7 +38009,15 @@ var AppWebSignInSession = class {
37980
38009
  if (this.flow.collectionConsentView()?.state === "pending") {
37981
38010
  this.consentPending = true;
37982
38011
  this.publish({ status: "authorizing" });
37983
- } else await this.refreshCollections();
38012
+ } else {
38013
+ let kept = null;
38014
+ try {
38015
+ kept = this.flow.cachedApprovedCollections(this.installation);
38016
+ } catch {
38017
+ kept = null;
38018
+ }
38019
+ await this.refreshCollections(kept);
38020
+ }
37984
38021
  } else this.publish({ status: view.state === "pending" ? "signed_out" : "authorizing" });
37985
38022
  return this.getSnapshot();
37986
38023
  });
@@ -38338,10 +38375,15 @@ var AppWebSignInSession = class {
38338
38375
  this.acknowledged = true;
38339
38376
  }
38340
38377
  }
38341
- async refreshCollections() {
38342
- const collections = await this.flow.listApprovedCollections(this.installation);
38378
+ /** `kept`: this installation's last listing instead of a read. It is not an
38379
+ * accepted inventory, so an approval missing from it is not a removal. */
38380
+ async refreshCollections(kept = null) {
38381
+ const collections = kept ?? await this.flow.listApprovedCollections(this.installation);
38343
38382
  this.check();
38344
- for (const id4 of this.flow.view().collectionIds) if (!collections.some((c) => c.collectionId === id4)) this.removedScopes.add(id4);
38383
+ this.listCached = kept !== null;
38384
+ if (!kept) {
38385
+ for (const id4 of this.flow.view().collectionIds) if (!collections.some((c) => c.collectionId === id4)) this.removedScopes.add(id4);
38386
+ }
38345
38387
  const visible = Object.freeze(collections.filter((c) => !this.forgotten.has(c.collectionId)));
38346
38388
  for (const slot of this.slots.values()) {
38347
38389
  if (slot.usable && slot.selected && !visible.some((c) => c.collectionId === slot.id)) {
@@ -38549,14 +38591,24 @@ var AppWebSignInSession = class {
38549
38591
  if (this.openingSlot === slot) this.openingSlot = null;
38550
38592
  slot.stopAssessment = selected.subscribe?.(() => {
38551
38593
  if (this.closing || this.slots.get(slot.id) !== slot || !slot.usable || slot.selected !== selected || slot.lifetime?.signal.aborted || !this.sameScope(slot.scope, slot.leaseScope) || slot.scope?.collection !== slot.id) return;
38552
- let ended = null;
38594
+ let ended2 = null;
38595
+ try {
38596
+ ended2 = selected.ended ?? null;
38597
+ } catch {
38598
+ ended2 = null;
38599
+ }
38600
+ if (ended2) {
38601
+ this.accessEnded(slot, selected, ended2);
38602
+ return;
38603
+ }
38604
+ let failed2 = null;
38553
38605
  try {
38554
- ended = selected.ended ?? null;
38606
+ failed2 = selected.failure ?? null;
38555
38607
  } catch {
38556
- ended = null;
38608
+ failed2 = null;
38557
38609
  }
38558
- if (ended) {
38559
- this.accessEnded(slot, selected, ended);
38610
+ if (failed2 !== null) {
38611
+ this.openFailedLate(slot, selected, failed2);
38560
38612
  return;
38561
38613
  }
38562
38614
  let next;
@@ -38567,6 +38619,26 @@ var AppWebSignInSession = class {
38567
38619
  }
38568
38620
  this.publishSlot(slot, next.status === "needs_unlock" ? { ...next, problem: null, transport: null, live: null } : { ...next, ...this.liveProblem(slot, selected) });
38569
38621
  }) ?? null;
38622
+ let ended = null;
38623
+ try {
38624
+ ended = selected.ended ?? null;
38625
+ } catch {
38626
+ ended = null;
38627
+ }
38628
+ let failed = null;
38629
+ try {
38630
+ failed = selected.failure ?? null;
38631
+ } catch {
38632
+ failed = null;
38633
+ }
38634
+ if (ended) {
38635
+ this.accessEnded(slot, selected, ended);
38636
+ return;
38637
+ }
38638
+ if (failed !== null) {
38639
+ this.openFailedLate(slot, selected, failed);
38640
+ return;
38641
+ }
38570
38642
  this.publishSlot(slot, { ...first, transport: null, live: null, openingProgress: null });
38571
38643
  }
38572
38644
  /** The adapter's own state, validated: `needs_unlock` only with its lock
@@ -38654,6 +38726,35 @@ var AppWebSignInSession = class {
38654
38726
  };
38655
38727
  stop();
38656
38728
  }
38729
+ /** A collection opened from its replica, whose log was then refused. The
38730
+ * same block and detail as a failed open; stopping it fences every page's
38731
+ * data ports. Local data, unsent edits included, stays on this device. */
38732
+ openFailedLate(slot, selected, error4) {
38733
+ if (slot.selected !== selected || !slot.usable) return;
38734
+ const { problem: problem4, detail } = webSignInProblem(error4);
38735
+ if (problem4 === "open_failed") {
38736
+ try {
38737
+ console.error("mdbase web sign-in open failed", detail.name, detail.code);
38738
+ } catch {
38739
+ }
38740
+ }
38741
+ slot.usable = false;
38742
+ this.publishSlot(slot, { status: "blocked", problem: problem4, problemDetail: detail, transport: null, live: null });
38743
+ const stop = () => {
38744
+ if (this.closing || this.options.signal.aborted || slot.selected !== selected || this.slots.get(slot.id) !== slot) return;
38745
+ if (this.pending) {
38746
+ void this.pending.catch(() => {
38747
+ }).then(stop);
38748
+ return;
38749
+ }
38750
+ void this.operation(async () => {
38751
+ await this.closeSlot(slot);
38752
+ this.publishSlot(slot, { status: "blocked", problem: problem4, problemDetail: detail });
38753
+ }, true).catch(() => {
38754
+ });
38755
+ };
38756
+ stop();
38757
+ }
38657
38758
  /** Live problem of an open collection: shown while it lasts, cleared after. */
38658
38759
  liveProblem(slot, selected) {
38659
38760
  let server = null;
@@ -38729,6 +38830,12 @@ var AppWebSignInSession = class {
38729
38830
  return this.getSnapshot();
38730
38831
  }, true);
38731
38832
  }
38833
+ /** The published list is the one this installation kept, shown by a
38834
+ * signed-in start; Connect's has not been read since. The owner calls
38835
+ * `refreshCollectionList` once, when that read can no longer delay a page. */
38836
+ collectionListCached() {
38837
+ return this.listCached;
38838
+ }
38732
38839
  /** Accepted COMPLETE inventory omission or terminal native/log refusal only.
38733
38840
  * Local Forget, failed GETs and transient transport failures do not qualify. */
38734
38841
  scopeWasRemoved(collectionId) {
@@ -39184,6 +39291,10 @@ var AppScopedSignInSession = class {
39184
39291
  await session.pollFileAccess(requestId);
39185
39292
  return this.getSnapshot();
39186
39293
  }
39294
+ /** Any of the sessions still shows the list it kept (a signed-in start). */
39295
+ collectionListCached() {
39296
+ return this.original.collectionListCached() || [...this.scoped.values()].some((actor) => actor.session.collectionListCached());
39297
+ }
39187
39298
  async refreshCollectionList() {
39188
39299
  await this.original.refreshCollectionList();
39189
39300
  for (const actor of this.scoped.values()) {
@@ -39919,6 +40030,7 @@ function createAppCollectionSql(source) {
39919
40030
  var PAUSED_AFTER_MS = 1e4;
39920
40031
  var BACKGROUND_FLUSH_MS = 12e4;
39921
40032
  var WARM_SYNC_MS = 2e3;
40033
+ var LOCAL_READ_MS = 5e3;
39922
40034
  var INITIAL_LOG_RETRY_MS = 15e3;
39923
40035
  var INITIAL_LOG_ATTEMPTS = 3;
39924
40036
  var AppWebCollectionError = class extends Error {
@@ -39959,6 +40071,10 @@ function createAppWebCollectionAdapter(source) {
39959
40071
  let assessment = "setup_required";
39960
40072
  let locked = null;
39961
40073
  let started = false;
40074
+ let logBound = false;
40075
+ let early = null;
40076
+ let failure2 = null;
40077
+ let keys2 = "settled";
39962
40078
  let push = null, paused = false, pausing = null, kicked = false;
39963
40079
  let ended = null;
39964
40080
  const attached = /* @__PURE__ */ new Map();
@@ -40119,6 +40235,79 @@ function createAppWebCollectionAdapter(source) {
40119
40235
  return false;
40120
40236
  }
40121
40237
  };
40238
+ const startLog = () => host.startCollectionLog({ signal: life.signal, fetch: options.fetch, allowLoopbackHttp: options.allowLoopbackHttp });
40239
+ const openPush = () => {
40240
+ try {
40241
+ push = typeof host.openLogPush === "function" ? host.openLogPush({
40242
+ push: (frame) => {
40243
+ if (!current2()) return;
40244
+ host.pushLog(frame);
40245
+ kick();
40246
+ },
40247
+ live: onLive,
40248
+ // Access ended: the replica has the `closed` push; drive so it
40249
+ // settles, then the session stops this collection in every tab.
40250
+ ended: (reason) => {
40251
+ ended = reason;
40252
+ kick();
40253
+ onLive(false);
40254
+ settleInitial?.();
40255
+ changed();
40256
+ }
40257
+ }) : null;
40258
+ } catch {
40259
+ push = null;
40260
+ }
40261
+ if (push) {
40262
+ push.start();
40263
+ onLive(false);
40264
+ } else setPaused(true);
40265
+ };
40266
+ const bindLog = async () => {
40267
+ const first = early;
40268
+ early = null;
40269
+ await (first ?? startLog());
40270
+ check3();
40271
+ logBound = true;
40272
+ openPush();
40273
+ };
40274
+ const settleKeys = () => {
40275
+ if (keys2 !== "pending" || !logBound) return;
40276
+ try {
40277
+ keys2 = host.keyringSettled?.() ?? "settled";
40278
+ } catch {
40279
+ return;
40280
+ }
40281
+ if (keys2 === "failed") refuse(new AppCloudCopyHostError("recovery_required"));
40282
+ };
40283
+ const refuse = (error4) => {
40284
+ failure2 = error4;
40285
+ started = false;
40286
+ if (timer !== null) clearTimeout(timer);
40287
+ timer = null;
40288
+ changed();
40289
+ };
40290
+ const bindFailed = (error4) => {
40291
+ let server = null;
40292
+ try {
40293
+ server = error4.server;
40294
+ } catch {
40295
+ server = null;
40296
+ }
40297
+ const outcome = isAppServerProblem(server) && appServerOutcome(server) !== null ? server : null;
40298
+ const foreign = !isAppServerProblem(server) && error4 instanceof AppCpAuthorityError && error4.reason === "response";
40299
+ const next = outcome ? null : foreign ? { kind: "unreachable" } : appLogHealth(error4);
40300
+ if (!outcome && (next === null || next.kind === "auth")) {
40301
+ refuse(error4);
40302
+ return;
40303
+ }
40304
+ unhealthy = next ? unhealthy + 1 : 0;
40305
+ if (outcome !== problem4 || JSON.stringify(next) !== JSON.stringify(health)) {
40306
+ problem4 = outcome;
40307
+ health = next;
40308
+ changed();
40309
+ }
40310
+ };
40122
40311
  const schedule = () => {
40123
40312
  if (!started || !current2()) return;
40124
40313
  if (!foreground && !flushing() && !kicked) {
@@ -40134,13 +40323,16 @@ function createAppWebCollectionAdapter(source) {
40134
40323
  if (!current2()) return;
40135
40324
  const wake = foreground && Date.now() >= wakeAt;
40136
40325
  if (wake) wakeAt = Number.POSITIVE_INFINITY;
40137
- const work2 = driving = wake ? host.wakeCollectionLog({ signal: life.signal }) : host.driveCollection({ signal: life.signal });
40326
+ const work2 = driving = !logBound ? bindLog().then(() => host.driveCollection({ signal: life.signal })) : wake ? host.wakeCollectionLog({ signal: life.signal }) : host.driveCollection({ signal: life.signal });
40138
40327
  void work2.then(
40139
40328
  () => {
40140
40329
  if (driving === work2) driving = null;
40141
40330
  failures = 0;
40142
40331
  turned = true;
40143
- if (current2()) observe();
40332
+ if (current2()) {
40333
+ observe();
40334
+ settleKeys();
40335
+ }
40144
40336
  settleInitial?.();
40145
40337
  schedule();
40146
40338
  },
@@ -40149,7 +40341,12 @@ function createAppWebCollectionAdapter(source) {
40149
40341
  (error4) => {
40150
40342
  if (driving === work2) driving = null;
40151
40343
  failures++;
40152
- if (current2()) observe();
40344
+ if (current2()) {
40345
+ if (logBound) {
40346
+ observe();
40347
+ settleKeys();
40348
+ } else bindFailed(error4);
40349
+ }
40153
40350
  turnFailed(error4);
40154
40351
  settleInitial?.();
40155
40352
  schedule();
@@ -40301,24 +40498,34 @@ function createAppWebCollectionAdapter(source) {
40301
40498
  report("opening_store");
40302
40499
  await host.openCollectionSql({ signal: life.signal, replicaId: receipt3.deviceId, endpoint: 0, openSql: openLedgerSql });
40303
40500
  check3();
40304
- report("connecting_log");
40305
- const retryUntil = Date.now() + INITIAL_LOG_RETRY_MS;
40306
- for (let attempt = 0; ; attempt++) {
40307
- try {
40308
- await host.startCollectionLog({ signal: life.signal, fetch: options.fetch, allowLoopbackHttp: options.allowLoopbackHttp });
40309
- check3();
40310
- break;
40311
- } catch (error4) {
40312
- check3();
40313
- const server = error4 instanceof AppCpAuthorityError && error4.reason === "unavailable" ? error4.server : null;
40314
- if (!isAppServerProblem(server) || server.status !== 503 || server.code !== "busy" || appServerOutcome(server) !== null || attempt + 1 >= INITIAL_LOG_ATTEMPTS) throw error4;
40315
- const wait2 = server.retryAfterMs ?? 1e3 * 2 ** attempt;
40316
- if (Date.now() + wait2 >= retryUntil) throw error4;
40317
- await waitForOpenRetry(wait2, life.signal);
40318
- check3();
40319
- if (Date.now() >= retryUntil) throw error4;
40501
+ const localFirst = !isPrivate && await ledger.syncedBefore({ signal: life.signal });
40502
+ check3();
40503
+ const connectLog = async () => {
40504
+ report("connecting_log");
40505
+ const retryUntil = Date.now() + INITIAL_LOG_RETRY_MS;
40506
+ for (let attempt = 0; ; attempt++) {
40507
+ try {
40508
+ await startLog();
40509
+ check3();
40510
+ break;
40511
+ } catch (error4) {
40512
+ check3();
40513
+ const server = error4 instanceof AppCpAuthorityError && error4.reason === "unavailable" ? error4.server : null;
40514
+ if (!isAppServerProblem(server) || server.status !== 503 || server.code !== "busy" || appServerOutcome(server) !== null || attempt + 1 >= INITIAL_LOG_ATTEMPTS) throw error4;
40515
+ const wait2 = server.retryAfterMs ?? 1e3 * 2 ** attempt;
40516
+ if (Date.now() + wait2 >= retryUntil) throw error4;
40517
+ await waitForOpenRetry(wait2, life.signal);
40518
+ check3();
40519
+ if (Date.now() >= retryUntil) throw error4;
40520
+ }
40320
40521
  }
40321
- }
40522
+ logBound = true;
40523
+ };
40524
+ if (localFirst) {
40525
+ early = startLog();
40526
+ void early.catch(() => {
40527
+ });
40528
+ } else await connectLog();
40322
40529
  const generation = crypto.randomUUID();
40323
40530
  let attachOpened = null;
40324
40531
  let detachOpened = null;
@@ -40333,44 +40540,48 @@ function createAppWebCollectionAdapter(source) {
40333
40540
  }
40334
40541
  }
40335
40542
  const finishOpen = async () => {
40336
- const warm = await ledger.syncedBefore({ signal: life.signal });
40543
+ const warm = isPrivate ? await ledger.syncedBefore({ signal: life.signal }) : localFirst;
40337
40544
  check3();
40338
- report("syncing");
40339
- await host.waitForVerifiedRead({ signal: life.signal, cold: !warm, onProgress: report });
40340
- check3();
40341
- started = true;
40342
- host.onLogWork?.(kick);
40343
- schedule();
40344
- try {
40345
- push = typeof host.openLogPush === "function" ? host.openLogPush({
40346
- push: (frame) => {
40347
- if (!current2()) return;
40348
- host.pushLog(frame);
40349
- kick();
40350
- },
40351
- live: onLive,
40352
- // Access ended: the replica has the `closed` push; drive so it
40353
- // settles, then the session stops this collection in every tab.
40354
- ended: (reason) => {
40355
- ended = reason;
40356
- kick();
40357
- onLive(false);
40358
- settleInitial?.();
40359
- changed();
40360
- }
40361
- }) : null;
40362
- } catch {
40363
- push = null;
40545
+ let local = false;
40546
+ if (localFirst && !logBound) {
40547
+ try {
40548
+ await host.waitForVerifiedRead({ signal: life.signal, local: true, timeoutMs: LOCAL_READ_MS, onProgress: report });
40549
+ check3();
40550
+ local = true;
40551
+ } catch {
40552
+ check3();
40553
+ const first = early;
40554
+ early = null;
40555
+ const bound = first ? await first.then(() => true, () => false) : false;
40556
+ check3();
40557
+ if (bound) logBound = true;
40558
+ else await connectLog();
40559
+ }
40364
40560
  }
40365
- if (push) {
40366
- push.start();
40367
- onLive(false);
40368
- } else paused = true;
40369
- if (warm) {
40561
+ if (!local) {
40370
40562
  report("syncing");
40371
- await waitForWarmSync();
40563
+ await host.waitForVerifiedRead({ signal: life.signal, cold: !warm, onProgress: report });
40564
+ check3();
40565
+ }
40566
+ started = true;
40567
+ host.onLogWork?.(kick);
40568
+ if (local) {
40569
+ keys2 = "pending";
40570
+ kicked = true;
40571
+ schedule();
40572
+ pausing = setTimeout(() => {
40573
+ pausing = null;
40574
+ if (current2() && !push?.live) setPaused(true);
40575
+ }, PAUSED_AFTER_MS);
40576
+ } else {
40577
+ schedule();
40578
+ openPush();
40579
+ if (warm) {
40580
+ report("syncing");
40581
+ await waitForWarmSync();
40582
+ }
40583
+ check3();
40372
40584
  }
40373
- check3();
40374
40585
  report("checking_setup");
40375
40586
  void ledger.markSynced({ signal: life.signal }).catch(() => {
40376
40587
  });
@@ -40557,6 +40768,9 @@ function createAppWebCollectionAdapter(source) {
40557
40768
  get ended() {
40558
40769
  return current2() ? ended : null;
40559
40770
  },
40771
+ get failure() {
40772
+ return current2() ? failure2 : null;
40773
+ },
40560
40774
  connection,
40561
40775
  close,
40562
40776
  subscribe(listener) {
@@ -41280,6 +41494,8 @@ function wipeUnlockSecret(request3) {
41280
41494
  }
41281
41495
  }
41282
41496
  var APPLICATION_WORKER_MAX_CLIENTS = 32;
41497
+ var LIST_CONFIRM_MS = 2e3;
41498
+ var LIST_CONFIRM_TRIES = 3;
41283
41499
  var ApplicationWorkerHost = class {
41284
41500
  session;
41285
41501
  lifetime = new AbortController();
@@ -41293,6 +41509,10 @@ var ApplicationWorkerHost = class {
41293
41509
  sessionForeground = null;
41294
41510
  collecting = false;
41295
41511
  revalidationQueued = false;
41512
+ /** A signed-in start showed the kept collection list; Connect's is read once. */
41513
+ listConfirm = "idle";
41514
+ listTimer = null;
41515
+ listAttempts = 0;
41296
41516
  nativeRpc = 0;
41297
41517
  nativeWait = null;
41298
41518
  /** The latest proxy portal's person-closed report (page `approvalClosed`). */
@@ -41697,18 +41917,55 @@ var ApplicationWorkerHost = class {
41697
41917
  /** One inventory read, in the SAME queue as lifecycle actions. Visibility and
41698
41918
  * native transport wake immediately; metadata cannot make a tab's start/select
41699
41919
  * fail busy or overlap an original consent/receipt operation. */
41700
- revalidateForeground() {
41920
+ revalidateForeground(shown = true) {
41701
41921
  if (this.stopping || this.revalidationQueued) return;
41702
41922
  this.revalidationQueued = true;
41703
41923
  this.queue.push({ client: null, request: null, job: async () => {
41704
41924
  try {
41705
- if (this.foreground && this.session.getSnapshot().signIn?.state === "paired") await this.session.refreshCollectionList();
41925
+ if ((this.foreground || !shown) && this.session.getSnapshot().signIn?.state === "paired") await this.session.refreshCollectionList();
41706
41926
  } finally {
41707
41927
  this.revalidationQueued = false;
41708
41928
  }
41709
41929
  } });
41710
41930
  this.pump();
41711
41931
  }
41932
+ /** A signed-in start published the collection list this installation kept
41933
+ * and did not wait for Connect. Read Connect's list once, in the same queue,
41934
+ * where it cannot delay the page: after the page's next request (opening its
41935
+ * remembered collection), at once when a collection is already open, or
41936
+ * shortly after when no request comes (the chooser is showing). */
41937
+ confirmListing(after) {
41938
+ if (this.stopping || this.listConfirm === "done") return;
41939
+ if (after === "start") {
41940
+ if (this.listConfirm !== "idle" || !this.session.collectionListCached()) return;
41941
+ this.listConfirm = "armed";
41942
+ if (this.session.heldCollections().length === 0) {
41943
+ this.listTimer = setTimeout(() => {
41944
+ this.listTimer = null;
41945
+ this.confirmListing("timer");
41946
+ }, LIST_CONFIRM_MS);
41947
+ return;
41948
+ }
41949
+ } else if (this.listConfirm !== "armed") return;
41950
+ if (this.listTimer !== null) clearTimeout(this.listTimer);
41951
+ this.listTimer = null;
41952
+ this.listConfirm = "done";
41953
+ if (!this.session.collectionListCached()) return;
41954
+ const attempt = ++this.listAttempts;
41955
+ this.queue.push({ client: null, request: null, job: async () => {
41956
+ try {
41957
+ if (this.session.getSnapshot().signIn?.state === "paired") await this.session.refreshCollectionList();
41958
+ } catch {
41959
+ }
41960
+ if (this.stopping || !this.session.collectionListCached() || attempt >= LIST_CONFIRM_TRIES) return;
41961
+ this.listConfirm = "armed";
41962
+ this.listTimer = setTimeout(() => {
41963
+ this.listTimer = null;
41964
+ this.confirmListing("timer");
41965
+ }, LIST_CONFIRM_MS);
41966
+ } });
41967
+ this.pump();
41968
+ }
41712
41969
  /** `wake`: a page reported visible/focus/online, so a visible connection
41713
41970
  * syncs now even if it was already in the foreground. `report`: a tab told
41714
41971
  * us, so the session hears it even when the aggregate did not change. */
@@ -41848,6 +42105,7 @@ var ApplicationWorkerHost = class {
41848
42105
  this.active = null;
41849
42106
  this.reconcile();
41850
42107
  this.applyForeground();
42108
+ if (next.client !== null) this.confirmListing(next.request[3] === "start" ? "start" : "request");
41851
42109
  this.pump();
41852
42110
  }
41853
42111
  };
@@ -41992,6 +42250,8 @@ var ApplicationWorkerHost = class {
41992
42250
  * original SDK leases; a subsequent call returns the SAME rejection. */
41993
42251
  stop(detach = true) {
41994
42252
  if (this.stopping) return this.stopping;
42253
+ if (this.listTimer !== null) clearTimeout(this.listTimer);
42254
+ this.listTimer = null;
41995
42255
  const pending = this.pending;
41996
42256
  for (const item of this.queue) wipeUnlockSecret(item.request);
41997
42257
  this.queue.length = 0;