@mudlet/mudlet-web 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +338 -338
  2. package/README.md +158 -150
  3. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  4. package/dist-lib/branding.d.ts +1 -1
  5. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  6. package/dist-lib/hooks/useOutput.d.ts +3 -1
  7. package/dist-lib/import/defaultPackages.d.ts +35 -2
  8. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  9. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  10. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  11. package/dist-lib/import/defaults/mudlet-mapper.xml +13023 -13023
  12. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  13. package/dist-lib/import/packageExport.d.ts +71 -0
  14. package/dist-lib/import/packageInstaller.d.ts +4 -1
  15. package/dist-lib/index.js +45910 -39931
  16. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  17. package/dist-lib/map/MapStore.d.ts +32 -4
  18. package/dist-lib/map/mapImageExport.d.ts +1 -1
  19. package/dist-lib/map/mapZoom.d.ts +71 -0
  20. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  21. package/dist-lib/mud/MudSession.d.ts +113 -11
  22. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  23. package/dist-lib/mud/commandSplit.d.ts +16 -0
  24. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  25. package/dist-lib/mud/connection/MudClient.d.ts +136 -19
  26. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  27. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  28. package/dist-lib/mud/events.d.ts +86 -4
  29. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  30. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  31. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  32. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  33. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  34. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  35. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  36. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  37. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  38. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  39. package/dist-lib/mud/protocol/index.d.ts +1 -1
  40. package/dist-lib/mud/protocol/msspTls.d.ts +54 -0
  41. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  42. package/dist-lib/mud/protocol/tlsCodes.d.ts +4 -0
  43. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  44. package/dist-lib/mud/text/Console.d.ts +64 -0
  45. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  46. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  47. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  48. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  49. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  50. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  51. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  52. package/dist-lib/scripting/IScriptingRuntime.d.ts +22 -0
  53. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  54. package/dist-lib/scripting/ScriptingAPI.d.ts +331 -54
  55. package/dist-lib/scripting/ScriptingEngine.d.ts +181 -24
  56. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  57. package/dist-lib/scripting/lua/LuaRuntime.d.ts +159 -2
  58. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  59. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  60. package/dist-lib/storage/index.d.ts +1 -1
  61. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  62. package/dist-lib/storage/schema.d.ts +166 -4
  63. package/dist-lib/styles.css +1 -1
  64. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  65. package/dist-lib/ui/CharLoginModal.d.ts +5 -1
  66. package/dist-lib/ui/CommandBar.d.ts +7 -1
  67. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  68. package/dist-lib/ui/FileBrowserModal.d.ts +1 -0
  69. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  70. package/dist-lib/ui/HelpModal.d.ts +11 -0
  71. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  72. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  73. package/dist-lib/ui/SettingsModal.d.ts +5 -1
  74. package/dist-lib/ui/TlsAlertBanner.d.ts +18 -0
  75. package/dist-lib/ui/TlsCertificateBox.d.ts +20 -0
  76. package/dist-lib/ui/TlsUpgradeModal.d.ts +16 -0
  77. package/dist-lib/ui/Toolbar.d.ts +2 -1
  78. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  79. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  80. package/dist-lib/ui/components/index.d.ts +1 -0
  81. package/dist-lib/ui/components/useModalFocus.d.ts +6 -0
  82. package/dist-lib/ui/helpTopics.d.ts +19 -0
  83. package/dist-lib/ui/labels/LabelManager.d.ts +32 -0
  84. package/dist-lib/ui/labels/labelLinks.d.ts +52 -0
  85. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  86. package/dist-lib/ui/markdown.d.ts +10 -1
  87. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  88. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  89. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  90. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  91. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  92. package/dist-lib/ui/output/linkNavigation.d.ts +22 -0
  93. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  94. package/dist-lib/ui/search/matcher.d.ts +15 -0
  95. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  96. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  97. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  98. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  99. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  100. package/dist-lib/ui/windows/WindowManager.d.ts +149 -2
  101. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  102. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  103. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  104. package/dist-lib/ui/windows/types.d.ts +35 -0
  105. package/dist-lib/utils/describeThrown.d.ts +22 -0
  106. package/dist-lib/utils/fontLoader.d.ts +5 -0
  107. package/dist-lib/vfs-sw.js +131 -131
  108. package/dist-lib/vite.js +22 -0
  109. package/package.json +97 -93
  110. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  111. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -3,7 +3,7 @@ import { AnsiAwareBuffer } from "../text/FormatState";
3
3
  import { type ChunkProcessor } from "../triggers/ChunkProcessor";
4
4
  import type { MudClientEvents } from "../events";
5
5
  export type { MudClientEvents } from "../events";
