@dabble/patches 0.9.1 → 0.9.2

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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/{BaseDoc-CXHXcW18.d.ts → BaseDoc-rCKMFV6B.d.ts} +1 -1
  3. package/dist/algorithms/ot/server/createVersion.d.ts +1 -1
  4. package/dist/client/BaseDoc.d.ts +1 -1
  5. package/dist/client/ClientAlgorithm.d.ts +1 -1
  6. package/dist/client/LWWAlgorithm.d.ts +1 -1
  7. package/dist/client/LWWDoc.d.ts +1 -1
  8. package/dist/client/OTAlgorithm.d.ts +1 -1
  9. package/dist/client/OTDoc.d.ts +1 -1
  10. package/dist/client/Patches.d.ts +1 -1
  11. package/dist/client/PatchesBranchClient.d.ts +1 -1
  12. package/dist/client/PatchesDoc.d.ts +1 -1
  13. package/dist/client/factories.d.ts +1 -1
  14. package/dist/client/index.d.ts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/net/PatchesSync.d.ts +1 -1
  17. package/dist/net/index.d.ts +5 -3
  18. package/dist/net/index.js +1 -1
  19. package/dist/net/protocol/types.d.ts +16 -2
  20. package/dist/net/rest/PatchesREST.d.ts +23 -0
  21. package/dist/net/rest/PatchesREST.js +46 -0
  22. package/dist/net/rest/PatchesRESTSignalingTransport.d.ts +42 -0
  23. package/dist/net/rest/PatchesRESTSignalingTransport.js +34 -0
  24. package/dist/net/rest/SSEServer.d.ts +15 -0
  25. package/dist/net/rest/SSEServer.js +25 -0
  26. package/dist/net/rest/SSESignalingService.d.ts +60 -0
  27. package/dist/net/rest/SSESignalingService.js +27 -0
  28. package/dist/net/rest/index.d.ts +3 -0
  29. package/dist/net/rest/index.js +2 -0
  30. package/dist/net/signaling/SignalingService.d.ts +74 -0
  31. package/dist/net/signaling/SignalingService.js +135 -0
  32. package/dist/net/signaling/index.d.ts +7 -0
  33. package/dist/net/signaling/index.js +1 -0
  34. package/dist/net/webrtc/WebRTCAwareness.d.ts +0 -2
  35. package/dist/net/webrtc/WebRTCTransport.d.ts +9 -8
  36. package/dist/net/webrtc/WebRTCTransport.js +3 -2
  37. package/dist/net/webrtc/index.d.ts +0 -2
  38. package/dist/net/websocket/SignalingService.d.ts +2 -66
  39. package/dist/net/websocket/SignalingService.js +1 -135
  40. package/dist/server/branchUtils.d.ts +1 -1
  41. package/dist/shared/doc-manager.d.ts +1 -1
  42. package/dist/solid/context.d.ts +1 -1
  43. package/dist/solid/doc-manager.d.ts +1 -1
  44. package/dist/solid/index.d.ts +1 -1
  45. package/dist/solid/primitives.d.ts +1 -1
  46. package/dist/vue/composables.d.ts +2 -2
  47. package/dist/vue/doc-manager.d.ts +1 -1
  48. package/dist/vue/index.d.ts +1 -1
  49. package/dist/vue/managed-docs.d.ts +2 -2
  50. package/dist/vue/provider.d.ts +1 -1
  51. package/package.json +1 -1
package/README.md CHANGED
@@ -254,7 +254,7 @@ When to use which? WebSocket for document sync. WebRTC for presence/cursors to r
254
254
 
255
255
  ### Awareness (Presence & Cursors)
256
256
 
257
- Show who's online, where their cursor is, what they're selecting. Works over both WebSocket and WebRTC.
257
+ Show who's online, where their cursor is, what they're selecting. `WebRTCAwareness` carries presence over peer-to-peer WebRTC connections, with the signaling handshake multiplexed over whatever sync channel you already have open — `WebSocketTransport` or `PatchesREST` (SSE+REST). No second connection, same `clientId`.
258
258
 
259
259
  See [Awareness documentation](./docs/awareness.md) for implementation details.
260
260
 
@@ -246,4 +246,4 @@ declare abstract class BaseDoc<T extends object = object> extends ReadonlyStoreC
246
246
  abstract import(snapshot: PatchesSnapshot<T>): void;
247
247
  }
248
248
 
249
- export { BaseDoc as B, OTDoc as O, type PatchesDocOptions as P, type PatchesDoc as a };
249
+ export { BaseDoc as B, OTDoc as O, type PatchesDoc as P, type PatchesDocOptions as a };
@@ -1,5 +1,5 @@
1
1
  import { OTStoreBackend } from '../../../server/types.js';
2
- import { EditableVersionMetadata, VersionMetadata, Change } from '../../../types.js';
2
+ import { EditableVersionMetadata, Change, VersionMetadata } from '../../../types.js';
3
3
  import '../../../json-patch/types.js';
4
4
  import '../../../json-patch/JSONPatch.js';
5
5
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import 'easy-signal';
2
2
  import '../json-patch/types.js';
3
3
  import '../types.js';
