@mml-io/3d-web-user-networking 0.15.0 → 0.16.0

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.
@@ -0,0 +1,5 @@
1
+ import { CharacterDescription } from "./UserNetworkingMessages";
2
+ export type UserData = {
3
+ readonly username: string;
4
+ readonly characterDescription: CharacterDescription;
5
+ };
@@ -1,9 +1,19 @@
1
1
  import { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from "./ReconnectingWebSocket";
2
2
  import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
3
+ import { CharacterDescription, FromClientMessage } from "./UserNetworkingMessages";
4
+ export type UserNetworkingClientConfig = {
5
+ url: string;
6
+ sessionToken: string;
7
+ websocketFactory: WebsocketFactory;
8
+ statusUpdateCallback: (status: WebsocketStatus) => void;
9
+ assignedIdentity: (clientId: number) => void;
10
+ clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void;
11
+ clientProfileUpdated: (id: number, username: string, characterDescription: CharacterDescription) => void;
12
+ };
3
13
  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);
14
+ private config;
15
+ constructor(config: UserNetworkingClientConfig);
7
16
  sendUpdate(update: UserNetworkingClientUpdate): void;
17
+ sendMessage(message: FromClientMessage): void;
8
18
  protected handleIncomingWebsocketMessage(message: MessageEvent): void;
9
19
  }
@@ -0,0 +1,53 @@
1
+ export declare const DISCONNECTED_MESSAGE_TYPE = "disconnected";
2
+ export declare const IDENTITY_MESSAGE_TYPE = "identity";
3
+ export declare const USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
4
+ export declare const USER_PROFILE_MESSAGE_TYPE = "user_profile";
5
+ export declare const USER_UPDATE_MESSAGE_TYPE = "user_update";
6
+ export declare const PING_MESSAGE_TYPE = "ping";
7
+ export declare const PONG_MESSAGE_TYPE = "pong";
8
+ export type IdentityMessage = {
9
+ type: typeof IDENTITY_MESSAGE_TYPE;
10
+ id: number;
11
+ };
12
+ export type CharacterDescription = {
13
+ meshFileUrl?: string;
14
+ mmlCharacterUrl?: string;
15
+ mmlCharacterString?: string;
16
+ } & ({
17
+ meshFileUrl: string;
18
+ } | {
19
+ mmlCharacterUrl: string;
20
+ } | {
21
+ mmlCharacterString: string;
22
+ });
23
+ export type UserProfileMessage = {
24
+ type: typeof USER_PROFILE_MESSAGE_TYPE;
25
+ id: number;
26
+ username: string;
27
+ characterDescription: CharacterDescription;
28
+ };
29
+ export type DisconnectedMessage = {
30
+ type: typeof DISCONNECTED_MESSAGE_TYPE;
31
+ id: number;
32
+ };
33
+ export type FromServerPingMessage = {
34
+ type: typeof PING_MESSAGE_TYPE;
35
+ };
36
+ export type FromServerMessage = IdentityMessage | UserProfileMessage | DisconnectedMessage | FromServerPingMessage;
37
+ export type FromClientPongMessage = {
38
+ type: typeof PONG_MESSAGE_TYPE;
39
+ };
40
+ export type UserIdentity = {
41
+ characterDescription: CharacterDescription | null;
42
+ username: string | null;
43
+ };
44
+ export type UserAuthenticateMessage = {
45
+ type: typeof USER_AUTHENTICATE_MESSAGE_TYPE;
46
+ sessionToken: string;
47
+ userIdentity?: UserIdentity;
48
+ };
49
+ export type UserUpdateMessage = {
50
+ type: typeof USER_UPDATE_MESSAGE_TYPE;
51
+ userIdentity: UserIdentity;
52
+ };
53
+ export type FromClientMessage = FromClientPongMessage | UserAuthenticateMessage | UserUpdateMessage;
@@ -1,16 +1,32 @@
1
1
  import WebSocket from "ws";
2
+ import { UserData } from "./UserData";
2
3
  import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
4
+ import { UserIdentity } from "./UserNetworkingMessages";
3
5
  export type Client = {
4
6
  socket: WebSocket;
7
+ id: number;
8
+ lastPong: number;
5
9
  update: UserNetworkingClientUpdate;
10
+ authenticatedUser: UserData | null;
11
+ };
12
+ export type UserNetworkingServerOptions = {
13
+ onClientConnect: (clientId: number, sessionToken: string, userIdentity?: UserIdentity) => Promise<UserData | null> | UserData | null;
14
+ onClientUserIdentityUpdate: (clientId: number, userIdentity: UserIdentity) => Promise<UserData | null> | UserData | null;
15
+ onClientDisconnect: (clientId: number) => void;
6
16
  };
