@abraca/dabra 2.20.0 → 2.22.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.
@@ -2771,8 +2771,8 @@ var AbracadabraBaseProvider = class extends EventEmitter {
2771
2771
  this.configuration.websocketProvider.on("destroy", this.configuration.onDestroy);
2772
2772
  this.configuration.websocketProvider.on("destroy", this.forwardDestroy);
2773
2773
  this.configuration.websocketProvider.on("rateLimited", this.forwardRateLimited);
2774
- this.configuration.websocketProvider.attach(this);
2775
2774
  this._isAttached = true;
2775
+ this.configuration.websocketProvider.attach(this);
2776
2776
  }
2777
2777
  permissionDeniedHandler(reason) {
2778
2778
  this.emit("authenticationFailed", { reason });
@@ -2824,9 +2824,12 @@ function txPromise$2(store, request) {
2824
2824
  var OfflineStore = class {
2825
2825
  /**
2826
2826
  * @param docId The document UUID.
2827
- * @param serverOrigin Hostname of the server (e.g. "abra.cou.sh").
2828
- * When provided the IndexedDB database is namespaced
2829
- * per-server, preventing cross-server data contamination.
2827
+ * @param serverOrigin Host of the server, including a non-default port
2828
+ * (e.g. "abra.cou.sh", "localhost:3001"). When provided
2829
+ * the IndexedDB database is namespaced per-server,
2830
+ * preventing cross-server data contamination — the port
2831
+ * matters because two same-host servers sharing the
2832
+ * default root_doc_id would otherwise collide.
2830
2833
  */
2831
2834
  constructor(docId, serverOrigin) {
2832
2835
  this.db = null;
@@ -3064,7 +3067,7 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
3064
3067
  static deriveServerOrigin(config, client) {
3065
3068
  try {
3066
3069
  const url = config.url ?? config.websocketProvider?.url ?? client?.wsUrl;
3067
- if (url) return new URL(url).hostname;
3070
+ if (url) return new URL(url).host;
3068
3071
  } catch {}
3069
3072
  }
3070
3073
  /**
@@ -10482,6 +10485,20 @@ var IdentityDocProvider = class extends EventEmitter {
10482
10485
  function fromBase64$3(b64) {
10483
10486
  return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
10484
10487
  }
10488
+ /**
10489
+ * Decode a base64url-encoded string (e.g. a JWT header/payload segment) to
10490
+ * UTF-8 text. `atob` only accepts standard base64, so we translate the
10491
+ * url-safe alphabet (`-`/`_`) and re-pad before decoding — otherwise a
10492
+ * perfectly valid token whose payload happens to contain `-` or `_` throws
10493
+ * and gets misclassified as invalid/expired.
10494
+ */
10495
+ function decodeBase64UrlToString(b64url) {
10496
+ let b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
10497
+ const pad = b64.length % 4;
10498
+ if (pad) b64 += "=".repeat(4 - pad);
10499
+ const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
10500
+ return new TextDecoder().decode(bytes);
10501
+ }
10485
10502
  var AbracadabraClient = class {
10486
10503
  constructor(config) {
10487
10504
  this.rootListInflight = /* @__PURE__ */ new Map();
@@ -10509,7 +10526,7 @@ var AbracadabraClient = class {
10509
10526
  if (!this._token) return false;
10510
10527
  try {
10511
10528
  const [, payload] = this._token.split(".");
10512
- const { exp } = JSON.parse(atob(payload));
10529
+ const { exp } = JSON.parse(decodeBase64UrlToString(payload));
10513
10530
  return typeof exp === "number" && exp * 1e3 > Date.now();
10514
10531
  } catch {
10515
10532
  return false;
@@ -10599,9 +10616,17 @@ var AbracadabraClient = class {
10599
10616
  async renameKey(keyId, deviceName) {
10600
10617
  await this.request("PATCH", `/auth/keys/${encodeURIComponent(keyId)}`, { body: { deviceName } });
10601
10618
  }
10602
- /** Revoke a public key by its ID. */
10619
+ /**
10620
+ * Revoke a public key by its ID. The server treats this as a panic
10621
+ * action: every outstanding JWT for the account is invalidated (a stolen
10622
+ * device's token must die immediately, and tokens carry no per-key
10623
+ * claim), and a fresh token for THIS session is returned and adopted
10624
+ * automatically — so the device performing the revocation stays
10625
+ * authenticated while every other device silently re-auths.
10626
+ */
10603
10627
  async revokeKey(keyId) {
10604
- await this.request("DELETE", `/auth/keys/${encodeURIComponent(keyId)}`);
10628
+ const res = await this.request("DELETE", `/auth/keys/${encodeURIComponent(keyId)}`);
10629
+ if (res && typeof res === "object" && typeof res.token === "string") this.token = res.token;
10605
10630
  }
10606
10631
  /** Create a single-use device invite code for pairing a new device to this account. */
10607
10632
  async createDeviceInvite(opts) {
@@ -11731,7 +11756,11 @@ var TokenManager = class extends EventEmitter {
11731
11756
  try {
11732
11757
  const [, payload] = jwt.split(".");
11733
11758
  if (!payload) return null;
11734
- const { exp } = JSON.parse(atob(payload));
11759
+ let b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
11760
+ const pad = b64.length % 4;
11761
+ if (pad) b64 += "=".repeat(4 - pad);
11762
+ const json = new TextDecoder().decode(Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)));
11763
+ const { exp } = JSON.parse(json);
11735
11764
  return typeof exp === "number" ? exp : null;
11736
11765
  } catch {
11737
11766
  return null;
@@ -15211,7 +15240,10 @@ var FileBlobStore = class FileBlobStore extends EventEmitter {
15211
15240
  */
15212
15241
  const HKDF_INFO = new TextEncoder().encode("abracadabra-dockey-v1");
15213
15242
  function fromBase64$2(b64) {
15214
- return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
15243
+ let normalized = b64.replace(/-/g, "+").replace(/_/g, "/");
15244
+ const pad = normalized.length % 4;
15245
+ if (pad) normalized += "=".repeat(4 - pad);
15246
+ return Uint8Array.from(atob(normalized), (c) => c.charCodeAt(0));
15215
15247
  }
15216
15248
  var DocKeyManager = class DocKeyManager {
15217
15249
  constructor() {
@@ -15268,7 +15300,7 @@ var DocKeyManager = class DocKeyManager {
15268
15300
  if (me.publicKey) {
15269
15301
  const primaryKey = (await client.listUserKeys(me.id))[0];
15270
15302
  if (primaryKey?.x25519Key) {
15271
- const x25519Pub = fromBase64$2(primaryKey.x25519Key.replace(/-/g, "+").replace(/_/g, "/"));
15303
+ const x25519Pub = fromBase64$2(primaryKey.x25519Key);
15272
15304
  const rewrapped = await this.wrapKeyForRecipient(docKey, x25519Pub, docId);
15273
15305
  const b64 = (() => {
15274
15306
  let s = "";
@@ -16093,7 +16125,7 @@ var BackgroundSyncManager = class extends EventEmitter {
16093
16125
  };
16094
16126
  let serverOrigin = "default";
16095
16127
  try {
16096
- serverOrigin = new URL(client.baseUrl ?? "").hostname;
16128
+ serverOrigin = new URL(client.baseUrl ?? "").host;
16097
16129
  } catch {}
16098
16130
  this.persistence = new BackgroundSyncPersistence(serverOrigin);
16099
16131
  this.semaphore = new Semaphore(this.opts.concurrency);
@@ -16240,7 +16272,7 @@ var BackgroundSyncManager = class extends EventEmitter {
16240
16272
  for (const docId of treeMap.keys()) docIds.add(docId);
16241
16273
  let serverOrigin;
16242
16274
  try {
16243
- serverOrigin = new URL(this.client.baseUrl ?? "").hostname;
16275
+ serverOrigin = new URL(this.client.baseUrl ?? "").host;
16244
16276
  } catch {}
16245
16277
  const clearPromises = Array.from(docIds).map(async (docId) => {
16246
16278
  try {