@abraca/dabra 1.6.0 → 1.8.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.
@@ -13152,8 +13152,12 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13152
13152
  /**
13153
13153
  * Approve the pending pairing request. Calls `client.addKey()` to
13154
13154
  * register Device B's public key, then notifies Device B.
13155
+ *
13156
+ * @param client Authenticated REST client.
13157
+ * @param masterPublicKey Approver's account-level Ed25519 public key (base64url).
13158
+ * Sent to Device B so it can adopt the master's identity doc.
13155
13159
  */
13156
- async approve(client) {
13160
+ async approve(client, masterPublicKey) {
13157
13161
  if (this.role !== "approver") return {
13158
13162
  success: false,
13159
13163
  error: "Only the approver can approve"
@@ -13173,7 +13177,10 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13173
13177
  deviceName: req.deviceName,
13174
13178
  x25519Key: req.x25519Key
13175
13179
  });
13176
- this.sendMessage({ type: "pair-approved" });
13180
+ this.sendMessage({
13181
+ type: "pair-approved",
13182
+ masterPublicKey
13183
+ });
13177
13184
  this._pendingRequest = null;
13178
13185
  this.emit("pairingComplete", { success: true });
13179
13186
  return { success: true };
@@ -13196,8 +13203,12 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13196
13203
  * Approve via server-side device invite. Creates a single-use invite code
13197
13204
  * and sends it to Device B over the E2EE channel. Device B redeems it
13198
13205
  * independently via HTTP — Device A can go offline after this.
13206
+ *
13207
+ * @param client Authenticated REST client.
13208
+ * @param masterPublicKey Approver's account-level Ed25519 public key (base64url).
13209
+ * Sent to Device B so it can adopt the master's identity doc.
13199
13210
  */
13200
- async approveWithInvite(client) {
13211
+ async approveWithInvite(client, masterPublicKey) {
13201
13212
  if (this.role !== "approver") return {
13202
13213
  success: false,
13203
13214
  error: "Only the approver can approve"
@@ -13214,7 +13225,8 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13214
13225
  const { code } = await client.createDeviceInvite();
13215
13226
  this.sendMessage({
13216
13227
  type: "pair-invite-code",
13217
- code
13228
+ code,
13229
+ masterPublicKey
13218
13230
  });
13219
13231
  this._pendingRequest = null;
13220
13232
  this.emit("pairingComplete", { success: true });
@@ -13385,7 +13397,7 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13385
13397
  break;
13386
13398
  case "pair-approved":
13387
13399
  if (this.role !== "requester") return;
13388
- this.emit("approved");
13400
+ this.emit("approved", { masterPublicKey: msg.masterPublicKey });
13389
13401
  this.emit("pairingComplete", { success: true });
13390
13402
  break;
13391
13403
  case "pair-rejected":
@@ -13398,7 +13410,7 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
13398
13410
  break;
13399
13411
  case "pair-invite-code":
13400
13412
  if (this.role !== "requester") return;
13401
- this.emit("inviteCode", msg.code);
13413
+ this.emit("inviteCode", msg.code, msg.masterPublicKey);
13402
13414
  break;
13403
13415
  }
13404
13416
  }
@@ -13925,6 +13937,19 @@ var IdentityDocProvider = class extends EventEmitter {
13925
13937
  return this.profileMap.size === 0 && this.serversMap.size === 0;
13926
13938
  }
13927
13939
  /**
13940
+ * Enable WebRTC P2P sync at runtime.
13941
+ * Use this for claimed/passkey users where E2EE identity derivation
13942
+ * was deferred to avoid biometric prompts on page load.
13943
+ */
13944
+ enableWebRTC(webrtcConfig) {
13945
+ if (this._destroyed || this.webrtc) return;
13946
+ this.config = {
13947
+ ...this.config,
13948
+ webrtc: webrtcConfig
13949
+ };
13950
+ this._connectWebRTC();
13951
+ }
13952
+ /**
13928
13953
  * Update the sync server URL at runtime (e.g. when user changes their
13929
13954
  * designated sync server in settings).
13930
13955
  */
@@ -14112,5 +14137,318 @@ var DeviceRegistrationService = class {
14112
14137
  };
14113
14138
 
14114
14139
  //#endregion
14115
- export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, ManualSignaling, MessageTooBig, MessageType, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptField, generateMnemonic, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, readAuthMessage, unwrapSeed, validateMnemonic, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
14140
+ //#region packages/provider/src/ChatClient.ts
14141
+ const DEFAULT_TIMEOUT_MS$1 = 1e4;
14142
+ /**
14143
+ * Typed client for the Abracadabra chat feature.
14144
+ *
14145
+ * Wraps a connected provider (or base provider) and translates JSON envelopes
14146
+ * on the stateless channel into typed method calls and events.
14147
+ *
14148
+ * Events emitted:
14149
+ * - `message` → ChatMessage (new message broadcast)
14150
+ * - `typing` → ChatTypingEvent (typing indicator broadcast)
14151
+ * - `readReceipt` → ChatReadReceipt (mark_read broadcast)
14152
+ */
14153
+ var ChatClient = class extends EventEmitter {
14154
+ constructor(provider, options) {
14155
+ super();
14156
+ this.pending = /* @__PURE__ */ new Map();
14157
+ this.provider = provider;
14158
+ this.responseTimeoutMs = options?.responseTimeoutMs ?? DEFAULT_TIMEOUT_MS$1;
14159
+ this.boundOnStateless = (data) => this.handleStateless(data.payload);
14160
+ this.provider.on("stateless", this.boundOnStateless);
14161
+ }
14162
+ /** Stop listening for chat messages. Does not disconnect the underlying provider. */
14163
+ destroy() {
14164
+ this.provider.off("stateless", this.boundOnStateless);
14165
+ for (const queue of this.pending.values()) for (const p of queue) {
14166
+ clearTimeout(p.timer);
14167
+ p.reject(/* @__PURE__ */ new Error("ChatClient destroyed"));
14168
+ }
14169
+ this.pending.clear();
14170
+ this.removeAllListeners();
14171
+ }
14172
+ /** Send a chat message on a channel. Fire-and-forget (the server broadcasts). */
14173
+ sendMessage(input) {
14174
+ this.provider.sendStateless(JSON.stringify({
14175
+ type: "chat:send",
14176
+ channel: input.channel,
14177
+ content: input.content,
14178
+ ...input.sender_name !== void 0 ? { sender_name: input.sender_name } : {}
14179
+ }));
14180
+ }
14181
+ /** Fetch historical messages for a channel. Resolves with the server response. */
14182
+ getHistory(input) {
14183
+ const promise = this.enqueue("chat:history");
14184
+ this.provider.sendStateless(JSON.stringify({
14185
+ type: "chat:history",
14186
+ channel: input.channel,
14187
+ ...input.before !== void 0 ? { before: input.before } : {},
14188
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
14189
+ }));
14190
+ return promise;
14191
+ }
14192
+ /** Broadcast a typing indicator on a channel. */
14193
+ sendTyping(channel) {
14194
+ this.provider.sendStateless(JSON.stringify({
14195
+ type: "chat:typing",
14196
+ channel
14197
+ }));
14198
+ }
14199
+ /** List the current user's channels (ordered by last activity). */
14200
+ listChannels() {
14201
+ const promise = this.enqueue("chat:channels");
14202
+ this.provider.sendStateless(JSON.stringify({ type: "chat:channels" }));
14203
+ return promise;
14204
+ }
14205
+ /** Mark a channel read up to `timestamp` (unix ms). */
14206
+ markRead(channel, timestamp) {
14207
+ this.provider.sendStateless(JSON.stringify({
14208
+ type: "chat:mark_read",
14209
+ channel,
14210
+ timestamp
14211
+ }));
14212
+ }
14213
+ /** Fetch per-user read cursors for a channel. */
14214
+ getReadCursors(channel) {
14215
+ const promise = this.enqueue("chat:read_cursors");
14216
+ this.provider.sendStateless(JSON.stringify({
14217
+ type: "chat:read_cursors",
14218
+ channel
14219
+ }));
14220
+ return promise;
14221
+ }
14222
+ onMessage(fn) {
14223
+ return this.on("message", fn);
14224
+ }
14225
+ onTyping(fn) {
14226
+ return this.on("typing", fn);
14227
+ }
14228
+ onReadReceipt(fn) {
14229
+ return this.on("readReceipt", fn);
14230
+ }
14231
+ enqueue(type) {
14232
+ return new Promise((resolve, reject) => {
14233
+ const entry = {
14234
+ resolve,
14235
+ reject,
14236
+ timer: setTimeout(() => {
14237
+ this.removePending(type, entry);
14238
+ reject(/* @__PURE__ */ new Error(`ChatClient: timeout waiting for ${type} response`));
14239
+ }, this.responseTimeoutMs)
14240
+ };
14241
+ const queue = this.pending.get(type) ?? [];
14242
+ queue.push(entry);
14243
+ this.pending.set(type, queue);
14244
+ });
14245
+ }
14246
+ removePending(type, entry) {
14247
+ const queue = this.pending.get(type);
14248
+ if (!queue) return;
14249
+ const idx = queue.indexOf(entry);
14250
+ if (idx >= 0) queue.splice(idx, 1);
14251
+ if (queue.length === 0) this.pending.delete(type);
14252
+ }
14253
+ resolveNext(type, value) {
14254
+ const queue = this.pending.get(type);
14255
+ if (!queue || queue.length === 0) return false;
14256
+ const next = queue.shift();
14257
+ if (queue.length === 0) this.pending.delete(type);
14258
+ clearTimeout(next.timer);
14259
+ next.resolve(value);
14260
+ return true;
14261
+ }
14262
+ handleStateless(payload) {
14263
+ let parsed;
14264
+ try {
14265
+ parsed = JSON.parse(payload);
14266
+ } catch {
14267
+ return;
14268
+ }
14269
+ const type = parsed?.type;
14270
+ if (typeof type !== "string" || !type.startsWith("chat:")) return;
14271
+ switch (type) {
14272
+ case "chat:message": {
14273
+ const { type: _t, ...rest } = parsed;
14274
+ this.emit("message", rest);
14275
+ break;
14276
+ }
14277
+ case "chat:history":
14278
+ this.resolveNext("chat:history", {
14279
+ channel: parsed.channel,
14280
+ messages: parsed.messages ?? []
14281
+ });
14282
+ break;
14283
+ case "chat:channels":
14284
+ this.resolveNext("chat:channels", { channels: parsed.channels ?? [] });
14285
+ break;
14286
+ case "chat:typing":
14287
+ this.emit("typing", {
14288
+ channel: parsed.channel,
14289
+ sender_id: parsed.sender_id,
14290
+ sender_name: parsed.sender_name ?? null
14291
+ });
14292
+ break;
14293
+ case "chat:read_receipt":
14294
+ this.emit("readReceipt", {
14295
+ channel: parsed.channel,
14296
+ user_id: parsed.user_id,
14297
+ last_read_at: parsed.last_read_at
14298
+ });
14299
+ break;
14300
+ case "chat:read_cursors":
14301
+ this.resolveNext("chat:read_cursors", {
14302
+ channel: parsed.channel,
14303
+ cursors: parsed.cursors ?? []
14304
+ });
14305
+ break;
14306
+ default: break;
14307
+ }
14308
+ }
14309
+ };
14310
+
14311
+ //#endregion
14312
+ //#region packages/provider/src/NotificationsClient.ts
14313
+ const DEFAULT_TIMEOUT_MS = 1e4;
14314
+ /**
14315
+ * Typed client for the Abracadabra notifications feature.
14316
+ *
14317
+ * Emits:
14318
+ * - `new` → NotificationRecord (incoming notify:new broadcast)
14319
+ * - `readUpdate` → NotificationReadUpdate (broadcast after mark_read / mark_all_read)
14320
+ */
14321
+ var NotificationsClient = class extends EventEmitter {
14322
+ constructor(provider, options) {
14323
+ super();
14324
+ this.pending = /* @__PURE__ */ new Map();
14325
+ this.provider = provider;
14326
+ this.responseTimeoutMs = options?.responseTimeoutMs ?? DEFAULT_TIMEOUT_MS;
14327
+ this.boundOnStateless = (data) => this.handleStateless(data.payload);
14328
+ this.provider.on("stateless", this.boundOnStateless);
14329
+ }
14330
+ destroy() {
14331
+ this.provider.off("stateless", this.boundOnStateless);
14332
+ for (const queue of this.pending.values()) for (const p of queue) {
14333
+ clearTimeout(p.timer);
14334
+ p.reject(/* @__PURE__ */ new Error("NotificationsClient destroyed"));
14335
+ }
14336
+ this.pending.clear();
14337
+ this.removeAllListeners();
14338
+ }
14339
+ /**
14340
+ * Create a notification targeting a specific recipient. Requires elevated role
14341
+ * (service or admin); a `server:error` event with code `forbidden` is emitted
14342
+ * by the underlying provider if the caller lacks permission.
14343
+ */
14344
+ create(input) {
14345
+ this.provider.sendStateless(JSON.stringify({
14346
+ type: "notify:create",
14347
+ recipient_id: input.recipient_id,
14348
+ ...input.notification_type !== void 0 ? { notification_type: input.notification_type } : {},
14349
+ title: input.title,
14350
+ ...input.body !== void 0 ? { body: input.body } : {},
14351
+ ...input.icon !== void 0 ? { icon: input.icon } : {},
14352
+ ...input.link !== void 0 ? { link: input.link } : {},
14353
+ ...input.source_id !== void 0 ? { source_id: input.source_id } : {}
14354
+ }));
14355
+ }
14356
+ /** Fetch notification history for the current user. */
14357
+ fetch(input = {}) {
14358
+ const promise = this.enqueue("notify:history");
14359
+ this.provider.sendStateless(JSON.stringify({
14360
+ type: "notify:fetch",
14361
+ ...input.before !== void 0 ? { before: input.before } : {},
14362
+ ...input.limit !== void 0 ? { limit: input.limit } : {},
14363
+ ...input.unread_only !== void 0 ? { unread_only: input.unread_only } : {}
14364
+ }));
14365
+ return promise;
14366
+ }
14367
+ /** Mark a single notification, or a batch, as read. */
14368
+ markRead(target) {
14369
+ const body = { type: "notify:mark_read" };
14370
+ if ("id" in target) body.id = target.id;
14371
+ else body.ids = target.ids;
14372
+ this.provider.sendStateless(JSON.stringify(body));
14373
+ }
14374
+ /** Mark every notification for the current user as read. */
14375
+ markAllRead() {
14376
+ this.provider.sendStateless(JSON.stringify({ type: "notify:mark_all_read" }));
14377
+ }
14378
+ /** Mark all notifications generated by the given source (e.g. a chat channel) as read. */
14379
+ markReadBySource(sourceId) {
14380
+ this.provider.sendStateless(JSON.stringify({
14381
+ type: "notify:mark_read_by_source",
14382
+ source_id: sourceId
14383
+ }));
14384
+ }
14385
+ onNew(fn) {
14386
+ return this.on("new", fn);
14387
+ }
14388
+ onReadUpdate(fn) {
14389
+ return this.on("readUpdate", fn);
14390
+ }
14391
+ enqueue(type) {
14392
+ return new Promise((resolve, reject) => {
14393
+ const entry = {
14394
+ resolve,
14395
+ reject,
14396
+ timer: setTimeout(() => {
14397
+ this.removePending(type, entry);
14398
+ reject(/* @__PURE__ */ new Error(`NotificationsClient: timeout waiting for ${type} response`));
14399
+ }, this.responseTimeoutMs)
14400
+ };
14401
+ const queue = this.pending.get(type) ?? [];
14402
+ queue.push(entry);
14403
+ this.pending.set(type, queue);
14404
+ });
14405
+ }
14406
+ removePending(type, entry) {
14407
+ const queue = this.pending.get(type);
14408
+ if (!queue) return;
14409
+ const idx = queue.indexOf(entry);
14410
+ if (idx >= 0) queue.splice(idx, 1);
14411
+ if (queue.length === 0) this.pending.delete(type);
14412
+ }
14413
+ resolveNext(type, value) {
14414
+ const queue = this.pending.get(type);
14415
+ if (!queue || queue.length === 0) return false;
14416
+ const next = queue.shift();
14417
+ if (queue.length === 0) this.pending.delete(type);
14418
+ clearTimeout(next.timer);
14419
+ next.resolve(value);
14420
+ return true;
14421
+ }
14422
+ handleStateless(payload) {
14423
+ let parsed;
14424
+ try {
14425
+ parsed = JSON.parse(payload);
14426
+ } catch {
14427
+ return;
14428
+ }
14429
+ const type = parsed?.type;
14430
+ if (typeof type !== "string" || !type.startsWith("notify:")) return;
14431
+ switch (type) {
14432
+ case "notify:new": {
14433
+ const { type: _t, ...rest } = parsed;
14434
+ this.emit("new", rest);
14435
+ break;
14436
+ }
14437
+ case "notify:history":
14438
+ this.resolveNext("notify:history", { notifications: parsed.notifications ?? [] });
14439
+ break;
14440
+ case "notify:read_update": {
14441
+ const update = { recipient_id: parsed.recipient_id };
14442
+ if (parsed.ids !== void 0) update.ids = parsed.ids;
14443
+ if (parsed.all !== void 0) update.all = parsed.all;
14444
+ this.emit("readUpdate", update);
14445
+ break;
14446
+ }
14447
+ default: break;
14448
+ }
14449
+ }
14450
+ };
14451
+
14452
+ //#endregion
14453
+ export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChatClient, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, ManualSignaling, MessageTooBig, MessageType, NotificationsClient, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptField, generateMnemonic, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, readAuthMessage, unwrapSeed, validateMnemonic, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
14116
14454
  //# sourceMappingURL=abracadabra-provider.esm.js.map