@automerge/automerge-repo-network-websocket 1.0.0-alpha.2 → 1.0.0-alpha.3

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.
@@ -1,6 +1,7 @@
1
1
  /// <reference types="ws" />
2
- import { ChannelId, NetworkAdapter, PeerId } from "@automerge/automerge-repo";
2
+ import { NetworkAdapter, PeerId } from "@automerge/automerge-repo";
3
3
  import WebSocket from "isomorphic-ws";
4
+ import { FromClientMessage } from "./messages.js";
4
5
  declare abstract class WebSocketNetworkAdapter extends NetworkAdapter {
5
6
  socket?: WebSocket;
6
7
  }
@@ -11,7 +12,7 @@ export declare class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapt
11
12
  connect(peerId: PeerId): void;
12
13
  join(): void;
13
14
  leave(): void;
14
- sendMessage(targetId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
15
+ send(message: FromClientMessage): void;
15
16
  announceConnection(peerId: PeerId): void;
16
17
  receiveMessage(message: Uint8Array): void;
17
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EAET,cAAc,EACd,MAAM,EACP,MAAM,2BAA2B,CAAA;AAElC,OAAO,SAAS,MAAM,eAAe,CAAA;AAOrC,uBAAe,uBAAwB,SAAQ,cAAc;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,qBAAa,6BAA8B,SAAQ,uBAAuB;IAGxE,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACvC,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM;IA8BtB,IAAI;IAwBJ,KAAK;IAOL,WAAW,CACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,OAAO;IAgCpB,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAUjC,cAAc,CAAC,OAAO,EAAE,UAAU;CAsCnC"}
1
+ {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAElE,OAAO,SAAS,MAAM,eAAe,CAAA;AAIrC,OAAO,EACL,iBAAiB,EAGlB,MAAM,eAAe,CAAA;AAMtB,uBAAe,uBAAwB,SAAQ,cAAc;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,qBAAa,6BAA8B,SAAQ,uBAAuB;IAGxE,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IAEvC,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM;IA8BtB,IAAI;IAoBJ,KAAK;IAOL,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAwB/B,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAUjC,cAAc,CAAC,OAAO,EAAE,UAAU;CA6BnC"}
@@ -1,14 +1,15 @@
1
- import { NetworkAdapter, } from "@automerge/automerge-repo";
1
+ import { NetworkAdapter } from "@automerge/automerge-repo";
2
2
  import * as CBOR from "cbor-x";
3
3
  import WebSocket from "isomorphic-ws";
4
4
  import debug from "debug";
5
5
  import { ProtocolV1 } from "./protocolVersion.js";
6
+ import { isValidMessage } from "@automerge/automerge-repo/dist/network/messages.js";
6
7
  const log = debug("WebsocketClient");
7
8
  class WebSocketNetworkAdapter extends NetworkAdapter {
8
9
  socket;
9
10
  }
10
11
  export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
11
- // Type trickery required for platform independence,
12
+ // Type trickery required for platform independence,
12
13
  // see https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window
13
14
  timerId;
14
15
  url;
@@ -44,14 +45,14 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
44
45
  throw new Error("WTF, get a socket");
45
46
  }
46
47
  if (this.socket.readyState === WebSocket.OPEN) {
47
- this.socket.send(CBOR.encode(joinMessage(this.peerId)));
48
+ this.send(joinMessage(this.peerId));
48
49
  }
49
50
  else {
50
51
  this.socket.addEventListener("open", () => {
51
52
  if (!this.socket) {
52
53
  throw new Error("WTF, get a socket");
53
54
  }
54
- this.socket.send(CBOR.encode(joinMessage(this.peerId)));
55
+ this.send(joinMessage(this.peerId));
55
56
  }, { once: true });
56
57
  }
57
58
  }
@@ -59,31 +60,23 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
59
60
  if (!this.socket) {
60
61
  throw new Error("WTF, get a socket");
61
62
  }
62
- this.socket.send(CBOR.encode({ type: "leave", senderId: this.peerId }));
63
+ this.send({ type: "leave", senderId: this.peerId });
63
64
  }
