@automerge/automerge-repo-network-websocket 1.0.0-alpha.0 → 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,18 +1,18 @@
1
- /// <reference types="node" />
2
1
  /// <reference types="ws" />
3
- import { ChannelId, NetworkAdapter, PeerId } from "@automerge/automerge-repo";
2
+ import { NetworkAdapter, PeerId } from "@automerge/automerge-repo";
4
3
  import WebSocket from "isomorphic-ws";
4
+ import { FromClientMessage } from "./messages.js";
5
5
  declare abstract class WebSocketNetworkAdapter extends NetworkAdapter {
6
6
  socket?: WebSocket;
7
7
  }
8
8
  export declare class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
9
- timerId?: NodeJS.Timer;
9
+ timerId?: ReturnType<typeof setTimeout>;
10
10
  url: string;
11
11
  constructor(url: string);
12
12
  connect(peerId: PeerId): void;
13
13
  join(): void;
14
14
  leave(): void;
15
- sendMessage(targetId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
15
+ send(message: FromClientMessage): void;
16
16
  announceConnection(peerId: PeerId): void;
17
17
  receiveMessage(message: Uint8Array): void;
18
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;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,KAAK,CAAA;IACtB,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,13 +1,16 @@
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
- import { ProtocolV1 } from "./protocolVersion";
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 {
12
+ // Type trickery required for platform independence,
13
+ // see https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window
11
14
  timerId;
12
15
  url;
13
16
  constructor(url) {
@@ -42,14 +45,14 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
42
45
  throw new Error("WTF, get a socket");
43
46
  }
44
47
  if (this.socket.readyState === WebSocket.OPEN) {
45
- this.socket.send(CBOR.encode(joinMessage(this.peerId)));
48
+ this.send(joinMessage(this.peerId));
46
49
  }
47
50
  else {
48
51
  this.socket.addEventListener("open", () => {
49
52
  if (!this.socket) {
50
53
  throw new Error("WTF, get a socket");
51
54
  }
52
- this.socket.send(CBOR.encode(joinMessage(this.peerId)));
55
+ this.send(joinMessage(this.peerId));
53
56
  }, { once: true });
54
57
  }
55
58
  }
@@ -57,31 +60,23 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
57
60
  if (!this.socket) {
58
61
  throw new Error("WTF, get a socket");
59
62
  }
60
- this.socket.send(CBOR.encode({ type: "leave", senderId: this.peerId }));
63
+ this.send({ type: "leave", senderId: this.peerId });
61
64
  }
62
- sendMessage(targetId, channelId, message, broadcast) {
63
- if (message.byteLength === 0) {
65
+ send(message) {
66
+ if ("data" in message && message.data.byteLength === 0) {
64
67
  throw new Error("tried to send a zero-length message");
65
68
  }
66
69
  if (!this.peerId) {
67
70
  throw new Error("Why don't we have a PeerID?");
68
71
  }
69
- const decoded = {
70
- senderId: this.peerId,
71
- targetId,
72
- channelId,
73
- type: "sync",
74
- message,
75
- broadcast,
76
- };
77
- const encoded = CBOR.encode(decoded);
78
- // This incantation deals with websocket sending the whole
79
- // underlying buffer even if we just have a uint8array view on it
80
- const arrayBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
81
72
  if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
82
73
  throw new Error("Websocket Socket not ready!");
83
74
  }
84
- 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);
85
80
  }
86
81
  announceConnection(peerId) {
87
82
  // return a peer object
@@ -93,7 +88,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
93
88
  }
94
89
  receiveMessage(message) {
95
90
  const decoded = CBOR.decode(new Uint8Array(message));
96
- const { type, senderId, targetId, channelId, message: messageData, broadcast, } = decoded;
91
+ const { type, senderId } = decoded;
97
92
  const socket = this.socket;
98
93
  if (!socket) {
99
94
  throw new Error("Missing socket at receiveMessage");
@@ -103,19 +98,17 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
103
98
  }
104
99
  switch (type) {
105
100
  case "peer":
106
- log(`peer: ${senderId}, ${channelId}`);
101
+ log(`peer: ${senderId}`);
107
102
  this.announceConnection(senderId);
108
103
  break;
109
104
  case "error":
110
- log(`error: ${decoded.errorMessage}`);
105
+ log(`error: ${decoded.message}`);
106
+ break;
111
107
  default:
112
- this.emit("message", {
113
- channelId,
114
- senderId,
115
- targetId,
116
- message: new Uint8Array(messageData),
117
- broadcast,
118
- });
108
+ if (!isValidMessage(decoded)) {
109
+ throw new Error("Invalid message received");
110
+ }
111
+ this.emit("message", decoded);
119
112
  }
120
113
  }
