diff --git a/backend/src/core/middleware/use-rest-endpoints.ts b/backend/src/core/middleware/use-rest-endpoints.ts index dd195718..2dd49eee 100644 --- a/backend/src/core/middleware/use-rest-endpoints.ts +++ b/backend/src/core/middleware/use-rest-endpoints.ts @@ -1,7 +1,7 @@ import { Express, Request, Response } from 'express'; +import { TelnetOptions } from '../../features/telnet/models/telnet-options.js'; import { TelnetControlSequences } from '../../features/telnet/types/telnet-control-sequences.js'; -import { TelnetOptions } from '../../features/telnet/types/telnet-options.js'; import { logger } from '../../shared/utils/logger.js'; import { SocketManager } from '../sockets/socket-manager.js'; diff --git a/backend/src/core/sockets/socket-manager.ts b/backend/src/core/sockets/socket-manager.ts index 472ca7f3..78782217 100644 --- a/backend/src/core/sockets/socket-manager.ts +++ b/backend/src/core/sockets/socket-manager.ts @@ -2,9 +2,9 @@ import { Server as HttpServer } from 'http'; import { Server as HttpsServer } from 'https'; import { Server, Socket } from 'socket.io'; +import { TelnetOptions } from '../../features/telnet/models/telnet-options.js'; import { TelnetClient } from '../../features/telnet/telnet-client.js'; import { TelnetControlSequences } from '../../features/telnet/types/telnet-control-sequences.js'; -import { TelnetOptions } from '../../features/telnet/types/telnet-options.js'; import { logger } from '../../shared/utils/logger.js'; import { mapToServerEncodings } from '../../shared/utils/supported-encodings.js'; import { Environment } from '../environment/environment.js'; diff --git a/backend/src/features/telnet/README.md b/backend/src/features/telnet/README.md index 22323159..e7e7a987 100644 --- a/backend/src/features/telnet/README.md +++ b/backend/src/features/telnet/README.md @@ -25,10 +25,10 @@ The TelnetClient class handles these negotiation commands, regardless of their o | Telnet Option | Client Support | Client Negotiation | Server Negotiation | Remarks | Discussion | | ----------------------------------------- | -------------- | ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | ECHO | Full | Dynamic | Dynamic | Is used in the telnet login flow to hide user input (password). This option is negotiated on the fly and can be enabled or disabled whenever needed. | | +| SGA (Suppress Go Ahead) | Full | DO | WONT | We offer a DO at startup and can handle changes on the fly. However, Unitopia WONT accept this option for unknown reasons. | https://github.com/unitopia-de/webmud3/issues/115 | | NAWS (Negotiate About Window Size) | Partial | WILL (+ Sub) | DO | We support this option to subnegotiate the window size. However, we send static values for the window size (80x25) and it does look like Unitopia is ignoring these values. | https://github.com/unitopia-de/webmud3/issues/108 | | CHARSET | Partial | DO / WILL (+ Sub) | WILL (+ Sub) / DO | We support this option to subnegotiate the character set with the server. However, we only accept UTF-8. If the server does not subnogitiate for UTF-8, an error will be thrown and the connection will be closed. | https://github.com/unitopia-de/webmud3/issues/111 | -| SGA (Suppress Go Ahead) | Todo | Not negotiated | Not negotiated | The Telnet Suppress Go Ahead (SGA) option disables the need for "Go Ahead" signals, allowing continuous, uninterrupted data flow in both directions, ideal for interactive applications like remote shells. | https://github.com/unitopia-de/webmud3/issues/115 | -| LINEMODE | Todo | WILL | DO | The Telnet LINEMODE option allows the client to send input line-by-line instead of character-by-character, optimizing bandwidth and reducing network load for text-based applications. | https://github.com/unitopia-de/webmud3/issues/114 | +| LINEMODE | Partial | WILL | DO | We support the LINEMODE option. However, the server wants us to send our input buffer whenever ANY ASCII control character is entered (FORWARDMASK), which is unsupported. On the other side, we do support SOFT_TAB and EDIT MODES | https://github.com/unitopia-de/webmud3/issues/114 | | EOR (End of Record) | Todo | DONT | WILL | Allows for the server to signal the end of a record which is not needed for our client. | https://github.com/unitopia-de/webmud3/issues/112 | | MSSP (Mud Server Status Protocol) | Todo | DO | WILL | Allows our client to retrieve basic information about the mud, like the current player count or the server name. | | | TTYPE | Todo | WILL | DO | Allows the client to send its name to the server. | | @@ -50,35 +50,63 @@ Define a new TelnetOptionHandler object for the option you want to handle. ```typescript const newOptionHandler: TelnetOptionHandler = { - negotiate: () => { + negotiate: (socket) => { // In this handler you can send a negotiation yourself uppon initialization. // Use this if you want the server to enable/disable the option. }, - handleDo: () => { + handleDo: (socket, getPreviousNegotiation?) => { // Handle the DO command for the new option // Return a TelnetNegotiationResult object with the appropriate control sequence and subnegotiation result }, - handleDont: () => { + handleDont: (socket, getPreviousNegotiation?) => { // Handle the DON'T command for the new option // Return a TelnetNegotiationResult object with the appropriate control sequence and subnegotiation result }, - handleWill: () => { + handleWill: (socket, getPreviousNegotiation?) => { // Handle the WILL command for the new option // Return a TelnetNegotiationResult object with the appropriate control sequence and subnegotiation result }, - handleWont: () => { + handleWont: (socket, getPreviousNegotiation?) => { // Handle the WON'T command for the new option // Return a TelnetNegotiationResult object with the appropriate control sequence and subnegotiation result }, - handleSub: (serverChunk: Buffer) => { + handleSub: (socket, serverChunk: Buffer) => { // Handle the subnegotiation data for the new option // Return a TelnetSubnegotiationResult object with the appropriate client chunk and client option }, + + // Whether this handler is dynamic and can be called again after initial negotiation + isDynamic?: boolean, }; ``` +_Note_: If you mark an option handler as dynamic, it will be called again after the initial negotiation with the server if the server requests a change to the option. This allows for more flexible and dynamic negotiation of Telnet options between the client and server. + Add the `newOptionHandler` object to the optionsHandler map in your TelnetClient class: ```typescript this.optionsHandler.set(TelnetOptions.TELOPT_LINEMODE, newOptionHandler); ``` + +### Accessing Previous Negotiation State + +In some cases, you may need to access the previous negotiation state to determine how to respond to a new negotiation command. The `TelnetOptionHandler` provides a way to do this through the `getPreviousNegotiation` function. + +The `getPreviousNegotiation` function returns the previous negotiation result, which can be used to determine the current state of the option. This can be useful in cases where the client needs to respond differently depending on the previous state of the option. + +Here is an example of how to use `getPreviousNegotiation` in a `TelnetOptionHandler`: + +```typescript +const newOptionHandler: TelnetOptionHandler = { + // ... + handleDo: (getPreviousNegotiation: () => TelnetNegotiationResult | undefined) => { + const previousNegotiation = getPreviousNegotiation(); + if (previousNegotiation?.client !== undefined) { + // Handle the case where the option is already enabled + } else { + // Handle the case where the option is not enabled + } + }, + // ... +}; +``` diff --git a/backend/src/features/telnet/types/telnet-options.ts b/backend/src/features/telnet/models/telnet-options.ts similarity index 100% rename from backend/src/features/telnet/types/telnet-options.ts rename to backend/src/features/telnet/models/telnet-options.ts diff --git a/backend/src/features/telnet/telnet-client.ts b/backend/src/features/telnet/telnet-client.ts index 3d4a7516..25fc68b5 100644 --- a/backend/src/features/telnet/telnet-client.ts +++ b/backend/src/features/telnet/telnet-client.ts @@ -1,21 +1,18 @@ -// Das siegreiche Gnomi sagt: Es gibt so ein paar Telnet-Optionen, die m.E. -// jeder Client unterstuetzen sollte: NAWS, CHARSET, EOR, ECHO, -// STARTTLS. -// Das siegreiche Gnomi sagt: Ah, und SGA oder LINEMODE - import EventEmitter from 'events'; import net from 'net'; import { TelnetSocket } from 'telnet-stream'; import tls from 'tls'; import { logger } from '../../shared/utils/logger.js'; +import { TelnetOptions } from './models/telnet-options.js'; import { TelnetControlSequences } from './types/telnet-control-sequences.js'; import { TelnetNegotiations } from './types/telnet-negotiations.js'; import { TelnetOptionHandler } from './types/telnet-option-handler.js'; -import { TelnetOptions } from './types/telnet-options.js'; import { handleCharsetOption } from './utils/handle-charset-option.js'; 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 { TelnetSocketWrapper } from './utils/telnet-socket-wrapper.js'; type TelnetClientEvents = { @@ -75,6 +72,8 @@ export class TelnetClient extends EventEmitter { [TelnetOptions.TELOPT_CHARSET, handleCharsetOption(this.telnetSocket)], [TelnetOptions.TELOPT_ECHO, handleEchoOption(this.telnetSocket)], [TelnetOptions.TELOPT_NAWS, handleNawsOption(this.telnetSocket)], + [TelnetOptions.TELOPT_SGA, handleSGAOption(this.telnetSocket)], + [TelnetOptions.TELOPT_LINEMODE, handleLinemodeOption(this.telnetSocket)], ]); this.telnetSocket.on('connect', () => this.handleConnect()); @@ -96,8 +95,6 @@ export class TelnetClient extends EventEmitter { this.telnetSocket.on('data', (chunkData: string | Buffer) => { this.emit('data', chunkData); }); - - this.connected = true; } public sendMessage(data: string): void { @@ -113,7 +110,21 @@ export class TelnetClient extends EventEmitter { } private handleConnect(): void { - logger.info(`[Telnet-Client] Connected`); + logger.info(`[Telnet-Client] Connected. Starting negotiation process.`); + + this.connected = true; + + for (const [option, handler] of this.optionsHandler) { + const handlerResult = handler.negotiate?.(); + + if (handlerResult !== undefined) { + this.updateNegotiations(option, { + client: handlerResult.controlSequence, + clientChunk: handlerResult.subNegotiationResult?.clientChunk, + clientOption: handlerResult.subNegotiationResult?.clientOption, + }); + } + } } private handleClose(hadErrors: boolean): void { @@ -129,6 +140,13 @@ export class TelnetClient extends EventEmitter { const handler = this.optionsHandler.get(option); + if ( + this._negotiations[option]?.client !== undefined && + handler?.isDynamic !== true + ) { + return; + } + const handlerResult = handler?.handleDo(); if (handlerResult !== undefined) { @@ -145,19 +163,6 @@ export class TelnetClient extends EventEmitter { }); } - // switch (option) { - - // case TelnetOptions.TELOPT_TM: { - // this.updateNegotiations(option, { - // server: TelnetControlSequences.DO, - // client: TelnetControlSequences.WILL, - // }); - - // this.telnetSocket.writeWill(option); - - // return; - // } - return; } @@ -168,6 +173,13 @@ export class TelnetClient extends EventEmitter { const handler = this.optionsHandler.get(option); + if ( + this._negotiations[option]?.client !== undefined && + handler?.isDynamic !== true + ) { + return; + } + const handlerResult = handler?.handleDont(); if (handlerResult !== undefined) { @@ -192,6 +204,13 @@ export class TelnetClient extends EventEmitter { const handler = this.optionsHandler.get(option); + if ( + this._negotiations[option]?.client !== undefined && + handler?.isDynamic !== true + ) { + return; + } + const handlerResult = handler?.handleWill(); if (handlerResult !== undefined) { @@ -207,20 +226,6 @@ export class TelnetClient extends EventEmitter { client: TelnetControlSequences.DONT, // we answer negatively but we should DO everything possible }); } - - // switch (option) { - - // case TelnetOptions.TELOPT_GMCP: { - // this.telnetSocket.writeDo(option); - - // this.updateNegotiations(option, { - // server: TelnetControlSequences.WILL, - // client: TelnetControlSequences.DO, - // }); - - // return; - // } - // } } private handleWont(option: TelnetOptions): void { @@ -230,6 +235,13 @@ export class TelnetClient extends EventEmitter { const handler = this.optionsHandler.get(option); + if ( + this._negotiations[option]?.client !== undefined && + handler?.isDynamic !== true + ) { + return; + } + const handlerResult = handler?.handleWont(); if (handlerResult !== undefined) { @@ -288,29 +300,36 @@ export class TelnetClient extends EventEmitter { // Update the control sequences for server and client const updatedNegotiation = { ...existing, - ...{ - server: negotiations.server ?? existing.server, - client: negotiations.client ?? existing.client, + server: negotiations.server ?? existing.server, + client: negotiations.client ?? existing.client, + subnegotiation: { + // Only define serverChunks if there is a new serverChunk or it already exists + ...(existing.subnegotiation?.serverChunks || negotiations.serverChunk + ? { + serverChunks: [ + ...(existing.subnegotiation?.serverChunks || []), + ...(negotiations.serverChunk + ? [`0x${negotiations.serverChunk.toString('hex')}`] + : []), + ], + } + : {}), + // Only define clientChunks if there is a new clientChunk or it already exists + ...(existing.subnegotiation?.clientChunks || negotiations.clientChunk + ? { + clientChunks: [ + ...(existing.subnegotiation?.clientChunks || []), + ...(negotiations.clientChunk + ? [`0x${negotiations.clientChunk.toString('hex')}`] + : []), + ], + } + : {}), + clientOption: + negotiations.clientOption ?? existing.subnegotiation?.clientOption, }, }; - // Update the subnegotiation properties if they exist - if (existing) { - updatedNegotiation.subnegotiation = { - ...existing.subnegotiation, - ...{ - serverChunk: negotiations.serverChunk - ? negotiations.serverChunk.toString() - : existing.subnegotiation?.serverChunk, - clientChunk: negotiations.clientChunk - ? negotiations.clientChunk.toString() - : existing.subnegotiation?.clientChunk, - clientOption: - negotiations.clientOption ?? existing.subnegotiation?.clientOption, - }, - }; - } - // Update the negotiations object this._negotiations[option] = updatedNegotiation; diff --git a/backend/src/features/telnet/types/telnet-negotiation-result.ts b/backend/src/features/telnet/types/telnet-negotiation-result.ts index 73cca71f..22384f89 100644 --- a/backend/src/features/telnet/types/telnet-negotiation-result.ts +++ b/backend/src/features/telnet/types/telnet-negotiation-result.ts @@ -1,8 +1,33 @@ import { TelnetControlSequences } from './telnet-control-sequences.js'; -import { TelnetSubnegotiationResult } from './telnet-subnegotiation-result.js'; export type TelnetNegotiationResult = { - subNegotiationResult?: TelnetSubnegotiationResult; + /** + * The control sequence received from the server (DO, DON'T, WILL, WON'T). + */ + server?: TelnetControlSequences; - controlSequence: TelnetControlSequences; + /** + * The control sequence sent by the client (DO, DON'T, WILL, WON'T). + */ + client?: TelnetControlSequences; + + /** + * Optional subnegotiation data exchanged between the server and client. + */ + subnegotiation?: { + /** + * The data chunk sent by the server during subnegotiation. + */ + serverChunks?: string[]; + + /** + * The data chunk sent by the client during subnegotiation. + */ + clientChunks?: string[]; + + /** + * The client option used during subnegotiation (e.g., a charset or mode). + */ + clientOption?: string; + }; }; diff --git a/backend/src/features/telnet/types/telnet-negotiations.ts b/backend/src/features/telnet/types/telnet-negotiations.ts index 1f589ba6..f205b358 100644 --- a/backend/src/features/telnet/types/telnet-negotiations.ts +++ b/backend/src/features/telnet/types/telnet-negotiations.ts @@ -1,40 +1,10 @@ -import { TelnetControlSequences } from './telnet-control-sequences.js'; -import { TelnetOptions } from './telnet-options.js'; +import { TelnetOptions } from './../models/telnet-options.js'; +import { TelnetNegotiationResult } from './telnet-negotiation-result.js'; /** * Represents the state of negotiations for Telnet options, including both server * and client control sequences, as well as any subnegotiation data. */ export type TelnetNegotiations = { - -readonly [key in keyof typeof TelnetOptions]?: { - /** - * The control sequence received from the server (DO, DON'T, WILL, WON'T). - */ - server?: TelnetControlSequences; - - /** - * The control sequence sent by the client (DO, DON'T, WILL, WON'T). - */ - client?: TelnetControlSequences; - - /** - * Optional subnegotiation data exchanged between the server and client. - */ - subnegotiation?: { - /** - * The data chunk sent by the server during subnegotiation. - */ - serverChunk?: string; - - /** - * The data chunk sent by the client during subnegotiation. - */ - clientChunk?: string; - - /** - * The client option used during subnegotiation (e.g., a charset or mode). - */ - clientOption?: string; - }; - }; + -readonly [key in keyof typeof TelnetOptions]?: TelnetNegotiationResult; }; diff --git a/backend/src/features/telnet/types/telnet-option-handler.ts b/backend/src/features/telnet/types/telnet-option-handler.ts index fb727d1f..cd815fa6 100644 --- a/backend/src/features/telnet/types/telnet-option-handler.ts +++ b/backend/src/features/telnet/types/telnet-option-handler.ts @@ -1,4 +1,5 @@ import { TelnetNegotiationResult } from './telnet-negotiation-result.js'; +import { TelnetOptionResult } from './telnet-option-result.js'; import { TelnetSubnegotiationResult } from './telnet-subnegotiation-result.js'; /** @@ -9,17 +10,19 @@ import { TelnetSubnegotiationResult } from './telnet-subnegotiation-result.js'; export type TelnetOptionHandler = { /** * Intiate a negotiation with the server. - * @returns {TelnetNegotiationResult} The control sequence (DO, DONT, WILL, WONT) sent back to the server. + * @returns {TelnetOptionResult} The control sequence (DO, DONT, WILL, WONT) sent back to the server. */ - negotiate?: () => TelnetNegotiationResult; + negotiate?: () => TelnetOptionResult; /** * Handles the "DO" command sent by the server, indicating that the server * wants the client to enable a particular option. * - * @returns {TelnetNegotiationResult} The control sequence (WILL, WONT) sent back to the server. + * @returns {TelnetOptionResult} The control sequence (WILL, WONT) sent back to the server. */ - handleDo: () => TelnetNegotiationResult; + handleDo: ( + getPreviousNegotiation?: () => TelnetNegotiationResult | undefined, + ) => TelnetOptionResult; /** * Handles the "DON'T" command sent by the server, indicating that the server @@ -27,23 +30,29 @@ export type TelnetOptionHandler = { * * @returns {TelnetControlSequences} The control sequence (WILL, WONT) sent back to the server. */ - handleDont: () => TelnetNegotiationResult; + handleDont: ( + getPreviousNegotiation?: () => TelnetNegotiationResult | undefined, + ) => TelnetOptionResult; /** * Handles the "WILL" command sent by the server, indicating that the server * is willing to enable a particular option. * - * @returns {TelnetNegotiationResult} The control sequence (DO, DONT) sent back to the server. + * @returns {TelnetOptionResult} The control sequence (DO, DONT) sent back to the server. */ - handleWill: () => TelnetNegotiationResult; + handleWill: ( + getPreviousNegotiation?: () => TelnetNegotiationResult | undefined, + ) => TelnetOptionResult; /** * Handles the "WON'T" command sent by the server, indicating that the server * is unwilling to enable a particular option. * - * @returns {TelnetNegotiationResult} The control sequence (DO, DONT) sent back to the server. + * @returns {TelnetOptionResult} The control sequence (DO, DONT) sent back to the server. */ - handleWont: () => TelnetNegotiationResult; + handleWont: ( + getPreviousNegotiation?: () => TelnetNegotiationResult | undefined, + ) => TelnetOptionResult; /** * Handles the subnegotiation message sent by the server. @@ -52,4 +61,13 @@ export type TelnetOptionHandler = { * @returns {TelnetSubnegotiationResult | null} The subnegotiation result, or null if not applicable. */ handleSub?: (serverChunk: Buffer) => TelnetSubnegotiationResult; + + /** + * Returns true if the option is dynamic. That means it can be re-negotiated after the client has made a decision. + * This is used in ECHO and the somewhat broken CHARSET option (server switches from DO to WILL, after we anwser WILL). + * You probably don't want to use this for your own options. + * + * @returns {boolean} True if the option is dynamic, false otherwise. + */ + isDynamic?: boolean; }; diff --git a/backend/src/features/telnet/types/telnet-option-result.ts b/backend/src/features/telnet/types/telnet-option-result.ts new file mode 100644 index 00000000..b809260a --- /dev/null +++ b/backend/src/features/telnet/types/telnet-option-result.ts @@ -0,0 +1,8 @@ +import { TelnetControlSequences } from './telnet-control-sequences.js'; +import { TelnetSubnegotiationResult } from './telnet-subnegotiation-result.js'; + +export type TelnetOptionResult = { + subNegotiationResult?: TelnetSubnegotiationResult; + + controlSequence: TelnetControlSequences; +}; diff --git a/backend/src/features/telnet/utils/handle-charset-option.ts b/backend/src/features/telnet/utils/handle-charset-option.ts index 21317147..1ccc2561 100644 --- a/backend/src/features/telnet/utils/handle-charset-option.ts +++ b/backend/src/features/telnet/utils/handle-charset-option.ts @@ -1,10 +1,10 @@ import { TelnetSocket } from 'telnet-stream'; import { logger } from '../../../shared/utils/logger.js'; +import { TelnetOptions } from '../models/telnet-options.js'; import { TelnetControlSequences } from '../types/telnet-control-sequences.js'; -import { TelnetNegotiationResult } from '../types/telnet-negotiation-result.js'; import { TelnetOptionHandler } from '../types/telnet-option-handler.js'; -import { TelnetOptions } from '../types/telnet-options.js'; +import { TelnetOptionResult } from '../types/telnet-option-result.js'; import { TelnetSubnegotiationResult } from '../types/telnet-subnegotiation-result.js'; const DEFAULT_CLIENT_ENCODING = 'UTF-8'; @@ -15,41 +15,37 @@ enum TelnetCharsetSubnogiation { CHARSET_ACCEPTED = 2, } -const handleCharsetDo = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeWill(TelnetOptions.TELOPT_CHARSET); +const handleCharsetDo = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWill(TelnetOptions.TELOPT_CHARSET); - return { - controlSequence: TelnetControlSequences.WILL, - }; + return { + controlSequence: TelnetControlSequences.WILL, }; +}; -const handleCharsetDont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeWont(TelnetOptions.TELOPT_CHARSET); +const handleCharsetDont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWont(TelnetOptions.TELOPT_CHARSET); - return { - controlSequence: TelnetControlSequences.WONT, - }; + return { + controlSequence: TelnetControlSequences.WONT, }; +}; -const handleCharsetWill = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDo(TelnetOptions.TELOPT_CHARSET); +const handleCharsetWill = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDo(TelnetOptions.TELOPT_CHARSET); - return { - controlSequence: TelnetControlSequences.DO, - }; + return { + controlSequence: TelnetControlSequences.DO, }; +}; -const handleCharsetWont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDont(TelnetOptions.TELOPT_CHARSET); +const handleCharsetWont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_CHARSET); - return { - controlSequence: TelnetControlSequences.DONT, - }; + return { + controlSequence: TelnetControlSequences.DONT, }; +}; const handleCharsetSub = (socket: TelnetSocket) => @@ -94,5 +90,6 @@ export const handleCharsetOption = ( handleWill: handleCharsetWill(socket), handleWont: handleCharsetWont(socket), handleSub: handleCharsetSub(socket), + isDynamic: true, }; }; diff --git a/backend/src/features/telnet/utils/handle-echo-option.ts b/backend/src/features/telnet/utils/handle-echo-option.ts index d9854647..e7f92e56 100644 --- a/backend/src/features/telnet/utils/handle-echo-option.ts +++ b/backend/src/features/telnet/utils/handle-echo-option.ts @@ -1,36 +1,33 @@ import { TelnetSocket } from 'telnet-stream'; +import { TelnetOptions } from '../models/telnet-options.js'; import { TelnetControlSequences } from '../types/telnet-control-sequences.js'; -import { TelnetNegotiationResult } from '../types/telnet-negotiation-result.js'; import { TelnetOptionHandler } from '../types/telnet-option-handler.js'; -import { TelnetOptions } from '../types/telnet-options.js'; +import { TelnetOptionResult } from '../types/telnet-option-result.js'; -const handleEchoDo = (socket: TelnetSocket) => (): TelnetNegotiationResult => { +const handleEchoDo = (socket: TelnetSocket) => (): TelnetOptionResult => { socket.writeWill(TelnetOptions.TELOPT_ECHO); return { controlSequence: TelnetControlSequences.WILL }; }; -const handleEchoDont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeWont(TelnetOptions.TELOPT_ECHO); +const handleEchoDont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWont(TelnetOptions.TELOPT_ECHO); - return { controlSequence: TelnetControlSequences.WONT }; - }; + return { controlSequence: TelnetControlSequences.WONT }; +}; -const handleEchoWill = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDo(TelnetOptions.TELOPT_ECHO); +const handleEchoWill = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDo(TelnetOptions.TELOPT_ECHO); - return { controlSequence: TelnetControlSequences.DO }; - }; + return { controlSequence: TelnetControlSequences.DO }; +}; -const handleEchoWont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDont(TelnetOptions.TELOPT_ECHO); +const handleEchoWont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_ECHO); - return { controlSequence: TelnetControlSequences.DONT }; - }; + return { controlSequence: TelnetControlSequences.DONT }; +}; export const handleEchoOption = (socket: TelnetSocket): TelnetOptionHandler => { return { @@ -38,5 +35,6 @@ export const handleEchoOption = (socket: TelnetSocket): TelnetOptionHandler => { handleDont: handleEchoDont(socket), handleWill: handleEchoWill(socket), handleWont: handleEchoWont(socket), + isDynamic: true, }; }; diff --git a/backend/src/features/telnet/utils/handle-linemode-option.ts b/backend/src/features/telnet/utils/handle-linemode-option.ts new file mode 100644 index 00000000..c5bdcbd9 --- /dev/null +++ b/backend/src/features/telnet/utils/handle-linemode-option.ts @@ -0,0 +1,41 @@ +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'; + +const handleLinemodeDo = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWill(TelnetOptions.TELOPT_LINEMODE); + + return { controlSequence: TelnetControlSequences.WILL }; +}; + +const handleLinemodeDont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWont(TelnetOptions.TELOPT_LINEMODE); + + return { controlSequence: TelnetControlSequences.WONT }; +}; + +const handleLinemodeWill = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDo(TelnetOptions.TELOPT_LINEMODE); + + return { controlSequence: TelnetControlSequences.DO }; +}; + +const handleLinemodeWont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_LINEMODE); + + return { controlSequence: TelnetControlSequences.DONT }; +}; + +export const handleLinemodeOption = ( + socket: TelnetSocket, +): TelnetOptionHandler => { + return { + handleDo: handleLinemodeDo(socket), + handleDont: handleLinemodeDont(socket), + handleWill: handleLinemodeWill(socket), + handleWont: handleLinemodeWont(socket), + }; +}; diff --git a/backend/src/features/telnet/utils/handle-naws-option.ts b/backend/src/features/telnet/utils/handle-naws-option.ts index 6c51a5e3..8d23a56f 100644 --- a/backend/src/features/telnet/utils/handle-naws-option.ts +++ b/backend/src/features/telnet/utils/handle-naws-option.ts @@ -1,16 +1,16 @@ import { TelnetSocket } from 'telnet-stream'; import { sizeToBuffer } from '../../../shared/utils/size-to-buffer.js'; +import { TelnetOptions } from '../models/telnet-options.js'; import { TelnetControlSequences } from '../types/telnet-control-sequences.js'; -import { TelnetNegotiationResult } from '../types/telnet-negotiation-result.js'; import { TelnetOptionHandler } from '../types/telnet-option-handler.js'; -import { TelnetOptions } from '../types/telnet-options.js'; +import { TelnetOptionResult } from '../types/telnet-option-result.js'; const DEFAULT_VIEWPORT_WIDTH = 80; const DEFAULT_VIEWPORT_HEIGHT = 25; -const handleNawsDo = (socket: TelnetSocket) => (): TelnetNegotiationResult => { +const handleNawsDo = (socket: TelnetSocket) => (): TelnetOptionResult => { socket.writeWill(TelnetOptions.TELOPT_NAWS); const buffer = sizeToBuffer(DEFAULT_VIEWPORT_WIDTH, DEFAULT_VIEWPORT_HEIGHT); @@ -26,28 +26,25 @@ const handleNawsDo = (socket: TelnetSocket) => (): TelnetNegotiationResult => { }; }; -const handleNawsDont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeWont(TelnetOptions.TELOPT_NAWS); +const handleNawsDont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWont(TelnetOptions.TELOPT_NAWS); - return { controlSequence: TelnetControlSequences.WONT }; - }; + return { controlSequence: TelnetControlSequences.WONT }; +}; -const handleNawsWill = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDont(TelnetOptions.TELOPT_NAWS); +const handleNawsWill = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_NAWS); - // We do not allow the MUD to set the window size since this makes no sense in our responsive client - // so any subnogitiation is ignored. - return { controlSequence: TelnetControlSequences.DONT }; - }; + // We do not allow the MUD to set the window size since this makes no sense in our responsive client + // so any subnogitiation is ignored. + return { controlSequence: TelnetControlSequences.DONT }; +}; -const handleNawsWont = - (socket: TelnetSocket) => (): TelnetNegotiationResult => { - socket.writeDont(TelnetOptions.TELOPT_NAWS); +const handleNawsWont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_NAWS); - return { controlSequence: TelnetControlSequences.DONT }; - }; + return { controlSequence: TelnetControlSequences.DONT }; +}; export const handleNawsOption = (socket: TelnetSocket): TelnetOptionHandler => { return { diff --git a/backend/src/features/telnet/utils/handle-sga-option.ts b/backend/src/features/telnet/utils/handle-sga-option.ts new file mode 100644 index 00000000..34cd13ae --- /dev/null +++ b/backend/src/features/telnet/utils/handle-sga-option.ts @@ -0,0 +1,47 @@ +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'; + +const handleNegotiation = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDo(TelnetOptions.TELOPT_SGA); + + return { controlSequence: TelnetControlSequences.DO }; +}; + +const handleSGADo = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWill(TelnetOptions.TELOPT_SGA); + + return { controlSequence: TelnetControlSequences.WILL }; +}; + +const handleSGADont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeWont(TelnetOptions.TELOPT_SGA); + + return { controlSequence: TelnetControlSequences.WONT }; +}; + +const handleSGAWill = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDo(TelnetOptions.TELOPT_SGA); + + return { controlSequence: TelnetControlSequences.DO }; +}; + +const handleSGAWont = (socket: TelnetSocket) => (): TelnetOptionResult => { + socket.writeDont(TelnetOptions.TELOPT_SGA); + + return { controlSequence: TelnetControlSequences.DONT }; +}; + +export const handleSGAOption = (socket: TelnetSocket): TelnetOptionHandler => { + return { + negotiate: handleNegotiation(socket), + handleDo: handleSGADo(socket), + handleDont: handleSGADont(socket), + handleWill: handleSGAWill(socket), + handleWont: handleSGAWont(socket), + isDynamic: true, + }; +}; diff --git a/backend/src/features/telnet/utils/log-negotiation.ts b/backend/src/features/telnet/utils/log-negotiation.ts index f2fb9f01..c55a2e05 100644 --- a/backend/src/features/telnet/utils/log-negotiation.ts +++ b/backend/src/features/telnet/utils/log-negotiation.ts @@ -1,5 +1,5 @@ import { logger } from '../../../shared/utils/logger.js'; -import { TelnetOptions } from '../types/telnet-options.js'; +import { TelnetOptions } from '../models/telnet-options.js'; export function logNegotiation( perspective: 'Received' | 'Send',