@automerge/automerge-repo-network-websocket 1.0.19 → 1.1.0-alpha.2

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,5 +1,5 @@
1
1
  /// <reference types="ws" />
2
- import { NetworkAdapter, PeerId } from "@automerge/automerge-repo";
2
+ import { NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo";
3
3
  import WebSocket from "isomorphic-ws";
4
4
  import { FromClientMessage } from "./messages.js";
5
5
  declare abstract class WebSocketNetworkAdapter extends NetworkAdapter {
@@ -11,14 +11,14 @@ export declare class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapt
11
11
  remotePeerId?: PeerId;
12
12
  url: string;
13
13
  constructor(url: string);
14
- connect(peerId: PeerId): void;
14
+ connect(peerId: PeerId, peerMetadata: PeerMetadata): void;
15
15
  onOpen: () => void;
16
16
  onClose: () => void;
17
17
  onMessage: (event: WebSocket.MessageEvent) => void;
18
18
  join(): void;
19
19
  disconnect(): void;
20
20
  send(message: FromClientMessage): void;
21
- announceConnection(peerId: PeerId): void;
21
+ announceConnection(peerId: PeerId, peerMetadata: PeerMetadata): void;
22
22
  receiveMessage(message: Uint8Array): void;
23
23
  }
24
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,EAAQ,MAAM,2BAA2B,CAAA;AACxE,OAAO,SAAS,MAAM,eAAe,CAAA;AAIrC,OAAO,EACL,iBAAiB,EAGlB,MAAM,eAAe,CAAA;AAKtB,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,YAAY,CAAC,EAAE,MAAM,CAAA;IAGrB,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM;IAkCtB,MAAM,aAKL;IAGD,OAAO,aAYN;IAED,SAAS,UAAW,sBAAsB,UAEzC;IAED,IAAI;IAWJ,UAAU;IAOV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAwB/B,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAcjC,cAAc,CAAC,OAAO,EAAE,UAAU;CA0BnC"}
1
+ {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EAElB,MAAM,2BAA2B,CAAA;AAClC,OAAO,SAAS,MAAM,eAAe,CAAA;AAIrC,OAAO,EACL,iBAAiB,EAIlB,MAAM,eAAe,CAAA;AAKtB,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,YAAY,CAAC,EAAE,MAAM,CAAA;IAGrB,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;IAmClD,MAAM,aAKL;IAGD,OAAO,aAYN;IAED,SAAS,UAAW,sBAAsB,UAEzC;IAED,IAAI;IAWJ,UAAU;IAOV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAwB/B,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;IAc7D,cAAc,CAAC,OAAO,EAAE,UAAU;CA4BnC"}
@@ -1,4 +1,4 @@
1
- import { NetworkAdapter, cbor } from "@automerge/automerge-repo";
1
+ import { NetworkAdapter, cbor, } from "@automerge/automerge-repo";
2
2
  import WebSocket from "isomorphic-ws";
3
3
  import debug from "debug";
4
4
  import { ProtocolV1 } from "./protocolVersion.js";
@@ -17,7 +17,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
17
17
  super();
18
18
  this.url = url;
19
19
  }
20
- connect(peerId) {
20
+ connect(peerId, peerMetadata) {
21
21
  // If we're reconnecting make sure we remove the old event listeners
22
22
  // before creating a new connection.
23
23
  if (this.socket) {
@@ -26,9 +26,10 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
26
26
  this.socket.removeEventListener("message", this.onMessage);
27
27
  }
28
28
  if (!this.timerId) {
29
- this.timerId = setInterval(() => this.connect(peerId), 5000);
29
+ this.timerId = setInterval(() => this.connect(peerId, peerMetadata), 5000);
30
30
  }
31
31
  this.peerId = peerId;
32
+ this.peerMetadata = peerMetadata;
32
33
  this.socket = new WebSocket(this.url);
33
34
  this.socket.binaryType = "arraybuffer";
34
35
  this.socket.addEventListener("open", this.onOpen);
@@ -49,7 +50,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
49
50
  log(`@ ${this.url}: open`);
50
51
  clearInterval(this.timerId);
51
52
  this.timerId = undefined;
52
- this.send(joinMessage(this.peerId));
53
+ this.send(joinMessage(this.peerId, this.peerMetadata));
53
54
  };
54
55
  // When a socket closes, or disconnects, remove it from the array.
55
56
  onClose = () => {
@@ -59,7 +60,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
59
60
  }
60
61
  if (!this.timerId) {
61
62
  if (this.peerId) {
62
- this.connect(this.peerId);
63
+ this.connect(this.peerId, this.peerMetadata);
63
64
  }
64
65
  }
65
66
  };
@@ -71,7 +72,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
71
72
  throw new Error("WTF, get a socket");
72
73
  }
