@abraca/dabra 1.0.3 → 1.0.5

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.
@@ -23,11 +23,26 @@ export class YjsDataChannel {
23
23
  private channelOpenHandler: ((data: { name: string; channel: RTCDataChannel }) => void) | null = null;
24
24
  private channelMessageHandler: ((data: { name: string; data: any }) => void) | null = null;
25
25
 
26
+ /** Channel names used for sync and awareness (supports namespaced subdoc channels). */
27
+ private readonly syncChannelName: string;
28
+ private readonly awarenessChannelName: string;
29
+
30
+ /**
31
+ * @param document - The Y.Doc to sync
32
+ * @param awareness - Optional Awareness instance
33
+ * @param router - DataChannelRouter for the peer connection
34
+ * @param channelPrefix - Optional prefix for subdocument channels (e.g. `"{childId}:"`)
35
+ */
26
36
  constructor(
27
37
  private readonly document: Y.Doc,
28
38
  private readonly awareness: Awareness | null,
29
39
  private readonly router: DataChannelRouter,
30
- ) {}
40
+ channelPrefix?: string,
41
+ ) {
42
+ const prefix = channelPrefix ?? "";
43
+ this.syncChannelName = `${prefix}${CHANNEL_NAMES.YJS_SYNC}`;
44
+ this.awarenessChannelName = `${prefix}${CHANNEL_NAMES.AWARENESS}`;
45
+ }
31
46
 
32
47
  /** Start listening for Y.js updates and data channel messages. */
33
48
  attach(): void {
@@ -36,13 +51,12 @@ export class YjsDataChannel {
36
51
  // Don't echo updates we received from this data channel.
37
52
  if (origin === this) return;
38
53
 
39
- const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
40
- if (!channel || channel.readyState !== "open") return;
54
+ if (!this.router.isOpen(this.syncChannelName)) return;
41
55
 
42
56
  const encoder = encoding.createEncoder();
43
57
  encoding.writeVarUint(encoder, YJS_MSG.UPDATE);
44
58
  encoding.writeVarUint8Array(encoder, update);
45
- channel.send(encoding.toUint8Array(encoder));
59
+ this.router.send(this.syncChannelName, encoding.toUint8Array(encoder));
46
60
  };
47
61
  this.document.on("update", this.docUpdateHandler);
48
62
 
@@ -52,21 +66,20 @@ export class YjsDataChannel {
52
66
  { added, updated, removed }: { added: number[]; updated: number[]; removed: number[] },
53
67
  _origin: any,
54
68
  ) => {
55
- const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
56
- if (!channel || channel.readyState !== "open") return;
69
+ if (!this.router.isOpen(this.awarenessChannelName)) return;
57
70
 
58
71
  const changedClients = added.concat(updated).concat(removed);
59
72
  const update = encodeAwarenessUpdate(this.awareness!, changedClients);
60
- channel.send(update);
73
+ this.router.send(this.awarenessChannelName, update);
61
74
  };
62
75
  this.awareness.on("update", this.awarenessUpdateHandler);
63
76
  }
64
77
 
65
78
  // Handle incoming data channel messages.
