@mdbase-dev/sdk 0.1.0-beta.173 → 0.1.0-beta.175

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 (42) hide show
  1. package/dist/app-host/cp-problem.d.ts +1 -1
  2. package/dist/app-host/cp-problem.js +4 -0
  3. package/dist/app-host/cp-problem.js.map +1 -1
  4. package/dist/app-host/http-log.d.ts +2 -6
  5. package/dist/app-host/http-log.js +10 -43
  6. package/dist/app-host/http-log.js.map +1 -1
  7. package/dist/app-host/installation-sign-in.d.ts +3 -0
  8. package/dist/app-host/installation-sign-in.js +18 -6
  9. package/dist/app-host/installation-sign-in.js.map +1 -1
  10. package/dist/app-host/scoped-sign-in-session.js +5 -1
  11. package/dist/app-host/scoped-sign-in-session.js.map +1 -1
  12. package/dist/app-host/spare-nonces.d.ts +27 -0
  13. package/dist/app-host/spare-nonces.js +58 -0
  14. package/dist/app-host/spare-nonces.js.map +1 -0
  15. package/dist/app-host/web-sign-in-problem.js +1 -1
  16. package/dist/app-host/web-sign-in-problem.js.map +1 -1
  17. package/dist/application/asset-pins.json +4 -4
  18. package/dist/application/collection-selection.d.ts +10 -0
  19. package/dist/application/collection-selection.js +47 -5
  20. package/dist/application/collection-selection.js.map +1 -1
  21. package/dist/application/entry.js +1 -1
  22. package/dist/application/entry.js.map +1 -1
  23. package/dist/application/phase.d.ts +3 -0
  24. package/dist/application/phase.js +3 -0
  25. package/dist/application/phase.js.map +1 -1
  26. package/dist/application/worker-client.js +7 -2
  27. package/dist/application/worker-client.js.map +1 -1
  28. package/dist/application/worker.js +65 -50
  29. package/dist/browser/mdbase-sdk.js +123 -60
  30. package/dist/browser/mdbase-sdk.min.js +43 -43
  31. package/dist/describe-error.d.ts +1 -1
  32. package/dist/describe-error.js +3 -1
  33. package/dist/describe-error.js.map +1 -1
  34. package/dist/native/provenance.json +5 -5
  35. package/dist/native/runtime.wasm +0 -0
  36. package/dist/transport/control-route.d.ts +2 -1
  37. package/dist/transport/control-route.js +4 -1
  38. package/dist/transport/control-route.js.map +1 -1
  39. package/dist/transport/relay.d.ts +7 -0
  40. package/dist/transport/relay.js +3 -1
  41. package/dist/transport/relay.js.map +1 -1
  42. package/package.json +1 -1
