webcanvas-wasm 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.
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The typed surface this repository exposes over the vendored Gecko engine.
3
+ *
4
+ * The raw `Gecko` class is a thin, leaky interface: `evalChrome` collapses every
5
+ * failure into `''`, and it stringifies its completion value synchronously, so a
6
+ * promise comes back as `"[object Promise]"`. `GeckoSession` is the ergonomic
7
+ * layer over that: it awaits promises, reports failures as typed throws, and
8
+ * exposes a subscription point so a UI framework can observe it.
9
+ */
10
+
11
+ import type { Gecko, GeckoEnv } from './engine.js';
12
+
13
+ export type SessionState = 'idle' | 'booting' | 'ready' | 'loading' | 'destroyed';
14
+
15
+ /** Why an evaluation failed. Stable across versions, safe to switch on. */
16
+ export type GeckoErrorCode = 'not-ready' | 'no-page' | 'no-result' | 'throw' | 'shape' | 'timeout';
17
+
18
+ /**
19
+ * Raised by {@link GeckoSession.eval} and friends.
20
+ *
21
+ * Distinct from a plain `Error` so callers can tell a WebView failure from a
22
+ * bug in their own code.
23
+ *
24
+ * The class and its guard are re-exported from the implementation rather than
25
+ * declared here. A second declaration would be a *different* type with the same
26
+ * name, which silently breaks `instanceof` narrowing for consumers.
27
+ */
28
+ export declare class GeckoEvalError extends Error {
29
+ readonly name: 'GeckoEvalError';
30
+ readonly code: GeckoErrorCode;
31
+ constructor(message: string, code: GeckoErrorCode);
32
+ }
33
+
34
+ export declare function isGeckoEvalError(value: unknown): value is GeckoEvalError;
35
+
36
+ /**
37
+ * Normalise a user-supplied address into something the engine can load.
38
+ *
39
+ * A bare host is assumed to be https; anything with a non-http scheme is
40
+ * rejected, because handing `javascript:` to a navigation is how a page would
41
+ * end up executing in the host document's origin.
42
+ */
43
+ export declare function normalizeHttpUrl(value: string): string;
44
+
45
+ /** Create a session. Nothing is loaded until {@link GeckoSession.init}. */
46
+ export declare function createGeckoRuntime(options?: Partial<SessionOptions>): GeckoSession;
47
+
48
+ /**
49
+ * Non-throwing result form.
50
+ *
51
+ * The ergonomic default is to throw, so this is only needed where a value is
52
+ * genuinely more convenient than a `try`/`catch` — typically in a render path
53
+ * that must not unwind.
54
+ */
55
+ export type EvalResult<T> =
56
+ | { readonly ok: true; readonly value: T }
57
+ | { readonly ok: false; readonly error: string };
58
+
59
+ export type TypeGuard<T> = (value: unknown) => value is T;
60
+
61
+ export interface EvaluateOptions {
62
+ /** Applies only to snippets whose value is a promise. */
63
+ timeoutMs?: number;
64
+ }
65
+
66
+ export interface WaitForOptions {
67
+ timeoutMs?: number;
68
+ intervalMs?: number;
69
+ }
70
+
71
+ export interface SessionOptions {
72
+ canvas: HTMLCanvasElement;
73
+ /** Defaults to `globalThis.window`; injectable for tests. */
74
+ host?: Window;
75
+ profile?: string;
76
+ width?: number;
77
+ height?: number;
78
+ env?: GeckoEnv;
79
+ /** Defaults to `/engine/gecko.js`. */
80
+ bundleUrl?: string;
81
+ /** Defaults to `/engine/gecko.wasm.zst`. */
82
+ wasmUrl?: string;
83
+ /** Injectable module loader; the default is a dynamic `import()`. */
84
+ loadModule?: (url: string) => Promise<{ Gecko: typeof Gecko }>;
85
+ onLog?: (line: string) => void;
86
+ onError?: (error: unknown) => void;
87
+ }
88
+
89
+ /** Rectangle reported by `getBoundingClientRect()`. */
90
+ export interface ElementRect {
91
+ x: number;
92
+ y: number;
93
+ width: number;
94
+ height: number;
95
+ }
96
+
97
+ /**
98
+ * A plain-data projection of a page element.
99
+ *
100
+ * The command bridge is string-based, so a live `Element` cannot cross it
101
+ * intact. These helpers return a snapshot instead of pretending otherwise.
102
+ */
103
+ export interface ElementSnapshot {
104
+ tag: string;
105
+ id: string | null;
106
+ className: string | null;
107
+ text: string;
108
+ html: string;
109
+ attrs: Record<string, string>;
110
+ visible: boolean;
111
+ rect: ElementRect;
112
+ }
113
+
114
+ export interface GeckoSession {
115
+ readonly state: SessionState;
116
+ /** URL of the most recent navigation, or `null` before the first one. */
117
+ readonly currentUrl: string | null;
118
+ /** `true` once the engine is instantiated and past its init handshake. */
119
+ readonly ready: boolean;
120
+
121
+ /**
122
+ * Instantiate the engine and wait for READY. Does not navigate: the boot path
123
+ * deliberately performs no hidden `about:blank` load.
124
+ *
125
+ * Resolves to the live engine instance, which stays reachable afterwards via
126
+ * {@link GeckoSession.raw}.
127
+ */
128
+ init(): Promise<Gecko>;
129
+
130
+ /** Normalise and dispatch a navigation without awaiting its promise. */
131
+ navigate(value: string): string;
132
+
133
+ /**
134
+ * Navigate and wait until the new document is scriptable.
135
+ *
136
+ * Prefer this over {@link GeckoSession.navigate}: navigating only dispatches,
137
+ * so evaluating immediately afterwards races the load.
138
+ */
139
+ open(value: string, timeoutMs?: number): Promise<string>;
140
+
141
+ /** Re-navigate to `currentUrl`, or return `null` if there is none. */
142
+ reload(): string | null;
143
+
144
+ resize(width?: number, height?: number): Promise<void>;
145
+
146
+ /**
147
+ * Subscribe to state changes. The listener fires immediately with the current
148
+ * state, which is what `useSyncExternalStore` expects.
149
+ *
150
+ * The listener runs on the engine's own callbacks, so it fires outside any
151
+ * framework's batching.
152
+ */
153
+ subscribe(listener: (state: SessionState) => void): () => void;
154
+
155
+ /**
156
+ * Evaluate a snippet in the loaded page and return its value.
157
+ *
158
+ * Throws {@link GeckoEvalError} rather than returning a sentinel, so
159
+ * `await session.eval('document.title')` just gives the title. A
160
+ * promise-returning snippet is awaited automatically.
161
+ */
162
+ eval<T = unknown>(code: string, options?: EvaluateOptions): Promise<T>;
163
+
164
+ /** Explicit alias for {@link GeckoSession.eval}. */
165
+ evaluate<T = unknown>(code: string, options?: EvaluateOptions): Promise<T>;
166
+
167
+ /**
168
+ * As {@link GeckoSession.eval}, but validates the shape.
169
+ *
170
+ * @throws `GeckoEvalError` with code `shape` when the guard rejects.
171
+ */
172
+ evalJson<T>(code: string, guard: TypeGuard<T>): Promise<T>;
173
+
174
+ /**
175
+ * Run a call and convert a thrown {@link GeckoEvalError} into a result union.
176
+ *
177
+ * @example
178
+ * const found = await session.attempt(() => session.eval('document.title'));
179
+ * if (found.ok) render(found.value);
180
+ */
181
+ attempt<T>(fn: () => Promise<T> | T): Promise<EvalResult<T>>;
182
+
183
+ /**
184
+ * @deprecated Promises are awaited automatically by `eval`. Kept so existing
185
+ * call sites keep working.
186
+ */
187
+ evalAsync<T = unknown>(code: string, timeoutMs?: number): Promise<T>;
188
+
189
+ /** Poll a boolean snippet until it returns true or the timeout elapses. */
190
+ waitFor(predicate: string, options?: WaitForOptions): Promise<boolean>;
191
+
192
+ /** Snapshot the first match, or `null` when nothing matches. */
193
+ query<T = ElementSnapshot>(selector: string): Promise<T | null>;
194
+
195
+ /** Snapshot every match; an empty array when nothing matches. */
196
+ queryAll<T = ElementSnapshot>(selector: string): Promise<T[]>;
197
+
198
+ /** Trimmed `textContent` of the first match, or `''` when nothing matches. */
199
+ text(selector: string): Promise<string>;
200
+
201
+ /** `getAttribute` on the first match, or `null` when absent or unmatched. */
202
+ attr(name: string, selector: string): Promise<string | null>;
203
+
204
+ /** Dispatch a synthetic click on the first match. */
205
+ click(selector: string): Promise<boolean>;
206
+
207
+ /** Stop the engine's loops and detach input handlers. */
208
+ destroy(): void;
209
+
210
+ /**
211
+ * Escape hatch to the underlying engine, for commands and properties this
212
+ * wrapper does not model. Null until {@link GeckoSession.init} resolves and
213
+ * after {@link GeckoSession.destroy}.
214
+ */
215
+ readonly raw: Gecko | null;
216
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `webcanvas-wasm/vite` — serves the engine and sets the isolation headers.
3
+ *
4
+ * The plugin type is declared structurally rather than as `import('vite').Plugin`
5
+ * so that a non-Vite consumer who merely has this package installed does not need
6
+ * Vite's own types present.
7
+ */
8
+
9
+ export interface GeckoVitePluginOptions {
10
+ /** Source directory holding the engine assets. Defaults to the bundled engine. */
11
+ engineDir?: string;
12
+ /** Public path to serve the engine from. Defaults to `/engine`. */
13
+ mount?: string;
14
+ /** Set false to serve the engine yourself instead of copying it on build. */
15
+ copyOnBuild?: boolean;
16
+ }
17
+
18
+ export interface GeckoVitePlugin {
19
+ name: string;
20
+ buildStart?: () => Promise<void>;
21
+ configureServer?: (server: GeckoDevServer) => void;
22
+ writeBundle?: (outputOptions: { dir?: string }) => Promise<void>;
23
+ }
24
+
25
+ /**
26
+ * A Connect-style middleware, typed loosely on purpose.
27
+ *
28
+ * The real `res` is a `ServerResponse`, which is a Writable stream with dozens
29
+ * of members. Spelling that out structurally would add nothing for a caller —
30
+ * Vite supplies the object, nobody constructs one — while making this file
31
+ * depend on Node's exact stream typings.
32
+ */
33
+ export type GeckoMiddleware = (req: any, res: any, next: () => void) => void;
34
+
35
+ export interface GeckoDevServer {
36
+ middlewares: {
37
+ use(handler: GeckoMiddleware): void;
38
+ use(route: string, handler: GeckoMiddleware): void;
39
+ };
40
+ }
41
+
42
+ export function geckoWebView(options?: GeckoVitePluginOptions): GeckoVitePlugin;
43
+
44
+ /** The two headers the engine requires, plus a matching resource policy. */
45
+ export function setIsolationHeaders(res: { setHeader(name: string, value: string): void }): void;
46
+
47
+ export default geckoWebView;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Minimal declarations for the one dependency that ships no types.
3
+ *
4
+ * The server only needs Wisp's request router, so declaring just that keeps
5
+ * `checkJs` honest without pulling the whole surface in.
6
+ */
7
+ declare module '@mercuryworkshop/wisp-js/server' {
8
+ export interface WispRouteServer {
9
+ routeRequest(
10
+ request: import('node:http').IncomingMessage,
11
+ socket: import('node:stream').Duplex,
12
+ head: Buffer
13
+ ): void;
14
+ }
15
+
16
+ export const server: WispRouteServer;
17
+ }