@automerge/automerge-repo-network-websocket 1.2.0 → 2.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,8 @@ export declare class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapt
8
8
  #private;
9
9
  readonly url: string;
10
10
  readonly retryInterval: number;
11
+ isReady(): boolean;
12
+ whenReady(): Promise<void>;
11
13
  remotePeerId?: PeerId;
12
14
  constructor(url: string, retryInterval?: number);
13
15
  connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
@@ -1 +1 @@
1
- {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,MAAM,EACN,YAAY,EAEb,MAAM,gCAAgC,CAAA;AACvC,OAAO,SAAS,MAAM,eAAe,CAAA;AAIrC,OAAO,EACL,iBAAiB,EAKlB,MAAM,eAAe,CAAA;AAKtB,uBAAe,uBAAwB,SAAQ,cAAc;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,qBAAa,6BAA8B,SAAQ,uBAAuB;;aAQtD,GAAG,EAAE,MAAM;aACX,aAAa;IAJ/B,YAAY,CAAC,EAAE,MAAM,CAAA;gBAGH,GAAG,EAAE,MAAM,EACX,aAAa,SAAO;IAMtC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAqCnD,MAAM,aAKL;IAGD,OAAO,aAWN;IAED,SAAS,UAAW,SAAS,CAAC,YAAY,UAEzC;IAED,mFAAmF;IACnF,OAAO,UAED,KAAK,GACL,SAAS,CAAC,UAAU,UAezB;IAQD,IAAI;IAUJ,UAAU;IAMV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAY/B,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;IAU9D,cAAc,CAAC,YAAY,EAAE,UAAU;CAiBxC"}
1
+ {"version":3,"file":"BrowserWebSocketClientAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserWebSocketClientAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,MAAM,EACN,YAAY,EAEb,MAAM,gCAAgC,CAAA;AACvC,OAAO,SAAS,MAAM,eAAe,CAAA;AAIrC,OAAO,EACL,iBAAiB,EAKlB,MAAM,eAAe,CAAA;AAKtB,uBAAe,uBAAwB,SAAQ,cAAc;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,qBAAa,6BAA8B,SAAQ,uBAAuB;;aA4BtD,GAAG,EAAE,MAAM;aACX,aAAa;IAtB/B,OAAO;IAIP,SAAS;IAcT,YAAY,CAAC,EAAE,MAAM,CAAA;gBAGH,GAAG,EAAE,MAAM,EACX,aAAa,SAAO;IAMtC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAqCnD,MAAM,aAKL;IAGD,OAAO,aAWN;IAED,SAAS,UAAW,SAAS,CAAC,YAAY,UAEzC;IAED,mFAAmF;IACnF,OAAO,UAED,KAAK,GACL,SAAS,CAAC,UAAU,UAezB;IAED,IAAI;IAUJ,UAAU;IAiBV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAe/B,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;IAU9D,cAAc,CAAC,YAAY,EAAE,UAAU;CAiBxC"}
@@ -11,7 +11,23 @@ class WebSocketNetworkAdapter extends NetworkAdapter {
11
11
  export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
12
12
  url;
13
13
  retryInterval;
14
- #isReady = false;
14
+ #ready = false;
15
+ #readyResolver;
16
+ #readyPromise = new Promise(resolve => {
17
+ this.#readyResolver = resolve;
18
+ });
19
+ isReady() {
20
+ return this.#ready;
21
+ }
22
+ whenReady() {
23
+ return this.#readyPromise;
24
+ }
25
+ #forceReady() {
26
+ if (!this.#ready) {
27
+ this.#ready = true;
28
+ this.#readyResolver?.();
29
+ }
30
+ }
15
31
  #retryIntervalId;
16
32
  #log = debug("automerge-repo:websocket:browser");
17
33
  remotePeerId; // this adapter only connects to one remote client at a time
@@ -51,7 +67,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
51
67
  // Mark this adapter as ready if we haven't received an ack in 1 second.
