arm64js 0.8.0 → 0.9.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/README.md +31 -3
- package/dist/frame-host.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/loader.d.ts +7 -0
- package/dist/loader.js +9 -0
- package/dist/runtime.js +7 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ const { output, exitCode } = await vm.exec('apk add --no-cache curl');
|
|
|
30
30
|
|
|
31
31
|
It returns raw `output` and `exitCode` so that you can verify the result.
|
|
32
32
|
|
|
33
|
-
The VM _does not_ have access to the Internet except for the apk registry. To give it your own data, you can use one of the two options:
|
|
33
|
+
The VM _does not_ have access to the Internet except for the apk registry, unless you run an internet gateway for it (see [Networking](#networking)). To give it your own data, you can use one of the two options:
|
|
34
34
|
|
|
35
35
|
- `vm.writeFile`: copies the specified file into the VM. Changing or deleting the file happens only inside the VM. The file is saved and loaded with the snapshot. Uses VM memory to copy the file, thus is intended mainly for smaller (up to 100 MiB) files.
|
|
36
36
|
- `vm.mount`: read-only, direct mount of the data blob. Doesn't use any VM memory. Not stored in the snapshot. To unmount use `vm.unmount()`.
|
|
@@ -114,7 +114,7 @@ npm install arm64js
|
|
|
114
114
|
|
|
115
115
|
## API
|
|
116
116
|
|
|
117
|
-
### `Arm64JS.boot(image | snapshotId, { vcpus?, onProgress? })` → `Vm`
|
|
117
|
+
### `Arm64JS.boot(image | snapshotId, { vcpus?, gateway?, onProgress? })` → `Vm`
|
|
118
118
|
|
|
119
119
|
A VM can be created either from a base image or your own local snapshot.
|
|
120
120
|
|
|
@@ -123,6 +123,8 @@ A VM can be created either from a base image or your own local snapshot.
|
|
|
123
123
|
|
|
124
124
|
Additionally, you can specify an `onProgress` callback to track VM loading phases and the number of virtual CPUs the VM will have (more is not always better).
|
|
125
125
|
|
|
126
|
+
`gateway: { url, token? }` sends the VM's internet traffic through a gateway you run, see [Internet gateway](#internet-gateway).
|
|
127
|
+
|
|
126
128
|
onProgress gets one argument, a BootProgress object:
|
|
127
129
|
|
|
128
130
|
```ts
|
|
@@ -390,10 +392,36 @@ It shows the VM console, sends what is typed to the VM, and sizes itself to the
|
|
|
390
392
|
|
|
391
393
|
## Networking
|
|
392
394
|
|
|
393
|
-
The VM has an emulated network card, DNS, and can install Alpine packages with `apk` through the CDN's package mirror. It has no general internet access.
|
|
395
|
+
The VM has an emulated network card, DNS, and can install Alpine packages with `apk` through the CDN's package mirror. It has no general internet access unless you give it a gateway.
|
|
394
396
|
|
|
395
397
|
The name `host.internal` is the page itself: see `vm.listen` and `vm.serveHttp`.
|
|
396
398
|
|
|
399
|
+
### Internet gateway
|
|
400
|
+
|
|
401
|
+
A browser cannot open TCP connections, so a VM reaches the internet through a gateway: a small program that takes the VM's connections over a WebSocket and opens them from the machine it runs on. Download `arm64js-gateway` for Linux or macOS from [arm64js.com](https://arm64js.com/#internet-gateway), start it, and pass its address when booting:
|
|
402
|
+
|
|
403
|
+
```sh
|
|
404
|
+
./arm64js-gateway --ws 127.0.0.1:4434
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
const vm = await Arm64JS.boot('alpine', { gateway: { url: 'ws://127.0.0.1:4434' } });
|
|
409
|
+
await vm.exec('wget -qO- https://example.com');
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
By default the gateway relays every connection from any page that reaches it, to any address, including the machine it runs on and its local network. To narrow that down:
|
|
413
|
+
|
|
414
|
+
- `--allow-host registry.npmjs.org` (repeatable, `*.npmjs.org` for a whole domain) relays only TCP connections to those names. A name that resolves to a private or local address is still refused; give the IP itself to allow an address. UDP and ping are then not relayed. Names that share an address with an allowed name on a large CDN can still be reached, because the gateway sees addresses, not the site inside the encrypted connection.
|
|
415
|
+
- `--allow-origin https://app.example.com` (repeatable) serves only web pages from that origin. It does not stop other programs, which can send any origin, so a gateway other machines can reach needs `--token`. In frame mode the VM runs in the CDN's frame, so the origin the gateway sees is `https://cdn.arm64js.com`: use `--token` instead.
|
|
416
|
+
- `--token <secret>` serves only clients that pass the same `token` in the boot option: `{ gateway: { url, token } }`. Anyone who can read your page can read the token too. A client the gateway refuses gets no connection at all, so the VM boots without internet access.
|
|
417
|
+
|
|
418
|
+
Things to know:
|
|
419
|
+
|
|
420
|
+
- Chrome asks the user to allow access to apps on their device the first time a page on another site connects to a gateway on `127.0.0.1`. A page served from `localhost` is not asked.
|
|
421
|
+
- A gateway on another machine needs `wss://`: browsers block a plain `ws://` connection from an `https` page to anything but the local machine. Put it behind a TLS proxy such as Caddy.
|
|
422
|
+
- DNS does not go through the gateway: it is resolved over HTTPS by the browser (Cloudflare's resolver). `apk` packages come from the CDN's mirror and use the gateway only when that download fails.
|
|
423
|
+
- Needs engine 0.9 or newer. A snapshot saved on an older engine resumes on that engine, and booting it with `gateway` fails with `engine-mismatch` rather than starting a VM without internet access.
|
|
424
|
+
|
|
397
425
|
## Development
|
|
398
426
|
|
|
399
427
|
```sh
|
package/dist/frame-host.js
CHANGED
|
@@ -27,6 +27,12 @@ export async function mountFrameHost(url, deps = {}) {
|
|
|
27
27
|
iframe.src = url;
|
|
28
28
|
iframe.setAttribute('title', 'arm64js');
|
|
29
29
|
iframe.setAttribute('aria-hidden', 'true');
|
|
30
|
+
// The VM's network worker runs in this frame. To reach a gateway on the user's
|
|
31
|
+
// own machine (`gateway: { url: 'ws://127.0.0.1:…' }`) Chrome asks the user for
|
|
32
|
+
// local network access, and a cross-origin frame may only ask when the page
|
|
33
|
+
// delegates it. All three names are Chrome's (it split the permission in 145);
|
|
34
|
+
// a browser that knows none of them ignores the attribute.
|
|
35
|
+
iframe.setAttribute('allow', 'local-network-access; local-network; loopback-network');
|
|
30
36
|
// In the layout and on screen, never display:none: a hidden frame's timers are
|
|
31
37
|
// throttled by the browser. The VM steps in workers, but the frame's main
|
|
32
38
|
// thread still relays.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { VERSION as version } from './version.js';
|
|
|
6
6
|
export { Connection, type ConnectionData } from './connection.js';
|
|
7
7
|
export type { HttpHandler } from './http.js';
|
|
8
8
|
export { DEFAULT_MAX_BODY_BYTES, Vm, type FileData, type Listener, type Mount, type ServeHttpOptions } from './vm.js';
|
|
9
|
-
export type { Arm64JSErrorCode, BootOptions, BootProgress, ExecOptions, ExecResult, OutputInfo, ReadFileOptions, SnapshotInfo, SnapshotOptions, StorageStatus, VmExit, WriteFileOptions, } from '@arm64js/protocol';
|
|
9
|
+
export type { Arm64JSErrorCode, BootOptions, BootProgress, ExecOptions, ExecResult, GatewayOptions, OutputInfo, ReadFileOptions, SnapshotInfo, SnapshotOptions, StorageStatus, VmExit, WriteFileOptions, } from '@arm64js/protocol';
|
|
10
10
|
export { Arm64JSError, HOST_INTERNAL_NAME } from '@arm64js/protocol';
|
|
11
11
|
export declare const Arm64JS: Arm64JSApi;
|
|
12
12
|
export default Arm64JS;
|
package/dist/loader.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export declare function packageEngine(): string;
|
|
|
4
4
|
export declare function engineParts(tag: string): [number, number] | null;
|
|
5
5
|
/** Whether engine `a` is newer than engine `b`. False unless both are exact tags. */
|
|
6
6
|
export declare function isNewerEngine(a: string, b: string): boolean;
|
|
7
|
+
/** The first engine whose runtime understands `BootOptions.gateway`. */
|
|
8
|
+
export declare const GATEWAY_MIN_ENGINE = "0.9";
|
|
9
|
+
/**
|
|
10
|
+
* Whether `engine` is known to predate `gateway`. Only an exact tag can say so;
|
|
11
|
+
* a development build (`'dev'`) is assumed current.
|
|
12
|
+
*/
|
|
13
|
+
export declare function predatesGateway(engine: string): boolean;
|
|
7
14
|
/** The immutable runtime directory for an exact `X.Y`, with its trailing slash. */
|
|
8
15
|
export declare function runtimeDir(version: string): string;
|
|
9
16
|
export declare function runtimeModuleUrl(version: string): string;
|
package/dist/loader.js
CHANGED
|
@@ -24,6 +24,15 @@ export function isNewerEngine(a, b) {
|
|
|
24
24
|
}
|
|
25
25
|
return x[0] !== y[0] ? x[0] > y[0] : x[1] > y[1];
|
|
26
26
|
}
|
|
27
|
+
/** The first engine whose runtime understands `BootOptions.gateway`. */
|
|
28
|
+
export const GATEWAY_MIN_ENGINE = '0.9';
|
|
29
|
+
/**
|
|
30
|
+
* Whether `engine` is known to predate `gateway`. Only an exact tag can say so;
|
|
31
|
+
* a development build (`'dev'`) is assumed current.
|
|
32
|
+
*/
|
|
33
|
+
export function predatesGateway(engine) {
|
|
34
|
+
return engineParts(engine) !== null && isNewerEngine(GATEWAY_MIN_ENGINE, engine);
|
|
35
|
+
}
|
|
27
36
|
/** The immutable runtime directory for an exact `X.Y`, with its trailing slash. */
|
|
28
37
|
export function runtimeDir(version) {
|
|
29
38
|
return `${CDN_BASE}/sdk-v${version}/`;
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arm64JSError } from '@arm64js/protocol';
|
|
2
2
|
import { withSdkErrors } from './errors.js';
|
|
3
3
|
import { loadHost } from './hosts.js';
|
|
4
|
-
import { engineParts, isNewerEngine, packageEngine } from './loader.js';
|
|
4
|
+
import { engineParts, GATEWAY_MIN_ENGINE, isNewerEngine, packageEngine, predatesGateway } from './loader.js';
|
|
5
5
|
import { Vm } from './vm.js';
|
|
6
6
|
/**
|
|
7
7
|
* The runtimes for this page: its own engine, loaded on the first call, and
|
|
@@ -71,6 +71,12 @@ export class Runtime {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
async bootOn(loaded, id, opts) {
|
|
74
|
+
// An older runtime drops the option it does not know, and the VM would boot
|
|
75
|
+
// with no internet and no error. A snapshot saved on an older engine resumes
|
|
76
|
+
// on that engine, so this can happen with a current SDK too.
|
|
77
|
+
if (opts?.gateway !== undefined && predatesGateway(loaded.engine)) {
|
|
78
|
+
throw new Arm64JSError('engine-mismatch', `the gateway option needs engine ${GATEWAY_MIN_ENGINE} or newer; this VM runs on ${loaded.engine}`);
|
|
79
|
+
}
|
|
74
80
|
const { vmId } = await withSdkErrors(loaded.host.boot(id, opts));
|
|
75
81
|
const vm = new Vm(vmId, loaded.host, loaded.engine, (v) => this.vms.delete(v));
|
|
76
82
|
this.vms.add(vm);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.9.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/stamp-version.mjs from package.json. Do not edit.
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.9.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arm64js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Run a Linux VM inside your web page. Boots Alpine from the arm64js CDN, runs commands, keeps snapshots in the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepare": "npm run stamp && npm run build:protocol && husky"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@arm64js/protocol": "^1.
|
|
37
|
+
"@arm64js/protocol": "^1.3.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/js": "^10.0.1",
|