diff --git a/example/multi-user-3d-web-experience/server/src/BasicUserAuthenticator.ts b/example/multi-user-3d-web-experience/server/src/BasicUserAuthenticator.ts index e051e3f7..5cdc86eb 100644 --- a/example/multi-user-3d-web-experience/server/src/BasicUserAuthenticator.ts +++ b/example/multi-user-3d-web-experience/server/src/BasicUserAuthenticator.ts @@ -48,17 +48,17 @@ export class BasicUserAuthenticator { ): UserData | null { console.log(`Client ID: ${clientId} joined with token`); let user = this.userBySessionToken.get(sessionToken); + if (!user && this.options.devAllowUnrecognizedSessions) { + console.warn(`Dev mode: allowing unrecognized session token`); + user = { + clientId: null, + sessionToken, + }; + this.userBySessionToken.set(sessionToken, user); + } + if (!user) { console.error(`Invalid initial user-update for clientId ${clientId}, unknown session`); - - if (this.options.devAllowUnrecognizedSessions) { - console.warn(`Dev mode: allowing unrecognized session token`); - user = { - clientId: null, - sessionToken, - }; - this.userBySessionToken.set(sessionToken, user); - } return null; } diff --git a/packages/3d-web-client-core/src/camera/CameraManager.ts b/packages/3d-web-client-core/src/camera/CameraManager.ts index dda6b18a..6cd6035f 100644 --- a/packages/3d-web-client-core/src/camera/CameraManager.ts +++ b/packages/3d-web-client-core/src/camera/CameraManager.ts @@ -154,7 +154,9 @@ export class CameraManager { } private onMouseMove(event: MouseEvent): void { - if (getTweakpaneActive()) return; + if (getTweakpaneActive()) { + return; + } if (this.dragging) { if (this.targetTheta === null || this.targetPhi === null) return; this.targetTheta += event.movementX * this.dampingScale; @@ -165,6 +167,9 @@ export class CameraManager { } private onMouseWheel(event: WheelEvent): void { + if (getTweakpaneActive()) { + return; + } const scrollAmount = event.deltaY * this.zoomScale * 0.1; this.targetDistance += scrollAmount; this.targetDistance = Math.max( diff --git a/packages/3d-web-client-core/src/error-screen/ErrorScreen.ts b/packages/3d-web-client-core/src/error-screen/ErrorScreen.ts new file mode 100644 index 00000000..c66f38fd --- /dev/null +++ b/packages/3d-web-client-core/src/error-screen/ErrorScreen.ts @@ -0,0 +1,39 @@ +export class ErrorScreen { + public readonly element: HTMLDivElement; + + private titleBannerText: HTMLDivElement; + private messageText: HTMLDivElement; + + constructor(title: string, message: string) { + this.element = document.createElement("div"); + this.element.style.position = "absolute"; + this.element.style.top = "0"; + this.element.style.left = "0"; + this.element.style.display = "flex"; + this.element.style.alignItems = "center"; + this.element.style.justifyContent = "center"; + this.element.style.flexDirection = "column"; + this.element.style.width = "100%"; + this.element.style.height = "100%"; + this.element.style.background = "linear-gradient(45deg, #111111 0%, #444444 100%)"; + this.element.style.color = "white"; + + this.titleBannerText = document.createElement("div"); + this.titleBannerText.textContent = title; + this.titleBannerText.style.fontSize = "40px"; + this.titleBannerText.style.fontWeight = "bold"; + this.titleBannerText.style.fontFamily = "sans-serif"; + this.element.append(this.titleBannerText); + + this.messageText = document.createElement("div"); + this.messageText.style.textAlign = "center"; + this.messageText.style.fontFamily = "sans-serif"; + this.messageText.style.fontWeight = "bold"; + this.messageText.textContent = message; + this.element.append(this.messageText); + } + + public dispose() { + this.element.remove(); + } +} diff --git a/packages/3d-web-client-core/src/index.ts b/packages/3d-web-client-core/src/index.ts index 2c943922..631b53f6 100644 --- a/packages/3d-web-client-core/src/index.ts +++ b/packages/3d-web-client-core/src/index.ts @@ -15,4 +15,5 @@ export { CollisionsManager } from "./collisions/CollisionsManager"; export { Sun } from "./sun/Sun"; export { GroundPlane } from "./ground-plane/GroundPlane"; export { LoadingScreen } from "./loading-screen/LoadingScreen"; +export { ErrorScreen } from "./error-screen/ErrorScreen"; export { EnvironmentConfiguration } from "./rendering/composer"; diff --git a/packages/3d-web-client-core/src/loading-screen/LoadingScreen.ts b/packages/3d-web-client-core/src/loading-screen/LoadingScreen.ts index 9233d2f9..e0d9ac5a 100644 --- a/packages/3d-web-client-core/src/loading-screen/LoadingScreen.ts +++ b/packages/3d-web-client-core/src/loading-screen/LoadingScreen.ts @@ -19,6 +19,7 @@ export class LoadingScreen { private hasCompleted = false; private loadingCallback: () => void; + private disposed: boolean = false; constructor(private loadingProgressManager: LoadingProgressManager) { this.element = document.createElement("div"); @@ -168,6 +169,10 @@ export class LoadingScreen { } public dispose() { + if (this.disposed) { + return; + } + this.disposed = true; this.loadingProgressManager.removeProgressCallback(this.loadingCallback); this.element.remove(); } diff --git a/packages/3d-web-client-core/src/rendering/composer.ts b/packages/3d-web-client-core/src/rendering/composer.ts index 0d9f3784..6efd731b 100644 --- a/packages/3d-web-client-core/src/rendering/composer.ts +++ b/packages/3d-web-client-core/src/rendering/composer.ts @@ -303,6 +303,7 @@ export class Composer { public dispose() { window.removeEventListener("resize", this.resizeListener); + this.renderer.dispose(); } public fitContainer() { diff --git a/packages/3d-web-client-core/src/tweakpane/TweakPane.ts b/packages/3d-web-client-core/src/tweakpane/TweakPane.ts index bea2f1e7..cd7becce 100644 --- a/packages/3d-web-client-core/src/tweakpane/TweakPane.ts +++ b/packages/3d-web-client-core/src/tweakpane/TweakPane.ts @@ -49,6 +49,7 @@ export class TweakPane { private saveVisibilityInLocalStorage: boolean = true; public guiVisible: boolean = false; + private tweakPaneWrapper: HTMLDivElement; constructor( private holderElement: HTMLElement, @@ -56,21 +57,21 @@ export class TweakPane { private scene: Scene, private composer: EffectComposer, ) { - const tweakPaneWrapper = document.createElement("div"); - tweakPaneWrapper.style.position = "fixed"; - tweakPaneWrapper.style.width = "400px"; - tweakPaneWrapper.style.height = "100%"; - tweakPaneWrapper.style.top = "0px"; - tweakPaneWrapper.style.right = "calc(-50vw)"; - tweakPaneWrapper.style.zIndex = "99"; - tweakPaneWrapper.style.overflow = "auto"; - tweakPaneWrapper.style.backgroundColor = "rgba(0, 0, 0, 0.66)"; - tweakPaneWrapper.style.paddingLeft = "5px"; - tweakPaneWrapper.style.boxShadow = "-7px 0px 12px rgba(0, 0, 0, 0.5)"; - tweakPaneWrapper.style.transition = "right cubic-bezier(0.83, 0, 0.17, 1) 0.7s"; - holderElement.appendChild(tweakPaneWrapper); - - this.gui = new Pane({ container: tweakPaneWrapper! }); + this.tweakPaneWrapper = document.createElement("div"); + this.tweakPaneWrapper.style.position = "fixed"; + this.tweakPaneWrapper.style.width = "400px"; + this.tweakPaneWrapper.style.height = "100%"; + this.tweakPaneWrapper.style.top = "0px"; + this.tweakPaneWrapper.style.right = "calc(-50vw)"; + this.tweakPaneWrapper.style.zIndex = "99"; + this.tweakPaneWrapper.style.overflow = "auto"; + this.tweakPaneWrapper.style.backgroundColor = "rgba(0, 0, 0, 0.66)"; + this.tweakPaneWrapper.style.paddingLeft = "5px"; + this.tweakPaneWrapper.style.boxShadow = "-7px 0px 12px rgba(0, 0, 0, 0.5)"; + this.tweakPaneWrapper.style.transition = "right cubic-bezier(0.83, 0, 0.17, 1) 0.7s"; + holderElement.appendChild(this.tweakPaneWrapper); + + this.gui = new Pane({ container: this.tweakPaneWrapper! }); this.gui.registerPlugin(EssentialsPlugin); if (this.saveVisibilityInLocalStorage) { @@ -104,10 +105,10 @@ export class TweakPane { this.export = this.gui.addFolder({ title: "import / export", expanded: false }); + this.setupGUIListeners(); window.addEventListener("keydown", (e) => { this.processKey(e); }); - this.setupGUIListeners(); } private setupGUIListeners(): void { @@ -115,9 +116,10 @@ export class TweakPane { const paneElement: HTMLElement = gui.containerElem_; paneElement.style.right = this.guiVisible ? "0px" : "-450px"; this.gui.element.addEventListener("mouseenter", () => setTweakpaneActive(true)); + this.gui.element.addEventListener("mousemove", () => setTweakpaneActive(true)); this.gui.element.addEventListener("mousedown", () => setTweakpaneActive(true)); - this.gui.element.addEventListener("mouseup", () => setTweakpaneActive(false)); this.gui.element.addEventListener("mouseleave", () => setTweakpaneActive(false)); + this.gui.element.addEventListener("mouseup", () => setTweakpaneActive(false)); } private processKey(e: KeyboardEvent): void { @@ -177,6 +179,11 @@ export class TweakPane { }); } + public dispose() { + this.gui.dispose(); + this.tweakPaneWrapper.remove(); + } + public setupCamPane(cameraManager: CameraManager) { this.camera.setupChangeEvent(cameraManager); } diff --git a/packages/3d-web-client-core/src/tweakpane/tweakPaneStyle.ts b/packages/3d-web-client-core/src/tweakpane/tweakPaneStyle.ts index 4d280bcb..d0b27ba1 100644 --- a/packages/3d-web-client-core/src/tweakpane/tweakPaneStyle.ts +++ b/packages/3d-web-client-core/src/tweakpane/tweakPaneStyle.ts @@ -21,9 +21,6 @@ export const tweakPaneStyle = ` --tp-label-foreground-color: hsla(0, 0%, 100%, 0.6); --tp-monitor-background-color: hsla(0, 0%, 0%, 0.3); --tp-monitor-foreground-color: hsla(0, 0%, 100%, 0.3); - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; } .tp-brkv { diff --git a/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts b/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts index b9829b2f..097fa980 100644 --- a/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts +++ b/packages/3d-web-experience-client/src/Networked3dWebExperienceClient.ts @@ -9,6 +9,7 @@ import { Composer, decodeCharacterAndCamera, EnvironmentConfiguration, + ErrorScreen, getSpawnPositionInsideCircle, GroundPlane, KeyInputManager, @@ -26,6 +27,9 @@ import { TextChatUIProps, } from "@mml-io/3d-web-text-chat"; import { + AUTHENTICATION_FAILED_ERROR_TYPE, + CONNECTION_LIMIT_REACHED_ERROR_TYPE, + ServerErrorType, UserData, UserNetworkingClient, UserNetworkingClientUpdate, @@ -95,6 +99,7 @@ export class Networked3dWebExperienceClient { private virtualJoystick: VirtualJoystick; private mmlCompositionScene: MMLCompositionScene; + private mmlFrames: Array = []; private clientId: number | null = null; private networkClient: UserNetworkingClient; @@ -113,6 +118,8 @@ export class Networked3dWebExperienceClient { private initialLoadCompleted = false; private loadingProgressManager = new LoadingProgressManager(); private loadingScreen: LoadingScreen; + private errorScreen?: ErrorScreen; + private currentRequestAnimationFrame: number | null = null; constructor( private holderElement: HTMLElement, @@ -211,6 +218,16 @@ export class Networked3dWebExperienceClient { characterDescription, }); }, + onServerError: (error: { message: string; errorType: ServerErrorType }) => { + switch (error.errorType) { + case AUTHENTICATION_FAILED_ERROR_TYPE: + this.disposeWithError(error.message); + break; + case CONNECTION_LIMIT_REACHED_ERROR_TYPE: + this.disposeWithError(error.message); + break; + } + }, }); this.characterManager = new CharacterManager({ @@ -243,7 +260,7 @@ export class Networked3dWebExperienceClient { this.setupMMLScene(); this.loadingScreen = new LoadingScreen(this.loadingProgressManager); - document.body.append(this.loadingScreen.element); + this.element.append(this.loadingScreen.element); this.loadingProgressManager.addProgressCallback(() => { const [, completed] = this.loadingProgressManager.toRatio(); @@ -383,7 +400,7 @@ export class Networked3dWebExperienceClient { } } } - requestAnimationFrame(() => { + this.currentRequestAnimationFrame = requestAnimationFrame(() => { this.update(); }); } @@ -422,6 +439,31 @@ export class Networked3dWebExperienceClient { } } + private disposeWithError(message: string) { + this.dispose(); + this.errorScreen = new ErrorScreen("An error occurred", message); + this.element.append(this.errorScreen.element); + } + + public dispose() { + this.networkClient.stop(); + this.networkChat?.stop(); + for (const mmlFrame of this.mmlFrames) { + mmlFrame.remove(); + } + this.mmlFrames = []; + this.mmlCompositionScene.dispose(); + this.composer.dispose(); + this.tweakPane?.dispose(); + if (this.currentRequestAnimationFrame !== null) { + cancelAnimationFrame(this.currentRequestAnimationFrame); + this.currentRequestAnimationFrame = null; + } + this.cameraManager.dispose(); + this.loadingScreen.dispose(); + this.errorScreen?.dispose(); + } + private setupMMLScene() { registerCustomElementsToWindow(window); this.mmlCompositionScene = new MMLCompositionScene({ @@ -464,6 +506,7 @@ export class Networked3dWebExperienceClient { } } document.body.appendChild(frameElement); + this.mmlFrames.push(frameElement); } } diff --git a/packages/3d-web-experience-server/src/Networked3dWebExperienceServer.ts b/packages/3d-web-experience-server/src/Networked3dWebExperienceServer.ts index ebe80e73..f747c9a3 100644 --- a/packages/3d-web-experience-server/src/Networked3dWebExperienceServer.ts +++ b/packages/3d-web-experience-server/src/Networked3dWebExperienceServer.ts @@ -25,6 +25,7 @@ type UserAuthenticator = { export const defaultSessionTokenPlaceholder = "SESSION.TOKEN.PLACEHOLDER"; export type Networked3dWebExperienceServerConfig = { + connectionLimit?: number; networkPath: string; webClientServing: { indexUrl: string; @@ -68,6 +69,7 @@ export class Networked3dWebExperienceServer { } this.userNetworkingServer = new UserNetworkingServer({ + connectionLimit: config.connectionLimit, onClientConnect: ( clientId: number, sessionToken: string, diff --git a/packages/3d-web-text-chat/src/chat-network/ReconnectingWebsocket.ts b/packages/3d-web-text-chat/src/chat-network/ReconnectingWebsocket.ts index 56bb7ae1..337cb3d2 100644 --- a/packages/3d-web-text-chat/src/chat-network/ReconnectingWebsocket.ts +++ b/packages/3d-web-text-chat/src/chat-network/ReconnectingWebsocket.ts @@ -124,7 +124,7 @@ export abstract class ReconnectingWebSocket { console.warn("Ignoring websocket close event because it is no longer current"); return; } - console.log("NetworkedDOMWebsocket close", e); + console.log("ReconnectingWebSocket close", e); onWebsocketClose(); }); websocket.addEventListener("error", (e) => { @@ -132,7 +132,7 @@ export abstract class ReconnectingWebSocket { console.log("Ignoring websocket error event because it is no longer current"); return; } - console.error("NetworkedDOMWebsocket error", e); + console.error("ReconnectingWebSocket error", e); onWebsocketClose(); }); diff --git a/packages/3d-web-user-networking/src/ReconnectingWebSocket.ts b/packages/3d-web-user-networking/src/ReconnectingWebSocket.ts index d6dee9f4..dc103149 100644 --- a/packages/3d-web-user-networking/src/ReconnectingWebSocket.ts +++ b/packages/3d-web-user-networking/src/ReconnectingWebSocket.ts @@ -139,7 +139,7 @@ export abstract class ReconnectingWebSocket { console.warn("Ignoring websocket close event because it is no longer current"); return; } - console.log("NetworkedDOMWebsocket close", e); + console.log("ReconnectingWebSocket close", e); onWebsocketClose(); }); websocket.addEventListener("error", (e) => { @@ -147,7 +147,7 @@ export abstract class ReconnectingWebSocket { console.log("Ignoring websocket error event because it is no longer current"); return; } - console.error("NetworkedDOMWebsocket error", e); + console.error("ReconnectingWebSocket error", e); onWebsocketClose(); }); diff --git a/packages/3d-web-user-networking/src/UserNetworkingClient.ts b/packages/3d-web-user-networking/src/UserNetworkingClient.ts index 05fc71fe..c7552f5e 100644 --- a/packages/3d-web-user-networking/src/UserNetworkingClient.ts +++ b/packages/3d-web-user-networking/src/UserNetworkingClient.ts @@ -7,6 +7,8 @@ import { FromServerMessage, IDENTITY_MESSAGE_TYPE, PING_MESSAGE_TYPE, + SERVER_ERROR_MESSAGE_TYPE, + ServerErrorType, USER_AUTHENTICATE_MESSAGE_TYPE, USER_PROFILE_MESSAGE_TYPE, } from "./UserNetworkingMessages"; @@ -23,6 +25,7 @@ export type UserNetworkingClientConfig = { username: string, characterDescription: CharacterDescription, ) => void; + onServerError: (error: { message: string; errorType: ServerErrorType }) => void; }; export class UserNetworkingClient extends ReconnectingWebSocket { @@ -51,6 +54,10 @@ export class UserNetworkingClient extends ReconnectingWebSocket { if (typeof message.data === "string") { const parsed = JSON.parse(message.data) as FromServerMessage; switch (parsed.type) { + case SERVER_ERROR_MESSAGE_TYPE: + console.error(`Server error: ${parsed.message}. errorType: ${parsed.errorType}`); + this.config.onServerError(parsed); + break; case DISCONNECTED_MESSAGE_TYPE: console.log(`Client ID: ${parsed.id} left`); this.config.clientUpdate(parsed.id, null); diff --git a/packages/3d-web-user-networking/src/UserNetworkingMessages.ts b/packages/3d-web-user-networking/src/UserNetworkingMessages.ts index 8d9bd9c9..79ae0386 100644 --- a/packages/3d-web-user-networking/src/UserNetworkingMessages.ts +++ b/packages/3d-web-user-networking/src/UserNetworkingMessages.ts @@ -3,6 +3,7 @@ export const IDENTITY_MESSAGE_TYPE = "identity"; export const USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth"; export const USER_PROFILE_MESSAGE_TYPE = "user_profile"; export const USER_UPDATE_MESSAGE_TYPE = "user_update"; +export const SERVER_ERROR_MESSAGE_TYPE = "error"; export const PING_MESSAGE_TYPE = "ping"; export const PONG_MESSAGE_TYPE = "pong"; @@ -39,6 +40,19 @@ export type DisconnectedMessage = { id: number; }; +export const CONNECTION_LIMIT_REACHED_ERROR_TYPE = "CONNECTION_LIMIT_REACHED"; +export const AUTHENTICATION_FAILED_ERROR_TYPE = "AUTHENTICATION_FAILED"; + +export type ServerErrorType = + | typeof CONNECTION_LIMIT_REACHED_ERROR_TYPE + | typeof AUTHENTICATION_FAILED_ERROR_TYPE; + +export type ServerError = { + type: typeof SERVER_ERROR_MESSAGE_TYPE; + errorType: ServerErrorType; + message: string; +}; + export type FromServerPingMessage = { type: typeof PING_MESSAGE_TYPE; }; @@ -47,7 +61,8 @@ export type FromServerMessage = | IdentityMessage | UserProfileMessage | DisconnectedMessage - | FromServerPingMessage; + | FromServerPingMessage + | ServerError; export type FromClientPongMessage = { type: typeof PONG_MESSAGE_TYPE; diff --git a/packages/3d-web-user-networking/src/UserNetworkingServer.ts b/packages/3d-web-user-networking/src/UserNetworkingServer.ts index baec6fb1..4fa74108 100644 --- a/packages/3d-web-user-networking/src/UserNetworkingServer.ts +++ b/packages/3d-web-user-networking/src/UserNetworkingServer.ts @@ -4,11 +4,14 @@ import { heartBeatRate, packetsUpdateRate, pingPongRate } from "./user-networkin import { UserData } from "./UserData"; import { UserNetworkingClientUpdate, UserNetworkingCodec } from "./UserNetworkingCodec"; import { + AUTHENTICATION_FAILED_ERROR_TYPE, + CONNECTION_LIMIT_REACHED_ERROR_TYPE, DISCONNECTED_MESSAGE_TYPE, FromClientMessage, FromServerMessage, IDENTITY_MESSAGE_TYPE, PONG_MESSAGE_TYPE, + SERVER_ERROR_MESSAGE_TYPE, USER_AUTHENTICATE_MESSAGE_TYPE, USER_PROFILE_MESSAGE_TYPE, USER_UPDATE_MESSAGE_TYPE as USER_UPDATE_MESSAGE_TYPE, @@ -28,6 +31,7 @@ export type Client = { const WebSocketOpenStatus = 1; export type UserNetworkingServerOptions = { + connectionLimit?: number; onClientConnect: ( clientId: number, sessionToken: string, @@ -80,7 +84,7 @@ export class UserNetworkingServer { const id = this.getId(); console.log(`Client ID: ${id} joined, waiting for user-identification`); - // Create a client but without user-information + // Create a client but without user information const client: Client = { id, lastPong: Date.now(), @@ -114,8 +118,73 @@ export class UserNetworkingServer { this.handleUserAuth(client, parsed).then((authResult) => { if (!authResult) { // If the user is not authorized, disconnect the client + const serverError = JSON.stringify({ + type: SERVER_ERROR_MESSAGE_TYPE, + errorType: AUTHENTICATION_FAILED_ERROR_TYPE, + message: "Authentication failed", + } as FromServerMessage); + socket.send(serverError); socket.close(); } else { + if ( + this.options.connectionLimit !== undefined && + this.authenticatedClientsById.size >= this.options.connectionLimit + ) { + // There is a connection limit and it has been met - disconnect the user + const serverError = JSON.stringify({ + type: SERVER_ERROR_MESSAGE_TYPE, + errorType: CONNECTION_LIMIT_REACHED_ERROR_TYPE, + message: "Connection limit reached", + } as FromServerMessage); + socket.send(serverError); + socket.close(); + return; + } + + const userData = authResult; + + // Give the client its own profile + const userProfileMessage = JSON.stringify({ + id: client.id, + type: USER_PROFILE_MESSAGE_TYPE, + username: userData.username, + characterDescription: userData.characterDescription, + } as FromServerMessage); + client.socket.send(userProfileMessage); + + // Give the client its own identity + const identityMessage = JSON.stringify({ + id: client.id, + type: IDENTITY_MESSAGE_TYPE, + } as FromServerMessage); + client.socket.send(identityMessage); + + const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update); + + // Send information about all other clients to the freshly connected client and vice versa + for (const [, otherClient] of this.authenticatedClientsById) { + if ( + otherClient.socket.readyState !== WebSocketOpenStatus || + otherClient === client + ) { + // Do not send updates for any clients which have not yet authenticated or not yet connected + continue; + } + // Send the character information + client.socket.send( + JSON.stringify({ + id: otherClient.update.id, + type: USER_PROFILE_MESSAGE_TYPE, + username: otherClient.authenticatedUser?.username, + characterDescription: otherClient.authenticatedUser?.characterDescription, + } as FromServerMessage), + ); + client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update)); + + otherClient.socket.send(userProfileMessage); + otherClient.socket.send(userUpdateMessage); + } + this.authenticatedClientsById.set(id, client); } }); @@ -170,7 +239,7 @@ export class UserNetworkingServer { private async handleUserAuth( client: Client, credentials: UserAuthenticateMessage, - ): Promise { + ): Promise { const userData = this.options.onClientConnect( client.id, credentials.sessionToken, @@ -190,47 +259,7 @@ export class UserNetworkingServer { console.log("Client authenticated", client.id, resolvedUserData); client.authenticatedUser = resolvedUserData; - const identityMessage = JSON.stringify({ - id: client.id, - type: IDENTITY_MESSAGE_TYPE, - } as FromServerMessage); - - const userProfileMessage = JSON.stringify({ - id: client.id, - type: USER_PROFILE_MESSAGE_TYPE, - username: resolvedUserData.username, - characterDescription: resolvedUserData.characterDescription, - } as FromServerMessage); - - client.socket.send(userProfileMessage); - client.socket.send(identityMessage); - - const userUpdateMessage = UserNetworkingCodec.encodeUpdate(client.update); - - // Send information about all other clients to the freshly connected client and vice versa - for (const [, otherClient] of this.authenticatedClientsById) { - if (otherClient.socket.readyState !== WebSocketOpenStatus || otherClient === client) { - // Do not send updates for any clients which have not yet authenticated or not yet connected - continue; - } - // Send the character information - client.socket.send( - JSON.stringify({ - id: otherClient.update.id, - type: USER_PROFILE_MESSAGE_TYPE, - username: otherClient.authenticatedUser?.username, - characterDescription: otherClient.authenticatedUser?.characterDescription, - } as FromServerMessage), - ); - client.socket.send(UserNetworkingCodec.encodeUpdate(otherClient.update)); - - otherClient.socket.send(userProfileMessage); - otherClient.socket.send(userUpdateMessage); - } - - console.log("Client authenticated", client.id); - - return true; + return resolvedUserData; } public updateUserCharacter(clientId: number, userData: UserData) { diff --git a/packages/3d-web-user-networking/test/UserNetworking.test.ts b/packages/3d-web-user-networking/test/UserNetworking.test.ts index 02d999a3..fa2e7a4b 100644 --- a/packages/3d-web-user-networking/test/UserNetworking.test.ts +++ b/packages/3d-web-user-networking/test/UserNetworking.test.ts @@ -86,6 +86,9 @@ describe("UserNetworking", () => { clientProfileUpdated: (id, username, characterDescription) => { user1Profiles.set(id, { username, characterDescription }); }, + onServerError: (error) => { + console.error("Received server error", error); + }, }); await user1ConnectPromise; expect(await user1IdentityPromise).toEqual(1); @@ -132,6 +135,9 @@ describe("UserNetworking", () => { clientProfileUpdated: (id, username, characterDescription) => { user2Profiles.set(id, { username, characterDescription }); }, + onServerError: (error) => { + console.error("Received server error", error); + }, }); await user2ConnectPromise;