@textmode/runner-protocol 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,122 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
10
+ HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF
11
+ THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
12
+
13
+ Statement of Purpose
14
+
15
+ The laws of most jurisdictions throughout the world automatically confer
16
+ exclusive Copyright and Related Rights (defined below) upon the creator
17
+ and subsequent owner(s) (each and all, an "owner") of an original work of
18
+ authorship and/or a database (each, a "Work").
19
+
20
+ Certain owners wish to permanently relinquish those rights to a Work for
21
+ the purpose of contributing to a commons of creative, cultural and
22
+ scientific works ("Commons") that the public can reliably and without
23
+ fear of later claims of infringement build upon, modify, incorporate in
24
+ other works, reuse and redistribute as freely as possible in any form
25
+ whatsoever and for any purposes, including without limitation commercial
26
+ purposes. These owners may contribute to the Commons to promote the ideal
27
+ of a free culture and the further production of creative, cultural and
28
+ scientific works, or to gain reputation or greater distribution for their
29
+ Work in part through the use and efforts of others.
30
+
31
+ For these and/or other purposes and motivations, and without any
32
+ expectation of additional consideration or compensation, the person
33
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or
34
+ she is an owner of Copyright and Related Rights in the Work, voluntarily
35
+ elects to apply CC0 to the Work and publicly distribute the Work under
36
+ its terms, with knowledge of his or her Copyright and Related Rights in
37
+ the Work and the meaning and intended legal effect of CC0 on those
38
+ rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of
53
+ data in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of
55
+ the European Parliament and of the Council of 11 March 1996 on the
56
+ legal protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of
74
+ each member of the public at large and to the detriment of Affirmer's
75
+ heirs and successors, fully intending that such Waiver shall not be
76
+ subject to revocation, rescission, cancellation, termination, or any
77
+ other legal or equitable action to disrupt the quiet enjoyment of the
78
+ Work by the public as contemplated by Affirmer's express Statement of
79
+ Purpose.
80
+
81
+ 3. Public License Fallback. Should any part of the Waiver for any reason
82
+ be judged legally invalid or ineffective under applicable law, then the
83
+ Waiver shall be preserved to the maximum extent permitted taking into
84
+ account Affirmer's express Statement of Purpose. In addition, to the
85
+ extent the Waiver is so judged Affirmer hereby grants to each affected
86
+ person a royalty-free, non transferable, non sublicensable, non
87
+ exclusive, irrevocable and unconditional license to exercise Affirmer's
88
+ Copyright and Related Rights in the Work (i) in all territories
89
+ worldwide, (ii) for the maximum duration provided by applicable law or
90
+ treaty (including future time extensions), (iii) in any current or future
91
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
92
+ including without limitation commercial, advertising or promotional
93
+ purposes (the "License"). The License shall be deemed effective as of the
94
+ date CC0 was applied by Affirmer to the Work. Should any part of the
95
+ License for any reason be judged legally invalid or ineffective under
96
+ applicable law, such partial invalidity or ineffectiveness shall not
97
+ invalidate the remainder of the License, and in such case Affirmer hereby
98
+ affirms that he or she will not (i) exercise any of his or her remaining
99
+ Copyright and Related Rights in the Work or (ii) assert any associated
100
+ claims and causes of action with respect to the Work, in either case
101
+ contrary to Affirmer's express Statement of Purpose.
102
+
103
+ 4. Limitations and Disclaimers.
104
+
105
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
106
+ surrendered, licensed or otherwise affected by this document.
107
+ b. Affirmer offers the Work as-is and makes no representations or
108
+ warranties of any kind concerning the Work, express, implied,
109
+ statutory or otherwise, including without limitation warranties of
110
+ title, merchantability, fitness for a particular purpose, non
111
+ infringement, or the absence of latent or other defects, accuracy, or
112
+ the present or absence of errors, whether or not discoverable, all to
113
+ the greatest extent permissible under applicable law.
114
+ c. Affirmer disclaims responsibility for clearing rights of other
115
+ persons that may apply to the Work or any use thereof, including
116
+ without limitation any person's Copyright and Related Rights in the
117
+ Work. Further, Affirmer disclaims responsibility for obtaining any
118
+ necessary consents, permissions or other rights required for any use
119
+ of the Work.
120
+ d. Affirmer understands and acknowledges that Creative Commons is not a
121
+ party to this document and has no duty or obligation with respect to
122
+ this CC0 or use of the Work.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @textmode/runner-protocol
2
+
3
+ Shared TypeScript message contract for the hosted textmode runner iframe.
4
+
5
+ This package is the single source of truth for the wire messages exchanged by
6
+ the runner and browser host apps in the textmode.js ecosystem.
7
+ It contains the public message types, capability model, runtime settings,
8
+ export/playback/font payloads, and runtime validators used on both sides of the
9
+ iframe boundary.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @textmode/runner-protocol
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import {
21
+ createRunnerCapabilities,
22
+ isParentMessage,
23
+ isRunnerMessage,
24
+ type ParentToRunnerMessage,
25
+ type RunnerToParentMessage,
26
+ } from '@textmode/runner-protocol';
27
+
28
+ const capabilities = createRunnerCapabilities();
29
+
30
+ function handleParentMessage(message: unknown): void {
31
+ if (!isParentMessage(message)) {
32
+ return;
33
+ }
34
+
35
+ runMessage(message);
36
+ }
37
+
38
+ function sendRunnerMessage(message: RunnerToParentMessage): void {
39
+ if (isRunnerMessage(message)) {
40
+ port.postMessage(message);
41
+ }
42
+ }
43
+
44
+ function runMessage(message: ParentToRunnerMessage): void {
45
+ switch (message.type) {
46
+ case 'RUN_CODE':
47
+ runCode(message.code, message.requestId);
48
+ break;
49
+ case 'PING':
50
+ port.postMessage({ type: 'PONG', nonce: message.nonce, timestamp: Date.now() });
51
+ break;
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Public API
57
+
58
+ Import from the package root only:
59
+
60
+ ```ts
61
+ import { isRunnerMessage, type RuntimeSettings } from '@textmode/runner-protocol';
62
+ ```
63
+
64
+ Public subpath imports are intentionally not supported. This keeps the protocol
65
+ package free to reorganize internal modules without breaking consumers.
66
+
67
+ The main exports are grouped around:
68
+
69
+ - capabilities: `RunnerCapabilities`, `ExportFormat`,
70
+ `createRunnerCapabilities`
71
+ - runtime settings: `RuntimeSettings`
72
+ - exports: image, SVG, TXT, GIF, and WebM option/result/progress types
73
+ - playback: `PlaybackAction`, `PlaybackState`
74
+ - messages: `InitMessage`, `ParentToRunnerMessage`, `RunnerToParentMessage`,
75
+ `Message`
76
+ - guards: `isInitMessage`, `isParentMessage`, `isRunnerMessage`,
77
+ `isRunnerCapabilities`
78
+
79
+ ## Protocol Model
80
+
81
+ The runner protocol has one current message shape. Runtime protocol version
82
+ negotiation is intentionally absent: npm package semver describes source
83
+ compatibility, while runner feature availability is described through
84
+ capabilities.
85
+
86
+ The initial window message is generic for every host app:
87
+
88
+ ```ts
89
+ { type: 'INIT' }
90
+ ```
91
+
92
+ After a successful handshake, the runner responds with:
93
+
94
+ ```ts
95
+ { type: 'READY', capabilities: RunnerCapabilities }
96
+ ```
97
+
98
+ Messages sent after that point use the `ParentToRunnerMessage` and
99
+ `RunnerToParentMessage` unions.
100
+
101
+ ## Validation
102
+
103
+ The exported guards are strict runtime validators for untrusted `postMessage`
104
+ payloads. Use them before dispatching messages across the iframe boundary:
105
+
106
+ ```ts
107
+ if (!isRunnerMessage(event.data)) {
108
+ return;
109
+ }
110
+ ```
111
+
112
+ The guards reject retired app-identity and protocol-version fields such as
113
+ `client`, `v`, `clients`, and `protocolVersions`.
114
+
115
+ ## API Docs
116
+
117
+ Generated TypeDoc Markdown lives in [`api/runner-protocol`](./api/runner-protocol/index.md).
118
+
119
+ ## License
120
+
121
+ CC0-1.0. See [LICENSE](./LICENSE).
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Export families supported by the current runner.
3
+ *
4
+ * @category Capabilities
5
+ */
6
+ export declare const EXPORT_FORMATS: readonly ["image", "svg", "txt", "gif", "webm"];
7
+ /**
8
+ * Export format advertised by runner capabilities and export messages.
9
+ *
10
+ * @category Capabilities
11
+ */
12
+ export type ExportFormat = (typeof EXPORT_FORMATS)[number];
13
+ /**
14
+ * Feature flags advertised by a ready runner iframe.
15
+ *
16
+ * Capabilities describe feature availability only. They are not a runtime
17
+ * protocol version negotiation mechanism.
18
+ *
19
+ * @category Capabilities
20
+ */
21
+ export interface RunnerCapabilities {
22
+ /** Whether fixed runtime settings can be configured before execution. */
23
+ runtimeConfig: boolean;
24
+ /** Export formats available through the runner. */
25
+ exports: ExportFormat[];
26
+ /** Whether host apps can load fonts into the runner. */
27
+ fonts: boolean;
28
+ /** Whether playback controls and state reporting are available. */
29
+ playback: boolean;
30
+ /** Whether the runner responds to heartbeat pings. */
31
+ heartbeat: boolean;
32
+ }
33
+ /**
34
+ * Creates the capability set for the current hosted runner implementation.
35
+ *
36
+ * @returns The current runner capability set.
37
+ * @category Capabilities
38
+ */
39
+ export declare function createRunnerCapabilities(): RunnerCapabilities;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Export families supported by the current runner.
3
+ *
4
+ * @category Capabilities
5
+ */
6
+ export const EXPORT_FORMATS = ['image', 'svg', 'txt', 'gif', 'webm'];
7
+ /**
8
+ * Creates the capability set for the current hosted runner implementation.
9
+ *
10
+ * @returns The current runner capability set.
11
+ * @category Capabilities
12
+ */
13
+ export function createRunnerCapabilities() {
14
+ return {
15
+ runtimeConfig: true,
16
+ exports: [...EXPORT_FORMATS],
17
+ fonts: true,
18
+ playback: true,
19
+ heartbeat: true,
20
+ };
21
+ }
@@ -0,0 +1,120 @@
1
+ import type { ExportFormat } from './capabilities';
2
+ /**
3
+ * Raster image export file format.
4
+ *
5
+ * @category Exports
6
+ */
7
+ export type ImageExportFormat = 'png' | 'jpg' | 'webp';
8
+ /**
9
+ * Options for raster image exports.
10
+ *
11
+ * @category Exports
12
+ */
13
+ export interface ImageExportOptions {
14
+ /** Output image format. */
15
+ format?: ImageExportFormat;
16
+ /** Export scale multiplier. */
17
+ scale?: number;
18
+ /** Encoder quality for lossy image formats. */
19
+ quality?: number;
20
+ }
21
+ /**
22
+ * Options for SVG exports.
23
+ *
24
+ * @category Exports
25
+ */
26
+ export interface SvgExportOptions {
27
+ /** Whether to include background rectangles in the SVG output. */
28
+ includeBackgroundRectangles?: boolean;
29
+ /** SVG drawing mode for text geometry. */
30
+ drawMode?: 'fill' | 'stroke';
31
+ /** Stroke width used when `drawMode` is `stroke`. */
32
+ strokeWidth?: number;
33
+ }
34
+ /**
35
+ * Options for plain text exports.
36
+ *
37
+ * @category Exports
38
+ */
39
+ export interface TxtExportOptions {
40
+ /** Whether trailing spaces should be preserved. */
41
+ preserveTrailingSpaces?: boolean;
42
+ /** Line ending style for generated text. */
43
+ lineEnding?: 'lf' | 'crlf';
44
+ }
45
+ /**
46
+ * Options for animated GIF exports.
47
+ *
48
+ * @category Exports
49
+ */
50
+ export interface GifExportOptions {
51
+ /** Suggested filename without requiring the runner to initiate a download. */
52
+ filename?: string;
53
+ /** Number of frames to record. */
54
+ frameCount?: number;
55
+ /** Capture frame rate. */
56
+ frameRate?: number;
57
+ /** Export scale multiplier. */
58
+ scale?: number;
59
+ /** GIF repeat count. */
60
+ repeat?: number;
61
+ }
62
+ /**
63
+ * Options for WebM exports.
64
+ *
65
+ * @category Exports
66
+ */
67
+ export interface WebmExportOptions {
68
+ /** Suggested filename without requiring the runner to initiate a download. */
69
+ filename?: string;
70
+ /** Number of frames to record. */
71
+ frameCount?: number;
72
+ /** Capture frame rate. */
73
+ frameRate?: number;
74
+ /** Encoder quality hint. */
75
+ quality?: number;
76
+ /** Whether the export should preserve transparency when supported. */
77
+ transparent?: boolean;
78
+ }
79
+ /**
80
+ * Typed export request payload grouped by export format.
81
+ *
82
+ * @category Exports
83
+ */
84
+ export type ExportRequest = {
85
+ format: 'image';
86
+ options?: ImageExportOptions;
87
+ } | {
88
+ format: 'svg';
89
+ options?: SvgExportOptions;
90
+ } | {
91
+ format: 'txt';
92
+ options?: TxtExportOptions;
93
+ } | {
94
+ format: 'gif';
95
+ options?: GifExportOptions;
96
+ } | {
97
+ format: 'webm';
98
+ options?: WebmExportOptions;
99
+ };
100
+ /**
101
+ * Progress payload emitted while recording multi-frame exports.
102
+ *
103
+ * @category Exports
104
+ */
105
+ export interface ExportProgress {
106
+ /** Export lifecycle state reported by the runner. */
107
+ state: string;
108
+ /** Zero-based frame currently being recorded, when applicable. */
109
+ frameIndex?: number;
110
+ /** Total frame count for the export, when known. */
111
+ totalFrames?: number;
112
+ /** Optional human-readable progress detail. */
113
+ message?: string;
114
+ }
115
+ /**
116
+ * Export format used by the protocol.
117
+ *
118
+ * @category Exports
119
+ */
120
+ export type ProtocolExportFormat = ExportFormat;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { RunnerCapabilities } from './capabilities';
2
+ import type { InitMessage, ParentToRunnerMessage, RunnerToParentMessage } from './messages';
3
+ /**
4
+ * Checks whether a value is a valid current runner-to-host message.
5
+ *
6
+ * @category Guards
7
+ */
8
+ export declare function isRunnerMessage(msg: unknown): msg is RunnerToParentMessage;
9
+ /**
10
+ * Checks whether a value is a valid current host-to-runner MessagePort message.
11
+ *
12
+ * @category Guards
13
+ */
14
+ export declare function isParentMessage(msg: unknown): msg is ParentToRunnerMessage;
15
+ /**
16
+ * Checks whether a value is a valid current runner iframe initialization message.
17
+ *
18
+ * @category Guards
19
+ */
20
+ export declare function isInitMessage(msg: unknown): msg is InitMessage;
21
+ /**
22
+ * Checks whether a value is a valid current runner capability set.
23
+ *
24
+ * @category Guards
25
+ */
26
+ export declare function isRunnerCapabilities(value: unknown): value is RunnerCapabilities;
@@ -0,0 +1,17 @@
1
+ import { type ExportFormat } from './capabilities';
2
+ import type { ExportProgress } from './exports';
3
+ import type { PlaybackAction, PlaybackState } from './playback';
4
+ import type { RuntimeSettings } from './runtime';
5
+ export declare function isMessageRecord(value: unknown): value is Record<string, unknown> & {
6
+ type?: unknown;
7
+ };
8
+ export declare function isFiniteNumber(value: unknown): value is number;
9
+ export declare function isOptionalString(value: unknown): value is string | undefined;
10
+ export declare function isOptionalFiniteNumber(value: unknown): value is number | undefined;
11
+ export declare function isOptionalBlob(value: unknown): value is Blob | undefined;
12
+ export declare function isRuntimeSettings(value: unknown): value is RuntimeSettings;
13
+ export declare function isPartialRuntimeSettings(value: unknown): value is Partial<RuntimeSettings>;
14
+ export declare function isExportFormat(value: unknown): value is ExportFormat;
15
+ export declare function isPlaybackAction(value: unknown): value is PlaybackAction;
16
+ export declare function isPlaybackState(value: unknown): value is PlaybackState;
17
+ export declare function isExportProgress(value: unknown): value is ExportProgress;
@@ -0,0 +1,64 @@
1
+ import { EXPORT_FORMATS } from './capabilities';
2
+ export function isMessageRecord(value) {
3
+ return typeof value === 'object' && value !== null;
4
+ }
5
+ export function isFiniteNumber(value) {
6
+ return typeof value === 'number' && Number.isFinite(value);
7
+ }
8
+ export function isOptionalString(value) {
9
+ return value === undefined || typeof value === 'string';
10
+ }
11
+ export function isOptionalFiniteNumber(value) {
12
+ return value === undefined || isFiniteNumber(value);
13
+ }
14
+ export function isOptionalBlob(value) {
15
+ return value === undefined || (typeof Blob !== 'undefined' && value instanceof Blob);
16
+ }
17
+ export function isRuntimeSettings(value) {
18
+ if (!isMessageRecord(value))
19
+ return false;
20
+ return (isPositiveFiniteNumber(value.width) &&
21
+ isPositiveFiniteNumber(value.height) &&
22
+ isPositiveFiniteNumber(value.fontSize) &&
23
+ isPositiveFiniteNumber(value.frameRate));
24
+ }
25
+ export function isPartialRuntimeSettings(value) {
26
+ if (!isMessageRecord(value))
27
+ return false;
28
+ return ((value.width === undefined || isPositiveFiniteNumber(value.width)) &&
29
+ (value.height === undefined || isPositiveFiniteNumber(value.height)) &&
30
+ (value.fontSize === undefined || isPositiveFiniteNumber(value.fontSize)) &&
31
+ (value.frameRate === undefined || isPositiveFiniteNumber(value.frameRate)));
32
+ }
33
+ export function isExportFormat(value) {
34
+ return EXPORT_FORMATS.includes(value);
35
+ }
36
+ export function isPlaybackAction(value) {
37
+ return (value === 'play' ||
38
+ value === 'pause' ||
39
+ value === 'stop' ||
40
+ value === 'seek' ||
41
+ value === 'next' ||
42
+ value === 'previous' ||
43
+ value === 'setMaxFrames' ||
44
+ value === 'state');
45
+ }
46
+ export function isPlaybackState(value) {
47
+ if (!isMessageRecord(value))
48
+ return false;
49
+ return (typeof value.isPlaying === 'boolean' &&
50
+ isFiniteNumber(value.frame) &&
51
+ isFiniteNumber(value.maxFrames) &&
52
+ (value.fps === undefined || isFiniteNumber(value.fps)));
53
+ }
54
+ export function isExportProgress(value) {
55
+ if (!isMessageRecord(value))
56
+ return false;
57
+ return (typeof value.state === 'string' &&
58
+ isOptionalFiniteNumber(value.frameIndex) &&
59
+ isOptionalFiniteNumber(value.totalFrames) &&
60
+ isOptionalString(value.message));
61
+ }
62
+ function isPositiveFiniteNumber(value) {
63
+ return isFiniteNumber(value) && value > 0;
64
+ }
package/dist/guards.js ADDED
@@ -0,0 +1,116 @@
1
+ import { isExportFormat, isExportProgress, isFiniteNumber, isMessageRecord, isOptionalBlob, isOptionalFiniteNumber, isOptionalString, isPartialRuntimeSettings, isPlaybackAction, isPlaybackState, isRuntimeSettings, } from './guards.internal';
2
+ /**
3
+ * Checks whether a value is a valid current runner-to-host message.
4
+ *
5
+ * @category Guards
6
+ */
7
+ export function isRunnerMessage(msg) {
8
+ if (!isMessageRecord(msg))
9
+ return false;
10
+ switch (msg.type) {
11
+ case 'READY':
12
+ return !('v' in msg) && isRunnerCapabilities(msg.capabilities);
13
+ case 'TOGGLE_UI':
14
+ case 'USER_INTERACTION':
15
+ return true;
16
+ case 'RUN_OK':
17
+ return isFiniteNumber(msg.timestamp) && isOptionalString(msg.requestId);
18
+ case 'RUN_ERROR':
19
+ return (typeof msg.message === 'string' &&
20
+ isOptionalString(msg.stack) &&
21
+ isOptionalFiniteNumber(msg.line) &&
22
+ isOptionalFiniteNumber(msg.column) &&
23
+ isOptionalString(msg.requestId));
24
+ case 'SYNTH_ERROR':
25
+ return typeof msg.message === 'string' && isOptionalString(msg.uniformName);
26
+ case 'EXPORT_RESULT':
27
+ return (typeof msg.requestId === 'string' &&
28
+ isExportFormat(msg.format) &&
29
+ isOptionalBlob(msg.blob) &&
30
+ isOptionalString(msg.text) &&
31
+ isOptionalString(msg.filename) &&
32
+ isOptionalString(msg.mimeType));
33
+ case 'EXPORT_PROGRESS':
34
+ return (typeof msg.requestId === 'string' &&
35
+ (msg.format === 'gif' || msg.format === 'webm') &&
36
+ isExportProgress(msg.progress));
37
+ case 'FONT_LOADED':
38
+ return (typeof msg.requestId === 'string' &&
39
+ (msg.familyName === null || typeof msg.familyName === 'string') &&
40
+ Array.isArray(msg.characters) &&
41
+ msg.characters.every((entry) => typeof entry === 'string'));
42
+ case 'FONT_ERROR':
43
+ return typeof msg.requestId === 'string' && typeof msg.message === 'string';
44
+ case 'PLAYBACK_STATE':
45
+ return isOptionalString(msg.requestId) && isPlaybackState(msg.state);
46
+ case 'PONG':
47
+ return isOptionalString(msg.nonce) && isFiniteNumber(msg.timestamp);
48
+ default:
49
+ return false;
50
+ }
51
+ }
52
+ /**
53
+ * Checks whether a value is a valid current host-to-runner MessagePort message.
54
+ *
55
+ * @category Guards
56
+ */
57
+ export function isParentMessage(msg) {
58
+ if (!isMessageRecord(msg))
59
+ return false;
60
+ switch (msg.type) {
61
+ case 'RUN_CODE':
62
+ case 'SOFT_RESET':
63
+ return typeof msg.code === 'string' && isOptionalString(msg.requestId);
64
+ case 'DISPOSE':
65
+ return true;
66
+ case 'CONFIGURE_RUNTIME':
67
+ return isRuntimeSettings(msg.settings) && isOptionalString(msg.requestId);
68
+ case 'SET_SETTINGS':
69
+ return isPartialRuntimeSettings(msg.settings) && isOptionalString(msg.requestId);
70
+ case 'EXPORT':
71
+ return (typeof msg.requestId === 'string' &&
72
+ isExportFormat(msg.format) &&
73
+ (msg.options === undefined || isMessageRecord(msg.options)));
74
+ case 'LOAD_FONT':
75
+ return (typeof msg.requestId === 'string' &&
76
+ typeof msg.fileName === 'string' &&
77
+ isOptionalString(msg.mimeType) &&
78
+ msg.buffer instanceof ArrayBuffer);
79
+ case 'PLAYBACK':
80
+ return (isOptionalString(msg.requestId) &&
81
+ isPlaybackAction(msg.action) &&
82
+ isOptionalFiniteNumber(msg.frame) &&
83
+ isOptionalFiniteNumber(msg.maxFrames));
84
+ case 'PING':
85
+ return isOptionalString(msg.nonce);
86
+ default:
87
+ return false;
88
+ }
89
+ }
90
+ /**
91
+ * Checks whether a value is a valid current runner iframe initialization message.
92
+ *
93
+ * @category Guards
94
+ */
95
+ export function isInitMessage(msg) {
96
+ return isMessageRecord(msg) && msg.type === 'INIT' && Object.keys(msg).length === 1;
97
+ }
98
+ /**
99
+ * Checks whether a value is a valid current runner capability set.
100
+ *
101
+ * @category Guards
102
+ */
103
+ export function isRunnerCapabilities(value) {
104
+ if (!isMessageRecord(value))
105
+ return false;
106
+ if ('protocolVersions' in value)
107
+ return false;
108
+ if ('clients' in value)
109
+ return false;
110
+ return (typeof value.runtimeConfig === 'boolean' &&
111
+ Array.isArray(value.exports) &&
112
+ value.exports.every(isExportFormat) &&
113
+ typeof value.fonts === 'boolean' &&
114
+ typeof value.playback === 'boolean' &&
115
+ typeof value.heartbeat === 'boolean');
116
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Shared message protocol for the textmode runner iframe.
5
+ *
6
+ * `@textmode/runner-protocol` is the single source of truth for the wire
7
+ * contract used by the hosted runner and browser host apps.
8
+ * Runtime protocol version negotiation is intentionally absent: package semver
9
+ * describes source compatibility, while this package describes the one current
10
+ * message shape. Feature availability is advertised through capabilities.
11
+ *
12
+ * @module @textmode/runner-protocol
13
+ */
14
+ export { EXPORT_FORMATS, createRunnerCapabilities, type ExportFormat, type RunnerCapabilities, } from './capabilities';
15
+ export { type ExportProgress, type ExportRequest, type GifExportOptions, type ImageExportFormat, type ImageExportOptions, type SvgExportOptions, type TxtExportOptions, type WebmExportOptions, } from './exports';
16
+ export { isInitMessage, isParentMessage, isRunnerCapabilities, isRunnerMessage, } from './guards';
17
+ export { type ConfigureRuntimeMessage, type DisposeMessage, type ExportMessage, type ExportProgressMessage, type ExportResultMessage, type FontErrorMessage, type FontLoadedMessage, type InitMessage, type LoadFontMessage, type Message, type ParentToRunnerMessage, type PingMessage, type PlaybackMessage, type PlaybackStateMessage, type PongMessage, type ReadyMessage, type RunCodeMessage, type RunErrorMessage, type RunOkMessage, type RunnerToParentMessage, type SetSettingsMessage, type SoftResetMessage, type SynthErrorMessage, type ToggleUIMessage, type UserInteractionMessage, type WindowToRunnerMessage, } from './messages';
18
+ export { type PlaybackAction, type PlaybackState } from './playback';
19
+ export { type RuntimeSettings } from './runtime';
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Shared message protocol for the textmode runner iframe.
5
+ *
6
+ * `@textmode/runner-protocol` is the single source of truth for the wire
7
+ * contract used by the hosted runner and browser host apps.
8
+ * Runtime protocol version negotiation is intentionally absent: package semver
9
+ * describes source compatibility, while this package describes the one current
10
+ * message shape. Feature availability is advertised through capabilities.
11
+ *
12
+ * @module @textmode/runner-protocol
13
+ */
14
+ export { EXPORT_FORMATS, createRunnerCapabilities, } from './capabilities';
15
+ export { isInitMessage, isParentMessage, isRunnerCapabilities, isRunnerMessage, } from './guards';
@@ -0,0 +1,300 @@
1
+ import type { RunnerCapabilities } from './capabilities';
2
+ import type { ExportProgress, ExportRequest } from './exports';
3
+ import type { PlaybackAction, PlaybackState } from './playback';
4
+ import type { RuntimeSettings } from './runtime';
5
+ /**
6
+ * Initial window message sent by a host app to the runner iframe.
7
+ *
8
+ * @category Messages
9
+ */
10
+ export interface InitMessage {
11
+ type: 'INIT';
12
+ }
13
+ /**
14
+ * Runner readiness message sent after a successful iframe handshake.
15
+ *
16
+ * @category Messages
17
+ */
18
+ export interface ReadyMessage {
19
+ type: 'READY';
20
+ /** Feature set supported by this runner. */
21
+ capabilities: RunnerCapabilities;
22
+ }
23
+ /**
24
+ * Successful code execution result.
25
+ *
26
+ * @category Messages
27
+ */
28
+ export interface RunOkMessage {
29
+ type: 'RUN_OK';
30
+ /** Runner-side completion timestamp. */
31
+ timestamp: number;
32
+ /** Request identifier when the run was initiated by a request/response host. */
33
+ requestId?: string;
34
+ }
35
+ /**
36
+ * Code execution failure result.
37
+ *
38
+ * @category Messages
39
+ */
40
+ export interface RunErrorMessage {
41
+ type: 'RUN_ERROR';
42
+ /** Human-readable error message. */
43
+ message: string;
44
+ /** Optional stack trace. */
45
+ stack?: string;
46
+ /** Optional 1-based source line. */
47
+ line?: number;
48
+ /** Optional 1-based source column. */
49
+ column?: number;
50
+ /** Request identifier when the failure belongs to a request/response call. */
51
+ requestId?: string;
52
+ }
53
+ /**
54
+ * Shader synth parameter error reported by the runner.
55
+ *
56
+ * @category Messages
57
+ */
58
+ export interface SynthErrorMessage {
59
+ type: 'SYNTH_ERROR';
60
+ /** Human-readable error message. */
61
+ message: string;
62
+ /** Uniform name associated with the error, when available. */
63
+ uniformName?: string;
64
+ }
65
+ /**
66
+ * Runner-originated shortcut event requesting host UI visibility changes.
67
+ *
68
+ * @category Messages
69
+ */
70
+ export interface ToggleUIMessage {
71
+ type: 'TOGGLE_UI';
72
+ }
73
+ /**
74
+ * Runner-originated user interaction event.
75
+ *
76
+ * @category Messages
77
+ */
78
+ export interface UserInteractionMessage {
79
+ type: 'USER_INTERACTION';
80
+ }
81
+ /**
82
+ * Export completion payload.
83
+ *
84
+ * @category Messages
85
+ */
86
+ export interface ExportResultMessage {
87
+ type: 'EXPORT_RESULT';
88
+ /** Request identifier for the export call. */
89
+ requestId: string;
90
+ /** Completed export format. */
91
+ format: ExportRequest['format'];
92
+ /** Binary export result for blob-based formats. */
93
+ blob?: Blob;
94
+ /** Text export result for text-based formats. */
95
+ text?: string;
96
+ /** Suggested filename, when provided by the runner. */
97
+ filename?: string;
98
+ /** MIME type for the export result. */
99
+ mimeType?: string;
100
+ }
101
+ /**
102
+ * Progress payload for multi-frame exports.
103
+ *
104
+ * @category Messages
105
+ */
106
+ export interface ExportProgressMessage {
107
+ type: 'EXPORT_PROGRESS';
108
+ /** Request identifier for the export call. */
109
+ requestId: string;
110
+ /** Streaming export format. */
111
+ format: 'gif' | 'webm';
112
+ /** Current progress snapshot. */
113
+ progress: ExportProgress;
114
+ }
115
+ /**
116
+ * Successful font load result.
117
+ *
118
+ * @category Messages
119
+ */
120
+ export interface FontLoadedMessage {
121
+ type: 'FONT_LOADED';
122
+ /** Request identifier for the font load call. */
123
+ requestId: string;
124
+ /** Font family name detected by the runner. */
125
+ familyName: string | null;
126
+ /** Characters available in the loaded font. */
127
+ characters: string[];
128
+ }
129
+ /**
130
+ * Font load failure result.
131
+ *
132
+ * @category Messages
133
+ */
134
+ export interface FontErrorMessage {
135
+ type: 'FONT_ERROR';
136
+ /** Request identifier for the font load call. */
137
+ requestId: string;
138
+ /** Human-readable error message. */
139
+ message: string;
140
+ }
141
+ /**
142
+ * Playback state response or event.
143
+ *
144
+ * @category Messages
145
+ */
146
+ export interface PlaybackStateMessage {
147
+ type: 'PLAYBACK_STATE';
148
+ /** Request identifier when the state belongs to a playback request. */
149
+ requestId?: string;
150
+ /** Current playback state. */
151
+ state: PlaybackState;
152
+ }
153
+ /**
154
+ * Heartbeat response from the runner.
155
+ *
156
+ * @category Messages
157
+ */
158
+ export interface PongMessage {
159
+ type: 'PONG';
160
+ /** Echoed heartbeat nonce. */
161
+ nonce?: string;
162
+ /** Runner-side response timestamp. */
163
+ timestamp: number;
164
+ }
165
+ /**
166
+ * Messages sent from the runner iframe to a host app.
167
+ *
168
+ * @category Messages
169
+ */
170
+ export type RunnerToParentMessage = ReadyMessage | RunOkMessage | RunErrorMessage | SynthErrorMessage | ToggleUIMessage | UserInteractionMessage | ExportResultMessage | ExportProgressMessage | FontLoadedMessage | FontErrorMessage | PlaybackStateMessage | PongMessage;
171
+ /**
172
+ * Request to execute code in the runner.
173
+ *
174
+ * @category Messages
175
+ */
176
+ export interface RunCodeMessage {
177
+ type: 'RUN_CODE';
178
+ /** Source code to execute. */
179
+ code: string;
180
+ /** Optional request identifier for result routing. */
181
+ requestId?: string;
182
+ }
183
+ /**
184
+ * Request to reset frame state and execute code.
185
+ *
186
+ * @category Messages
187
+ */
188
+ export interface SoftResetMessage {
189
+ type: 'SOFT_RESET';
190
+ /** Source code to execute after soft reset. */
191
+ code: string;
192
+ /** Optional request identifier for result routing. */
193
+ requestId?: string;
194
+ }
195
+ /**
196
+ * Request to dispose the runner runtime.
197
+ *
198
+ * @category Messages
199
+ */
200
+ export interface DisposeMessage {
201
+ type: 'DISPOSE';
202
+ }
203
+ /**
204
+ * Request to initialize or reconfigure fixed runtime settings.
205
+ *
206
+ * @category Messages
207
+ */
208
+ export interface ConfigureRuntimeMessage {
209
+ type: 'CONFIGURE_RUNTIME';
210
+ /** Complete runtime settings. */
211
+ settings: RuntimeSettings;
212
+ /** Optional request identifier for result routing. */
213
+ requestId?: string;
214
+ }
215
+ /**
216
+ * Request to update part of the current runtime settings.
217
+ *
218
+ * @category Messages
219
+ */
220
+ export interface SetSettingsMessage {
221
+ type: 'SET_SETTINGS';
222
+ /** Partial runtime settings to apply. */
223
+ settings: Partial<RuntimeSettings>;
224
+ /** Optional request identifier for result routing. */
225
+ requestId?: string;
226
+ }
227
+ /**
228
+ * Request to export the current runner output.
229
+ *
230
+ * @category Messages
231
+ */
232
+ export interface ExportMessage {
233
+ type: 'EXPORT';
234
+ /** Request identifier for result routing. */
235
+ requestId: string;
236
+ /** Requested export format. */
237
+ format: ExportRequest['format'];
238
+ /** Export options matching the requested format. */
239
+ options?: ExportRequest['options'];
240
+ }
241
+ /**
242
+ * Request to load a font file into the runner.
243
+ *
244
+ * @category Messages
245
+ */
246
+ export interface LoadFontMessage {
247
+ type: 'LOAD_FONT';
248
+ /** Request identifier for result routing. */
249
+ requestId: string;
250
+ /** Original file name. */
251
+ fileName: string;
252
+ /** Browser-reported MIME type, when available. */
253
+ mimeType?: string;
254
+ /** Font file bytes. */
255
+ buffer: ArrayBuffer;
256
+ }
257
+ /**
258
+ * Request to control or inspect playback.
259
+ *
260
+ * @category Messages
261
+ */
262
+ export interface PlaybackMessage {
263
+ type: 'PLAYBACK';
264
+ /** Optional request identifier for result routing. */
265
+ requestId?: string;
266
+ /** Playback action to perform. */
267
+ action: PlaybackAction;
268
+ /** Target frame for seek-like actions. */
269
+ frame?: number;
270
+ /** Maximum frame count for playback range updates. */
271
+ maxFrames?: number;
272
+ }
273
+ /**
274
+ * Heartbeat request sent by a host app.
275
+ *
276
+ * @category Messages
277
+ */
278
+ export interface PingMessage {
279
+ type: 'PING';
280
+ /** Optional nonce echoed by the runner. */
281
+ nonce?: string;
282
+ }
283
+ /**
284
+ * Messages sent from a host app to the runner after handshake.
285
+ *
286
+ * @category Messages
287
+ */
288
+ export type ParentToRunnerMessage = RunCodeMessage | SoftResetMessage | DisposeMessage | ConfigureRuntimeMessage | SetSettingsMessage | ExportMessage | LoadFontMessage | PlaybackMessage | PingMessage;
289
+ /**
290
+ * Messages sent to the runner iframe window before MessagePort attachment.
291
+ *
292
+ * @category Messages
293
+ */
294
+ export type WindowToRunnerMessage = InitMessage;
295
+ /**
296
+ * Any message in the runner protocol.
297
+ *
298
+ * @category Messages
299
+ */
300
+ export type Message = RunnerToParentMessage | ParentToRunnerMessage | WindowToRunnerMessage;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Playback command accepted by the runner.
3
+ *
4
+ * @category Playback
5
+ */
6
+ export type PlaybackAction = 'play' | 'pause' | 'stop' | 'seek' | 'next' | 'previous' | 'setMaxFrames' | 'state';
7
+ /**
8
+ * Playback state snapshot emitted by the runner.
9
+ *
10
+ * @category Playback
11
+ */
12
+ export interface PlaybackState {
13
+ /** Whether playback is actively advancing frames. */
14
+ isPlaying: boolean;
15
+ /** Current frame index. */
16
+ frame: number;
17
+ /** Maximum frame count used by playback controls. */
18
+ maxFrames: number;
19
+ /** Optional measured or configured frames per second. */
20
+ fps?: number;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Fixed runtime dimensions and timing used by configurable runner hosts.
3
+ *
4
+ * @category Runtime
5
+ */
6
+ export interface RuntimeSettings {
7
+ /** Canvas width in CSS pixels. */
8
+ width: number;
9
+ /** Canvas height in CSS pixels. */
10
+ height: number;
11
+ /** Textmode font size in CSS pixels. */
12
+ fontSize: number;
13
+ /** Target animation frame rate. */
14
+ frameRate: number;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@textmode/runner-protocol",
3
+ "version": "0.1.0",
4
+ "description": "Shared message protocol and validators for the hosted textmode runner iframe.",
5
+ "license": "CC0-1.0",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "dist/**/*.d.ts",
21
+ "dist/**/*.js"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -b",
25
+ "check-types": "tsc -p tsconfig.json --noEmit",
26
+ "lint": "eslint .",
27
+ "test": "vitest run --passWithNoTests",
28
+ "build:docs": "typedoc --options typedoc.json",
29
+ "dev:docs": "typedoc --options typedoc.json --watch",
30
+ "prepare": "npm run build"
31
+ }
32
+ }