@shet-anirudh/crdt-sync-transport-ws 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 crdt-sync contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serialization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/serialization.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createRecord, applyLocalChange, deleteField } from '@shet-anirudh/crdt-sync-core';
3
+ import { serializeRecord, deserializeRecord } from '../serialization.js';
4
+ describe('MessagePack serialization', () => {
5
+ it('serializes and deserializes an empty record', () => {
6
+ const record = createRecord();
7
+ const bytes = serializeRecord(record);
8
+ const restored = deserializeRecord(bytes);
9
+ // Deep equal check
10
+ expect(restored).toEqual(record);
11
+ // Verify it uses a null prototype like the original
12
+ expect(Object.getPrototypeOf(restored)).toBeNull();
13
+ });
14
+ it('serializes and deserializes a record with various field types', () => {
15
+ let record = createRecord();
16
+ const now = 1000;
17
+ record = applyLocalChange(record, 'stringField', 'hello', 'dev-a', now);
18
+ record = applyLocalChange(record, 'numberField', 42, 'dev-a', now);
19
+ record = applyLocalChange(record, 'booleanField', true, 'dev-a', now);
20
+ const bytes = serializeRecord(record);
21
+ const restored = deserializeRecord(bytes);
22
+ expect(restored).toEqual(record);
23
+ expect(restored['stringField']?.value).toBe('hello');
24
+ expect(restored['stringField']?.origin).toBe('dev-a');
25
+ expect(restored['numberField']?.value).toBe(42);
26
+ expect(restored['booleanField']?.value).toBe(true);
27
+ });
28
+ it('serializes and deserializes tombstones (deleted fields)', () => {
29
+ let record = createRecord();
30
+ record = applyLocalChange(record, 'temp', 'value', 'dev', 1000);
31
+ record = deleteField(record, 'temp', 'dev', 2000);
32
+ const bytes = serializeRecord(record);
33
+ const restored = deserializeRecord(bytes);
34
+ expect(restored).toEqual(record);
35
+ expect(restored['temp']?.deleted).toBe(true);
36
+ expect(restored['temp']?.value).toBeNull();
37
+ });
38
+ });
39
+ //# sourceMappingURL=serialization.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.test.js","sourceRoot":"","sources":["../../src/__tests__/serialization.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEzE,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAE1C,mBAAmB;QACnB,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjC,oDAAoD;QACpD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,IAAI,MAAM,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC;QAEjB,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QACxE,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QACnE,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAEtE,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAE1C,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,IAAI,MAAM,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAChE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAElD,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAE1C,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transport.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/transport.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { createRecord, applyLocalChange, mergeRecords } from '@shet-anirudh/crdt-sync-core';
3
+ import { startRelayServer } from '../relay-server.js';
4
+ import { WebSocketTransport } from '../ws-transport.js';
5
+ import { serializeRecord, deserializeRecord } from '../serialization.js';
6
+ describe('WebSocket Transport Integration', () => {
7
+ let server;
8
+ let serverUrl;
9
+ beforeAll(async () => {
10
+ // Start on random available port
11
+ server = await startRelayServer({ port: 0 });
12
+ serverUrl = `ws://localhost:${server.port}`;
13
+ });
14
+ afterAll(async () => {
15
+ if (server) {
16
+ await server.close();
17
+ }
18
+ });
19
+ it('connects two peers and exchanges messages', async () => {
20
+ const transportA = new WebSocketTransport({ serverUrl, initialReconnectDelayMs: 100 });
21
+ const transportB = new WebSocketTransport({ serverUrl, initialReconnectDelayMs: 100 });
22
+ const messagesReceivedByA = [];
23
+ const messagesReceivedByB = [];
24
+ transportA.onReceive((from, payload) => {
25
+ expect(from).toBe('peer-b');
26
+ messagesReceivedByA.push(payload);
27
+ });
28
+ transportB.onReceive((from, payload) => {
29
+ expect(from).toBe('peer-a');
30
+ messagesReceivedByB.push(payload);
31
+ });
32
+ // 1. Connect
33
+ await Promise.all([
34
+ transportA.connect('peer-a'),
35
+ transportB.connect('peer-b'),
36
+ ]);
37
+ expect(transportA.isConnected).toBe(true);
38
+ expect(transportB.isConnected).toBe(true);
39
+ // 2. Exchange CRDT records
40
+ let recordA = createRecord();
41
+ recordA = applyLocalChange(recordA, 'title', 'Hello from A', 'peer-a', Date.now());
42
+ let recordB = createRecord();
43
+ recordB = applyLocalChange(recordB, 'status', 'Active', 'peer-b', Date.now());
44
+ // A sends to B
45
+ await transportA.send('peer-b', serializeRecord(recordA));
46
+ // B sends to A
47
+ await transportB.send('peer-a', serializeRecord(recordB));
48
+ // Wait a moment for delivery
49
+ await new Promise((resolve) => setTimeout(resolve, 100));
50
+ expect(messagesReceivedByA.length).toBe(1);
51
+ expect(messagesReceivedByB.length).toBe(1);
52
+ // 3. Deserialize and Merge
53
+ const receivedRecordA = deserializeRecord(messagesReceivedByB[0]); // B received A's record
54
+ const receivedRecordB = deserializeRecord(messagesReceivedByA[0]); // A received B's record
55
+ const mergedAtA = mergeRecords(recordA, receivedRecordB);
56
+ const mergedAtB = mergeRecords(recordB, receivedRecordA);
57
+ // Both should now have exactly the same data
58
+ expect(mergedAtA['title']?.value).toBe('Hello from A');
59
+ expect(mergedAtA['status']?.value).toBe('Active');
60
+ expect(mergedAtA).toEqual(mergedAtB);
61
+ // 4. Disconnect
62
+ await Promise.all([
63
+ transportA.disconnect(),
64
+ transportB.disconnect(),
65
+ ]);
66
+ expect(transportA.isConnected).toBe(false);
67
+ expect(transportB.isConnected).toBe(false);
68
+ });
69
+ it('buffers messages sent while offline and flushes on reconnect', async () => {
70
+ const transportA = new WebSocketTransport({ serverUrl, initialReconnectDelayMs: 100 });
71
+ const transportB = new WebSocketTransport({ serverUrl, initialReconnectDelayMs: 100 });
72
+ const receivedByB = [];
73
+ transportB.onReceive((_from, payload) => {
74
+ receivedByB.push(payload);
75
+ });
76
+ await transportB.connect('peer-b');
77
+ // A sends a message BEFORE connecting
78
+ const payload = new Uint8Array([1, 2, 3]);
79
+ await transportA.send('peer-b', payload);
80
+ expect(receivedByB.length).toBe(0);
81
+ // Connect A — it should flush the queue
82
+ await transportA.connect('peer-a');
83
+ // Wait for delivery
84
+ await new Promise((resolve) => setTimeout(resolve, 100));
85
+ expect(receivedByB.length).toBe(1);
86
+ expect(receivedByB[0]).toEqual(payload);
87
+ await Promise.all([transportA.disconnect(), transportB.disconnect()]);
88
+ });
89
+ });
90
+ //# sourceMappingURL=transport.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.test.js","sourceRoot":"","sources":["../../src/__tests__/transport.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @shet-anirudh/crdt-sync-transport-ws — Public API
3
+ *
4
+ * Provides a WebSocket-based transport adapter and relay server for the
5
+ * CRDT sync engine.
6
+ */
7
+ export type { PeerId, SyncTransport, WireMessage } from './types.js';
8
+ export { serializeRecord, deserializeRecord, serializeMessage, deserializeMessage, } from './serialization.js';
9
+ export { WebSocketTransport, type WebSocketTransportOptions, } from './ws-transport.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @shet-anirudh/crdt-sync-transport-ws — Public API
3
+ *
4
+ * Provides a WebSocket-based transport adapter and relay server for the
5
+ * CRDT sync engine.
6
+ */
7
+ export { serializeRecord, deserializeRecord, serializeMessage, deserializeMessage, } from './serialization.js';
8
+ export { WebSocketTransport, } from './ws-transport.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WebSocket Relay Server
3
+ *
4
+ * A minimal relay server that connects two (or more) peers by name.
5
+ * Peers register with their DeviceId, then send messages addressed to
6
+ * another DeviceId. The server forwards the raw bytes — it is CRDT-unaware.
7
+ *
8
+ * Architecture:
9
+ * [Device A] ──ws──▶ [Relay Server] ──ws──▶ [Device B]
10
+ *
11
+ * Why relay mode instead of direct P2P?
12
+ * Direct P2P needs NAT traversal (ICE/STUN/TURN). A relay is simpler
13
+ * to demo reliably and is the standard starting point before adding
14
+ * WebRTC or Nearby Connections.
15
+ *
16
+ * Wire protocol (all messages are MessagePack-encoded):
17
+ * Client → Server: { type: 'register', peerId: string }
18
+ * { type: 'send', to: string, payload: Uint8Array }
19
+ * Server → Client: { type: 'message', from: string, payload: Uint8Array }
20
+ * { type: 'peer_joined', peerId: string }
21
+ * { type: 'peer_left', peerId: string }
22
+ * { type: 'error', message: string }
23
+ */
24
+ export interface RelayServerOptions {
25
+ port?: number;
26
+ host?: string;
27
+ }
28
+ /**
29
+ * Start the relay server.
30
+ * @returns A promise resolving to an object with the bound port and a close() method.
31
+ */
32
+ export declare function startRelayServer(options?: RelayServerOptions): Promise<{
33
+ close: () => Promise<void>;
34
+ port: number;
35
+ }>;
36
+ //# sourceMappingURL=relay-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-server.d.ts","sourceRoot":"","sources":["../src/relay-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAsJD"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * WebSocket Relay Server
3
+ *
4
+ * A minimal relay server that connects two (or more) peers by name.
5
+ * Peers register with their DeviceId, then send messages addressed to
6
+ * another DeviceId. The server forwards the raw bytes — it is CRDT-unaware.
7
+ *
8
+ * Architecture:
9
+ * [Device A] ──ws──▶ [Relay Server] ──ws──▶ [Device B]
10
+ *
11
+ * Why relay mode instead of direct P2P?
12
+ * Direct P2P needs NAT traversal (ICE/STUN/TURN). A relay is simpler
13
+ * to demo reliably and is the standard starting point before adding
14
+ * WebRTC or Nearby Connections.
15
+ *
16
+ * Wire protocol (all messages are MessagePack-encoded):
17
+ * Client → Server: { type: 'register', peerId: string }
18
+ * { type: 'send', to: string, payload: Uint8Array }
19
+ * Server → Client: { type: 'message', from: string, payload: Uint8Array }
20
+ * { type: 'peer_joined', peerId: string }
21
+ * { type: 'peer_left', peerId: string }
22
+ * { type: 'error', message: string }
23
+ */
24
+ import { createServer } from 'http';
25
+ import { decode, encode } from '@msgpack/msgpack';
26
+ import { WebSocket, WebSocketServer } from 'ws';
27
+ /**
28
+ * Start the relay server.
29
+ * @returns A promise resolving to an object with the bound port and a close() method.
30
+ */
31
+ export function startRelayServer(options = {}) {
32
+ return new Promise((resolveStart) => {
33
+ const port = options.port ?? 8787;
34
+ const host = options.host ?? '0.0.0.0';
35
+ const httpServer = createServer();
36
+ const wss = new WebSocketServer({ server: httpServer });
37
+ // PeerId → ConnectedPeer
38
+ const peers = new Map();
39
+ wss.on('connection', (socket) => {
40
+ let registeredPeerId = null;
41
+ socket.on('message', (raw) => {
42
+ let msg;
43
+ try {
44
+ const decoded = decode(raw);
45
+ if (typeof decoded !== 'object' || decoded === null) {
46
+ sendError(socket, 'Message must be an object');
47
+ return;
48
+ }
49
+ msg = decoded;
50
+ }
51
+ catch {
52
+ sendError(socket, 'Failed to decode MessagePack message');
53
+ return;
54
+ }
55
+ const type = msg['type'];
56
+ // ── Register ──────────────────────────────────────────────────────────
57
+ if (type === 'register') {
58
+ const peerId = msg['peerId'];
59
+ if (typeof peerId !== 'string' || peerId.length === 0) {
60
+ sendError(socket, 'register.peerId must be a non-empty string');
61
+ return;
62
+ }
63
+ // If the peer is already registered (e.g. React StrictMode remount or
64
+ // reconnect), close the stale connection and accept the new one.
65
+ if (peers.has(peerId)) {
66
+ const stale = peers.get(peerId);
67
+ if (stale.socket !== socket) {
68
+ stale.socket.close();
69
+ }
70
+ peers.delete(peerId);
71
+ }
72
+ registeredPeerId = peerId;
73
+ peers.set(peerId, { peerId, socket });
74
+ console.log(`[relay] peer registered: ${peerId} (${peers.size} total)`);
75
+ // Reply to sender to confirm registration (ws-transport awaits this)
76
+ socket.send(encode({ type: 'registered' }));
77
+ // Notify all other peers that someone joined
78
+ broadcast(peerId, encode({ type: 'peer_joined', peerId }), peers);
79
+ return;
80
+ }
81
+ // ── Send ──────────────────────────────────────────────────────────────
82
+ if (type === 'send') {
83
+ if (registeredPeerId === null) {
84
+ sendError(socket, 'Must register before sending');
85
+ return;
86
+ }
87
+ const to = msg['to'];
88
+ const payload = msg['payload'];
89
+ if (typeof to !== 'string') {
90
+ sendError(socket, 'send.to must be a string');
91
+ return;
92
+ }
93
+ if (!(payload instanceof Uint8Array) && !Buffer.isBuffer(payload)) {
94
+ sendError(socket, 'send.payload must be a Uint8Array');
95
+ return;
96
+ }
97
+ const recipient = peers.get(to);
98
+ if (recipient === undefined) {
99
+ sendError(socket, `Peer "${to}" is not connected`);
100
+ return;
101
+ }
102
+ // Forward the payload to the recipient, tagging it with the sender
103
+ const envelope = encode({
104
+ type: 'message',
105
+ from: registeredPeerId,
106
+ payload: payload instanceof Buffer ? new Uint8Array(payload) : payload,
107
+ });
108
+ recipient.socket.send(envelope);
109
+ return;
110
+ }
111
+ // ── Broadcast ─────────────────────────────────────────────────────────
112
+ if (type === 'broadcast') {
113
+ if (registeredPeerId === null) {
114
+ sendError(socket, 'Must register before broadcasting');
115
+ return;
116
+ }
117
+ const payload = msg['payload'];
118
+ if (!(payload instanceof Uint8Array) && !Buffer.isBuffer(payload)) {
119
+ sendError(socket, 'broadcast.payload must be a Uint8Array');
120
+ return;
121
+ }
122
+ const bytes = payload instanceof Buffer ? new Uint8Array(payload) : payload;
123
+ const envelope = encode({ type: 'message', from: registeredPeerId, payload: bytes });
124
+ broadcast(registeredPeerId, envelope, peers);
125
+ return;
126
+ }
127
+ sendError(socket, `Unknown message type: ${String(type)}`);
128
+ });
129
+ socket.on('close', () => {
130
+ if (registeredPeerId !== null) {
131
+ peers.delete(registeredPeerId);
132
+ console.log(`[relay] peer left: ${registeredPeerId} (${peers.size} remaining)`);
133
+ broadcast(registeredPeerId, encode({ type: 'peer_left', peerId: registeredPeerId }), peers);
134
+ }
135
+ });
136
+ socket.on('error', (err) => {
137
+ console.error(`[relay] socket error for ${registeredPeerId ?? 'unregistered'}:`, err.message);
138
+ });
139
+ });
140
+ httpServer.listen(port, host, () => {
141
+ const address = httpServer.address();
142
+ const boundPort = address && typeof address === 'object' ? address.port : port;
143
+ console.log(`[relay] server listening on ws://${host}:${boundPort}`);
144
+ resolveStart({
145
+ port: boundPort,
146
+ close: () => new Promise((resolve, reject) => {
147
+ wss.close((err) => {
148
+ if (err) {
149
+ reject(err);
150
+ return;
151
+ }
152
+ httpServer.close((err2) => {
153
+ if (err2)
154
+ reject(err2);
155
+ else
156
+ resolve();
157
+ });
158
+ });
159
+ }),
160
+ });
161
+ });
162
+ });
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // Helpers
166
+ // ---------------------------------------------------------------------------
167
+ function sendError(socket, message) {
168
+ if (socket.readyState === WebSocket.OPEN) {
169
+ socket.send(encode({ type: 'error', message }));
170
+ }
171
+ }
172
+ /** Send encoded bytes to all peers except the sender. */
173
+ function broadcast(excludePeerId, encoded, peers) {
174
+ for (const [id, peer] of peers) {
175
+ if (id !== excludePeerId && peer.socket.readyState === WebSocket.OPEN) {
176
+ peer.socket.send(encoded);
177
+ }
178
+ }
179
+ }
180
+ // ---------------------------------------------------------------------------
181
+ // CLI entrypoint — auto-start when run directly: node dist/relay-server.js
182
+ // ---------------------------------------------------------------------------
183
+ const isMain = typeof process !== 'undefined' &&
184
+ process.argv[1] !== undefined &&
185
+ (process.argv[1].endsWith('relay-server.js') || process.argv[1].endsWith('relay-server.ts'));
186
+ if (isMain) {
187
+ const port = process.env['PORT'] ? Number(process.env['PORT']) : 8787;
188
+ startRelayServer({ port }).then(({ port: bound }) => {
189
+ console.log(`[relay] ready — ws://localhost:${bound}`);
190
+ }).catch((err) => {
191
+ console.error('[relay] failed to start:', err);
192
+ process.exit(1);
193
+ });
194
+ }
195
+ //# sourceMappingURL=relay-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-server.js","sourceRoot":"","sources":["../src/relay-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * MessagePack serialization for CRDT records.
3
+ *
4
+ * Why MessagePack over JSON?
5
+ * - Binary format: no string escaping, smaller payloads (~30% on average)
6
+ * - Preserves numeric types (no JSON number precision loss)
7
+ * - Fast encode/decode
8
+ * - Well-supported in Node.js and browsers
9
+ *
10
+ * The wire format wraps the CRDTRecord in an envelope:
11
+ * { type, from, to?, record }
12
+ */
13
+ import type { CRDTRecord } from '@shet-anirudh/crdt-sync-core';
14
+ import type { WireMessage } from './types.js';
15
+ /**
16
+ * Serialize a CRDTRecord into a compact binary Uint8Array using MessagePack.
17
+ *
18
+ * The record is first converted to a plain JSON-compatible object (no
19
+ * null-prototype, HLC expanded to { wallTime, logical }) before encoding.
20
+ */
21
+ export declare function serializeRecord(record: CRDTRecord): Uint8Array;
22
+ /**
23
+ * Deserialize a Uint8Array (MessagePack) back into a CRDTRecord.
24
+ * @throws If the bytes are malformed or missing required fields.
25
+ */
26
+ export declare function deserializeRecord(bytes: Uint8Array): CRDTRecord;
27
+ /**
28
+ * Serialize a full WireMessage (including CRDTRecord payload) to bytes.
29
+ */
30
+ export declare function serializeMessage(msg: WireMessage): Uint8Array;
31
+ /**
32
+ * Deserialize a WireMessage from bytes.
33
+ * @throws If the bytes are malformed.
34
+ */
35
+ export declare function deserializeMessage(bytes: Uint8Array): WireMessage;
36
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,8BAA8B,CAAC;AAGhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAG9D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAG/D;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,CAW7D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAuBjE"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * MessagePack serialization for CRDT records.
3
+ *
4
+ * Why MessagePack over JSON?
5
+ * - Binary format: no string escaping, smaller payloads (~30% on average)
6
+ * - Preserves numeric types (no JSON number precision loss)
7
+ * - Fast encode/decode
8
+ * - Well-supported in Node.js and browsers
9
+ *
10
+ * The wire format wraps the CRDTRecord in an envelope:
11
+ * { type, from, to?, record }
12
+ */
13
+ import { decode, encode } from '@msgpack/msgpack';
14
+ import { hlcFromJSON, hlcToJSON } from '@shet-anirudh/crdt-sync-core';
15
+ // ---------------------------------------------------------------------------
16
+ // CRDTRecord serialization
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Serialize a CRDTRecord into a compact binary Uint8Array using MessagePack.
20
+ *
21
+ * The record is first converted to a plain JSON-compatible object (no
22
+ * null-prototype, HLC expanded to { wallTime, logical }) before encoding.
23
+ */
24
+ export function serializeRecord(record) {
25
+ const plain = recordToPlain(record);
26
+ return encode(plain);
27
+ }
28
+ /**
29
+ * Deserialize a Uint8Array (MessagePack) back into a CRDTRecord.
30
+ * @throws If the bytes are malformed or missing required fields.
31
+ */
32
+ export function deserializeRecord(bytes) {
33
+ const plain = decode(bytes);
34
+ return plainToRecord(plain);
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // WireMessage serialization (used by relay server + client)
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Serialize a full WireMessage (including CRDTRecord payload) to bytes.
41
+ */
42
+ export function serializeMessage(msg) {
43
+ const plain = {
44
+ type: msg.type,
45
+ from: msg.from,
46
+ };
47
+ if (msg.to !== undefined)
48
+ plain['to'] = msg.to;
49
+ if (msg.payload !== undefined) {
50
+ // payload is already a CRDTRecord; convert to plain before outer encode
51
+ plain['payload'] = recordToPlain(msg.payload);
52
+ }
53
+ return encode(plain);
54
+ }
55
+ /**
56
+ * Deserialize a WireMessage from bytes.
57
+ * @throws If the bytes are malformed.
58
+ */
59
+ export function deserializeMessage(bytes) {
60
+ const raw = decode(bytes);
61
+ assertObject(raw, 'WireMessage');
62
+ const obj = raw;
63
+ const type = obj['type'];
64
+ const from = obj['from'];
65
+ if (type !== 'sync' && type !== 'ping' && type !== 'pong') {
66
+ throw new Error(`Invalid WireMessage type: ${String(type)}`);
67
+ }
68
+ if (typeof from !== 'string') {
69
+ throw new Error(`WireMessage.from must be a string`);
70
+ }
71
+ const msg = { type, from };
72
+ if (typeof obj['to'] === 'string')
73
+ msg.to = obj['to'];
74
+ if (obj['payload'] !== undefined) {
75
+ msg.payload = plainToRecord(obj['payload']);
76
+ }
77
+ return msg;
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Internal helpers
81
+ // ---------------------------------------------------------------------------
82
+ /** Convert a CRDTRecord (null-prototype) to a plain JSON-safe object. */
83
+ function recordToPlain(record) {
84
+ const out = {};
85
+ for (const [key, entry] of Object.entries(record)) {
86
+ out[key] = fieldEntryToPlain(entry);
87
+ }
88
+ return out;
89
+ }
90
+ function fieldEntryToPlain(entry) {
91
+ const out = {
92
+ value: entry.value,
93
+ hlc: hlcToJSON(entry.hlc),
94
+ origin: entry.origin,
95
+ };
96
+ if (entry.deleted === true)
97
+ out['deleted'] = true;
98
+ return out;
99
+ }
100
+ /** Reconstruct a CRDTRecord from a plain decoded object. */
101
+ function plainToRecord(raw) {
102
+ assertObject(raw, 'CRDTRecord');
103
+ const obj = raw;
104
+ // Use null-prototype object to match core's convention
105
+ const record = Object.create(null);
106
+ for (const [key, rawEntry] of Object.entries(obj)) {
107
+ record[key] = plainToFieldEntry(rawEntry, key);
108
+ }
109
+ return record;
110
+ }
111
+ function plainToFieldEntry(raw, key) {
112
+ assertObject(raw, `FieldEntry[${key}]`);
113
+ const obj = raw;
114
+ const value = obj['value'] ?? null;
115
+ if (value !== null &&
116
+ typeof value !== 'string' &&
117
+ typeof value !== 'number' &&
118
+ typeof value !== 'boolean') {
119
+ throw new Error(`FieldEntry[${key}].value has invalid type: ${typeof value}`);
120
+ }
121
+ const hlc = hlcFromJSON(obj['hlc']);
122
+ const origin = obj['origin'];
123
+ if (typeof origin !== 'string') {
124
+ throw new Error(`FieldEntry[${key}].origin must be a string`);
125
+ }
126
+ const entry = { value, hlc, origin };
127
+ if (obj['deleted'] === true) {
128
+ return { ...entry, deleted: true };
129
+ }
130
+ return entry;
131
+ }
132
+ function assertObject(val, label) {
133
+ if (typeof val !== 'object' || val === null || Array.isArray(val)) {
134
+ throw new Error(`Expected object for ${label}, got: ${JSON.stringify(val)}`);
135
+ }
136
+ }
137
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Transport abstraction for the CRDT sync engine.
3
+ *
4
+ * The SyncTransport interface is the *only* contract the core engine needs.
5
+ * Any transport implementation (WebSocket, WebRTC, Bluetooth, SMS) plugs in
6
+ * by implementing this interface — core never changes.
7
+ */
8
+ /** Unique identifier for a peer/device on the network. */
9
+ export type PeerId = string;
10
+ /**
11
+ * The pluggable transport interface.
12
+ *
13
+ * Payloads are raw bytes (Uint8Array) — the transport is intentionally
14
+ * unaware of the CRDT serialization format. Serialization lives in
15
+ * serialization.ts, transport just moves bytes.
16
+ */
17
+ export interface SyncTransport {
18
+ /**
19
+ * Connect to the relay server (or directly to a peer).
20
+ * @param peerId - This device's own identifier on the network.
21
+ */
22
+ connect(peerId: PeerId): Promise<void>;
23
+ /**
24
+ * Send a binary payload to a specific peer.
25
+ * If the transport is disconnected, implementations should queue the
26
+ * message and deliver it on reconnect.
27
+ */
28
+ send(toPeerId: PeerId, payload: Uint8Array): Promise<void>;
29
+ /**
30
+ * Register a callback invoked whenever a message arrives from any peer.
31
+ */
32
+ onReceive(callback: (fromPeerId: PeerId, payload: Uint8Array) => void): void;
33
+ /**
34
+ * Gracefully disconnect from the network.
35
+ */
36
+ disconnect(): Promise<void>;
37
+ /**
38
+ * Returns true if the transport is currently connected.
39
+ */
40
+ readonly isConnected: boolean;
41
+ }
42
+ /**
43
+ * Wire message format exchanged between peers via the relay server.
44
+ * Serialized with MessagePack before going onto the wire.
45
+ */
46
+ export interface WireMessage {
47
+ /** Message type discriminant. */
48
+ type: 'sync' | 'ping' | 'pong';
49
+ /** Sender's device/peer ID. */
50
+ from: PeerId;
51
+ /** Recipient's device/peer ID (undefined = broadcast). */
52
+ to?: PeerId;
53
+ /** CRDT record payload (only present on 'sync' messages). */
54
+ payload?: unknown;
55
+ }
56
+ /**
57
+ * Relay server → client envelope.
58
+ * The relay server wraps incoming peer messages with the sender's ID.
59
+ */
60
+ export interface RelayEnvelope {
61
+ from: PeerId;
62
+ payload: Uint8Array;
63
+ }
64
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;;OAIG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAE7E;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,CAAC;CACrB"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Transport abstraction for the CRDT sync engine.
3
+ *
4
+ * The SyncTransport interface is the *only* contract the core engine needs.
5
+ * Any transport implementation (WebSocket, WebRTC, Bluetooth, SMS) plugs in
6
+ * by implementing this interface — core never changes.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * WebSocket client transport — implements SyncTransport.
3
+ *
4
+ * Connects to the relay server, registers with a peerId, and
5
+ * handles message routing to/from other peers.
6
+ *
7
+ * Works in both browser (native WebSocket via globalThis.WebSocket) and
8
+ * Node.js (globalThis.WebSocket is available from Node 22+, or via the `ws`
9
+ * polyfill injected at startup).
10
+ *
11
+ * Resilience features:
12
+ * - Exponential backoff reconnection (1s → 2s → 4s → ... → 30s max)
13
+ * - Outbound message queue: messages sent while offline are buffered
14
+ * and flushed on reconnect — this is what makes the transport
15
+ * offline-first rather than just a thin WebSocket wrapper.
16
+ */
17
+ import type { PeerId, SyncTransport } from './types.js';
18
+ export interface WebSocketTransportOptions {
19
+ /** WebSocket relay server URL, e.g. "ws://localhost:8787" */
20
+ serverUrl: string;
21
+ /** Initial reconnect delay in ms. Doubles each attempt up to maxReconnectDelayMs. */
22
+ initialReconnectDelayMs?: number;
23
+ /** Maximum reconnect delay (default 30 000 ms). */
24
+ maxReconnectDelayMs?: number;
25
+ }
26
+ export declare class WebSocketTransport implements SyncTransport {
27
+ private readonly serverUrl;
28
+ private readonly initialDelay;
29
+ private readonly maxDelay;
30
+ private ws;
31
+ private peerId;
32
+ private _isConnected;
33
+ private destroyed;
34
+ private receiveCallback;
35
+ private outboundQueue;
36
+ private reconnectDelay;
37
+ private reconnectTimer;
38
+ constructor(options: WebSocketTransportOptions);
39
+ get isConnected(): boolean;
40
+ connect(peerId: PeerId): Promise<void>;
41
+ send(toPeerId: PeerId, payload: Uint8Array): Promise<void>;
42
+ onReceive(callback: (fromPeerId: PeerId, payload: Uint8Array) => void): void;
43
+ disconnect(): Promise<void>;
44
+ private openSocket;
45
+ private scheduleReconnect;
46
+ private sendRaw;
47
+ private flushQueue;
48
+ }
49
+ //# sourceMappingURL=ws-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-transport.d.ts","sourceRoot":"","sources":["../src/ws-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAOD,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,EAAE,CAAqC;IAC/C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,eAAe,CAAoE;IAC3F,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAA8C;gBAExD,OAAO,EAAE,yBAAyB;IAO9C,IAAI,WAAW,IAAI,OAAO,CAEzB;IAIK,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE,SAAS,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAItE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC,OAAO,CAAC,UAAU;IAoGlB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;CASnB"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * WebSocket client transport — implements SyncTransport.
3
+ *
4
+ * Connects to the relay server, registers with a peerId, and
5
+ * handles message routing to/from other peers.
6
+ *
7
+ * Works in both browser (native WebSocket via globalThis.WebSocket) and
8
+ * Node.js (globalThis.WebSocket is available from Node 22+, or via the `ws`
9
+ * polyfill injected at startup).
10
+ *
11
+ * Resilience features:
12
+ * - Exponential backoff reconnection (1s → 2s → 4s → ... → 30s max)
13
+ * - Outbound message queue: messages sent while offline are buffered
14
+ * and flushed on reconnect — this is what makes the transport
15
+ * offline-first rather than just a thin WebSocket wrapper.
16
+ */
17
+ import { decode, encode } from '@msgpack/msgpack';
18
+ export class WebSocketTransport {
19
+ serverUrl;
20
+ initialDelay;
21
+ maxDelay;
22
+ ws = null;
23
+ peerId = null;
24
+ _isConnected = false;
25
+ destroyed = false;
26
+ receiveCallback = null;
27
+ outboundQueue = [];
28
+ reconnectDelay;
29
+ reconnectTimer = null;
30
+ constructor(options) {
31
+ this.serverUrl = options.serverUrl;
32
+ this.initialDelay = options.initialReconnectDelayMs ?? 1_000;
33
+ this.maxDelay = options.maxReconnectDelayMs ?? 30_000;
34
+ this.reconnectDelay = this.initialDelay;
35
+ }
36
+ get isConnected() {
37
+ return this._isConnected;
38
+ }
39
+ // ── SyncTransport API ────────────────────────────────────────────────────
40
+ async connect(peerId) {
41
+ if (this._isConnected)
42
+ return;
43
+ this.peerId = peerId;
44
+ this.destroyed = false;
45
+ await this.openSocket();
46
+ }
47
+ async send(toPeerId, payload) {
48
+ if (!this._isConnected || this.ws === null) {
49
+ // Buffer for delivery on reconnect
50
+ this.outboundQueue.push({ to: toPeerId, payload });
51
+ return;
52
+ }
53
+ this.sendRaw(toPeerId, payload);
54
+ }
55
+ onReceive(callback) {
56
+ this.receiveCallback = callback;
57
+ }
58
+ async disconnect() {
59
+ this.destroyed = true;
60
+ if (this.reconnectTimer !== null) {
61
+ clearTimeout(this.reconnectTimer);
62
+ this.reconnectTimer = null;
63
+ }
64
+ if (this.ws !== null) {
65
+ this.ws.close();
66
+ this.ws = null;
67
+ }
68
+ this._isConnected = false;
69
+ }
70
+ // ── Internal ─────────────────────────────────────────────────────────────
71
+ openSocket() {
72
+ return new Promise((resolve, reject) => {
73
+ // Use the native browser WebSocket (globalThis.WebSocket).
74
+ // This works in Vite/React without any Node.js polyfill.
75
+ // In Node.js 22+ WebSocket is also available on globalThis.
76
+ const socket = new globalThis.WebSocket(this.serverUrl);
77
+ socket.binaryType = 'arraybuffer';
78
+ this.ws = socket;
79
+ socket.addEventListener('open', () => {
80
+ // Register with the relay server
81
+ socket.send(encode({ type: 'register', peerId: this.peerId }));
82
+ });
83
+ socket.addEventListener('message', (event) => {
84
+ let raw;
85
+ if (event.data instanceof ArrayBuffer) {
86
+ raw = new Uint8Array(event.data);
87
+ }
88
+ else if (event.data instanceof Uint8Array) {
89
+ raw = event.data;
90
+ }
91
+ else {
92
+ console.warn('[ws-transport] unexpected message data type:', typeof event.data);
93
+ return;
94
+ }
95
+ let msg;
96
+ try {
97
+ const decoded = decode(raw);
98
+ if (typeof decoded !== 'object' || decoded === null)
99
+ return;
100
+ msg = decoded;
101
+ }
102
+ catch {
103
+ console.warn('[ws-transport] failed to decode message');
104
+ return;
105
+ }
106
+ const type = msg['type'];
107
+ if (type === 'registered') {
108
+ // Server confirmed registration
109
+ this._isConnected = true;
110
+ this.reconnectDelay = this.initialDelay; // reset backoff on success
111
+ this.flushQueue();
112
+ resolve();
113
+ return;
114
+ }
115
+ if (type === 'message') {
116
+ const from = msg['from'];
117
+ const payload = msg['payload'];
118
+ if (typeof from === 'string' &&
119
+ (payload instanceof Uint8Array || payload instanceof ArrayBuffer) &&
120
+ this.receiveCallback !== null) {
121
+ const bytes = payload instanceof ArrayBuffer ? new Uint8Array(payload) : payload;
122
+ this.receiveCallback(from, bytes);
123
+ }
124
+ return;
125
+ }
126
+ if (type === 'peer_joined') {
127
+ console.log(`[ws-transport] peer joined: ${String(msg['peerId'])}`);
128
+ return;
129
+ }
130
+ if (type === 'peer_left') {
131
+ console.log(`[ws-transport] peer left: ${String(msg['peerId'])}`);
132
+ return;
133
+ }
134
+ if (type === 'error') {
135
+ console.error(`[ws-transport] server error: ${String(msg['message'])}`);
136
+ // If we weren't registered yet, reject the connect() promise
137
+ if (!this._isConnected) {
138
+ reject(new Error(String(msg['message'])));
139
+ }
140
+ return;
141
+ }
142
+ });
143
+ socket.addEventListener('close', () => {
144
+ this._isConnected = false;
145
+ this.ws = null;
146
+ if (!this.destroyed) {
147
+ console.log(`[ws-transport] disconnected. Reconnecting in ${this.reconnectDelay}ms...`);
148
+ this.scheduleReconnect();
149
+ }
150
+ });
151
+ socket.addEventListener('error', () => {
152
+ console.error('[ws-transport] WebSocket error');
153
+ if (!this._isConnected) {
154
+ reject(new Error('WebSocket connection failed'));
155
+ }
156
+ });
157
+ });
158
+ }
159
+ scheduleReconnect() {
160
+ if (this.destroyed || this.peerId === null)
161
+ return;
162
+ this.reconnectTimer = setTimeout(() => {
163
+ this.reconnectTimer = null;
164
+ // Exponential backoff with jitter (±10%)
165
+ const jitter = this.reconnectDelay * 0.1 * (Math.random() * 2 - 1);
166
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2 + jitter, this.maxDelay);
167
+ console.log(`[ws-transport] attempting reconnect as "${this.peerId}"...`);
168
+ this.openSocket().catch((err) => {
169
+ console.error('[ws-transport] reconnect failed:', err);
170
+ });
171
+ }, this.reconnectDelay);
172
+ }
173
+ sendRaw(toPeerId, payload) {
174
+ if (this.ws === null || this.ws.readyState !== globalThis.WebSocket.OPEN)
175
+ return;
176
+ this.ws.send(encode({ type: 'send', to: toPeerId, payload }));
177
+ }
178
+ flushQueue() {
179
+ const queued = this.outboundQueue.splice(0);
180
+ for (const { to, payload } of queued) {
181
+ this.sendRaw(to, payload);
182
+ }
183
+ if (queued.length > 0) {
184
+ console.log(`[ws-transport] flushed ${queued.length} queued message(s)`);
185
+ }
186
+ }
187
+ }
188
+ //# sourceMappingURL=ws-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-transport.js","sourceRoot":"","sources":["../src/ws-transport.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@shet-anirudh/crdt-sync-transport-ws",
3
+ "version": "0.1.0",
4
+ "description": "WebSocket transport adapter and relay server for @shet-anirudh/crdt-sync-core.",
5
+ "keywords": [
6
+ "crdt",
7
+ "websocket",
8
+ "transport",
9
+ "sync",
10
+ "offline-first"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Anirudh Shet",
14
+ "homepage": "https://github.com/shet-anirudh/crdt-sync-engine#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/shet-anirudh/crdt-sync-engine.git",
18
+ "directory": "packages/transport-ws"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/shet-anirudh/crdt-sync-engine/issues"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ },
32
+ "./server": {
33
+ "import": "./dist/relay-server.js",
34
+ "types": "./dist/relay-server.d.ts"
35
+ }
36
+ },
37
+ "main": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "dependencies": {
43
+ "@msgpack/msgpack": "^3.0.0",
44
+ "ws": "^8.18.0",
45
+ "@shet-anirudh/crdt-sync-core": "0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/ws": "^8.5.12",
49
+ "rimraf": "^6.0.1",
50
+ "vitest": "^2.0.5"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc --project tsconfig.json",
54
+ "build:watch": "tsc --project tsconfig.json --watch",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "lint": "eslint src --ext .ts",
58
+ "clean": "rimraf dist",
59
+ "start:relay": "node dist/relay-server.js"
60
+ }
61
+ }