64
- sendMessage(targetId, channelId, message, broadcast) {
65
- if (message.byteLength === 0) {
65
+ send(message) {
66
+ if ("data" in message && message.data.byteLength === 0) {
66
67
  throw new Error("tried to send a zero-length message");
67
68
  }
68
69
  if (!this.peerId) {
69
70
  throw new Error("Why don't we have a PeerID?");
70
71
  }
71
- const decoded = {
72
- senderId: this.peerId,
73
- targetId,
74
- channelId,
75
- type: "sync",
76
- message,
77
- broadcast,
78
- };
79
- const encoded = CBOR.encode(decoded);
80
- // This incantation deals with websocket sending the whole
81
- // underlying buffer even if we just have a uint8array view on it
82
- const arrayBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
83
72
  if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
84
73
  throw new Error("Websocket Socket not ready!");
85
74
  }
86
- this.socket.send(arrayBuf);
75
+ const encoded = CBOR.encode(message);
76
+ // This incantation deals with websocket sending the whole
77
+ // underlying buffer even if we just have a uint8array view on it
78
+ const arrayBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
79
+ this.socket?.send(arrayBuf);
87
80
  }
88
81
  announceConnection(peerId) {
89
82
  // return a peer object
@@ -95,7 +88,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
95
88
  }
96
89
  receiveMessage(message) {
97
90
  const decoded = CBOR.decode(new Uint8Array(message));
98
- const { type, senderId, targetId, channelId, message: messageData, broadcast, } = decoded;
91
+ const { type, senderId } = decoded;
99
92
  const socket = this.socket;
100
93
  if (!socket) {
101
94
  throw new Error("Missing socket at receiveMessage");
@@ -105,19 +98,17 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
105
98
  }
106
99
  switch (type) {
107
100
  case "peer":
108
- log(`peer: ${senderId}, ${channelId}`);
101
+ log(`peer: ${senderId}`);
109
102
  this.announceConnection(senderId);
110
103
  break;
111
104
  case "error":
112
- log(`error: ${decoded.errorMessage}`);
105
+ log(`error: ${decoded.message}`);
106
+ break;
113
107
  default:
114
- this.emit("message", {
115
- channelId,
116
- senderId,
117
- targetId,
118
- message: new Uint8Array(messageData),
119
- broadcast,
120
- });
108
+ if (!isValidMessage(decoded)) {
109
+ throw new Error("Invalid message received");
110
+ }
111
+ this.emit("message", decoded);
121
112
  }
122
113
  }
123
114
  }
@@ -1,6 +1,7 @@
1
1
  /// <reference types="ws" />
2
2
  import { WebSocket, type WebSocketServer } from "isomorphic-ws";
