Skip to content

Commit

Permalink
Merge pull request wasmerio#363 from wasmerio/improvements
Browse files Browse the repository at this point in the history
Improvements

fix: Automatically fix up the `wasmer_js_bg.wasm` URL when loading from `unpkg.com`
  • Loading branch information
Michael Bryan authored Dec 14, 2023
2 parents 13f70f2 + b9fd77e commit 4340567
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 26 deletions.
44 changes: 23 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,7 @@
[![Wasmer Discord Channel](https://img.shields.io/discord/1110300506942881873)](https://discord.gg/qBTfsNP7N8)
[![API Docs](https://img.shields.io/badge/API%20Docs-open-blue?link=wasmerio.github.io%2Fwasmer-js%2F)](https://wasmerio.github.io/wasmer-js/)

Isomorphic Javascript library for running WASI programs.

The Javascript Package supports:

* [X] WASI support
* [X] Environment variables
* [X] FileSystem access
* [X] Command-line arguments
* [X] Stdio
* [X] WASIX support
* [X] Multi-threading
* [X] Spawning sub-processes
* [ ] Networking
* [X] Mounting directories inside the WASIX instance
* [X] Running packages from the [Wasmer Registry](https://wasmer.io)
* Platforms
* [X] Browser
* [ ] NodeJS
* [ ] Deno
Javascript library for running Wasmer packages at ease, including WASI and WASIX modules.

## Getting Started

Expand Down Expand Up @@ -52,7 +34,7 @@ const { code, stdout } = await instance.wait();
console.log(`Python exited with ${code}: ${stdout}`);
```

### Install from CDN
### Use from CDN

It is possible to avoid needing to use a bundler by importing `@wasmer/sdk` as
a UMD module.
Expand All @@ -68,7 +50,7 @@ will be available as the `WasmerSDK` global variable.
async function runPython() {
await init();
const packageName = "python/python@3.12";
const packageName = "python/python";
const pkg = await Wasmer.fromRegistry(packageName);
const instance = await pkg.entrypoint.run({
args: ["-c", "print('Hello, World!')"],
Expand Down Expand Up @@ -125,6 +107,26 @@ To avoid Cross-Origin Isolation issues, make sure any web pages using
See the [`SharedArrayBuffer` and Cross-Origin Isolation][coi-docs] section under
the *Troubleshooting Common Problems* docs for more.

# Features

The Wasmer SDK Javascript Package supports:

* [X] WASI support
* [X] Environment variables
* [X] FileSystem access
* [X] Command-line arguments
* [X] Stdio
* [X] WASIX support
* [X] Multi-threading
* [X] Spawning sub-processes
* [ ] Networking (on the works)
* [X] Mounting directories inside the WASIX instance
* [X] Running packages from the [Wasmer Registry](https://wasmer.io)
* Platforms
* [X] Browser
* [ ] NodeJS
* [ ] Deno

# License

The entire project is under the MIT License. Please read [the
Expand Down
17 changes: 17 additions & 0 deletions examples/cdn-coi-serviceworker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# CDN with `coi-serviceworker` patch.

This example showcases how to use the Wasmer SDK when using a CDN.

In this case, by using the [`coi-serviceworker.js`](./coi-serviceworker.js) file you can automatically patch the required
COOP/COEP headers using a service worker, so the SDK runs properly without requiring the server to send
the COOP/COEP headers.

This example should work in Github Pages and almost any other provider without modification.

You can test this running locally also very easily:

```
$ python3 -m http.server
```

And then visit http://localhost:8000/
147 changes: 147 additions & 0 deletions examples/cdn-coi-serviceworker/coi-serviceworker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// File taken from: https://github.com/gzuidhof/coi-serviceworker/tree/master
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
let coepCredentialless = false;
if (typeof window === 'undefined') {
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));

self.addEventListener("message", (ev) => {
if (!ev.data) {
return;
} else if (ev.data.type === "deregister") {
self.registration
.unregister()
.then(() => {
return self.clients.matchAll();
})
.then(clients => {
clients.forEach((client) => client.navigate(client.url));
});
} else if (ev.data.type === "coepCredentialless") {
coepCredentialless = ev.data.value;
}
});

self.addEventListener("fetch", function (event) {
const r = event.request;
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
return;
}

const request = (coepCredentialless && r.mode === "no-cors")
? new Request(r, {
credentials: "omit",
})
: r;
event.respondWith(
fetch(request)
.then((response) => {
if (response.status === 0) {
return response;
}

const newHeaders = new Headers(response.headers);
newHeaders.set("Cross-Origin-Embedder-Policy",
coepCredentialless ? "credentialless" : "require-corp"
);
if (!coepCredentialless) {
newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
}
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
})
.catch((e) => console.error(e))
);
});

} else {
(() => {
const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf");
window.sessionStorage.removeItem("coiReloadedBySelf");
const coepDegrading = (reloadedBySelf == "coepdegrade");

// You can customize the behavior of this script through a global `coi` variable.
const coi = {
shouldRegister: () => !reloadedBySelf,
shouldDeregister: () => false,
coepCredentialless: () => true,
coepDegrade: () => true,
doReload: () => window.location.reload(),
quiet: false,
...window.coi
};

const n = navigator;
const controlling = n.serviceWorker && n.serviceWorker.controller;

// Record the failure if the page is served by serviceWorker.
if (controlling && !window.crossOriginIsolated) {
window.sessionStorage.setItem("coiCoepHasFailed", "true");
}
const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed");

if (controlling) {
// Reload only on the first failure.
const reloadToDegrade = coi.coepDegrade() && !(
coepDegrading || window.crossOriginIsolated
);
n.serviceWorker.controller.postMessage({
type: "coepCredentialless",
value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
? false
: coi.coepCredentialless(),
});
if (reloadToDegrade) {
!coi.quiet && console.log("Reloading page to degrade COEP.");
window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade");
coi.doReload("coepdegrade");
}

if (coi.shouldDeregister()) {
n.serviceWorker.controller.postMessage({ type: "deregister" });
}
}

// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;

if (!window.isSecureContext) {
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
return;
}

// In some environments (e.g. Firefox private mode) this won't be available
if (!n.serviceWorker) {
!coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode.");
return;
}

n.serviceWorker.register(window.document.currentScript.src).then(
(registration) => {
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);

registration.addEventListener("updatefound", () => {
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
window.sessionStorage.setItem("coiReloadedBySelf", "updatefound");
coi.doReload();
});

// If the registration is active, but it's not controlling the page
if (registration.active && !n.serviceWorker.controller) {
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling");
coi.doReload();
}
},
(err) => {
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
}
);
})();
}
44 changes: 44 additions & 0 deletions examples/cdn-coi-serviceworker/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wasmer JavaScript SDK</title>
<script src="coi-serviceworker.js"></script>
<script defer type="module">
import { init, Wasmer } from "https://unpkg.com/@wasmer/sdk@latest?module";

async function runPython() {
const status = document.getElementById("status");

status.innerHTML = "Initializing...";
await init();

const packageName = "python/python";
status.innerHTML = `Fetching ${packageName}...`;
const pkg = await Wasmer.fromRegistry(packageName);

status.innerHTML = `Starting ${packageName}...`;
const instance = await pkg.entrypoint.run({
args: ["-c", "print('Hello, World!')"],
});

status.innerHTML = `Running ${packageName}...`;
const { code, stdout } = await instance.wait();

status.innerHTML = `Exited with status code: ${code}`;
const stdoutElement = document.getElementById("stdout");
stdoutElement.innerHTML = stdout;
}

runPython();
</script>
</head>

<body>
<h1 id="status"></h1>
<pre><code id="stdout"></code></pre>
</body>

</html>
14 changes: 14 additions & 0 deletions examples/cdn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CDN

This example showcases how to use the Wasmer SDK when using a CDN.

You'll need to set up the COOP/COEP headers when serving the html file, so it works
properly in the broser:

```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```

If you are unable to modify the server to send this headers when serving the html file,
please use the [cdn-coi-serviceworker](../cdn-coi-serviceworker/) example instead.
4 changes: 2 additions & 2 deletions examples/cdn/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wasmer JavaScript SDK</title>
<script defer type="module">
import { init, Wasmer } from "https://unpkg.com/@wasmer/sdk@latest?module";
import { init, Wasmer } from "https://unpkg.com/@wasmer/sdk@latest/dist/WasmerSDK.js?module";

async function runPython() {
const status = document.getElementById("status");

status.innerHTML = "Initializing...";
await init();

const packageName = "python/python@3.12";
const packageName = "python/python";
status.innerHTML = `Fetching ${packageName}...`;
const pkg = await Wasmer.fromRegistry(packageName);

Expand Down
19 changes: 17 additions & 2 deletions lib.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
export * from "./pkg/wasmer_js";
// @ts-ignore
import load, { InitInput, InitOutput, ThreadPoolWorker } from "./pkg/wasmer_js";
import wasm_bytes from "./pkg/wasmer_js_bg.wasm";

/**
* Initialize the underlying WebAssembly module.
*/
export const init = load;

export const init = async (module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<InitOutput> => {
let regex = new RegExp("^(https?:\/\/unpkg\.com\/@wasmer\/sdk(@[^/\?]*)?)(.*)\??(.*)");
if (!module_or_path) {
// Patch the unpkg url to load Wasmer from the right location
let baseUrl = import.meta.url.match(regex);
if (baseUrl) {
let _version = baseUrl[2];
let path = baseUrl[3];
// If there's a path determined, then we don't need to calculate the wasm path
if (!path) {
module_or_path = new URL(`${baseUrl[1]}/dist/wasmer_js_bg.wasm`);
}
}
}
return load(module_or_path, maybe_memory);
}

// HACK: We save these to the global scope because it's the most reliable way to
// make sure worker.js gets access to them. Normal exports are removed when
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"rootDir": ".",
"noImplicitAny": true,
"declaration": true,
"module": "es6",
"module": "es2020",
"target": "ES2015",
"jsx": "react",
"allowJs": true,
Expand Down

0 comments on commit 4340567

Please sign in to comment.