@textmode/runner-client 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 +661 -0
- package/README.md +133 -0
- package/dist/IframeTextmodeRuntime.d.ts +177 -0
- package/dist/IframeTextmodeRuntime.js +539 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.js +21 -0
- package/dist/font.d.ts +11 -0
- package/dist/font.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/internal/heartbeat.d.ts +26 -0
- package/dist/internal/heartbeat.js +44 -0
- package/dist/internal/iframeMount.d.ts +4 -0
- package/dist/internal/iframeMount.js +29 -0
- package/dist/internal/messageRouter.d.ts +16 -0
- package/dist/internal/messageRouter.js +45 -0
- package/dist/internal/requestRegistry.d.ts +25 -0
- package/dist/internal/requestRegistry.js +77 -0
- package/dist/internal/sandboxPolicy.d.ts +7 -0
- package/dist/internal/sandboxPolicy.js +6 -0
- package/dist/options.d.ts +67 -0
- package/dist/options.js +15 -0
- package/dist/status.d.ts +6 -0
- package/dist/status.js +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @textmode/runner-client
|
|
2
|
+
|
|
3
|
+
Browser iframe runtime client for the hosted textmode runner.
|
|
4
|
+
|
|
5
|
+
This package gives any browser host app a typed runtime API for mounting the
|
|
6
|
+
runner iframe, performing the current generic protocol handshake, routing
|
|
7
|
+
request/response messages, monitoring heartbeat status, loading fonts,
|
|
8
|
+
controlling playback, running code, exporting output, reconnecting, and
|
|
9
|
+
disposing the transport.
|
|
10
|
+
|
|
11
|
+
It does not execute textmode.js sketches directly. It controls a runner app at
|
|
12
|
+
the `runnerUrl` you provide.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install @textmode/runner-client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`@textmode/runner-protocol` is installed as a dependency and is also available
|
|
21
|
+
for consumers that need the protocol types directly.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
IframeTextmodeRuntime,
|
|
28
|
+
RunnerRequestError,
|
|
29
|
+
type RunnerRuntimeStatus,
|
|
30
|
+
} from '@textmode/runner-client';
|
|
31
|
+
|
|
32
|
+
const container = document.querySelector<HTMLElement>('#runner');
|
|
33
|
+
|
|
34
|
+
if (!container) {
|
|
35
|
+
throw new Error('missing runner container');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const runtime = new IframeTextmodeRuntime({
|
|
39
|
+
runnerUrl: 'https://runner.textmode.art/',
|
|
40
|
+
onStatusChange(status: RunnerRuntimeStatus, reason) {
|
|
41
|
+
console.info('runner status changed', status, reason);
|
|
42
|
+
},
|
|
43
|
+
onExportProgress(requestId, format, progress) {
|
|
44
|
+
console.info('export progress', requestId, format, progress);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await runtime.init(container, {
|
|
49
|
+
width: 640,
|
|
50
|
+
height: 640,
|
|
51
|
+
fontSize: 16,
|
|
52
|
+
frameRate: 60,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await runtime.runCode(`
|
|
57
|
+
t.draw(() => {
|
|
58
|
+
t.print('textmode', 0, 0);
|
|
59
|
+
});
|
|
60
|
+
`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof RunnerRequestError) {
|
|
63
|
+
console.error(error.message, error.line, error.column);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const image = await runtime.export('image', {
|
|
68
|
+
format: 'png',
|
|
69
|
+
scale: 2,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
console.info(image.filename, image.mimeType, image.blob);
|
|
73
|
+
runtime.dispose();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Public API
|
|
77
|
+
|
|
78
|
+
Import from the package root only:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { IframeTextmodeRuntime } from '@textmode/runner-client';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Public subpath imports are intentionally not supported. Internal modules such
|
|
85
|
+
as request routing, heartbeat control, iframe mounting, and sandbox policy may
|
|
86
|
+
change without a semver-major release.
|
|
87
|
+
|
|
88
|
+
The main exports are:
|
|
89
|
+
|
|
90
|
+
- `IframeTextmodeRuntime`
|
|
91
|
+
- `RunnerRuntimeStatus`
|
|
92
|
+
- `RunnerExecutionError`
|
|
93
|
+
- `RunnerRequestError`
|
|
94
|
+
- `FontLoadResult`
|
|
95
|
+
- `IframeTextmodeRuntimeOptions`
|
|
96
|
+
- `IframeMountMode`
|
|
97
|
+
- `IframeSandboxToken`
|
|
98
|
+
- `DEFAULT_IFRAME_SANDBOX_TOKENS`
|
|
99
|
+
|
|
100
|
+
## Runtime Lifecycle
|
|
101
|
+
|
|
102
|
+
Typical host apps follow this lifecycle:
|
|
103
|
+
|
|
104
|
+
1. Create an `IframeTextmodeRuntime` with a trusted `runnerUrl`.
|
|
105
|
+
2. Call `init(container, settings)` from a browser context.
|
|
106
|
+
3. Use `runCode`, `configure`, `setSettings`, `export`, `loadFont`, and
|
|
107
|
+
`playback` as needed.
|
|
108
|
+
4. Call `reconnect` after a recoverable runner failure.
|
|
109
|
+
5. Call `dispose` when the host view is unmounted.
|
|
110
|
+
|
|
111
|
+
The runtime exposes `status`, `isReady`, and `frame` getters for host UI state.
|
|
112
|
+
|
|
113
|
+
## Sandbox Policy
|
|
114
|
+
|
|
115
|
+
The default iframe sandbox tokens are:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
['allow-scripts', 'allow-same-origin']
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`allow-downloads` is not included by default. Export downloads should be
|
|
122
|
+
initiated by the host app after it receives an export result from the runner.
|
|
123
|
+
|
|
124
|
+
The runtime refuses to start a runner that combines `allow-scripts` and
|
|
125
|
+
`allow-same-origin` on the same origin as the parent page.
|
|
126
|
+
|
|
127
|
+
## API Docs
|
|
128
|
+
|
|
129
|
+
Generated TypeDoc Markdown lives in [`api/runner-client`](./api/runner-client/index.md).
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
AGPL-3.0-or-later. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { type ExportMessage, type ExportResultMessage, type GifExportOptions, type ImageExportOptions, type PlaybackAction, type PlaybackState, type RunnerCapabilities, type RuntimeSettings, type SvgExportOptions, type TxtExportOptions, type WebmExportOptions } from '@textmode/runner-protocol';
|
|
2
|
+
import type { FontLoadResult } from './font';
|
|
3
|
+
import { type IframeTextmodeRuntimeOptions } from './options';
|
|
4
|
+
import type { RunnerRuntimeStatus } from './status';
|
|
5
|
+
/**
|
|
6
|
+
* Browser iframe runtime for communicating with the hosted textmode runner.
|
|
7
|
+
*
|
|
8
|
+
* @category Runtime
|
|
9
|
+
*/
|
|
10
|
+
export declare class IframeTextmodeRuntime {
|
|
11
|
+
private readonly runnerHref;
|
|
12
|
+
private readonly runnerOrigin;
|
|
13
|
+
private readonly sandboxTokens;
|
|
14
|
+
private readonly handshakeTimeoutMs;
|
|
15
|
+
private readonly requestTimeoutMs;
|
|
16
|
+
private readonly options;
|
|
17
|
+
private readonly mountMode;
|
|
18
|
+
private readonly pending;
|
|
19
|
+
private readonly heartbeat;
|
|
20
|
+
private iframe;
|
|
21
|
+
private channel;
|
|
22
|
+
private port;
|
|
23
|
+
private container;
|
|
24
|
+
private settings;
|
|
25
|
+
private capabilities;
|
|
26
|
+
private ready;
|
|
27
|
+
private currentStatus;
|
|
28
|
+
private readyResolver;
|
|
29
|
+
private readyRejecter;
|
|
30
|
+
private readyTimeoutId;
|
|
31
|
+
private lastRequestedCode;
|
|
32
|
+
constructor(options: IframeTextmodeRuntimeOptions);
|
|
33
|
+
/**
|
|
34
|
+
* Whether the runner iframe is ready to accept requests.
|
|
35
|
+
*
|
|
36
|
+
* @category Runtime
|
|
37
|
+
*/
|
|
38
|
+
get isReady(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Current runner iframe element, when mounted.
|
|
41
|
+
*
|
|
42
|
+
* @category Runtime
|
|
43
|
+
*/
|
|
44
|
+
get frame(): HTMLIFrameElement | null;
|
|
45
|
+
/**
|
|
46
|
+
* Current runner lifecycle status.
|
|
47
|
+
*
|
|
48
|
+
* @category Runtime
|
|
49
|
+
*/
|
|
50
|
+
get status(): RunnerRuntimeStatus;
|
|
51
|
+
/**
|
|
52
|
+
* Alias for {@link IframeTextmodeRuntime.status}.
|
|
53
|
+
*
|
|
54
|
+
* @category Runtime
|
|
55
|
+
*/
|
|
56
|
+
get runnerStatus(): RunnerRuntimeStatus;
|
|
57
|
+
/**
|
|
58
|
+
* Capabilities advertised by the connected runner.
|
|
59
|
+
*
|
|
60
|
+
* @category Runtime
|
|
61
|
+
*/
|
|
62
|
+
get advertisedCapabilities(): RunnerCapabilities | null;
|
|
63
|
+
/**
|
|
64
|
+
* Mounts the runner iframe and performs the current protocol handshake.
|
|
65
|
+
*
|
|
66
|
+
* @param container - DOM element that should contain the runner iframe.
|
|
67
|
+
* @param settings - Optional fixed runtime settings to configure after ready.
|
|
68
|
+
* @returns `true` when the runner is ready.
|
|
69
|
+
* @category Runtime
|
|
70
|
+
*/
|
|
71
|
+
init(container: HTMLElement, settings?: RuntimeSettings): Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Disposes the iframe connection and rejects pending requests.
|
|
74
|
+
*
|
|
75
|
+
* @category Runtime
|
|
76
|
+
*/
|
|
77
|
+
dispose(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Recreates the iframe and reruns the last requested code when available.
|
|
80
|
+
*
|
|
81
|
+
* @returns `true` when reconnection succeeds.
|
|
82
|
+
* @category Runtime
|
|
83
|
+
*/
|
|
84
|
+
reconnect(): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Focuses the iframe from a host user gesture.
|
|
87
|
+
*
|
|
88
|
+
* Some browsers use this to unlock normal iframe animation cadence.
|
|
89
|
+
*
|
|
90
|
+
* @category Runtime
|
|
91
|
+
*/
|
|
92
|
+
activateFromUserGesture(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Configures complete fixed runtime settings.
|
|
95
|
+
*
|
|
96
|
+
* @category Runtime
|
|
97
|
+
*/
|
|
98
|
+
configure(settings: RuntimeSettings): Promise<PlaybackState | null>;
|
|
99
|
+
/**
|
|
100
|
+
* Applies a partial runtime settings update.
|
|
101
|
+
*
|
|
102
|
+
* @category Runtime
|
|
103
|
+
*/
|
|
104
|
+
setSettings(settings: Partial<RuntimeSettings>): Promise<PlaybackState | null>;
|
|
105
|
+
/**
|
|
106
|
+
* Executes code in the runner.
|
|
107
|
+
*
|
|
108
|
+
* @category Runtime
|
|
109
|
+
*/
|
|
110
|
+
runCode(code: string, options?: {
|
|
111
|
+
softReset?: boolean;
|
|
112
|
+
}): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Exports the current runner output in any supported format.
|
|
115
|
+
*
|
|
116
|
+
* @category Exports
|
|
117
|
+
*/
|
|
118
|
+
export(format: ExportMessage['format'], options?: ImageExportOptions | SvgExportOptions | TxtExportOptions | GifExportOptions | WebmExportOptions, timeoutMs?: number): Promise<ExportResultMessage>;
|
|
119
|
+
/**
|
|
120
|
+
* Exports the current runner output as a raster image.
|
|
121
|
+
*
|
|
122
|
+
* @category Exports
|
|
123
|
+
*/
|
|
124
|
+
exportImage(options: ImageExportOptions): Promise<ExportResultMessage>;
|
|
125
|
+
/**
|
|
126
|
+
* Exports the current runner output as SVG.
|
|
127
|
+
*
|
|
128
|
+
* @category Exports
|
|
129
|
+
*/
|
|
130
|
+
exportSvg(options: SvgExportOptions): Promise<ExportResultMessage>;
|
|
131
|
+
/**
|
|
132
|
+
* Exports the current runner output as plain text.
|
|
133
|
+
*
|
|
134
|
+
* @category Exports
|
|
135
|
+
*/
|
|
136
|
+
exportTxt(options: TxtExportOptions): Promise<ExportResultMessage>;
|
|
137
|
+
/**
|
|
138
|
+
* Records an animated GIF export.
|
|
139
|
+
*
|
|
140
|
+
* @category Exports
|
|
141
|
+
*/
|
|
142
|
+
exportGif(options: GifExportOptions): Promise<ExportResultMessage>;
|
|
143
|
+
/**
|
|
144
|
+
* Records a WebM export.
|
|
145
|
+
*
|
|
146
|
+
* @category Exports
|
|
147
|
+
*/
|
|
148
|
+
exportWebm(options: WebmExportOptions): Promise<ExportResultMessage>;
|
|
149
|
+
/**
|
|
150
|
+
* Loads a font file into the runner.
|
|
151
|
+
*
|
|
152
|
+
* @category Fonts
|
|
153
|
+
*/
|
|
154
|
+
loadFont(file: File): Promise<FontLoadResult>;
|
|
155
|
+
/**
|
|
156
|
+
* Sends a playback command and resolves with the resulting playback state.
|
|
157
|
+
*
|
|
158
|
+
* @category Playback
|
|
159
|
+
*/
|
|
160
|
+
playback(action: PlaybackAction, options?: {
|
|
161
|
+
frame?: number;
|
|
162
|
+
maxFrames?: number;
|
|
163
|
+
}): Promise<PlaybackState>;
|
|
164
|
+
private connectPort;
|
|
165
|
+
private handlePortMessage;
|
|
166
|
+
private handleReady;
|
|
167
|
+
private markReady;
|
|
168
|
+
private handleRunError;
|
|
169
|
+
private request;
|
|
170
|
+
private postMessage;
|
|
171
|
+
private handleUnavailable;
|
|
172
|
+
private validateReadyMessage;
|
|
173
|
+
private assertSandboxOriginPolicy;
|
|
174
|
+
private setStatus;
|
|
175
|
+
private disposeFrame;
|
|
176
|
+
private createRequestId;
|
|
177
|
+
}
|