@automerge/automerge-repo-network-websocket 2.0.0-alpha.7 → 2.0.0-collectionsync-alpha.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.
package/README.md CHANGED
@@ -27,12 +27,12 @@ Handshake is the following steps:
27
27
 
28
28
  - Once a connection is established the initiating peer sends a
29
29
  [join](#join) message with the `senderId` set to the initiating peers ID and
30
- the `protocolVersion` set to "1"
30
+ a `supportedProtocolVersions` array containing "1"
31
31
  - The receiving peer waits until it receives a message from the initiating
32
32
  peer, if the initiating peer receives a message before sending the join message
33
33
  the initiating peer SHOULD terminate the connection.
34
34
  - When the receiving peer receives the join message
35
- - if the `protocolVersion` is not "1" the receiving peer sends an
35
+ - if the `supportedProtocolVersions` does not contain "1" the receiving peer sends an
36
36
  [error](#error) message and terminates the connection
37
37
  - otherwise
38
38
  - store the `senderId` as the peer ID of the initiating peer
@@ -119,7 +119,7 @@ Sent by the initiating peer in the [handshake](#handshake) phase.
119
119
  {
120
120
  type: "join",
121
121
  senderId: peer_id,
122
- supportedProtocolVersions: protocol_version
122
+ supportedProtocolVersions: [protocol_version]
123
123
  ? metadata: peer_metadata,
124
124
  }
125
125
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,OAAO,EAEL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAEL,iBAAiB,EAElB,MAAM,eAAe,CAAA;AAOtB,qBAAa,mBAAoB,SAAQ,cAAc;;IAyBnD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAzB3B,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;IAQ7C,OAAO;IAIP,SAAS;gBAYC,MAAM,EAAE,eAAe,EACvB,iBAAiB,SAAO;IAKlC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAyCnD,UAAU;IAQV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAqB/B,cAAc,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CAoE3D"}
1
+ {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,OAAO,EAEL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAEL,iBAAiB,EAElB,MAAM,eAAe,CAAA;AAOtB,qBAAa,mBAAoB,SAAQ,cAAc;;IAyBnD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAzB3B,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;IAQ7C,OAAO;IAIP,SAAS;gBAYC,MAAM,EAAE,eAAe,EACvB,iBAAiB,SAAO;IAKlC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAyCnD,UAAU;IAQV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAqB/B,cAAc,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CA2E3D"}
@@ -89,7 +89,15 @@ export class NodeWSServerAdapter extends NetworkAdapter {
89
89
  socket.send(arrayBuf);
90
90
  }
91
91
  receiveMessage(messageBytes, socket) {
92
- const message = decode(messageBytes);
92
+ let message;
93
+ try {
94
+ message = decode(messageBytes);
95
+ }
96
+ catch (e) {
97
+ log("invalid message received, closing connection");
98
+ socket.close();
99
+ return;
100
+ }
93
101
  const { type, senderId } = message;
94
102
  const myPeerId = this.peerId;
95
103
  assert(myPeerId);
@@ -0,0 +1,22 @@
1
+ /// <reference types="ws" />
2
+ import { NetworkAdapter } from "automerge-repo"
3
+ import WebSocket from "isomorphic-ws"
4
+ export interface WebSocketNetworkAdapter extends NetworkAdapter {
5
+ client?: WebSocket
6
+ }
7
+ export declare function sendMessage(
8
+ socket: WebSocket,
9
+ channelId: string,
10
+ senderId: string,
11
+ message: Uint8Array
12
+ ): void
13
+ export declare function receiveMessageClient(
14
+ message: Uint8Array,
15
+ self: WebSocketNetworkAdapter
16
+ ): void
17
+ export declare function receiveMessageServer(
18
+ message: Uint8Array,
19
+ socket: WebSocket,
20
+ self: WebSocketNetworkAdapter
21
+ ): void
22
+ //# sourceMappingURL=WSShared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WSShared.d.ts","sourceRoot":"","sources":["../src/WSShared.ts"],"names":[],"mappings":";AACA,OAAO,EAEL,cAAc,EAEf,MAAM,gBAAgB,CAAA;AACvB,OAAO,SAAS,MAAM,eAAe,CAAA;AAErC,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,QAoBpB;AAsBD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,uBAAuB,QA0B9B;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,uBAAuB,QA0B9B"}
@@ -0,0 +1,83 @@
1
+ import * as CBOR from "cbor-x"
2
+ import WebSocket from "isomorphic-ws"
3
+ export function sendMessage(socket, channelId, senderId, message) {
4
+ if (message.byteLength === 0) {
5
+ throw new Error("tried to send a zero-length message")
6
+ }
7
+ const decoded = {
8
+ senderId,
9
+ channelId,
10
+ type: "sync",
11
+ data: message,
12
+ }
13
+ const encoded = CBOR.encode(decoded)
14
+ // This incantation deals with websocket sending the whole
15
+ // underlying buffer even if we just have a uint8array view on it
16
+ const arrayBuf = encoded.buffer.slice(
17
+ encoded.byteOffset,
18
+ encoded.byteOffset + encoded.byteLength
19
+ )
20
+ socket.send(arrayBuf)
21
+ }
22
+ function announceConnection(channelId, peerId, socket, self) {
23
+ // return a peer object
24
+ const myPeerId = self.peerId
25
+ if (!myPeerId) {
26
+ throw new Error("we should have a peer ID by now")
27
+ }
28
+ const connection = {
29
+ close: () => socket.close(),
30
+ isOpen: () => socket.readyState === WebSocket.OPEN,
31
+ send: message => sendMessage(socket, channelId, myPeerId, message),
32
+ }
33
+ self.emit("peer-candidate", { peerId, channelId, connection })
34
+ }
35
+ export function receiveMessageClient(message, self) {
36
+ const decoded = CBOR.decode(new Uint8Array(message))
37
+ const { type, senderId, channelId, data } = decoded
38
+ const client = self.client
39
+ if (!client) {
40
+ throw new Error("Missing client at receiveMessage")
41
+ }
42
+ if (message.byteLength === 0) {
43
+ throw new Error("received a zero-length message")
44
+ }
45
+ switch (type) {
46
+ case "peer":
47
+ // console.log(`peer: ${senderId}, ${channelId}`)
48
+ announceConnection(channelId, senderId, client, self)
49
+ break
50
+ default:
51
+ self.emit("message", {
52
+ channelId,
53
+ senderId,
54
+ message: new Uint8Array(data),
55
+ })
56
+ }
57
+ }
58
+ export function receiveMessageServer(message, socket, self) {
59
+ const cbor = CBOR.decode(message)
60
+ console.log("received: ", cbor)
61
+ const { type, channelId, senderId, data } = cbor
62
+ switch (type) {
63
+ case "join":
64
+ announceConnection(channelId, senderId, socket, self)
65
+ socket.send(
66
+ CBOR.encode({ type: "peer", senderId: self.peerId, channelId })
67
+ )
68
+ break
69
+ case "leave":
70
+ // ?
71
+ break
72
+ case "sync":
73
+ self.emit("message", {
74
+ senderId,
75
+ channelId,
76
+ message: new Uint8Array(data),
77
+ })
78
+ break
79
+ default:
80
+ // console.log("unrecognized message type")
81
+ break
82
+ }
83
+ }
@@ -0,0 +1,4 @@
1
+ import * as CBOR from "cbor-x"
2
+ declare const _default: CBOR.Encoder
3
+ export default _default
4
+ //# sourceMappingURL=encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../src/encoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAA;;AAE9B,wBAAuD"}
@@ -0,0 +1,2 @@
1
+ import * as CBOR from "cbor-x"
2
+ export default new CBOR.Encoder({ tagUint8Array: false })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-network-websocket",
3
- "version": "2.0.0-alpha.7",
3
+ "version": "2.0.0-collectionsync-alpha.1",
4
4
  "description": "isomorphic node/browser Websocket network adapter for Automerge Repo",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo-network-websocket",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -13,7 +13,7 @@
13
13
  "test": "vitest"
14
14
  },
15
15
  "dependencies": {
16
- "@automerge/automerge-repo": "2.0.0-alpha.7",
16
+ "@automerge/automerge-repo": "2.0.0-collectionsync-alpha.1",
17
17
  "cbor-x": "^1.3.0",
18
18
  "debug": "^4.3.4",
19
19
  "eventemitter3": "^5.0.1",
@@ -31,5 +31,5 @@
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "gitHead": "4279df7dad7ef5f33b1544e3945dab3839fa5ef6"
34
+ "gitHead": "0a639a7d57bcee4e0a87890e6592b9582c2a5d6f"
35
35
  }
@@ -123,7 +123,14 @@ export class NodeWSServerAdapter extends NetworkAdapter {
123
123
  }
124
124
 
125
125
  receiveMessage(messageBytes: Uint8Array, socket: WebSocket) {
126
- const message: FromClientMessage = decode(messageBytes)
126
+ let message: FromClientMessage
127
+ try {
128
+ message = decode(messageBytes)
129
+ } catch (e) {
130
+ log("invalid message received, closing connection")
131
+ socket.close()
132
+ return
133
+ }
127
134
 
128
135
  const { type, senderId } = message
129
136
 
@@ -338,6 +338,43 @@ describe("Websocket adapters", () => {
338
338
  await eventPromise(serverAdapter, "peer-disconnected")
339
339
  })
340
340
 
341
+ it("should disconnect from a client that sends an invalid CBOR message", async () => {
342
+ // Set up a server and wait for it to be ready
343
+ const port = await getPort()
344
+ const serverUrl = `ws://localhost:${port}`
345
+ const server = http.createServer()
346
+ const serverSocket = new WebSocket.Server({ server })
347
+ await new Promise<void>(resolve => server.listen(port, resolve))
348
+
349
+ // Create a repo listening on the socket
350
+ const serverAdapter = new NodeWSServerAdapter(serverSocket)
351
+ const serverRepo = new Repo({
352
+ network: [serverAdapter],
353
+ peerId: serverPeerId,
354
+ })
355
+
356
+ // Create a new socket connected to the repo
357
+ const browserSocket = new WebSocket(serverUrl)
358
+ await new Promise(resolve => browserSocket.on("open", resolve))
359
+ const disconnected = new Promise(resolve =>
360
+ browserSocket.on("close", resolve)
361
+ )
362
+
363
+ // Send an invalid CBOR message, in this case we use a definite length
364
+ // array with too many elements. This test should actually work for any
365
+ // invalid message but this reproduces a specific issue we were seeing on
366
+ // the sycn server
367
+ //
368
+ // 0x9 (1001) is major type 4, for an array
369
+ // 0xB (1011) indicates that the length will be encoded in the next 8 bytes
370
+ // 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 is 2**32, which is longer than allowed
371
+ const invalidLargeArray = new Uint8Array([
372
+ 0x9b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
373
+ ])
374
+ browserSocket.send(invalidLargeArray)
375
+ await disconnected
376
+ })
377
+
341
378
  it("should send the negotiated protocol version in its hello message", async () => {
342
379
  const response = await serverResponse({
343
380
  type: "join",