52
68
  // We might hear back from the other end at some point but we shouldn't
53
69
  // hold up marking things as unavailable for any longer
54
- setTimeout(() => this.#ready(), 1000);
70
+ setTimeout(() => this.#forceReady(), 1000);
55
71
  this.join();
56
72
  }
57
73
  onOpen = () => {
@@ -93,12 +109,6 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
93
109
  }
94
110
  this.#log("Connection failed, retrying...");
95
111
  };
96
- #ready() {
97
- if (this.#isReady)
98
- return;
99
- this.#isReady = true;
100
- this.emit("ready", { network: this });
101
- }
102
112
  join() {
103
113
  assert(this.peerId);
104
114
  assert(this.socket);
@@ -112,13 +122,27 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
112
122
  disconnect() {
113
123
  assert(this.peerId);
114
124
  assert(this.socket);
115
- this.send({ type: "leave", senderId: this.peerId });
125
+ const socket = this.socket;
126
+ if (socket) {
127
+ socket.removeEventListener("open", this.onOpen);
128
+ socket.removeEventListener("close", this.onClose);
129
+ socket.removeEventListener("message", this.onMessage);
130
+ socket.removeEventListener("error", this.onError);
131
+ socket.close();
132
+ }
133
+ clearInterval(this.#retryIntervalId);
134
+ if (this.remotePeerId)
135
+ this.emit("peer-disconnected", { peerId: this.remotePeerId });
136
+ this.socket = undefined;
116
137
  }
117
138
  send(message) {
118
139
  if ("data" in message && message.data?.byteLength === 0)
119
140
  throw new Error("Tried to send a zero-length message");
120
141
  assert(this.peerId);
121
- assert(this.socket);
142
+ if (!this.socket) {
143
+ this.#log("Tried to send on a disconnected socket.");
144
+ return;
145
+ }
122
146
  if (this.socket.readyState !== WebSocket.OPEN)
123
147
  throw new Error(`Websocket not ready (${this.socket.readyState})`);
124
148
  const encoded = cbor.encode(message);
@@ -126,7 +150,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
126
150
  }
127
151
  peerCandidate(remotePeerId, peerMetadata) {
128
152
  assert(this.socket);
129
- this.#ready();
153
+ this.#forceReady();
130
154
  this.remotePeerId = remotePeerId;
131
155
  this.emit("peer-candidate", {
132
156
  peerId: remotePeerId,
@@ -9,6 +9,8 @@ export declare class NodeWSServerAdapter extends NetworkAdapter {
9
9
  sockets: {
10
10
  [peerId: PeerId]: WebSocket;
11
11
  };
12
+ isReady(): boolean;
13
+ whenReady(): Promise<void>;
12
14
  constructor(server: WebSocketServer, keepAliveInterval?: number);
13
15
  connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
14
16
  disconnect(): 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,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAEL,iBAAiB,EAGlB,MAAM,eAAe,CAAA;AAOtB,qBAAa,mBAAoB,SAAQ,cAAc;;IAInD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAJ3B,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;gBAGnC,MAAM,EAAE,eAAe,EACvB,iBAAiB,SAAO;IAKlC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAyCnD,UAAU;IAQV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAqB/B,cAAc,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CA0E3D"}
1
+ {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,OAAO,EAEL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAEL,iBAAiB,EAElB,MAAM,eAAe,CAAA;AAOtB,qBAAa,mBAAoB,SAAQ,cAAc;;IAyBnD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,iBAAiB;IAzB3B,OAAO,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAK;IAQ7C,OAAO;IAIP,SAAS;gBAYC,MAAM,EAAE,eAAe,EACvB,iBAAiB,SAAO;IAKlC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY;IAyCnD,UAAU;IAQV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAqB/B,cAAc,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CAoE3D"}
@@ -2,7 +2,7 @@ import WebSocket from "isomorphic-ws";
2
2
  import debug from "debug";
3
3
  const log = debug("WebsocketServer");
4
4
  import { cbor as cborHelpers, NetworkAdapter, } from "@automerge/automerge-repo/slim";
5
- import { isJoinMessage, isLeaveMessage, } from "./messages.js";
5
+ import { isJoinMessage, } from "./messages.js";
6
6
  import { ProtocolV1 } from "./protocolVersion.js";
7
7
  import { assert } from "./assert.js";
8
8
  import { toArrayBuffer } from "./toArrayBuffer.js";
@@ -11,6 +11,23 @@ export class NodeWSServerAdapter extends NetworkAdapter {
11
11
  server;
12
12
  keepAliveInterval;
13
13
  sockets = {};
14
+ #ready = false;
15
+ #readyResolver;
16
+ #readyPromise = new Promise(resolve => {
17
+ this.#readyResolver = resolve;
18
+ });
19
+ isReady() {
20
+ return this.#ready;
21
+ }
22
+ whenReady() {
23
+ return this.#readyPromise;
24
+ }
25
+ #forceReady() {
26
+ if (!this.#ready) {
27
+ this.#ready = true;
28
+ this.#readyResolver?.();
29
+ }
30
+ }
14
31
  constructor(server, keepAliveInterval = 5000) {
15
32
  super();
16
33
  this.server = server;
@@ -32,7 +49,7 @@ export class NodeWSServerAdapter extends NetworkAdapter {
32
49
  // Start out "alive", and every time we get a pong, reset that state.
33
50
  socket.isAlive = true;
34
51
  socket.on("pong", () => (socket.isAlive = true));
35
- this.emit("ready", { network: this });
52
+ this.#forceReady();
36
53
  });
37
54
  const keepAliveId = setInterval(() => {
38
55
  // Terminate connections to lost clients
@@ -112,14 +129,6 @@ export class NodeWSServerAdapter extends NetworkAdapter {
112
129
  });
113
130
  }
114
131
  }
115
- else if (isLeaveMessage(message)) {
116
- const { senderId } = message;
117
- const socket = this.sockets[senderId];
118
- /* c8 ignore next */
119
- if (!socket)
120
- return;
121
- this.#terminate(socket);
122
- }
123
132
  else {
124
133
  this.emit("message", message);
125
134
  }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  * */
15
15
  export { BrowserWebSocketClientAdapter } from "./BrowserWebSocketClientAdapter.js";
16
16
  export { NodeWSServerAdapter } from "./NodeWSServerAdapter.js";
17
- export type { FromClientMessage, FromServerMessage, JoinMessage, LeaveMessage, ErrorMessage, PeerMessage, } from "./messages.js";
18
- export type { ProtocolVersion, ProtocolV1 } from "./protocolVersion.js";
17
+ export type { FromClientMessage, FromServerMessage, JoinMessage, ErrorMessage, PeerMessage, } from "./messages.js";
18
+ export type { ProtocolVersion } from "./protocolVersion.js";
19
+ export { ProtocolV1 } from "./protocolVersion.js";
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;KAaK;AACL,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;KAaK;AACL,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -14,3 +14,4 @@
14
14
  * */
15
15
  export { BrowserWebSocketClientAdapter } from "./BrowserWebSocketClientAdapter.js";
16
16
  export { NodeWSServerAdapter } from "./NodeWSServerAdapter.js";
17
+ export { ProtocolV1 } from "./protocolVersion.js";
@@ -1,10 +1,5 @@
1
1
  import type { Message, PeerId, PeerMetadata } from "@automerge/automerge-repo/slim";
2
2
  import type { ProtocolVersion } from "./protocolVersion.js";
3
- /** The sender is disconnecting */
4
- export type LeaveMessage = {
5
- type: "leave";
6
- senderId: PeerId;
7
- };
8
3
  /** Sent by the client to the server to tell the server the clients PeerID */
9
4
  export type JoinMessage = {
10
5
  type: "join";
@@ -38,11 +33,10 @@ export type ErrorMessage = {
38
33
  targetId: PeerId;
39
34
  };
40
35
  /** A message from the client to the server */
41
- export type FromClientMessage = JoinMessage | LeaveMessage | Message;
36
+ export type FromClientMessage = JoinMessage | Message;
42
37
  /** A message from the server to the client */
43
38
  export type FromServerMessage = PeerMessage | ErrorMessage | Message;
44
39
  export declare const isJoinMessage: (message: FromClientMessage) => message is JoinMessage;
45
- export declare const isLeaveMessage: (message: FromClientMessage) => message is LeaveMessage;
46
40
  export declare const isPeerMessage: (message: FromServerMessage) => message is PeerMessage;
47
41
  export declare const isErrorMessage: (message: FromServerMessage) => message is ErrorMessage;
48
42
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,MAAM,EACN,YAAY,EACb,MAAM,gCAAgC,CAAA;AACvC,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;AAED,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;AAIpE,eAAO,MAAM,aAAa,YACf,iBAAiB,KACzB,OAAO,IAAI,WAAsC,CAAA;AAEpD,eAAO,MAAM,cAAc,YAChB,iBAAiB,KACzB,OAAO,IAAI,YAAwC,CAAA;AAEtD,eAAO,MAAM,aAAa,YACf,iBAAiB,KACzB,OAAO,IAAI,WAAsC,CAAA;AAEpD,eAAO,MAAM,cAAc,YAChB,iBAAiB,KACzB,OAAO,IAAI,YAAwC,CAAA"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,MAAM,EACN,YAAY,EACb,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAE3D,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;AAED,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,OAAO,CAAA;AAErD,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AAIpE,eAAO,MAAM,aAAa,YACf,iBAAiB,KACzB,OAAO,IAAI,WAAsC,CAAA;AAEpD,eAAO,MAAM,aAAa,YACf,iBAAiB,KACzB,OAAO,IAAI,WAAsC,CAAA;AAEpD,eAAO,MAAM,cAAc,YAChB,iBAAiB,KACzB,OAAO,IAAI,YAAwC,CAAA"}
package/dist/messages.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // TYPE GUARDS
2
2
  export const isJoinMessage = (message) => message.type === "join";
3
- export const isLeaveMessage = (message) => message.type === "leave";
4
3
  export const isPeerMessage = (message) => message.type === "peer";
5
4
  export const isErrorMessage = (message) => message.type === "error";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-network-websocket",
3
- "version": "1.2.0",
3
+ "version": "2.0.0-alpha.0",
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.2.0",
16
+ "@automerge/automerge-repo": "2.0.0-alpha.0",
17
17
  "cbor-x": "^1.3.0",
18
18
  "debug": "^4.3.4",
19
19
  "eventemitter3": "^5.0.1",
@@ -31,5 +31,5 @@
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "gitHead": "06126099322474bb8c6251b58b1ee5e63c174f06"
34
+ "gitHead": "16356392fb2ed9245565ae04f6e6b49e61195e65"
35
35
  }
@@ -24,7 +24,27 @@ abstract class WebSocketNetworkAdapter extends NetworkAdapter {
24
24
  }
25
25
 
26
26
  export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
27
- #isReady = false
27
+ #ready = false
28
+ #readyResolver?: () => void
29
+ #readyPromise: Promise<void> = new Promise<void>(resolve => {
30
+ this.#readyResolver = resolve
31
+ })
32
+
33
+ isReady() {
34
+ return this.#ready
35
+ }
36
+
37
+ whenReady() {
38
+ return this.#readyPromise
39
+ }
40
+
41
+ #forceReady() {
42
+ if (!this.#ready) {
43
+ this.#ready = true
44
+ this.#readyResolver?.()
45
+ }
46
+ }
47
+
28
48
  #retryIntervalId?: TimeoutId
