@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.
- package/README.md +1 -1
- package/dist/{BaseDoc-CXHXcW18.d.ts → BaseDoc-rCKMFV6B.d.ts} +1 -1
- package/dist/algorithms/ot/server/createVersion.d.ts +1 -1
- package/dist/client/BaseDoc.d.ts +1 -1
- package/dist/client/ClientAlgorithm.d.ts +1 -1
- package/dist/client/LWWAlgorithm.d.ts +1 -1
- package/dist/client/LWWDoc.d.ts +1 -1
- package/dist/client/OTAlgorithm.d.ts +1 -1
- package/dist/client/OTDoc.d.ts +1 -1
- package/dist/client/Patches.d.ts +1 -1
- package/dist/client/PatchesBranchClient.d.ts +1 -1
- package/dist/client/PatchesDoc.d.ts +1 -1
- package/dist/client/factories.d.ts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/net/PatchesSync.d.ts +1 -1
- package/dist/net/index.d.ts +5 -3
- package/dist/net/index.js +1 -1
- package/dist/net/protocol/types.d.ts +16 -2
- package/dist/net/rest/PatchesREST.d.ts +23 -0
- package/dist/net/rest/PatchesREST.js +46 -0
- package/dist/net/rest/PatchesRESTSignalingTransport.d.ts +42 -0
- package/dist/net/rest/PatchesRESTSignalingTransport.js +34 -0
- package/dist/net/rest/SSEServer.d.ts +15 -0
- package/dist/net/rest/SSEServer.js +25 -0
- package/dist/net/rest/SSESignalingService.d.ts +60 -0
- package/dist/net/rest/SSESignalingService.js +27 -0
- package/dist/net/rest/index.d.ts +3 -0
- package/dist/net/rest/index.js +2 -0
- package/dist/net/signaling/SignalingService.d.ts +74 -0
- package/dist/net/signaling/SignalingService.js +135 -0
- package/dist/net/signaling/index.d.ts +7 -0
- package/dist/net/signaling/index.js +1 -0
- package/dist/net/webrtc/WebRTCAwareness.d.ts +0 -2
- package/dist/net/webrtc/WebRTCTransport.d.ts +9 -8
- package/dist/net/webrtc/WebRTCTransport.js +3 -2
- package/dist/net/webrtc/index.d.ts +0 -2
- package/dist/net/websocket/SignalingService.d.ts +2 -66
- package/dist/net/websocket/SignalingService.js +1 -135
- package/dist/server/branchUtils.d.ts +1 -1
- package/dist/shared/doc-manager.d.ts +1 -1
- package/dist/solid/context.d.ts +1 -1
- package/dist/solid/doc-manager.d.ts +1 -1
- package/dist/solid/index.d.ts +1 -1
- package/dist/solid/primitives.d.ts +1 -1
- package/dist/vue/composables.d.ts +2 -2
- package/dist/vue/doc-manager.d.ts +1 -1
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/managed-docs.d.ts +2 -2
- package/dist/vue/provider.d.ts +1 -1
- 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.
|
|
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
|
|
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,
|
|
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';
|
package/dist/client/BaseDoc.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSONPatchOp } from '../json-patch/types.js';
|
|
2
2
|
import { PatchesSnapshot, Change } from '../types.js';
|
|
3
|
-
import {
|
|
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 {
|
|
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';
|
package/dist/client/LWWDoc.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
|
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';
|
package/dist/client/OTDoc.d.ts
CHANGED
package/dist/client/Patches.d.ts
CHANGED
|
@@ -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 {
|
|
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-
|
|
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,
|
|
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 {
|
|
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';
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BaseDoc, O as OTDoc,
|
|
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,
|
|
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-
|
|
16
|
+
import '../BaseDoc-rCKMFV6B.js';
|
|
17
17
|
import '../utils/deferred.js';
|
|
18
18
|
|
|
19
19
|
interface PatchesSyncState {
|
package/dist/net/index.d.ts
CHANGED
|
@@ -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 './
|
|
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-
|
|
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 "./
|
|
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
|
+
};
|
package/dist/net/rest/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/net/rest/index.js
CHANGED
|
@@ -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 @@
|
|
|
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
|
|
14
|
-
*
|
|
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
|
|
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 -
|
|
44
|
+
* @param transport - A signaling-capable transport (e.g. `WebSocketTransport`
|
|
45
|
+
* or `PatchesRESTSignalingTransport`) used to relay WebRTC handshake messages.
|
|
45
46
|
*/
|
|
46
|
-
constructor(transport:
|
|
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 -
|
|
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
|
|
52
|
+
* This is delegated directly from the signaling transport.
|
|
52
53
|
*/
|
|
53
54
|
get onStateChange() {
|
|
54
55
|
return this.transport.onStateChange;
|
|
@@ -1,71 +1,7 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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,
|
|
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';
|
package/dist/solid/context.d.ts
CHANGED
|
@@ -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-
|
|
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/dist/solid/index.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
|
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,
|
|
1
|
+
import { ShallowRef, Ref, MaybeRef, MaybeRefOrGetter } from 'vue';
|
|
2
2
|
import { OpenDocOptions } from '../client/Patches.js';
|
|
3
|
-
import {
|
|
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';
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
|
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-
|
|
9
|
+
import '../BaseDoc-rCKMFV6B.js';
|
|
10
10
|
import '../client/PatchesStore.js';
|
|
11
11
|
|
|
12
12
|
/**
|
package/dist/vue/provider.d.ts
CHANGED
|
@@ -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-
|
|
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.
|
|
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": {
|