6
- export { SUPPORTED_SERVER_ENCODINGS } from "../protocol";
6
+ export { SUPPORTED_SERVER_ENCODINGS, DEFAULT_SERVER_ENCODING, canonicalServerEncoding, canEncodeForServer } from "../protocol";
7
7
  export interface MudClientOptions {
8
8
  url: string;
9
9
  mccpEnabled?: boolean;
@@ -67,9 +67,9 @@ export interface MudClientOptions {
67
67
  /** Whether the link to the *game server* is TLS-encrypted, reported as the
68
68
  * NEW-ENVIRON `TLS` capability. Defaults to whether `url` is `wss://` — the
69
69
  * correct answer for a direct websocket-mode connection. In proxy (`mud`)
70
- * mode the caller passes `false` explicitly, because a `wss://` proxy URL
71
- * only secures the browser↔proxy hop while the proxy↔MUD telnet socket is
72
- * plaintext (see connectionSecureTransport). */
70
+ * mode the caller passes the answer explicitly, because a `wss://` proxy URL
71
+ * only secures the browser↔proxy hop: the proxy↔MUD leg is plaintext telnet
72
+ * unless the profile enabled TLS (see connectionSecureTransport). */
73
73
  secureTransport?: boolean;
74
74
  /** Whether to advertise screen-reader use, reported as the MTTS SCREEN
75
75
  * READER bit and the NEW-ENVIRON `SCREEN_READER` capability
@@ -88,6 +88,51 @@ export interface MudClientOptions {
88
88
  * of each GA-terminated data block — the IRE-server bug Mudlet patches in
89
89
  * `cTelnet::gotPrompt`. See LineAssembler. */
90
90
  fixUnnecessaryLinebreaks?: boolean;
91
+ /** Mudlet's `Host::mUndoServerWrap` — rejoin the lines the game wrapped
92
+ * itself, so triggers and rendering see whole logical lines. Default false.
93
+ * See LineAssembler and mud/text/serverWrap.ts. */
94
+ undoServerWrap?: boolean;
95
+ /** The column that game wraps at (`Host::mUndoServerWrapWidth`, default 80).
96
+ * Only consulted while {@link undoServerWrap} is on. */
97
+ undoServerWrapWidth?: number;
98
+ /** Mudlet's `setConfig("inputLineStrictUnixEndings", …)` (host flag
99
+ * `mUSE_UNIX_EOL`). Default false. When true a submitted command is
100
+ * terminated with a bare `\n` instead of the telnet-standard `\r\n`
101
+ * (`cTelnet::sendData` appends the CR only when the flag is off) — some
102
+ * Unix-y servers treat the CR as part of the command. */
103
+ inputLineStrictUnixEndings?: boolean;
104
+ /** Mudlet's `setConfig("specialForceGAOff", …)` (host flag `mFORCE_GA_OFF`).
105
+ * Default false. When true an inbound IAC GA / IAC EOR is *not* treated as
106
+ * a prompt marker: the session never latches into GA-driven mode and the
107
+ * marker becomes a plain newline in the data stream, exactly as
108
+ * `cTelnet::processSocketData` does in its `else` branch. For servers whose
109
+ * GA placement is wrong often enough that prompt detection does more harm
110
+ * than good. Read once per connect (Mudlet snapshots it in `connectIt`), so
111
+ * a mid-session change applies on the next dial. */
112
+ specialForceGAOff?: boolean;
113
+ /** Mudlet's `setConfig("promptForVersionInTTYPE", …)` (host flag
114
+ * `mPromptedForVersionInTTYPE`). Default false. Latches once the KaVir
115
+ * auto-detect has had its say for this profile, so a user who then turns
116
+ * `versionInTTYPE` back off isn't overridden on the next connect. */
117
+ promptForVersionInTTYPE?: boolean;
118
+ /** Mudlet's `setConfig("promptForMXPProcessorOn", …)` (host flag
119
+ * `mPromptedForMXPProcessorOn`). Default false. Latches once the in-band
120
+ * MXP auto-detect has fired for this profile; together with
121
+ * `specialForceMXPProcessorOn` it decides whether an `ESC[<n>z` from a
122
+ * server that never negotiated option 91 may still start MXP. */
123
+ promptForMXPProcessorOn?: boolean;
124
+ /** Mudlet's `setConfig("specialForceMXPProcessorOn", …)` (host flag
125
+ * `mForceMXPProcessorOn`). Default false. Only consulted here for the
126
+ * in-band-detection gate above — the parser-side effect lives in
127
+ * ScriptingEngine, which owns the MXP processor. */
128
+ specialForceMXPProcessorOn?: boolean;
129
+ /** Mudlet's `setConfig("versionInTTYPE", …)` (host flag `mVersionInTTYPE`).
130
+ * Default false. When true the first TTYPE cycle value carries our version
131
+ * after the client name (`MUDLET-WEB 1.2.3`). Off by default because the
132
+ * period is not a legal TTYPE character per RFC 1091 — but servers running
133
+ * KaVir's protocol snippet parse a version out of it and fall back to 16
134
+ * colours without one, so it's opt-in rather than absent. */
135
+ versionInTTYPE?: boolean;
91
136
  /** WebSocket subprotocols to advertise in the opening handshake's
92
137
  * `Sec-WebSocket-Protocol` header (RFC 6455), in preference order — the
93
138
  * server selects at most one. Mutually-exclusive stream *modes*, not layers:
@@ -117,6 +162,8 @@ export declare const MUD_TELNET_SUBPROTOCOL = "telnet.mudstandards.org";
117
162
  * GMCP/MSDP/MSSP subnegotiation streams.
118
163
  */
119
164
  export declare class MudClient {
165
+ /** The live WebSocket, or null when there is none — before the first
166
+ * connect, and from the moment disconnect() lets go of one. */
120
167
  private socket;
121
168
  private readonly eventBus;
122
169
  private readonly chunkProcessor;
@@ -130,8 +177,12 @@ export declare class MudClient {
130
177
  private readonly negotiator;
131
178
  /** Latches once `sysCharacterModeDetected` has fired for this connection so
132
179
  * the warning isn't repeated. Reset on each connect(). See
133
- * {@link maybeDetectCharacterMode}. */
180
+ * {@link checkCharacterModePattern}. */
134
181
  private charModeDetected;
182
+ /** Pending character-at-a-time verdict, armed by a submitted game command
183
+ * while ECHO+SGA are active and cancelled if the server releases ECHO
184
+ * first. Null when nothing is in flight. */
185
+ private charModeTimer;
135
186
  /** Byte↔char codec for the session (inbound streaming decoder + outgoing
136
187
  * encoding). Swapped between encodings by the CHARSET handler. */
137
188
  private readonly codec;
@@ -149,6 +200,14 @@ export declare class MudClient {
149
200
  * handshake this session, so a server that re-offers GMCP doesn't make us
150
201
  * announce ourselves twice. Reset on each connect(). */
151
202
  private gmcpHelloSent;
203
+ /** Set when this dial asked the proxy for TLS (`&tls=1` in the URL), so the
204
+ * client knows to expect a `tls.established` control frame and to police
205
+ * the deadline below. */
206
+ private readonly tlsRequested;
207
+ /** True once the proxy confirmed the handshake, or once any game bytes have
208
+ * arrived (which can only happen through a working tunnel). */
209
+ private tlsResolved;
210
+ private tlsDeadline;
152
211
  commandEcho: boolean;
153
212
  /** Gates the in-band `!!SOUND(...)` / `!!MUSIC(...)` tag parsing — the tag
154
213
  * bytes are legitimate text on non-MSP MUDs. */
@@ -156,26 +215,78 @@ export declare class MudClient {
156
215
  private readonly mspParser;
157
216
  /** WebSocket subprotocols advertised on connect (see MudClientOptions). */
158
217
  private readonly subprotocols;
159
- constructor({ url, mccpEnabled, commandEcho, chunkProcessor, promptTimeoutMs, gmcpEnabled, mttsEnabled, msdpEnabled, msspEnabled, charsetEnabled, mspEnabled, mxpEnabled, mnesEnabled, newEnvironEnabled, secureTransport, screenReaderAdvertised, nawsEnabled, fixUnnecessaryLinebreaks, subprotocols, }: MudClientOptions, eventBus: EventBus<MudClientEvents>);
160
- /** Mudlet-parity character-at-a-time detection (cTelnet::
161
- * checkCharacterModePattern). When the server has both asked to suppress
162
- * go-ahead (IAC WILL SGA — which we refuse) *and* enabled server-side echo,
163
- * it's driving a character-at-a-time session that mudix, a line-based
164
- * client, can't handle well. Raise `sysCharacterModeDetected` once so the
165
- * UI / scripts can warn the user. Checked on both trigger edges: an SGA
166
- * request arriving while echo is already on, and echo committing while SGA
167
- * was already requested. */
168
- private maybeDetectCharacterMode;
218
+ /** Mudlet `mUSE_UNIX_EOL` terminate submitted commands with a bare `\n`.
219
+ * Read on every send, so a change applies to the next command (Mudlet reads
220
+ * the host flag in `sendData` too). */
221
+ private strictUnixEndings;
222
+ /** Mudlet `mFORCE_GA_OFF` see MudClientOptions.specialForceGAOff. A live
223
+ * session keeps parsing with the value it connected with (Mudlet snapshots
224
+ * the host flag into cTelnet at `connectIt`), which here falls out of
225
+ * MudSession building a fresh client per connect. An unconnected one takes
226
+ * the change straight away — see {@link setForceGaOff}. */
227
+ private forceGaOff;
228
+ /** Handed to the telnet sequence parser and mutated in place, so the parser
229
+ * reads the current setting rather than the one it was built with. */
230
+ private readonly telnetParserOpts;
231
+ constructor({ url, mccpEnabled, commandEcho, chunkProcessor, promptTimeoutMs, gmcpEnabled, mttsEnabled, msdpEnabled, msspEnabled, charsetEnabled, mspEnabled, mxpEnabled, mnesEnabled, newEnvironEnabled, secureTransport, screenReaderAdvertised, nawsEnabled, fixUnnecessaryLinebreaks, undoServerWrap, undoServerWrapWidth, inputLineStrictUnixEndings, specialForceGAOff, versionInTTYPE, promptForVersionInTTYPE, promptForMXPProcessorOn, specialForceMXPProcessorOn, subprotocols, }: MudClientOptions, eventBus: EventBus<MudClientEvents>);
232
+ /** Mudlet-parity character-at-a-time detection (cTelnet::sendData's
233
+ * `armCharacterModeDetection`). ECHO+SGA on its own is *not* the signature:
234
+ * an ordinary line-mode server masking a password negotiates exactly the
235
+ * same pair. The two are told apart by behaviour, so detection is armed
236
+ * only by a submitted game command and only decides {@link
237
+ * CHARACTER_MODE_DETECT_MS} later — a password mask sends WONT ECHO right
238
+ * after the masked line and cancels it first.
239
+ *
240
+ * Re-armed on every game command so the window always measures from the
241
+ * most recent submission. */
242
+ private armCharacterModeDetection;
243
+ private cancelCharacterModeDetection;
244
+ /** The armed window elapsed with ECHO+SGA still active — a full input line
245
+ * was submitted without the server releasing echo, which a password prompt
246
+ * never does. Raise `sysCharacterModeDetected` once per connection so the
247
+ * UI / scripts can warn the user.
248
+ *
249
+ * Accepted limitation (same as Mudlet's): this can't tell genuine
250
+ * character-at-a-time from the rarer cases of a buggy password prompt that
251
+ * never sends WONT ECHO, or a line-mode server using persistent
252
+ * server-side echo. We stay in line mode regardless and the message is
253
+ * only advisory. */
254
+ private checkCharacterModePattern;
169
255
  setMccpEnabled(enabled: boolean): void;
170
256
  isMccpEnabled(): boolean;
171
257
  /** Mudlet `setConfig("fixUnnecessaryLinebreaks", …)`. Takes effect on the
172
258
  * next GA-driven block; never retroactive. */
173
259
  setFixUnnecessaryLinebreaks(enabled: boolean): void;
260
+ /**
261
+ * Mudlet `setConfig("specialForceGAOff", …)`. Only an UNCONNECTED client
262
+ * takes the change: a live session has to keep parsing telnet the way it
263
+ * connected, or a GA mid-stream would suddenly mean something else. Mudlet
264
+ * draws the same line — `cacheHostSettings()` is re-run from setConfig only
265
+ * while the socket is unconnected, and otherwise at the next connect.
266
+ */
267
+ setForceGaOff(enabled: boolean): void;
268
+ /** Mudlet `setConfig("undoServerWrap", …)`. Live: the next server line is
269
+ * judged under the new setting. */
270
+ setUndoServerWrap(enabled: boolean): void;
271
+ /** Mudlet `setConfig("undoServerWrapWidth", …)`. */
272
+ setUndoServerWrapWidth(width: number): void;
273
+ /** Commit a line held for a wrap continuation whose flush delay has passed.
274
+ * See {@link LineAssembler.pumpServerWrapDue}. */
275
+ pumpServerWrapDue(now?: number): boolean;
276
+ /** Mudlet `setConfig("inputLineStrictUnixEndings", …)`. Live: `cTelnet::
277
+ * sendData` reads the host flag per send, so the next command submitted
278
+ * uses the new terminator. */
279
+ setInputLineStrictUnixEndings(enabled: boolean): void;
174
280
  /** Mudlet `addSupportedTelnetOption(option)`. See TelnetNegotiator. */
175
281
  addSupportedTelnetOption(option: number): boolean;
176
282
  setPromptTimeoutMs(ms: number): void;
177
283
  getPromptTimeoutMs(): number;
178
284
  connect(): void;
285
+ /** Stop policing the TLS deadline — the handshake is accounted for. */
286
+ private resolveTls;
287
+ /** Decode one proxy control frame. Anything unrecognised is ignored so a
288
+ * newer proxy can add message types without breaking older clients. */
289
+ private handleControlFrame;
179
290
  disconnect(): void;
180
291
  /** Whether MSP is live on this connection (negotiated, not merely allowed
181
292
  * by the profile config) — gates Mudlet's receiveMSP. */
@@ -187,7 +298,11 @@ export declare class MudClient {
187
298
  isSocketOpen(): boolean;
188
299
  isPasswordMode(): boolean;
189
300
  shouldEchoCommand(): boolean;
190
- send(message: string): void;
301
+ /** `isGameCommand` mirrors `cTelnet::sendData`'s third argument: only a real
302
+ * game command (typed or from a script's `send()`) may arm
303
+ * character-at-a-time detection. Credentials and internal protocol replies
304
+ * route through here too and must not. */
305
+ send(message: string, isGameCommand?: boolean): void;
191
306
  private sendRaw;
192
307
  /** Encodes a Latin-1 byte-string to raw bytes and sends it as a binary
193
308
  * WebSocket frame. The proxy worker expects binary, not base64. Every
@@ -232,9 +347,11 @@ export declare class MudClient {
232
347
  * (the zMUD generic out-of-band channel) and sends it raw. Returns false
233
348
  * when the socket isn't open. */
234
349
  sendTelnetChannel102(msg: string): boolean;
235
- /** Frame a raw `IAC SB <opt> <payload> IAC SE` subnegotiation and send it
236
- * with no encoding conversion (each char → one byte), like the other
237
- * telnet negotiations. Shared by sendATCP / sendTelnetChannel102. */
350
+ /** Frame an `IAC SB <opt> <payload> IAC SE` subnegotiation and send it.
351
+ * `payload` must already be a Latin-1 byte-string (each char → one byte)
352
+ * with any IAC handled the framing is all this does, since ATCP and
353
+ * channel 102 need different byte treatments. Shared by sendATCP /
354
+ * sendTelnetChannel102. */
238
355
  private sendSubnegotiation;
239
356
  /** Mudlet `getServerEncoding()`. The IANA name of the decoder currently
240
357
  * applied to the inbound stream — 'utf-8' until CHARSET negotiation (or an
@@ -18,6 +18,25 @@ export interface TelnetNegotiatorFlags {
18
18
  * key. Default false; some MUDs adjust their output (e.g. suppress ASCII
19
19
  * art, add extra room-description detail) when this is set. */
20
20
  screenReaderAdvertised: boolean;
21
+ /** Whether the first TTYPE cycle value carries our version after the client
22
+ * name — Mudlet's `versionInTTYPE` config key (`mVersionInTTYPE`). Default
23
+ * false: RFC 1091 doesn't permit the period, so Mudlet stopped sending it
24
+ * in 2024. Servers running KaVir's protocol snippet want it anyway (without
25
+ * a version they assume 1.0 and cap colour support at 16), which is what
26
+ * the KaVir auto-detect below turns it on for. */
27
+ versionInTTYPE: boolean;
28
+ /** Whether this profile has already been through the KaVir auto-detect —
29
+ * Mudlet's `promptForVersionInTTYPE` config key (`mPromptedForVersionInTTYPE`).
30
+ * When true the detector below is disabled, so a user who turned
31
+ * `versionInTTYPE` back off is not overridden on every reconnect. */
32
+ versionInTTYPEPrompted: boolean;
33
+ /** Whether an in-band `ESC[<n>z` may still auto-start MXP on a server that
34
+ * never negotiated telnet option 91. Mudlet gates its equivalent scan on
35
+ * `mForceMXPProcessorOn || !mPromptedForMXPProcessorOn`
36
+ * (`cTelnet::gotRest`): once the auto-detect has fired for a profile, only
37
+ * the forced-on state keeps it live — so a user who then turns
38
+ * `specialForceMXPProcessorOn` off is not re-overridden every connect. */
39
+ mxpInBandDetectionEnabled: boolean;
21
40
  }
22
41
  export interface TelnetNegotiatorHooks {
23
42
  sendRaw(data: string): void;
@@ -30,6 +49,10 @@ export interface TelnetNegotiatorHooks {
30
49
  /** Current inbound encoding (IANA label) — drives the MTTS UTF-8 bit and
31
50
  * the MNES/NEW-ENVIRON CHARSET variable. */
32
51
  getEncoding(): string;
52
+ /** The server's option-negotiation order matched KaVir's protocol snippet —
53
+ * it wants a version in our TTYPE reply. Fired at most once per connection;
54
+ * the owner turns `versionInTTYPE` on and redials. */
55
+ onKaVirProtocolDetected(): void;
33
56
  }
34
57
  /**
35
58
  * Telnet option negotiation for a MUD session. Owns the WILL/WONT/DO/DONT
@@ -76,6 +99,15 @@ export declare class TelnetNegotiator {
76
99
  * telnet handshake, so the in-band signal is the reliable trigger. Reset on
77
100
  * each connect(). */
78
101
  private mxpStarted;
102
+ /** Rolling window of the last {@link KAVIR_NEGOTIATION_ORDER}`.length`
103
+ * options the server sent us a WILL/DO for, oldest first — Mudlet's
104
+ * `mNegotiationOrder`. Compared against the KaVir fingerprint after each
105
+ * push. Reset on each connect(). */
106
+ private negotiationOrder;
107
+ /** Latches once the KaVir fingerprint has matched this connection, so the
108
+ * detector fires the hook at most once even though a server may keep
109
+ * re-offering options. */
110
+ private kaVirDetected;
79
111
  /** True once we've sent IAC WILL NAWS this session (proactively on connect,
80
112
  * or in response to a server-initiated IAC DO NAWS), so we don't re-offer. */
81
113
  private nawsWillSent;
@@ -141,6 +173,13 @@ export declare class TelnetNegotiator {
141
173
  * supported-options registry answers every occurrence; anything left is
142
174
  * surfaced as a `telnet.event`. */
143
175
  private handleNegotiationCommand;
176
+ /** Mudlet `cTelnet::trackKaVirNegotiation`. Records the option of each
177
+ * inbound WILL/DO in a rolling window and fires the hook the first time the
178
+ * window equals the KaVir fingerprint. Recording happens before the
179
+ * per-frame response dedupe, so the order seen here is the order the server
180
+ * actually sent — the same thing Mudlet feeds its own tracker from
181
+ * `processTelnetCommand`. */
182
+ private trackKaVirNegotiation;
144
183
  private respondToKnownOption;
145
184
  /** Raise `telnet.event` for an SB of an option we neither handle natively
146
185
  * nor auto-negotiate (type 5 in Mudlet's sysTelnetEvent mapping). */
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Mudlet's `<XX>` byte placeholders for `feedTelnet(data)` —
3
+ * `TLuaInterpreter::decodeBytes`.
4
+ *
5
+ * Lua strings are text, and the bytes a telnet stream is made of mostly are not:
6
+ * `\xff\xf9` typed into a script is awkward to read and easy to get wrong, and a
7
+ * profile that saves scripts as XML has to survive round-tripping them. So
8
+ * Mudlet lets the data name its control bytes instead — `<T_IAC><T_GA>` for a
9
+ * prompt marker, `<ESC>` for the start of an ANSI sequence — and decodes them
10
+ * here before the bytes reach the parser.
11
+ *
12
+ * Anything that isn't a known tag is passed through unchanged, so ordinary text
13
+ * containing `<` survives; `<<` and `>>` are the escapes for a literal one.
14
+ */
15
+ /**
16
+ * Version of the tag table, as Mudlet's `decodeBytes` reports it: feeding the
17
+ * empty string yields this number as text rather than nothing, so a script can
18
+ * ask which tags it can count on.
19
+ */
20
+ export declare const TELNET_BYTE_TAG_TABLE_VERSION = 1;
21
+ /**
22
+ * Decode Mudlet's `<XX>` byte placeholders into the bytes they name, as
23
+ * `feedTelnet` does before handing data to the telnet parser.
24
+ *
25
+ * The result is a byte-string (each code unit ≤ 0xFF), which is what the inbound
26
+ * pipeline takes — the same shape a decoded WebSocket frame arrives in.
27
+ *
28
+ * - `<<` yields a literal `<`, `>>` a literal `>`.
29
+ * - An unknown `<…>` is left exactly as written, so prose and markup pass
30
+ * through: `<b>bold</b>` is text, not a failed decode.
31
+ * - An unclosed `<` is likewise just a `<`.
32
+ * - The empty string yields the tag table's version number as text, which is how
33
+ * a script asks what this build understands.
34
+ */
35
+ export declare function decodeTelnetByteTags(input: string): string;
@@ -2,6 +2,55 @@ import type { AnsiAwareBuffer } from './text/FormatState';
2
2
  import type { MspCommand } from './protocol';
3
3
  import type { ScriptLogSource } from './MudSession';
4
4
  export type SessionStatus = 'disconnected' | 'connecting' | 'connected';
5
+ /** The peer certificate as reported by the proxy. Mirrors the four fields
6
+ * Mudlet shows (issuer / issued-to / expiry / serial) plus a little extra for
7
+ * diagnostics. All strings, already formatted for display. */
8
+ export interface TlsCertInfo {
9
+ subject: string;
10
+ subjectOrg: string;
11
+ issuer: string;
12
+ issuerOrg: string;
13
+ validFrom: string;
14
+ validTo: string;
15
+ serial: string;
16
+ fingerprint: string;
17
+ altNames: string;
18
+ }
19
+ export interface TlsEstablished {
20
+ /** Negotiated protocol, e.g. `TLSv1.3`. Empty when the proxy can't report it. */
21
+ protocol: string;
22
+ /** Negotiated cipher suite name. Empty when the proxy can't report it. */
23
+ cipher: string;
24
+ cert: TlsCertInfo | null;
25
+ /** False when the proxy runtime cannot inspect certificates at all. */
26
+ certInspection: boolean;
27
+ /** Certificate faults tolerated because of the profile's ignore-flags. */
28
+ acceptedDespite: string[];
29
+ /** Cert-tolerance options this proxy was asked for but cannot honour. */
30
+ unsupportedOptions: string[];
31
+ }
32
+ /** What the UI knows about the current connection's TLS state. */
33
+ export type TlsStatus = {
34
+ kind: 'established';
35
+ info: TlsEstablished;
36
+ } | {
37
+ kind: 'error';
38
+ info: TlsError;
39
+ }
40
+ /** TLS was asked for but nothing came back — see the `tls.timeout` event. */
41
+ | {
42
+ kind: 'timeout';
43
+ host: string;
44
+ port: number;
45
+ };
46
+ export interface TlsError {
47
+ /** Primary blocking fault, e.g. `CERT_HAS_EXPIRED`. */
48
+ code: string;
49
+ message: string;
50
+ codes: string[];
51
+ cert: TlsCertInfo | null;
52
+ certInspection: boolean;
53
+ }
5
54
  /** A pending Mudlet `invokeFileDialog(...)` request. The Lua handler that
6
55
  * called it is suspended (parked coroutine) until `onPick` fires, so the UI
7
56
  * must always resolve it eventually — pass the picked VFS path, or '' for
@@ -26,6 +75,24 @@ export type MudClientEvents = {
26
75
  /** The WebSocket subprotocol the server selected from our advertised list
27
76
  * (RFC 6455), or '' if none — only emitted when we advertised any. */
28
77
  'client.subprotocol': [protocol: string];
78
+ /** The proxy completed a TLS handshake with the game and the link is
79
+ * carrying decrypted traffic. `cert` is null when the proxy cannot inspect
80
+ * certificates (the Cloudflare Worker runtime can't), in which case
81
+ * `certInspection` is false. `acceptedDespite` lists any certificate faults
82
+ * the profile's ignore-flags waved through — non-empty means encrypted but
83
+ * not authenticated. */
84
+ 'tls.established': [info: TlsEstablished];
85
+ /** The proxy refused the game's certificate, or the TLS handshake failed.
86
+ * The connection is closing; `codes` carries every blocking fault. */
87
+ 'tls.error': [info: TlsError];
88
+ /** TLS was requested but the link produced no evidence of a handshake before
89
+ * the deadline. Distinct from `tls.error` because the cause is ambiguous:
90
+ * a proxy too old to understand `&tls=1`, or a Cloudflare-Worker-backed
91
+ * proxy where a rejected certificate hangs silently instead of reporting. */
92
+ 'tls.timeout': [info: {
93
+ host: string;
94
+ port: number;
95
+ }];
29
96
  'gmcp.negotiated': void;
30
97
  'msdp.negotiated': void;
31
98
  'mssp.negotiated': void;
@@ -122,11 +189,21 @@ export type MudClientEvents = {
122
189
  * (`'SUPPRESS_GO_AHEAD'` or `'LINEMODE'`). */
123
190
  'protocol.rejected': [protocol: string];
124
191
  /** Mudlet `sysCharacterModeDetected`. Fires once per connection when the
125
- * server has both asked to suppress go-ahead (IAC WILL SGA) *and* enabled
126
- * server-side echo the classic character-at-a-time signature that mudix,
127
- * a line-based client, can't drive well. Lets scripts / the UI warn the
128
- * user that input may not behave as expected. */
192
+ * server has both asked to suppress go-ahead (IAC WILL SGA) *and* kept
193
+ * server-side echo on across a submitted game command — the
194
+ * character-at-a-time signature that mudix, a line-based client, can't
195
+ * drive well. The three-second delay is what separates it from an ordinary
196
+ * password mask, which negotiates the same pair but releases echo as soon
197
+ * as the masked line is in. Lets scripts / the UI warn the user that input
198
+ * may not behave as expected. */
129
199
  'charmode.detected': void;
200
+ /** The server's telnet option-negotiation order matched KaVir's protocol
201
+ * snippet (Mudlet `cTelnet::trackKaVirNegotiation`). Such servers read a
202
+ * decimal version out of our TTYPE client-name reply and fall back to 16
203
+ * colours without one, so the owner switches `versionInTTYPE` on and
204
+ * redials. Fires at most once per connection, and never once the profile's
205
+ * `promptForVersionInTTYPE` latch is set. */
206
+ 'kavir.detected': void;
130
207
  } & Record<string, any>;
131
208
  export type MudEvents = MudClientEvents & {
132
209
  'status': [status: SessionStatus];
@@ -140,6 +217,11 @@ export type MudEvents = MudClientEvents & {
140
217
  'script.clearcmd': void;
141
218
  'script.selectcmd': void;
142
219
  'script.cmdlinesuggestions': [items: string[]];
220
+ /** Words Tab completion must never offer, whatever list they came from. */
221
+ 'script.cmdlineblacklist': [items: string[]];
222
+ /** Whether the main command bar's history is persisted at all
223
+ * (setSaveCommandHistory); the size cap stays a separate setting. */
224
+ 'script.savecommandhistory': [save: boolean];
143
225
  'script.openvfs': [path: string];
144
226
  /** Fired by ScriptingAPI when Lua calls `invokeFileDialog(...)`. The UI
145
227
  * (ProfileSession) shows the in-app VFS picker and resolves the request
@@ -0,0 +1,29 @@
1
+ /** One entry of Mudlet's bundled-game catalogue (its C++ `GameDetail`). */
2
+ export interface BundledGame {
3
+ name: string;
4
+ hostUrl: string;
5
+ port: number;
6
+ tlsEnabled: boolean;
7
+ /** Small HTML fragment of website/forum/Discord links. */
8
+ websiteInfo: string;
9
+ /** Qt resource path of the game's icon, kept as the upstream identifier. */
10
+ icon: string;
11
+ /** Filename of the copy vendored in ./icons/, when one was available.
12
+ * Resolve it with `gameIconUrl()` rather than building a path by hand —
13
+ * the bundler only emits assets it can see referenced. */
14
+ iconFile?: string;
15
+ description: string;
16
+ /** The game's own loader installs its full interface, so the generic
17
+ * starter UI is not preinstalled for it. */
18
+ providesOwnUi?: boolean;
19
+ /** Other hostnames the game answers on. */
20
+ alternateHostUrls?: string[];
21
+ }
22
+ export declare const BUNDLED_GAMES: readonly BundledGame[];
23
+ /** The bundled game called `name`, matched the way Mudlet matches it
24
+ * (TGameDetails::findGame — exact, case-sensitive). */
25
+ export declare function findBundledGame(name: string): BundledGame | null;
26
+ /** Whether the game reachable at `hostUrl` installs its own full interface,
27
+ * in which case the generic starter UI is not preinstalled (Mudlet's
28
+ * TGameDetails::gameProvidesOwnUi). */
29
+ export declare function gameProvidesOwnUi(hostUrl: string): boolean;
@@ -0,0 +1,5 @@
1
+ import { type BundledGame } from './bundledGames';
2
+ /** URL of a bundled game's logo, or null when it has none vendored. Accepts the
3
+ * entry or its exact name, so a caller holding only a profile name (which is
4
+ * the game's name for a profile made from the catalogue) can ask too. */
5
+ export declare function gameIconUrl(game: BundledGame | string | undefined): string | null;
@@ -1,3 +1,4 @@
1
+ import { ItemIdSequence } from '../ItemIdSequence';
1
2
  import type { KeyNode } from '../../storage/schema';
2
3
  export type { KeyNode };
3
4
  type TempFn = () => void;
@@ -9,8 +10,11 @@ export interface KeyCodeInfo {
9
10
  export declare class KeyEngine {
10
11
  private readonly temp;
11
12
  private perm;
12
- private nextId;
13
- /** Number of live session-scoped temp keys (Mudlet `getProfileStats` temp count). */
13
+ /** Shared with every other engine in the profile — see ItemIdSequence. */
14
+ private idSeq;
15
+ setIdSequence(seq: ItemIdSequence): void;
16
+ /** Number of live session-scoped temp keys (Mudlet `getProfileStats` temp
17
+ * count). Killed-but-unreaped keys are not live and don't count. */
14
18
  get tempCount(): number;
15
19
  addTemp(key: string, modifiers: string[], fn: TempFn, qt?: {
16
20
  keyCode: number;
@@ -29,6 +33,9 @@ export declare class KeyEngine {
29
33
  */
30
34
  getKeyCode(idOrName: number | string): KeyCodeInfo | null;
31
35
  killKey(id: number): boolean;
36
+ /** Free every key killed since the last call. Runs once per processed line
37
+ * batch, mirroring the deferred cleanup Mudlet's TKeyUnit does. */
38
+ reapKilled(): void;
32
39
  processTemp(event: KeyboardEvent): boolean;
33
40
  loadPerm(keybindings: KeyNode[]): void;
34
41
  matchPerm(event: KeyboardEvent): KeyNode | null;
@@ -25,7 +25,11 @@ export interface ReservedKeyWarning {
25
25
  }
26
26
  /** Platform accelerator — browser shortcuts use Cmd (meta) on macOS, Ctrl elsewhere. */
27
27
  export type Accel = 'ctrl' | 'meta';
28
- /** Detect the platform accelerator modifier. Falls back to Ctrl off-browser (tests). */
28
+ /** Detect the platform accelerator modifier, from `navigator.platform` on every
29
+ * call. Note that a `navigator` exists under vitest's node environment too, and
30
+ * reports the real host — so this is not a Ctrl fallback in tests, and a test
31
+ * that hard-codes one accelerator will pass on CI and fail on a Mac. Callers
32
+ * that need a fixed platform should pass `accel` explicitly. */
29
33
  export declare function detectAccel(): Accel;
30
34
  /** Render a keybinding as a display combo like "Ctrl+Shift+I". */
31
35
  export declare function formatKeyCombo(node: Pick<KeyNode, 'key' | 'modifiers'>): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Whether a keydown belongs to the focused text widget rather than to the
3
+ * keybinding engine.
4
+ *
5
+ * The MUD command line is a `<textarea class="command-input">` (an `<input>`
6
+ * of the same class in password mode) and holds focus for essentially the
7
+ * whole session. So the usual "don't hijack keys while the user is typing"
8
+ * guard — `tagName === 'TEXTAREA'` — cannot be applied to it: it is true
9
+ * almost always, and it silently disables every keybinding in the profile.
10
+ * Typing a command is the app's resting state, not a text-entry mode that
11
+ * should own every hotkey.
12
+ *
13
+ * Real text entry elsewhere — the Lua script editor (CodeMirror, so
14
+ * `contentEditable`), a modal's form field, the script tree's filter box —
15
+ * does keep the event to itself.
16
+ */
17
+ export declare function isTextEntryTarget(target: EventTarget | null): boolean;
@@ -1,5 +1,7 @@
1
1
  /** Qt::KeypadModifier — set by Mudlet when a binding came from the numpad. */
2
2
  export declare const QT_KEYPAD_MODIFIER = 536870912;
3
+ /** Qt::Key_unknown — what Mudlet stores for a key item with nothing bound to it. */
4
+ export declare const QT_KEY_UNKNOWN = 33554431;
3
5
  /**
4
6
  * Translate a Qt::Key integer (or already-translated DOM `KeyboardEvent.code`
5
7
  * string) into a DOM `KeyboardEvent.code`. Mudlet `tempKey` accepts both;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * GMCP `Char.Login` flow decisions.
3
+ *
4
+ * The server sends `Char.Login.Default` to ask for credentials and then waits,
5
+ * withholding its text login prompt until the client answers. Answering is
6
+ * therefore mandatory — every path here ends in either a credentials reply or a
7
+ * popup that will produce one. `Char.Login.Result` reports the outcome, and a
8
+ * server that rejects an attempt commonly re-sends `Char.Login.Default` to ask
9
+ * again, so these rules run repeatedly within one connection.
10
+ *
11
+ * Mirrors Mudlet's `GMCPAuthenticator` (GMCPAuthenticator.cpp). Kept free of
12
+ * React and session plumbing so the rules can be tested directly; the caller
13
+ * supplies the per-connection state.
14
+ */
15
+ /** What to do about a `Char.Login.Default` request. */
16
+ export type CharLoginAction =
17
+ /** Reply with the empty `{}` form — "no credentials, use your next method". */
18
+ {
19
+ kind: 'decline';
20
+ }
21
+ /** Send stored credentials without troubling the player. */
22
+ | {
23
+ kind: 'autofill';
24
+ account: string;
25
+ password: string;
26
+ }
27
+ /** Raise the credentials popup. */
28
+ | {
29
+ kind: 'prompt';
30
+ };
31
+ export interface CharLoginRequestState {
32
+ /** The `type` list from `Char.Login.Default`; empty when the server sent none. */
33
+ methods: string[];
34
+ /** The player chose "Use text login" earlier this connection. */
35
+ declined: boolean;
36
+ /** Credentials have already been sent once this connection (stored or typed). */
37
+ attempted: boolean;
38
+ /** Stored/in-memory credentials, if any. */
39
+ account?: string;
40
+ password?: string;
41
+ }
42
+ /**
43
+ * Decide how to answer a `Char.Login.Default`.
44
+ *
45
+ * The one automatic attempt per connection is the load-bearing rule: it makes a
46
+ * saved login silent when it works, and guarantees that a *wrong* saved login
47
+ * turns into a visible popup instead of a silent send-reject-send loop against
48
+ * the server's repeated asks.
49
+ */
50
+ export declare function decideCharLoginRequest(state: CharLoginRequestState): CharLoginAction;
51
+ /**
52
+ * The console line for a rejected login, in Mudlet's wording
53
+ * (`GMCPAuthenticator::handleAuthResult`). Servers doing GMCP login usually send
54
+ * no text of their own, so this is often the only trace of the failure.
55
+ */
56
+ /**
57
+ * The console line for credentials the game never answered at all: it closed the
58
+ * connection instead of sending `Char.Login.Result`.
59
+ *
60
+ * Measured against Achaea (2026-08-02): a rejected `Char.Login.Credentials` gets
61
+ * no result, no text, no close reason — the socket simply ends. The same wrong
62
+ * password typed into its *text* login prints "Password incorrect. For recovery,
63
+ * see …" before dropping, so the silence is specific to the GMCP path. Mudlet
64
+ * only reports `Char.Login.Result`, so it is equally silent here; without this
65
+ * line a mistyped password is indistinguishable from a network failure.
66
+ */
67
+ export declare const CHAR_LOGIN_SILENT_DROP_MESSAGE: string;
68
+ export declare function charLoginFailureMessage(message?: string): string;