@abraca/dabra 1.0.20 → 1.0.21

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.
@@ -3198,10 +3198,30 @@ var AbracadabraClient = class {
3198
3198
  async listKeys() {
3199
3199
  return (await this.request("GET", "/auth/keys")).keys;
3200
3200
  }
3201
+ /** Rename a registered device key. */
3202
+ async renameKey(keyId, deviceName) {
3203
+ await this.request("PATCH", `/auth/keys/${encodeURIComponent(keyId)}`, { body: { deviceName } });
3204
+ }
3201
3205
  /** Revoke a public key by its ID. */
3202
3206
  async revokeKey(keyId) {
3203
3207
  await this.request("DELETE", `/auth/keys/${encodeURIComponent(keyId)}`);
3204
3208
  }
3209
+ /** Create a single-use device invite code for pairing a new device to this account. */
3210
+ async createDeviceInvite(opts) {
3211
+ return this.request("POST", "/auth/device-invite", { body: opts?.expiresIn != null ? { expiresIn: opts.expiresIn } : {} });
3212
+ }
3213
+ /** Redeem a device invite code to register a new device key. Returns a JWT token. */
3214
+ async redeemDeviceInvite(opts) {
3215
+ return this.request("POST", "/auth/device-redeem", {
3216
+ body: {
3217
+ code: opts.code,
3218
+ publicKey: opts.publicKey,
3219
+ x25519Key: opts.x25519Key,
3220
+ deviceName: opts.deviceName
3221
+ },
3222
+ auth: false
3223
+ });
3224
+ }
3205
3225
  /** Get encryption info for a document. */
3206
3226
  async getDocEncryption(docId) {
3207
3227
  return this.request("GET", `/docs/${encodeURIComponent(docId)}/encryption`);
@@ -9897,19 +9917,23 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
9897
9917
  }
9898
9918
  /**
9899
9919
  * Send a custom string message to a specific peer via a data channel.
9920
+ * When E2EE is active, the message is encrypted through the router.
9900
9921
  */
9901
9922
  sendCustomMessage(peerId, payload) {
9902
9923
  const pc = this.peerConnections.get(peerId);
9903
9924
  if (!pc) return;
9904
- let channel = pc.router.getChannel("custom");
9925
+ const channelName = "custom";
9926
+ let channel = pc.router.getChannel(channelName);
9905
9927
  if (!channel || channel.readyState !== "open") {
9906
- channel = pc.router.createChannel("custom", { ordered: true });
9928
+ channel = pc.router.createChannel(channelName, { ordered: true });
9907
9929
  channel.onopen = () => {
9908
- channel.send(payload);
9930
+ const data = new TextEncoder().encode(payload);
9931
+ pc.router.send(channelName, data);
9909
9932
  };
9910
9933
  return;
9911
9934
  }
9912
- channel.send(payload);
9935
+ const data = new TextEncoder().encode(payload);
9936
+ pc.router.send(channelName, data);
9913
9937
  }
9914
9938
  /**
9915
9939
  * Send a custom string message to all connected peers.
@@ -10256,6 +10280,270 @@ var ManualSignaling = class extends EventEmitter {
10256
10280
  }
10257
10281
  };
10258
10282
 
10283
+ //#endregion
10284
+ //#region packages/provider/src/webrtc/DevicePairingChannel.ts
10285
+ /**
10286
+ * DevicePairingChannel
10287
+ *
10288
+ * Enables cross-device identity pairing over an E2EE WebRTC data channel.
10289
+ * Device A (approver) creates a pairing session with a short code. Device B
10290
+ * (requester) joins with the code. After E2EE is established, Device B sends
10291
+ * its public key and Device A registers it via `addKey()`.
10292
+ *
10293
+ * Reuses the existing WebRTC stack: SignalingSocket, PeerConnection,
10294
+ * DataChannelRouter, and E2EEChannel (X25519 ECDH + AES-256-GCM).
10295
+ */
10296
+ /** Ambiguity-free charset (no 0/O/1/I). */
10297
+ const CODE_CHARSET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
10298
+ const CODE_LENGTH = 6;
10299
+ const PAIRING_TIMEOUT_MS = 300 * 1e3;
10300
+ function generatePairingCode() {
10301
+ const bytes = crypto.getRandomValues(new Uint8Array(CODE_LENGTH));
10302
+ return Array.from(bytes).map((b) => CODE_CHARSET[b % 32]).join("");
10303
+ }
10304
+ function codeToRoomId(code) {
10305
+ const hash = sha256(new TextEncoder().encode(code.toUpperCase()));
10306
+ return `__pairing_${Array.from(hash.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
10307
+ }
10308
+ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
10309
+ constructor(role, pairingCode, config) {
10310
+ super();
10311
+ this.config = config;
10312
+ this.webrtc = null;
10313
+ this.timeoutHandle = null;
10314
+ this._destroyed = false;
10315
+ this._pendingRequest = null;
10316
+ this._connectedPeerId = null;
10317
+ this.role = role;
10318
+ this.pairingCode = pairingCode;
10319
+ }
10320
+ /**
10321
+ * Create an approver session (Device A). Returns the channel and a
10322
+ * 6-character pairing code to share with Device B.
10323
+ */
10324
+ static createApprover(config) {
10325
+ const code = generatePairingCode();
10326
+ const channel = new DevicePairingChannel("approver", code, config);
10327
+ channel.start();
10328
+ return {
10329
+ channel,
10330
+ pairingCode: code
10331
+ };
10332
+ }
10333
+ /**
10334
+ * Create a requester session (Device B). Joins with a pairing code
10335
+ * obtained from Device A.
10336
+ */
10337
+ static createRequester(config, pairingCode) {
10338
+ const channel = new DevicePairingChannel("requester", pairingCode.toUpperCase().replace(/[^A-Z2-9]/g, ""), config);
10339
+ channel.start();
10340
+ return channel;
10341
+ }
10342
+ /**
10343
+ * Approve the pending pairing request. Calls `client.addKey()` to
10344
+ * register Device B's public key, then notifies Device B.
10345
+ */
10346
+ async approve(client) {
10347
+ if (this.role !== "approver") return {
10348
+ success: false,
10349
+ error: "Only the approver can approve"
10350
+ };
10351
+ if (!this._pendingRequest) return {
10352
+ success: false,
10353
+ error: "No pending pairing request"
10354
+ };
10355
+ if (!this._connectedPeerId) return {
10356
+ success: false,
10357
+ error: "Peer not connected"
10358
+ };
10359
+ const req = this._pendingRequest;
10360
+ try {
10361
+ await client.addKey({
10362
+ publicKey: req.publicKey,
10363
+ deviceName: req.deviceName,
10364
+ x25519Key: req.x25519Key
10365
+ });
10366
+ this.sendMessage({ type: "pair-approved" });
10367
+ this._pendingRequest = null;
10368
+ this.emit("pairingComplete", { success: true });
10369
+ return { success: true };
10370
+ } catch (err) {
10371
+ const error = err?.message ?? "Failed to register device key";
10372
+ this.sendMessage({
10373
+ type: "pair-rejected",
10374
+ reason: error
10375
+ });
10376
+ this._pendingRequest = null;
10377
+ const result = {
10378
+ success: false,
10379
+ error
10380
+ };
10381
+ this.emit("pairingComplete", result);
10382
+ return result;
10383
+ }
10384
+ }
10385
+ /**
10386
+ * Approve via server-side device invite. Creates a single-use invite code
10387
+ * and sends it to Device B over the E2EE channel. Device B redeems it
10388
+ * independently via HTTP — Device A can go offline after this.
10389
+ */
10390
+ async approveWithInvite(client) {
10391
+ if (this.role !== "approver") return {
10392
+ success: false,
10393
+ error: "Only the approver can approve"
10394
+ };
10395
+ if (!this._pendingRequest) return {
10396
+ success: false,
10397
+ error: "No pending pairing request"
10398
+ };
10399
+ if (!this._connectedPeerId) return {
10400
+ success: false,
10401
+ error: "Peer not connected"
10402
+ };
10403
+ try {
10404
+ const { code } = await client.createDeviceInvite();
10405
+ this.sendMessage({
10406
+ type: "pair-invite-code",
10407
+ code
10408
+ });
10409
+ this._pendingRequest = null;
10410
+ this.emit("pairingComplete", { success: true });
10411
+ return { success: true };
10412
+ } catch (err) {
10413
+ const error = err?.message ?? "Failed to create device invite";
10414
+ this.sendMessage({
10415
+ type: "pair-rejected",
10416
+ reason: error
10417
+ });
10418
+ this._pendingRequest = null;
10419
+ const result = {
10420
+ success: false,
10421
+ error
10422
+ };
10423
+ this.emit("pairingComplete", result);
10424
+ return result;
10425
+ }
10426
+ }
10427
+ /**
10428
+ * Reject the pending pairing request.
10429
+ */
10430
+ reject(reason = "Rejected by user") {
10431
+ if (this.role !== "approver" || !this._pendingRequest) return;
10432
+ this.sendMessage({
10433
+ type: "pair-rejected",
10434
+ reason
10435
+ });
10436
+ this._pendingRequest = null;
10437
+ this.emit("pairingComplete", {
10438
+ success: false,
10439
+ error: reason
10440
+ });
10441
+ }
10442
+ /**
10443
+ * Send a pairing request to Device A. Call this after the "connected"
10444
+ * event fires.
10445
+ */
10446
+ requestPairing(request) {
10447
+ if (this.role !== "requester") return;
10448
+ this.sendMessage({
10449
+ type: "pair-request",
10450
+ publicKey: request.publicKey,
10451
+ x25519Key: request.x25519Key,
10452
+ deviceName: request.deviceName
10453
+ });
10454
+ }
10455
+ get isDestroyed() {
10456
+ return this._destroyed;
10457
+ }
10458
+ destroy() {
10459
+ if (this._destroyed) return;
10460
+ this._destroyed = true;
10461
+ if (this.timeoutHandle) {
10462
+ clearTimeout(this.timeoutHandle);
10463
+ this.timeoutHandle = null;
10464
+ }
10465
+ if (this.webrtc) {
10466
+ this.webrtc.destroy();
10467
+ this.webrtc = null;
10468
+ }
10469
+ this.removeAllListeners();
10470
+ }
10471
+ start() {
10472
+ this.webrtc = new AbracadabraWebRTC({
10473
+ docId: codeToRoomId(this.pairingCode),
10474
+ url: this.config.serverUrl,
10475
+ token: this.config.token,
10476
+ iceServers: this.config.iceServers,
10477
+ e2ee: this.config.e2ee,
10478
+ enableDocSync: false,
10479
+ enableAwarenessSync: false,
10480
+ enableFileTransfer: false,
10481
+ autoConnect: false,
10482
+ WebSocketPolyfill: this.config.WebSocketPolyfill
10483
+ });
10484
+ this.webrtc.on("e2eeEstablished", ({ peerId }) => {
10485
+ this._connectedPeerId = peerId;
10486
+ this.emit("connected");
10487
+ });
10488
+ this.webrtc.on("customMessage", ({ peerId, payload }) => {
10489
+ this.handleMessage(peerId, payload);
10490
+ });
10491
+ this.webrtc.on("peerLeft", () => {
10492
+ if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error("Peer disconnected"));
10493
+ });
10494
+ this.webrtc.on("signalingError", (err) => {
10495
+ this.emit("error", /* @__PURE__ */ new Error(`Signaling: ${err.message}`));
10496
+ });
10497
+ this.timeoutHandle = setTimeout(() => {
10498
+ if (!this._destroyed) {
10499
+ this.emit("error", /* @__PURE__ */ new Error("Pairing timed out"));
10500
+ this.destroy();
10501
+ }
10502
+ }, PAIRING_TIMEOUT_MS);
10503
+ this.webrtc.connect();
10504
+ }
10505
+ sendMessage(msg) {
10506
+ if (!this.webrtc || !this._connectedPeerId) return;
10507
+ this.webrtc.sendCustomMessage(this._connectedPeerId, JSON.stringify(msg));
10508
+ }
10509
+ handleMessage(peerId, payload) {
10510
+ let msg;
10511
+ try {
10512
+ msg = JSON.parse(payload);
10513
+ } catch {
10514
+ return;
10515
+ }
10516
+ switch (msg.type) {
10517
+ case "pair-request":
10518
+ if (this.role !== "approver") return;
10519
+ this._pendingRequest = {
10520
+ publicKey: msg.publicKey,
10521
+ x25519Key: msg.x25519Key,
10522
+ deviceName: msg.deviceName
10523
+ };
10524
+ this.emit("pairingRequest", this._pendingRequest);
10525
+ break;
10526
+ case "pair-approved":
10527
+ if (this.role !== "requester") return;
10528
+ this.emit("approved");
10529
+ this.emit("pairingComplete", { success: true });
10530
+ break;
10531
+ case "pair-rejected":
10532
+ if (this.role !== "requester") return;
10533
+ this.emit("rejected", msg.reason);
10534
+ this.emit("pairingComplete", {
10535
+ success: false,
10536
+ error: msg.reason
10537
+ });
10538
+ break;
10539
+ case "pair-invite-code":
10540
+ if (this.role !== "requester") return;
10541
+ this.emit("inviteCode", msg.code);
10542
+ break;
10543
+ }
10544
+ }
10545
+ };
10546
+
10259
10547
  //#endregion
10260
10548
  //#region packages/provider/src/sync/BroadcastChannelSync.ts
10261
10549
  /**
@@ -10408,5 +10696,5 @@ var BroadcastChannelSync = class BroadcastChannelSync extends EventEmitter {
10408
10696
  };
10409
10697
 
10410
10698
  //#endregion
10411
- export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, KEY_EXCHANGE_CHANNEL, ManualSignaling, MessageTooBig, MessageType, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, decryptField, encryptField, makeEncryptedYMap, makeEncryptedYText, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
10699
+ export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, KEY_EXCHANGE_CHANNEL, ManualSignaling, MessageTooBig, MessageType, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, decryptField, encryptField, makeEncryptedYMap, makeEncryptedYText, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
10412
10700
  //# sourceMappingURL=abracadabra-provider.esm.js.map