73
74
  if (this.socket.readyState === WebSocket.OPEN) {
74
- this.send(joinMessage(this.peerId));
75
+ this.send(joinMessage(this.peerId, this.peerMetadata));
75
76
  }
76
77
  else {
77
78
  // The onOpen handler automatically sends a join message
@@ -99,7 +100,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
99
100
  const arrayBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
100
101
  this.socket?.send(arrayBuf);
101
102
  }
102
- announceConnection(peerId) {
103
+ announceConnection(peerId, peerMetadata) {
103
104
  // return a peer object
104
105
  const myPeerId = this.peerId;
105
106
  if (!myPeerId) {
@@ -110,7 +111,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
110
111
  this.emit("ready", { network: this });
111
112
  }
112
113
  this.remotePeerId = peerId;
113
- this.emit("peer-candidate", { peerId });
114
+ this.emit("peer-candidate", { peerId, peerMetadata });
114
115
  }
115
116
  receiveMessage(message) {
116
117
  const decoded = cbor.decode(new Uint8Array(message));
@@ -123,10 +124,12 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
123
124
  throw new Error("received a zero-length message");
124
125
  }
125
126
  switch (type) {
126
- case "peer":
127
+ case "peer": {
128
+ const { peerMetadata } = decoded;
127
129
  log(`peer: ${senderId}`);
128
- this.announceConnection(senderId);
130
+ this.announceConnection(senderId, peerMetadata);
129
131
  break;
132
+ }
130
133
  case "error":
131
134
  log(`error: ${decoded.message}`);
132
135
  break;
@@ -135,10 +138,11 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
135
138
  }
136
139
  }
137
140
  }
