@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.
- package/dist/BrowserWebSocketClientAdapter.js +3 -3
- package/dist/NodeWSServerAdapter.d.ts.map +1 -1
- package/dist/NodeWSServerAdapter.js +27 -1
- package/dist/messages.d.ts +3 -3
- package/dist/messages.d.ts.map +1 -1
- package/package.json +4 -7
- package/src/BrowserWebSocketClientAdapter.ts +3 -3
- package/src/NodeWSServerAdapter.ts +37 -1
- package/src/messages.ts +7 -4
- package/test/Websocket.test.ts +3 -2
- package/typedoc.json +3 -3
- package/.mocharc.json +0 -5
|
@@ -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 {
|
|
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 (!
|
|
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;
|
|
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("
|
|
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.")
|
package/dist/messages.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
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 |
|
|
37
|
+
export type FromClientMessage = JoinMessage | LeaveMessage | RepoMessage;
|
|
38
38
|
/** A message from the server to the client */
|
|
39
|
-
export type FromServerMessage = PeerMessage | ErrorMessage |
|
|
39
|
+
export type FromServerMessage = PeerMessage | ErrorMessage | RepoMessage;
|
|
40
40
|
//# sourceMappingURL=messages.d.ts.map
|
package/dist/messages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,
|
|
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.
|
|
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": "
|
|
16
|
+
"test": "vitest"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@automerge/automerge-repo": "^1.0.
|
|
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
|
-
"
|
|
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 {
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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
|
|
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 |
|
|
45
|
+
export type FromClientMessage = JoinMessage | LeaveMessage | RepoMessage
|
|
46
|
+
|
|
44
47
|
/** A message from the server to the client */
|
|
45
|
-
export type FromServerMessage = PeerMessage | ErrorMessage |
|
|
48
|
+
export type FromServerMessage = PeerMessage | ErrorMessage | RepoMessage
|
package/test/Websocket.test.ts
CHANGED
|
@@ -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
|
|
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
|
-
;
|
|
21
|
+
;({ socket, server } = await startServer(port))
|
|
21
22
|
} catch (e: any) {
|
|
22
23
|
if (e.code === "EADDRINUSE") {
|
|
23
24
|
port++
|
package/typedoc.json
CHANGED