7
17
  export declare class UserNetworkingServer {
8
- private clients;
9
- private clientLastPong;
10
- constructor();
11
- heartBeat(): void;
12
- pingClients(): void;
13
- getId(): number;
18
+ private options;
19
+ private allClientsById;
20
+ private authenticatedClientsById;
21
+ constructor(options: UserNetworkingServerOptions);
22
+ private heartBeat;
23
+ private pingClients;
24
+ private getId;
14
25
  connectClient(socket: WebSocket): void;
15
- sendUpdates(): void;
26
+ private handleDisconnectedClient;
27
+ private handleUserAuth;
28
+ updateUserCharacter(clientId: number, userData: UserData): void;
29
+ private internalUpdateUser;
30
+ private handleUserUpdate;
31
+ private sendUpdates;
16
32
  }
package/build/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./UserNetworkingCodec";
2
2
  export * from "./UserNetworkingServer";
3
3
  export * from "./UserNetworkingClient";
4
+ export * from "./UserData";
4
5
  export * from "./ReconnectingWebSocket";
5
- export * from "./messages";
6
+ export * from "./UserNetworkingMessages";
package/build/index.js CHANGED
@@ -27,48 +27,42 @@ var UserNetworkingCodec = class {
27
27
  }
28
28
  };
29
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
30
  // src/user-networking-settings.ts
38
31
  var pingPongRate = 1500;
39
32
  var heartBeatRate = 15e3;
40
33
  var packetsUpdateRate = 1 / 30 * 1e3;
41
34
 
35
+ // src/UserNetworkingMessages.ts
36
+ var DISCONNECTED_MESSAGE_TYPE = "disconnected";
37
+ var IDENTITY_MESSAGE_TYPE = "identity";
38
+ var USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
39
+ var USER_PROFILE_MESSAGE_TYPE = "user_profile";
40
+ var USER_UPDATE_MESSAGE_TYPE = "user_update";
41
+ var PING_MESSAGE_TYPE = "ping";
42
+ var PONG_MESSAGE_TYPE = "pong";
43
+
42
44
  // src/UserNetworkingServer.ts
43
45
  var WebSocketOpenStatus = 1;
