@dabble/patches 0.1.1

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 (120) hide show
  1. package/README.md +632 -0
  2. package/dist/client/PatchDoc.d.ts +85 -0
  3. package/dist/client/PatchDoc.js +299 -0
  4. package/dist/client/index.d.ts +2 -0
  5. package/dist/client/index.js +1 -0
  6. package/dist/event-signal.d.ts +31 -0
  7. package/dist/event-signal.js +40 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +1 -0
  10. package/dist/json-patch/JSONPatch.d.ts +126 -0
  11. package/dist/json-patch/JSONPatch.js +221 -0
  12. package/dist/json-patch/applyPatch.d.ts +11 -0
  13. package/dist/json-patch/applyPatch.js +37 -0
  14. package/dist/json-patch/composePatch.d.ts +2 -0
  15. package/dist/json-patch/composePatch.js +38 -0
  16. package/dist/json-patch/createJSONPatch.d.ts +35 -0
  17. package/dist/json-patch/createJSONPatch.js +41 -0
  18. package/dist/json-patch/index.d.ts +9 -0
  19. package/dist/json-patch/index.js +8 -0
  20. package/dist/json-patch/invertPatch.d.ts +2 -0
  21. package/dist/json-patch/invertPatch.js +31 -0
  22. package/dist/json-patch/ops/add.d.ts +2 -0
  23. package/dist/json-patch/ops/add.js +52 -0
  24. package/dist/json-patch/ops/bitmask.d.ts +14 -0
  25. package/dist/json-patch/ops/bitmask.js +48 -0
  26. package/dist/json-patch/ops/copy.d.ts +2 -0
  27. package/dist/json-patch/ops/copy.js +34 -0
  28. package/dist/json-patch/ops/increment.d.ts +5 -0
  29. package/dist/json-patch/ops/increment.js +21 -0
  30. package/dist/json-patch/ops/index.d.ts +22 -0
  31. package/dist/json-patch/ops/index.js +25 -0
  32. package/dist/json-patch/ops/move.d.ts +2 -0
  33. package/dist/json-patch/ops/move.js +211 -0
  34. package/dist/json-patch/ops/remove.d.ts +2 -0
  35. package/dist/json-patch/ops/remove.js +31 -0
  36. package/dist/json-patch/ops/replace.d.ts +2 -0
  37. package/dist/json-patch/ops/replace.js +44 -0
  38. package/dist/json-patch/ops/test.d.ts +2 -0
  39. package/dist/json-patch/ops/test.js +22 -0
  40. package/dist/json-patch/ops/text.d.ts +2 -0
  41. package/dist/json-patch/ops/text.js +57 -0
  42. package/dist/json-patch/patchProxy.d.ts +41 -0
  43. package/dist/json-patch/patchProxy.js +125 -0
  44. package/dist/json-patch/state.d.ts +2 -0
  45. package/dist/json-patch/state.js +8 -0
  46. package/dist/json-patch/transformPatch.d.ts +19 -0
  47. package/dist/json-patch/transformPatch.js +37 -0
  48. package/dist/json-patch/types.d.ts +52 -0
  49. package/dist/json-patch/types.js +1 -0
  50. package/dist/json-patch/utils/deepEqual.d.ts +1 -0
  51. package/dist/json-patch/utils/deepEqual.js +33 -0
  52. package/dist/json-patch/utils/exit.d.ts +2 -0
  53. package/dist/json-patch/utils/exit.js +4 -0
  54. package/dist/json-patch/utils/get.d.ts +2 -0
  55. package/dist/json-patch/utils/get.js +6 -0
  56. package/dist/json-patch/utils/getOpData.d.ts +2 -0
  57. package/dist/json-patch/utils/getOpData.js +10 -0
  58. package/dist/json-patch/utils/getType.d.ts +3 -0
  59. package/dist/json-patch/utils/getType.js +6 -0
  60. package/dist/json-patch/utils/index.d.ts +14 -0
  61. package/dist/json-patch/utils/index.js +14 -0
  62. package/dist/json-patch/utils/log.d.ts +2 -0
  63. package/dist/json-patch/utils/log.js +7 -0
  64. package/dist/json-patch/utils/ops.d.ts +14 -0
  65. package/dist/json-patch/utils/ops.js +103 -0
  66. package/dist/json-patch/utils/paths.d.ts +9 -0
  67. package/dist/json-patch/utils/paths.js +53 -0
  68. package/dist/json-patch/utils/pluck.d.ts +5 -0
  69. package/dist/json-patch/utils/pluck.js +30 -0
  70. package/dist/json-patch/utils/shallowCopy.d.ts +1 -0
  71. package/dist/json-patch/utils/shallowCopy.js +20 -0
  72. package/dist/json-patch/utils/softWrites.d.ts +7 -0
  73. package/dist/json-patch/utils/softWrites.js +18 -0
  74. package/dist/json-patch/utils/toArrayIndex.d.ts +1 -0
  75. package/dist/json-patch/utils/toArrayIndex.js +12 -0
  76. package/dist/json-patch/utils/toKeys.d.ts +1 -0
  77. package/dist/json-patch/utils/toKeys.js +15 -0
  78. package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -0
  79. package/dist/json-patch/utils/updateArrayIndexes.js +38 -0
  80. package/dist/json-patch/utils/updateArrayPath.d.ts +5 -0
  81. package/dist/json-patch/utils/updateArrayPath.js +45 -0
  82. package/dist/net/AbstractTransport.d.ts +47 -0
  83. package/dist/net/AbstractTransport.js +37 -0
  84. package/dist/net/PatchesOfflineFirst.d.ts +3 -0
  85. package/dist/net/PatchesOfflineFirst.js +3 -0
  86. package/dist/net/PatchesRealtime.d.ts +90 -0
  87. package/dist/net/PatchesRealtime.js +257 -0
  88. package/dist/net/index.d.ts +9 -0
  89. package/dist/net/index.js +8 -0
  90. package/dist/net/protocol/JSONRPCClient.d.ts +55 -0
  91. package/dist/net/protocol/JSONRPCClient.js +106 -0
  92. package/dist/net/protocol/types.d.ts +142 -0
  93. package/dist/net/protocol/types.js +1 -0
  94. package/dist/net/webrtc/WebRTCAwareness.d.ts +81 -0
  95. package/dist/net/webrtc/WebRTCAwareness.js +119 -0
  96. package/dist/net/webrtc/WebRTCTransport.d.ts +80 -0
  97. package/dist/net/webrtc/WebRTCTransport.js +157 -0
  98. package/dist/net/websocket/PatchesWebSocket.d.ts +107 -0
  99. package/dist/net/websocket/PatchesWebSocket.js +144 -0
  100. package/dist/net/websocket/SignalingService.d.ts +91 -0
  101. package/dist/net/websocket/SignalingService.js +140 -0
  102. package/dist/net/websocket/WebSocketTransport.d.ts +47 -0
  103. package/dist/net/websocket/WebSocketTransport.js +138 -0
  104. package/dist/persist/IndexedDBStore.d.ts +72 -0
  105. package/dist/persist/IndexedDBStore.js +283 -0
  106. package/dist/persist/index.d.ts +2 -0
  107. package/dist/persist/index.js +1 -0
  108. package/dist/server/BranchManager.d.ts +40 -0
  109. package/dist/server/BranchManager.js +138 -0
  110. package/dist/server/HistoryManager.d.ts +63 -0
  111. package/dist/server/HistoryManager.js +92 -0
  112. package/dist/server/PatchServer.d.ts +129 -0
  113. package/dist/server/PatchServer.js +358 -0
  114. package/dist/server/index.d.ts +4 -0
  115. package/dist/server/index.js +3 -0
  116. package/dist/types.d.ts +158 -0
  117. package/dist/types.js +1 -0
  118. package/dist/utils.d.ts +36 -0
  119. package/dist/utils.js +83 -0
  120. package/package.json +78 -0