66
79
  this.channelMessageHandler = ({ name, data }: { name: string; data: any }) => {
67
- if (name === CHANNEL_NAMES.YJS_SYNC) {
80
+ if (name === this.syncChannelName) {
68
81
  this.handleSyncMessage(data);
69
- } else if (name === CHANNEL_NAMES.AWARENESS) {
82
+ } else if (name === this.awarenessChannelName) {
70
83
  this.handleAwarenessMessage(data);
71
84
  }
72
85
  };
@@ -74,37 +87,33 @@ export class YjsDataChannel {
74
87
 
75
88
  // When sync channel opens, initiate sync handshake.
76
89
  this.channelOpenHandler = ({ name }: { name: string; channel: RTCDataChannel }) => {
77
- if (name === CHANNEL_NAMES.YJS_SYNC) {
90
+ if (name === this.syncChannelName) {
78
91
  this.sendSyncStep1();
79
- } else if (name === CHANNEL_NAMES.AWARENESS && this.awareness) {
92
+ } else if (name === this.awarenessChannelName && this.awareness) {
80
93
  // Send full awareness state on channel open.
81
- const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
82
- if (channel?.readyState === "open") {
94
+ if (this.router.isOpen(this.awarenessChannelName)) {
83
95
  const update = encodeAwarenessUpdate(
84
96
  this.awareness,
85
97
  Array.from(this.awareness.getStates().keys()),
86
98
  );
87
- channel.send(update);
99
+ this.router.send(this.awarenessChannelName, update);
88
100
  }
89
101
  }
90
102
  };
91
103
  this.router.on("channelOpen", this.channelOpenHandler);
92
104
 
93
105
  // If sync channel is already open, start sync immediately.
94
- if (this.router.isOpen(CHANNEL_NAMES.YJS_SYNC)) {
106
+ if (this.router.isOpen(this.syncChannelName)) {
95
107
  this.sendSyncStep1();
96
108
  }
97
109
 
98
110
  // If awareness channel is already open, send state immediately.
99
- if (this.awareness && this.router.isOpen(CHANNEL_NAMES.AWARENESS)) {
100
- const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
101
- if (channel?.readyState === "open") {
102
- const update = encodeAwarenessUpdate(
103
- this.awareness,
104
- Array.from(this.awareness.getStates().keys()),
105
- );
106
- channel.send(update);
107
- }
111
+ if (this.awareness && this.router.isOpen(this.awarenessChannelName)) {
112
+ const update = encodeAwarenessUpdate(
113
+ this.awareness,
114
+ Array.from(this.awareness.getStates().keys()),
115
+ );
116
+ this.router.send(this.awarenessChannelName, update);
108
117
  }
109
118
  }
110
119
 
@@ -138,13 +147,12 @@ export class YjsDataChannel {
138
147
  }
139
148
 
140
149
  private sendSyncStep1(): void {
141
- const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
142
- if (!channel || channel.readyState !== "open") return;
150
+ if (!this.router.isOpen(this.syncChannelName)) return;
143
151
 
144
152
  const encoder = encoding.createEncoder();
145
153
  encoding.writeVarUint(encoder, YJS_MSG.SYNC);
146
154
  syncProtocol.writeSyncStep1(encoder, this.document);
147
- channel.send(encoding.toUint8Array(encoder));
155
+ this.router.send(this.syncChannelName, encoding.toUint8Array(encoder));
148
156
  }
149
157
 
150
158
  private handleSyncMessage(data: ArrayBuffer | Uint8Array): void {
@@ -170,9 +178,8 @@ export class YjsDataChannel {
170
178
  responseEncoder,
171
179
  encoding.toUint8Array(encoder),
172
180
  );
173
- const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
174
- if (channel?.readyState === "open") {
175
- channel.send(encoding.toUint8Array(responseEncoder));
181
+ if (this.router.isOpen(this.syncChannelName)) {
182
+ this.router.send(this.syncChannelName, encoding.toUint8Array(responseEncoder));
176
183
  }
177
184
  }
178
185
 
@@ -1,9 +1,13 @@
1
1
  export { AbracadabraWebRTC } from "./AbracadabraWebRTC.ts";
2
2
  export { SignalingSocket } from "./SignalingSocket.ts";
3
3
  export { PeerConnection } from "./PeerConnection.ts";
4
- export { DataChannelRouter } from "./DataChannelRouter.ts";
4
+ export { DataChannelRouter, KEY_EXCHANGE_CHANNEL } from "./DataChannelRouter.ts";
5
5
  export { YjsDataChannel } from "./YjsDataChannel.ts";
6
6
  export { FileTransferChannel, FileTransferHandle } from "./FileTransferChannel.ts";
7
+ export { E2EEChannel } from "./E2EEChannel.ts";
8
+ export type { E2EEIdentity } from "./E2EEChannel.ts";
9
+ export { ManualSignaling } from "./ManualSignaling.ts";
10
+ export type { ManualSignalingBlob } from "./ManualSignaling.ts";
7
11
  export type {
8
12
  AbracadabraWebRTCConfiguration,
9
13
  PeerInfo,
@@ -98,6 +98,10 @@ export const CHANNEL_NAMES = {
98
98
  CUSTOM: "custom",
99
99
  } as const;
100
100
 
101
+ // ── E2EE ────────────────────────────────────────────────────────────────────
102
+
103
+ export type { E2EEIdentity } from "./E2EEChannel.ts";
104
+
101
105
  // ── Configuration ───────────────────────────────────────────────────────────
102
106
 
103
107
  export interface AbracadabraWebRTCConfiguration {
@@ -107,6 +111,13 @@ export interface AbracadabraWebRTCConfiguration {
107
111
  /** Server base URL (http/https). Signaling URL derived automatically. */
108
112
  url: string;
109
113
 
114
+ /**
115
+ * Override the signaling WebSocket URL. When set, signaling connects to
116
+ * this server instead of deriving from `url`. Useful for local spaces that
117
+ * piggyback a remote server's signaling endpoint.
118
+ */
119
+ signalingUrl?: string;
120
+
110
121
  /** JWT token or async token factory. */
111
122
  token: string | (() => string) | (() => Promise<string>);
112
123
 
@@ -142,6 +153,13 @@ export interface AbracadabraWebRTCConfiguration {
142
153
  /** Auto-connect on construction. Default: true. */
143
154
  autoConnect?: boolean;
144
155
 
156
+ /**
157
+ * E2EE identity for application-level encryption on data channels.
158
+ * When provided, all data channel messages (except key-exchange) are
159
+ * encrypted with AES-256-GCM using X25519 ECDH-derived session keys.
160
+ */
161
+ e2ee?: import("./E2EEChannel.ts").E2EEIdentity;
162
+
145
163
  /** WebSocket polyfill for signaling (e.g. for Node.js). */
146
164
  WebSocketPolyfill?: any;
147
165
  }