44
46
  var UserNetworkingServer = class {
45
- constructor() {
46
- this.clients = /* @__PURE__ */ new Map();
47
- this.clientLastPong = /* @__PURE__ */ new Map();
47
+ constructor(options) {
48
+ this.options = options;
49
+ this.allClientsById = /* @__PURE__ */ new Map();
50
+ this.authenticatedClientsById = /* @__PURE__ */ new Map();
48
51
  setInterval(this.sendUpdates.bind(this), packetsUpdateRate);
49
52
  setInterval(this.pingClients.bind(this), pingPongRate);
50
53
  setInterval(this.heartBeat.bind(this), heartBeatRate);
51
54
  }
52
55
  heartBeat() {
53
56
  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
- }
57
+ this.allClientsById.forEach((client) => {
58
+ if (now - client.lastPong > heartBeatRate) {
59
+ client.socket.close();
60
+ this.handleDisconnectedClient(client);
67
61
  }
68
62
  });
69
63
  }
70
64
  pingClients() {
71
- this.clients.forEach((client) => {
65
+ this.authenticatedClientsById.forEach((client) => {
72
66
  if (client.socket.readyState === WebSocketOpenStatus) {
73
67
  client.socket.send(JSON.stringify({ type: "ping" }));
74
68
  }
@@ -76,77 +70,189 @@ var UserNetworkingServer = class {
76
70
  }
77
71
  getId() {
78
72
  let id = 1;
79
- while (this.clients.has(id))
73
+ while (this.allClientsById.has(id)) {
80
74
  id++;
75
+ }
81
76
  return id;
82
77
  }
83
78
  connectClient(socket) {
84
79
  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({
80
+ console.log(`Client ID: ${id} joined, waiting for user-identification`);
81
+ const client = {
96
82
  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, {
83
+ lastPong: Date.now(),
104
84
  socket,
85
+ authenticatedUser: null,
105
86
  update: {
106
87
  id,
107
88
  position: { x: 0, y: 0, z: 0 },
108
89
  rotation: { quaternionY: 0, quaternionW: 1 },
109
90
  state: 0
110
91
  }
111
- });
92
+ };
93
+ this.allClientsById.set(id, client);
112
94
  socket.on("message", (message, _isBinary) => {
113
95
  if (message instanceof Buffer) {
114
96
  const arrayBuffer = new Uint8Array(message).buffer;
115
97
  const update = UserNetworkingCodec.decodeUpdate(arrayBuffer);
116
98
  update.id = id;
117
- if (this.clients.get(id) !== void 0) {
118
- this.clients.get(id).update = update;
119
- }
99
+ client.update = update;
120
100
  } else {
101
+ let parsed;
121
102
  try {
122
- const data = JSON.parse(message);
123
- if (data.type === "pong") {
124
- this.clientLastPong.set(id, Date.now());
125
- }
103
+ parsed = JSON.parse(message);
126
104
  } catch (e) {
127
105
  console.error("Error parsing JSON message", message, e);
106
+ return;
107
+ }
108
+ if (!client.authenticatedUser) {
109
+ if (parsed.type === USER_AUTHENTICATE_MESSAGE_TYPE) {
110
+ this.handleUserAuth(client, parsed).then((authResult) => {
111
+ if (!authResult) {
112
+ socket.close();
113
+ } else {
114
+ this.authenticatedClientsById.set(id, client);
115
+ }
116
+ });
117
+ } else {
118
+ console.error(`Unhandled message pre-auth: ${JSON.stringify(parsed)}`);
119
+ socket.close();
120
+ }
121
+ } else {
122
+ switch (parsed.type) {
123
+ case PONG_MESSAGE_TYPE:
124
+ client.lastPong = Date.now();
125
+ break;
126
+ case USER_UPDATE_MESSAGE_TYPE:
127
+ this.handleUserUpdate(id, parsed);
128
+ break;
129
+ default:
130
+ console.error(`Unhandled message: ${JSON.stringify(parsed)}`);
131
+ }
128
132
  }
129
133
  }
130
134
  });
131
135
  socket.on("close", () => {
132
136
  console.log("Client disconnected", id);
133
- this.clients.delete(id);
137
+ this.handleDisconnectedClient(client);
138
+ });
139
+ }
140
+ handleDisconnectedClient(client) {
141
+ if (!this.allClientsById.has(client.id)) {
142
+ return;
143
+ }
144
+ this.allClientsById.delete(client.id);
145
+ if (client.authenticatedUser !== null) {
146
+ this.options.onClientDisconnect(client.id);
147
+ this.authenticatedClientsById.delete(client.id);
134
148
  const disconnectMessage = JSON.stringify({
135
- id,
149
+ id: client.id,
136
150
  type: DISCONNECTED_MESSAGE_TYPE
137
151
  });
138
- for (const [clientId, { socket: otherSocket }] of this.clients) {
139
- if (otherSocket.readyState === WebSocketOpenStatus) {
140
- otherSocket.send(disconnectMessage);
152
+ for (const [, otherClient] of this.authenticatedClientsById) {
153
+ if (otherClient.socket.readyState === WebSocketOpenStatus) {
154
+ otherClient.socket.send(disconnectMessage);
141
155
  }
142
156
  }
157
+ }
158
+ }
159
+ async handleUserAuth(client, credentials) {
160
+ var _a, _b;
161
+ const userData = this.options.onClientConnect(
162
+ client.id,
163
+ credentials.sessionToken,
164
+ credentials.userIdentity
165
+ );
166
+ let resolvedUserData;
167
+ if (userData instanceof Promise) {
168
+ resolvedUserData = await userData;
169
+ } else {
170
+ resolvedUserData = userData;
171
+ }
172
+ if (resolvedUserData === null) {
173
+ console.error(`Client-id ${client.id} user_auth unauthorized and ignored`);
174
+ return false;
175
+ }
176
+ console.log("Client authenticated", client.id, resolvedUserData);
177
+ client.authenticatedUser = resolvedUserData;
178
+ const identityMessage = JSON.stringify({
179
+ id: client.id,
180
+ type: IDENTITY_MESSAGE_TYPE
143
181
  });
182
+ const userProfileMessage = JSON.stringify({
183
+ id: client.id,
184
+ type: USER_PROFILE_MESSAGE_TYPE,
185
+ username: resolvedUserData.username,
186
+ characterDescription: resolvedUserData.characterDescription
187
+ });
188
+ client.socket.send(userProfileMessage);
189
+ client.socket.send(identityMessage);
190
+ const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update);
191
+ for (const [, otherClient] of this.authenticatedClientsById) {
192
+ if (otherClient.socket.readyState !== WebSocketOpenStatus || otherClient === client) {
193
+ continue;
194
+ }
195
+ client.socket.send(
196
+ JSON.stringify({
197
+ id: otherClient.update.id,
198
+ type: USER_PROFILE_MESSAGE_TYPE,
199
+ username: (_a = otherClient.authenticatedUser) == null ? void 0 : _a.username,
200
+ characterDescription: (_b = otherClient.authenticatedUser) == null ? void 0 : _b.characterDescription
201
+ })
202
+ );
203
+ client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update));
204
+ otherClient.socket.send(userProfileMessage);
205
+ otherClient.socket.send(userUpdateMessage);
206
+ }
207
+ console.log("Client authenticated", client.id);
208
+ return true;
209
+ }
210
+ updateUserCharacter(clientId, userData) {
211
+ this.internalUpdateUser(clientId, userData);
212
+ }
213
+ internalUpdateUser(clientId, userData) {
214
+ const client = this.authenticatedClientsById.get(clientId);
215
+ client.authenticatedUser = userData;
216
+ this.authenticatedClientsById.set(clientId, client);
217
+ const newUserData = JSON.stringify({
218
+ id: clientId,
219
+ type: USER_PROFILE_MESSAGE_TYPE,
220
+ username: userData.username,
221
+ characterDescription: userData.characterDescription
222
+ });
223
+ for (const [otherClientId, otherClient] of this.authenticatedClientsById) {
224
+ if (otherClient.socket.readyState === WebSocketOpenStatus) {
225
+ otherClient.socket.send(newUserData);
226
+ }
227
+ }
228
+ }
229
+ async handleUserUpdate(clientId, message) {
230
+ const client = this.authenticatedClientsById.get(clientId);
231
+ if (!client) {
232
+ console.error(`Client-id ${clientId} user_update ignored, client not found`);
233
+ return;
234
+ }
235
+ const authorizedUserData = this.options.onClientUserIdentityUpdate(
236
+ clientId,
237
+ message.userIdentity
238
+ );
239
+ let resolvedAuthorizedUserData;
240
+ if (authorizedUserData instanceof Promise) {
241
+ resolvedAuthorizedUserData = await authorizedUserData;
242
+ } else {
243
+ resolvedAuthorizedUserData = authorizedUserData;
244
+ }
245
+ if (!resolvedAuthorizedUserData) {
246
+ console.warn(`Client-id ${clientId} user_update unauthorized and ignored`);
247
+ return;
248
+ }
249
+ this.internalUpdateUser(clientId, resolvedAuthorizedUserData);
144
250
  }
145
251
  sendUpdates() {
146
- for (const [clientId, client] of this.clients) {
252
+ for (const [clientId, client] of this.authenticatedClientsById) {
147
253
  const update = client.update;
148
254
  const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
149
- for (const [otherClientId, otherClient] of this.clients) {
255
+ for (const [otherClientId, otherClient] of this.authenticatedClientsById) {
150
256
  if (otherClientId !== clientId && otherClient.socket.readyState === WebSocketOpenStatus) {
151
257
  otherClient.socket.send(encodedUpdate);
152
258
  }
@@ -156,12 +262,12 @@ var UserNetworkingServer = class {
156
262
  };
157
263
 
158
264
  // 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;
265
+ var WebsocketStatus = /* @__PURE__ */ ((WebsocketStatus2) => {
266
+ WebsocketStatus2[WebsocketStatus2["Connecting"] = 0] = "Connecting";
267
+ WebsocketStatus2[WebsocketStatus2["Connected"] = 1] = "Connected";
268
+ WebsocketStatus2[WebsocketStatus2["Reconnecting"] = 2] = "Reconnecting";
269
+ WebsocketStatus2[WebsocketStatus2["Disconnected"] = 3] = "Disconnected";
270
+ return WebsocketStatus2;
165
271
  })(WebsocketStatus || {});
166
272
  var startingBackoffTimeMilliseconds = 100;
167
273
  var maximumBackoffTimeMilliseconds = 1e4;
@@ -304,52 +410,65 @@ var ReconnectingWebSocket = class {
304
410
 
305
411
  // src/UserNetworkingClient.ts
306
412
  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;
413
+ constructor(config) {
414
+ super(config.url, config.websocketFactory, (status) => {
415
+ if (status === 1 /* Connected */) {
416
+ this.sendMessage({
417
+ type: USER_AUTHENTICATE_MESSAGE_TYPE,
418
+ sessionToken: config.sessionToken
419
+ });
420
+ }
421
+ config.statusUpdateCallback(status);
422
+ });
423
+ this.config = config;
311
424
  }
312
425
  sendUpdate(update) {
313
426
  const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);
314
427
  this.send(encodedUpdate);
315
428
  }
429
+ sendMessage(message) {
430
+ this.send(message);
431
+ }
316
432
  handleIncomingWebsocketMessage(message) {
317
433
  if (typeof message.data === "string") {
318
434
  const parsed = JSON.parse(message.data);
319
435
  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
436
  case DISCONNECTED_MESSAGE_TYPE:
328
437
  console.log(`Client ID: ${parsed.id} left`);
329
- this.clientUpdate(parsed.id, null);
438
+ this.config.clientUpdate(parsed.id, null);
439
+ break;
440
+ case IDENTITY_MESSAGE_TYPE:
441
+ console.log(`Client ID: ${parsed.id} assigned to self`);
442
+ this.config.assignedIdentity(parsed.id);
443
+ break;
444
+ case USER_PROFILE_MESSAGE_TYPE:
445
+ console.log(`Client ID: ${parsed.id} updated profile`);
446
+ this.config.clientProfileUpdated(parsed.id, parsed.username, parsed.characterDescription);
330
447
  break;
331
448
  case PING_MESSAGE_TYPE: {
332
- this.send({ type: "pong" });
449
+ this.sendMessage({ type: "pong" });
333
450
  break;
334
451
  }
335
452
  default:
336
- console.warn("unknown message type received", parsed);
453
+ console.error("Unhandled message", parsed);
337
454
  }
338
455
  } else if (message.data instanceof ArrayBuffer) {
339
456
  const userNetworkingClientUpdate = UserNetworkingCodec.decodeUpdate(message.data);
340
- this.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);
457
+ this.config.clientUpdate(userNetworkingClientUpdate.id, userNetworkingClientUpdate);
341
458
  } else {
342
459
  console.error("Unhandled message type", message.data);
343
460
  }
344
461
  }
345
462
  };
346
463
  export {
347
- CONNECTED_MESSAGE_TYPE,
348
464
  DISCONNECTED_MESSAGE_TYPE,
349
465
  IDENTITY_MESSAGE_TYPE,
350
466
  PING_MESSAGE_TYPE,
351
467
  PONG_MESSAGE_TYPE,
352
468
  ReconnectingWebSocket,
469
+ USER_AUTHENTICATE_MESSAGE_TYPE,
470
+ USER_PROFILE_MESSAGE_TYPE,
471
+ USER_UPDATE_MESSAGE_TYPE,
353
472
  UserNetworkingClient,
354
473
  UserNetworkingCodec,
355
474
  UserNetworkingServer,
@@ -1,7 +1,7 @@
1
1
  {
2
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 = 15000;\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: 1 },\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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",
3
+ "sources": ["../src/UserNetworkingCodec.ts", "../src/user-networking-settings.ts", "../src/UserNetworkingMessages.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 pingPongRate: number = 1500;\nexport const heartBeatRate: number = 15000;\nexport const packetsUpdateRate: number = (1 / 30) * 1000;\n", "export const DISCONNECTED_MESSAGE_TYPE = \"disconnected\";\nexport const IDENTITY_MESSAGE_TYPE = \"identity\";\nexport const USER_AUTHENTICATE_MESSAGE_TYPE = \"user_auth\";\nexport const USER_PROFILE_MESSAGE_TYPE = \"user_profile\";\nexport const USER_UPDATE_MESSAGE_TYPE = \"user_update\";\nexport const PING_MESSAGE_TYPE = \"ping\";\nexport const PONG_MESSAGE_TYPE = \"pong\";\n\nexport type IdentityMessage = {\n type: typeof IDENTITY_MESSAGE_TYPE;\n id: number;\n};\n\nexport type CharacterDescription = {\n meshFileUrl?: string;\n mmlCharacterUrl?: string;\n mmlCharacterString?: string;\n} & (\n | {\n meshFileUrl: string;\n }\n | {\n mmlCharacterUrl: string;\n }\n | {\n mmlCharacterString: string;\n }\n);\n\nexport type UserProfileMessage = {\n type: typeof USER_PROFILE_MESSAGE_TYPE;\n id: number;\n username: string;\n characterDescription: CharacterDescription;\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 | IdentityMessage\n | UserProfileMessage\n | DisconnectedMessage\n | FromServerPingMessage;\n\nexport type FromClientPongMessage = {\n type: typeof PONG_MESSAGE_TYPE;\n};\n\nexport type UserIdentity = {\n characterDescription: CharacterDescription | null;\n username: string | null;\n};\n\nexport type UserAuthenticateMessage = {\n type: typeof USER_AUTHENTICATE_MESSAGE_TYPE;\n sessionToken: string;\n // The client can send a UserIdentity to use as the initial user profile and the server can choose to accept it or not\n userIdentity?: UserIdentity;\n};\n\nexport type UserUpdateMessage = {\n type: typeof USER_UPDATE_MESSAGE_TYPE;\n userIdentity: UserIdentity;\n};\n\nexport type FromClientMessage = FromClientPongMessage | UserAuthenticateMessage | UserUpdateMessage;\n", "import WebSocket from \"ws\";\n\nimport { heartBeatRate, packetsUpdateRate, pingPongRate } from \"./user-networking-settings\";\nimport { UserData } from \"./UserData\";\nimport { UserNetworkingClientUpdate, UserNetworkingCodec } from \"./UserNetworkingCodec\";\nimport {\n DISCONNECTED_MESSAGE_TYPE,\n FromClientMessage,\n FromServerMessage,\n IDENTITY_MESSAGE_TYPE,\n PONG_MESSAGE_TYPE,\n USER_AUTHENTICATE_MESSAGE_TYPE,\n USER_PROFILE_MESSAGE_TYPE,\n USER_UPDATE_MESSAGE_TYPE as USER_UPDATE_MESSAGE_TYPE,\n UserAuthenticateMessage,\n UserIdentity,\n UserUpdateMessage,\n} from \"./UserNetworkingMessages\";\n\nexport type Client = {\n socket: WebSocket;\n id: number;\n lastPong: number;\n update: UserNetworkingClientUpdate;\n authenticatedUser: UserData | null;\n};\n\nconst WebSocketOpenStatus = 1;\n\nexport type UserNetworkingServerOptions = {\n onClientConnect: (\n clientId: number,\n sessionToken: string,\n userIdentity?: UserIdentity,\n ) => Promise<UserData | null> | UserData | null;\n onClientUserIdentityUpdate: (\n clientId: number,\n userIdentity: UserIdentity,\n ) => Promise<UserData | null> | UserData | null;\n onClientDisconnect: (clientId: number) => void;\n};\n\nexport class UserNetworkingServer {\n private allClientsById = new Map<number, Client>();\n private authenticatedClientsById: Map<number, Client> = new Map();\n\n constructor(private options: UserNetworkingServerOptions) {\n setInterval(this.sendUpdates.bind(this), packetsUpdateRate);\n setInterval(this.pingClients.bind(this), pingPongRate);\n setInterval(this.heartBeat.bind(this), heartBeatRate);\n }\n\n private heartBeat() {\n const now = Date.now();\n this.allClientsById.forEach((client) => {\n if (now - client.lastPong > heartBeatRate) {\n client.socket.close();\n this.handleDisconnectedClient(client);\n }\n });\n }\n\n private pingClients() {\n this.authenticatedClientsById.forEach((client) => {\n if (client.socket.readyState === WebSocketOpenStatus) {\n client.socket.send(JSON.stringify({ type: \"ping\" } as FromServerMessage));\n }\n });\n }\n\n private getId(): number {\n let id = 1;\n while (this.allClientsById.has(id)) {\n id++;\n }\n return id;\n }\n\n public connectClient(socket: WebSocket) {\n const id = this.getId();\n console.log(`Client ID: ${id} joined, waiting for user-identification`);\n\n // Create a client but without user-information\n const client: Client = {\n id,\n lastPong: Date.now(),\n socket: socket as WebSocket,\n authenticatedUser: null,\n update: {\n id,\n position: { x: 0, y: 0, z: 0 },\n rotation: { quaternionY: 0, quaternionW: 1 },\n state: 0,\n },\n };\n this.allClientsById.set(id, client);\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 client.update = update;\n } else {\n let parsed;\n try {\n parsed = JSON.parse(message as string) as FromClientMessage;\n } catch (e) {\n console.error(\"Error parsing JSON message\", message, e);\n return;\n }\n if (!client.authenticatedUser) {\n if (parsed.type === USER_AUTHENTICATE_MESSAGE_TYPE) {\n this.handleUserAuth(client, parsed).then((authResult) => {\n if (!authResult) {\n // If the user is not authorized, disconnect the client\n socket.close();\n } else {\n this.authenticatedClientsById.set(id, client);\n }\n });\n } else {\n console.error(`Unhandled message pre-auth: ${JSON.stringify(parsed)}`);\n socket.close();\n }\n } else {\n switch (parsed.type) {\n case PONG_MESSAGE_TYPE:\n client.lastPong = Date.now();\n break;\n\n case USER_UPDATE_MESSAGE_TYPE:\n this.handleUserUpdate(id, parsed as UserUpdateMessage);\n break;\n\n default:\n console.error(`Unhandled message: ${JSON.stringify(parsed)}`);\n }\n }\n }\n });\n\n socket.on(\"close\", () => {\n console.log(\"Client disconnected\", id);\n this.handleDisconnectedClient(client);\n });\n }\n\n private handleDisconnectedClient(client: Client) {\n if (!this.allClientsById.has(client.id)) {\n return;\n }\n this.allClientsById.delete(client.id);\n if (client.authenticatedUser !== null) {\n // Only report disconnections of clients that were authenticated\n this.options.onClientDisconnect(client.id);\n this.authenticatedClientsById.delete(client.id);\n const disconnectMessage = JSON.stringify({\n id: client.id,\n type: DISCONNECTED_MESSAGE_TYPE,\n } as FromServerMessage);\n for (const [, otherClient] of this.authenticatedClientsById) {\n if (otherClient.socket.readyState === WebSocketOpenStatus) {\n otherClient.socket.send(disconnectMessage);\n }\n }\n }\n }\n\n private async handleUserAuth(\n client: Client,\n credentials: UserAuthenticateMessage,\n ): Promise<boolean> {\n const userData = this.options.onClientConnect(\n client.id,\n credentials.sessionToken,\n credentials.userIdentity,\n );\n let resolvedUserData;\n if (userData instanceof Promise) {\n resolvedUserData = await userData;\n } else {\n resolvedUserData = userData;\n }\n if (resolvedUserData === null) {\n console.error(`Client-id ${client.id} user_auth unauthorized and ignored`);\n return false;\n }\n\n console.log(\"Client authenticated\", client.id, resolvedUserData);\n client.authenticatedUser = resolvedUserData;\n\n const identityMessage = JSON.stringify({\n id: client.id,\n type: IDENTITY_MESSAGE_TYPE,\n } as FromServerMessage);\n\n const userProfileMessage = JSON.stringify({\n id: client.id,\n type: USER_PROFILE_MESSAGE_TYPE,\n username: resolvedUserData.username,\n characterDescription: resolvedUserData.characterDescription,\n } as FromServerMessage);\n\n client.socket.send(userProfileMessage);\n client.socket.send(identityMessage);\n\n const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update);\n\n // Send information about all other clients to the freshly connected client and vice versa\n for (const [, otherClient] of this.authenticatedClientsById) {\n if (otherClient.socket.readyState !== WebSocketOpenStatus || otherClient === client) {\n // Do not send updates for any clients which have not yet authenticated or not yet connected\n continue;\n }\n // Send the character information\n client.socket.send(\n JSON.stringify({\n id: otherClient.update.id,\n type: USER_PROFILE_MESSAGE_TYPE,\n username: otherClient.authenticatedUser?.username,\n characterDescription: otherClient.authenticatedUser?.characterDescription,\n } as FromServerMessage),\n );\n client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update));\n\n otherClient.socket.send(userProfileMessage);\n otherClient.socket.send(userUpdateMessage);\n }\n\n console.log(\"Client authenticated\", client.id);\n\n return true;\n }\n\n public updateUserCharacter(clientId: number, userData: UserData) {\n this.internalUpdateUser(clientId, userData);\n }\n\n private internalUpdateUser(clientId: number, userData: UserData) {\n // This function assumes authorization has already been done\n const client = this.authenticatedClientsById.get(clientId)!;\n\n client.authenticatedUser = userData;\n this.authenticatedClientsById.set(clientId, client);\n\n const newUserData = JSON.stringify({\n id: clientId,\n type: USER_PROFILE_MESSAGE_TYPE,\n username: userData.username,\n characterDescription: userData.characterDescription,\n } as FromServerMessage);\n\n // Broadcast the new userdata to all sockets, INCLUDING the user of the calling socket\n // Clients will always render based on the public userProfile.\n // This makes it intuitive, as it is \"what you see is what other's see\" from a user's perspective.\n for (const [otherClientId, otherClient] of this.authenticatedClientsById) {\n if (otherClient.socket.readyState === WebSocketOpenStatus) {\n otherClient.socket.send(newUserData);\n }\n }\n }\n\n private async handleUserUpdate(clientId: number, message: UserUpdateMessage): Promise<void> {\n const client = this.authenticatedClientsById.get(clientId);\n if (!client) {\n console.error(`Client-id ${clientId} user_update ignored, client not found`);\n return;\n }\n\n // Verify using the user authenticator what the allowed version of this update is\n const authorizedUserData = this.options.onClientUserIdentityUpdate(\n clientId,\n message.userIdentity,\n );\n let resolvedAuthorizedUserData;\n if (authorizedUserData instanceof Promise) {\n resolvedAuthorizedUserData = await authorizedUserData;\n } else {\n resolvedAuthorizedUserData = authorizedUserData;\n }\n if (!resolvedAuthorizedUserData) {\n // TODO - inform the client about the unauthorized update\n console.warn(`Client-id ${clientId} user_update unauthorized and ignored`);\n return;\n }\n\n this.internalUpdateUser(clientId, resolvedAuthorizedUserData);\n }\n\n private sendUpdates(): void {\n for (const [clientId, client] of this.authenticatedClientsById) {\n const update = client.update;\n const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);\n\n for (const [otherClientId, otherClient] of this.authenticatedClientsById) {\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 { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from \"./ReconnectingWebSocket\";\nimport { UserNetworkingClientUpdate, UserNetworkingCodec } from \"./UserNetworkingCodec\";\nimport {\n CharacterDescription,\n DISCONNECTED_MESSAGE_TYPE,\n FromClientMessage,\n FromServerMessage,\n IDENTITY_MESSAGE_TYPE,\n PING_MESSAGE_TYPE,\n USER_AUTHENTICATE_MESSAGE_TYPE,\n USER_PROFILE_MESSAGE_TYPE,\n} from \"./UserNetworkingMessages\";\n\nexport type UserNetworkingClientConfig = {\n url: string;\n sessionToken: string;\n websocketFactory: WebsocketFactory;\n statusUpdateCallback: (status: WebsocketStatus) => void;\n assignedIdentity: (clientId: number) => void;\n clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void;\n clientProfileUpdated: (\n id: number,\n username: string,\n characterDescription: CharacterDescription,\n ) => void;\n};\n\nexport class UserNetworkingClient extends ReconnectingWebSocket {\n constructor(private config: UserNetworkingClientConfig) {\n super(config.url, config.websocketFactory, (status: WebsocketStatus) => {\n if (status === WebsocketStatus.Connected) {\n this.sendMessage({\n type: USER_AUTHENTICATE_MESSAGE_TYPE,\n sessionToken: config.sessionToken,\n });\n }\n config.statusUpdateCallback(status);\n });\n }\n\n public sendUpdate(update: UserNetworkingClientUpdate): void {\n const encodedUpdate = UserNetworkingCodec.encodeUpdate(update);\n this.send(encodedUpdate);\n }\n\n public sendMessage(message: FromClientMessage): void {\n this.send(message);\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 DISCONNECTED_MESSAGE_TYPE:\n console.log(`Client ID: ${parsed.id} left`);\n this.config.clientUpdate(parsed.id, null);\n break;\n case IDENTITY_MESSAGE_TYPE:\n console.log(`Client ID: ${parsed.id} assigned to self`);\n this.config.assignedIdentity(parsed.id);\n break;\n case USER_PROFILE_MESSAGE_TYPE:\n console.log(`Client ID: ${parsed.id} updated profile`);\n this.config.clientProfileUpdated(parsed.id, parsed.username, parsed.characterDescription);\n break;\n case PING_MESSAGE_TYPE: {\n this.sendMessage({ type: \"pong\" } as FromClientMessage);\n break;\n }\n default:\n console.error(\"Unhandled message\", parsed);\n }\n } else if (message.data instanceof ArrayBuffer) {\n const userNetworkingClientUpdate = UserNetworkingCodec.decodeUpdate(message.data);\n this.config.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
6
  "names": ["WebsocketStatus"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mml-io/3d-web-user-networking",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,10 +24,10 @@
24
24
  "devDependencies": {
25
25
  "@types/express": "^4.17.21",
26
26
  "@types/express-ws": "^3.0.4",
27
- "@types/node": "^20.11.13",
27
+ "@types/node": "^20.12.7",
28
28
  "@types/ws": "^8.5.10",
29
- "express": "4.18.2",
29
+ "express": "4.19.2",
30
30
  "express-ws": "5.0.2"
31
31
  },
32
- "gitHead": "8e179c2269a7865766dcab19e6a8992aec44feaa"
32
+ "gitHead": "c4ef44ae56497ed3cb9abaeee56be11dbc133460"
33
33
  }
@@ -1,25 +0,0 @@
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;