@automerge/automerge-repo-network-websocket 1.0.7 → 1.0.9
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.
|
@@ -11,6 +11,9 @@ export declare class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapt
|
|
|
11
11
|
url: string;
|
|
12
12
|
constructor(url: string);
|
|
13
13
|
connect(peerId: PeerId): void;
|
|
14
|
+
onOpen: () => void;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
onMessage: (event: WebSocket.MessageEvent) => void;
|
|
14
17
|
join(): void;
|
|
15
18
|
disconnect(): void;
|
|
16
19
|
send(message: FromClientMessage): void;
|
|
@@ -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;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;IAGvC,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM;
|
|
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;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;IAGvC,GAAG,EAAE,MAAM,CAAA;gBAEC,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,MAAM,EAAE,MAAM;IAkCtB,MAAM,aAKL;IAGD,OAAO,aAON;IAED,SAAS,UAAW,sBAAsB,UAEzC;IAED,IAAI;IAWJ,UAAU;IAOV,IAAI,CAAC,OAAO,EAAE,iBAAiB;IAwB/B,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAajC,cAAc,CAAC,OAAO,EAAE,UAAU;CA6BnC"}
|
|
@@ -18,27 +18,22 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
18
18
|
this.url = url;
|
|
19
19
|
}
|
|
20
20
|
connect(peerId) {
|
|
21
|
-
|
|
21
|
+
// If we're reconnecting make sure we remove the old event listeners
|
|
22
|
+
// before creating a new connection.
|
|
23
|
+
if (this.socket) {
|
|
24
|
+
this.socket.removeEventListener("open", this.onOpen);
|
|
25
|
+
this.socket.removeEventListener("close", this.onClose);
|
|
26
|
+
this.socket.removeEventListener("message", this.onMessage);
|
|
27
|
+
}
|
|
22
28
|
if (!this.timerId) {
|
|
23
29
|
this.timerId = setInterval(() => this.connect(peerId), 5000);
|
|
24
30
|
}
|
|
25
31
|
this.peerId = peerId;
|
|
26
32
|
this.socket = new WebSocket(this.url);
|
|
27
33
|
this.socket.binaryType = "arraybuffer";
|
|
28
|
-
this.socket.addEventListener("open",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.timerId = undefined;
|
|
32
|
-
});
|
|
33
|
-
// When a socket closes, or disconnects, remove it from the array.
|
|
34
|
-
this.socket.addEventListener("close", () => {
|
|
35
|
-
log(`${this.url}: close`);
|
|
36
|
-
if (!this.timerId) {
|
|
37
|
-
this.connect(peerId);
|
|
38
|
-
}
|
|
39
|
-
// log("Disconnected from server")
|
|
40
|
-
});
|
|
41
|
-
this.socket.addEventListener("message", (event) => this.receiveMessage(event.data));
|
|
34
|
+
this.socket.addEventListener("open", this.onOpen);
|
|
35
|
+
this.socket.addEventListener("close", this.onClose);
|
|
36
|
+
this.socket.addEventListener("message", this.onMessage);
|
|
42
37
|
// mark this adapter as ready if we haven't received an ack in 1 second.
|
|
43
38
|
// We might hear back from the other end at some point but we shouldn't
|
|
44
39
|
// hold up marking things as unavailable for any longer
|
|
@@ -50,6 +45,24 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
50
45
|
}, 1000);
|
|
51
46
|
this.join();
|
|
52
47
|
}
|
|
48
|
+
onOpen = () => {
|
|
49
|
+
log(`@ ${this.url}: open`);
|
|
50
|
+
clearInterval(this.timerId);
|
|
51
|
+
this.timerId = undefined;
|
|
52
|
+
this.send(joinMessage(this.peerId));
|
|
53
|
+
};
|
|
54
|
+
// When a socket closes, or disconnects, remove it from the array.
|
|
55
|
+
onClose = () => {
|
|
56
|
+
log(`${this.url}: close`);
|
|
57
|
+
if (!this.timerId) {
|
|
58
|
+
if (this.peerId) {
|
|
59
|
+
this.connect(this.peerId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
onMessage = (event) => {
|
|
64
|
+
this.receiveMessage(event.data);
|
|
65
|
+
};
|
|
53
66
|
join() {
|
|
54
67
|
if (!this.socket) {
|
|
55
68
|
throw new Error("WTF, get a socket");
|
|
@@ -58,12 +71,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
58
71
|
this.send(joinMessage(this.peerId));
|
|
59
72
|
}
|
|
60
73
|
else {
|
|
61
|
-
|
|
62
|
-
if (!this.socket) {
|
|
63
|
-
throw new Error("WTF, get a socket");
|
|
64
|
-
}
|
|
65
|
-
this.send(joinMessage(this.peerId));
|
|
66
|
-
}, { once: true });
|
|
74
|
+
// The onOpen handler automatically sends a join message
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
disconnect() {
|
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.9",
|
|
4
4
|
"description": "isomorphic node/browser Websocket network adapter for Automerge Repo",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@automerge/automerge": "^2.1.0"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test": "vitest"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@automerge/automerge-repo": "^1.0.
|
|
19
|
+
"@automerge/automerge-repo": "^1.0.9",
|
|
20
20
|
"cbor-x": "^1.3.0",
|
|
21
21
|
"eventemitter3": "^5.0.1",
|
|
22
22
|
"isomorphic-ws": "^5.0.0",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "c277a6e8546cdd14236070d033ca62db68dfdeb3"
|
|
37
37
|
}
|
|
@@ -31,7 +31,14 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
connect(peerId: PeerId) {
|
|
34
|
-
|
|
34
|
+
// If we're reconnecting make sure we remove the old event listeners
|
|
35
|
+
// before creating a new connection.
|
|
36
|
+
if (this.socket) {
|
|
37
|
+
this.socket.removeEventListener("open", this.onOpen)
|
|
38
|
+
this.socket.removeEventListener("close", this.onClose)
|
|
39
|
+
this.socket.removeEventListener("message", this.onMessage)
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
if (!this.timerId) {
|
|
36
43
|
this.timerId = setInterval(() => this.connect(peerId), 5000)
|
|
37
44
|
}
|
|
@@ -40,24 +47,9 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
40
47
|
this.socket = new WebSocket(this.url)
|
|
41
48
|
this.socket.binaryType = "arraybuffer"
|
|
42
49
|
|
|
43
|
-
this.socket.addEventListener("open",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.timerId = undefined
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
// When a socket closes, or disconnects, remove it from the array.
|
|
50
|
-
this.socket.addEventListener("close", () => {
|
|
51
|
-
log(`${this.url}: close`)
|
|
52
|
-
if (!this.timerId) {
|
|
53
|
-
this.connect(peerId)
|
|
54
|
-
}
|
|
55
|
-
// log("Disconnected from server")
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
this.socket.addEventListener("message", (event: WebSocket.MessageEvent) =>
|
|
59
|
-
this.receiveMessage(event.data as Uint8Array)
|
|
60
|
-
)
|
|
50
|
+
this.socket.addEventListener("open", this.onOpen)
|
|
51
|
+
this.socket.addEventListener("close", this.onClose)
|
|
52
|
+
this.socket.addEventListener("message", this.onMessage)
|
|
61
53
|
|
|
62
54
|
// mark this adapter as ready if we haven't received an ack in 1 second.
|
|
63
55
|
// We might hear back from the other end at some point but we shouldn't
|
|
@@ -72,6 +64,27 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
72
64
|
this.join()
|
|
73
65
|
}
|
|
74
66
|
|
|
67
|
+
onOpen = () => {
|
|
68
|
+
log(`@ ${this.url}: open`)
|
|
69
|
+
clearInterval(this.timerId)
|
|
70
|
+
this.timerId = undefined
|
|
71
|
+
this.send(joinMessage(this.peerId!))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// When a socket closes, or disconnects, remove it from the array.
|
|
75
|
+
onClose = () => {
|
|
76
|
+
log(`${this.url}: close`)
|
|
77
|
+
if (!this.timerId) {
|
|
78
|
+
if (this.peerId) {
|
|
79
|
+
this.connect(this.peerId)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
onMessage = (event: WebSocket.MessageEvent) => {
|
|
85
|
+
this.receiveMessage(event.data as Uint8Array)
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
join() {
|
|
76
89
|
if (!this.socket) {
|
|
77
90
|
throw new Error("WTF, get a socket")
|
|
@@ -79,16 +92,7 @@ export class BrowserWebSocketClientAdapter extends WebSocketNetworkAdapter {
|
|
|
79
92
|
if (this.socket.readyState === WebSocket.OPEN) {
|
|
80
93
|
this.send(joinMessage(this.peerId!))
|
|
81
94
|
} else {
|
|
82
|
-
|
|
83
|
-
"open",
|
|
84
|
-
() => {
|
|
85
|
-
if (!this.socket) {
|
|
86
|
-
throw new Error("WTF, get a socket")
|
|
87
|
-
}
|
|
88
|
-
this.send(joinMessage(this.peerId!))
|
|
89
|
-
},
|
|
90
|
-
{ once: true }
|
|
91
|
-
)
|
|
95
|
+
// The onOpen handler automatically sends a join message
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
package/test/Websocket.test.ts
CHANGED
|
@@ -105,6 +105,37 @@ describe("The NodeWSServerAdapter", () => {
|
|
|
105
105
|
selectedProtocolVersion: "1",
|
|
106
106
|
})
|
|
107
107
|
})
|
|
108
|
+
|
|
109
|
+
it("should correctly clear event handlers on reconnect", async () => {
|
|
110
|
+
// This reproduces an issue where the BrowserWebSocketClientAdapter.connect
|
|
111
|
+
// call registered event handlers on the websocket but didn't clear them
|
|
112
|
+
// up again on a second call to connect. This combined with the reconnect
|
|
113
|
+
// timer to produce the following sequence of events:
|
|
114
|
+
//
|
|
115
|
+
// * first call to connect creates a socket and registers an "open"
|
|
116
|
+
// handler. The "open" handler will attempt to send a join message
|
|
117
|
+
// * The connection is slow, so the reconnect timer fires before "open",
|
|
118
|
+
// the reconnect timer calls connect again. this.socket is now a new socket
|
|
119
|
+
// * The other end replies to the first socket, "open"
|
|
120
|
+
// * The original "open" handler attempts to send a message, but on the new
|
|
121
|
+
// socket (because it uses this.socket), which isn't open yet, so we get an
|
|
122
|
+
// error that the socket is not ready
|
|
123
|
+
const { socket, server } = await startServer(0)
|
|
124
|
+
let port = (server.address()!! as AddressInfo).port
|
|
125
|
+
const serverUrl = `ws://localhost:${port}`
|
|
126
|
+
const serverAdapter = new NodeWSServerAdapter(socket)
|
|
127
|
+
const browserAdapter = new BrowserWebSocketClientAdapter(serverUrl)
|
|
128
|
+
|
|
129
|
+
const peerId = "testclient" as PeerId
|
|
130
|
+
browserAdapter.connect(peerId)
|
|
131
|
+
// simulate the reconnect timer firing before the other end has responded
|
|
132
|
+
// (which works here because we haven't yielded to the event loop yet so
|
|
133
|
+
// the server, which is on the same event loop as us, can't respond)
|
|
134
|
+
browserAdapter.connect(peerId)
|
|
135
|
+
// Now await, so the server responds on the first socket, if the listeners
|
|
136
|
+
// are cleaned up correctly we shouldn't throw
|
|
137
|
+
await pause(1)
|
|
138
|
+
})
|
|
108
139
|
})
|
|
109
140
|
|
|
110
141
|
async function serverHelloGivenClientHello(
|
|
@@ -144,3 +175,6 @@ async function firstMessage(
|
|
|
144
175
|
})
|
|
145
176
|
})
|
|
146
177
|
}
|
|
178
|
+
|
|
179
|
+
export const pause = (t = 0) =>
|
|
180
|
+
new Promise<void>(resolve => setTimeout(() => resolve(), t))
|