3
- import { ChannelId, NetworkAdapter, PeerId } from "@automerge/automerge-repo";
3
+ import { NetworkAdapter, type PeerId } from "@automerge/automerge-repo";
4
+ import { FromServerMessage } from "./messages.js";
4
5
  export declare class NodeWSServerAdapter extends NetworkAdapter {
5
6
  server: WebSocketServer;
6
7
  sockets: {
@@ -10,7 +11,7 @@ export declare class NodeWSServerAdapter extends NetworkAdapter {
10
11
  connect(peerId: PeerId): void;
11
12
  join(): void;
12
13
  leave(): void;
13
- sendMessage(targetId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
14
+ send(message: FromServerMessage): void;
14
15
  receiveMessage(message: Uint8Array, socket: WebSocket): void;
15
16
  }
16
17
  //# sourceMappingURL=NodeWSServerAdapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAK/D,OAAO,EACL,SAAS,EAET,cAAc,EACd,MAAM,EACP,MAAM,2BAA2B,CAAA;AAIlC,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;gBAEjC,MAAM,EAAE,eAAe;IAKnC,OAAO,CAAC,MAAM,EAAE,MAAM;IAmBtB,IAAI;IAIJ,KAAK;IAIL,WAAW,CACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,OAAO;IAsCpB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CA2EtD"}
1
+ {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAM/D,OAAO,EACL,cAAc,EAEd,KAAK,MAAM,EACZ,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAqB,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEpE,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;gBAEjC,MAAM,EAAE,eAAe;IAKnC,OAAO,CAAC,MAAM,EAAE,MAAM;IAmBtB,IAAI;IAIJ,KAAK;IAIL,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAyB/B,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CAuDtD"}
@@ -1,8 +1,8 @@
1
1
  import * as CBOR from "cbor-x";
2
2
  import debug from "debug";
3
3
  const log = debug("WebsocketServer");
4
- import { NetworkAdapter, } from "@automerge/automerge-repo";
5
4
  import { ProtocolV1 } from "./protocolVersion.js";
5
+ import { NetworkAdapter, } from "@automerge/automerge-repo";
6
6
  export class NodeWSServerAdapter extends NetworkAdapter {
7
7
  server;
8
8
  sockets = {};
@@ -31,41 +31,32 @@ export class NodeWSServerAdapter extends NetworkAdapter {
31
31
  leave() {
32
32
  // throw new Error("The server doesn't join channels.")
33
33
  }
34
- sendMessage(targetId, channelId, message, broadcast) {
35
- if (message.byteLength === 0) {
34
+ send(message) {
35
+ if ("data" in message && message.data.byteLength === 0) {
36
36
  throw new Error("tried to send a zero-length message");
37
37
  }
38
38
  const senderId = this.peerId;
39
39
  if (!senderId) {
40
40
  throw new Error("No peerId set for the websocket server network adapter.");
41
41
  }
42
- if (this.sockets[targetId] === undefined) {
43
- log(`Tried to send message to disconnected peer: ${targetId}`);
42
+ if (this.sockets[message.targetId] === undefined) {
43
+ log(`Tried to send message to disconnected peer: ${message.targetId}`);
44
44
  return;
45
45
  }
46
- const decoded = {
47
- senderId,
48
- targetId,
49
- channelId,
50
- type: "sync",
51
- message,
52
- broadcast,
53
- };
54
- const encoded = CBOR.encode(decoded);
46
+ const encoded = CBOR.encode(message);
55
47
  // This incantation deals with websocket sending the whole
56
48
  // underlying buffer even if we just have a uint8array view on it
57
49
  const arrayBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
58
- log(`[${senderId}->${targetId}@${channelId}] "sync" | ${arrayBuf.byteLength} bytes`);
59
- this.sockets[targetId].send(arrayBuf);
50
+ this.sockets[message.targetId]?.send(arrayBuf);
60
51
  }
61
52
  receiveMessage(message, socket) {
62
53
  const cbor = CBOR.decode(message);
63
- const { type, channelId, senderId, targetId, message: data, broadcast, supportedProtocolVersions, } = cbor;
54
+ const { type, senderId } = cbor;
64
55
  const myPeerId = this.peerId;
65
56
  if (!myPeerId) {
66
57
  throw new Error("Missing my peer ID.");
67
58
  }
68
- log(`[${senderId}->${myPeerId}@${channelId}] ${type} | ${message.byteLength} bytes`);
59
+ log(`[${senderId}->${myPeerId}${"documentId" in cbor ? "@" + cbor.documentId : ""}] ${type} | ${message.byteLength} bytes`);
69
60
  switch (type) {
70
61
  case "join":
71
62
  // Let the rest of the system know that we have a new connection.
@@ -73,21 +64,24 @@ export class NodeWSServerAdapter extends NetworkAdapter {
73
64
  this.sockets[senderId] = socket;
74
65
  // In this client-server connection, there's only ever one peer: us!
75
66
  // (and we pretend to be joined to every channel)
76
- const selectedProtocolVersion = selectProtocol(supportedProtocolVersions);
67
+ const selectedProtocolVersion = selectProtocol(cbor.supportedProtocolVersions);
77
68
  if (selectedProtocolVersion === null) {
78
- socket.send(CBOR.encode({
69
+ this.send({
79
70
  type: "error",
80
- errorMessage: "unsupported protocol version",
81
- }));
71
+ senderId: this.peerId,
72
+ message: "unsupported protocol version",
73
+ targetId: senderId,
74
+ });
82
75
  this.sockets[senderId].close();
83
76
  delete this.sockets[senderId];
84
77
  }
85
78
  else {
86
- socket.send(CBOR.encode({
79
+ this.send({
87
80
  type: "peer",
88
81
  senderId: this.peerId,
89
82
  selectedProtocolVersion: ProtocolV1,
90
- }));
83
+ targetId: senderId,
84
+ });
91
85
  }
92
86
  break;
93
87
  case "leave":
@@ -96,21 +90,8 @@ export class NodeWSServerAdapter extends NetworkAdapter {
96
90
  // TODO: confirm this
97
91
  // ?
98
92
  break;
99
- // We accept both "message" and "sync" because a previous version of this
100
- // codebase sent sync messages in the BrowserWebSocketClientAdapter as
101
- // type "message" and we want to stay backwards compatible
102
- case "message":
103
- case "sync":
104
- this.emit("message", {
105
- senderId,
106
- targetId,
107
- channelId,
108
- message: new Uint8Array(data),
109
- broadcast,
110
- });
111
- break;
112
93
  default:
113
- log(`unrecognized message type ${type}`);
94
+ this.emit("message", cbor);
114
95
  break;
115
96
  }
116
97
  }
@@ -0,0 +1,26 @@
1
+ import { type Message, type PeerId } from "@automerge/automerge-repo";
2
+ import { ProtocolVersion } from "./protocolVersion.js";
3
+ export type LeaveMessage = {
4
+ type: "leave";
5
+ senderId: PeerId;
6
+ };
7
+ export type JoinMessage = {
8
+ type: "join";
9
+ senderId: PeerId;
10
+ supportedProtocolVersions: ProtocolVersion[];
11
+ };
12
+ export type PeerMessage = {
13
+ type: "peer";
14
+ senderId: PeerId;
15
+ selectedProtocolVersion: ProtocolVersion;
16
+ targetId: PeerId;
17
+ };
18
+ export type ErrorMessage = {
19
+ type: "error";
20
+ senderId: PeerId;
21
+ message: string;
22
+ targetId: PeerId;
23
+ };
24
+ export type FromClientMessage = JoinMessage | LeaveMessage | Message;
25
+ export type FromServerMessage = PeerMessage | ErrorMessage | Message;
26
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,yBAAyB,EAAE,eAAe,EAAE,CAAA;CAC7C,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,uBAAuB,EAAE,eAAe,CAAA;IACxC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAGD,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AACpE,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-network-websocket",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "isomorphic node/browser Websocket network adapter for Automerge Repo",
5
5
  "peerDependencies": {
6
6
  "@automerge/automerge": "^2.1.0-alpha.10"
@@ -17,7 +17,7 @@
17
17
  "test": "mocha --no-warnings --experimental-specifier-resolution=node --exit"
18
18
  },
19
19
  "dependencies": {
20
- "@automerge/automerge-repo": "^1.0.0-alpha.2",
20
+ "@automerge/automerge-repo": "^1.0.0-alpha.3",
21
21
  "cbor-x": "^1.3.0",
22
22
  "eventemitter3": "^4.0.7",
23
23
  "isomorphic-ws": "^5.0.0",
@@ -34,5 +34,5 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "gitHead": "b5830dde8f135b694809698aaad2a9fdc79a9898"
37
+ "gitHead": "0ed108273084319aeea64ceccb49c3d58709f107"
38
38
  }
@@ -1,15 +1,17 @@
1
- import {
2
- ChannelId,
3
- InboundMessagePayload,
4
- NetworkAdapter,
5
- PeerId,
6
- } from "@automerge/automerge-repo"
1
+ import { NetworkAdapter, PeerId } from "@automerge/automerge-repo"
7
2
  import * as CBOR from "cbor-x"
8
3
  import WebSocket from "isomorphic-ws"
9
4
 
10
5
  import debug from "debug"
11
- import {ProtocolV1} from "./protocolVersion.js"
12
- import {InboundWebSocketMessage, OutboundWebSocketMessage} from "./message.js"
6
+
7
+ import {
8
+ FromClientMessage,
9
+ FromServerMessage,
10
+ JoinMessage,
11
+ } from "./messages.js"
12
+ import { ProtocolV1 } from "./protocolVersion.js"
13
+ import { isValidMessage } from "@automerge/automerge-repo/dist/network/messages.js"
14
+
13
15
  const log = debug("WebsocketClient")
14
16
 
15
17
  abstract class WebSocketNetworkAdapter extends NetworkAdapter {
@@ -17,9 +19,10 @@ abstract class WebSocketNetworkAdapter extends NetworkAdapter {
17
19
  }
18
20
 
19
21
  export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
20
- // Type trickery required for platform independence,
22
+ // Type trickery required for platform independence,
21
23
  // see https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window
22
24
  timerId?: ReturnType<typeof setTimeout>
25
+
23
26
  url: string
24
27
 
25
28
  constructor(url: string) {
@@ -62,9 +65,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
62
65
  throw new Error("WTF, get a socket")
63
66
  }
64
67
  if (this.socket.readyState === WebSocket.OPEN) {
65
- this.socket.send(
66
- CBOR.encode(joinMessage(this.peerId))
67
- )
68
+ this.send(joinMessage(this.peerId!))
68
69
  } else {
69
70
  this.socket.addEventListener(
70
71
  "open",
@@ -72,9 +73,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
72
73
  if (!this.socket) {
73
74
  throw new Error("WTF, get a socket")
74
75
  }
75
- this.socket.send(
76
- CBOR.encode(joinMessage(this.peerId))
77
- )
76
+ this.send(joinMessage(this.peerId!))
78
77
  },
79
78
  { once: true }
80
79
  )
@@ -85,43 +84,31 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
85
84
  if (!this.socket) {
86
85
  throw new Error("WTF, get a socket")
87
86
  }
88
- this.socket.send(CBOR.encode({ type: "leave", senderId: this.peerId }))
87
+ this.send({ type: "leave", senderId: this.peerId! })
89
88
  }
90
89
 
91
- sendMessage(
92
- targetId: PeerId,
93
- channelId: ChannelId,
94
- message: Uint8Array,
95
- broadcast: boolean
96
- ) {
97
- if (message.byteLength === 0) {
90
+ send(message: FromClientMessage) {
91
+ if ("data" in message && message.data.byteLength === 0) {
98
92
  throw new Error("tried to send a zero-length message")
99
93
  }
94
+
100
95
  if (!this.peerId) {
101
96
  throw new Error("Why don't we have a PeerID?")
102
97
  }
103
98
 
104
- const decoded: InboundMessagePayload = {
105
- senderId: this.peerId,
106
- targetId,
107
- channelId,
108
- type: "sync",
109
- message,
110
- broadcast,
99
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
100
+ throw new Error("Websocket Socket not ready!")
111
101
  }
112
102
 
113
- const encoded = CBOR.encode(decoded)
114
-
103
+ const encoded = CBOR.encode(message)
115
104
  // This incantation deals with websocket sending the whole
116
105
  // underlying buffer even if we just have a uint8array view on it
117
106
  const arrayBuf = encoded.buffer.slice(
118
107
  encoded.byteOffset,
119
108
  encoded.byteOffset + encoded.byteLength
120
109
  )
121
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
122
- throw new Error("Websocket Socket not ready!")
123
- }
124
- this.socket.send(arrayBuf)
110
+
111
+ this.socket?.send(arrayBuf)
125
112
  }
126
113
 
127
114
  announceConnection(peerId: PeerId) {
@@ -135,16 +122,9 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
135
122
  }
136
123
 
137
124
  receiveMessage(message: Uint8Array) {
138
- const decoded: OutboundWebSocketMessage = CBOR.decode(new Uint8Array(message))
125
+ const decoded: FromServerMessage = CBOR.decode(new Uint8Array(message))
139
126
 
140
- const {
141
- type,
142
- senderId,
143
- targetId,
144
- channelId,
145
- message: messageData,
146
- broadcast,
147
- } = decoded
127
+ const { type, senderId } = decoded
148
128
 
149
129
  const socket = this.socket
150
130
  if (!socket) {
@@ -157,24 +137,22 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
157
137
 
158
138
  switch (type) {
159
139
  case "peer":
160
- log(`peer: ${senderId}, ${channelId}`)
140
+ log(`peer: ${senderId}`)
161
141
  this.announceConnection(senderId)
162
142
  break
163
143
  case "error":
164
- log(`error: ${decoded.errorMessage}`)
144
+ log(`error: ${decoded.message}`)
145
+ break
165
146
  default:
166
- this.emit("message", {
167
- channelId,
168
- senderId,
169
- targetId,
170
- message: new Uint8Array(messageData),
171
- broadcast,
172
- })
147
+ if (!isValidMessage(decoded)) {
148
+ throw new Error("Invalid message received")
149
+ }
150
+ this.emit("message", decoded)
173
151
  }
174
152
  }
175
153
  }
176
154
 
177
- function joinMessage(senderId?: PeerId): Record<string, any> {
155
+ function joinMessage(senderId: PeerId): JoinMessage {
178
156
  return {
179
157
  type: "join",
180
158
  senderId,
@@ -4,14 +4,13 @@ import { WebSocket, type WebSocketServer } from "isomorphic-ws"
4
4
  import debug from "debug"
5
5
  const log = debug("WebsocketServer")
6
6
 
7
+ import { ProtocolV1, ProtocolVersion } from "./protocolVersion.js"
7
8
  import {
8
- ChannelId,
9
- InboundMessagePayload,
10
9
  NetworkAdapter,
11
- PeerId,
10
+ type NetworkAdapterMessage,
11
+ type PeerId,
12
12
  } from "@automerge/automerge-repo"
13
- import { ProtocolV1, ProtocolVersion } from "./protocolVersion.js"
14
- import { InboundWebSocketMessage } from "./message.js"
13
+ import { FromClientMessage, FromServerMessage } from "./messages.js"
15
14
 
16
15
  export class NodeWSServerAdapter extends NetworkAdapter {
17
16
  server: WebSocketServer
@@ -49,34 +48,21 @@ export class NodeWSServerAdapter extends NetworkAdapter {
49
48
  // throw new Error("The server doesn't join channels.")
50
49
  }
51
50
 
52
- sendMessage(
53
- targetId: PeerId,
54
- channelId: ChannelId,
55
- message: Uint8Array,
56
- broadcast: boolean
57
- ) {
58
- if (message.byteLength === 0) {
51
+ send(message: FromServerMessage) {
52
+ if ("data" in message && message.data.byteLength === 0) {
59
53
  throw new Error("tried to send a zero-length message")
60
54
  }
61
55
  const senderId = this.peerId
62
56
  if (!senderId) {
63
57
  throw new Error("No peerId set for the websocket server network adapter.")
64
58
  }
65
- if (this.sockets[targetId] === undefined) {
66
- log(`Tried to send message to disconnected peer: ${targetId}`)
67
- return
68
- }
69
59
 
70
- const decoded: InboundMessagePayload = {
71
- senderId,
72
- targetId,
73
- channelId,
74
- type: "sync",
75
- message,
76
- broadcast,
60
+ if (this.sockets[message.targetId] === undefined) {
61
+ log(`Tried to send message to disconnected peer: ${message.targetId}`)
62
+ return
77
63
  }
78
- const encoded = CBOR.encode(decoded)
79
64
 
65
+ const encoded = CBOR.encode(message)
80
66
  // This incantation deals with websocket sending the whole
81
67
  // underlying buffer even if we just have a uint8array view on it
82
68
  const arrayBuf = encoded.buffer.slice(
@@ -84,32 +70,22 @@ export class NodeWSServerAdapter extends NetworkAdapter {
84
70
  encoded.byteOffset + encoded.byteLength
85
71
  )
86
72
 
87
- log(
88
- `[${senderId}->${targetId}@${channelId}] "sync" | ${arrayBuf.byteLength} bytes`
89
- )
90
-
91
- this.sockets[targetId].send(arrayBuf)
73
+ this.sockets[message.targetId]?.send(arrayBuf)
92
74
  }
93
75
 
94
76
  receiveMessage(message: Uint8Array, socket: WebSocket) {
95
- const cbor: InboundWebSocketMessage = CBOR.decode(message)
96
-
97
- const {
98
- type,
99
- channelId,
100
- senderId,
101
- targetId,
102
- message: data,
103
- broadcast,
104
- supportedProtocolVersions,
105
- } = cbor
77
+ const cbor: FromClientMessage = CBOR.decode(message)
78
+
79
+ const { type, senderId } = cbor
106
80
 
107
81
  const myPeerId = this.peerId
108
82
  if (!myPeerId) {
109
83
  throw new Error("Missing my peer ID.")
110
84
  }
111
85
  log(
112
- `[${senderId}->${myPeerId}@${channelId}] ${type} | ${message.byteLength} bytes`
86
+ `[${senderId}->${myPeerId}${
87
+ "documentId" in cbor ? "@" + cbor.documentId : ""
88
+ }] ${type} | ${message.byteLength} bytes`
113
89
  )
114
90
  switch (type) {
115
91
  case "join":
@@ -120,25 +96,24 @@ export class NodeWSServerAdapter extends NetworkAdapter {
120
96
  // In this client-server connection, there's only ever one peer: us!
121
97
  // (and we pretend to be joined to every channel)
122
98
  const selectedProtocolVersion = selectProtocol(
123
- supportedProtocolVersions
99
+ cbor.supportedProtocolVersions
124
100
  )
125
101
  if (selectedProtocolVersion === null) {
126
- socket.send(
127
- CBOR.encode({
128
- type: "error",
129
- errorMessage: "unsupported protocol version",
130
- })
131
- )
102
+ this.send({
103
+ type: "error",
104
+ senderId: this.peerId!,
105
+ message: "unsupported protocol version",
106
+ targetId: senderId,
107
+ })
132
108
  this.sockets[senderId].close()
133
109
  delete this.sockets[senderId]
134
110
  } else {
135
- socket.send(
136
- CBOR.encode({
137
- type: "peer",
138
- senderId: this.peerId,
139
- selectedProtocolVersion: ProtocolV1,
140
- })
141
- )
111
+ this.send({
112
+ type: "peer",
113
+ senderId: this.peerId!,
114
+ selectedProtocolVersion: ProtocolV1,
115
+ targetId: senderId,
116
+ })
142
117
  }
143
118
  break
144
119
  case "leave":
@@ -148,21 +123,8 @@ export class NodeWSServerAdapter extends NetworkAdapter {
148
123
  // ?
149
124
  break
150
125
 
151
- // We accept both "message" and "sync" because a previous version of this
152
- // codebase sent sync messages in the BrowserWebSocketClientAdapter as
153
- // type "message" and we want to stay backwards compatible
154
- case "message":
155
- case "sync":
156
- this.emit("message", {
157
- senderId,
158
- targetId,
159
- channelId,
160
- message: new Uint8Array(data),
161
- broadcast,
162
- })
163
- break
164
126
  default:
165
- log(`unrecognized message type ${type}`)
127
+ this.emit("message", cbor)
166
128
  break
167
129
  }
168
130
  }
@@ -0,0 +1,31 @@
1
+ import { type Message, type PeerId } from "@automerge/automerge-repo"
2
+ import { ProtocolVersion } from "./protocolVersion.js"
3
+
4
+ export type LeaveMessage = {
5
+ type: "leave"
6
+ senderId: PeerId
7
+ }
8
+
9
+ export type JoinMessage = {
10
+ type: "join"
11
+ senderId: PeerId
12
+ supportedProtocolVersions: ProtocolVersion[]
13
+ }
14
+
15
+ export type PeerMessage = {
16
+ type: "peer"
17
+ senderId: PeerId
18
+ selectedProtocolVersion: ProtocolVersion
19
+ targetId: PeerId
20
+ }
21
+
22
+ export type ErrorMessage = {
23
+ type: "error"
24
+ senderId: PeerId
25
+ message: string
26
+ targetId: PeerId
27
+ }
28
+
29
+ // This adapter doesn't use NetworkAdapterMessage, it has its own idea of how to handle join/leave
30
+ export type FromClientMessage = JoinMessage | LeaveMessage | Message
31
+ export type FromServerMessage = PeerMessage | ErrorMessage | Message
@@ -3,17 +3,30 @@ import { BrowserWebSocketClientAdapter } from "../src/BrowserWebSocketClientAdap
3
3
  import { NodeWSServerAdapter } from "../src/NodeWSServerAdapter"
4
4
  import { startServer } from "./utilities/WebSockets"
5
5
  import * as CBOR from "cbor-x"
6
- import WebSocket, {AddressInfo} from "ws"
6
+ import WebSocket, { AddressInfo } from "ws"
7
7
  import { assert } from "chai"
8
- import {ChannelId, PeerId, Repo} from "@automerge/automerge-repo"
8
+ import { PeerId, Repo } from "@automerge/automerge-repo"
9
9
  import { once } from "events"
10
10
 
11
11
  describe("Websocket adapters", async () => {
12
12
  let port = 8080
13
13
 
14
14
  runAdapterTests(async () => {
15
- port += 1 // Increment port to avoid conflicts
16
- const { socket, server } = await startServer(port)
15
+ let socket: WebSocket.Server | undefined = undefined
16
+ let server: any
17
+
18
+ while (socket === undefined) {
19
+ try {
20
+ ; ({ socket, server } = await startServer(port))
21
+ } catch (e: any) {
22
+ if (e.code === "EADDRINUSE") {
23
+ port++
24
+ } else {
25
+ throw e
26
+ }
27
+ }
28
+ }
29
+
17
30
  const serverAdapter = new NodeWSServerAdapter(socket)
18
31
 
19
32
  const serverUrl = `ws://localhost:${port}`
@@ -25,19 +38,19 @@ describe("Websocket adapters", async () => {
25
38
  server.close()
26
39
  }
27
40
 
28
- return { adapters: [serverAdapter, aliceAdapter, bobAdapter], teardown }
41
+ return { adapters: [aliceAdapter, serverAdapter, bobAdapter], teardown }
29
42
  })
30
43
  })
31
44
 
32
45
  describe("The BrowserWebSocketClientAdapter", () => {
33
46
  it("should advertise the protocol versions it supports in its join message", async () => {
34
- const { socket, server } = await startServer(0)
47
+ const { socket, server } = await startServer(0)
35
48
  let port = (server.address()!! as AddressInfo).port
36
49
  const serverUrl = `ws://localhost:${port}`
37
50
  const helloPromise = firstMessage(socket)
38
51
 
39
52
  const client = new BrowserWebSocketClientAdapter(serverUrl)
40
- const repo = new Repo({network: [client], peerId: "browser" as PeerId})
53
+ const repo = new Repo({ network: [client], peerId: "browser" as PeerId })
41
54
 
42
55
  const hello = await helloPromise
43
56
 
@@ -45,8 +58,8 @@ describe("The BrowserWebSocketClientAdapter", () => {
45
58
  assert.deepEqual(message, {
46
59
  type: "join",
47
60
  senderId: "browser",
48
- supportedProtocolVersions: ["1"]
49
- })
61
+ supportedProtocolVersions: ["1"],
62
+ })
50
63
  })
51
64
  })
52
65
 
@@ -55,12 +68,13 @@ describe("The NodeWSServerAdapter", () => {
55
68
  const response = await serverHelloGivenClientHello({
56
69
  type: "join",
57
70
  senderId: "browser",
58
- supportedProtocolVersions: ["1"]
71
+ supportedProtocolVersions: ["1"],
59
72
  })
60
73
  assert.deepEqual<any>(response, {
61
74
  type: "peer",
62
75
  senderId: "server",
63
- selectedProtocolVersion: "1"
76
+ targetId: "browser",
77
+ selectedProtocolVersion: "1",
64
78
  })
65
79
  })
66
80
 
@@ -68,11 +82,13 @@ describe("The NodeWSServerAdapter", () => {
68
82
  const response = await serverHelloGivenClientHello({
69
83
  type: "join",
70
84
  senderId: "browser",
71
- supportedProtocolVersions: ["fake"]
85
+ supportedProtocolVersions: ["fake"],
72
86
  })
73
87
  assert.deepEqual<any>(response, {
74
88
  type: "error",
75
- errorMessage: "unsupported protocol version",
89
+ senderId: "server",
90
+ targetId: "browser",
91
+ message: "unsupported protocol version",
76
92
  })
77
93
  })
78
94
 
@@ -84,17 +100,20 @@ describe("The NodeWSServerAdapter", () => {
84
100
  assert.deepEqual<any>(response, {
85
101
  type: "peer",
86
102
  senderId: "server",
87
- selectedProtocolVersion: "1"
103
+ targetId: "browser",
104
+ selectedProtocolVersion: "1",
88
105
  })
89
106
  })
90
107
  })
91
108
 
92
- async function serverHelloGivenClientHello(clientHello: Object): Promise<Object | null> {
93
- const { socket, server } = await startServer(0)
109
+ async function serverHelloGivenClientHello(
110
+ clientHello: Object
111
+ ): Promise<Object | null> {
112
+ const { socket, server } = await startServer(0)
94
113
  let port = (server.address()!! as AddressInfo).port
95
114
  const serverUrl = `ws://localhost:${port}`
96
115
  const adapter = new NodeWSServerAdapter(socket)
97
- const repo = new Repo({network: [adapter], peerId: "server" as PeerId})
116
+ const repo = new Repo({ network: [adapter], peerId: "server" as PeerId })
98
117
 
99
118
  const clientSocket = new WebSocket(serverUrl)
100
119
  await once(clientSocket, "open")
@@ -107,9 +126,11 @@ async function serverHelloGivenClientHello(clientHello: Object): Promise<Object
107
126
  return message
108
127
  }
109
128
 
110
- async function firstMessage(socket: WebSocket.Server<any>): Promise<Object | null> {
129
+ async function firstMessage(
130
+ socket: WebSocket.Server<any>
131
+ ): Promise<Object | null> {
111
132
  return new Promise((resolve, reject) => {
112
- socket.once("connection", (ws) => {
133
+ socket.once("connection", ws => {
113
134
  ws.once("message", (message: any) => {
114
135
  resolve(message)
115
136
  })
@@ -117,7 +138,7 @@ async function firstMessage(socket: WebSocket.Server<any>): Promise<Object | nul
117
138
  reject(error)
118
139
  })
119
140
  })
120
- socket.once("error", (error) => {
141
+ socket.once("error", error => {
121
142
  reject(error)
122
143
  })
123
144
  })
package/dist/message.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import { type InboundMessagePayload } from "@automerge/automerge-repo";
2
- import { ProtocolVersion } from "./protocolVersion.js";
3
- export interface InboundWebSocketMessage extends InboundMessagePayload {
4
- supportedProtocolVersions?: ProtocolVersion[];
5
- }
6
- export interface OutboundWebSocketMessage extends InboundMessagePayload {
7
- errorMessage?: string;
8
- }
9
- //# sourceMappingURL=message.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../src/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,yBAAyB,CAAC,EAAE,eAAe,EAAE,CAAA;CAC9C;AAED,MAAM,WAAW,wBAAyB,SAAQ,qBAAqB;IACrE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB"}
package/src/message.ts DELETED
@@ -1,10 +0,0 @@
1
- import { type InboundMessagePayload } from "@automerge/automerge-repo"
2
- import { ProtocolVersion } from "./protocolVersion.js"
3
-
4
- export interface InboundWebSocketMessage extends InboundMessagePayload {
5
- supportedProtocolVersions?: ProtocolVersion[]
6
- }
7
-
8
- export interface OutboundWebSocketMessage extends InboundMessagePayload {
9
- errorMessage?: string
10
- }
File without changes