121
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;CAoEtD"}
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 { ProtocolV1 } from "./protocolVersion.js";
4
5
  import { NetworkAdapter, } from "@automerge/automerge-repo";
5
- import { ProtocolV1 } from "./protocolVersion";
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,18 +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({ type: "error", errorMessage: "unsupported protocol version" }));
69
+ this.send({
70
+ type: "error",
71
+ senderId: this.peerId,
72
+ message: "unsupported protocol version",
73
+ targetId: senderId,
74
+ });
79
75
  this.sockets[senderId].close();
80
76
  delete this.sockets[senderId];
81
77
  }
82
78
  else {
83
- socket.send(CBOR.encode({
79
+ this.send({
84
80
  type: "peer",
85
81
  senderId: this.peerId,
86
82
  selectedProtocolVersion: ProtocolV1,
87
- }));
83
+ targetId: senderId,
84
+ });
88
85
  }
89
86
  break;
90
87
  case "leave":
@@ -93,21 +90,8 @@ export class NodeWSServerAdapter extends NetworkAdapter {
93
90
  // TODO: confirm this
94
91
  // ?
95
92
  break;
96
- // We accept both "message" and "sync" because a previous version of this
97
- // codebase sent sync messages in the BrowserWebSocketClientAdapter as
98
- // type "message" and we want to stay backwards compatible
99
- case "message":
100
- case "sync":
101
- this.emit("message", {
102
- senderId,
103
- targetId,
104
- channelId,
105
- message: new Uint8Array(data),
106
- broadcast,
107
- });
108
- break;
109
93
  default:
110
- log(`unrecognized message type ${type}`);
94
+ this.emit("message", cbor);
111
95
  break;
112
96
  }
113
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,9 +1,9 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-network-websocket",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "isomorphic node/browser Websocket network adapter for Automerge Repo",
5
5
  "peerDependencies": {
6
- "@automerge/automerge": "^2.1.0-alpha.9"
6
+ "@automerge/automerge": "^2.1.0-alpha.10"
7
7
  },
8
8
  "repository": "https://github.com/automerge/automerge-repo",
9
9
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -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.0",
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": "38c0c32796ddca5f86a2e55ab0f1202a2ce107c8"
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"
12
- import {InboundWebSocketMessage, OutboundWebSocketMessage} from "./message"
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,7 +19,10 @@ abstract class WebSocketNetworkAdapter extends NetworkAdapter {
17
19
  }
18
20
 
19
21
  export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
20
- timerId?: NodeJS.Timer
22
+ // Type trickery required for platform independence,
23
+ // see https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window
24
+ timerId?: ReturnType<typeof setTimeout>
25
+
21
26
  url: string
22
27
 
23
28
  constructor(url: string) {
@@ -60,9 +65,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
60
65
  throw new Error("WTF, get a socket")
61
66
  }
62
67
  if (this.socket.readyState === WebSocket.OPEN) {
63
- this.socket.send(
64
- CBOR.encode(joinMessage(this.peerId))
65
- )
68
+ this.send(joinMessage(this.peerId!))
66
69
  } else {
67
70
  this.socket.addEventListener(
68
71
  "open",
@@ -70,9 +73,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
70
73
  if (!this.socket) {
71
74
  throw new Error("WTF, get a socket")
72
75
  }
73
- this.socket.send(
74
- CBOR.encode(joinMessage(this.peerId))
75
- )
76
+ this.send(joinMessage(this.peerId!))
76
77
  },
77
78
  { once: true }
78
79
  )
@@ -83,43 +84,31 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
83
84
  if (!this.socket) {
84
85
  throw new Error("WTF, get a socket")
85
86
  }
86
- this.socket.send(CBOR.encode({ type: "leave", senderId: this.peerId }))
87
+ this.send({ type: "leave", senderId: this.peerId! })
87
88
  }
88
89
 
