@mml-io/3d-web-user-networking 0.17.0 → 0.18.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.
@@ -1,6 +1,6 @@
|
|
1
1
|
import { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from "./ReconnectingWebSocket";
|
2
2
|
import { UserNetworkingClientUpdate } from "./UserNetworkingCodec";
|
3
|
-
import { CharacterDescription, FromClientMessage } from "./UserNetworkingMessages";
|
3
|
+
import { CharacterDescription, FromClientMessage, ServerErrorType } from "./UserNetworkingMessages";
|
4
4
|
export type UserNetworkingClientConfig = {
|
5
5
|
url: string;
|
6
6
|
sessionToken: string;
|
@@ -9,6 +9,10 @@ export type UserNetworkingClientConfig = {
|
|
9
9
|
assignedIdentity: (clientId: number) => void;
|
10
10
|
clientUpdate: (id: number, update: null | UserNetworkingClientUpdate) => void;
|
11
11
|
clientProfileUpdated: (id: number, username: string, characterDescription: CharacterDescription) => void;
|
12
|
+
onServerError: (error: {
|
13
|
+
message: string;
|
14
|
+
errorType: ServerErrorType;
|
15
|
+
}) => void;
|
12
16
|
};
|
13
17
|
export declare class UserNetworkingClient extends ReconnectingWebSocket {
|
14
18
|
private config;
|
@@ -3,6 +3,7 @@ export declare const IDENTITY_MESSAGE_TYPE = "identity";
|
|
3
3
|
export declare const USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
|
4
4
|
export declare const USER_PROFILE_MESSAGE_TYPE = "user_profile";
|
5
5
|
export declare const USER_UPDATE_MESSAGE_TYPE = "user_update";
|
6
|
+
export declare const SERVER_ERROR_MESSAGE_TYPE = "error";
|
6
7
|
export declare const PING_MESSAGE_TYPE = "ping";
|
7
8
|
export declare const PONG_MESSAGE_TYPE = "pong";
|
8
9
|
export type IdentityMessage = {
|
@@ -30,10 +31,18 @@ export type DisconnectedMessage = {
|
|
30
31
|
type: typeof DISCONNECTED_MESSAGE_TYPE;
|
31
32
|
id: number;
|
32
33
|
};
|
34
|
+
export declare const CONNECTION_LIMIT_REACHED_ERROR_TYPE = "CONNECTION_LIMIT_REACHED";
|
35
|
+
export declare const AUTHENTICATION_FAILED_ERROR_TYPE = "AUTHENTICATION_FAILED";
|
36
|
+
export type ServerErrorType = typeof CONNECTION_LIMIT_REACHED_ERROR_TYPE | typeof AUTHENTICATION_FAILED_ERROR_TYPE;
|
37
|
+
export type ServerError = {
|
38
|
+
type: typeof SERVER_ERROR_MESSAGE_TYPE;
|
39
|
+
errorType: ServerErrorType;
|
40
|
+
message: string;
|
41
|
+
};
|
33
42
|
export type FromServerPingMessage = {
|
34
43
|
type: typeof PING_MESSAGE_TYPE;
|
35
44
|
};
|
36
|
-
export type FromServerMessage = IdentityMessage | UserProfileMessage | DisconnectedMessage | FromServerPingMessage;
|
45
|
+
export type FromServerMessage = IdentityMessage | UserProfileMessage | DisconnectedMessage | FromServerPingMessage | ServerError;
|
37
46
|
export type FromClientPongMessage = {
|
38
47
|
type: typeof PONG_MESSAGE_TYPE;
|
39
48
|
};
|
@@ -10,6 +10,7 @@ export type Client = {
|
|
10
10
|
authenticatedUser: UserData | null;
|
11
11
|
};
|
12
12
|
export type UserNetworkingServerOptions = {
|
13
|
+
connectionLimit?: number;
|
13
14
|
onClientConnect: (clientId: number, sessionToken: string, userIdentity?: UserIdentity) => Promise<UserData | null> | UserData | null;
|
14
15
|
onClientUserIdentityUpdate: (clientId: number, userIdentity: UserIdentity) => Promise<UserData | null> | UserData | null;
|
15
16
|
onClientDisconnect: (clientId: number) => void;
|
package/build/index.js
CHANGED
@@ -38,8 +38,11 @@ var IDENTITY_MESSAGE_TYPE = "identity";
|
|
38
38
|
var USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
|
39
39
|
var USER_PROFILE_MESSAGE_TYPE = "user_profile";
|
40
40
|
var USER_UPDATE_MESSAGE_TYPE = "user_update";
|
41
|
+
var SERVER_ERROR_MESSAGE_TYPE = "error";
|
41
42
|
var PING_MESSAGE_TYPE = "ping";
|
42
43
|
var PONG_MESSAGE_TYPE = "pong";
|
44
|
+
var CONNECTION_LIMIT_REACHED_ERROR_TYPE = "CONNECTION_LIMIT_REACHED";
|
45
|
+
var AUTHENTICATION_FAILED_ERROR_TYPE = "AUTHENTICATION_FAILED";
|
43
46
|
|
44
47
|
// src/UserNetworkingServer.ts
|
45
48
|
var WebSocketOpenStatus = 1;
|
@@ -108,9 +111,56 @@ var UserNetworkingServer = class {
|
|
108
111
|
if (!client.authenticatedUser) {
|
109
112
|
if (parsed.type === USER_AUTHENTICATE_MESSAGE_TYPE) {
|
110
113
|
this.handleUserAuth(client, parsed).then((authResult) => {
|
114
|
+
var _a, _b;
|
111
115
|
if (!authResult) {
|
116
|
+
const serverError = JSON.stringify({
|
117
|
+
type: SERVER_ERROR_MESSAGE_TYPE,
|
118
|
+
errorType: AUTHENTICATION_FAILED_ERROR_TYPE,
|
119
|
+
message: "Authentication failed"
|
120
|
+
});
|
121
|
+
socket.send(serverError);
|
112
122
|
socket.close();
|
113
123
|
} else {
|
124
|
+
if (this.options.connectionLimit !== void 0 && this.authenticatedClientsById.size >= this.options.connectionLimit) {
|
125
|
+
const serverError = JSON.stringify({
|
126
|
+
type: SERVER_ERROR_MESSAGE_TYPE,
|
127
|
+
errorType: CONNECTION_LIMIT_REACHED_ERROR_TYPE,
|
128
|
+
message: "Connection limit reached"
|
129
|
+
});
|
130
|
+
socket.send(serverError);
|
131
|
+
socket.close();
|
132
|
+
return;
|
133
|
+
}
|
134
|
+
const userData = authResult;
|
135
|
+
const userProfileMessage = JSON.stringify({
|
136
|
+
id: client.id,
|
137
|
+
type: USER_PROFILE_MESSAGE_TYPE,
|
138
|
+
username: userData.username,
|
139
|
+
characterDescription: userData.characterDescription
|
140
|
+
});
|
141
|
+
client.socket.send(userProfileMessage);
|
142
|
+
const identityMessage = JSON.stringify({
|
143
|
+
id: client.id,
|
144
|
+
type: IDENTITY_MESSAGE_TYPE
|
145
|
+
});
|
146
|
+
client.socket.send(identityMessage);
|
147
|
+
const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update);
|
148
|
+
for (const [, otherClient] of this.authenticatedClientsById) {
|
149
|
+
if (otherClient.socket.readyState !== WebSocketOpenStatus || otherClient === client) {
|
150
|
+
continue;
|
151
|
+
}
|
152
|
+
client.socket.send(
|
153
|
+
JSON.stringify({
|
154
|
+
id: otherClient.update.id,
|
155
|
+
type: USER_PROFILE_MESSAGE_TYPE,
|
156
|
+
username: (_a = otherClient.authenticatedUser) == null ? void 0 : _a.username,
|
157
|
+
characterDescription: (_b = otherClient.authenticatedUser) == null ? void 0 : _b.characterDescription
|
158
|
+
})
|
159
|
+
);
|
160
|
+
client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update));
|
161
|
+
otherClient.socket.send(userProfileMessage);
|
162
|
+
otherClient.socket.send(userUpdateMessage);
|
163
|
+
}
|
114
164
|
this.authenticatedClientsById.set(id, client);
|
115
165
|
}
|
116
166
|
});
|
@@ -157,7 +207,6 @@ var UserNetworkingServer = class {
|
|
157
207
|
}
|
158
208
|
}
|
159
209
|
async handleUserAuth(client, credentials) {
|
160
|
-
var _a, _b;
|
161
210
|
const userData = this.options.onClientConnect(
|
162
211
|
client.id,
|
163
212
|
credentials.sessionToken,
|
@@ -175,37 +224,7 @@ var UserNetworkingServer = class {
|
|
175
224
|
}
|
176
225
|
console.log("Client authenticated", client.id, resolvedUserData);
|
177
226
|
client.authenticatedUser = resolvedUserData;
|
178
|
-
|
179
|
-
id: client.id,
|
180
|
-
type: IDENTITY_MESSAGE_TYPE
|
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;
|
227
|
+
return resolvedUserData;
|
209
228
|
}
|
210
229
|
updateUserCharacter(clientId, userData) {
|
211
230
|
this.internalUpdateUser(clientId, userData);
|
@@ -380,7 +399,7 @@ var ReconnectingWebSocket = class {
|
|
380
399
|
console.warn("Ignoring websocket close event because it is no longer current");
|
381
400
|
return;
|
382
401
|
}
|
383
|
-
console.log("
|
402
|
+
console.log("ReconnectingWebSocket close", e);
|
384
403
|
onWebsocketClose();
|
385
404
|
});
|
386
405
|
websocket.addEventListener("error", (e) => {
|
@@ -388,7 +407,7 @@ var ReconnectingWebSocket = class {
|
|
388
407
|
console.log("Ignoring websocket error event because it is no longer current");
|
389
408
|
return;
|
390
409
|
}
|
391
|
-
console.error("
|
410
|
+
console.error("ReconnectingWebSocket error", e);
|
392
411
|
onWebsocketClose();
|
393
412
|
});
|
394
413
|
resolve(websocket);
|
@@ -433,6 +452,10 @@ var UserNetworkingClient = class extends ReconnectingWebSocket {
|
|
433
452
|
if (typeof message.data === "string") {
|
434
453
|
const parsed = JSON.parse(message.data);
|
435
454
|
switch (parsed.type) {
|
455
|
+
case SERVER_ERROR_MESSAGE_TYPE:
|
456
|
+
console.error(`Server error: ${parsed.message}. errorType: ${parsed.errorType}`);
|
457
|
+
this.config.onServerError(parsed);
|
458
|
+
break;
|
436
459
|
case DISCONNECTED_MESSAGE_TYPE:
|
437
460
|
console.log(`Client ID: ${parsed.id} left`);
|
438
461
|
this.config.clientUpdate(parsed.id, null);
|
@@ -461,11 +484,14 @@ var UserNetworkingClient = class extends ReconnectingWebSocket {
|
|
461
484
|
}
|
462
485
|
};
|
463
486
|
export {
|
487
|
+
AUTHENTICATION_FAILED_ERROR_TYPE,
|
488
|
+
CONNECTION_LIMIT_REACHED_ERROR_TYPE,
|
464
489
|
DISCONNECTED_MESSAGE_TYPE,
|
465
490
|
IDENTITY_MESSAGE_TYPE,
|
466
491
|
PING_MESSAGE_TYPE,
|
467
492
|
PONG_MESSAGE_TYPE,
|
468
493
|
ReconnectingWebSocket,
|
494
|
+
SERVER_ERROR_MESSAGE_TYPE,
|
469
495
|
USER_AUTHENTICATE_MESSAGE_TYPE,
|
470
496
|
USER_PROFILE_MESSAGE_TYPE,
|
471
497
|
USER_UPDATE_MESSAGE_TYPE,
|
package/build/index.js.map
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
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": ";AAOO,IAAM,sBAAN,MAA0B;AAAA,EAC/B,OAAO,aAAa,QAAgD;AAClE,UAAM,SAAS,IAAI,YAAY,EAAE;AACjC,UAAM,WAAW,IAAI,SAAS,MAAM;AACpC,aAAS,UAAU,GAAG,OAAO,EAAE;AAC/B,aAAS,WAAW,GAAG,OAAO,SAAS,CAAC;AACxC,aAAS,WAAW,GAAG,OAAO,SAAS,CAAC;AACxC,aAAS,WAAW,IAAI,OAAO,SAAS,CAAC;AACzC,aAAS,SAAS,IAAI,OAAO,SAAS,cAAc,KAAK;AACzD,aAAS,SAAS,IAAI,OAAO,SAAS,cAAc,KAAK;AACzD,aAAS,SAAS,IAAI,OAAO,KAAK;AAClC,WAAO,IAAI,WAAW,MAAM;AAAA,EAC9B;AAAA,EAEA,OAAO,aAAa,QAAiD;AACnE,UAAM,WAAW,IAAI,SAAS,MAAM;AACpC,UAAM,KAAK,SAAS,UAAU,CAAC;AAC/B,UAAM,IAAI,SAAS,WAAW,CAAC;AAC/B,UAAM,IAAI,SAAS,WAAW,CAAC;AAC/B,UAAM,IAAI,SAAS,WAAW,EAAE;AAChC,UAAM,cAAc,SAAS,SAAS,EAAE,IAAI;AAC5C,UAAM,cAAc,SAAS,SAAS,EAAE,IAAI;AAC5C,UAAM,QAAQ,SAAS,SAAS,EAAE;AAClC,UAAM,WAAW,EAAE,GAAG,GAAG,EAAE;AAC3B,UAAM,WAAW,EAAE,aAAa,YAAY;AAC5C,WAAO,EAAE,IAAI,UAAU,UAAU,MAAM;AAAA,EACzC;AACF;;;AClCO,IAAM,eAAuB;AAC7B,IAAM,gBAAwB;AAC9B,IAAM,oBAA6B,IAAI,KAAM;;;ACF7C,IAAM,4BAA4B;AAClC,IAAM,wBAAwB;AAC9B,IAAM,iCAAiC;AACvC,IAAM,4BAA4B;AAClC,IAAM,2BAA2B;AACjC,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;;;
|
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 SERVER_ERROR_MESSAGE_TYPE = \"error\";\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 const CONNECTION_LIMIT_REACHED_ERROR_TYPE = \"CONNECTION_LIMIT_REACHED\";\nexport const AUTHENTICATION_FAILED_ERROR_TYPE = \"AUTHENTICATION_FAILED\";\n\nexport type ServerErrorType =\n | typeof CONNECTION_LIMIT_REACHED_ERROR_TYPE\n | typeof AUTHENTICATION_FAILED_ERROR_TYPE;\n\nexport type ServerError = {\n type: typeof SERVER_ERROR_MESSAGE_TYPE;\n errorType: ServerErrorType;\n message: string;\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 | ServerError;\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 AUTHENTICATION_FAILED_ERROR_TYPE,\n CONNECTION_LIMIT_REACHED_ERROR_TYPE,\n DISCONNECTED_MESSAGE_TYPE,\n FromClientMessage,\n FromServerMessage,\n IDENTITY_MESSAGE_TYPE,\n PONG_MESSAGE_TYPE,\n SERVER_ERROR_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 connectionLimit?: number;\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 const serverError = JSON.stringify({\n type: SERVER_ERROR_MESSAGE_TYPE,\n errorType: AUTHENTICATION_FAILED_ERROR_TYPE,\n message: \"Authentication failed\",\n } as FromServerMessage);\n socket.send(serverError);\n socket.close();\n } else {\n if (\n this.options.connectionLimit !== undefined &&\n this.authenticatedClientsById.size >= this.options.connectionLimit\n ) {\n // There is a connection limit and it has been met - disconnect the user\n const serverError = JSON.stringify({\n type: SERVER_ERROR_MESSAGE_TYPE,\n errorType: CONNECTION_LIMIT_REACHED_ERROR_TYPE,\n message: \"Connection limit reached\",\n } as FromServerMessage);\n socket.send(serverError);\n socket.close();\n return;\n }\n\n const userData = authResult;\n\n // Give the client its own profile\n const userProfileMessage = JSON.stringify({\n id: client.id,\n type: USER_PROFILE_MESSAGE_TYPE,\n username: userData.username,\n characterDescription: userData.characterDescription,\n } as FromServerMessage);\n client.socket.send(userProfileMessage);\n\n // Give the client its own identity\n const identityMessage = JSON.stringify({\n id: client.id,\n type: IDENTITY_MESSAGE_TYPE,\n } as FromServerMessage);\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 (\n otherClient.socket.readyState !== WebSocketOpenStatus ||\n otherClient === client\n ) {\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 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<false | UserData> {\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 return resolvedUserData;\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\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(\"ReconnectingWebSocket 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(\"ReconnectingWebSocket 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 SERVER_ERROR_MESSAGE_TYPE,\n ServerErrorType,\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 onServerError: (error: { message: string; errorType: ServerErrorType }) => 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 SERVER_ERROR_MESSAGE_TYPE:\n console.error(`Server error: ${parsed.message}. errorType: ${parsed.errorType}`);\n this.config.onServerError(parsed);\n break;\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.
|
3
|
+
"version": "0.18.0",
|
4
4
|
"publishConfig": {
|
5
5
|
"access": "public"
|
6
6
|
},
|
@@ -19,15 +19,15 @@
|
|
19
19
|
"test": "jest"
|
20
20
|
},
|
21
21
|
"dependencies": {
|
22
|
-
"ws": "^8.
|
22
|
+
"ws": "^8.18.0"
|
23
23
|
},
|
24
24
|
"devDependencies": {
|
25
25
|
"@types/express": "^4.17.21",
|
26
26
|
"@types/express-ws": "^3.0.4",
|
27
|
-
"@types/node": "^20.
|
27
|
+
"@types/node": "^20.14.10",
|
28
28
|
"@types/ws": "^8.5.10",
|
29
29
|
"express": "4.19.2",
|
30
30
|
"express-ws": "5.0.2"
|
31
31
|
},
|
32
|
-
"gitHead": "
|
32
|
+
"gitHead": "8eb8acbdc767b15eacf3e8d62c5a0c92d2690f37"
|
33
33
|
}
|