Skip to content

Commit

Permalink
Server shutdown (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusLongmuir authored Jul 10, 2024
1 parent 8eb8acb commit 2a37862
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 359 deletions.
317 changes: 151 additions & 166 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/3d-web-client-core/src/camera/CameraManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export class CameraManager {

public dispose() {
this.eventHandlerCollection.clear();
document.body.style.cursor = "";
}

private easeOutExpo(x: number): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ import {
} from "@mml-io/3d-web-client-core";
import {
ChatNetworkingClient,
FromClientChatMessage,
ChatNetworkingClientChatMessage,
ChatNetworkingServerErrorType,
StringToHslOptions,
TextChatUI,
TextChatUIProps,
} from "@mml-io/3d-web-text-chat";
import {
AUTHENTICATION_FAILED_ERROR_TYPE,
CONNECTION_LIMIT_REACHED_ERROR_TYPE,
ServerErrorType,
USER_UPDATE_MESSAGE_TYPE,
USER_NETWORKING_AUTHENTICATION_FAILED_ERROR_TYPE,
USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE,
USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
USER_NETWORKING_USER_UPDATE_MESSAGE_TYPE,
UserData,
UserNetworkingClient,
UserNetworkingClientUpdate,
UserNetworkingServerErrorType,
WebsocketStatus,
} from "@mml-io/3d-web-user-networking";
import { VoiceChatManager } from "@mml-io/3d-web-voice-chat";
Expand Down Expand Up @@ -235,14 +237,20 @@ export class Networked3dWebExperienceClient {
characterDescription,
});
},
onServerError: (error: { message: string; errorType: ServerErrorType }) => {
onServerError: (error: { message: string; errorType: UserNetworkingServerErrorType }) => {
switch (error.errorType) {
case AUTHENTICATION_FAILED_ERROR_TYPE:
case USER_NETWORKING_AUTHENTICATION_FAILED_ERROR_TYPE:
this.disposeWithError(error.message);
break;
case CONNECTION_LIMIT_REACHED_ERROR_TYPE:
case USER_NETWORKING_CONNECTION_LIMIT_REACHED_ERROR_TYPE:
this.disposeWithError(error.message);
break;
case USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE:
this.disposeWithError(error.message || "Server shutdown");
break;
default:
console.error(`Unhandled server error: ${error.message}`);
this.disposeWithError(error.message);
}
},
});
Expand Down Expand Up @@ -373,7 +381,7 @@ export class Networked3dWebExperienceClient {
}