138
- function joinMessage(senderId) {
141
+ function joinMessage(senderId, peerMetadata) {
139
142
  return {
140
143
  type: "join",
141
144
  senderId,
145
+ peerMetadata,
142
146
  supportedProtocolVersions: [ProtocolV1],
143
147
  };
144
148
  }
@@ -1,7 +1,7 @@
1
1
  /// <reference types="ws" />
2
2
  import WebSocket from "isomorphic-ws";
3
3
  import { type WebSocketServer } from "isomorphic-ws";
4
- import { NetworkAdapter, type PeerId } from "@automerge/automerge-repo";
4
+ import { NetworkAdapter, type PeerMetadata, type PeerId } from "@automerge/automerge-repo";
5
5
  import { FromServerMessage } from "./messages.js";
6
6
  export declare class NodeWSServerAdapter extends NetworkAdapter {
7
7
  server: WebSocketServer;
@@ -9,7 +9,7 @@ export declare class NodeWSServerAdapter extends NetworkAdapter {
9
9
  [peerId: PeerId]: WebSocket;
10
10
  };
11
11
  constructor(server: WebSocketServer);
12
- connect(peerId: PeerId): void;
12
+ connect(peerId: PeerId, peerMetadata: PeerMetadata): void;
13
13
  disconnect(): void;
14
14
  send(message: FromServerMessage): void;
15
15
  receiveMessage(message: Uint8Array, socket: WebSocket): void;
@@ -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,MAAM,EACZ,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAqB,iBAAiB,EAAE,MAAM,eAAe,CAAA;AASpE,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;IAoDtB,UAAU;IAIV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAyB/B,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CA+DtD"}
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,2BAA2B,CAAA;AAClC,OAAO,EAAqB,iBAAiB,EAAE,MAAM,eAAe,CAAA;AASpE,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,EAAE,YAAY,EAAE,YAAY;IAqDlD,UAAU;IAIV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAyB/B,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CAsEtD"}
@@ -11,8 +11,9 @@ export class NodeWSServerAdapter extends NetworkAdapter {
11
11
  super();
12
12
  this.server = server;
13
13
  }
14
- connect(peerId) {
14
+ connect(peerId, peerMetadata) {
15
15
  this.peerId = peerId;
16
+ this.peerMetadata = peerMetadata;
16
17
  this.server.on("close", function close() {
17
18
  clearInterval(interval);
18
19
  });
@@ -84,36 +85,43 @@ export class NodeWSServerAdapter extends NetworkAdapter {
84
85
  log(`[${senderId}->${myPeerId}${"documentId" in cbor ? "@" + cbor.documentId : ""}] ${type} | ${message.byteLength} bytes`);
85
86
  switch (type) {
86
87
  case "join":
87
- const existingSocket = this.sockets[senderId];
88
- if (existingSocket) {
89
- if (existingSocket.readyState === WebSocket.OPEN) {
90
- existingSocket.close();
88
+ {
89
+ const existingSocket = this.sockets[senderId];
90
+ if (existingSocket) {
91
+ if (existingSocket.readyState === WebSocket.OPEN) {
92
+ existingSocket.close();
93
+ }
94
+ this.emit("peer-disconnected", { peerId: senderId });
91
95
  }
92
- this.emit("peer-disconnected", { peerId: senderId });
93
- }
94
- // Let the rest of the system know that we have a new connection.
95
- this.emit("peer-candidate", { peerId: senderId });
96
- this.sockets[senderId] = socket;
97
- // In this client-server connection, there's only ever one peer: us!
98
- // (and we pretend to be joined to every channel)
99
- const selectedProtocolVersion = selectProtocol(cbor.supportedProtocolVersions);
100
- if (selectedProtocolVersion === null) {
101
- this.send({
102
- type: "error",
103
- senderId: this.peerId,
104
- message: "unsupported protocol version",
105
- targetId: senderId,
106
- });
107
- this.sockets[senderId].close();
108
- delete this.sockets[senderId];
109
- }
110
- else {
111
- this.send({
112
- type: "peer",
113
- senderId: this.peerId,
114
- selectedProtocolVersion: ProtocolV1,
115
- targetId: senderId,
96
+ const { peerMetadata } = cbor;
97
+ // Let the rest of the system know that we have a new connection.
98
+ this.emit("peer-candidate", {
99
+ peerId: senderId,
100
+ peerMetadata,
116
101
  });
102
+ this.sockets[senderId] = socket;
103
+ // In this client-server connection, there's only ever one peer: us!
104
+ // (and we pretend to be joined to every channel)
105
+ const selectedProtocolVersion = selectProtocol(cbor.supportedProtocolVersions);
106
+ if (selectedProtocolVersion === null) {
107
+ this.send({
108
+ type: "error",
109
+ senderId: this.peerId,
110
+ message: "unsupported protocol version",
111
+ targetId: senderId,
112
+ });
113
+ this.sockets[senderId].close();
114
+ delete this.sockets[senderId];
115
+ }
116
+ else {
117
+ this.send({
118
+ type: "peer",
119
+ senderId: this.peerId,
120
+ peerMetadata: this.peerMetadata,
121
+ selectedProtocolVersion: ProtocolV1,
122
+ targetId: senderId,
123
+ });
124
+ }
117
125
  }
118
126
  break;
119
127
  case "leave":
@@ -1,4 +1,4 @@
1
- import type { Message, PeerId } from "@automerge/automerge-repo";
1
+ import type { Message, PeerId, PeerMetadata } from "@automerge/automerge-repo";
2
2
  import type { ProtocolVersion } from "./protocolVersion.js";
3
3
  /** The sender is disconnecting */
4
4
  export type LeaveMessage = {
@@ -10,6 +10,8 @@ export type JoinMessage = {
10
10
  type: "join";
11
11
  /** The PeerID of the client */
12
12
  senderId: PeerId;
13
+ /** Metadata presented by the peer */
14
+ peerMetadata: PeerMetadata;
13
15
  /** The protocol version the client supports */
14
16
  supportedProtocolVersions: ProtocolVersion[];
15
17
  };
@@ -18,6 +20,8 @@ export type PeerMessage = {
18
20
  type: "peer";
19
21
  /** The PeerID of the server */
20
22
  senderId: PeerId;
23
+ /** Metadata presented by the peer */
24
+ peerMetadata: PeerMetadata;
21
25
  /** The protocol version the server selected for this connection */
22
26
  selectedProtocolVersion: ProtocolVersion;
23
27
  /** The PeerID of the client */
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAE3D,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,+CAA+C;IAC/C,yBAAyB,EAAE,eAAe,EAAE,CAAA;CAC7C,CAAA;AAED,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,uBAAuB,EAAE,eAAe,CAAA;IACxC,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,gGAAgG;AAChG,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAKD,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AAEpE,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAE3D,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAEhB,sCAAsC;IACtC,YAAY,EAAE,YAAY,CAAA;IAE1B,+CAA+C;IAC/C,yBAAyB,EAAE,eAAe,EAAE,CAAA;CAC7C,CAAA;AAED,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAEhB,sCAAsC;IACtC,YAAY,EAAE,YAAY,CAAA;IAE1B,mEAAmE;IACnE,uBAAuB,EAAE,eAAe,CAAA;IACxC,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,gGAAgG;AAChG,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAKD,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AAEpE,8CAA8C;AAC9C,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.19",
3
+ "version": "1.1.0-alpha.2",
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": "^1.0.19",
16
+ "@automerge/automerge-repo": "^1.1.0-alpha.2",
17
17
  "cbor-x": "^1.3.0",
18
18
  "eventemitter3": "^5.0.1",
19
19
  "isomorphic-ws": "^5.0.0",
@@ -30,5 +30,5 @@
30
30
  "publishConfig": {
31
31
  "access": "public"
32
32
  },
33
- "gitHead": "7d28ca50dfa437ac6f7b1722b89b3f6844b90de7"
33
+ "gitHead": "d73e71588c3835a172fdf4d19e56a1f946c041ed"
34
34
  }
@@ -1,4 +1,9 @@
1
- import { NetworkAdapter, PeerId, cbor } from "@automerge/automerge-repo"
1
+ import {
2
+ NetworkAdapter,
3
+ type PeerId,
4
+ type PeerMetadata,
5
+ cbor,
6
+ } from "@automerge/automerge-repo"
2
7
  import WebSocket from "isomorphic-ws"
3
8
 
4
9
  import debug from "debug"
@@ -7,6 +12,7 @@ import {
7
12
  FromClientMessage,
8
13
  FromServerMessage,
9
14
  JoinMessage,
15
+ PeerMessage,
10
16
  } from "./messages.js"
11
17
  import { ProtocolV1 } from "./protocolVersion.js"
12
18
 
@@ -30,7 +36,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
30
36
  this.url = url
31
37
  }
32
38
 
33
- connect(peerId: PeerId) {
39
+ connect(peerId: PeerId, peerMetadata: PeerMetadata) {
34
40
  // If we're reconnecting make sure we remove the old event listeners
35
41
  // before creating a new connection.
36
42
  if (this.socket) {
@@ -40,10 +46,11 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
40
46
  }
41
47
 
42
48
  if (!this.timerId) {
43
- this.timerId = setInterval(() => this.connect(peerId), 5000)
49
+ this.timerId = setInterval(() => this.connect(peerId, peerMetadata), 5000)
44
50
  }
45
51
 
46
52
  this.peerId = peerId
53
+ this.peerMetadata = peerMetadata
47
54
  this.socket = new WebSocket(this.url)
48
55
  this.socket.binaryType = "arraybuffer"
49
56
 
@@ -68,7 +75,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
68
75
  log(`@ ${this.url}: open`)
69
76
  clearInterval(this.timerId)
70
77
  this.timerId = undefined
71
- this.send(joinMessage(this.peerId!))
78
+ this.send(joinMessage(this.peerId!, this.peerMetadata!))
72
79
  }
73
80
 
74
81
  // When a socket closes, or disconnects, remove it from the array.
@@ -81,7 +88,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
81
88
 
82
89
  if (!this.timerId) {
83
90
  if (this.peerId) {
84
- this.connect(this.peerId)
91
+ this.connect(this.peerId, this.peerMetadata!)
85
92
  }
86
93
  }
87
94
  }
@@ -95,7 +102,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
95
102
  throw new Error("WTF, get a socket")
96
103
  }
97
104
  if (this.socket.readyState === WebSocket.OPEN) {
98
- this.send(joinMessage(this.peerId!))
105
+ this.send(joinMessage(this.peerId!, this.peerMetadata!))
99
106
  } else {
100
107
  // The onOpen handler automatically sends a join message
101
108
  }
@@ -132,7 +139,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
132
139
  this.socket?.send(arrayBuf)
133
140
  }
134
141
 
135
- announceConnection(peerId: PeerId) {
142
+ announceConnection(peerId: PeerId, peerMetadata: PeerMetadata) {
136
143
  // return a peer object
137
144
  const myPeerId = this.peerId
138
145
  if (!myPeerId) {
@@ -143,7 +150,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
143
150
  this.emit("ready", { network: this })
144
151
  }
145
152
  this.remotePeerId = peerId
146
- this.emit("peer-candidate", { peerId })
153
+ this.emit("peer-candidate", { peerId, peerMetadata })
147
154
  }
148
155
 
149
156
  receiveMessage(message: Uint8Array) {
@@ -161,10 +168,12 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
161
168
  }
162
169
 
163
170
  switch (type) {
164
- case "peer":
171
+ case "peer": {
172
+ const { peerMetadata } = decoded
165
173
  log(`peer: ${senderId}`)
166
- this.announceConnection(senderId)
174
+ this.announceConnection(senderId, peerMetadata)
167
175
  break
176
+ }
168
177
  case "error":
169
178
  log(`error: ${decoded.message}`)
170
179
  break
@@ -174,10 +183,14 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
174
183
  }
175
184
  }
176
185
 
177
- function joinMessage(senderId: PeerId): JoinMessage {
186
+ function joinMessage(
187
+ senderId: PeerId,
188
+ peerMetadata: PeerMetadata
189
+ ): JoinMessage {
178
190
  return {
179
191
  type: "join",
180
192
  senderId,
193
+ peerMetadata,
181
194
  supportedProtocolVersions: [ProtocolV1],
182
195
  }
183
196
  }
@@ -7,6 +7,7 @@ const log = debug("WebsocketServer")
7
7
  import {
8
8
  cbor as cborHelpers,
9
9
  NetworkAdapter,
10
+ type PeerMetadata,
10
11
  type PeerId,
11
12
  } from "@automerge/automerge-repo"
12
13
  import { FromClientMessage, FromServerMessage } from "./messages.js"
@@ -27,8 +28,9 @@ export class NodeWSServerAdapter extends NetworkAdapter {
27
28
  this.server = server
28
29
  }
29
30
 
30
- connect(peerId: PeerId) {
31
+ connect(peerId: PeerId, peerMetadata: PeerMetadata) {
31
32
  this.peerId = peerId
33
+ this.peerMetadata = peerMetadata
32
34
 
33
35
  this.server.on("close", function close() {
34
36
  clearInterval(interval)
@@ -124,39 +126,46 @@ export class NodeWSServerAdapter extends NetworkAdapter {
124
126
  )
125
127
  switch (type) {
126
128
  case "join":
127
- const existingSocket = this.sockets[senderId]
128
- if (existingSocket) {
129
- if (existingSocket.readyState === WebSocket.OPEN) {
130
- existingSocket.close()
129
+ {
130
+ const existingSocket = this.sockets[senderId]
131
+ if (existingSocket) {
132
+ if (existingSocket.readyState === WebSocket.OPEN) {
133
+ existingSocket.close()
134
+ }
135
+ this.emit("peer-disconnected", { peerId: senderId })
131
136
  }
132
- this.emit("peer-disconnected", {peerId: senderId})
133
- }
134
137
 
135
- // Let the rest of the system know that we have a new connection.
136
- this.emit("peer-candidate", { peerId: senderId })
137
- this.sockets[senderId] = socket
138
-
139
- // In this client-server connection, there's only ever one peer: us!
140
- // (and we pretend to be joined to every channel)
141
- const selectedProtocolVersion = selectProtocol(
142
- cbor.supportedProtocolVersions
143
- )
144
- if (selectedProtocolVersion === null) {
145
- this.send({
146
- type: "error",
147
- senderId: this.peerId!,
148
- message: "unsupported protocol version",
149
- targetId: senderId,
150
- })
151
- this.sockets[senderId].close()
152
- delete this.sockets[senderId]
153
- } else {
154
- this.send({
155
- type: "peer",
156
- senderId: this.peerId!,
157
- selectedProtocolVersion: ProtocolV1,
158
- targetId: senderId,
138
+ const { peerMetadata } = cbor
139
+ // Let the rest of the system know that we have a new connection.
140
+ this.emit("peer-candidate", {
141
+ peerId: senderId,
142
+ peerMetadata,
159
143
  })
144
+ this.sockets[senderId] = socket
145
+
146
+ // In this client-server connection, there's only ever one peer: us!
147
+ // (and we pretend to be joined to every channel)
148
+ const selectedProtocolVersion = selectProtocol(
149
+ cbor.supportedProtocolVersions
150
+ )
151
+ if (selectedProtocolVersion === null) {
152
+ this.send({
153
+ type: "error",
154
+ senderId: this.peerId!,
155
+ message: "unsupported protocol version",
156
+ targetId: senderId,
157
+ })
158
+ this.sockets[senderId].close()
159
+ delete this.sockets[senderId]
160
+ } else {
161
+ this.send({
162
+ type: "peer",
163
+ senderId: this.peerId!,
164
+ peerMetadata: this.peerMetadata!,
165
+ selectedProtocolVersion: ProtocolV1,
166
+ targetId: senderId,
167
+ })
168
+ }
160
169
  }
161
170
  break
162
171
  case "leave":
package/src/messages.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Message, PeerId } from "@automerge/automerge-repo"
1
+ import type { Message, PeerId, PeerMetadata } from "@automerge/automerge-repo"
2
2
  import type { ProtocolVersion } from "./protocolVersion.js"
3
3
 
4
4
  /** The sender is disconnecting */
@@ -12,6 +12,10 @@ export type JoinMessage = {
12
12
  type: "join"
13
13
  /** The PeerID of the client */
14
14
  senderId: PeerId
15
+
16
+ /** Metadata presented by the peer */
17
+ peerMetadata: PeerMetadata
18
+
15
19
  /** The protocol version the client supports */
16
20
  supportedProtocolVersions: ProtocolVersion[]
17
21
  }
@@ -21,6 +25,10 @@ export type PeerMessage = {
21
25
  type: "peer"
22
26
  /** The PeerID of the server */
23
27
  senderId: PeerId
28
+
29
+ /** Metadata presented by the peer */
30
+ peerMetadata: PeerMetadata
31
+
24
32
  /** The protocol version the server selected for this connection */
25
33
  selectedProtocolVersion: ProtocolVersion
26
34
  /** The PeerID of the client */
@@ -1,5 +1,12 @@
1
1
  import { next as A } from "@automerge/automerge"
2
- import { AutomergeUrl, DocumentId, PeerId, Repo, SyncMessage, parseAutomergeUrl } from "@automerge/automerge-repo"
2
+ import {
3
+ AutomergeUrl,
4
+ DocumentId,
5
+ PeerId,
6
+ Repo,
7
+ SyncMessage,
8
+ parseAutomergeUrl,
9
+ } from "@automerge/automerge-repo"
3
10
  import assert from "assert"
4
11
  import * as CBOR from "cbor-x"
5
12
  import { once } from "events"
@@ -10,7 +17,7 @@ import { runAdapterTests } from "../../automerge-repo/src/helpers/tests/network-
10
17
  import { DummyStorageAdapter } from "../../automerge-repo/test/helpers/DummyStorageAdapter.js"
11
18
  import { BrowserWebSocketClientAdapter } from "../src/BrowserWebSocketClientAdapter.js"
12
19
  import { NodeWSServerAdapter } from "../src/NodeWSServerAdapter.js"
13
- import {headsAreSame} from "@automerge/automerge-repo/src/helpers/headsAreSame.js"
20
+ import { headsAreSame } from "@automerge/automerge-repo/src/helpers/headsAreSame.js"
14
21
 
15
22
  describe("Websocket adapters", () => {
16
23
  const setup = async (clientCount = 1) => {
@@ -73,6 +80,7 @@ describe("Websocket adapters", () => {
73
80
  assert.deepEqual(message, {
74
81
  type: "join",
75
82
  senderId: "browser",
83
+ peerMetadata: { storageId: undefined, isEphemeral: true },
76
84
  supportedProtocolVersions: ["1"],
77
85
  })
78
86
  })
@@ -122,12 +130,12 @@ describe("Websocket adapters", () => {
122
130
  } = await setup()
123
131
 
124
132
  const peerId = "testclient" as PeerId
125
- browser.connect(peerId)
133
+ browser.connect(peerId, undefined, true)
126
134
 
127
135
  // simulate the reconnect timer firing before the other end has responded
128
136
  // (which works here because we haven't yielded to the event loop yet so
129
137
  // the server, which is on the same event loop as us, can't respond)
130
- browser.connect(peerId)
138
+ browser.connect(peerId, undefined, true)
131
139
 
132
140
  // Now yield, so the server responds on the first socket, if the listeners
133
141
  // are cleaned up correctly we shouldn't throw
@@ -156,11 +164,11 @@ describe("Websocket adapters", () => {
156
164
  }
157
165
 
158
166
  async function recvOrTimeout(socket: WebSocket): Promise<Buffer | null> {
159
- return new Promise((resolve) => {
167
+ return new Promise(resolve => {
160
168
  const timer = setTimeout(() => {
161
169
  resolve(null)
162
170
  }, 1000)
163
- socket.once("message", (msg) => {
171
+ socket.once("message", msg => {
164
172
  clearTimeout(timer)
165
173
  resolve(msg as Buffer)
166
174
  })
@@ -176,6 +184,7 @@ describe("Websocket adapters", () => {
176
184
  assert.deepEqual(response, {
177
185
  type: "peer",
178
186
  senderId: "server",
187
+ peerMetadata: { storageId: undefined, isEphemeral: true },
179
188
  targetId: "browser",
180
189
  selectedProtocolVersion: "1",
181
190
  })
@@ -203,26 +212,29 @@ describe("Websocket adapters", () => {
203
212
  assert.deepEqual(response, {
204
213
  type: "peer",
205
214
  senderId: "server",
215
+ peerMetadata: { storageId: undefined, isEphemeral: true },
206
216
  targetId: "browser",
207
217
  selectedProtocolVersion: "1",
208
218
  })
209
219
  })
210
220
 
211
- /**
221
+ /**
212
222
  * Create a new document, initialized with the given contents and return a
213
223
  * storage containign that document as well as the URL and a fork of the
214
224
  * document
215
225
  *
216
226
  * @param contents - The contents to initialize the document with
217
227
  */
218
- async function initDocAndStorage<T extends Record<string, unknown>>(contents: T): Promise<{
219
- storage: DummyStorageAdapter,
220
- url: AutomergeUrl,
221
- doc: A.Doc<T>,
222
- documentId: DocumentId
223
- }> {
228
+ async function initDocAndStorage<T extends Record<string, unknown>>(
229
+ contents: T
230
+ ): Promise<{
231
+ storage: DummyStorageAdapter
232
+ url: AutomergeUrl
233
+ doc: A.Doc<T>
234
+ documentId: DocumentId
235
+ }> {
224
236
  const storage = new DummyStorageAdapter()
225
- const silentRepo = new Repo({storage, network: []})
237
+ const silentRepo = new Repo({ storage, network: [] })
226
238
  const doc = A.from<T>(contents)
227
239
  const handle = silentRepo.create()
228
240
  handle.update(() => A.clone(doc))
@@ -246,7 +258,10 @@ describe("Websocket adapters", () => {
246
258
  }
247
259
  }
248
260
 
249
- function assertIsSyncMessage(forDocument: DocumentId, msg: Buffer | null): SyncMessage {
261
+ function assertIsSyncMessage(
262
+ forDocument: DocumentId,
263
+ msg: Buffer | null
264
+ ): SyncMessage {
250
265
  if (msg == null) {
251
266
  throw new Error("expected a peer message, got null")
252
267
  }
@@ -255,13 +270,15 @@ describe("Websocket adapters", () => {
255
270
  throw new Error(`expected a peer message, got type: ${decoded.type}`)
256
271
  }
257
272
  if (decoded.documentId !== forDocument) {
258
- throw new Error(`expected a sync message for ${forDocument}, not for ${decoded.documentId}`)
273
+ throw new Error(
274
+ `expected a sync message for ${forDocument}, not for ${decoded.documentId}`
275
+ )
259
276
  }
260
277
  return decoded
261
278
  }
262
279
 
263
280
  it("should disconnect existing peers on reconnect before announcing them", async () => {
264
- // This test exercises a sync loop which is exposed in the following
281
+ // This test exercises a sync loop which is exposed in the following
265
282
  // sequence of events:
266
283
  //
267
284
  // 1. A document exists on both the server and the client with divergent
@@ -276,27 +293,33 @@ describe("Websocket adapters", () => {
276
293
  // asks for them
277
294
  // 7. The server responds with an empty sync message because it thinks it
278
295
  // has already sent the changes
279
- //
296
+ //
280
297
  // 6 and 7 continue in an infinite loop. The root cause is the servers
281
298
  // failure to clear the sync state associated with the given peer when
282
299
  // it receives a new connection from the same peer ID.
283
300
  const { socket, serverUrl } = await setup(0)
284
301
 
285
302
  // Create a doc, populate a DummyStorageAdapter with that doc
286
- const {storage, url, doc, documentId} = await initDocAndStorage({foo: "bar"})
303
+ const { storage, url, doc, documentId } = await initDocAndStorage({
304
+ foo: "bar",
305
+ })
287
306
 
288
307
  // Create a copy of the document to represent the client state
289
- let clientDoc = A.clone<{foo: string}>(doc)
290
- clientDoc = A.change(clientDoc, d => d.foo = "qux")
308
+ let clientDoc = A.clone<{ foo: string }>(doc)
309
+ clientDoc = A.change(clientDoc, d => (d.foo = "qux"))
291
310
 
292
311
  // Now create a websocket sync server with the original document in it's storage
293
312
  const adapter = new NodeWSServerAdapter(socket)
294
- const repo = new Repo({ network: [adapter], storage, peerId: "server" as PeerId })
313
+ const repo = new Repo({
314
+ network: [adapter],
315
+ storage,
316
+ peerId: "server" as PeerId,
317
+ })
295
318
 
296
319
  // make a change to the handle on the sync server
297
- const handle = repo.find<{foo: string}>(url)
320
+ const handle = repo.find<{ foo: string }>(url)
298
321
  await handle.whenReady()
299
- handle.change(d => d.foo = "baz")
322
+ handle.change(d => (d.foo = "baz"))
300
323
 
301
324
  // Okay, so now there is a document on both the client and the server
302
325
  // which has concurrent changes on each peer.
@@ -306,11 +329,13 @@ describe("Websocket adapters", () => {
306
329
  await once(clientSocket, "open")
307
330
 
308
331
  // Run through the client/server hello
309
- clientSocket.send(CBOR.encode({
310
- type: "join",
311
- senderId: "client",
312
- supportedProtocolVersions: ["1"],
313
- }))
332
+ clientSocket.send(
333
+ CBOR.encode({
334
+ type: "join",
335
+ senderId: "client",
336
+ supportedProtocolVersions: ["1"],
337
+ })
338
+ )
314
339
 
315
340
  let response = await recvOrTimeout(clientSocket)
316
341
  assertIsPeerMessage(response)
@@ -318,24 +343,29 @@ describe("Websocket adapters", () => {
318
343
  // Okay now we start syncing
319
344
 
320
345
  let clientState = A.initSyncState()
321
- let [newSyncState, message] = A.generateSyncMessage(clientDoc, clientState)
346
+ let [newSyncState, message] = A.generateSyncMessage(
347
+ clientDoc,
348
+ clientState
349
+ )
322
350
  clientState = newSyncState
323
351
 
324
352
  // Send the initial sync state
325
- clientSocket.send(CBOR.encode({
326
- type: "request",
327
- documentId,
328
- targetId: "server",
329
- senderId: "client",
330
- data: message
331
- }))
353
+ clientSocket.send(
354
+ CBOR.encode({
355
+ type: "request",
356
+ documentId,
357
+ targetId: "server",
358
+ senderId: "client",
359
+ data: message,
360
+ })
361
+ )
332
362
 
333
363
  response = await recvOrTimeout(clientSocket)
334
364
  assertIsSyncMessage(documentId, response)
335
365
 
336
- // Now, assume either the network or the server is going slow, so the
366
+ // Now, assume either the network or the server is going slow, so the
337
367
  // server thinks it has sent the response above, but for whatever reason
338
- // it never gets to the client. In that case the reconnect timer in the
368
+ // it never gets to the client. In that case the reconnect timer in the
339
369
  // BrowserWebSocketClientAdapter will fire and we'll create a new
340
370
  // websocket and connect it. To simulate this we drop the above response
341
371
  // on the floor and start connecting again.
@@ -344,34 +374,42 @@ describe("Websocket adapters", () => {
344
374
  await once(clientSocket, "open")
345
375
 
346
376
  // and we also make a change to the client doc
347
- clientDoc = A.change(clientDoc, d => d.foo = "quoxen")
377
+ clientDoc = A.change(clientDoc, d => (d.foo = "quoxen"))
348
378
 
349
379
  // Run through the whole client/server hello dance again
350
- clientSocket.send(CBOR.encode({
351
- type: "join",
352
- senderId: "client",
353
- supportedProtocolVersions: ["1"],
354
- }))
380
+ clientSocket.send(
381
+ CBOR.encode({
382
+ type: "join",
383
+ senderId: "client",
384
+ supportedProtocolVersions: ["1"],
385
+ })
386
+ )
355
387
 
356
388
  response = await recvOrTimeout(clientSocket)
357
389
  assertIsPeerMessage(response)
358
390
 
359
391
  // Now, we start syncing. If we're not buggy, this loop should terminate.
360
- while(true) {
392
+ while (true) {
361
393
  ;[clientState, message] = A.generateSyncMessage(clientDoc, clientState)
362
394
  if (message) {
363
- clientSocket.send(CBOR.encode({
364
- type: "sync",
365
- documentId,
366
- targetId: "server",
367
- senderId: "client",
368
- data: message
369
- }))
395
+ clientSocket.send(
396
+ CBOR.encode({
397
+ type: "sync",
398
+ documentId,
399
+ targetId: "server",
400
+ senderId: "client",
401
+ data: message,
402
+ })
403
+ )
370
404
  }
371
405
  const response = await recvOrTimeout(clientSocket)
372
406
  if (response) {
373
407
  const decoded = assertIsSyncMessage(documentId, response)
374
- ;[clientDoc, clientState] = A.receiveSyncMessage(clientDoc, clientState, decoded.data)
408
+ ;[clientDoc, clientState] = A.receiveSyncMessage(
409
+ clientDoc,
410
+ clientState,
411
+ decoded.data
412
+ )
375
413
  }
376
414
  if (response == null && message == null) {
377
415
  break