@yodaos-pkg/ink 0.3.0 → 0.6.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/dist/README.md DELETED
@@ -1,724 +0,0 @@
1
- # `@yodaos-pkg/ink`
2
-
3
- `@yodaos-pkg/ink` is the browser-facing Ink SDK for third-party frontend
4
- projects.
5
-
6
- It wraps the low-level wasm runtime and `InkWebView` into a stable JavaScript
7
- and TypeScript API so you can:
8
-
9
- - initialize the Ink wasm runtime
10
- - create and destroy an `InkView`
11
- - bind a `canvas` and forward DOM events such as keyboard and pointer input
12
- - open an Ink bundle directly from memory
13
- - fetch and render an Ink bundle from an HTTP VFS service
14
-
15
- ## Use Cases
16
-
17
- This package is designed for:
18
-
19
- - plain browser-based frontend applications
20
- - React applications embedding Ink inside a component
21
- - Vue applications embedding Ink inside a page or component
22
- - frontend apps that fetch Ink resources from a backend VFS service
23
-
24
- ## What You Need Before Starting
25
-
26
- Before integrating the SDK, make sure you have one of these two inputs:
27
-
28
- 1. An in-memory bundle
29
- - You already have the contents of `app.json`, page scripts, styles, images,
30
- and other files.
31
- - This is a good fit when your build pipeline or backend has already prepared
32
- the bundle.
33
-
34
- 2. A VFS service URL
35
- - The frontend only knows an `appId` and an HTTP VFS endpoint.
36
- - This is a good fit when assets live on the server and the browser fetches
37
- them on demand.
38
-
39
- If you use the VFS flow, provide your own HTTP VFS endpoint on the server side.
40
-
41
- ## Installation
42
-
43
- ### Step 1: Install the SDK
44
-
45
- ```bash
46
- npm install @yodaos-pkg/ink
47
- ```
48
-
49
- If you also want to use the VFS flow, prepare an HTTP endpoint that exposes:
50
-
51
- ```text
52
- GET /apps/:appId/manifest
53
- GET /apps/:appId/files/:path
54
- ```
55
-
56
- ## Core API
57
-
58
- These are the most commonly used APIs:
59
-
60
- ### `createInkView(options)`
61
-
62
- Creates an `InkView` instance and initializes the wasm runtime internally.
63
-
64
- **Parameters**
65
-
66
- - `options.width: number`
67
- - Required
68
- - The logical width of the Ink view
69
- - `options.height: number`
70
- - Required
71
- - The logical height of the Ink view
72
- - `options.scaleFactor?: number`
73
- - Optional
74
- - The device pixel ratio, defaulting to `window.devicePixelRatio`
75
- - `options.canvas?: HTMLCanvasElement`
76
- - Optional
77
- - If provided, the view is automatically bound to this canvas
78
- - `options.wasm?: InitInkOptions`
79
- - Optional
80
- - Lets you customize wasm initialization
81
- - `wasmUrl?: string | URL | Request`
82
- - `moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module`
83
-
84
- **What it does**
85
-
86
- - Initializes the Ink wasm runtime
87
- - Creates the underlying `InkWebView`
88
- - Returns the higher-level `InkView` wrapper
89
- - Automatically calls `bindCanvas()` if a `canvas` is provided
90
-
91
- **Returns**
92
-
93
- - `Promise<InkView>`
94
- - Resolves to an `InkView` instance that can then call
95
- `bindDomEvents()`, `openBundle()`, and `openFromVfs()`
96
-
97
- ### `configureNetwork(options)`
98
-
99
- Configures host-controlled request interception for Ink network traffic.
100
-
101
- **Parameters**
102
-
103
- - `options.interceptor`
104
- - Optional
105
- - A function that receives the outgoing request and returns an action
106
- - `options.proxyUrl`
107
- - Optional
108
- - A convenience option for rewriting requests through a proxy endpoint
109
- - `options.targetSearchParam`
110
- - Optional
111
- - The query parameter name used when `proxyUrl` is provided
112
- - `options.clear`
113
- - Optional
114
- - Clears the previously registered interceptor
115
-
116
- **What it does**
117
-
118
- - Registers a global Ink request interceptor
119
- - Lets hosts continue, rewrite, or block outgoing requests
120
- - Mirrors the shape of a WebView-style request interception hook
121
-
122
- **Returns**
123
-
124
- - `void`
125
-
126
- ### `createProxyUrlResolver(options)`
127
-
128
- Creates a request interceptor that rewrites outgoing requests to a host-managed
129
- proxy endpoint such as `'/__proxy?url=...'`.
130
-
131
- **Parameters**
132
-
133
- - `options.proxyUrl`
134
- - Required
135
- - The proxy endpoint to call
136
- - `options.targetSearchParam`
137
- - Optional
138
- - The query parameter name that receives the original target URL
139
-
140
- **Returns**
141
-
142
- - `InkRequestInterceptor`
143
- - A reusable interceptor for `configureNetwork()` or `initInk()`
144
-
145
- ### `view.bindDomEvents(options?)`
146
-
147
- Binds common DOM events to the current `InkView`.
148
-
149
- **Parameters**
150
-
151
- - `options.canvas?: HTMLCanvasElement`
152
- - Optional
153
- - Overrides the currently bound canvas
154
- - `options.keyboardTarget?: EventTarget | null`
155
- - Optional
156
- - The keyboard event target, defaulting to `window` when available
157
- - `options.focusTarget?: HTMLElement | HTMLCanvasElement | null`
158
- - Optional
159
- - The target used for focus and blur events, defaulting to the canvas
160
- - `options.preventWheelDefault?: boolean`
161
- - Optional
162
- - Controls whether wheel default behavior is prevented
163
- - Defaults to `true`
164
-
165
- **What it does**
166
-
167
- - Binds `pointerdown` / `pointermove` / `pointerup` / `pointercancel`
168
- - Binds `wheel`
169
- - Binds `keydown` / `keyup`
170
- - Binds `focus` / `blur`
171
- - Automatically forwards these browser events into the Ink runtime
172
-
173
- **Returns**
174
-
175
- - `() => void`
176
- - Returns a cleanup function
177
- - Calling it removes every listener created by this binding call
178
-
179
- ### `view.openBundle(options)`
180
-
181
- Opens an Ink bundle that already exists in memory.
182
-
183
- **Parameters**
184
-
185
- - `options.appId: string`
186
- - Required
187
- - The application ID of the Ink app to open
188
- - `options.files: BundleFiles`
189
- - Required
190
- - Supports three formats:
191
- - `Map<string, BundleFileInput>`
192
- - `Array<[string, BundleFileInput]>`
193
- - `Record<string, BundleFileInput>`
194
- - `BundleFileInput`
195
- - Supported input types:
196
- - `string`
197
- - `Uint8Array`
198
- - `ArrayBuffer`
199
- - `ArrayBufferView`
200
- - `options.initialPage?: string | null`
201
- - Optional
202
- - Sets the first page to open
203
- - `options.query?: string | Record<string, unknown> | null`
204
- - Optional
205
- - Initial query payload
206
- - If an object is provided, the SDK serializes it into JSON
207
-
208
- **What it does**
209
-
210
- - Normalizes the file set into the bundle shape expected by the runtime
211
- - Calls the underlying `openBundle`
212
- - Opens the requested Ink app
213
- - Automatically triggers a render request
214
-
215
- **Returns**
216
-
217
- - `InkView`
218
- - Returns the current instance so it can be chained
219
-
220
- ### `view.openFromVfs(options)`
221
-
222
- Fetches an Ink bundle from a remote HTTP VFS service and opens it.
223
-
224
- **Parameters**
225
-
226
- - `options.appId: string`
227
- - Required
228
- - The application ID to open
229
- - `options.baseUrl: string`
230
- - Required
231
- - The VFS service base URL, for example
232
- `https://example.com/ink-vfs`
233
- - `options.fetch?: typeof globalThis.fetch`
234
- - Optional
235
- - Lets you provide a custom `fetch` implementation
236
- - `options.signal?: AbortSignal`
237
- - Optional
238
- - Used to cancel the request flow
239
- - `options.headers?: HeadersInit`
240
- - Optional
241
- - Extra request headers
242
- - `options.requestInterceptor?: InkRequestInterceptor`
243
- - Optional
244
- - Overrides the global interceptor for this VFS load only
245
- - `options.initialPage?: string | null`
246
- - Optional
247
- - Sets the first page to open
248
- - `options.query?: string | Record<string, unknown> | null`
249
- - Optional
250
- - Initial query payload
251
-
252
- **What it does**
253
-
254
- - Fetches the VFS manifest first
255
- - Fetches each file listed in the manifest
256
- - Reconstructs the bundle in the browser
257
- - Applies the request interceptor when one is configured
258
- - Calls `openBundle()` automatically
259
-
260
- **Returns**
261
-
262
- - `Promise<LoadedVfsBundle>`
263
- - Resolves to the fetched bundle data:
264
- - `appId: string`
265
- - `manifest: VfsManifest`
266
- - `files: Map<string, Uint8Array>`
267
-
268
- ### `initInk(options)`
269
-
270
- Initializes the wasm runtime and optionally installs a host networking policy.
271
-
272
- **Parameters**
273
-
274
- - `options.wasmUrl?: string | URL | Request`
275
- - Optional
276
- - Custom URL for the wasm binary
277
- - `options.moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module`
278
- - Optional
279
- - Custom wasm module input supported by `wasm-bindgen`
280
- - `options.requestInterceptor?: InkRequestInterceptor`
281
- - Optional
282
- - Installs a host request interceptor before Ink networking starts
283
- - `options.proxyUrl?: string`
284
- - Optional
285
- - Shorthand for a proxy-based interceptor
286
-
287
- **Returns**
288
-
289
- - `Promise<{ InkWebView, getInkVersion }>`
290
- - Resolves to the wasm bindings
291
-
292
- ### `view.startRendering()`
293
-
294
- Starts the continuous render loop.
295
-
296
- **Parameters**
297
-
298
- - None
299
-
300
- **What it does**
301
-
302
- - Enables automatic rendering mode
303
- - Uses `requestAnimationFrame` internally to keep rendering
304
- - Works well for animations, interaction-heavy apps, and continuously updating
305
- UIs
306
-
307
- **Returns**
308
-
309
- - `InkView`
310
- - Returns the current instance so it can be chained
311
-
312
- ### `view.destroy()`
313
-
314
- Destroys the current `InkView` and releases its resources.
315
-
316
- **Parameters**
317
-
318
- - None
319
-
320
- **What it does**
321
-
322
- - Stops the render loop
323
- - Removes event listeners created by `bindDomEvents()`
324
- - Calls the underlying `InkWebView.destroy()`
325
- - Releases runtime resources held by the current view
326
-
327
- **Returns**
328
-
329
- - `void`
330
-
331
- ## Plain JavaScript Integration
332
-
333
- This is the most direct way to integrate Ink into a plain JavaScript app.
334
-
335
- ### Step 1: Prepare a container element
336
-
337
- Start with a `canvas` element:
338
-
339
- ```html
340
- <canvas id="ink-root" style="width: 375px; height: 667px;"></canvas>
341
- ```
342
-
343
- ### Step 2: Import the SDK
344
-
345
- ```js
346
- import { createInkView } from '@yodaos-pkg/ink';
347
- ```
348
-
349
- ### Step 3: Prepare the bundle files
350
-
351
- If your assets are already available in memory, organize them as an object or a
352
- `Map`:
353
-
354
- ```js
355
- const files = {
356
- 'app.json': JSON.stringify({
357
- pages: ['pages/index/index'],
358
- }),
359
- 'pages/index/index.js': `
360
- export default {
361
- data: {
362
- title: 'Hello Ink'
363
- }
364
- };
365
- `,
366
- };
367
- ```
368
-
369
- ### Step 4: Create the view and bind the canvas
370
-
371
- ```js
372
- const canvas = document.getElementById('ink-root');
373
-
374
- const view = await createInkView({
375
- width: canvas.clientWidth,
376
- height: canvas.clientHeight,
377
- scaleFactor: window.devicePixelRatio,
378
- canvas,
379
- });
380
- ```
381
-
382
- ### Step 5: Bind DOM events
383
-
384
- This wires common pointer, keyboard, focus, and blur events automatically:
385
-
386
- ```js
387
- view.bindDomEvents();
388
- ```
389
-
390
- ### Step 6: Open the Ink bundle
391
-
392
- ```js
393
- view.openBundle({
394
- appId: 'demo-app',
395
- files,
396
- });
397
- ```
398
-
399
- ### Step 7: Start the render loop
400
-
401
- ```js
402
- view.startRendering();
403
- ```
404
-
405
- ### Step 8: Clean up when the page is destroyed
406
-
407
- ```js
408
- window.addEventListener('beforeunload', () => {
409
- view.destroy();
410
- });
411
- ```
412
-
413
- ### Full Plain JavaScript Example
414
-
415
- ```js
416
- import { createInkView } from '@yodaos-pkg/ink';
417
-
418
- const files = {
419
- 'app.json': JSON.stringify({
420
- pages: ['pages/index/index'],
421
- }),
422
- 'pages/index/index.js': `
423
- export default {
424
- data: {
425
- title: 'Hello Ink'
426
- }
427
- };
428
- `,
429
- };
430
-
431
- const canvas = document.getElementById('ink-root');
432
-
433
- const view = await createInkView({
434
- width: canvas.clientWidth,
435
- height: canvas.clientHeight,
436
- scaleFactor: window.devicePixelRatio,
437
- canvas,
438
- });
439
-
440
- view.bindDomEvents();
441
- view.openBundle({
442
- appId: 'demo-app',
443
- files,
444
- });
445
- view.startRendering();
446
-
447
- window.addEventListener('beforeunload', () => {
448
- view.destroy();
449
- });
450
- ```
451
-
452
- ## Opening an App Through VFS
453
-
454
- If you do not want to embed the full bundle in the browser and instead prefer
455
- to fetch resources from the server, use `openFromVfs()`.
456
-
457
- ### Step 1: Prepare an accessible VFS service
458
-
459
- For example, the server may expose:
460
-
461
- - `https://example.com/ink-vfs/apps/demo-app/manifest`
462
- - `https://example.com/ink-vfs/apps/demo-app/files/...`
463
-
464
- ### Step 2: Create the view
465
-
466
- ```js
467
- import { createInkView } from '@yodaos-pkg/ink';
468
-
469
- const canvas = document.getElementById('ink-root');
470
-
471
- const view = await createInkView({
472
- width: canvas.clientWidth,
473
- height: canvas.clientHeight,
474
- scaleFactor: window.devicePixelRatio,
475
- canvas,
476
- });
477
- ```
478
-
479
- ### Step 3: Open the remote app
480
-
481
- ```js
482
- view.bindDomEvents();
483
-
484
- await view.openFromVfs({
485
- appId: 'demo-app',
486
- baseUrl: 'https://example.com/ink-vfs',
487
- });
488
-
489
- view.startRendering();
490
- ```
491
-
492
- ### Step 4: Release resources when done
493
-
494
- ```js
495
- view.destroy();
496
- ```
497
-
498
- ## Vue.js Integration
499
-
500
- The recommended approach is to wrap Ink inside a Vue component and manage it
501
- through `onMounted` and `onBeforeUnmount`.
502
-
503
- ### Step 1: Install the dependency
504
-
505
- ```bash
506
- npm install @yodaos-pkg/ink
507
- ```
508
-
509
- ### Step 2: Create a Vue component
510
-
511
- Below is a minimal `InkCanvas.vue` example.
512
-
513
- ### Step 3: Initialize in `onMounted`
514
-
515
- - Get the `canvas`
516
- - Create the `InkView`
517
- - Bind DOM events
518
- - Open the bundle
519
- - Start rendering
520
-
521
- ### Step 4: Clean up in `onBeforeUnmount`
522
-
523
- - Call `view.destroy()`
524
-
525
- ### Full Vue Example
526
-
527
- ```vue
528
- <script setup lang="ts">
529
- import { onBeforeUnmount, onMounted, ref } from 'vue';
530
- import { createInkView } from '@yodaos-pkg/ink';
531
-
532
- const props = defineProps<{
533
- appId: string;
534
- files: Record<string, string | Uint8Array>;
535
- }>();
536
-
537
- const canvasRef = ref<HTMLCanvasElement | null>(null);
538
- let cleanup = () => {};
539
-
540
- onMounted(async () => {
541
- const canvas = canvasRef.value;
542
- if (!canvas) {
543
- return;
544
- }
545
-
546
- const view = await createInkView({
547
- width: canvas.clientWidth,
548
- height: canvas.clientHeight,
549
- scaleFactor: window.devicePixelRatio,
550
- canvas,
551
- });
552
-
553
- view.bindDomEvents();
554
- view.openBundle({
555
- appId: props.appId,
556
- files: props.files,
557
- });
558
- view.startRendering();
559
-
560
- cleanup = () => view.destroy();
561
- });
562
-
563
- onBeforeUnmount(() => {
564
- cleanup();
565
- });
566
- </script>
567
-
568
- <template>
569
- <canvas ref="canvasRef" style="width: 375px; height: 667px;" />
570
- </template>
571
- ```
572
-
573
- ### Vue Integration Tips
574
-
575
- - Treat `files` and `appId` as component inputs
576
- - If the bundle changes, destroy the old view before creating a new one
577
- - If the component size changes, call `view.resize()` from the host layer
578
-
579
- ## React.js Integration
580
-
581
- The recommended approach is to wrap Ink in a React component and manage its
582
- lifecycle inside `useEffect`.
583
-
584
- ### Step 1: Install the dependency
585
-
586
- ```bash
587
- npm install @yodaos-pkg/ink
588
- ```
589
-
590
- ### Step 2: Create a React component
591
-
592
- The component keeps a `canvas ref` and creates or destroys the Ink view inside
593
- `useEffect`.
594
-
595
- ### Step 3: Initialize inside `useEffect`
596
-
597
- - Get the `canvas`
598
- - Create the `InkView`
599
- - Bind DOM events
600
- - Open the bundle
601
- - Start rendering
602
-
603
- ### Step 4: Destroy it in the effect cleanup
604
-
605
- - Call `view.destroy()`
606
-
607
- ### Full React Example
608
-
609
- ```tsx
610
- import { useEffect, useRef } from 'react';
611
- import { createInkView } from '@yodaos-pkg/ink';
612
-
613
- type InkCanvasProps = {
614
- appId: string;
615
- files: Record<string, string | Uint8Array>;
616
- };
617
-
618
- export function InkCanvas({ appId, files }: InkCanvasProps) {
619
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
620
-
621
- useEffect(() => {
622
- let disposed = false;
623
- let cleanup = () => {};
624
-
625
- async function mount() {
626
- const canvas = canvasRef.current;
627
- if (!canvas) {
628
- return;
629
- }
630
-
631
- const view = await createInkView({
632
- width: canvas.clientWidth,
633
- height: canvas.clientHeight,
634
- scaleFactor: window.devicePixelRatio,
635
- canvas,
636
- });
637
-
638
- view.bindDomEvents();
639
- view.openBundle({
640
- appId,
641
- files,
642
- });
643
- view.startRendering();
644
-
645
- cleanup = () => view.destroy();
646
- if (disposed) {
647
- cleanup();
648
- }
649
- }
650
-
651
- mount();
652
-
653
- return () => {
654
- disposed = true;
655
- cleanup();
656
- };
657
- }, [appId, files]);
658
-
659
- return <canvas ref={canvasRef} style={{ width: 375, height: 667 }} />;
660
- }
661
- ```
662
-
663
- ### React Integration Tips
664
-
665
- - Keep `files` as a stable reference when possible, so the view is not rebuilt
666
- on every render
667
- - If you need to switch apps, reinitialize when `appId` or a wrapping `key`
668
- changes
669
- - If the host size changes significantly, use `ResizeObserver` and call
670
- `view.resize()`
671
-
672
- ## Frequently Asked Questions
673
-
674
- ### 1. The page renders, but input does not work
675
-
676
- The usual cause is missing this step:
677
-
678
- ```js
679
- view.bindDomEvents();
680
- ```
681
-
682
- ### 2. Resources are still retained after the page is destroyed
683
-
684
- Make sure you call this when the component unmounts or the page exits:
685
-
686
- ```js
687
- view.destroy();
688
- ```
689
-
690
- ### 3. Should bundle files be strings or binary?
691
-
692
- Both are supported:
693
-
694
- - `string`
695
- - `Uint8Array`
696
- - `ArrayBuffer`
697
-
698
- ### 4. When should I use `openBundle()` vs `openFromVfs()`?
699
-
700
- - If you already have the full resource contents, use `openBundle()`
701
- - If you only know the `appId` and a server endpoint, use `openFromVfs()`
702
-
703
- ## Local Development
704
-
705
- ### Build
706
-
707
- ```bash
708
- npm run build
709
- ```
710
-
711
- ### Test
712
-
713
- ```bash
714
- npm test
715
- ```
716
-
717
- ## Package Shape
718
-
719
- - `dist/index.js`
720
- - SDK entry point
721
- - `dist/pkg/*`
722
- - wasm-bindgen output
723
- - `dist/env.js`
724
- - runtime import shim