29
49
  #log = debug("automerge-repo:websocket:browser")
30
50
 
@@ -71,7 +91,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
71
91
  // Mark this adapter as ready if we haven't received an ack in 1 second.
72
92
  // We might hear back from the other end at some point but we shouldn't
73
93
  // hold up marking things as unavailable for any longer
74
- setTimeout(() => this.#ready(), 1000)
94
+ setTimeout(() => this.#forceReady(), 1000)
75
95
  this.join()
76
96
  }
77
97
 
@@ -121,12 +141,6 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
121
141
  this.#log("Connection failed, retrying...")
122
142
  }
123
143
 
124
- #ready() {
125
- if (this.#isReady) return
126
- this.#isReady = true
127
- this.emit("ready", { network: this })
128
- }
129
-
130
144
  join() {
131
145
  assert(this.peerId)
132
146
  assert(this.socket)
@@ -140,14 +154,28 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
140
154
  disconnect() {
141
155
  assert(this.peerId)
142
156
  assert(this.socket)
143
- this.send({ type: "leave", senderId: this.peerId })
157
+ const socket = this.socket
158
+ if (socket) {
159
+ socket.removeEventListener("open", this.onOpen)
160
+ socket.removeEventListener("close", this.onClose)
161
+ socket.removeEventListener("message", this.onMessage)
162
+ socket.removeEventListener("error", this.onError)
163
+ socket.close()
164
+ }
165
+ clearInterval(this.#retryIntervalId)
166
+ if (this.remotePeerId)
167
+ this.emit("peer-disconnected", { peerId: this.remotePeerId })
168
+ this.socket = undefined
144
169
  }
145
170
 
146
171
  send(message: FromClientMessage) {
147
172
  if ("data" in message && message.data?.byteLength === 0)
148
173
  throw new Error("Tried to send a zero-length message")
149
174
  assert(this.peerId)
150
- assert(this.socket)
175
+ if (!this.socket) {
176
+ this.#log("Tried to send on a disconnected socket.")
177
+ return
178
+ }
151
179
  if (this.socket.readyState !== WebSocket.OPEN)
152
180
  throw new Error(`Websocket not ready (${this.socket.readyState})`)
153
181
 
@@ -157,7 +185,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
157
185
 
158
186
  peerCandidate(remotePeerId: PeerId, peerMetadata: PeerMetadata) {
159
187
  assert(this.socket)
160
- this.#ready()
188
+ this.#forceReady()
161
189
  this.remotePeerId = remotePeerId
162
190
  this.emit("peer-candidate", {
163
191
  peerId: remotePeerId,
@@ -14,7 +14,6 @@ import {
14
14
  FromClientMessage,
15
15
  FromServerMessage,
16
16
  isJoinMessage,
17
- isLeaveMessage,
18
17
  } from "./messages.js"
19
18
  import { ProtocolV1, ProtocolVersion } from "./protocolVersion.js"
20
19
  import { assert } from "./assert.js"
@@ -25,6 +24,27 @@ const { encode, decode } = cborHelpers
25
24
  export class NodeWSServerAdapter extends NetworkAdapter {
26
25
  sockets: { [peerId: PeerId]: WebSocket } = {}
27
26
 
27
+ #ready = false
28
+ #readyResolver?: () => void
29
+ #readyPromise: Promise<void> = new Promise<void>(resolve => {
30
+ this.#readyResolver = resolve
31
+ })
32
+
33
+ isReady() {
34
+ return this.#ready
35
+ }
36
+
37
+ whenReady() {
38
+ return this.#readyPromise
39
+ }
40
+
41
+ #forceReady() {
42
+ if (!this.#ready) {
43
+ this.#ready = true
44
+ this.#readyResolver?.()
45
+ }
46
+ }
47
+
28
48
  constructor(
29
49
  private server: WebSocketServer,
30
50
  private keepAliveInterval = 5000
@@ -55,7 +75,7 @@ export class NodeWSServerAdapter extends NetworkAdapter {
55
75
  socket.isAlive = true
56
76
  socket.on("pong", () => (socket.isAlive = true))
57
77
 
58
- this.emit("ready", { network: this })
78
+ this.#forceReady()
59
79
  })
60
80
 
61
81
  const keepAliveId = setInterval(() => {
@@ -147,12 +167,6 @@ export class NodeWSServerAdapter extends NetworkAdapter {
147
167
  targetId: senderId,
148
168
  })
149
169
  }
150
- } else if (isLeaveMessage(message)) {
151
- const { senderId } = message
152
- const socket = this.sockets[senderId]
153
- /* c8 ignore next */
154
- if (!socket) return
155
- this.#terminate(socket as WebSocketWithIsAlive)
156
170
  } else {
157
171
  this.emit("message", message)
158
172
  }
package/src/index.ts CHANGED
@@ -18,8 +18,8 @@ export type {
18
18
  FromClientMessage,
19
19
  FromServerMessage,
20
20
  JoinMessage,
21
- LeaveMessage,
22
21
  ErrorMessage,
23
22
  PeerMessage,
24
23
  } from "./messages.js"
25
- export type { ProtocolVersion, ProtocolV1 } from "./protocolVersion.js"
24
+ export type { ProtocolVersion } from "./protocolVersion.js"
25
+ export { ProtocolV1 } from "./protocolVersion.js"
package/src/messages.ts CHANGED
@@ -5,12 +5,6 @@ import type {
5
5
  } from "@automerge/automerge-repo/slim"
6
6
  import type { ProtocolVersion } from "./protocolVersion.js"
7
7
 
8
- /** The sender is disconnecting */
9
- export type LeaveMessage = {
10
- type: "leave"
11
- senderId: PeerId
12
- }
13
-
14
8
  /** Sent by the client to the server to tell the server the clients PeerID */
15
9
  export type JoinMessage = {
16
10
  type: "join"
@@ -51,7 +45,7 @@ export type ErrorMessage = {
51
45
  }
52
46
 
53
47
  /** A message from the client to the server */
54
- export type FromClientMessage = JoinMessage | LeaveMessage | Message
48
+ export type FromClientMessage = JoinMessage | Message
55
49
 
56
50
  /** A message from the server to the client */
57
51
  export type FromServerMessage = PeerMessage | ErrorMessage | Message
@@ -62,10 +56,6 @@ export const isJoinMessage = (
62
56
  message: FromClientMessage
63
57
  ): message is JoinMessage => message.type === "join"
64
58
 
65
- export const isLeaveMessage = (
66
- message: FromClientMessage
67
- ): message is LeaveMessage => message.type === "leave"
68
-
69
59
  export const isPeerMessage = (
70
60
  message: FromServerMessage
71
61
  ): message is PeerMessage => message.type === "peer"
@@ -66,7 +66,7 @@ describe("Websocket adapters", () => {
66
66
  })
67
67
  })
68
68
 
69
- it("should announce disconnections", async () => {
69
+ it("should connect and emit peers", async () => {
70
70
  const {
71
71
  serverAdapter,
72
72
  clients: [browserAdapter],
@@ -82,13 +82,9 @@ describe("Websocket adapters", () => {
82
82
  peerId: serverPeerId,
83
83
  })
84
84
 
85
- await eventPromise(serverRepo.networkSubsystem, "peer")
86
-
87
- browserAdapter.disconnect()
88
-
89
85
  await Promise.all([
90
- eventPromise(browserAdapter, "peer-disconnected"),
91
- eventPromise(serverAdapter, "peer-disconnected"),
86
+ eventPromise(browserRepo.networkSubsystem, "peer"),
87
+ eventPromise(serverRepo.networkSubsystem, "peer"),
92
88
  ])
93
89
  })
94
90