@@ -0,0 +1,142 @@
1
+ import type { Change, PatchSnapshot, PatchState, VersionMetadata } from '../../types';
2
+ /**
3
+ * Represents the possible states of a network transport connection.
4
+ * - 'connecting': Connection is being established
5
+ * - 'connected': Connection is active and ready for communication
6
+ * - 'disconnected': Connection is not active
7
+ * - 'error': Connection encountered an error
8
+ */
9
+ export type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error';
10
+ /**
11
+ * Interface defining the core functionality of a transport layer.
12
+ * Transport implementations provide a communication channel between client and server,
13
+ * abstracting the underlying protocol details (WebSocket, WebRTC, etc.).
14
+ */
15
+ export interface Transport {
16
+ /**
17
+ * Establishes the connection for this transport.
18
+ * @returns A promise that resolves when the connection is established
19
+ */
20
+ connect(): Promise<void>;
21
+ /**
22
+ * Terminates the connection for this transport.
23
+ */
24
+ disconnect(): void;
25
+ /**
26
+ * Sends data through this transport.
27
+ * @param data - The string data to send
28
+ */
29
+ send(data: string): void;
30
+ /**
31
+ * Registers a handler for incoming messages.
32
+ * @param handler - Function that will be called when a message is received
33
+ */
34
+ onMessage(handler: (data: string) => void): void;
35
+ /**
36
+ * Registers a handler for connection state changes.
37
+ * @param handler - Function that will be called when the connection state changes
38
+ */
39
+ onStateChange(handler: (state: ConnectionState) => void): void;
40
+ }
41
+ /**
42
+ * Represents a JSON-RPC 2.0 request object.
43
+ */
44
+ export interface Request {
45
+ /** JSON-RPC protocol version, always "2.0" */
46
+ jsonrpc: '2.0';
47
+ /** Request identifier, used to match responses to requests */
48
+ id?: number;
49
+ /** Name of the remote procedure to call */
50
+ method: string;
51
+ /** Parameters to pass to the remote procedure */
52
+ params?: any;
53
+ }
54
+ /**
55
+ * Represents a JSON-RPC 2.0 response object.
56
+ */
57
+ export interface Response {
58
+ /** JSON-RPC protocol version, always "2.0" */
59
+ jsonrpc: '2.0';
60
+ /** Response identifier, matches the id of the corresponding request */
61
+ id: number;
62
+ /** Result of the successful procedure call */
63
+ result?: any;
64
+ /** Error information if the procedure call failed */
65
+ error?: {
66
+ /** Numeric error code */
67
+ code: number;
68
+ /** Short error description */
69
+ message: string;
70
+ /** Additional error details (optional) */
71
+ data?: any;
72
+ };
73
+ }
74
+ /**
75
+ * Represents a JSON-RPC 2.0 notification object.
76
+ * Notifications are one-way messages that don't expect a response.
77
+ */
78
+ export interface Notification {
79
+ /** JSON-RPC protocol version, always "2.0" */
80
+ jsonrpc: '2.0';
81
+ /** Name of the notification method */
82
+ method: string;
83
+ /** Parameters associated with the notification */
84
+ params?: any;
85
+ }
86
+ /** Union type for all possible JSON-RPC message types */
87
+ export type Message = Request | Response | Notification;
88
+ export interface ListOptions {
89
+ startAt?: string;
90
+ startAfter?: string;
91
+ endAt?: string;
92
+ endBefore?: string;
93
+ prefix?: string;
94
+ limit?: number;
95
+ reverse?: boolean;
96
+ }
97
+ export interface PatchesAPI {
98
+ /**
99
+ * Subscribes the connected client to one or more documents.
100
+ * @param ids Document ID(s) to subscribe to.
101
+ * @returns A list of document IDs the client is now successfully subscribed to.
102
+ */
103
+ subscribe(ids: string | string[]): Promise<string[]>;
104
+ /**
105
+ * Unsubscribes the connected client from one or more documents.
106
+ * @param ids Document ID(s) to unsubscribe from.
107
+ */
108
+ unsubscribe(ids: string | string[]): Promise<void>;
109
+ /** Get the latest version of a document and changes since the last version. */
110
+ getDoc(docId: string, atRev?: number): Promise<PatchSnapshot>;
111
+ /** Get changes that occurred after a specific revision. */
112
+ getChangesSince(docId: string, rev: number): Promise<Change[]>;
113
+ /** Apply a set of changes from the client to a document. Returns the committed changes. */
114
+ commitChanges(docId: string, changes: Change[]): Promise<Change[]>;
115
+ /** Delete a document. */
116
+ deleteDoc(docId: string): Promise<void>;
117
+ /** Create a new named version snapshot of a document's current state. */
118
+ createVersion(docId: string, name: string): Promise<string>;
119
+ /** List metadata for saved versions of a document. */
120
+ listVersions(docId: string, options: ListOptions): Promise<VersionMetadata[]>;
121
+ /** Get the state snapshot for a specific version ID. */
122
+ getVersionState(docId: string, versionId: string): Promise<PatchState>;
123
+ /** Get the original Change objects associated with a specific version ID. */
124
+ getVersionChanges(docId: string, versionId: string): Promise<Change[]>;
125
+ /** Update the name of a specific version. */
126
+ updateVersion(docId: string, versionId: string, name: string): Promise<void>;
127
+ }
128
+ export interface PatchesNotificationParams {
129
+ docId: string;
130
+ changes: Change[];
131
+ }
132
+ export interface AwarenessUpdateNotificationParams {
133
+ docId: string;
134
+ /** The awareness state from a specific client (server should add sender info) */
135
+ state: any;
136
+ /** Server should add the client ID of the sender */
137
+ clientId?: string;
138
+ }
139
+ export interface SignalNotificationParams {
140
+ fromClientId: string;
141
+ data: any;
142
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ import type { WebRTCTransport } from './WebRTCTransport.js';
2
+ /**
3
+ * Base type for awareness states, representing arbitrary structured data
4
+ * that will be shared between peers.
5
+ */
6
+ type AwarenessState = Record<string, any>;
7
+ /**
8
+ * Implements the awareness protocol over WebRTC to synchronize peer states.
9
+ * Awareness allows peers to share real-time information about their current state,
10
+ * such as cursor position, selection, user info, or any other application-specific data.
11
+ *
12
+ * @template T - The type of awareness state to be shared between peers
13
+ */
14
+ export declare class WebRTCAwareness<T extends AwarenessState = AwarenessState> {
15
+ private transport;
16
+ private _states;
17
+ private _localState;
18
+ /**
19
+ * Signal that emits when the awareness state is updated.
20
+ * Subscribers receive the complete new awareness state array.
21
+ */
22
+ readonly onUpdate: import("../../event-signal.js").Signal<(states: T[]) => void>;
23
+ /**
24
+ * The peer ID of this client, obtained from the WebRTC transport.
25
+ */
26
+ private myId;
27
+ /**
28
+ * Creates a new WebRTC awareness instance.
29
+ * @param transport - The WebRTC transport to use for awareness communication
30
+ */
31
+ constructor(transport: WebRTCTransport);
32
+ /**
33
+ * Connects to the WebRTC network to start synchronizing awareness states.
34
+ * @returns A promise that resolves when the connection is established
35
+ */
36
+ connect(): Promise<void>;
37
+ /**
38
+ * Disconnects from the WebRTC network and stops awareness synchronization.
39
+ */
40
+ disconnect(): void;
41
+ /**
42
+ * Gets the current combined awareness state of all peers.
43
+ * @returns An array of awareness states from all connected peers
44
+ */
45
+ get states(): T[];
46
+ /**
47
+ * Sets the combined awareness state and emits an update event.
48
+ * This method is protected and typically used internally.
49
+ * @param state - The new combined awareness state array
50
+ */
51
+ protected set states(state: T[]);
52
+ /**
53
+ * Gets this peer's local awareness state.
54
+ * @returns The current local awareness state
55
+ */
56
+ get localState(): T;
57
+ /**
58
+ * Sets this peer's local awareness state and broadcasts it to all connected peers.
59
+ * @param value - The new local awareness state to set and broadcast
60
+ */
61
+ set localState(value: T);
62
+ /**
63
+ * Handles a new peer connection by sending the local state to the new peer.
64
+ * @private
65
+ * @param peerId - ID of the newly connected peer
66
+ */
67
+ private _addPeer;
68
+ /**
69
+ * Handles a peer disconnection by removing their state from the awareness state.
70
+ * @private
71
+ * @param peerId - ID of the disconnected peer
72
+ */
73
+ private _removePeer;
74
+ /**
75
+ * Processes incoming awareness data from other peers.
76
+ * @private
77
+ * @param data - The serialized awareness state data received from a peer
78
+ */
79
+ private _receiveData;
80
+ }
81
+ export {};
@@ -0,0 +1,119 @@
1
+ import { signal } from '../../event-signal.js';
2
+ /**
3
+ * Implements the awareness protocol over WebRTC to synchronize peer states.
4
+ * Awareness allows peers to share real-time information about their current state,
5
+ * such as cursor position, selection, user info, or any other application-specific data.
6
+ *
7
+ * @template T - The type of awareness state to be shared between peers
8
+ */
9
+ export class WebRTCAwareness {
10
+ /**
11
+ * Creates a new WebRTC awareness instance.
12
+ * @param transport - The WebRTC transport to use for awareness communication
13
+ */
14
+ constructor(transport) {
15
+ this.transport = transport;
16
+ this._states = [];
17
+ this._localState = {};
18
+ /**
19
+ * Signal that emits when the awareness state is updated.
20
+ * Subscribers receive the complete new awareness state array.
21
+ */
22
+ this.onUpdate = signal();
23
+ this.transport.onPeerConnect(this._addPeer.bind(this));
24
+ this.transport.onPeerDisconnect(this._removePeer.bind(this));
25
+ this.transport.onMessage(this._receiveData.bind(this));
26
+ }
27
+ /**
28
+ * Connects to the WebRTC network to start synchronizing awareness states.
29
+ * @returns A promise that resolves when the connection is established
30
+ */
31
+ async connect() {
32
+ await this.transport.connect();
33
+ // Get the peer ID from the transport after connection
34
+ this.myId = this.transport.id;
35
+ }
36
+ /**
37
+ * Disconnects from the WebRTC network and stops awareness synchronization.
38
+ */
39
+ disconnect() {
40
+ this.transport.disconnect();
41
+ }
42
+ /**
43
+ * Gets the current combined awareness state of all peers.
44
+ * @returns An array of awareness states from all connected peers
45
+ */
46
+ get states() {
47
+ return this._states;
48
+ }
49
+ /**
50
+ * Sets the combined awareness state and emits an update event.
51
+ * This method is protected and typically used internally.
52
+ * @param state - The new combined awareness state array
53
+ */
54
+ set states(state) {
55
+ this._states = state;
56
+ this.onUpdate.emit(this._states);
57
+ }
58
+ /**
59
+ * Gets this peer's local awareness state.
60
+ * @returns The current local awareness state
61
+ */
62
+ get localState() {
63
+ return this._localState;
64
+ }
65
+ /**
66
+ * Sets this peer's local awareness state and broadcasts it to all connected peers.
67
+ * @param value - The new local awareness state to set and broadcast
68
+ */
69
+ set localState(value) {
70
+ value.id = this.myId;
71
+ this._localState = value;
72
+ this.transport.send(JSON.stringify(value));
73
+ }
74
+ /**
75
+ * Handles a new peer connection by sending the local state to the new peer.
76
+ * @private
77
+ * @param peerId - ID of the newly connected peer
78
+ */
79
+ _addPeer(peerId) {
80
+ // If localState has been set (id will exist once set)
81
+ if (this._localState.id) {
82
+ this.transport.send(JSON.stringify(this._localState), peerId);
83
+ }
84
+ }
85
+ /**
86
+ * Handles a peer disconnection by removing their state from the awareness state.
87
+ * @private
88
+ * @param peerId - ID of the disconnected peer
89
+ */
90
+ _removePeer(peerId) {
91
+ this.states = this.states.filter(s => s.id !== peerId);
92
+ }
93
+ /**
94
+ * Processes incoming awareness data from other peers.
95
+ * @private
96
+ * @param data - The serialized awareness state data received from a peer
97
+ */
98
+ _receiveData(data) {
99
+ let peerState;
100
+ try {
101
+ peerState = JSON.parse(data);
102
+ if (!peerState?.id)
103
+ return;
104
+ }
105
+ catch (err) {
106
+ console.error('Invalid peer data:', err);
107
+ return;
108
+ }
109
+ const update = [...this.states];
110
+ const existingIndex = update.findIndex(s => s.id === peerState.id);
111
+ if (existingIndex !== -1) {
112
+ update.splice(existingIndex, 1, { ...update[existingIndex], ...peerState });
113
+ }
114
+ else {
115
+ update.push(peerState);
116
+ }
117
+ this.states = update;
118
+ }
119
+ }
@@ -0,0 +1,80 @@
1
+ import Peer from 'simple-peer';
2
+ import { AbstractTransport } from '../AbstractTransport.js';
3
+ import type { WebSocketTransport } from '../websocket/WebSocketTransport.js';
4
+ /**
5
+ * WebRTC-based transport implementation that enables direct peer-to-peer communication.
6
+ * Uses a WebSocket transport as a signaling channel to establish WebRTC connections.
7
+ * Once connections are established, data flows directly between peers without going through a server.
8
+ */
9
+ export declare class WebRTCTransport extends AbstractTransport {
10
+ private transport;
11
+ private rpc;
12
+ private peers;
13
+ private _id;
14
+ private subscriptions;
15
+ /**
16
+ * Signal that emits when a message is received from a peer.
17
+ * Provides the message data, peer ID, and peer instance.
18
+ */
19
+ readonly onMessage: import("../../event-signal.js").Signal<(data: string, peerId: string, peer: Peer.Instance) => void>;
20
+ /**
21
+ * Signal that emits when a new peer connection is established.
22
+ * Provides the peer ID and peer instance.
23
+ */
24
+ readonly onPeerConnect: import("../../event-signal.js").Signal<(peerId: string, peer: Peer.Instance) => void>;
25
+ /**
26
+ * Signal that emits when a peer disconnects.
27
+ * Provides the peer ID and peer instance.
28
+ */
29
+ readonly onPeerDisconnect: import("../../event-signal.js").Signal<(peerId: string, peer: Peer.Instance) => void>;
30
+ /**
31
+ * Signal that emits when the underlying signaling transport's state changes.
32
+ * This is delegated directly from the WebSocketTransport.
33
+ */
34
+ readonly onStateChange: import("../../event-signal.js").Signal<(state: import("../index.js").ConnectionState) => void>;
35
+ /**
36
+ * Creates a new WebRTC transport instance.
37
+ * @param transport - The WebSocket transport to use for signaling
38
+ */
39
+ constructor(transport: WebSocketTransport);
40
+ /**
41
+ * Gets the unique ID assigned to this peer by the signaling server.
42
+ * @returns The peer ID, or undefined if not yet connected
43
+ */
44
+ get id(): string | undefined;
45
+ /**
46
+ * Gets the current connection state of the underlying signaling transport.
47
+ * @returns The current connection state
48
+ */
49
+ get state(): import("../index.js").ConnectionState;
50
+ /**
51
+ * Establishes the connection by connecting to the signaling server.
52
+ * Peer connections will be established automatically after this.
53
+ * @returns A promise that resolves when connected to the signaling server
54
+ */
55
+ connect(): Promise<void>;
56
+ /**
57
+ * Terminates the connection for this transport.
58
+ * Must be implemented by concrete subclasses.
59
+ */
60
+ disconnect(): void;
61
+ /**
62
+ * Sends data to one or all connected peers.
63
+ * @param data - The string data to send
64
+ * @param peerId - Optional ID of specific peer to send to; if omitted, sends to all peers
65
+ */
66
+ send(data: string, peerId?: string): void;
67
+ /**
68
+ * Establishes a WebRTC connection to a peer.
69
+ * @private
70
+ * @param peerId - ID of the peer to connect to
71
+ * @param initiator - Whether this peer is initiating the connection
72
+ */
73
+ private _connectToPeer;
74
+ /**
75
+ * Removes a peer from the connection pool and cleans up resources.
76
+ * @private
77
+ * @param peerId - ID of the peer to remove
78
+ */
79
+ private _removePeer;
80
+ }
@@ -0,0 +1,157 @@
1
+ import Peer from 'simple-peer';
2
+ import { signal } from '../../event-signal.js';
3
+ import { JSONRPCClient } from '../../net/protocol/JSONRPCClient.js';
4
+ import { AbstractTransport } from '../AbstractTransport.js';
5
+ /**
6
+ * WebRTC-based transport implementation that enables direct peer-to-peer communication.
7
+ * Uses a WebSocket transport as a signaling channel to establish WebRTC connections.
8
+ * Once connections are established, data flows directly between peers without going through a server.
9
+ */
10
+ export class WebRTCTransport extends AbstractTransport {
11
+ /**
12
+ * Creates a new WebRTC transport instance.
13
+ * @param transport - The WebSocket transport to use for signaling
14
+ */
15
+ constructor(transport) {
16
+ super();
17
+ this.transport = transport;
18
+ this.peers = new Map();
19
+ /**
20
+ * Signal that emits when a message is received from a peer.
21
+ * Provides the message data, peer ID, and peer instance.
22
+ */
23
+ this.onMessage = signal();
24
+ /**
25
+ * Signal that emits when a new peer connection is established.
26
+ * Provides the peer ID and peer instance.
27
+ */
28
+ this.onPeerConnect = signal();
29
+ /**
30
+ * Signal that emits when a peer disconnects.
31
+ * Provides the peer ID and peer instance.
32
+ */
33
+ this.onPeerDisconnect = signal();
34
+ /**
35
+ * Signal that emits when the underlying signaling transport's state changes.
36
+ * This is delegated directly from the WebSocketTransport.
37
+ */
38
+ this.onStateChange = this.transport.onStateChange;
39
+ this.rpc = new JSONRPCClient(transport);
40
+ this.subscriptions = [
41
+ this.rpc.on('peer-welcome', ({ id, peers }) => {
42
+ this._id = id;
43
+ peers.forEach((peerId) => this._connectToPeer(peerId, true));
44
+ }),
45
+ this.rpc.on('peer-disconnected', ({ id }) => {
46
+ this._removePeer(id);
47
+ }),
48
+ this.rpc.on('peer-signal', ({ from, data }) => {
49
+ if (!this.peers.has(from)) {
50
+ this._connectToPeer(from, false);
51
+ }
52
+ this.peers.get(from)?.peer.signal(data);
53
+ }),
54
+ ];
55
+ }
56
+ /**
57
+ * Gets the unique ID assigned to this peer by the signaling server.
58
+ * @returns The peer ID, or undefined if not yet connected
59
+ */
60
+ get id() {
61
+ return this._id;
62
+ }
63
+ /**
64
+ * Gets the current connection state of the underlying signaling transport.
65
+ * @returns The current connection state
66
+ */
67
+ get state() {
68
+ return this.transport.state;
69
+ }
70
+ /**
71
+ * Establishes the connection by connecting to the signaling server.
72
+ * Peer connections will be established automatically after this.
73
+ * @returns A promise that resolves when connected to the signaling server
74
+ */
75
+ async connect() {
76
+ await this.transport.connect();
77
+ }
78
+ /**
79
+ * Terminates the connection for this transport.
80
+ * Must be implemented by concrete subclasses.
81
+ */
82
+ disconnect() {
83
+ this.subscriptions.forEach(u => u());
84
+ this.subscriptions.length = 0;
85
+ // Call _removePeer for each peer, which handles destroy()
86
+ const peerIds = Array.from(this.peers.keys());
87
+ peerIds.forEach(peerId => this._removePeer(peerId));
88
+ // Ensure the map is clear after removal
89
+ this.peers.clear();
90
+ // Disconnect the underlying transport if needed (optional, depends on desired behavior)
91
+ // this.transport.disconnect();
92
+ }
93
+ /**
94
+ * Sends data to one or all connected peers.
95
+ * @param data - The string data to send
96
+ * @param peerId - Optional ID of specific peer to send to; if omitted, sends to all peers
97
+ */
98
+ send(data, peerId) {
99
+ for (const info of this.peers.values()) {
100
+ if (peerId && info.id !== peerId)
101
+ continue;
102
+ if (info.connected) {
103
+ try {
104
+ info.peer.send(data);
105
+ }
106
+ catch (e) {
107
+ console.warn('Failed to send to peer:', e);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Establishes a WebRTC connection to a peer.
114
+ * @private
115
+ * @param peerId - ID of the peer to connect to
116
+ * @param initiator - Whether this peer is initiating the connection
117
+ */
118
+ _connectToPeer(peerId, initiator) {
119
+ const peer = new Peer({ initiator, trickle: false });
120
+ peer.on('signal', data => {
121
+ this.rpc.request('peer-signal', { to: peerId, data });
122
+ });
123
+ peer.on('connect', () => {
124
+ this.peers.set(peerId, { id: peerId, peer, connected: true });
125
+ this.onPeerConnect.emit(peerId, peer);
126
+ });
127
+ peer.on('data', raw => {
128
+ try {
129
+ this.onMessage.emit(raw, peerId, peer);
130
+ }
131
+ catch (e) {
132
+ console.error('Invalid peer data:', e);
133
+ }
134
+ });
135
+ peer.on('close', () => {
136
+ this._removePeer(peerId);
137
+ });
138
+ peer.on('error', err => {
139
+ console.error('Peer error:', err);
140
+ this._removePeer(peerId);
141
+ });
142
+ this.peers.set(peerId, { id: peerId, peer, connected: false });
143
+ }
144
+ /**
145
+ * Removes a peer from the connection pool and cleans up resources.
146
+ * @private
147
+ * @param peerId - ID of the peer to remove
148
+ */
149
+ _removePeer(peerId) {
150
+ const info = this.peers.get(peerId);
151
+ if (info) {
152
+ this.peers.delete(peerId);
153
+ this.onPeerDisconnect.emit(peerId, info.peer);
154
+ info.peer.destroy();
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,107 @@
1
+ import { type Signal } from '../../event-signal.js';
2
+ import type { Change, PatchSnapshot, VersionMetadata } from '../../types.js';
3
+ import type { ConnectionState, ListOptions, PatchesAPI, PatchesNotificationParams } from '../protocol/types.js';
4
+ import { type WebSocketOptions } from './WebSocketTransport.js';
5
+ /**
6
+ * High-level client for the Patches real-time collaboration service.
7
+ * This class provides document subscription, patch notification handling,
8
+ * versioning, and other OT-specific functionality
9
+ * over a WebSocket connection.
10
+ */
11
+ export declare class PatchesWebSocket implements PatchesAPI {
12
+ private rpc;
13
+ private transport;
14
+ /** Signal emitted when the underlying WebSocket connection state changes. */
15
+ readonly onStateChange: Signal<(state: ConnectionState) => void>;
16
+ /** Signal emitted when the server pushes document changes. */
17
+ readonly onChangesCommitted: Signal<(params: PatchesNotificationParams) => void>;
18
+ /**
19
+ * Creates a new Patches WebSocket client instance.
20
+ * @param url - The WebSocket server URL to connect to
21
+ * @param wsOptions - Optional configuration for the underlying WebSocket connection
22
+ */
23
+ constructor(url: string, wsOptions?: WebSocketOptions);
24
+ /**
25
+ * Establishes a connection to the Patches server.
26
+ * @returns A promise that resolves when the connection is established
27
+ */
28
+ connect(): Promise<void>;
29
+ /**
30
+ * Terminates the connection to the Patches server.
31
+ */
32
+ disconnect(): void;
33
+ /**
34
+ * Subscribes the client to one or more documents to receive real-time updates.
35
+ * @param ids - Document ID or IDs to subscribe to.
36
+ * @returns A promise resolving with the list of successfully subscribed document IDs.
37
+ */
38
+ subscribe(ids: string | string[]): Promise<string[]>;
39
+ /**
40
+ * Unsubscribes the client from one or more documents.
41
+ * @param ids - Document ID or IDs to unsubscribe from.
42
+ * @returns A promise resolving when the unsubscription is confirmed.
43
+ */
44
+ unsubscribe(ids: string | string[]): Promise<void>;
45
+ /**
46
+ * Gets the latest state (content and revision) of a document.
47
+ * @param docId - The ID of the document.
48
+ * @returns A promise resolving with the document snapshot.
49
+ */
50
+ getDoc(docId: string): Promise<PatchSnapshot>;
51
+ /**
52
+ * Gets changes that occurred for a document after a specific revision number.
53
+ * @param docId - The ID of the document.
54
+ * @param rev - The revision number after which to fetch changes.
55
+ * @returns A promise resolving with an array of changes.
56
+ */
57
+ getChangesSince(docId: string, rev: number): Promise<Change[]>;
58
+ /**
59
+ * Applies a set of client-generated changes to a document on the server.
60
+ * @param docId - The ID of the document.
61
+ * @param changes - An array of changes to apply.
62
+ * @returns A promise resolving with the changes as committed by the server (potentially transformed).
63
+ */
64
+ commitChanges(docId: string, changes: Change[]): Promise<Change[]>;
65
+ /**
66
+ * Deletes a document on the server.
67
+ * @param docId - The ID of the document to delete.
68
+ * @returns A promise resolving when the deletion is confirmed.
69
+ */
70
+ deleteDoc(docId: string): Promise<void>;
71
+ /**
72
+ * Creates a named version snapshot of a document's current state on the server.
73
+ * @param docId - The ID of the document.
74
+ * @param name - A descriptive name for the version.
75
+ * @returns A promise resolving with the unique ID of the newly created version.
76
+ */
77
+ createVersion(docId: string, name: string): Promise<string>;
78
+ /**
79
+ * Lists metadata for saved versions of a document.
80
+ * @param docId - The ID of the document.
81
+ * @param options - Options for filtering or pagination (e.g., limit, offset).
82
+ * @returns A promise resolving with an array of version metadata objects.
83
+ */
84
+ listVersions(docId: string, options?: ListOptions): Promise<VersionMetadata[]>;
85
+ /**
86
+ * Gets the document state snapshot corresponding to a specific version ID.
87
+ * @param docId - The ID of the document.
88
+ * @param versionId - The ID of the version to retrieve.
89
+ * @returns A promise resolving with the document snapshot for that version.
90
+ */
91
+ getVersionState(docId: string, versionId: string): Promise<PatchSnapshot>;
92
+ /**
93
+ * Gets the original changes associated with a specific version ID.
94
+ * @param docId - The ID of the document.
95
+ * @param versionId - The ID of the version.
96
+ * @returns A promise resolving with an array of changes that constitute that version.
97
+ */
98
+ getVersionChanges(docId: string, versionId: string): Promise<Change[]>;
99
+ /**
100
+ * Updates the name of a specific version.
101
+ * @param docId - The ID of the document.
102
+ * @param versionId - The ID of the version to update.
103
+ * @param name - The new name for the version.
104
+ * @returns A promise resolving when the update is confirmed.
105
+ */
106
+ updateVersion(docId: string, versionId: string, name: string): Promise<void>;
107
+ }