this.networkClient.sendMessage({
type: USER_UPDATE_MESSAGE_TYPE,
type: USER_NETWORKING_USER_UPDATE_MESSAGE_TYPE,
userIdentity: {
username: userProfile.username,
characterDescription: {
Expand Down Expand Up @@ -433,13 +441,17 @@ export class Networked3dWebExperienceClient {
},
clientChatUpdate: (
clientId: number,
chatNetworkingUpdate: null | FromClientChatMessage,
chatNetworkingUpdate: null | ChatNetworkingClientChatMessage,
) => {
if (chatNetworkingUpdate !== null && this.textChatUI !== null) {
const username = this.userProfiles.get(clientId)?.username || "Unknown";
this.textChatUI.addTextMessage(username, chatNetworkingUpdate.text);
}
},
onServerError: (error: { message: string; errorType: ChatNetworkingServerErrorType }) => {
console.error(`Chat server error: ${error.message}. errorType: ${error.errorType}`);
this.disposeWithError(error.message);
},
});
}
}
Expand Down Expand Up @@ -544,6 +556,7 @@ export class Networked3dWebExperienceClient {
for (const mmlFrame of this.mmlFrames) {
mmlFrame.remove();
}
this.textChatUI?.dispose();
this.mmlFrames = [];
this.mmlCompositionScene.dispose();
this.composer.dispose();
Expand Down
15 changes: 12 additions & 3 deletions packages/3d-web-experience-server/src/MMLDocumentsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from "fs";
import path from "path";
import url from "url";

import chokidar from "chokidar";
import chokidar, { FSWatcher } from "chokidar";
import { EditableNetworkedDOM, LocalObservableDOMFactory } from "networked-dom-server";
import WebSocket from "ws";

Expand All @@ -18,11 +18,20 @@ export class MMLDocumentsServer {
document: EditableNetworkedDOM;
}
>();
private watcher: FSWatcher;

constructor(private directory: string) {
this.watch();
}

public dispose() {
for (const { document } of this.documents.values()) {
document.dispose();
}
this.documents.clear();
this.watcher.close();
}

public handle(filename: string, ws: WebSocket) {
const document = this.documents.get(filename)?.document;
if (!document) {
Expand All @@ -37,11 +46,11 @@ export class MMLDocumentsServer {
}

private watch() {
const watcher = chokidar.watch(this.directory, {
this.watcher = chokidar.watch(this.directory, {
ignored: /^\./,
persistent: true,
});
watcher
this.watcher
.on("add", (relativeFilePath) => {
const filename = path.basename(relativeFilePath);
console.log(`Example document '${filename}' has been added`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { ChatNetworkingServer } from "@mml-io/3d-web-text-chat";
import { UserData, UserIdentity, UserNetworkingServer } from "@mml-io/3d-web-user-networking";
import {
CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
CHAT_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
ChatNetworkingServer,
} from "@mml-io/3d-web-text-chat";
import {
USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
UserData,
UserIdentity,
UserNetworkingServer,
} from "@mml-io/3d-web-user-networking";
import cors from "cors";
import express from "express";
import enableWs from "express-ws";
Expand Down Expand Up @@ -103,6 +113,32 @@ export class Networked3dWebExperienceServer {
this.userNetworkingServer.updateUserCharacter(clientId, userData);
}

public dispose(errorMessage?: string) {
this.userNetworkingServer.dispose(
errorMessage
? {
type: USER_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
errorType: USER_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
message: errorMessage,
}
: undefined,
);
if (this.chatNetworkingServer) {
this.chatNetworkingServer.dispose(
errorMessage
? {
type: CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
errorType: CHAT_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE,
message: errorMessage,
}
: undefined,
);
}
if (this.mmlDocumentsServer) {
this.mmlDocumentsServer.dispose();
}
}

registerExpressRoutes(app: enableWs.Application) {
app.ws(this.config.networkPath, (ws) => {
this.userNetworkingServer.connectClient(ws);
Expand Down
37 changes: 22 additions & 15 deletions packages/3d-web-text-chat/src/chat-network/ChatNetworkingClient.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {
CHAT_MESSAGE_TYPE,
CONNECTED_MESSAGE_TYPE,
DISCONNECTED_MESSAGE_TYPE,
FromClientChatMessage,
CHAT_NETWORKING_CHAT_MESSAGE_TYPE,
CHAT_NETWORKING_CONNECTED_MESSAGE_TYPE,
CHAT_NETWORKING_DISCONNECTED_MESSAGE_TYPE,
ChatNetworkingClientChatMessage,
FromClientMessage,
FromServerMessage,
IDENTITY_MESSAGE_TYPE,
PING_MESSAGE_TYPE,
USER_AUTHENTICATE_MESSAGE_TYPE,
CHAT_NETWORKING_IDENTITY_MESSAGE_TYPE,
CHAT_NETWORKING_PING_MESSAGE_TYPE,
CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE,
ChatNetworkingServerErrorType,
CHAT_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE,
} from "./ChatNetworkingMessages";
import { ReconnectingWebSocket, WebsocketFactory, WebsocketStatus } from "./ReconnectingWebsocket";

Expand All @@ -16,15 +18,16 @@ export type ChatNetworkingClientConfig = {
sessionToken: string;
websocketFactory: WebsocketFactory;
statusUpdateCallback: (status: WebsocketStatus) => void;
clientChatUpdate: (id: number, update: null | FromClientChatMessage) => void;
clientChatUpdate: (id: number, update: null | ChatNetworkingClientChatMessage) => void;
onServerError: (error: { message: string; errorType: ChatNetworkingServerErrorType }) => void;
};

export class ChatNetworkingClient extends ReconnectingWebSocket {
constructor(private config: ChatNetworkingClientConfig) {
super(config.url, config.websocketFactory, (status: WebsocketStatus) => {
if (status === WebsocketStatus.Connected) {
this.sendMessage({
type: USER_AUTHENTICATE_MESSAGE_TYPE,
type: CHAT_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE,
sessionToken: config.sessionToken,
});
}
Expand All @@ -33,7 +36,7 @@ export class ChatNetworkingClient extends ReconnectingWebSocket {
}

public sendChatMessage(message: string) {
this.sendMessage({ type: CHAT_MESSAGE_TYPE, text: message });
this.sendMessage({ type: CHAT_NETWORKING_CHAT_MESSAGE_TYPE, text: message });
}

private sendMessage(message: FromClientMessage): void {
Expand All @@ -44,20 +47,24 @@ export class ChatNetworkingClient extends ReconnectingWebSocket {
if (typeof message.data === "string") {
const parsed = JSON.parse(message.data) as FromServerMessage;
switch (parsed.type) {
case IDENTITY_MESSAGE_TYPE:
case CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE:
console.error(`Server error: ${parsed.message}. errorType: ${parsed.errorType}`);
this.config.onServerError(parsed);
break;
case CHAT_NETWORKING_IDENTITY_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} assigned to self`);
break;
case CONNECTED_MESSAGE_TYPE:
case CHAT_NETWORKING_CONNECTED_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} joined chat`);
break;
case DISCONNECTED_MESSAGE_TYPE:
case CHAT_NETWORKING_DISCONNECTED_MESSAGE_TYPE:
console.log(`Client ID: ${parsed.id} left chat`);
break;
case PING_MESSAGE_TYPE: {
case CHAT_NETWORKING_PING_MESSAGE_TYPE: {
this.sendMessage({ type: "pong" });
break;
}
case CHAT_MESSAGE_TYPE: {
case CHAT_NETWORKING_CHAT_MESSAGE_TYPE: {
this.config.clientChatUpdate(parsed.id, parsed);
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,73 @@
export const IDENTITY_MESSAGE_TYPE = "identity";
export const USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
export const CONNECTED_MESSAGE_TYPE = "connected";
export const DISCONNECTED_MESSAGE_TYPE = "disconnected";
export const PING_MESSAGE_TYPE = "ping";
export const PONG_MESSAGE_TYPE = "pong";
export const CHAT_MESSAGE_TYPE = "chat";

export type IdentityMessage = {
type: typeof IDENTITY_MESSAGE_TYPE;
export const CHAT_NETWORKING_IDENTITY_MESSAGE_TYPE = "identity";
export const CHAT_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE = "user_auth";
export const CHAT_NETWORKING_CONNECTED_MESSAGE_TYPE = "connected";
export const CHAT_NETWORKING_DISCONNECTED_MESSAGE_TYPE = "disconnected";
export const CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE = "error";
export const CHAT_NETWORKING_PING_MESSAGE_TYPE = "ping";
export const CHAT_NETWORKING_PONG_MESSAGE_TYPE = "pong";
export const CHAT_NETWORKING_CHAT_MESSAGE_TYPE = "chat";

export type ChatNetworkingIdentityMessage = {
type: typeof CHAT_NETWORKING_IDENTITY_MESSAGE_TYPE;
id: number;
};

export type ConnectedMessage = {
type: typeof CONNECTED_MESSAGE_TYPE;
export type ChatNetworkingConnectedMessage = {
type: typeof CHAT_NETWORKING_CONNECTED_MESSAGE_TYPE;
id: number;
};

export type DisconnectedMessage = {
type: typeof DISCONNECTED_MESSAGE_TYPE;
export type ChatNetworkingDisconnectedMessage = {
type: typeof CHAT_NETWORKING_DISCONNECTED_MESSAGE_TYPE;
id: number;
};

export type FromServerPingMessage = {
type: typeof PING_MESSAGE_TYPE;
export const CHAT_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE = "SERVER_SHUTDOWN";
export const CHAT_NETWORKING_UNKNOWN_ERROR = "UNKNOWN_ERROR";

export type ChatNetworkingServerErrorType =
| typeof CHAT_NETWORKING_SERVER_SHUTDOWN_ERROR_TYPE
| typeof CHAT_NETWORKING_UNKNOWN_ERROR;

export type ChatNetworkingServerError = {
type: typeof CHAT_NETWORKING_SERVER_ERROR_MESSAGE_TYPE;
errorType: ChatNetworkingServerErrorType;
message: string;
};

export type ChatNetworkingServerPingMessage = {
type: typeof CHAT_NETWORKING_PING_MESSAGE_TYPE;
};

export type FromServerChatMessage = {
type: typeof CHAT_MESSAGE_TYPE;
export type ChatNetworkingServerChatMessage = {
type: typeof CHAT_NETWORKING_CHAT_MESSAGE_TYPE;
id: number;
text: string;
};

export type FromServerMessage =
| IdentityMessage
| ConnectedMessage
| DisconnectedMessage
| FromServerPingMessage
| FromServerChatMessage;
| ChatNetworkingIdentityMessage
| ChatNetworkingConnectedMessage
| ChatNetworkingDisconnectedMessage
| ChatNetworkingServerPingMessage
| ChatNetworkingServerChatMessage
| ChatNetworkingServerError;

export type FromClientPongMessage = {
type: typeof PONG_MESSAGE_TYPE;
export type ChatNetworkingClientPongMessage = {
type: typeof CHAT_NETWORKING_PONG_MESSAGE_TYPE;
};

export type FromClientAuthenticateMessage = {
type: typeof USER_AUTHENTICATE_MESSAGE_TYPE;
export type ChatNetworkingClientAuthenticateMessage = {
type: typeof CHAT_NETWORKING_USER_AUTHENTICATE_MESSAGE_TYPE;
sessionToken: string;
};

export type FromClientChatMessage = {
type: typeof CHAT_MESSAGE_TYPE;
export type ChatNetworkingClientChatMessage = {
type: typeof CHAT_NETWORKING_CHAT_MESSAGE_TYPE;
text: string;
};

export type FromClientMessage =
| FromClientPongMessage
| FromClientAuthenticateMessage
| FromClientChatMessage;
| ChatNetworkingClientPongMessage
| ChatNetworkingClientAuthenticateMessage
| ChatNetworkingClientChatMessage;
Loading

0 comments on commit 2a37862

Please sign in to comment.