@mml-io/3d-web-user-networking 0.0.0-experimental-751c031-20230804
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/build/ReconnectingWebSocket.d.ts +27 -0
- package/build/UserNetworkingClient.d.ts +9 -0
- package/build/UserNetworkingCodec.d.ts +17 -0
- package/build/UserNetworkingServer.d.ts +16 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +358 -0
- package/build/index.js.map +7 -0
- package/build/messages.d.ts +25 -0
- package/build/user-networking-settings.d.ts +3 -0
- package/package.json +29 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
|
2
|
+
export type WebsocketFactory = (url: string) => WebSocket;
|
3
|
+
export declare enum WebsocketStatus {
|
4
|
+
Connecting = 0,
|
5
|
+
Connected = 1,
|
6
|
+
Reconnecting = 2,
|
7
|
+
Disconnected = 3
|
8
|
+
}
|
9
|
+
export declare abstract class ReconnectingWebSocket {
|
10
|
+
private url;
|
11
|
+
private websocketFactory;
|
12
|
+
private statusUpdateCallback;
|
13
|
+
private websocket;
|
14
|
+
private status;
|
15
|
+
private receivedMessageSinceOpen;
|
16
|
+
private backoffTime;
|
17
|
+
private stopped;
|
18
|
+
constructor(url: string, websocketFactory: WebsocketFactory, statusUpdateCallback: (status: WebsocketStatus) => void);
|
19
|
+
private setStatus;
|
20
|
+
sendUpdate(update: UserNetworkingClientUpdate): void;
|
21
|
+
private startWebSocketConnectionAttempt;
|
22
|
+
private waitBackoffTime;
|
23
|
+
protected abstract handleIncomingWebsocketMessage(message: MessageEvent): void;
|
24
|
+
protected send(message: object | Uint8Array): void;
|
25
|
+
private createWebsocketWithTimeout;
|
26
|
+
stop(): void;
|
27
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from "./ReconnectingWebSocket";
|
2
|
+
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
|
3
|
+
export declare class UserNetworkingClient extends ReconnectingWebSocket {
|
4
|
+
private setIdentityCallback;
|
5
|
+
private clientUpdate;
|
6
|
+
constructor(url: string, websocketFactory: WebsocketFactory, statusUpdateCallback: (status: WebsocketStatus) => void, setIdentityCallback: (id: number) => void, clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void);
|
7
|
+
sendUpdate(update: UserNetworkingClientUpdate): void;
|
8
|
+
protected handleIncomingWebsocketMessage(message: MessageEvent): void;
|
9
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export type UserNetworkingClientUpdate = {
|
2
|
+
id: number;
|
3
|
+
position: {
|
4
|
+
x: number;
|
5
|
+
y: number;
|
6
|
+
z: number;
|
7
|
+
};
|
8
|
+
rotation: {
|
9
|
+
quaternionY: number;
|
10
|
+
quaternionW: number;
|
11
|
+
};
|
12
|
+
state: number;
|
13
|
+
};
|
14
|
+
export declare class UserNetworkingCodec {
|
15
|
+
static encodeUpdate(update: UserNetworkingClientUpdate): Uint8Array;
|
16
|
+
static decodeUpdate(buffer: ArrayBuffer): UserNetworkingClientUpdate;
|
17
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import WebSocket from "ws";
|
2
|
+
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
|
3
|
+
export type Client = {
|
4
|
+
socket: WebSocket;
|
5
|
+
update: UserNetworkingClientUpdate;
|
6
|
+
};
|
7
|
+
export declare class UserNetworkingServer {
|
8
|
+
private clients;
|
9
|
+
private clientLastPong;
|
10
|
+
constructor();
|
11
|
+
heartBeat(): void;
|
12
|
+
pingClients(): void;
|
13
|
+
getId(): number;
|
14
|
+
connectClient(socket: WebSocket): void;
|
15
|
+
sendUpdates(): void;
|
16
|
+
}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
// src/UserNetworkingCodec.ts
|
2
|
+
var UserNetworkingCodec = class {
|
3
|
+
static encodeUpdate(update) {
|
4
|
+
const buffer = new ArrayBuffer(19);
|
5
|
+
const dataView = new DataView(buffer);
|
6
|
+
dataView.setUint16(0, update.id);
|
7
|
+
dataView.setFloat32(2, update.position.x);
|
8
|
+
dataView.setFloat32(6, update.position.y);
|
9
|
+
dataView.setFloat32(10, update.position.z);
|
10
|
+
dataView.setInt16(14, update.rotation.quaternionY * 32767);
|
11
|
+
dataView.setInt16(16, update.rotation.quaternionW * 32767);
|
12
|
+
dataView.setUint8(18, update.state);
|
13
|
+
return new Uint8Array(buffer);
|
14
|
+
}
|
15
|
+
static decodeUpdate(buffer) {
|
16
|
+
const dataView = new DataView(buffer);
|
17
|
+
const id = dataView.getUint16(0);
|
18
|
+
const x = dataView.getFloat32(2);
|
19
|
+
const y = dataView.getFloat32(6);
|
20
|
+
const z = dataView.getFloat32(10);
|
21
|
+
const quaternionY = dataView.getInt16(14) / 32767;
|
22
|
+
const quaternionW = dataView.getInt16(16) / 32767;
|
23
|
+
const state = dataView.getUint8(18);
|
24
|
+
const position = { x, y, z };
|
25
|
+
const rotation = { quaternionY, quaternionW };
|
26
|
+
return { id, position, rotation, state };
|
27
|
+
}
|
28
|
+
};
|
29
|
+
|
30
|
+
// src/messages.ts
|
31
|
+
var CONNECTED_MESSAGE_TYPE = "connected";
|
32
|
+
var DISCONNECTED_MESSAGE_TYPE = "disconnected";
|
33
|
+
var IDENTITY_MESSAGE_TYPE = "identity";
|
34
|
+
var PING_MESSAGE_TYPE = "ping";
|
35
|
+
var PONG_MESSAGE_TYPE = "pong";
|
36
|
+
|
37
|
+
// src/user-networking-settings.ts
|
38
|
+
var pingPongRate = 1500;
|
39
|
+
var heartBeatRate = 3500;
|
40
|
+
var packetsUpdateRate = 1 / 30 * 1e3;
|
41
|
+
|
42
|
+
// src/UserNetworkingServer.ts
|
43
|
+
var WebSocketOpenStatus = 1;
|
44
|
+
var UserNetworkingServer = class {
|
45
|
+
constructor() {
|
46
|
+
this.clients = /* @__PURE__ */ new Map();
|
47
|
+
this.clientLastPong = /* @__PURE__ */ new Map();
|
48
|
+
setInterval(this.sendUpdates.bind(this), packetsUpdateRate);
|
49
|
+
setInterval(this.pingClients.bind(this), pingPongRate);
|
50
|
+
setInterval(this.heartBeat.bind(this), heartBeatRate);
|
51
|
+
}
|
52
|
+
heartBeat() {
|
53
|
+
const now = Date.now();
|
54
|
+
this.clientLastPong.forEach((clientLastPong, id) => {
|
55
|
+
if (now - clientLastPong > heartBeatRate) {
|
56
|
+
this.clients.delete(id);
|
57
|
+
this.clientLastPong.delete(id);
|
58
|
+
const disconnectMessage = JSON.stringify({
|
59
|
+
id,
|
60
|
+
type: DISCONNECTED_MESSAGE_TYPE
|
61
|
+
});
|
62
|
+
for (const { socket: otherSocket } of this.clients.values()) {
|
63
|
+
if (otherSocket.readyState === WebSocketOpenStatus) {
|
64
|
+
otherSocket.send(disconnectMessage);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
});
|
69
|
+
}
|
70
|
+
pingClients() {
|
71
|
+
this.clients.forEach((client) => {
|
72
|
+
if (client.socket.readyState === WebSocketOpenStatus) {
|
73
|
+
client.socket.send(JSON.stringify({ type: "ping" }));
|
74
|
+
}
|
75
|
+
});
|
76
|
+
}
|
77
|
+
getId() {
|
78
|
+
let id = 1;
|
79
|
+
while (this.clients.has(id))
|
80
|
+
id++;
|
81
|
+
return id;
|
82
|
+
}
|
83
|
+
connectClient(socket) {
|
84
|
+
const id = this.getId();
|
85
|
+
console.log(`Client ID: ${id} joined`);
|
86
|
+
const connectMessage = JSON.stringify({
|
87
|
+
id,
|
88
|
+
type: CONNECTED_MESSAGE_TYPE
|
89
|
+
});
|
90
|
+
for (const { socket: otherSocket } of this.clients.values()) {
|
91
|
+
if (otherSocket.readyState === WebSocketOpenStatus) {
|
92
|
+
otherSocket.send(connectMessage);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
const identityMessage = JSON.stringify({
|
96
|
+
id,
|
97
|
+
type: IDENTITY_MESSAGE_TYPE
|
98
|
+
});
|
99
|
+
socket.send(identityMessage);
|
100
|
+
for (const { update } of this.clients.values()) {
|
101
|
+
socket.send(UserNetworkingCodec.encodeUpdate(update));
|
102
|
+
}
|
103
|
+
this.clients.set(id, {
|
104
|
+
socket,
|
105
|
+
update: {
|
106
|
+
id,
|
107
|
+
position: { x: 0, y: 0, z: 0 },
|
108
|
+
rotation: { quaternionY: 0, quaternionW: 0 },
|
109
|
+
state: 0
|
110
|
+
}
|
111
|
+
});
|
112
|
+
socket.on("message", (message, _isBinary) => {
|
113
|
+
if (message instanceof Buffer) {
|
114
|
+
const arrayBuffer = new Uint8Array(message).buffer;
|
115
|
+
const update = UserNetworkingCodec.decodeUpdate(arrayBuffer);
|
116
|
+
update.id = id;
|
117
|
+
if (this.clients.get(id) !== void 0) {
|
118
|
+
this.clients.get(id).update = update;
|
119
|
+
}
|
120
|
+
} else {
|
121
|
+
try {
|
122
|
+
const data = JSON.parse(message);
|
123
|
+
if (data.type === "pong") {
|
124
|
+
this.clientLastPong.set(id, Date.now());
|
125
|
+
}
|
126
|
+
} catch (e) {
|
127
|
+
console.error("Error parsing JSON message", message, e);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
});
|
131
|
+
socket.on("close", () => {
|
132
|
+
console.log("Client disconnected", id);
|
133
|
+
this.clients.delete(id);
|
134
|
+
const disconnectMessage = JSON.stringify({
|
135
|
+
id,
|
136
|
+
type: DISCONNECTED_MESSAGE_TYPE
|
137
|
+
});
|
138
|
+
for (const [clientId, { socket: otherSocket }] of this.clients) {
|
139
|
+
if (otherSocket.readyState === WebSocketOpenStatus) {
|
140
|
+
otherSocket.send(disconnectMessage);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
});
|
144
|
+
}
|
145
|
+
sendUpdates() {
|
146
|
+
for (const [clientId, client] of this.clients) {
|
147
|
+
const update = client.update;
|
148
|
+
const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
|
149
|
+
for (const [otherClientId, otherClient] of this.clients) {
|
150
|
+
if (otherClientId !== clientId && otherClient.socket.readyState === WebSocketOpenStatus) {
|
151
|
+
otherClient.socket.send(encodedUpdate);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
// src/ReconnectingWebSocket.ts
|
159
|
+
var WebsocketStatus = /* @__PURE__ */ ((WebsocketStatus3) => {
|
160
|
+
WebsocketStatus3[WebsocketStatus3["Connecting"] = 0] = "Connecting";
|
161
|
+
WebsocketStatus3[WebsocketStatus3["Connected"] = 1] = "Connected";
|
162
|
+
WebsocketStatus3[WebsocketStatus3["Reconnecting"] = 2] = "Reconnecting";
|
163
|
+
WebsocketStatus3[WebsocketStatus3["Disconnected"] = 3] = "Disconnected";
|
164
|
+
return WebsocketStatus3;
|
165
|
+
})(WebsocketStatus || {});
|
166
|
+
var startingBackoffTimeMilliseconds = 100;
|
167
|
+
var maximumBackoffTimeMilliseconds = 1e4;
|
168
|
+
var maximumWebsocketConnectionTimeout = 5e3;
|
169
|
+
var ReconnectingWebSocket = class {
|
170
|
+
constructor(url, websocketFactory, statusUpdateCallback) {
|
171
|
+
this.url = url;
|
172
|
+
this.websocketFactory = websocketFactory;
|
173
|
+
this.statusUpdateCallback = statusUpdateCallback;
|
174
|
+
this.websocket = null;
|
175
|
+
this.status = null;
|
176
|
+
this.receivedMessageSinceOpen = false;
|
177
|
+
this.backoffTime = startingBackoffTimeMilliseconds;
|
178
|
+
this.stopped = false;
|
179
|
+
this.setStatus(0 /* Connecting */);
|
180
|
+
this.startWebSocketConnectionAttempt();
|
181
|
+
}
|
182
|
+
setStatus(status) {
|
183
|
+
if (this.status !== status) {
|
184
|
+
this.status = status;
|
185
|
+
this.statusUpdateCallback(status);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
sendUpdate(update) {
|
189
|
+
if (!this.websocket) {
|
190
|
+
console.error("Not connected to the server");
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
|
194
|
+
this.send(encodedUpdate);
|
195
|
+
}
|
196
|
+
async startWebSocketConnectionAttempt() {
|
197
|
+
if (this.stopped) {
|
198
|
+
return;
|
199
|
+
}
|
200
|
+
while (true) {
|
201
|
+
if (this.stopped) {
|
202
|
+
return;
|
203
|
+
}
|
204
|
+
try {
|
205
|
+
await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);
|
206
|
+
break;
|
207
|
+
} catch (e) {
|
208
|
+
this.setStatus(2 /* Reconnecting */);
|
209
|
+
await this.waitBackoffTime();
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
async waitBackoffTime() {
|
214
|
+
console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, this.backoffTime));
|
216
|
+
this.backoffTime = Math.min(
|
217
|
+
// Introduce a small amount of randomness to prevent clients from retrying in lockstep
|
218
|
+
this.backoffTime * (1.5 + Math.random() * 0.5),
|
219
|
+
maximumBackoffTimeMilliseconds
|
220
|
+
);
|
221
|
+
}
|
222
|
+
send(message) {
|
223
|
+
if (!this.websocket) {
|
224
|
+
console.error("Not connected to the server");
|
225
|
+
return;
|
226
|
+
}
|
227
|
+
if (message instanceof Uint8Array) {
|
228
|
+
this.websocket.send(message);
|
229
|
+
} else {
|
230
|
+
this.websocket.send(JSON.stringify(message));
|
231
|
+
}
|
232
|
+
}
|
233
|
+
createWebsocketWithTimeout(timeout) {
|
234
|
+
return new Promise((resolve, reject) => {
|
235
|
+
const timeoutId = setTimeout(() => {
|
236
|
+
reject(new Error("websocket connection timed out"));
|
237
|
+
}, timeout);
|
238
|
+
const websocket = this.websocketFactory(this.url);
|
239
|
+
websocket.binaryType = "arraybuffer";
|
240
|
+
websocket.addEventListener("open", () => {
|
241
|
+
clearTimeout(timeoutId);
|
242
|
+
this.receivedMessageSinceOpen = false;
|
243
|
+
this.websocket = websocket;
|
244
|
+
this.setStatus(1 /* Connected */);
|
245
|
+
websocket.addEventListener("message", (event) => {
|
246
|
+
if (websocket !== this.websocket) {
|
247
|
+
console.log("Ignoring websocket message event because it is no longer current");
|
248
|
+
websocket.close();
|
249
|
+
return;
|
250
|
+
}
|
251
|
+
if (!this.receivedMessageSinceOpen) {
|
252
|
+
this.receivedMessageSinceOpen = true;
|
253
|
+
}
|
254
|
+
this.handleIncomingWebsocketMessage(event);
|
255
|
+
});
|
256
|
+
const onWebsocketClose = async () => {
|
257
|
+
if (websocket !== this.websocket) {
|
258
|
+
console.log("Ignoring websocket close event because it is no longer current");
|
259
|
+
return;
|
260
|
+
}
|
261
|
+
this.websocket = null;
|
262
|
+
if (this.stopped) {
|
263
|
+
this.setStatus(3 /* Disconnected */);
|
264
|
+
return;
|
265
|
+
}
|
266
|
+
if (!this.receivedMessageSinceOpen) {
|
267
|
+
await this.waitBackoffTime();
|
268
|
+
}
|
269
|
+
this.setStatus(2 /* Reconnecting */);
|
270
|
+
this.startWebSocketConnectionAttempt();
|
271
|
+
};
|
272
|
+
websocket.addEventListener("close", (e) => {
|
273
|
+
if (websocket !== this.websocket) {
|
274
|
+
console.warn("Ignoring websocket close event because it is no longer current");
|
275
|
+
return;
|
276
|
+
}
|
277
|
+
console.log("NetworkedDOMWebsocket close", e);
|
278
|
+
onWebsocketClose();
|
279
|
+
});
|
280
|
+
websocket.addEventListener("error", (e) => {
|
281
|
+
if (websocket !== this.websocket) {
|
282
|
+
console.log("Ignoring websocket error event because it is no longer current");
|
283
|
+
return;
|
284
|
+
}
|
285
|
+
console.error("NetworkedDOMWebsocket error", e);
|
286
|
+
onWebsocketClose();
|
287
|
+
});
|
288
|
+
resolve(websocket);
|
289
|
+
});
|
290
|
+
websocket.addEventListener("error", (e) => {
|
291
|
+
clearTimeout(timeoutId);
|
292
|
+
reject(e);
|
293
|
+
});
|
294
|
+
});
|
295
|
+
}
|
296
|
+
stop() {
|
297
|
+
this.stopped = true;
|
298
|
+
if (this.websocket !== null) {
|
299
|
+
this.websocket.close();
|
300
|
+
this.websocket = null;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
};
|
304
|
+
|
305
|
+
// src/UserNetworkingClient.ts
|
306
|
+
var UserNetworkingClient = class extends ReconnectingWebSocket {
|
307
|
+
constructor(url, websocketFactory, statusUpdateCallback, setIdentityCallback, clientUpdate) {
|
308
|
+
super(url, websocketFactory, statusUpdateCallback);
|
309
|
+
this.setIdentityCallback = setIdentityCallback;
|
310
|
+
this.clientUpdate = clientUpdate;
|
311
|
+
}
|
312
|
+
sendUpdate(update) {
|
313
|
+
const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
|
314
|
+
this.send(encodedUpdate);
|
315
|
+
}
|
316
|
+
handleIncomingWebsocketMessage(message) {
|
317
|
+
if (typeof message.data === "string") {
|
318
|
+
const parsed = JSON.parse(message.data);
|
319
|
+
switch (parsed.type) {
|
320
|
+
case IDENTITY_MESSAGE_TYPE:
|
321
|
+
console.log(`Assigned ID: ${parsed.id}`);
|
322
|
+
this.setIdentityCallback(parsed.id);
|
323
|
+
break;
|
324
|
+
case CONNECTED_MESSAGE_TYPE:
|
325
|
+
console.log(`Client ID: ${parsed.id} joined`);
|
326
|
+
break;
|
327
|
+
case DISCONNECTED_MESSAGE_TYPE:
|
328
|
+
console.log(`Client ID: ${parsed.id} left`);
|
329
|
+
this.clientUpdate(parsed.id, null);
|
330
|
+
break;
|
331
|
+
case PING_MESSAGE_TYPE: {
|
332
|
+
this.send({ type: "pong" });
|
333
|
+
break;
|
334
|
+
}
|
335
|
+
default:
|
336
|
+
console.warn("unknown message type received", parsed);
|
337
|
+
}
|
338
|
+
} else if (message.data instanceof ArrayBuffer) {
|
339
|
+
const userNetworkingClientUpdate = UserNetworkingCodec.decodeUpdate(message.data);
|
340
|
+
this.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);
|
341
|
+
} else {
|
342
|
+
console.error("Unhandled message type", message.data);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
};
|
346
|
+
export {
|
347
|
+
CONNECTED_MESSAGE_TYPE,
|
348
|
+
DISCONNECTED_MESSAGE_TYPE,
|
349
|
+
IDENTITY_MESSAGE_TYPE,
|
350
|
+
PING_MESSAGE_TYPE,
|
351
|
+
PONG_MESSAGE_TYPE,
|
352
|
+
ReconnectingWebSocket,
|
353
|
+
UserNetworkingClient,
|
354
|
+
UserNetworkingCodec,
|
355
|
+
UserNetworkingServer,
|
356
|
+
WebsocketStatus
|
357
|
+
};
|
358
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"version": 3,
|
3
|
+
"sources": ["../src/UserNetworkingCodec.ts", "../src/messages.ts", "../src/user-networking-settings.ts", "../src/UserNetworkingServer.ts", "../src/ReconnectingWebSocket.ts", "../src/UserNetworkingClient.ts"],
|
4
|
+
"sourcesContent": ["export type UserNetworkingClientUpdate = {\n id: number;\n position: { x: number; y: number; z: number };\n rotation: { quaternionY: number; quaternionW: number };\n state: number;\n};\n\nexport class UserNetworkingCodec {\n static encodeUpdate(update: UserNetworkingClientUpdate): Uint8Array {\n const buffer = new ArrayBuffer(19);\n const dataView = new DataView(buffer);\n dataView.setUint16(0, update.id); // id\n dataView.setFloat32(2, update.position.x); // position.x\n dataView.setFloat32(6, update.position.y); // position.y\n dataView.setFloat32(10, update.position.z); // position.z\n dataView.setInt16(14, update.rotation.quaternionY * 32767); // quaternion.y\n dataView.setInt16(16, update.rotation.quaternionW * 32767); // quaternion.w\n dataView.setUint8(18, update.state); // animationState\n return new Uint8Array(buffer);\n }\n\n static decodeUpdate(buffer: ArrayBuffer): UserNetworkingClientUpdate {\n const dataView = new DataView(buffer);\n const id = dataView.getUint16(0); // id\n const x = dataView.getFloat32(2); // position.x\n const y = dataView.getFloat32(6); // position.y\n const z = dataView.getFloat32(10); // position.z\n const quaternionY = dataView.getInt16(14) / 32767; // quaternion.y\n const quaternionW = dataView.getInt16(16) / 32767; // quaternion.w\n const state = dataView.getUint8(18); // animationState\n const position = { x, y, z };\n const rotation = { quaternionY, quaternionW };\n return { id, position, rotation, state };\n }\n}\n", "export const CONNECTED_MESSAGE_TYPE = \"connected\";\nexport const DISCONNECTED_MESSAGE_TYPE = \"disconnected\";\nexport const IDENTITY_MESSAGE_TYPE = \"identity\";\nexport const PING_MESSAGE_TYPE = \"ping\";\nexport const PONG_MESSAGE_TYPE = \"pong\";\n\nexport type ConnectedMessage = {\n type: typeof CONNECTED_MESSAGE_TYPE;\n id: number;\n};\n\nexport type IdentityMessage = {\n type: typeof IDENTITY_MESSAGE_TYPE;\n id: number;\n};\n\nexport type DisconnectedMessage = {\n type: typeof DISCONNECTED_MESSAGE_TYPE;\n id: number;\n};\n\nexport type FromServerPingMessage = {\n type: typeof PING_MESSAGE_TYPE;\n};\n\nexport type FromServerMessage =\n | ConnectedMessage\n | IdentityMessage\n | DisconnectedMessage\n | FromServerPingMessage;\n\nexport type FromClientPongMessage = {\n type: typeof PONG_MESSAGE_TYPE;\n};\n\nexport type FromClientMessage = FromClientPongMessage;\n", "export const pingPongRate: number = 1500;\nexport const heartBeatRate: number = 3500;\nexport const packetsUpdateRate: number = (1 / 30) * 1000;\n", "import WebSocket from \"ws\";\n\nimport {\n CONNECTED_MESSAGE_TYPE,\n DISCONNECTED_MESSAGE_TYPE,\n FromClientMessage,\n FromServerMessage,\n IDENTITY_MESSAGE_TYPE,\n} from \"./messages\";\nimport { heartBeatRate, packetsUpdateRate, pingPongRate } from \"./user-networking-settings\";\nimport { UserNetworkingClientUpdate, UserNetworkingCodec } from \"./UserNetworkingCodec\";\n\nexport type Client = {\n socket: WebSocket;\n update: UserNetworkingClientUpdate;\n};\n\nconst WebSocketOpenStatus = 1;\n\nexport class UserNetworkingServer {\n private clients: Map<number, Client> = new Map();\n private clientLastPong: Map<number, number> = new Map();\n\n constructor() {\n setInterval(this.sendUpdates.bind(this), packetsUpdateRate);\n setInterval(this.pingClients.bind(this), pingPongRate);\n setInterval(this.heartBeat.bind(this), heartBeatRate);\n }\n\n heartBeat() {\n const now = Date.now();\n this.clientLastPong.forEach((clientLastPong, id) => {\n if (now - clientLastPong > heartBeatRate) {\n this.clients.delete(id);\n this.clientLastPong.delete(id);\n const disconnectMessage = JSON.stringify({\n id,\n type: DISCONNECTED_MESSAGE_TYPE,\n } as FromServerMessage);\n for (const { socket: otherSocket } of this.clients.values()) {\n if (otherSocket.readyState === WebSocketOpenStatus) {\n otherSocket.send(disconnectMessage);\n }\n }\n }\n });\n }\n\n pingClients() {\n this.clients.forEach((client) => {\n if (client.socket.readyState === WebSocketOpenStatus) {\n client.socket.send(JSON.stringify({ type: \"ping\" } as FromServerMessage));\n }\n });\n }\n\n getId(): number {\n let id = 1;\n while (this.clients.has(id)) id++;\n return id;\n }\n\n connectClient(socket: WebSocket) {\n const id = this.getId();\n console.log(`Client ID: ${id} joined`);\n\n const connectMessage = JSON.stringify({\n id,\n type: CONNECTED_MESSAGE_TYPE,\n } as FromServerMessage);\n for (const { socket: otherSocket } of this.clients.values()) {\n if (otherSocket.readyState === WebSocketOpenStatus) {\n otherSocket.send(connectMessage);\n }\n }\n\n const identityMessage = JSON.stringify({\n id,\n type: IDENTITY_MESSAGE_TYPE,\n } as FromServerMessage);\n socket.send(identityMessage);\n\n for (const { update } of this.clients.values()) {\n socket.send(UserNetworkingCodec.encodeUpdate(update));\n }\n\n this.clients.set(id, {\n socket: socket as WebSocket,\n update: {\n id,\n position: { x: 0, y: 0, z: 0 },\n rotation: { quaternionY: 0, quaternionW: 0 },\n state: 0,\n },\n });\n\n socket.on(\"message\", (message: WebSocket.Data, _isBinary: boolean) => {\n if (message instanceof Buffer) {\n const arrayBuffer = new Uint8Array(message).buffer;\n const update = UserNetworkingCodec.decodeUpdate(arrayBuffer);\n update.id = id;\n if (this.clients.get(id) !== undefined) {\n this.clients.get(id)!.update = update;\n }\n } else {\n try {\n const data = JSON.parse(message as string) as FromClientMessage;\n if (data.type === \"pong\") {\n this.clientLastPong.set(id, Date.now());\n }\n } catch (e) {\n console.error(\"Error parsing JSON message\", message, e);\n }\n }\n });\n\n socket.on(\"close\", () => {\n console.log(\"Client disconnected\", id);\n this.clients.delete(id);\n const disconnectMessage = JSON.stringify({\n id,\n type: DISCONNECTED_MESSAGE_TYPE,\n } as FromServerMessage);\n for (const [clientId, { socket: otherSocket }] of this.clients) {\n if (otherSocket.readyState === WebSocketOpenStatus) {\n otherSocket.send(disconnectMessage);\n }\n }\n });\n }\n\n sendUpdates(): void {\n for (const [clientId, client] of this.clients) {\n const update = client.update;\n const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);\n\n for (const [otherClientId, otherClient] of this.clients) {\n if (otherClientId !== clientId && otherClient.socket.readyState === WebSocketOpenStatus) {\n otherClient.socket.send(encodedUpdate);\n }\n }\n }\n }\n}\n", "import { UserNetworkingClientUpdate, UserNetworkingCodec } from \"./UserNetworkingCodec\";\n\nexport type WebsocketFactory = (url: string) => WebSocket;\n\nexport enum WebsocketStatus {\n Connecting,\n Connected,\n Reconnecting,\n Disconnected,\n}\n\nconst startingBackoffTimeMilliseconds = 100;\nconst maximumBackoffTimeMilliseconds = 10000;\nconst maximumWebsocketConnectionTimeout = 5000;\n\nexport abstract class ReconnectingWebSocket {\n private websocket: WebSocket | null = null;\n private status: WebsocketStatus | null = null;\n private receivedMessageSinceOpen = false;\n private backoffTime = startingBackoffTimeMilliseconds;\n private stopped = false;\n\n constructor(\n private url: string,\n private websocketFactory: WebsocketFactory,\n private statusUpdateCallback: (status: WebsocketStatus) => void,\n ) {\n this.setStatus(WebsocketStatus.Connecting);\n this.startWebSocketConnectionAttempt();\n }\n\n private setStatus(status: WebsocketStatus) {\n if (this.status !== status) {\n this.status = status;\n this.statusUpdateCallback(status);\n }\n }\n\n public sendUpdate(update: UserNetworkingClientUpdate): void {\n if (!this.websocket) {\n console.error(\"Not connected to the server\");\n return;\n }\n const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);\n this.send(encodedUpdate);\n }\n\n private async startWebSocketConnectionAttempt() {\n if (this.stopped) {\n return;\n }\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.stopped) {\n return;\n }\n try {\n await this.createWebsocketWithTimeout(maximumWebsocketConnectionTimeout);\n break;\n } catch (e) {\n // Connection failed, retry with backoff\n this.setStatus(WebsocketStatus.Reconnecting);\n await this.waitBackoffTime();\n }\n }\n }\n\n private async waitBackoffTime(): Promise<void> {\n console.warn(`Websocket connection to '${this.url}' failed: retrying in ${this.backoffTime}ms`);\n await new Promise((resolve) => setTimeout(resolve, this.backoffTime));\n this.backoffTime = Math.min(\n // Introduce a small amount of randomness to prevent clients from retrying in lockstep\n this.backoffTime * (1.5 + Math.random() * 0.5),\n maximumBackoffTimeMilliseconds,\n );\n }\n\n protected abstract handleIncomingWebsocketMessage(message: MessageEvent): void;\n\n protected send(message: object | Uint8Array): void {\n if (!this.websocket) {\n console.error(\"Not connected to the server\");\n return;\n }\n if (message instanceof Uint8Array) {\n this.websocket.send(message);\n } else {\n this.websocket.send(JSON.stringify(message));\n }\n }\n\n private createWebsocketWithTimeout(timeout: number): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(\"websocket connection timed out\"));\n }, timeout);\n const websocket = this.websocketFactory(this.url);\n websocket.binaryType = \"arraybuffer\";\n websocket.addEventListener(\"open\", () => {\n clearTimeout(timeoutId);\n this.receivedMessageSinceOpen = false;\n this.websocket = websocket;\n this.setStatus(WebsocketStatus.Connected);\n\n websocket.addEventListener(\"message\", (event) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket message event because it is no longer current\");\n websocket.close();\n return;\n }\n if (!this.receivedMessageSinceOpen) {\n this.receivedMessageSinceOpen = true;\n }\n this.handleIncomingWebsocketMessage(event);\n });\n\n const onWebsocketClose = async () => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket close event because it is no longer current\");\n return;\n }\n this.websocket = null;\n if (this.stopped) {\n // This closing is expected. The client closed the websocket.\n this.setStatus(WebsocketStatus.Disconnected);\n return;\n }\n if (!this.receivedMessageSinceOpen) {\n // The websocket did not deliver any contents. It may have been successfully opened, but immediately closed. This client should back off to prevent this happening in a rapid loop.\n await this.waitBackoffTime();\n }\n // The websocket closed unexpectedly. Try to reconnect.\n this.setStatus(WebsocketStatus.Reconnecting);\n this.startWebSocketConnectionAttempt();\n };\n\n websocket.addEventListener(\"close\", (e) => {\n if (websocket !== this.websocket) {\n console.warn(\"Ignoring websocket close event because it is no longer current\");\n return;\n }\n console.log(\"NetworkedDOMWebsocket close\", e);\n onWebsocketClose();\n });\n websocket.addEventListener(\"error\", (e) => {\n if (websocket !== this.websocket) {\n console.log(\"Ignoring websocket error event because it is no longer current\");\n return;\n }\n console.error(\"NetworkedDOMWebsocket error\", e);\n onWebsocketClose();\n });\n\n resolve(websocket);\n });\n websocket.addEventListener(\"error\", (e) => {\n clearTimeout(timeoutId);\n reject(e);\n });\n });\n }\n\n public stop() {\n this.stopped = true;\n if (this.websocket !== null) {\n this.websocket.close();\n this.websocket = null;\n }\n }\n}\n", "import {\n CONNECTED_MESSAGE_TYPE,\n DISCONNECTED_MESSAGE_TYPE,\n FromClientMessage,\n FromServerMessage,\n IDENTITY_MESSAGE_TYPE,\n PING_MESSAGE_TYPE,\n} from \"./messages\";\nimport { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from \"./ReconnectingWebSocket\";\nimport { UserNetworkingClientUpdate, UserNetworkingCodec } from \"./UserNetworkingCodec\";\n\nexport class UserNetworkingClient extends ReconnectingWebSocket {\n constructor(\n url: string,\n websocketFactory: WebsocketFactory,\n statusUpdateCallback: (status: WebsocketStatus) => void,\n private setIdentityCallback: (id: number) => void,\n private clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void,\n ) {\n super(url, websocketFactory, statusUpdateCallback);\n }\n\n public sendUpdate(update: UserNetworkingClientUpdate): void {\n const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);\n this.send(encodedUpdate);\n }\n\n protected handleIncomingWebsocketMessage(message: MessageEvent) {\n if (typeof message.data === \"string\") {\n const parsed = JSON.parse(message.data) as FromServerMessage;\n switch (parsed.type) {\n case IDENTITY_MESSAGE_TYPE:\n console.log(`Assigned ID: ${parsed.id}`);\n this.setIdentityCallback(parsed.id);\n break;\n case CONNECTED_MESSAGE_TYPE:\n console.log(`Client ID: ${parsed.id} joined`);\n break;\n case DISCONNECTED_MESSAGE_TYPE:\n console.log(`Client ID: ${parsed.id} left`);\n this.clientUpdate(parsed.id, null);\n break;\n case PING_MESSAGE_TYPE: {\n this.send({ type: \"pong\" } as FromClientMessage);\n break;\n }\n default:\n console.warn(\"unknown message type received\", parsed);\n }\n } else if (message.data instanceof ArrayBuffer) {\n const userNetworkingClientUpdate = UserNetworkingCodec.decodeUpdate(message.data);\n this.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);\n } else {\n console.error(\"Unhandled message type\", message.data);\n }\n }\n}\n"],
|
5
|
+
"mappings": "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",
|
6
|
+
"names": ["WebsocketStatus"]
|
7
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
export declare const CONNECTED_MESSAGE_TYPE = "connected";
|
2
|
+
export declare const DISCONNECTED_MESSAGE_TYPE = "disconnected";
|
3
|
+
export declare const IDENTITY_MESSAGE_TYPE = "identity";
|
4
|
+
export declare const PING_MESSAGE_TYPE = "ping";
|
5
|
+
export declare const PONG_MESSAGE_TYPE = "pong";
|
6
|
+
export type ConnectedMessage = {
|
7
|
+
type: typeof CONNECTED_MESSAGE_TYPE;
|
8
|
+
id: number;
|
9
|
+
};
|
10
|
+
export type IdentityMessage = {
|
11
|
+
type: typeof IDENTITY_MESSAGE_TYPE;
|
12
|
+
id: number;
|
13
|
+
};
|
14
|
+
export type DisconnectedMessage = {
|
15
|
+
type: typeof DISCONNECTED_MESSAGE_TYPE;
|
16
|
+
id: number;
|
17
|
+
};
|
18
|
+
export type FromServerPingMessage = {
|
19
|
+
type: typeof PING_MESSAGE_TYPE;
|
20
|
+
};
|
21
|
+
export type FromServerMessage = ConnectedMessage | IdentityMessage | DisconnectedMessage | FromServerPingMessage;
|
22
|
+
export type FromClientPongMessage = {
|
23
|
+
type: typeof PONG_MESSAGE_TYPE;
|
24
|
+
};
|
25
|
+
export type FromClientMessage = FromClientPongMessage;
|
package/package.json
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
{
|
2
|
+
"name": "@mml-io/3d-web-user-networking",
|
3
|
+
"version": "0.0.0-experimental-751c031-20230804",
|
4
|
+
"main": "./build/index.js",
|
5
|
+
"types": "./build/index.d.ts",
|
6
|
+
"type": "module",
|
7
|
+
"files": [
|
8
|
+
"/build"
|
9
|
+
],
|
10
|
+
"scripts": {
|
11
|
+
"build": "rimraf ./build && node ./build.js --build",
|
12
|
+
"iterate": "node ./build.js --watch",
|
13
|
+
"type-check": "tsc --noEmit",
|
14
|
+
"lint": "eslint \"./src/**/*.{ts,}\" --max-warnings 0",
|
15
|
+
"lint:fix": "eslint \"./src/**/*.{ts,}\" --fix",
|
16
|
+
"test": "jest"
|
17
|
+
},
|
18
|
+
"dependencies": {
|
19
|
+
"ws": "^8.13.0"
|
20
|
+
},
|
21
|
+
"devDependencies": {
|
22
|
+
"@types/express": "^4.17.17",
|
23
|
+
"@types/express-ws": "^3.0.1",
|
24
|
+
"@types/node": "^20.1.7",
|
25
|
+
"@types/ws": "^8.5.4",
|
26
|
+
"express": "4.18.2",
|
27
|
+
"express-ws": "5.0.2"
|
28
|
+
}
|
29
|
+
}
|