4
- export { B as BaseDoc } from '../BaseDoc-CXHXcW18.js';
4
+ export { B as BaseDoc } from '../BaseDoc-rCKMFV6B.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
- import { a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
3
+ import { P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
4
4
  import { PatchesStore, TrackedDoc } from './PatchesStore.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
3
  import { ClientAlgorithm } from './ClientAlgorithm.js';
4
4
  import { LWWClientStore } from './LWWClientStore.js';
5
- import { a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
5
+ import { P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
6
6
  import { TrackedDoc } from './PatchesStore.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
- import { B as BaseDoc } from '../BaseDoc-CXHXcW18.js';
3
+ import { B as BaseDoc } from '../BaseDoc-rCKMFV6B.js';
4
4
  import '../json-patch/JSONPatch.js';
5
5
  import '@dabble/delta';
6
6
  import 'easy-signal';
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
3
  import { ClientAlgorithm } from './ClientAlgorithm.js';
4
4
  import { OTClientStore } from './OTClientStore.js';
5
- import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
5
+ import { a as PatchesDocOptions, P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
6
6
  import { TrackedDoc } from './PatchesStore.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
@@ -1,5 +1,5 @@
1
1
  import '../types.js';
2
- export { O as OTDoc } from '../BaseDoc-CXHXcW18.js';
2
+ export { O as OTDoc } from '../BaseDoc-rCKMFV6B.js';
3
3
  import '../json-patch/JSONPatch.js';
4
4
  import '@dabble/delta';
5
5
  import '../json-patch/types.js';
@@ -3,7 +3,7 @@ import { Unsubscriber } from 'easy-signal';
3
3
  import { JSONPatchOp } from '../json-patch/types.js';
4
4
  import { Change } from '../types.js';
5
5
  import { ClientAlgorithm } from './ClientAlgorithm.js';
6
- import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
6
+ import { a as PatchesDocOptions, P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
7
7
  import { AlgorithmName } from './PatchesStore.js';
8
8
  import '../json-patch/JSONPatch.js';
9
9
  import '@dabble/delta';
@@ -8,7 +8,7 @@ import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../json-patch/types.js';
10
10
  import './ClientAlgorithm.js';
11
- import '../BaseDoc-CXHXcW18.js';
11
+ import '../BaseDoc-rCKMFV6B.js';
12
12
 
13
13
  interface PatchesBranchClientOptions {
14
14
  /** Algorithm to use for the branch document (defaults to the Patches instance default). */
@@ -1,6 +1,6 @@
1
1
  import 'easy-signal';
2
2
  import '../json-patch/types.js';
3
3
  import '../types.js';
4
- export { O as OTDoc, a as PatchesDoc, P as PatchesDocOptions } from '../BaseDoc-CXHXcW18.js';
4
+ export { O as OTDoc, P as PatchesDoc, a as PatchesDocOptions } from '../BaseDoc-rCKMFV6B.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import { AlgorithmName } from './PatchesStore.js';
2
2
  import { Patches } from './Patches.js';
3
- import { P as PatchesDocOptions } from '../BaseDoc-CXHXcW18.js';
3
+ import { a as PatchesDocOptions } from '../BaseDoc-rCKMFV6B.js';
4
4
  import '../types.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -1,4 +1,4 @@
1
- export { B as BaseDoc, O as OTDoc, a as PatchesDoc, P as PatchesDocOptions } from '../BaseDoc-CXHXcW18.js';
1
+ export { B as BaseDoc, O as OTDoc, P as PatchesDoc, a as PatchesDocOptions } from '../BaseDoc-rCKMFV6B.js';
2
2
  export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmExternalDBPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches, upgradePatchesDB } from './factories.js';
3
3
  export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './IndexedDBStore.js';
4
4
  export { OTIndexedDBStore } from './OTIndexedDBStore.js';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { Delta } from '@dabble/delta';
2
- export { B as BaseDoc, O as OTDoc, a as PatchesDoc, P as PatchesDocOptions } from './BaseDoc-CXHXcW18.js';
2
+ export { B as BaseDoc, O as OTDoc, P as PatchesDoc, a as PatchesDocOptions } from './BaseDoc-rCKMFV6B.js';
3
3
  export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmExternalDBPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches, upgradePatchesDB } from './client/factories.js';
4
4
  export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './client/IndexedDBStore.js';
5
5
  export { OTIndexedDBStore } from './client/OTIndexedDBStore.js';
@@ -13,7 +13,7 @@ import { WebSocketOptions } from './websocket/WebSocketTransport.js';
13
13
  import '../json-patch/JSONPatch.js';
14
14
  import '@dabble/delta';
15
15
  import '../json-patch/types.js';
16
- import '../BaseDoc-CXHXcW18.js';
16
+ import '../BaseDoc-rCKMFV6B.js';
17
17
  import '../utils/deferred.js';
18
18
 
19
19
  interface PatchesSyncState {
@@ -4,17 +4,19 @@ export { PatchesClient } from './PatchesClient.js';
4
4
  export { PatchesConnection } from './PatchesConnection.js';
5
5
  export { PatchesSync, PatchesSyncOptions, PatchesSyncState } from './PatchesSync.js';
6
6
  export { PatchesREST, PatchesRESTOptions } from './rest/PatchesREST.js';
7
+ export { PatchesRESTSignalingTransport } from './rest/PatchesRESTSignalingTransport.js';
7
8
  export { BufferedEvent, SSEServer, SSEServerOptions } from './rest/SSEServer.js';
9
+ export { SSESignalingService } from './rest/SSESignalingService.js';
8
10
  export { normalizeIds } from './rest/utils.js';
9
11
  export { JSONRPCClient } from './protocol/JSONRPCClient.js';
10
12
  export { ApiDefinition, ConnectionSignalSubscriber, JSONRPCServer, JSONRPCServerOptions, MessageHandler } from './protocol/JSONRPCServer.js';
11
13
  export { getAuthContext, getClientId } from './serverContext.js';
12
- export { AwarenessUpdateNotificationParams, BranchAPI, ClientTransport, ConnectionState, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, Message, PatchesAPI, PatchesNotificationParams, ServerTransport, SignalNotificationParams } from './protocol/types.js';
14
+ export { AwarenessUpdateNotificationParams, BranchAPI, ClientTransport, ConnectionState, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, Message, PatchesAPI, PatchesNotificationParams, ServerTransport, SignalNotificationParams, SignalingTransport } from './protocol/types.js';
13
15
  export { rpcError, rpcNotification, rpcResponse } from './protocol/utils.js';
14
16
  export { Access, AuthContext, AuthorizationProvider, allowAll, assertNotDeleted, denyAll } from './websocket/AuthorizationProvider.js';
15
17
  export { onlineState } from './websocket/onlineState.js';
16
18
  export { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
17
- export { JsonRpcMessage, SignalingService } from './websocket/SignalingService.js';
19
+ export { JsonRpcMessage, SignalingService } from './signaling/SignalingService.js';
18
20
  export { WebSocketServer, WebSocketServerOptions } from './websocket/WebSocketServer.js';
19
21
  export { WebSocketOptions, WebSocketTransport } from './websocket/WebSocketTransport.js';
20
22
  export { CommitChangesOptions } from '../types.js';
@@ -23,7 +25,7 @@ import '../algorithms/ot/shared/changeBatching.js';
23
25
  import '../client/BranchClientStore.js';
24
26
  import '../client/ClientAlgorithm.js';
25
27
  import '../json-patch/types.js';
26
- import '../BaseDoc-CXHXcW18.js';
28
+ import '../BaseDoc-rCKMFV6B.js';
27
29
  import '../client/PatchesStore.js';
28
30
  import '../client/Patches.js';
29
31
  import '../server/types.js';
package/dist/net/index.js CHANGED
@@ -12,7 +12,7 @@ export * from "./protocol/utils.js";
12
12
  export * from "./websocket/AuthorizationProvider.js";
13
13
  export * from "./websocket/onlineState.js";
14
14
  export * from "./websocket/PatchesWebSocket.js";
15
- export * from "./websocket/SignalingService.js";
15
+ export * from "./signaling/SignalingService.js";
16
16
  export * from "./websocket/WebSocketServer.js";
17
17
  export * from "./websocket/WebSocketTransport.js";
18
18
  export {
@@ -1,4 +1,4 @@
1
- import { Unsubscriber } from 'easy-signal';
1
+ import { Unsubscriber, Signal } from 'easy-signal';
2
2
  import { ListBranchesOptions, Branch, CreateBranchMetadata, EditableBranchMetadata, PatchesState, Change, ChangeInput, CommitChangesOptions, EditableVersionMetadata, ListVersionsOptions, VersionMetadata } from '../../types.js';
3
3
  import '../../json-patch/JSONPatch.js';
4
4
  import '@dabble/delta';
@@ -31,6 +31,20 @@ interface ClientTransport {
31
31
  */
32
32
  onMessage(cb: (raw: string) => void): Unsubscriber;
33
33
  }
34
+ /**
35
+ * Lifecycle-aware transport that {@link WebRTCTransport} can ride on top of as a
36
+ * signaling channel. Anything implementing {@link ClientTransport} plus a
37
+ * `connect()` / `state` / `onStateChange` triplet qualifies — including
38
+ * `WebSocketTransport` and `PatchesRESTSignalingTransport`.
39
+ */
40
+ interface SignalingTransport extends ClientTransport {
41
+ /** Open the underlying channel. May be a no-op if the channel is already open. */
42
+ connect(): Promise<void> | void;
43
+ /** Current connection state. */
44
+ readonly state: ConnectionState;
45
+ /** Emits whenever {@link state} changes. */
46
+ readonly onStateChange: Signal<(state: ConnectionState) => void>;
47
+ }
34
48
  /**
35
49
  * Minimal contract for server-side transports that can have **multiple** logical peers.
36
50
  * Each message must indicate the **from / to** connection. Any additional lifecycle
@@ -155,4 +169,4 @@ interface SignalNotificationParams {
155
169
  data: any;
156
170
  }
157
171
 
158
- export { type AwarenessUpdateNotificationParams, type BranchAPI, type ClientTransport, CommitChangesOptions, type ConnectionState, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type Message, type PatchesAPI, type PatchesNotificationParams, type ServerTransport, type SignalNotificationParams };
172
+ export { type AwarenessUpdateNotificationParams, type BranchAPI, type ClientTransport, CommitChangesOptions, type ConnectionState, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type Message, type PatchesAPI, type PatchesNotificationParams, type ServerTransport, type SignalNotificationParams, type SignalingTransport };
@@ -37,6 +37,11 @@ declare class PatchesREST implements PatchesConnection {
37
37
  readonly onStateChange: easy_signal.Signal<(state: ConnectionState) => void>;
38
38
  readonly onChangesCommitted: easy_signal.Signal<(docId: string, changes: Change[], options?: CommitChangesOptions) => void>;
39
39
  readonly onDocDeleted: easy_signal.Signal<(docId: string) => void>;
40
+ /**
41
+ * Emits raw JSON-RPC strings received over the multiplexed `signal` SSE channel.
42
+ * Used by `PatchesRESTSignalingTransport` to drive `WebRTCTransport`'s signaling.
43
+ */
44
+ readonly onSignal: easy_signal.Signal<(raw: string) => void>;
40
45
  private _url;
41
46
  private _state;
42
47
  private eventSource;
@@ -46,6 +51,8 @@ declare class PatchesREST implements PatchesConnection {
46
51
  constructor(url: string, options?: PatchesRESTOptions);
47
52
  get url(): string;
48
53
  set url(url: string);
54
+ /** Current connection state of the underlying SSE stream. */
55
+ get state(): ConnectionState;
49
56
  connect(): Promise<void>;
50
57
  disconnect(): void;
51
58
  subscribe(ids: string | string[]): Promise<string[]>;
@@ -91,6 +98,22 @@ declare class PatchesREST implements PatchesConnection {
91
98
  * `POST /docs/:docId/_members/:uid/revoke`
92
99
  */
93
100
  revokeMember(docId: string, targetUid: string): Promise<boolean>;
101
+ /**
102
+ * POSTs a raw JSON-RPC string to `/signal/:clientId`. Used as the upstream
103
+ * half of the multiplexed signaling channel: receive happens via the `signal`
104
+ * SSE event (see {@link onSignal}).
105
+ *
106
+ * The body is sent verbatim — callers pass an already-stringified JSON-RPC
107
+ * message. The endpoint forwards it to {@link SignalingService.handleClientMessage}.
108
+ *
109
+ * Silently no-ops when the SSE stream is not connected. Signaling is
110
+ * inherently best-effort and the server has no live `signal` channel back
111
+ * to this client until the stream is up, so a frame sent before connect
112
+ * resolves can't be relayed anyway. Throwing here would surface as an
113
+ * unhandled rejection through `JSONRPCClient.call`, which doesn't await
114
+ * `transport.send`.
115
+ */
116
+ sendSignal(raw: string): Promise<void>;
94
117
  private _setState;
95
118
  private _getHeaders;
96
119
  private _fetch;
@@ -13,6 +13,11 @@ class PatchesREST {
13
13
  onStateChange = signal();
14
14
  onChangesCommitted = signal();
15
15
  onDocDeleted = signal();
16
+ /**
17
+ * Emits raw JSON-RPC strings received over the multiplexed `signal` SSE channel.
18
+ * Used by `PatchesRESTSignalingTransport` to drive `WebRTCTransport`'s signaling.
19
+ */
20
+ onSignal = signal();
16
21
  _url;
17
22
  _state = "disconnected";
18
23
  eventSource = null;
@@ -36,6 +41,11 @@ class PatchesREST {
36
41
  set url(url) {
37
42
  this._url = url.replace(/\/$/, "");
38
43
  }
44
+ // --- Connection State ---
45
+ /** Current connection state of the underlying SSE stream. */
46
+ get state() {
47
+ return this._state;
48
+ }
39
49
  // --- Connection Lifecycle ---
40
50
  connect() {
41
51
  this.shouldBeConnected = true;
@@ -94,6 +104,9 @@ class PatchesREST {
94
104
  } catch {
95
105
  }
96
106
  });
107
+ es.addEventListener("signal", (e) => {
108
+ this.onSignal.emit(e.data);
109
+ });
97
110
  es.addEventListener("resync", () => {
98
111
  if (!this.shouldBeConnected) return;
99
112
  this._setState("disconnected");
@@ -235,6 +248,39 @@ class PatchesREST {
235
248
  );
236
249
  return Boolean(result?.ok);
237
250
  }
251
+ // --- WebRTC Signaling ---
252
+ /**
253
+ * POSTs a raw JSON-RPC string to `/signal/:clientId`. Used as the upstream
254
+ * half of the multiplexed signaling channel: receive happens via the `signal`
255
+ * SSE event (see {@link onSignal}).
256
+ *
257
+ * The body is sent verbatim — callers pass an already-stringified JSON-RPC
258
+ * message. The endpoint forwards it to {@link SignalingService.handleClientMessage}.
259
+ *
260
+ * Silently no-ops when the SSE stream is not connected. Signaling is
261
+ * inherently best-effort and the server has no live `signal` channel back
262
+ * to this client until the stream is up, so a frame sent before connect
263
+ * resolves can't be relayed anyway. Throwing here would surface as an
264
+ * unhandled rejection through `JSONRPCClient.call`, which doesn't await
265
+ * `transport.send`.
266
+ */
267
+ async sendSignal(raw) {
268
+ if (this._state !== "connected") return;
269
+ const headers = await this._getHeaders();
270
+ const response = await globalThis.fetch(`${this._url}/signal/${this.clientId}`, {
271
+ method: "POST",
272
+ credentials: "include",
273
+ headers: {
274
+ "Content-Type": "application/json",
275
+ ...headers
276
+ },
277
+ body: raw,
278
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
279
+ });
280
+ if (!response.ok) {
281
+ throw new StatusError(response.status, response.statusText);
282
+ }
283
+ }
238
284
  // --- Private Helpers ---
239
285
  _setState(state) {
240
286
  if (state === this._state) return;
@@ -0,0 +1,42 @@
1
+ import * as easy_signal from 'easy-signal';
2
+ import { Unsubscriber } from 'easy-signal';
3
+ import { SignalingTransport, ConnectionState } from '../protocol/types.js';
4
+ import { PatchesREST } from './PatchesREST.js';
5
+ import '../../types.js';
6
+ import '../../json-patch/JSONPatch.js';
7
+ import '@dabble/delta';
8
+ import '../../json-patch/types.js';
9
+ import '../PatchesConnection.js';
10
+ import '../invite.js';
11
+
12
+ /**
13
+ * Adapter that exposes a {@link PatchesREST} connection as a {@link SignalingTransport}
14
+ * for `WebRTCTransport`. Multiplexes signaling over the existing SSE stream:
15
+ *
16
+ * - **send** → `PatchesREST.sendSignal()` → `POST /signal/:clientId`.
17
+ * - **receive** → subscribes to `PatchesREST.onSignal` (the `signal` SSE event).
18
+ *
19
+ * Does not own the connection. The application calls `patches.connect()`
20
+ * directly; `connect()` here just delegates so callers can still `await` it.
21
+ */
22
+ declare class PatchesRESTSignalingTransport implements SignalingTransport {
23
+ private patches;
24
+ /**
25
+ * @param patches - The shared `PatchesREST` instance handling document sync.
26
+ * The same `clientId` is used for both, so signaling addressing matches.
27
+ */
28
+ constructor(patches: PatchesREST);
29
+ get state(): ConnectionState;
30
+ get onStateChange(): easy_signal.Signal<(state: ConnectionState) => void>;
31
+ /**
32
+ * Delegates to {@link PatchesREST.connect}. Safe to call multiple times — if
33
+ * the SSE stream is already open, the underlying call is a no-op.
34
+ */
35
+ connect(): Promise<void>;
36
+ /** Sends a raw JSON-RPC frame upstream over the signaling REST endpoint. */
37
+ send(raw: string): Promise<void>;
38
+ /** Subscribes to inbound JSON-RPC frames received via the `signal` SSE event. */
39
+ onMessage(cb: (raw: string) => void): Unsubscriber;
40
+ }
41
+
42
+ export { PatchesRESTSignalingTransport };
@@ -0,0 +1,34 @@
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ class PatchesRESTSignalingTransport {
3
+ /**
4
+ * @param patches - The shared `PatchesREST` instance handling document sync.
5
+ * The same `clientId` is used for both, so signaling addressing matches.
6
+ */
7
+ constructor(patches) {
8
+ this.patches = patches;
9
+ }
10
+ get state() {
11
+ return this.patches.state;
12
+ }
13
+ get onStateChange() {
14
+ return this.patches.onStateChange;
15
+ }
16
+ /**
17
+ * Delegates to {@link PatchesREST.connect}. Safe to call multiple times — if
18
+ * the SSE stream is already open, the underlying call is a no-op.
19
+ */
20
+ async connect() {
21
+ await this.patches.connect();
22
+ }
23
+ /** Sends a raw JSON-RPC frame upstream over the signaling REST endpoint. */
24
+ send(raw) {
25
+ return this.patches.sendSignal(raw);
26
+ }
27
+ /** Subscribes to inbound JSON-RPC frames received via the `signal` SSE event. */
28
+ onMessage(cb) {
29
+ return this.patches.onSignal(cb);
30
+ }
31
+ }
32
+ export {
33
+ PatchesRESTSignalingTransport
34
+ };
@@ -127,6 +127,21 @@ declare class SSEServer {
127
127
  * @param exceptClientId - Client ID to exclude (typically the one who made the change).
128
128
  */
129
129
  notify(docId: string, event: string, params: Record<string, any>, exceptClientId?: string): void;
130
+ /**
131
+ * Send an event directly to a single connected client, bypassing subscription
132
+ * routing. Used for client-targeted traffic like WebRTC signaling.
133
+ *
134
+ * Unlike {@link notify}, this does NOT buffer through expiry: if the client
135
+ * is currently disconnected, the call returns false and the event is dropped.
136
+ * Stale signaling replayed after a buffer expiry is harmful (peers gone, ICE
137
+ * candidates moot), so we deliberately do not preserve it.
138
+ *
139
+ * @param clientId - Target client.
140
+ * @param event - SSE event type (e.g. `'signal'`).
141
+ * @param data - Pre-serialised event payload.
142
+ * @returns true if the client was connected and the event was written.
143
+ */
144
+ sendToClient(clientId: string, event: string, data: string): boolean;
130
145
  /**
131
146
  * List all client IDs subscribed to a document.
132
147
  */
@@ -157,6 +157,31 @@ class SSEServer {
157
157
  }
158
158
  }
159
159
  }
160
+ /**
161
+ * Send an event directly to a single connected client, bypassing subscription
162
+ * routing. Used for client-targeted traffic like WebRTC signaling.
163
+ *
164
+ * Unlike {@link notify}, this does NOT buffer through expiry: if the client
165
+ * is currently disconnected, the call returns false and the event is dropped.
166
+ * Stale signaling replayed after a buffer expiry is harmful (peers gone, ICE
167
+ * candidates moot), so we deliberately do not preserve it.
168
+ *
169
+ * @param clientId - Target client.
170
+ * @param event - SSE event type (e.g. `'signal'`).
171
+ * @param data - Pre-serialised event payload.
172
+ * @returns true if the client was connected and the event was written.
173
+ */
174
+ sendToClient(clientId, event, data) {
175
+ const client = this.clients.get(clientId);
176
+ if (!client || !client.writer) return false;
177
+ this._writeEvent(client, {
178
+ id: client.nextEventId++,
179
+ event,
180
+ data,
181
+ timestamp: Date.now()
182
+ });
183
+ return true;
184
+ }
160
185
  /**
161
186
  * List all client IDs subscribed to a document.
162
187
  */
@@ -0,0 +1,60 @@
1
+ import { SignalingService, JsonRpcMessage } from '../signaling/SignalingService.js';
2
+ import { SSEServer } from './SSEServer.js';
3
+ import '../protocol/types.js';
4
+ import 'easy-signal';
5
+ import '../../types.js';
6
+ import '../../json-patch/JSONPatch.js';
7
+ import '@dabble/delta';
8
+ import '../../json-patch/types.js';
9
+ import '../websocket/AuthorizationProvider.js';
10
+ import '../../server/types.js';
11
+
12
+ /**
13
+ * {@link SignalingService} that delivers WebRTC signaling frames over an
14
+ * {@link SSEServer} stream as the multiplexed `signal` event.
15
+ *
16
+ * Wire it up in your routes alongside the existing SSE doc-sync handlers.
17
+ *
18
+ * **Security:** the `fromId` passed to {@link handleClientMessage} MUST come
19
+ * from your authenticated session, not from the request URL. If you trust the
20
+ * URL `:clientId`, client A can POST to `/signal/B` and impersonate B in the
21
+ * WebRTC mesh, redirecting peer connections. Treat the URL parameter as
22
+ * untrusted input and bind sender identity to auth.
23
+ *
24
+ * ```typescript
25
+ * const sse = new SSEServer();
26
+ * const signaling = new SSESignalingService(sse);
27
+ *
28
+ * // GET /events/:clientId — after creating the SSE stream:
29
+ * const clientId = req.auth.clientId; // authenticated, not URL-derived
30
+ * await signaling.onClientConnected(clientId);
31
+ *
32
+ * // POST /signal/:clientId
33
+ * app.post('/signal/:clientId', async (req, res) => {
34
+ * if (req.auth.clientId !== req.params.clientId) return res.status(403).end();
35
+ * const body = await readBody(req);
36
+ * await signaling.handleClientMessage(req.auth.clientId, body);
37
+ * res.status(204).end();
38
+ * });
39
+ *
40
+ * // On SSE stream close:
41
+ * await signaling.onClientDisconnected(clientId);
42
+ * ```
43
+ */
44
+ declare class SSESignalingService extends SignalingService {
45
+ private sse;
46
+ constructor(sse: SSEServer);
47
+ /**
48
+ * Derived from the live SSE connection set rather than tracked separately,
49
+ * so peer-routing decisions can't drift from actual writer liveness. If a
50
+ * client's SSE stream silently dies, `getConnectionIds()` excludes it
51
+ * immediately and `handleClientMessage` will respond with "Target not
52
+ * connected" instead of relaying into the void.
53
+ */
54
+ getClients(): Promise<Set<string>>;
55
+ /** No-op: the SSEServer's connection set is the source of truth. */
56
+ setClients(_clients: Set<string>): Promise<void>;
57
+ send(id: string, message: JsonRpcMessage): void;
58
+ }
59
+
60
+ export { SSESignalingService };
@@ -0,0 +1,27 @@
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ import { SignalingService } from "../signaling/SignalingService.js";
3
+ class SSESignalingService extends SignalingService {
4
+ constructor(sse) {
5
+ super();
6
+ this.sse = sse;
7
+ }
8
+ /**
9
+ * Derived from the live SSE connection set rather than tracked separately,
10
+ * so peer-routing decisions can't drift from actual writer liveness. If a
11
+ * client's SSE stream silently dies, `getConnectionIds()` excludes it
12
+ * immediately and `handleClientMessage` will respond with "Target not
13
+ * connected" instead of relaying into the void.
14
+ */
15
+ async getClients() {
16
+ return new Set(this.sse.getConnectionIds());
17
+ }
18
+ /** No-op: the SSEServer's connection set is the source of truth. */
19
+ async setClients(_clients) {
20
+ }
21
+ send(id, message) {
22
+ this.sse.sendToClient(id, "signal", JSON.stringify(message));
23
+ }
24
+ }
25
+ export {
26
+ SSESignalingService
27
+ };
@@ -1,5 +1,7 @@
1
1
  export { PatchesREST, PatchesRESTOptions } from './PatchesREST.js';
2
+ export { PatchesRESTSignalingTransport } from './PatchesRESTSignalingTransport.js';
2
3
  export { BufferedEvent, SSEServer, SSEServerOptions } from './SSEServer.js';
4
+ export { SSESignalingService } from './SSESignalingService.js';
3
5
  export { normalizeIds } from './utils.js';
4
6
  import 'easy-signal';
5
7
  import '../../types.js';
@@ -11,3 +13,4 @@ import '../protocol/types.js';
11
13
  import '../invite.js';
12
14
  import '../websocket/AuthorizationProvider.js';
13
15
  import '../../server/types.js';
16
+ import '../signaling/SignalingService.js';
@@ -1,6 +1,8 @@
1
1
  import "../../chunk-IZ2YBCUP.js";
2
2
  export * from "./PatchesREST.js";
3
+ export * from "./PatchesRESTSignalingTransport.js";
3
4
  export * from "./SSEServer.js";
5
+ export * from "./SSESignalingService.js";
4
6
  import { normalizeIds } from "./utils.js";
5
7
  export {
6
8
  normalizeIds
@@ -0,0 +1,74 @@
1
+ import { JsonRpcRequest, JsonRpcResponse } from '../protocol/types.js';
2
+ import 'easy-signal';
3
+ import '../../types.js';
4
+ import '../../json-patch/JSONPatch.js';
5
+ import '@dabble/delta';
6
+ import '../../json-patch/types.js';
7
+
8
+ /** Union type for all possible JSON-RPC message types */
9
+ type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse;
10
+ /**
11
+ * Service that facilitates WebRTC connection establishment by relaying signaling messages.
12
+ * Acts as a central hub for WebRTC peers to exchange connection information.
13
+ *
14
+ * Transport-agnostic: concrete subclasses implement {@link send} over WebSocket,
15
+ * SSE, or any other channel.
16
+ */
17
+ declare abstract class SignalingService {
18
+ protected clients: Set<string>;
19
+ abstract send(id: string, message: JsonRpcMessage): void | Promise<void>;
20
+ /**
21
+ * Returns the list of all connected client IDs.
22
+ * @returns Array of client IDs
23
+ */
24
+ getClients(): Promise<Set<string>>;
25
+ /**
26
+ * Sets the list of all connected client IDs.
27
+ * @param clients - Set of client IDs
28
+ */
29
+ setClients(clients: Set<string>): Promise<void>;
30
+ /**
31
+ * Registers a new client connection with the signaling service.
32
+ * Assigns a unique ID to the client and informs them of other connected peers.
33
+ *
34
+ * @param id - Optional client ID (generated if not provided)
35
+ * @returns The client's assigned ID
36
+ */
37
+ onClientConnected(id?: string): Promise<string>;
38
+ /**
39
+ * Handles a client disconnection by removing them from the registry
40
+ * and notifying all other connected clients.
41
+ *
42
+ * @param id - ID of the disconnected client
43
+ */
44
+ onClientDisconnected(id: string): Promise<void>;
45
+ /**
46
+ * Handles a signaling message from a client, relaying WebRTC session data
47
+ * between peers to facilitate connection establishment.
48
+ *
49
+ * @param fromId - ID of the client sending the message
50
+ * @param message - The JSON-RPC message or its string representation
51
+ * @returns True if the message was a valid signaling message and was handled, false otherwise
52
+ */
53
+ handleClientMessage(fromId: string, message: string | JsonRpcRequest): Promise<boolean>;
54
+ /**
55
+ * Sends a successful JSON-RPC response to a client.
56
+ *
57
+ * @protected
58
+ * @param toId - ID of the client to send the response to
59
+ * @param id - Request ID to match in the response
60
+ * @param result - Result data to include in the response
61
+ */
62
+ protected respond(toId: string, id: number, result: any): Promise<void>;
63
+ /**
64
+ * Sends an error JSON-RPC response to a client.
65
+ *
66
+ * @protected
67
+ * @param toId - ID of the client to send the error response to
68
+ * @param id - Request ID to match in the response, or undefined for notifications
69
+ * @param message - Error message to include
70
+ */
71
+ protected respondError(toId: string, id: number | undefined, message: string): Promise<void>;
72
+ }
73
+
74
+ export { type JsonRpcMessage, SignalingService };
@@ -0,0 +1,135 @@
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ import { createId } from "crypto-id";
3
+ class SignalingService {
4
+ clients = /* @__PURE__ */ new Set();
5
+ /**
6
+ * Returns the list of all connected client IDs.
7
+ * @returns Array of client IDs
8
+ */
9
+ async getClients() {
10
+ return new Set(this.clients);
11
+ }
12
+ /**
13
+ * Sets the list of all connected client IDs.
14
+ * @param clients - Set of client IDs
15
+ */
16
+ async setClients(clients) {
17
+ this.clients = clients;
18
+ }
19
+ /**
20
+ * Registers a new client connection with the signaling service.
21
+ * Assigns a unique ID to the client and informs them of other connected peers.
22
+ *
23
+ * @param id - Optional client ID (generated if not provided)
24
+ * @returns The client's assigned ID
25
+ */
26
+ async onClientConnected(id = createId(14)) {
27
+ const clients = await this.getClients();
28
+ clients.add(id);
29
+ await this.setClients(clients);
30
+ const welcome = {
31
+ jsonrpc: "2.0",
32
+ method: "peer-welcome",
33
+ params: {
34
+ id,
35
+ peers: Array.from(clients).filter((pid) => pid !== id)
36
+ }
37
+ };
38
+ await this.send(id, welcome);
39
+ return id;
40
+ }
41
+ /**
42
+ * Handles a client disconnection by removing them from the registry
43
+ * and notifying all other connected clients.
44
+ *
45
+ * @param id - ID of the disconnected client
46
+ */
47
+ async onClientDisconnected(id) {
48
+ const clients = await this.getClients();
49
+ clients.delete(id);
50
+ await this.setClients(clients);
51
+ const message = {
52
+ jsonrpc: "2.0",
53
+ method: "peer-disconnected",
54
+ params: { id }
55
+ };
56
+ await Promise.all(Array.from(clients).map((clientId) => this.send(clientId, message)));
57
+ }
58
+ /**
59
+ * Handles a signaling message from a client, relaying WebRTC session data
60
+ * between peers to facilitate connection establishment.
61
+ *
62
+ * @param fromId - ID of the client sending the message
63
+ * @param message - The JSON-RPC message or its string representation
64
+ * @returns True if the message was a valid signaling message and was handled, false otherwise
65
+ */
66
+ async handleClientMessage(fromId, message) {
67
+ let parsed;
68
+ try {
69
+ parsed = typeof message === "string" ? JSON.parse(message) : message;
70
+ } catch {
71
+ return false;
72
+ }
73
+ if (parsed.jsonrpc !== "2.0" || parsed.method !== "peer-signal" || !parsed.params?.to) return false;
74
+ const { params, id } = parsed;
75
+ const { to, data } = params;
76
+ const clients = await this.getClients();
77
+ if (!clients.has(to)) {
78
+ this.respondError(fromId, id, "Target not connected");
79
+ return true;
80
+ }
81
+ const outbound = {
82
+ jsonrpc: "2.0",
83
+ method: "signal",
84
+ params: {
85
+ from: fromId,
86
+ data
87
+ }
88
+ };
89
+ await this.send(to, outbound);
90
+ if (id !== void 0) {
91
+ await this.respond(fromId, id, "ok");
92
+ }
93
+ return true;
94
+ }
95
+ /**
96
+ * Sends a successful JSON-RPC response to a client.
97
+ *
98
+ * @protected
99
+ * @param toId - ID of the client to send the response to
100
+ * @param id - Request ID to match in the response
101
+ * @param result - Result data to include in the response
102
+ */
103
+ async respond(toId, id, result) {
104
+ const clients = await this.getClients();
105
+ if (!clients.has(toId)) return;
106
+ const response = {
107
+ jsonrpc: "2.0",
108
+ result,
109
+ id
110
+ };
111
+ await this.send(toId, response);
112
+ }
113
+ /**
114
+ * Sends an error JSON-RPC response to a client.
115
+ *
116
+ * @protected
117
+ * @param toId - ID of the client to send the error response to
118
+ * @param id - Request ID to match in the response, or undefined for notifications
119
+ * @param message - Error message to include
120
+ */
121
+ async respondError(toId, id, message) {
122
+ if (id === void 0) return;
123
+ const clients = await this.getClients();
124
+ if (!clients.has(toId)) return;
125
+ const response = {
126
+ jsonrpc: "2.0",
127
+ error: { code: -32e3, message },
128
+ id
129
+ };
130
+ await this.send(toId, response);
131
+ }
132
+ }
133
+ export {
134
+ SignalingService
135
+ };
@@ -0,0 +1,7 @@
1
+ export { JsonRpcMessage, SignalingService } from './SignalingService.js';
2
+ import '../protocol/types.js';
3
+ import 'easy-signal';
4
+ import '../../types.js';
5
+ import '../../json-patch/JSONPatch.js';
6
+ import '@dabble/delta';
7
+ import '../../json-patch/types.js';
@@ -0,0 +1 @@
1
+ export * from "./SignalingService.js";
@@ -6,8 +6,6 @@ import '../../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
7
7
  import '../../json-patch/types.js';
8
8
  import 'simple-peer';
9
- import '../websocket/WebSocketTransport.js';
10
- import '../../utils/deferred.js';
11
9
 
12
10
  /**
13
11
  * Base type for awareness states, representing arbitrary structured data
@@ -1,17 +1,17 @@
1
- import { ClientTransport, ConnectionState } from '../protocol/types.js';
1
+ import { ClientTransport, ConnectionState, SignalingTransport } from '../protocol/types.js';
2
2
  import * as easy_signal from 'easy-signal';
3
3
  import Peer from 'simple-peer';
4
- import { WebSocketTransport } from '../websocket/WebSocketTransport.js';
5
4
  import '../../types.js';
6
5
  import '../../json-patch/JSONPatch.js';
7
6
  import '@dabble/delta';
8
7
  import '../../json-patch/types.js';
9
- import '../../utils/deferred.js';
10
8
 
11
9
  /**
12
10
  * WebRTC-based transport implementation that enables direct peer-to-peer communication.
13
- * Uses a WebSocket transport as a signaling channel to establish WebRTC connections.
14
- * Once connections are established, data flows directly between peers without going through a server.
11
+ * Uses a {@link SignalingTransport} (e.g. `WebSocketTransport`,
12
+ * `PatchesRESTSignalingTransport`) as a signaling channel to establish WebRTC
13
+ * connections. Once connections are established, data flows directly between peers
14
+ * without going through a server.
15
15
  */
16
16
  declare class WebRTCTransport implements ClientTransport {
17
17
  private transport;
@@ -36,14 +36,15 @@ declare class WebRTCTransport implements ClientTransport {
36
36
  readonly onPeerDisconnect: easy_signal.Signal<(peerId: string, peer: Peer.Instance) => void>;
37
37
  /**
38
38
  * Signal that emits when the underlying signaling transport's state changes.
39
- * This is delegated directly from the WebSocketTransport.
39
+ * This is delegated directly from the signaling transport.
40
40
  */
41
41
  get onStateChange(): easy_signal.Signal<(state: ConnectionState) => void>;
42
42
  /**
43
43
  * Creates a new WebRTC transport instance.
44
- * @param transport - The WebSocket transport to use for signaling
44
+ * @param transport - A signaling-capable transport (e.g. `WebSocketTransport`
45
+ * or `PatchesRESTSignalingTransport`) used to relay WebRTC handshake messages.
45
46
  */
46
- constructor(transport: WebSocketTransport);
47
+ constructor(transport: SignalingTransport);
47
48
  /**
48
49
  * Gets the unique ID assigned to this peer by the signaling server.
49
50
  * @returns The peer ID, or undefined if not yet connected
@@ -6,7 +6,8 @@ import { rpcError } from "../protocol/utils.js";
6
6
  class WebRTCTransport {
7
7
  /**
8
8
  * Creates a new WebRTC transport instance.
9
- * @param transport - The WebSocket transport to use for signaling
9
+ * @param transport - A signaling-capable transport (e.g. `WebSocketTransport`
10
+ * or `PatchesRESTSignalingTransport`) used to relay WebRTC handshake messages.
10
11
  */
11
12
  constructor(transport) {
12
13
  this.transport = transport;
@@ -48,7 +49,7 @@ class WebRTCTransport {
48
49
  onPeerDisconnect = signal();
49
50
  /**
50
51
  * Signal that emits when the underlying signaling transport's state changes.
51
- * This is delegated directly from the WebSocketTransport.
52
+ * This is delegated directly from the signaling transport.
52
53
  */
53
54
  get onStateChange() {
54
55
  return this.transport.onStateChange;
@@ -7,5 +7,3 @@ import '../../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../../json-patch/types.js';
9
9
  import 'simple-peer';
10
- import '../websocket/WebSocketTransport.js';
11
- import '../../utils/deferred.js';
@@ -1,71 +1,7 @@
1
- import { JsonRpcRequest, JsonRpcResponse } from '../protocol/types.js';
1
+ export { JsonRpcMessage, SignalingService } from '../signaling/SignalingService.js';
2
+ import '../protocol/types.js';
2
3
  import 'easy-signal';
3
4
  import '../../types.js';
4
5
  import '../../json-patch/JSONPatch.js';
5
6
  import '@dabble/delta';
6
7
  import '../../json-patch/types.js';
7
-
8
- /** Union type for all possible JSON-RPC message types */
9
- type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse;
10
- /**
11
- * Service that facilitates WebRTC connection establishment by relaying signaling messages.
12
- * Acts as a central hub for WebRTC peers to exchange connection information.
13
- */
14
- declare abstract class SignalingService {
15
- protected clients: Set<string>;
16
- abstract send(id: string, message: JsonRpcMessage): void | Promise<void>;
17
- /**
18
- * Returns the list of all connected client IDs.
19
- * @returns Array of client IDs
20
- */
21
- getClients(): Promise<Set<string>>;
22
- /**
23
- * Sets the list of all connected client IDs.
24
- * @param clients - Set of client IDs
25
- */
26
- setClients(clients: Set<string>): Promise<void>;
27
- /**
28
- * Registers a new client connection with the signaling service.
29
- * Assigns a unique ID to the client and informs them of other connected peers.
30
- *
31
- * @param id - Optional client ID (generated if not provided)
32
- * @returns The client's assigned ID
33
- */
34
- onClientConnected(id?: string): Promise<string>;
35
- /**
36
- * Handles a client disconnection by removing them from the registry
37
- * and notifying all other connected clients.
38
- *
39
- * @param id - ID of the disconnected client
40
- */
41
- onClientDisconnected(id: string): Promise<void>;
42
- /**
43
- * Handles a signaling message from a client, relaying WebRTC session data
44
- * between peers to facilitate connection establishment.
45
- *
46
- * @param fromId - ID of the client sending the message
47
- * @param message - The JSON-RPC message or its string representation
48
- * @returns True if the message was a valid signaling message and was handled, false otherwise
49
- */
50
- handleClientMessage(fromId: string, message: string | JsonRpcRequest): Promise<boolean>;
51
- /**
52
- * Sends a successful JSON-RPC response to a client.
53
- *
54
- * @protected
55
- * @param toId - ID of the client to send the response to
56
- * @param id - Request ID to match in the response
57
- * @param result - Result data to include in the response
58
- */
59
- protected respond(toId: string, id: number, result: any): Promise<void>;
60
- /**
61
- * Sends an error JSON-RPC response to a client.
62
- *
63
- * @protected
64
- * @param toId - ID of the client to send the error response to
65
- * @param id - Request ID to match in the response, or undefined for notifications
66
- * @param message - Error message to include
67
- */
68
- protected respondError(toId: string, id: number | undefined, message: string): Promise<void>;
69
- }
70
-
71
- export { type JsonRpcMessage, SignalingService };
@@ -1,135 +1 @@
1
- import "../../chunk-IZ2YBCUP.js";
2
- import { createId } from "crypto-id";
3
- class SignalingService {
4
- clients = /* @__PURE__ */ new Set();
5
- /**
6
- * Returns the list of all connected client IDs.
7
- * @returns Array of client IDs
8
- */
9
- async getClients() {
10
- return new Set(this.clients);
11
- }
12
- /**
13
- * Sets the list of all connected client IDs.
14
- * @param clients - Set of client IDs
15
- */
16
- async setClients(clients) {
17
- this.clients = clients;
18
- }
19
- /**
20
- * Registers a new client connection with the signaling service.
21
- * Assigns a unique ID to the client and informs them of other connected peers.
22
- *
23
- * @param id - Optional client ID (generated if not provided)
24
- * @returns The client's assigned ID
25
- */
26
- async onClientConnected(id = createId(14)) {
27
- const clients = await this.getClients();
28
- clients.add(id);
29
- await this.setClients(clients);
30
- const welcome = {
31
- jsonrpc: "2.0",
32
- method: "peer-welcome",
33
- params: {
34
- id,
35
- peers: Array.from(this.clients).filter((pid) => pid !== id)
36
- }
37
- };
38
- this.send(id, welcome);
39
- return id;
40
- }
41
- /**
42
- * Handles a client disconnection by removing them from the registry
43
- * and notifying all other connected clients.
44
- *
45
- * @param id - ID of the disconnected client
46
- */
47
- async onClientDisconnected(id) {
48
- const clients = await this.getClients();
49
- clients.delete(id);
50
- await this.setClients(clients);
51
- const message = {
52
- jsonrpc: "2.0",
53
- method: "peer-disconnected",
54
- params: { id }
55
- };
56
- await Promise.all(Array.from(clients).map((clientId) => this.send(clientId, message)));
57
- }
58
- /**
59
- * Handles a signaling message from a client, relaying WebRTC session data
60
- * between peers to facilitate connection establishment.
61
- *
62
- * @param fromId - ID of the client sending the message
63
- * @param message - The JSON-RPC message or its string representation
64
- * @returns True if the message was a valid signaling message and was handled, false otherwise
65
- */
66
- async handleClientMessage(fromId, message) {
67
- let parsed;
68
- try {
69
- parsed = typeof message === "string" ? JSON.parse(message) : message;
70
- } catch {
71
- return false;
72
- }
73
- if (parsed.jsonrpc !== "2.0" || parsed.method !== "peer-signal" || !parsed.params?.to) return false;
74
- const { params, id } = parsed;
75
- const { to, data } = params;
76
- const clients = await this.getClients();
77
- if (!clients.has(to)) {
78
- this.respondError(fromId, id, "Target not connected");
79
- return true;
80
- }
81
- const outbound = {
82
- jsonrpc: "2.0",
83
- method: "signal",
84
- params: {
85
- from: fromId,
86
- data
87
- }
88
- };
89
- await this.send(to, outbound);
90
- if (id !== void 0) {
91
- await this.respond(fromId, id, "ok");
92
- }
93
- return true;
94
- }
95
- /**
96
- * Sends a successful JSON-RPC response to a client.
97
- *
98
- * @protected
99
- * @param toId - ID of the client to send the response to
100
- * @param id - Request ID to match in the response
101
- * @param result - Result data to include in the response
102
- */
103
- async respond(toId, id, result) {
104
- const clients = await this.getClients();
105
- if (!clients.has(toId)) return;
106
- const response = {
107
- jsonrpc: "2.0",
108
- result,
109
- id
110
- };
111
- await this.send(toId, response);
112
- }
113
- /**
114
- * Sends an error JSON-RPC response to a client.
115
- *
116
- * @protected
117
- * @param toId - ID of the client to send the error response to
118
- * @param id - Request ID to match in the response, or undefined for notifications
119
- * @param message - Error message to include
120
- */
121
- async respondError(toId, id, message) {
122
- if (id === void 0) return;
123
- const clients = await this.getClients();
124
- if (!clients.has(toId)) return;
125
- const response = {
126
- jsonrpc: "2.0",
127
- error: { code: -32e3, message },
128
- id
129
- };
130
- await this.send(toId, response);
131
- }
132
- }
133
- export {
134
- SignalingService
135
- };
1
+ export * from "../signaling/SignalingService.js";
@@ -1,5 +1,5 @@
1
1
  import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
2
- import { EditableBranchMetadata, CreateBranchMetadata, Branch } from '../types.js';
2
+ import { EditableBranchMetadata, Branch, CreateBranchMetadata } from '../types.js';
3
3
  import 'easy-signal';
4
4
  import '../net/websocket/AuthorizationProvider.js';
5
5
  import './types.js';
@@ -1,5 +1,5 @@
1
1
  import { Patches, OpenDocOptions } from '../client/Patches.js';
2
- import { a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
2
+ import { P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
3
3
  import 'easy-signal';
4
4
  import '../json-patch/types.js';
5
5
  import '../types.js';
@@ -7,7 +7,7 @@ import '../types.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../client/ClientAlgorithm.js';
10
- import '../BaseDoc-CXHXcW18.js';
10
+ import '../BaseDoc-rCKMFV6B.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../algorithms/ot/shared/changeBatching.js';
13
13
  import '../client/BranchClientStore.js';
@@ -6,5 +6,5 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-CXHXcW18.js';
9
+ import '../BaseDoc-rCKMFV6B.js';
10
10
  import '../client/PatchesStore.js';
@@ -11,7 +11,7 @@ import '../types.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../client/ClientAlgorithm.js';
14
- import '../BaseDoc-CXHXcW18.js';
14
+ import '../BaseDoc-rCKMFV6B.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../algorithms/ot/shared/changeBatching.js';
@@ -1,6 +1,6 @@
1
1
  import { Accessor } from 'solid-js';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
- import { a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
3
+ import { P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
4
4
  import { ChangeMutator } from '../types.js';
5
5
  import 'easy-signal';
6
6
  import '../json-patch/types.js';
@@ -1,6 +1,6 @@
1
- import { ShallowRef, Ref, MaybeRefOrGetter, MaybeRef } from 'vue';
1
+ import { ShallowRef, Ref, MaybeRef, MaybeRefOrGetter } from 'vue';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
- import { a as PatchesDoc } from '../BaseDoc-CXHXcW18.js';
3
+ import { P as PatchesDoc } from '../BaseDoc-rCKMFV6B.js';
4
4
  import { ChangeMutator } from '../types.js';
5
5
  import 'easy-signal';
6
6
  import '../json-patch/types.js';
@@ -6,5 +6,5 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-CXHXcW18.js';
9
+ import '../BaseDoc-rCKMFV6B.js';
10
10
  import '../client/PatchesStore.js';
@@ -11,7 +11,7 @@ import '../types.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../client/ClientAlgorithm.js';
14
- import '../BaseDoc-CXHXcW18.js';
14
+ import '../BaseDoc-rCKMFV6B.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../algorithms/ot/shared/changeBatching.js';
@@ -1,4 +1,4 @@
1
- import { Ref, ShallowRef } from 'vue';
1
+ import { ShallowRef, Ref } from 'vue';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
3
  import 'easy-signal';
4
4
  import '../json-patch/types.js';
@@ -6,7 +6,7 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-CXHXcW18.js';
9
+ import '../BaseDoc-rCKMFV6B.js';
10
10
  import '../client/PatchesStore.js';
11
11
 
12
12
  /**
@@ -7,7 +7,7 @@ import '../types.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../client/ClientAlgorithm.js';
10
- import '../BaseDoc-CXHXcW18.js';
10
+ import '../BaseDoc-rCKMFV6B.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../algorithms/ot/shared/changeBatching.js';
13
13
  import '../client/BranchClientStore.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
5
5
  "author": "Jacob Wright <jacwright@gmail.com>",
6
6
  "bugs": {