@automerge/automerge-repo-network-websocket 1.0.5 → 1.0.7

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.
@@ -2,7 +2,7 @@ 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";
5
- import { isValidMessage } from "@automerge/automerge-repo";
5
+ import { isValidRepoMessage } from "@automerge/automerge-repo";
6
6
  const log = debug("WebsocketClient");
7
7
  class WebSocketNetworkAdapter extends NetworkAdapter {
8
8
  socket;
@@ -18,6 +18,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
18
18
  this.url = url;
19
19
  }
20
20
  connect(peerId) {
21
+ this.socket?.removeAllListeners();
21
22
  if (!this.timerId) {
22
23
  this.timerId = setInterval(() => this.connect(peerId), 5000);
23
24
  }
@@ -28,7 +29,6 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
28
29
  log(`@ ${this.url}: open`);
29
30
  clearInterval(this.timerId);
30
31
  this.timerId = undefined;
31
- this.join();
32
32
  });
33
33
  // When a socket closes, or disconnects, remove it from the array.
34
34
  this.socket.addEventListener("close", () => {
@@ -119,7 +119,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
119
119
  log(`error: ${decoded.message}`);
120
120
  break;
121
121
  default:
122
- if (!isValidMessage(decoded)) {
122
+ if (!isValidRepoMessage(decoded)) {
123
123
  throw new Error("Invalid message received");
124
124
  }
125
125
  this.emit("message", decoded);
@@ -1 +1 @@
1
- {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAK/D,OAAO,EAEL,cAAc,EACd,KAAK,MAAM,EACZ,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAqB,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAKpE,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;IAoBtB,UAAU;IAIV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAyB/B,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS;CAuDtD"}
1
+ {"version":3,"file":"NodeWSServerAdapter.d.ts","sourceRoot":"","sources":["../src/NodeWSServerAdapter.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAK/D,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;CAuDtD"}
@@ -12,7 +12,10 @@ export class NodeWSServerAdapter extends NetworkAdapter {
12
12
  }
13
13
  connect(peerId) {
14
14
  this.peerId = peerId;
15
- this.server.on("connection", socket => {
15
+ this.server.on("close", function close() {
16
+ clearInterval(interval);
17
+ });
18
+ this.server.on("connection", (socket) => {
16
19
  // When a socket closes, or disconnects, remove it from our list
17
20
  socket.on("close", () => {
18
21
  for (const [otherPeerId, otherSocket] of Object.entries(this.sockets)) {
@@ -23,8 +26,31 @@ export class NodeWSServerAdapter extends NetworkAdapter {
23
26
  }
24
27
  });
25
28
  socket.on("message", message => this.receiveMessage(message, socket));
29
+ // Start out "alive", and every time we get a pong, reset that state.
30
+ socket.isAlive = true;
31
+ socket.on("pong", () => (socket.isAlive = true));
26
32
  this.emit("ready", { network: this });
27
33
  });
34
+ // Every interval, terminate connections to lost clients,
35
+ // then mark all clients as potentially dead and then ping them.
36
+ const interval = setInterval(() => {
37
+ ;
38
+ this.server.clients.forEach(socket => {
39
+ if (socket.isAlive === false) {
40
+ // Make sure we clean up this socket even though we're terminating.
41
+ // This might be unnecessary but I have read reports of the close() not happening for 30s.
42
+ for (const [otherPeerId, otherSocket] of Object.entries(this.sockets)) {
43
+ if (socket === otherSocket) {
44
+ this.emit("peer-disconnected", { peerId: otherPeerId });
45
+ delete this.sockets[otherPeerId];
46
+ }
47
+ }
48
+ return socket.terminate();
49
+ }
50
+ socket.isAlive = false;
51
+ socket.ping();
52
+ });
53
+ }, 30000);
28
54
  }
29
55
  disconnect() {
30
56
  // throw new Error("The server doesn't join channels.")
@@ -1,4 +1,4 @@
1
- import { type Message, type PeerId } from "@automerge/automerge-repo";
1
+ import { type RepoMessage, type PeerId } from "@automerge/automerge-repo";
2
2
  import { ProtocolVersion } from "./protocolVersion.js";
3
3
  /** The sender is disconnecting */
4
4
  export type LeaveMessage = {
@@ -34,7 +34,7 @@ export type ErrorMessage = {
34
34
  targetId: PeerId;
35
35
  };
36
36
  /** A message from the client to the server */
37
- export type FromClientMessage = JoinMessage | LeaveMessage | Message;
37
+ export type FromClientMessage = JoinMessage | LeaveMessage | RepoMessage;
38
38
  /** A message from the server to the client */
39
- export type FromServerMessage = PeerMessage | ErrorMessage | Message;
39
+ export type FromServerMessage = PeerMessage | ErrorMessage | RepoMessage;
40
40
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +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,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;AAGD,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAA;AACpE,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,EAAE,KAAK,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,2BAA2B,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,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,WAAW,CAAA;AAExE,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-network-websocket",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "isomorphic node/browser Websocket network adapter for Automerge Repo",
5
5
  "peerDependencies": {
6
6
  "@automerge/automerge": "^2.1.0"
@@ -13,10 +13,10 @@
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "watch": "npm-watch",
16
- "test": "mocha --no-warnings --experimental-specifier-resolution=node --exit"
16
+ "test": "vitest"
17
17
  },
18
18
  "dependencies": {
19
- "@automerge/automerge-repo": "^1.0.5",
19
+ "@automerge/automerge-repo": "^1.0.7",
20
20
  "cbor-x": "^1.3.0",
21
21
  "eventemitter3": "^5.0.1",
22
22
  "isomorphic-ws": "^5.0.0",
@@ -33,8 +33,5 @@
33
33
  "publishConfig": {
34
34
  "access": "public"
35
35
  },
36
- "devDependencies": {
37
- "@automerge/automerge": "^2.1.0"
38
- },
39
- "gitHead": "09256d61e0e4e0afd5d8deb1e187b57cb2413e1e"
36
+ "gitHead": "71060981f168e511a99ab85b155a54a13fd04bcc"
40
37
  }
@@ -9,7 +9,7 @@ import {
9
9
  JoinMessage,
10
10
  } from "./messages.js"
11
11
  import { ProtocolV1 } from "./protocolVersion.js"
12
- import { isValidMessage } from "@automerge/automerge-repo"
12
+ import { isValidRepoMessage } from "@automerge/automerge-repo"
13
13
 
14
14
  const log = debug("WebsocketClient")
15
15
 
@@ -31,6 +31,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
31
31
  }
32
32
 
33
33
  connect(peerId: PeerId) {
34
+ this.socket?.removeAllListeners()
34
35
  if (!this.timerId) {
35
36
  this.timerId = setInterval(() => this.connect(peerId), 5000)
36
37
  }
@@ -43,7 +44,6 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
43
44
  log(`@ ${this.url}: open`)
44
45
  clearInterval(this.timerId)
45
46
  this.timerId = undefined
46
- this.join()
47
47
  })
48
48
 
49
49
  // When a socket closes, or disconnects, remove it from the array.
@@ -159,7 +159,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
159
159
  log(`error: ${decoded.message}`)
160
160
  break
161
161
  default:
162
- if (!isValidMessage(decoded)) {
162
+ if (!isValidRepoMessage(decoded)) {
163
163
  throw new Error("Invalid message received")
164
164
  }
165
165
  this.emit("message", decoded)
@@ -13,6 +13,10 @@ import { ProtocolV1, ProtocolVersion } from "./protocolVersion.js"
13
13
 
14
14
  const { encode, decode } = cborHelpers
15
15
 
16
+ interface WebSocketWithIsAlive extends WebSocket {
17
+ isAlive: boolean
18
+ }
19
+
16
20
  export class NodeWSServerAdapter extends NetworkAdapter {
17
21
  server: WebSocketServer
18
22
  sockets: { [peerId: PeerId]: WebSocket } = {}
@@ -24,7 +28,12 @@ export class NodeWSServerAdapter extends NetworkAdapter {
24
28
 
25
29
  connect(peerId: PeerId) {
26
30
  this.peerId = peerId
27
- this.server.on("connection", socket => {
31
+
32
+ this.server.on("close", function close() {
33
+ clearInterval(interval)
34
+ })
35
+
36
+ this.server.on("connection", (socket: WebSocketWithIsAlive) => {
28
37
  // When a socket closes, or disconnects, remove it from our list
29
38
  socket.on("close", () => {
30
39
  for (const [otherPeerId, otherSocket] of Object.entries(this.sockets)) {
@@ -38,8 +47,35 @@ export class NodeWSServerAdapter extends NetworkAdapter {
38
47
  socket.on("message", message =>
39
48
  this.receiveMessage(message as Uint8Array, socket)
40
49
  )
50
+
51
+ // Start out "alive", and every time we get a pong, reset that state.
52
+ socket.isAlive = true
53
+ socket.on("pong", () => (socket.isAlive = true))
54
+
41
55
  this.emit("ready", { network: this })
42
56
  })
57
+
58
+ // Every interval, terminate connections to lost clients,
59
+ // then mark all clients as potentially dead and then ping them.
60
+ const interval = setInterval(() => {
61
+ ;(this.server.clients as Set<WebSocketWithIsAlive>).forEach(socket => {
62
+ if (socket.isAlive === false) {
63
+ // Make sure we clean up this socket even though we're terminating.
64
+ // This might be unnecessary but I have read reports of the close() not happening for 30s.
65
+ for (const [otherPeerId, otherSocket] of Object.entries(
66
+ this.sockets
67
+ )) {
68
+ if (socket === otherSocket) {
69
+ this.emit("peer-disconnected", { peerId: otherPeerId as PeerId })
70
+ delete this.sockets[otherPeerId as PeerId]
71
+ }
72
+ }
73
+ return socket.terminate()
74
+ }
75
+ socket.isAlive = false
76
+ socket.ping()
77
+ })
78
+ }, 30000)
43
79
  }
44
80
 
45
81
  disconnect() {
package/src/messages.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Message, type PeerId } from "@automerge/automerge-repo"
1
+ import { type RepoMessage, type PeerId } from "@automerge/automerge-repo"
2
2
  import { ProtocolVersion } from "./protocolVersion.js"
3
3
 
4
4
  /** The sender is disconnecting */
@@ -38,8 +38,11 @@ export type ErrorMessage = {
38
38
  targetId: PeerId
39
39
  }
40
40
 
41
- // This adapter doesn't use NetworkAdapterMessage, it has its own idea of how to handle join/leave
41
+ // This adapter doesn't use the network adapter Message types, it has its own idea of how to handle
42
+ // join/leave
43
+
42
44
  /** A message from the client to the server */
43
- export type FromClientMessage = JoinMessage | LeaveMessage | Message
45
+ export type FromClientMessage = JoinMessage | LeaveMessage | RepoMessage
46
+
44
47
  /** A message from the server to the client */
45
- export type FromServerMessage = PeerMessage | ErrorMessage | Message
48
+ export type FromServerMessage = PeerMessage | ErrorMessage | RepoMessage
@@ -4,9 +4,10 @@ import { NodeWSServerAdapter } from "../src/NodeWSServerAdapter.js"
4
4
  import { startServer } from "./utilities/WebSockets.js"
5
5
  import * as CBOR from "cbor-x"
6
6
  import WebSocket, { AddressInfo } from "ws"
7
- import { assert } from "chai"
7
+ import assert from "assert"
8
8
  import { PeerId, Repo } from "@automerge/automerge-repo"
9
9
  import { once } from "events"
10
+ import { describe, it } from "vitest"
10
11
 
11
12
  describe("Websocket adapters", async () => {
12
13
  let port = 8080
@@ -17,7 +18,7 @@ describe("Websocket adapters", async () => {
17
18
 
18
19
  while (socket === undefined) {
19
20
  try {
20
- ; ({ socket, server } = await startServer(port))
21
+ ;({ socket, server } = await startServer(port))
21
22
  } catch (e: any) {
22
23
  if (e.code === "EADDRINUSE") {
23
24
  port++
package/typedoc.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "extends": "../../typedoc.base.json",
3
- "entryPoints": ["src/index.ts"],
4
- "readme": "none"
2
+ "extends": ["../../typedoc.base.json"],
3
+ "entryPoints": ["src/index.ts"],
4
+ "readme": "none"
5
5
  }
package/.mocharc.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "extension": ["ts"],
3
- "spec": "test/*.test.ts",
4
- "loader": "ts-node/esm"
5
- }