@mudlet/mudlet-web 0.4.0 → 0.4.2
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.
- package/dist-lib/import/defaultPackages.d.ts +9 -5
- package/dist-lib/import/defaults/mpkg/mpkg.mpackage +0 -0
- package/dist-lib/index.js +2608 -2504
- package/dist-lib/map/xmlMapImport.d.ts +29 -0
- package/dist-lib/mud/connection/PingTracker.d.ts +13 -0
- package/dist-lib/mud/protocol/byteString.d.ts +33 -0
- package/dist-lib/mud/protocol/gmcp.d.ts +12 -7
- package/dist-lib/mud/protocol/index.d.ts +2 -1
- package/dist-lib/mud/protocol/mnes.d.ts +32 -9
- package/dist-lib/ui/sound/SoundManager.d.ts +16 -4
- package/dist-lib/ui/video/VideoManager.d.ts +6 -1
- package/dist-lib/utils/githubRawUrl.d.ts +1 -0
- package/package.json +1 -1
|
@@ -20,9 +20,38 @@
|
|
|
20
20
|
* </map>
|
|
21
21
|
*/
|
|
22
22
|
import type { MudletMap } from 'mudlet-map-binary-reader';
|
|
23
|
+
/**
|
|
24
|
+
* Why a document was refused. Mudlet keeps these three apart
|
|
25
|
+
* (`fileHoldsMapData` in `TMap.cpp`), because they are three different things
|
|
26
|
+
* to tell a player:
|
|
27
|
+
*
|
|
28
|
+
* `not-a-map` somebody else's well-formed document — a game's "no map
|
|
29
|
+
* here" page answered with a 200, or a `<MudletPackage>`.
|
|
30
|
+
* Every element parses and none of it means anything, which
|
|
31
|
+
* is what used to count as importing a map successfully.
|
|
32
|
+
* `import-failed` a map document that would not parse: the player's own map,
|
|
33
|
+
* damaged. Rooted at `<map>`, so the root test passes and the
|
|
34
|
+
* failure is further in.
|
|
35
|
+
* `damaged` not XML at all — no element to read a root from.
|
|
36
|
+
*
|
|
37
|
+
* The root is taken from the first start tag in the text rather than from a
|
|
38
|
+
* parsed document, exactly as Mudlet's streaming reader does, so a file that is
|
|
39
|
+
* rooted at `<map>` but truncated is still recognised as a map that broke.
|
|
40
|
+
*/
|
|
41
|
+
export type XmlMapRefusal = 'not-a-map' | 'import-failed' | 'damaged';
|
|
42
|
+
export type XmlMapParse = {
|
|
43
|
+
ok: true;
|
|
44
|
+
map: MudletMap;
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
reason: XmlMapRefusal;
|
|
48
|
+
};
|
|
23
49
|
/**
|
|
24
50
|
* Parse an XML map document into the `MudletMap` shape `loadFromBinary`
|
|
25
51
|
* ingests. Returns null when the text isn't well-formed XML or the root
|
|
26
52
|
* element isn't `<map>` (e.g. an HTML error page served for the map URL).
|
|
53
|
+
* {@link parseXmlMapResult} is the same parse with the refusal reason kept.
|
|
27
54
|
*/
|
|
28
55
|
export declare function parseXmlMap(xmlText: string): MudletMap | null;
|
|
56
|
+
/** {@link parseXmlMap}, reporting *why* a document was refused. */
|
|
57
|
+
export declare function parseXmlMapResult(xmlText: string): XmlMapParse;
|
|
@@ -11,6 +11,12 @@ export declare class PingTracker {
|
|
|
11
11
|
private lastSentAt;
|
|
12
12
|
private lastDuration;
|
|
13
13
|
private supported;
|
|
14
|
+
/** The responsiveness beat, live only while a ping is outstanding. */
|
|
15
|
+
private beatTimer;
|
|
16
|
+
private lastBeatAt;
|
|
17
|
+
/** Longest interval the event loop went undelivered during the current
|
|
18
|
+
* wait — see {@link MAX_BEAT_GAP_MS}. */
|
|
19
|
+
private worstBeatGapMs;
|
|
14
20
|
private readonly unsubs;
|
|
15
21
|
constructor(sendPingCommand: (latencyMs: number) => void, onPing: (duration: number | null) => void, source: PingEventSource);
|
|
16
22
|
destroy(): void;
|
|
@@ -24,5 +30,12 @@ export declare class PingTracker {
|
|
|
24
30
|
* `Core.Ping` makes servers that unconditionally JSON-parse the part after
|
|
25
31
|
* the module name choke on an empty string. */
|
|
26
32
|
private sendPing;
|
|
33
|
+
/** Begin witnessing the event loop for the wait that starts now. */
|
|
34
|
+
private startBeat;
|
|
35
|
+
private recordBeatGap;
|
|
36
|
+
/** Stop the beat and answer the worst gap it saw, counting the stretch
|
|
37
|
+
* between the final beat and now — a stall that began after the last beat
|
|
38
|
+
* and ran until the reply is still a stall the reading spans. */
|
|
39
|
+
private stopBeat;
|
|
27
40
|
private handlePingResponse;
|
|
28
41
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcoding between the wire and JS strings, for the protocol handlers that
|
|
3
|
+
* carry UTF-8 text inside a telnet subnegotiation (GMCP, MSDP, MSSP).
|
|
4
|
+
*
|
|
5
|
+
* Everything upstream of these speaks *byte-strings*: `bytesToLatin1` in
|
|
6
|
+
* `MudClient.ts` maps each socket byte to one char, and the client's `sendBytes`
|
|
7
|
+
* reverses it with `charCodeAt(i) & 0xff`. Subnegotiations are extracted from that byte-string
|
|
8
|
+
* before the session codec ever runs (`stripTelnetSequences` precedes
|
|
9
|
+
* `codec.decode`), which is what makes these three protocols independent of the
|
|
10
|
+
* session's text encoding — and what obliges each of them to do its own
|
|
11
|
+
* transcoding here.
|
|
12
|
+
*/
|
|
13
|
+
export interface DecodedByteString {
|
|
14
|
+
text: string;
|
|
15
|
+
/** The bytes weren't valid UTF-8, so `text` contains U+FFFD where they were.
|
|
16
|
+
* Callers that can report it should; nothing downstream is able to notice,
|
|
17
|
+
* since the substitution is indistinguishable from a legitimate U+FFFD. */
|
|
18
|
+
malformed: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Decode a Latin-1 byte-string (one char per byte, as produced upstream) as
|
|
21
|
+
* UTF-8. Decoding is lenient by design — a replacement character in one field
|
|
22
|
+
* beats dropping the whole message — but a strict pass runs first so the
|
|
23
|
+
* caller can tell the two apart and say so. */
|
|
24
|
+
export declare const fromByteString: (s: string) => DecodedByteString;
|
|
25
|
+
/** UTF-8-encode into a Latin-1 byte-string, for `MudClient.sendBytes`. Inverse
|
|
26
|
+
* of {@link fromByteString} for well-formed input only — both directions are
|
|
27
|
+
* non-fatal, so invalid UTF-8 inbound and lone surrogates outbound each
|
|
28
|
+
* collapse to U+FFFD rather than round-tripping. Plain ASCII is unchanged. */
|
|
29
|
+
export declare const toByteString: (s: string) => string;
|
|
30
|
+
/** Render a byte-string as hex, for diagnostics that would otherwise print the
|
|
31
|
+
* post-decode text — where every malformed sequence has already collapsed to
|
|
32
|
+
* an indistinguishable U+FFFD. Truncated: the head is where the fault is. */
|
|
33
|
+
export declare const toHex: (s: string) => string;
|
|
@@ -3,9 +3,6 @@ export interface GmcpEnvelope {
|
|
|
3
3
|
value: unknown;
|
|
4
4
|
}
|
|
5
5
|
export type TelnetOptionHandler = (data: string) => string;
|
|
6
|
-
/** UTF-8-encode into a Latin-1 byte-string, for MudClient.sendBytes (which
|
|
7
|
-
* writes `charCodeAt(i) & 0xff` per char). The inverse of fromByteString. */
|
|
8
|
-
export declare const toByteString: (s: string) => string;
|
|
9
6
|
export declare const createTelnetOptionParser: (onSubnegotiation: (data: string) => void, opts?: {
|
|
10
7
|
/** When true, an inbound IAC GA / IAC EOR is replaced by a newline
|
|
11
8
|
* rather than stripped — Mudlet's `mFORCE_GA_OFF` behaviour
|
|
@@ -21,10 +18,18 @@ export declare const encodeGmcp: (path: string, payload: unknown) => string;
|
|
|
21
18
|
/** Encode a GMCP frame from a single pre-formatted body (e.g. `"Module.Sub args"`).
|
|
22
19
|
* Mudlet's `sendGMCP` semantics — the caller controls the body between IAC SB
|
|
23
20
|
* GMCP and IAC SE — except that the body is transcoded to UTF-8 rather than to
|
|
24
|
-
* the session's outgoing encoding, so
|
|
25
|
-
* bytes on the wire
|
|
26
|
-
* keeps a 0xFF byte (which would need
|
|
27
|
-
* out of the body in the first place.
|
|
21
|
+
* the session's outgoing encoding, so a JS caller can't use it to place arbitrary
|
|
22
|
+
* raw bytes on the wire: `U+00FF` goes out as `c3 bf`, not as `ff`. UTF-8 is what
|
|
23
|
+
* the GMCP spec asks for, and it's also what keeps a 0xFF byte (which would need
|
|
24
|
+
* IAC-escaping inside a subnegotiation) out of the body in the first place.
|
|
25
|
+
*
|
|
26
|
+
* From Lua the same limit holds, but it isn't this call that imposes it — the
|
|
27
|
+
* bytes are gone a layer earlier. wasmoon marshals Lua→JS strings through
|
|
28
|
+
* `UTF8ToString`, which UTF-8-*decodes* them, so `"\1\200"` reaches `sendGMCP`
|
|
29
|
+
* as `U+0001 U+0200` and `"\1\255"` as a lone surrogate. Re-encoding here
|
|
30
|
+
* restores the caller's original Lua bytes rather than destroying them.
|
|
31
|
+
* Carrying genuinely arbitrary bytes across that bridge needs numbers, or the
|
|
32
|
+
* `%XX` armoring the VFS io path uses. */
|
|
28
33
|
export declare const encodeGmcpRaw: (message: string) => string;
|
|
29
34
|
export interface GmcpStreamOptions {
|
|
30
35
|
onEnvelope: (payload: GmcpEnvelope) => void;
|
|
@@ -2,10 +2,11 @@ export * from "./constants";
|
|
|
2
2
|
export * from "./gmcp";
|
|
3
3
|
export * from "./msdp";
|
|
4
4
|
export * from "./mssp";
|
|
5
|
+
export { toByteString } from "./byteString";
|
|
5
6
|
export { MccpHandler } from "./mccp";
|
|
6
7
|
export { EchoHandler } from "./echo";
|
|
7
8
|
export { MspParser, type MspCommand, type MspKind } from "./msp";
|
|
8
9
|
export { MxpParser, splitMxpResultLines, type MxpLink, type MxpLineResult } from "./mxp";
|
|
9
|
-
export { parseMnesRequest, encodeMnesIs, selectMnesVars, buildNewEnvironVars, CLIENT_NAME, CLIENT_VERSION, TERMINAL_TYPE, type MnesVar, type MnesRequest, type NewEnvironState } from "./mnes";
|
|
10
|
+
export { parseMnesRequest, encodeMnesIs, selectMnesVars, MNES_UNMAINTAINED, buildNewEnvironVars, CLIENT_NAME, CLIENT_VERSION, TERMINAL_TYPE, type MnesVar, type MnesRequest, type NewEnvironState } from "./mnes";
|
|
10
11
|
export { encodeNaws } from "./naws";
|
|
11
12
|
export { SessionCodec, CharsetHandler, normalizeCharsetName, pickCharsetFromRequest, SUPPORTED_SERVER_ENCODINGS, DEFAULT_SERVER_ENCODING, canonicalServerEncoding, canEncodeForServer, type CharsetHandlerHooks } from "./charset";
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { CLIENT_NAME, CLIENT_VERSION, TERMINAL_TYPE } from "../../version";
|
|
2
2
|
/** A single MNES variable the client reports back, e.g. `{ name: "CHARSET",
|
|
3
|
-
* value: "UTF-8" }`.
|
|
3
|
+
* value: "UTF-8" }`.
|
|
4
|
+
*
|
|
5
|
+
* A null `value` means **undefined** rather than empty. RFC 1572 draws the
|
|
6
|
+
* distinction structurally: a name followed by VALUE is defined (and, with
|
|
7
|
+
* nothing after the VALUE, defined-but-empty), while a name followed by the
|
|
8
|
+
* next marker or IAC with no VALUE at all is undefined. That is how a variable
|
|
9
|
+
* the client deliberately does not supply is answered — see
|
|
10
|
+
* {@link MNES_UNMAINTAINED}. */
|
|
4
11
|
export interface MnesVar {
|
|
5
12
|
name: string;
|
|
6
|
-
value: string;
|
|
13
|
+
value: string | null;
|
|
7
14
|
}
|
|
8
15
|
/** Result of parsing an `IAC SB NEW-ENVIRON SEND … IAC SE` request body. */
|
|
9
16
|
export interface MnesRequest {
|
|
@@ -72,12 +79,28 @@ export { CLIENT_NAME, CLIENT_VERSION, TERMINAL_TYPE };
|
|
|
72
79
|
* the result with VAR (MNES) or USERVAR (NEW-ENVIRON) via encodeMnesIs.
|
|
73
80
|
*/
|
|
74
81
|
export declare function buildNewEnvironVars(state: NewEnvironState, extended: boolean): MnesVar[];
|
|
82
|
+
/** MNES names the standard defines that mudix deliberately does not supply.
|
|
83
|
+
* They are still *known*, so a server that asks for one is told it is
|
|
84
|
+
* undefined rather than left with silence — Mudlet's `isMNESVariable` lists
|
|
85
|
+
* IPADDRESS alongside the five it reports, and answers it with a name and no
|
|
86
|
+
* VALUE. (A browser tab has no way to learn its own address, and the client is
|
|
87
|
+
* not the right party to guess: the server already sees the peer address.) */
|
|
88
|
+
export declare const MNES_UNMAINTAINED: ReadonlyArray<string>;
|
|
75
89
|
/**
|
|
76
|
-
* Pick which of the client's `available`
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
90
|
+
* Pick which of the client's `available` variables to report in response to a
|
|
91
|
+
* parsed request, mirroring Mudlet's `sendIsMNESValues` /
|
|
92
|
+
* `sendIsNewEnvironValues`:
|
|
93
|
+
*
|
|
94
|
+
* - A bare SEND (no names) asks for everything, and gets it.
|
|
95
|
+
* - Named variables come back in request order, each either with its value or
|
|
96
|
+
* — for a name in `undefinedNames` — as undefined (no VALUE).
|
|
97
|
+
* - A name that is neither is left out entirely.
|
|
98
|
+
*
|
|
99
|
+
* A request naming only names we do not report therefore selects **nothing**.
|
|
100
|
+
* It used to fall back to sending everything on the reasoning that the server
|
|
101
|
+
* still learns who we are, but that answers a question nobody asked: a server
|
|
102
|
+
* probing for one specific variable gets an unsolicited dump of the whole set,
|
|
103
|
+
* which is not what Mudlet does and not what the request meant. The caller
|
|
104
|
+
* decides what an empty selection is framed as.
|
|
82
105
|
*/
|
|
83
|
-
export declare function selectMnesVars(request: MnesRequest, available: ReadonlyArray<MnesVar>): MnesVar[];
|
|
106
|
+
export declare function selectMnesVars(request: MnesRequest, available: ReadonlyArray<MnesVar>, undefinedNames?: ReadonlyArray<string>): MnesVar[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MediaCaptionInfo } from './closedCaption';
|
|
1
|
+
import type { MediaCaptionInfo, MediaKind } from './closedCaption';
|
|
2
2
|
type LoaderFn = (path: string) => Promise<ArrayBuffer | null>;
|
|
3
3
|
/** Which mute gate a playback belongs to. `api` = triggered by a Lua script
|
|
4
4
|
* (playSoundFile / playMusicFile); `game` = triggered by the server (MSP, and
|
|
@@ -54,13 +54,25 @@ export declare class SoundManager {
|
|
|
54
54
|
* tearing them down.
|
|
55
55
|
*/
|
|
56
56
|
private muted;
|
|
57
|
+
/**
|
|
58
|
+
* Raised when a tracked source starts playing. ScriptingEngine wires this
|
|
59
|
+
* to Mudlet's `sysMediaStarted(file, path, mediaType, key, tag)` — the same
|
|
60
|
+
* five arguments TMedia.cpp appends when a player reaches PlayingState.
|
|
61
|
+
*
|
|
62
|
+
* Fired after `source.start()` has actually been accepted, so a decode
|
|
63
|
+
* failure or a rejected start never announces a playback that isn't
|
|
64
|
+
* happening. Mudlet withholds it for preload-volume loads; mudix has no
|
|
65
|
+
* preload volume (`preload()` only warms the decode cache and never builds
|
|
66
|
+
* a source), so there is nothing to withhold it for.
|
|
67
|
+
*/
|
|
68
|
+
onMediaStarted?: (file: string, path: string, mediaType: MediaKind, key: string, tag: string) => void;
|
|
57
69
|
/**
|
|
58
70
|
* Raised when a tracked source ends — whether it played out naturally or
|
|
59
71
|
* was stopped. ScriptingEngine wires this to raise Mudlet's
|
|
60
|
-
* `sysMediaFinished(
|
|
61
|
-
* before stopping, so engine teardown never fires it.
|
|
72
|
+
* `sysMediaFinished(file, path, mediaType, key, tag)`. `stopAll()` nulls
|
|
73
|
+
* each source's onended before stopping, so engine teardown never fires it.
|
|
62
74
|
*/
|
|
63
|
-
onMediaFinished?: (
|
|
75
|
+
onMediaFinished?: (file: string, path: string, mediaType: MediaKind, key: string, tag: string) => void;
|
|
64
76
|
/**
|
|
65
77
|
* Raised when a tracked source starts ('plays') or ends ('stops'), so the
|
|
66
78
|
* engine can print a closed caption (Mudlet's enableClosedCaption). Fires
|
|
@@ -33,9 +33,14 @@ export declare class VideoManager {
|
|
|
33
33
|
* still reports what the script asked for.
|
|
34
34
|
*/
|
|
35
35
|
private muted;
|
|
36
|
+
/** Fires once the element has actually begun playing — mirrors Mudlet's
|
|
37
|
+
* sysMediaStarted. Deliberately after `el.play()` resolves rather than
|
|
38
|
+
* beside the caption below it: autoplay policy can reject the play, and an
|
|
39
|
+
* event announcing a playback that never started is worse than none. */
|
|
40
|
+
onStarted: ((file: string, path: string) => void) | null;
|
|
36
41
|
/** Fires when a video ends naturally or is stopped — mirrors Mudlet's
|
|
37
42
|
* sysMediaFinished. */
|
|
38
|
-
onEnded: ((
|
|
43
|
+
onEnded: ((file: string, path: string) => void) | null;
|
|
39
44
|
/** Raised when a video starts ('plays') or finishes ('stops') so the engine
|
|
40
45
|
* can print a closed caption (Mudlet's enableClosedCaption). */
|
|
41
46
|
onMediaCaption: ((info: MediaCaptionInfo) => void) | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function githubRawUrl(url: string): string;
|
package/package.json
CHANGED