89
- sendMessage(
90
- targetId: PeerId,
91
- channelId: ChannelId,
92
- message: Uint8Array,
93
- broadcast: boolean
94
- ) {
95
- if (message.byteLength === 0) {
90
+ send(message: FromClientMessage) {
91
+ if ("data" in message && message.data.byteLength === 0) {
96
92
  throw new Error("tried to send a zero-length message")
97
93
  }
94
+
98
95
  if (!this.peerId) {
99
96
  throw new Error("Why don't we have a PeerID?")
100
97
  }
101
98
 
102
- const decoded: InboundMessagePayload = {
103
- senderId: this.peerId,
104
- targetId,
105
- channelId,
106
- type: "sync",
107
- message,
108
- broadcast,
99
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
100
+ throw new Error("Websocket Socket not ready!")
109
101
  }
110
102
 
111
- const encoded = CBOR.encode(decoded)
112
-
103
+ const encoded = CBOR.encode(message)
113
104
  // This incantation deals with websocket sending the whole
114
105
  // underlying buffer even if we just have a uint8array view on it
115
106
  const arrayBuf = encoded.buffer.slice(
116
107
  encoded.byteOffset,
117
108
  encoded.byteOffset + encoded.byteLength
118
109
  )
119
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
120
- throw new Error("Websocket Socket not ready!")
121
- }
122
- this.socket.send(arrayBuf)
110
+
111
+ this.socket?.send(arrayBuf)
123
112
  }
124
113
 
125
114
  announceConnection(peerId: PeerId) {
@@ -133,16 +122,9 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
133
122
  }
134
123
 
135
124
  receiveMessage(message: Uint8Array) {
136
- const decoded: OutboundWebSocketMessage = CBOR.decode(new Uint8Array(message))
125
+ const decoded: FromServerMessage = CBOR.decode(new Uint8Array(message))
137
126
 
138
- const {
139
- type,
140
- senderId,
141
- targetId,
142
- channelId,
143
- message: messageData,
144
- broadcast,
145
- } = decoded
127
+ const { type, senderId } = decoded
146
128
 
147
129
  const socket = this.socket
148
130
  if (!socket) {
@@ -155,24 +137,22 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
155
137
 
156
138
  switch (type) {
157
139
  case "peer":
158
- log(`peer: ${senderId}, ${channelId}`)
140
+ log(`peer: ${senderId}`)
159
141
  this.announceConnection(senderId)
160
142
  break
161
143
  case "error":
162
- log(`error: ${decoded.errorMessage}`)
144
+ log(`error: ${decoded.message}`)
145
+ break
163
146
  default:
164
- this.emit("message", {
165
- channelId,
166
- senderId,
167
- targetId,
168
- message: new Uint8Array(messageData),
169
- broadcast,
170
- })
147
+ if (!isValidMessage(decoded)) {
148
+ throw new Error("Invalid message received")
149
+ }
150
+ this.emit("message", decoded)
171
151
  }
172
152
  }
173
153
  }
174
154
 
175
- function joinMessage(senderId?: PeerId): Record<string, any> {
155
+ function joinMessage(senderId: PeerId): JoinMessage {
176
156
  return {
177
157
  type: "join",
178
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"
14
- import {InboundWebSocketMessage} from "./message"
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":
@@ -119,19 +95,25 @@ export class NodeWSServerAdapter extends NetworkAdapter {
119
95
 
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
- const selectedProtocolVersion = selectProtocol(supportedProtocolVersions)
98
+ const selectedProtocolVersion = selectProtocol(
99
+ cbor.supportedProtocolVersions
100
+ )
123
101
  if (selectedProtocolVersion === null) {
124
- socket.send(CBOR.encode({ type: "error", errorMessage: "unsupported protocol version"}))
102
+ this.send({
103
+ type: "error",
104
+ senderId: this.peerId!,
105
+ message: "unsupported protocol version",
106
+ targetId: senderId,
107
+ })
125
108
  this.sockets[senderId].close()
126
109
  delete this.sockets[senderId]
127
110
  } else {
128
- socket.send(
129
- CBOR.encode({
130
- type: "peer",
131
- senderId: this.peerId,
132
- selectedProtocolVersion: ProtocolV1,
133
- })
134
- )
111
+ this.send({
112
+ type: "peer",
113
+ senderId: this.peerId!,
114
+ selectedProtocolVersion: ProtocolV1,
115
+ targetId: senderId,
116
+ })
135
117
  }
136
118
  break
137
119
  case "leave":
@@ -141,21 +123,8 @@ export class NodeWSServerAdapter extends NetworkAdapter {
141
123
  // ?
142
124
  break
143
125
 
144
- // We accept both "message" and "sync" because a previous version of this
145
- // codebase sent sync messages in the BrowserWebSocketClientAdapter as
146
- // type "message" and we want to stay backwards compatible
147
- case "message":
148
- case "sync":
149
- this.emit("message", {
150
- senderId,
151
- targetId,
152
- channelId,
153
- message: new Uint8Array(data),
154
- broadcast,
155
- })
156
- break
157
126
  default:
158
- log(`unrecognized message type ${type}`)
127
+ this.emit("message", cbor)
159
128
  break
160
129
  }
161
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";
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,EAAC,KAAK,qBAAqB,EAAC,MAAM,2BAA2B,CAAA;AACpE,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AAEjD,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"
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