@@ -12501,6 +12501,7 @@ var MdbaseSDK = (() => {
12501
12501
  if (names.includes("collection_not_yet_migrated")) return "collection_not_yet_migrated";
12502
12502
  if (names.includes("collection_migrating")) return "collection_migrating";
12503
12503
  if (names.includes("account_suspended")) return "account_suspended";
12504
+ if (problem7.status === 403 && problem7.code === "installation_private_device_not_allowed") return "private_device_not_allowed";
12504
12505
  if (names.some((n) => n === "moved_to_next_runtime" || n === "collection_moved" || n === "client_update_required") || problem7.status === 426) return "update_required";
12505
12506
  return null;
12506
12507
  }
@@ -12782,7 +12783,8 @@ var MdbaseSDK = (() => {
12782
12783
  "unlock_strict",
12783
12784
  "unlock_unavailable",
12784
12785
  "unlock_failed",
12785
- "private_device_limit"
12786
+ "private_device_limit",
12787
+ "private_device_not_allowed"
12786
12788
  ];
12787
12789
  GENERIC = ["Something went wrong", "An unexpected problem occurred. Reload the app, and contact support if it keeps happening.", "reload", "error"];
12788
12790
  BUSY = ["Another step hasn't finished", "The app couldn't finish this step yet. Try again, or reload if it keeps happening.", "retry", "warning", true];
@@ -12910,7 +12912,8 @@ var MdbaseSDK = (() => {
12910
12912
  unlock_strict: ["Can't be unlocked here", "This account approves each device from another device instead of using an encryption password, and this app can't do that yet. Use the mdbase desktop app on a computer that has this collection.", "none", "info"],
12911
12913
  unlock_unavailable: ["This app can't unlock yet", "This version of the app can't unlock Private collections. Update the app, then try again.", "update_app", "warning"],
12912
12914
  unlock_failed: ["Couldn't unlock this collection", "Your password or recovery key is right, but this collection didn't give this device its key. Try again. If it keeps happening, open the mdbase desktop app on a computer that has this collection.", "retry", "warning"],
12913
- private_device_limit: ["One Private collection per app", "This app already keeps another Private collection on this device, and it can keep only one. Open that collection, or sign out of mdbase in this app to switch.", "choose_collection", "warning"]
12915
+ private_device_limit: ["One Private collection per app", "This app already keeps another Private collection on this device, and it can keep only one. Open that collection, or sign out of mdbase in this app to switch.", "choose_collection", "warning"],
12916
+ private_device_not_allowed: ["This app can't keep this collection", "Access to this Private collection can't be restored in this app on this server. Open it in the mdbase desktop app on a computer that has it.", "none", "warning"]
12914
12917
  };
12915
12918
  ALIASES = Object.assign(/* @__PURE__ */ Object.create(null), {
12916
12919
  no_device_online: "device_offline",
@@ -13024,6 +13027,7 @@ var MdbaseSDK = (() => {
13024
13027
  sync_required: "Collection availability sync_required: the grant no longer matches the collection's membership; run authorize() again.",
13025
13028
  no_device_registered: "Collection availability no_device_registered: Connect has no device that can serve this collection (none registered with sync on, or its device was revoked).",
13026
13029
  installation_scope_removed: "The original installation lost this collection scope. Only explicit approval of a fresh scoped installation can regain access.",
13030
+ private_device_not_allowed: "Connect refused a recovery sign-in's Private device access (403 installation_private_device_not_allowed): MDBASE_NEXT_PRIVATE_APP_DEVICES is off, or the registered manifest does not declare offline.replica.",
13027
13031
  access_removed: "Collection availability access_removed: the grant's account is no longer a member of the collection.",
13028
13032
  collection_deleted: "Collection availability collection_deleted: the owner deleted the collection.",
13029
13033
  collection_unavailable: "Connect problem collection_unavailable without a known reason: the collection is not offered to this app or client.",
@@ -29987,7 +29991,7 @@ ${end.comment}` : end.comment;
29987
29991
  const binding2 = object5(p.recoveryFor, "accountId,collectionId");
29988
29992
  id2(binding2.accountId);
29989
29993
  id2(binding2.collectionId);
29990
- if (!p.owner || p.requestedCreateCollections === true || p.freshRequestedCreateCollections === true || p.freshRequestedPrivateDevice === true) throw fail3("binding");
29994
+ if (!p.owner || p.requestedCreateCollections === true || p.freshRequestedCreateCollections === true) throw fail3("binding");
29991
29995
  }
29992
29996
  if (selection !== void 0) {
29993
29997
  if (!isAppEnvironmentSelection(selection) || selection.environment !== p.environment || selection.appOrigin !== p.origin || selection.cpOrigin !== p.cpOrigin || p.allowLoopbackHttp === true && !selection.allowLoopbackHttp) throw fail3("binding");
@@ -30025,7 +30029,7 @@ ${end.comment}` : end.comment;
30025
30029
  if (s.requestedPrivateDevice !== void 0 && s.requestedPrivateDevice !== true) throw fail3("recovery_required");
30026
30030
  if (s.privateDeviceCollectionIds !== void 0) {
30027
30031
  const scopes = this.collectionIds(s.privateDeviceCollectionIds);
30028
- if (!scopes.length || !s.token || s.recoveryFor !== void 0 || scopes.some((c) => !s.collectionIds.includes(c))) throw fail3("recovery_required");
30032
+ if (!scopes.length || !s.token || scopes.some((c) => !s.collectionIds.includes(c))) throw fail3("recovery_required");
30029
30033
  s.privateDeviceCollectionIds = scopes;
30030
30034
  }
30031
30035
  if (s.recoveryFor !== void 0) {
@@ -30033,7 +30037,7 @@ ${end.comment}` : end.comment;
30033
30037
  id2(binding2.accountId);
30034
30038
  id2(binding2.collectionId);
30035
30039
  }
30036
- if (s.recoveryFor === void 0 !== (p.recoveryFor === void 0) || s.recoveryFor?.accountId !== p.recoveryFor?.accountId || s.recoveryFor?.collectionId !== p.recoveryFor?.collectionId || s.recoveryFor && (s.requestedCreateCollections || s.createCollections || s.selection && s.selection.accountId !== s.recoveryFor.accountId || s.token && (s.nativeSelection ? s.collectionIds.length !== 0 || s.nativeSelection.collectionId !== s.recoveryFor.collectionId : s.collectionIds.length !== 1 || s.collectionIds[0] !== s.recoveryFor.collectionId || s.collectionSelection?.selectedCollectionId !== s.recoveryFor.collectionId))) throw fail3("recovery_required");
30040
+ if (s.recoveryFor === void 0 !== (p.recoveryFor === void 0) || s.recoveryFor?.accountId !== p.recoveryFor?.accountId || s.recoveryFor?.collectionId !== p.recoveryFor?.collectionId || s.recoveryFor && (s.requestedCreateCollections || s.createCollections || s.selection && s.selection.accountId !== s.recoveryFor.accountId || s.token && (s.nativeSelection ? s.collectionIds.length !== 0 || s.nativeSelection.collectionId !== s.recoveryFor.collectionId : !_AppProtectedInstallationSignIn.recovered(s.recoveryFor.collectionId, s.collectionIds, s.privateDeviceCollectionIds ?? [], s.collectionSelection)))) throw fail3("recovery_required");
30037
30041
  if (s.consentRequest !== null) {
30038
30042
  const c = object5(s.consentRequest, `requestId,secret,requestedCreateCollections,challenge,completed,addedCollectionIds,approvedCreateCollections${Object.hasOwn(s.consentRequest, "requestedPrivateDevice") ? ",requestedPrivateDevice" : ""}${portalKeys(s.consentRequest, true)}`);
30039
30043
  if (c.requestedPrivateDevice !== void 0 && c.requestedPrivateDevice !== true) throw fail3("recovery_required");
@@ -30335,7 +30339,7 @@ ${end.comment}` : end.comment;
30335
30339
  const { status, value: v } = await this.json(`/v1/pairing-requests/${this.state.requestId}/exchange`, true);
30336
30340
  if (v.status === "pending") {
30337
30341
  object5(v, "status");
30338
- if (status !== 202 || this.state.selection) throw fail3("response");
30342
+ if (status !== 202 || this.state.selection && !this.state.renewal) throw fail3("response");
30339
30343
  return this.view();
30340
30344
  }
30341
30345
  const keys2 = `status,request_id,account_id,connector_id,device_id,installation_id,kind,challenge,approval_mode,app_id,app_origin,expires_at${portalKeys(v)}${this.recoveryTargetKeys()}`;
@@ -30364,7 +30368,7 @@ ${end.comment}` : end.comment;
30364
30368
  const nativeSelection = native ? this.parseNativeSelection(v.native_selection, this.state.requestId) : void 0;
30365
30369
  if (native && (!scoped || picked || createCollections || collectionIds.length !== 0 || v.timer_consent === true)) throw fail3("response");
30366
30370
  if (this.state.recoveryFor && nativeSelection && nativeSelection.collectionId !== this.state.recoveryFor.collectionId) throw fail3("binding");
30367
- if (this.state.recoveryFor && !nativeSelection && (createCollections || collectionIds.length !== 1 || collectionIds[0] !== this.state.recoveryFor.collectionId || collectionSelection?.selectedCollectionId !== this.state.recoveryFor.collectionId || collectionSelection.createdCollectionIds.length !== 0)) throw fail3("binding");
30371
+ if (this.state.recoveryFor && !nativeSelection && (createCollections || !_AppProtectedInstallationSignIn.recovered(this.state.recoveryFor.collectionId, collectionIds, privateDeviceCollectionIds, collectionSelection))) throw fail3("binding");
30368
30372
  if (v.timer_consent === true && !collectionSelection) throw fail3("response");
30369
30373
  const deviceOnly = !collectionSelection && privateDeviceCollectionIds.length > 0;
30370
30374
  const reminder = timerConsent(v, this.state.requestId, collectionIds, selection.portalConfirmed === true, collectionSelection?.selectedCollectionId);
@@ -30373,6 +30377,12 @@ ${end.comment}` : end.comment;
30373
30377
  return this.view();
30374
30378
  });
30375
30379
  }
30380
+ /** A recovery slot holds exactly its one collection: the selected cloud copy,
30381
+ * or the one Private device scope (which Connect never names as a selection). */
30382
+ static recovered(collection2, ids, device, selection) {
30383
+ if (ids.length !== 1 || ids[0] !== collection2) return false;
30384
+ return device.length ? device.length === 1 && device[0] === collection2 && selection === void 0 : selection?.selectedCollectionId === collection2 && selection.createdCollectionIds.length === 0;
30385
+ }
30376
30386
  parseNativeSelection(value2, requestId) {
30377
30387
  const n = object5(value2, "authorization_request_id,collection_id"), a = this.state.nativeAuthorization;
30378
30388
  if (!a || a.requestId !== requestId || id2(n.authorization_request_id) !== a.authorizationId) throw fail3("binding");
@@ -30951,6 +30961,49 @@ ${end.comment}` : end.comment;
30951
30961
  }
30952
30962
  });
30953
30963
 
30964
+ // src/app-host/spare-nonces.ts
30965
+ function nonceFromHex(text9) {
30966
+ return /^[0-9a-f]{64}$/.test(text9) ? Uint8Array.from(text9.match(/../g).map((v) => parseInt(v, 16))) : null;
30967
+ }
30968
+ function nonceRefusal(response2) {
30969
+ if (!(response2 instanceof Map)) return false;
30970
+ const e = response2.get(3);
30971
+ if (!(e instanceof Map)) return false;
30972
+ return e.get(0) === "unauthenticated" && (e.get(1) === "nonce" || e.get(1) === "replay");
30973
+ }
30974
+ var NEXT_NONCE_HEADER, SPARE_NONCE_MS, SPARE_NONCES, SpareNonces;
30975
+ var init_spare_nonces = __esm({
30976
+ "src/app-host/spare-nonces.ts"() {
30977
+ "use strict";
30978
+ NEXT_NONCE_HEADER = "x-mdbase-next-nonce";
30979
+ SPARE_NONCE_MS = 4e4;
30980
+ SPARE_NONCES = 6;
30981
+ SpareNonces = class {
30982
+ constructor(now = Date.now) {
30983
+ this.now = now;
30984
+ }
30985
+ now;
30986
+ spares = [];
30987
+ /** The newest fresh spare, removed from the pool (single use), or null. */
30988
+ take() {
30989
+ for (let spare = this.spares.pop(); spare; spare = this.spares.pop()) {
30990
+ const age = this.now() - spare.at;
30991
+ if (age >= 0 && age < SPARE_NONCE_MS) return spare.nonce;
30992
+ spare.nonce.fill(0);
30993
+ }
30994
+ return null;
30995
+ }
30996
+ /** Keep a response's piggybacked nonce, if it carries a well-formed one. */
30997
+ keep(headers) {
30998
+ const next = nonceFromHex(headers.get(NEXT_NONCE_HEADER) ?? "");
30999
+ if (!next) return;
31000
+ this.spares.push({ nonce: next, at: this.now() });
31001
+ while (this.spares.length > SPARE_NONCES) this.spares.shift().nonce.fill(0);
31002
+ }
31003
+ };
31004
+ }
31005
+ });
31006
+
30954
31007
  // src/app-host/http-log.ts
30955
31008
  function uint64(v) {
30956
31009
  if ((typeof v !== "number" || !Number.isSafeInteger(v)) && typeof v !== "bigint") throw error2("shape");
@@ -31016,20 +31069,7 @@ ${end.comment}` : end.comment;
31016
31069
  pre.fill(0);
31017
31070
  return Object.freeze({ endpoint: endpoint2, originalCallId, frame: frame.slice(), token, method, path: "/v1/rpc", collection: collection2, nonce: nonce.slice(), bodyHash, tokenHash, digest: digest2 });
31018
31071
  }
31019
- function nonceBytes(text9) {
31020
- return /^[0-9a-f]{64}$/.test(text9) ? Uint8Array.from(text9.match(/../g).map((v) => parseInt(v, 16))) : null;
31021
- }
31022
- function nonceRefused(reply) {
31023
- try {
31024
- const e = decode(reply).get(3);
31025
- if (!(e instanceof Map)) return false;
31026
- const m2 = e;
31027
- return m2.get(0) === "unauthenticated" && (m2.get(1) === "nonce" || m2.get(1) === "replay");
31028
- } catch {
31029
- return false;
31030
- }
31031
- }
31032
- var MAX_FRAME2, MAX_OBJECT, METHODS, FORBIDDEN, utf82, AppHttpLogError, error2, SPARE_NONCE_MS, SPARE_NONCES, NEXT_NONCE, AppHttpLogTransport;
31072
+ var MAX_FRAME2, MAX_OBJECT, METHODS, FORBIDDEN, utf82, AppHttpLogError, error2, AppHttpLogTransport;
31033
31073
  var init_http_log = __esm({
31034
31074
  "src/app-host/http-log.ts"() {
31035
31075
  "use strict";
@@ -31038,6 +31078,7 @@ ${end.comment}` : end.comment;
31038
31078
  init_cbor();
31039
31079
  init_codec();
31040
31080
  init_cp_problem();
31081
+ init_spare_nonces();
31041
31082
  MAX_FRAME2 = 16 * 1024 * 1024;
31042
31083
  MAX_OBJECT = 9 * 1024 * 1024;
31043
31084
  METHODS = /* @__PURE__ */ new Set(["append", "read", "head", "subscribe", "unsubscribe", "put_object", "get_object", "has_objects", "put_snapshot", "get_snapshot", "endorse_snapshot", "stream_join", "stream_leave", "stream_send"]);
@@ -31054,9 +31095,6 @@ ${end.comment}` : end.comment;
31054
31095
  server;
31055
31096
  };
31056
31097
  error2 = (reason) => new AppHttpLogError(reason);
31057
- SPARE_NONCE_MS = 4e4;
31058
- SPARE_NONCES = 6;
31059
- NEXT_NONCE = "x-mdbase-next-nonce";
31060
31098
  AppHttpLogTransport = class {
31061
31099
  constructor(authority, options = {}) {
31062
31100
  this.authority = authority;
@@ -31071,6 +31109,7 @@ ${end.comment}` : end.comment;
31071
31109
  this.approved = /* @__PURE__ */ new Set([this.base, ...authority.directOrigins.map((v) => origin2(v, this.loopback))]);
31072
31110
  this.fetchImpl = options.fetch ?? globalThis.fetch;
31073
31111
  this.now = options.now ?? Date.now;
31112
+ this.spares = new SpareNonces(this.now);
31074
31113
  } catch {
31075
31114
  throw error2("binding");
31076
31115
  }
@@ -31086,9 +31125,8 @@ ${end.comment}` : end.comment;
31086
31125
  now;
31087
31126
  loopback;
31088
31127
  commitId = (1n << 64n) - 1n;
31089
- /** Unused piggybacked nonces (one per reply, a few kept for concurrent RPCs);
31090
- * each is taken (never shared) by exactly one later RPC. */
31091
- spares = [];
31128
+ /** Unused piggybacked nonces; each is taken (never shared) by exactly one later RPC. */
31129
+ spares;
31092
31130
  responseId = 0;
31093
31131
  receiving = /* @__PURE__ */ new Map();
31094
31132
  /** Only active logical sends, with bytes admitted under the existing body /
@@ -31207,28 +31245,13 @@ ${end.comment}` : end.comment;
31207
31245
  const server = appServerProblem(r.status, body, r.headers);
31208
31246
  return new AppHttpLogError(appServerOutcome(server) ?? "unavailable", server);
31209
31247
  }
31210
- /** Take the newest fresh spare nonce; it is single use, so never hand it out twice. */
31211
- takeSpare() {
31212
- for (let spare = this.spares.pop(); spare; spare = this.spares.pop()) {
31213
- const age = this.now() - spare.at;
31214
- if (age >= 0 && age < SPARE_NONCE_MS) return spare.nonce;
31215
- spare.nonce.fill(0);
31216
- }
31217
- return null;
31218
- }
31219
- keepSpare(r) {
31220
- const next = nonceBytes(r.headers.get(NEXT_NONCE) ?? "");
31221
- if (!next) return;
31222
- this.spares.push({ nonce: next, at: this.now() });
31223
- while (this.spares.length > SPARE_NONCES) this.spares.shift().nonce.fill(0);
31224
- }
31225
31248
  async freshNonce(signal, receive) {
31226
31249
  const nr = await this.fetch(`${this.base}/v1/nonce`, {}, signal, 15e3);
31227
31250
  if (!nr.ok) throw await this.refusal(nr, signal, receive);
31228
31251
  const raw = await this.read(nr, 128, signal, receive);
31229
31252
  try {
31230
31253
  this.check(signal);
31231
- const nonce = nonceBytes(new TextDecoder("utf-8", { fatal: true }).decode(raw).trim());
31254
+ const nonce = nonceFromHex(new TextDecoder("utf-8", { fatal: true }).decode(raw).trim());
31232
31255
  if (!nonce) throw error2("shape");
31233
31256
  return nonce;
31234
31257
  } finally {
@@ -31240,7 +31263,7 @@ ${end.comment}` : end.comment;
31240
31263
  const token = await this.authority.accessToken({ signal });
31241
31264
  this.check(signal);
31242
31265
  if (typeof token !== "string" || !token || token.length > 16 * 1024 || /[\r\n]/.test(token)) throw error2("shape");
31243
- const spare = allowSpare ? this.takeSpare() : null;
31266
+ const spare = allowSpare ? this.spares.take() : null;
31244
31267
  const nonce = spare ?? await this.freshNonce(signal, receive);
31245
31268
  this.check(signal);
31246
31269
  const p = proof(this.endpoint, originalId, method, this.collection, token, frame, nonce);
@@ -31251,18 +31274,19 @@ ${end.comment}` : end.comment;
31251
31274
  this.check(signal);
31252
31275
  if (!(signature instanceof Uint8Array) || signature.length !== 64) throw error2("shape");
31253
31276
  const r = await this.fetch(`${this.base}/v1/rpc`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/cbor", "x-mdbase-nonce": hex3(p.nonce), "x-mdbase-sig": hex3(signature) }, body: frame }, signal, 15e3);
31254
- this.keepSpare(r);
31277
+ this.spares.keep(r.headers);
31255
31278
  if (r.status === 401) this.authority.invalidateToken?.(token);
31256
31279
  if (!r.ok) throw await this.refusal(r, signal, receive);
31257
31280
  const reply = await this.read(r, method === "append" ? 64 * 1024 : MAX_FRAME2, signal, receive);
31281
+ let refused5;
31258
31282
  try {
31259
31283
  this.check(signal);
31260
- this.response(reply, id6);
31284
+ refused5 = nonceRefusal(this.response(reply, id6));
31261
31285
  } catch (e) {
31262
31286
  reply.fill(0);
31263
31287
  throw e;
31264
31288
  }
31265
- if (spare && nonceRefused(reply)) {
31289
+ if (spare && refused5) {
31266
31290
  reply.fill(0);
31267
31291
  return await this.rpc(frame, method, id6, signal, receive, originalId, false);
31268
31292
  }
@@ -43939,6 +43963,7 @@ ${end.comment}` : end.comment;
43939
43963
  if (portal) {
43940
43964
  if (!["authorize", "renewExpiredPairing", "requestFileAccess"].includes(action)) return Promise.reject(new ApplicationWorkerError("application_worker_binding"));
43941
43965
  this.portal?.life.abort();
43966
+ if (this.portal) this.closedPortal = this.portal.id;
43942
43967
  try {
43943
43968
  this.portal?.value.close();
43944
43969
  } catch {
@@ -44040,7 +44065,7 @@ ${end.comment}` : end.comment;
44040
44065
  }
44041
44066
  if (kind === "nativeAuthorization") {
44042
44067
  const call2 = nativeAuthorizationCall(value2), held = this.portal;
44043
- if (!held && id6 === this.closedPortal) {
44068
+ if (id6 === this.closedPortal && held?.id !== id6) {
44044
44069
  if (call2.action !== "cancel") this.port.postMessage([APPLICATION_WORKER_PROTOCOL, this.binding, "nativeAuthorizationReply", id6, { rpc: call2.rpc, ok: false }]);
44045
44070
  return;
44046
44071
  }
@@ -44948,8 +44973,10 @@ ${end.comment}` : end.comment;
44948
44973
  if (!route2) {
44949
44974
  throw mdbaseError("unavailable", "no device of this collection is online", "no_device_online");
44950
44975
  }
44976
+ const prologue = clientPrologue(collection2, grant, uuidToBytes(route2.targetDevice));
44951
44977
  return {
44952
- prologue: clientPrologue(collection2, grant, uuidToBytes(route2.targetDevice)),
44978
+ prologue,
44979
+ ...route2.prologueFirst && !route2.pipe ? { preamble: prologue } : {},
44953
44980
  remoteStatic: route2.noisePublicKey,
44954
44981
  device: route2.targetDevice,
44955
44982
  openCarrier: (signal) => route2.pipe ? relayPipeCarrier(
@@ -45238,6 +45265,7 @@ ${end.comment}` : end.comment;
45238
45265
  if (!/^[0-9a-f]{64}$/i.test(t.noise_pk) || !t.url || !t.device) continue;
45239
45266
  const route2 = { url: t.url, targetDevice: t.device, noisePublicKey: fromHex(t.noise_pk) };
45240
45267
  if (t.relay_collection) route2.pipe = { collection: t.relay_collection, accessToken };
45268
+ else if (t.kind === "hosted") route2.prologueFirst = true;
45241
45269
  return route2;
45242
45270
  }
45243
45271
  return null;
@@ -45641,6 +45669,7 @@ ${end.comment}` : end.comment;
45641
45669
  const name3 = s.collections.find((c) => c.collectionId === id6)?.displayName ?? requested?.displayName ?? null;
45642
45670
  return { kind: "opening", collectionId: id6, displayName: name3, slow: !!o.slow };
45643
45671
  }
45672
+ if (o.startPending && paired && s.status === "unselected" && !s.problem && sel?.state === "none") return { kind: "starting" };
45644
45673
  return { kind: "choosing", lastApproval: sel?.lastApproval ?? null };
45645
45674
  }
45646
45675
  function hold2(next, s, o) {
@@ -45814,12 +45843,12 @@ ${end.comment}` : end.comment;
45814
45843
  // src/application/asset-pins.json
45815
45844
  var asset_pins_default = {
45816
45845
  runtime: {
45817
- sha256: "a046b77ae1319049910766e2d6f393b2f9116ab0a15af4ca77bd8744386fc71e",
45818
- bytes: 3846571,
45819
- maxBytes: 3846571,
45846
+ sha256: "8b4e96047bdaeb6df873e7f33368509de6bfa5d3427542dae7be05430f519f5a",
45847
+ bytes: 3849226,
45848
+ maxBytes: 3849226,
45820
45849
  source: {
45821
45850
  repository: "mdbase-dev/mdbase-connect",
45822
- commit: "d92cc4ca4b1c83ff68643c3d4e3a3f5ccb74988b"
45851
+ commit: "5ca14dd53e24941411cce2f6bc793fe1736a69a9"
45823
45852
  }
45824
45853
  },
45825
45854
  sqlite: {
@@ -46089,6 +46118,12 @@ ${end.comment}` : end.comment;
46089
46118
  key = "";
46090
46119
  startTarget = null;
46091
46120
  startApplied = false;
46121
+ /** Still deciding the start request, with nothing left to wait for but this object. */
46122
+ startHeld = true;
46123
+ /** The request whose open the session reported as failed (blocked). */
46124
+ failed = null;
46125
+ /** The session snapshot when the current attempt began. */
46126
+ before = null;
46092
46127
  /** Last request auto-opened (null = auto-cleared); undefined = none yet. */
46093
46128
  attempted = void 0;
46094
46129
  lastRequested = null;
@@ -46109,7 +46144,15 @@ ${end.comment}` : end.comment;
46109
46144
  setTimeout(task, 0);
46110
46145
  });
46111
46146
  this.value = this.derive();
46112
- this.key = JSON.stringify(this.value);
46147
+ this.key = this.keyOf(this.value);
46148
+ }
46149
+ /** True until the start request (URL, or the remembered collection) has been
46150
+ * decided: a settled session with nothing requested is not yet a chooser. */
46151
+ startPending() {
46152
+ return this.startHeld;
46153
+ }
46154
+ keyOf(value2) {
46155
+ return JSON.stringify(value2) + (this.startHeld ? "|start" : "");
46113
46156
  }
46114
46157
  getSnapshot() {
46115
46158
  return this.value;
@@ -46221,6 +46264,7 @@ ${end.comment}` : end.comment;
46221
46264
  resync() {
46222
46265
  if (this.closed) return;
46223
46266
  this.attempted = void 0;
46267
+ this.failed = null;
46224
46268
  this.approval = null;
46225
46269
  this.changed();
46226
46270
  }
@@ -46256,9 +46300,14 @@ ${end.comment}` : end.comment;
46256
46300
  if (id6 !== this.lastRequested) {
46257
46301
  this.lastRequested = id6;
46258
46302
  this.attempted = void 0;
46303
+ this.failed = null;
46259
46304
  if (this.declined !== id6) this.declined = null;
46260
46305
  }
46261
- const next = this.derive(), key3 = JSON.stringify(next);
46306
+ if (id6 !== null && this.attempted === id6) {
46307
+ const s = this.session.getSnapshot();
46308
+ if (s !== this.before && (s.status === "blocked" ? s.selectedCollectionId === id6 : s.status === "unselected" && s.problem !== null)) this.failed = id6;
46309
+ }
46310
+ const next = this.derive(), key3 = this.keyOf(next);
46262
46311
  if (next.state === "usable") this.remember(next.usable.collectionId);
46263
46312
  if (key3 === this.key) return;
46264
46313
  this.key = key3;
@@ -46311,6 +46360,8 @@ ${end.comment}` : end.comment;
46311
46360
  }
46312
46361
  run(id6, options = {}, explicit = false) {
46313
46362
  this.attempted = id6;
46363
+ this.failed = null;
46364
+ this.before = this.session.getSnapshot();
46314
46365
  const request3 = options.signal ? { signal: options.signal } : {};
46315
46366
  const raw = Promise.resolve().then(() => id6 === null ? this.session.clear(request3) : this.session.open(id6, request3));
46316
46367
  const op2 = raw.then(() => {
@@ -46350,15 +46401,20 @@ ${end.comment}` : end.comment;
46350
46401
  storedId = this.memory?.read() ?? null;
46351
46402
  } catch {
46352
46403
  }
46353
- if (storedId !== null && !approved(s, storedId) && this.session.listing?.()) return;
46404
+ if (storedId !== null && !approved(s, storedId) && this.session.listing?.()) {
46405
+ this.startHeld = false;
46406
+ this.refresh();
46407
+ return;
46408
+ }
46354
46409
  this.startApplied = true;
46355
46410
  const target2 = applicationStartTarget(this.browser.getSnapshot(), { storedId, preferredId: this.preferredId, collections: s.collections });
46356
46411
  if (target2.requestedCollectionId !== null && !target2.problem) {
46357
46412
  this.browser.select(target2.requestedCollectionId, { history: "replace" });
46358
46413
  this.startTarget = target2;
46359
- this.refresh();
46360
46414
  }
46361
46415
  } else this.startApplied = true;
46416
+ this.startHeld = false;
46417
+ this.refresh();
46362
46418
  }
46363
46419
  const r = this.requested(), id6 = r.requestedCollectionId;
46364
46420
  if (r.problem) return;
@@ -46368,9 +46424,16 @@ ${end.comment}` : end.comment;
46368
46424
  }
46369
46425
  if (s.selectedCollectionId === id6 && (isOpen(s) || isLocked(s) || s.status === "opening")) {
46370
46426
  this.attempted = id6;
46427
+ this.failed = null;
46428
+ return;
46429
+ }
46430
+ if (s.selectedCollectionId === id6 && s.status === "blocked") {
46431
+ this.failed = id6;
46371
46432
  return;
46372
46433
  }
46373
- if (!approved(s, id6) || this.attempted === id6) return;
46434
+ if (!approved(s, id6)) return;
46435
+ if (this.attempted === id6 && (this.failed !== id6 || s.status !== "unselected" || s.problem !== null)) return;
46436
+ this.failed = null;
46374
46437
  void this.run(id6);
46375
46438
  }
46376
46439
  finishApproval(s) {
@@ -48142,7 +48205,7 @@ ${end.comment}` : end.comment;
48142
48205
  const base = hidden?.size && shown.collections.some((c) => hidden.has(c.collectionId) && c.collectionId !== shown.selectedCollectionId) ? Object.freeze({ ...shown, collections: Object.freeze(shown.collections.filter((c) => !hidden.has(c.collectionId) || c.collectionId === shown.selectedCollectionId)) }) : shown;
48143
48206
  const id6 = base.selectedCollectionId, connection = id6 && !this.closed ? this.privates?.connection(id6) ?? this.runtime?.connection(id6) ?? null : null;
48144
48207
  const openingUnsynced = this.unsyncedOpening > 0 && !base.unsynced && !base.signOutResult && selection.requested.collectionId === null;
48145
- let phase = applicationPhase({ ...base, selection }, { connection, previous: last?.value.phase ?? null, openingUnsynced, ...this.cpOrigin ? { cpOrigin: this.cpOrigin } : {} });
48208
+ let phase = applicationPhase({ ...base, selection }, { connection, previous: last?.value.phase ?? null, openingUnsynced, startPending: this.selection?.startPending() ?? false, ...this.cpOrigin ? { cpOrigin: this.cpOrigin } : {} });
48146
48209
  phase = this.trackOpening(phase);
48147
48210
  this.timing.observe(phase);
48148
48211
  const sync = this.trackSync(phase, base.problem, base.transport ?? null, base.live ?? null);
@@ -53145,7 +53208,7 @@ ${end.comment}` : end.comment;
53145
53208
  init_account_key_host();
53146
53209
  init_owner();
53147
53210
  init_cp_problem();
53148
- var reasons = /* @__PURE__ */ new Set(["binding", "fenced", "busy", "open_in_another_tab", "account_confirmation_required", "recovery_account_mismatch", "outcome_unknown", "response", "refused", "expired", "recovery_required", "unavailable", "collection_migrating", "collection_not_yet_migrated", "account_suspended", "update_required", "private_device_limit"]);
53211
+ var reasons = /* @__PURE__ */ new Set(["binding", "fenced", "busy", "open_in_another_tab", "account_confirmation_required", "recovery_account_mismatch", "outcome_unknown", "response", "refused", "expired", "recovery_required", "unavailable", "collection_migrating", "collection_not_yet_migrated", "account_suspended", "update_required", "private_device_limit", "private_device_not_allowed"]);
53149
53212
  var domCodes = /* @__PURE__ */ new Set(["AbortError", "ConstraintError", "DataCloneError", "InvalidStateError", "NotAllowedError", "NotFoundError", "NotReadableError", "OperationError", "QuotaExceededError", "SecurityError", "TimeoutError", "TransactionInactiveError", "UnknownError", "VersionError"]);
53150
53213
  var result = (problem7, name3, code, server) => Object.freeze({ problem: problem7, detail: Object.freeze({ name: name3, code, ...server ? { server } : {} }) });
53151
53214
  function webSignInProblem(error5) {