Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for Telnet TTYPE (Terminal Type) option #128

Open
wants to merge 3 commits into
base: feat/telnet-linemode
Choose a base branch
from
Open
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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ Example .env file:
TELNET_HOST=127.0.0.1 # Required | the IP of your MUD
TELNET_PORT=23 # Required | the PORT of your MUD
SOCKET_ROOT=/socket.io # Required | the named socket for
HOST=0.0.0.0 # Optional, defaults to '0.0.0.0' | the IP the backend will listen for
PORT=5000 # Optional, defaults to 5000 | the PORT the backend will listen for
TELNET_TLS=false # Optional, defaults to 'false' | set this to true if you want a secure connection
SOCKET_TIMEOUT=900000 # Optional, defaults to 900000 (15 min) | timeout for any lost frontend<->backend connection
CHARSET=utf8 # Optional, defaults to 'utf8'
NAME=webmud3 # Optional | defaults to 'webmud3' | the name of the client
HOST=0.0.0.0 # Optional | defaults to '0.0.0.0' | the IP the backend will listen for
PORT=5000 # Optional | defaults to 5000 | the PORT the backend will listen for
TELNET_TLS=false # Optional | defaults to 'false' | set this to true if you want a secure connection
SOCKET_TIMEOUT=900000 # Optional | defaults to 900000 (15 min) | timeout for any lost frontend <-> backend connection
```

> [!TIP]
Expand Down
3 changes: 3 additions & 0 deletions backend/src/core/environment/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class Environment implements IEnvironment {
public readonly socketRoot: string;
public readonly socketTimeout: number;
public readonly environment: 'production' | 'development';
public readonly name: string;

/**
* Private constructor to enforce singleton pattern.
Expand Down Expand Up @@ -64,6 +65,8 @@ export class Environment implements IEnvironment {

this.projectRoot = resolveModulePath('../../../main.js');

this.name = String(getEnvironmentVariable('NAME', false, 'webmud3b'));

logger.info('[Environment] initialized', this);
}

Expand Down
1 change: 1 addition & 0 deletions backend/src/core/environment/types/environment-keys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type EnvironmentKeys =
| 'HOST' // Optional | defaults to '0.0.0.0' | the IP the backend will listen for
| 'PORT' // Optional | defaults to 5000 | the PORT the backend will listen for
| 'NAME' // Required | the name of your client. Will be send to the MUD. Defaults to 'webmud3b'
| '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
Expand Down
4 changes: 1 addition & 3 deletions backend/src/core/environment/types/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ export interface IEnvironment {
readonly telnetHost: string;
readonly telnetPort: number;
readonly telnetTLS: boolean;

readonly name: string;
readonly projectRoot: string;
readonly socketRoot: string;

readonly socketTimeout: number;

readonly environment: 'production' | 'development';
}
1 change: 1 addition & 0 deletions backend/src/core/middleware/use-sockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export const useSockets = (
telnetPort: environment.telnetPort,
useTelnetTls: environment.telnetTLS,
socketRoot: environment.socketRoot,
clientName: environment.name,
});
};
2 changes: 2 additions & 0 deletions backend/src/core/sockets/socket-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class SocketManager extends Server<
telnetPort: number;
useTelnetTls: boolean;
socketRoot: string;
clientName: string;
},
) {
super(server, {
Expand Down Expand Up @@ -144,6 +145,7 @@ export class SocketManager extends Server<
this.managerOptions.telnetHost,
this.managerOptions.telnetPort,
this.managerOptions.useTelnetTls,
this.managerOptions.clientName,
);

telnetClient.on('data', (data: string | Buffer) => {
Expand Down
12 changes: 11 additions & 1 deletion backend/src/features/telnet/telnet-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { handleEchoOption } from './utils/handle-echo-option.js';
import { handleLinemodeOption } from './utils/handle-linemode-option.js';
import { handleNawsOption } from './utils/handle-naws-option.js';
import { handleSGAOption } from './utils/handle-sga-option.js';
import { handleTTypeOption } from './utils/handle-ttype-option.js';
import { TelnetSocketWrapper } from './utils/telnet-socket-wrapper.js';

type TelnetClientEvents = {
Expand Down Expand Up @@ -54,7 +55,12 @@ export class TelnetClient extends EventEmitter<TelnetClientEvents> {
* @param {number} telnetPort - The port number of the Telnet server.
* @param {boolean} useTls - Indicates whether to use TLS encryption for the connection.
*/
constructor(telnetHost: string, telnetPort: number, useTls: boolean) {
constructor(
telnetHost: string,
telnetPort: number,
useTls: boolean,
clientName: string,
) {
super();

const telnetConnection = createTelnetConnection(
Expand All @@ -74,6 +80,10 @@ export class TelnetClient extends EventEmitter<TelnetClientEvents> {
[TelnetOptions.TELOPT_NAWS, handleNawsOption(this.telnetSocket)],
[TelnetOptions.TELOPT_SGA, handleSGAOption(this.telnetSocket)],
[TelnetOptions.TELOPT_LINEMODE, handleLinemodeOption(this.telnetSocket)],
[
TelnetOptions.TELOPT_TTYPE,
handleTTypeOption(this.telnetSocket, clientName),
],
]);

this.telnetSocket.on('connect', () => this.handleConnect());
Expand Down
66 changes: 66 additions & 0 deletions backend/src/features/telnet/utils/handle-ttype-option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { TelnetSocket } from 'telnet-stream';

import { TelnetOptions } from '../models/telnet-options.js';
import { TelnetControlSequences } from '../types/telnet-control-sequences.js';
import { TelnetOptionHandler } from '../types/telnet-option-handler.js';
import { TelnetOptionResult } from '../types/telnet-option-result.js';
import { TelnetSubnegotiationResult } from '../types/telnet-subnegotiation-result.js';

enum TelnetTTypeSubnogiation {
TTYPE_SEND = 1,
}

const handleTTypeDo = (socket: TelnetSocket) => (): TelnetOptionResult => {
socket.writeWill(TelnetOptions.TELOPT_TTYPE);

return { controlSequence: TelnetControlSequences.WILL };
};

const handleTTypeDont = (socket: TelnetSocket) => (): TelnetOptionResult => {
socket.writeWont(TelnetOptions.TELOPT_TTYPE);

return { controlSequence: TelnetControlSequences.WONT };
};

const handleTTypeWill = (socket: TelnetSocket) => (): TelnetOptionResult => {
socket.writeDont(TelnetOptions.TELOPT_TTYPE);

// This makes no sense since WE are giving the type of the terminal
return { controlSequence: TelnetControlSequences.DONT };
};

const handleTTypeWont = (socket: TelnetSocket) => (): TelnetOptionResult => {
socket.writeDont(TelnetOptions.TELOPT_TTYPE);

return { controlSequence: TelnetControlSequences.DONT };
};

const handleTTypeSub =
(socket: TelnetSocket, clientName: string) =>
(serverChunk: Buffer): TelnetSubnegotiationResult => {
if (new Uint8Array(serverChunk)[0] === TelnetTTypeSubnogiation.TTYPE_SEND) {
const buffer = Buffer.from(clientName);

socket.writeSub(TelnetOptions.TELOPT_TTYPE, buffer);

return {
clientChunk: buffer,
clientOption: clientName,
};
}

return null;
};

export const handleTTypeOption = (
socket: TelnetSocket,
clientName: string,
): TelnetOptionHandler => {
return {
handleDo: handleTTypeDo(socket),
handleDont: handleTTypeDont(socket),
handleWill: handleTTypeWill(socket),
handleWont: handleTTypeWont(socket),
handleSub: handleTTypeSub(socket, clientName),
};
};