Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ NAME=webmud3 # Optional | defaults to 'webmud3'
TELNET_HOST=127.0.0.1 # Required | the IP of your MUD
TELNET_PORT=23 # Required | the PORT of your MUD
TELNET_TLS=false # Optional | defaults to 'false' | set this to true if you want a secure connection
TELNET_KEEPALIVE_DELAY=30000 # Optional | defaults to 30000 (30s) | TCP keepalive interval for telnet connections
SOCKET_PING_INTERVAL=25000 # Optional | defaults to 25000 (25s) | Socket.IO ping interval
SOCKET_PING_TIMEOUT=20000 # Optional | defaults to 20000 (20s) | Socket.IO ping timeout
SOCKET_TIMEOUT=900000 # Optional | defaults to 900000 (15 min) | timeout for any lost frontend <-> backend connection
SOCKET_ROOT=/socket.io # Required | URL for the socket connection. e.g. 'https://mud.example.com/socket.io'
ENVIRONMENT='development' # Optional | accepts values 'development' or 'production' | defaults to 'production' | Enables Debug REST Endpoint /api/info and allows for permissive CORS if set to 'development'
Expand Down
15 changes: 15 additions & 0 deletions backend/src/core/environment/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export class Environment implements IEnvironment {
public readonly telnetTLS: boolean;
public readonly projectRoot: string;
public readonly socketRoot: string;
public readonly socketPingInterval: number;
public readonly socketPingTimeout: number;
public readonly socketTimeout: number;
public readonly telnetKeepAliveDelay: number;
public readonly environment: 'production' | 'development';
public readonly name: string;
public readonly corsAllowList: string[];
Expand Down Expand Up @@ -49,10 +52,22 @@ export class Environment implements IEnvironment {

this.socketRoot = String(getEnvironmentVariable('SOCKET_ROOT'));

this.socketPingInterval = Number(
getEnvironmentVariable('SOCKET_PING_INTERVAL', false, '25000'),
);

this.socketPingTimeout = Number(
getEnvironmentVariable('SOCKET_PING_TIMEOUT', false, '20000'),
);

this.socketTimeout = Number(
getEnvironmentVariable('SOCKET_TIMEOUT', false, '900000'),
);

this.telnetKeepAliveDelay = Number(
getEnvironmentVariable('TELNET_KEEPALIVE_DELAY', false, '30000'),
);

const environment = String(
getEnvironmentVariable('ENVIRONMENT', false, 'production'),
).toLocaleLowerCase();
Expand Down
3 changes: 3 additions & 0 deletions backend/src/core/environment/types/environment-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export type EnvironmentKeys =
| 'TELNET_HOST' // Required | the IP of your MUD
| 'TELNET_PORT' // Required | the PORT of your MUD
| 'TELNET_TLS' // Optional | defaults to 'false' | set this to true if you want a secure connection
| 'TELNET_KEEPALIVE_DELAY' // in milliseconds | default: 30000 (30s) | TCP keepalive interval for telnet connections
| 'SOCKET_PING_INTERVAL' // in milliseconds | default: 25000 (25s) | Socket.IO ping interval
| 'SOCKET_PING_TIMEOUT' // in milliseconds | default: 20000 (20s) | Socket.IO ping timeout
| 'SOCKET_TIMEOUT' // in milliseconds | default: 900000 (15 min) | determines how long messages are buffed for the disconnected frontend and when the telnet connection is closed
| 'SOCKET_ROOT' // Required | URL for the socket connection. e.g. 'https://mud.example.com/socket.io'
| 'ENVIRONMENT' // Optional | accepts values 'development' or 'production' | defaults to 'production' | Enables Debug REST Endpoint /api/info and allows for permissive CORS if set to 'development'
Expand Down
3 changes: 3 additions & 0 deletions backend/src/core/environment/types/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export interface IEnvironment {
readonly name: string;
readonly projectRoot: string;
readonly socketRoot: string;
readonly socketPingInterval: number;
readonly socketPingTimeout: number;
readonly socketTimeout: number;
readonly telnetKeepAliveDelay: number;
readonly environment: 'production' | 'development';
readonly corsAllowList: string[];
}
9 changes: 7 additions & 2 deletions backend/src/core/sockets/socket-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ export class SocketManager extends Server<
clientName: string;
},
) {
const environment = Environment.getInstance();

super(server, {
path: managerOptions.socketRoot,
transports: ['websocket'],
transports: ['websocket', 'polling'],
pingInterval: environment.socketPingInterval,
pingTimeout: environment.socketPingTimeout,
connectionStateRecovery: {
maxDisconnectionDuration: Environment.getInstance().socketTimeout,
maxDisconnectionDuration: environment.socketTimeout,
},
});

Expand Down Expand Up @@ -286,6 +290,7 @@ export class SocketManager extends Server<
this.managerOptions.clientName,
{
initialViewPort,
keepAliveDelayMs: Environment.getInstance().telnetKeepAliveDelay,
},
);

