@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.
- package/dist/BrowserWebSocketClientAdapter.d.ts +3 -3
- package/dist/BrowserWebSocketClientAdapter.d.ts.map +1 -1
- package/dist/BrowserWebSocketClientAdapter.js +15 -11
- package/dist/NodeWSServerAdapter.d.ts +2 -2
- package/dist/NodeWSServerAdapter.d.ts.map +1 -1
- package/dist/NodeWSServerAdapter.js +37 -29
- package/dist/messages.d.ts +5 -1
- package/dist/messages.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/BrowserWebSocketClientAdapter.ts +24 -11
- package/src/NodeWSServerAdapter.ts +40 -31
- package/src/messages.ts +9 -1
- package/test/Websocket.test.ts +92 -54
|
@@ -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,
|
|
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;
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
if (existingSocket
|
|
90
|
-
existingSocket.
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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":
|
package/dist/messages.d.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
|
/** 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 */
|
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,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,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.
|
|
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.
|
|
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": "
|
|
33
|
+
"gitHead": "d73e71588c3835a172fdf4d19e56a1f946c041ed"
|
|
34
34
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
if (existingSocket
|
|
130
|
-
existingSocket.
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 */
|
package/test/Websocket.test.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { next as A } from "@automerge/automerge"
|
|
2
|
-
import {
|
|
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(
|
|
167
|
+
return new Promise(resolve => {
|
|
160
168
|
const timer = setTimeout(() => {
|
|
161
169
|
resolve(null)
|
|
162
170
|
}, 1000)
|
|
163
|
-
socket.once("message",
|
|
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>>(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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(
|
|
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(
|
|
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({
|
|
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({
|
|
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(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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(
|
|
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(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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(
|
|
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
|