Expand Down
19 changes: 18 additions & 1 deletion backend/src/features/telnet/telnet-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export class TelnetClient extends EventEmitter<TelnetClientEvents> {
telnetPort: number,
useTls: boolean,
clientName: string,
extraOptions?: { initialViewPort: { columns: number; rows: number } },
extraOptions?: {
initialViewPort: { columns: number; rows: number };
keepAliveDelayMs?: number;
},
) {
super();

Expand All @@ -96,6 +99,20 @@ export class TelnetClient extends EventEmitter<TelnetClientEvents> {
telnetPort,
);

if (extraOptions?.keepAliveDelayMs !== undefined) {
// Enable TCP keepalive to reduce idle disconnects on intermediaries.
telnetConnection.setKeepAlive(true, extraOptions.keepAliveDelayMs);
}

telnetConnection.on('error', (error) => {
logger.error(
`[${this.socketId}] [Telnet-Client] Telnet socket error`,
{
error,
},
);
});

if (useTls) {
logger.info(
`[${this.socketId}] [Telnet-Client] created https connection for telnet`,
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/app/features/sockets/sockets.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export class SocketsService {
private readonly inputQueue: string[] = [];
private isReconnecting = false;
private sessionToken: string;
// Forces a fresh session token after reconnect failed to avoid reusing a dead backend session.
private forceNewSession = false;

public onMudConnect = new EventEmitter<boolean>(); // Emits isNewConnection
public onMudDisconnect = new EventEmitter();
Expand All @@ -54,7 +56,7 @@ export class SocketsService {

this.manager = new Manager(socketUrl, {
path: socketNamespace,
transports: ['websocket'],
transports: ['websocket', 'polling'],
reconnectionAttempts: Infinity,
reconnection: true,
});
Expand Down Expand Up @@ -146,6 +148,11 @@ export class SocketsService {
columns: number;
rows: number;
}): void {
if (this.forceNewSession) {
this.resetSessionToken();
this.forceNewSession = false;
}

console.log(
`[Sockets] Sockets-Service: 'connectToMud' with sessionToken: ${this.sessionToken}`,
);
Expand Down Expand Up @@ -291,6 +298,7 @@ export class SocketsService {

private handleReconnectFailed = () => {
this.connectedToServer.next(false);
this.forceNewSession = true;

console.error('[Sockets] Sockets-Service: Reconnect Failed');
};
Expand Down Expand Up @@ -392,4 +400,15 @@ export class SocketsService {
},
);
}

/**
* Resets the session token to force a clean backend session on next connect.
* This is used after a reconnect failure to avoid reusing a potentially dead backend session.
*/
private resetSessionToken(): void {
const newToken = this.generateUUID();
this.sessionToken = newToken;
this.saveSessionToken(newToken);
this.socket.auth = { sessionToken: newToken };
}
}
2 changes: 1 addition & 1 deletion frontend/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Environment } from './environment.interface';
export const environment: Environment = {
production: false,
// Change this to your local IP if you want to test on a mobile device in the same network
backendUrl: () => "http://localhost:5000",
backendUrl: () => 'http://localhost:5000',
};

/*
Expand Down