Skip to content

Commit 514c69f

Browse files
authored
Merge pull request #15 from topheman/feat/support-tee-on-web
Support plugin-tee in the web host by supporting write operation on a local fork of `@bytecodealliance/preview2-shim`
2 parents 04d15a7 + 4a8c83d commit 514c69f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+4295
-37
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Plugins are sandboxed by default - they cannot access the filesystem or network
3737
Plugins like `ls` or `cat` can interact with the filesystem using the primitives of the languages they are written in.
3838

3939
- on the CLI, a folder from the disk is mounted via the `--dir` flag
40-
- on the browser, a virtual filesystem is mounted, the I/O operations are forwarded via the `@bytecodealliance/preview2-shim/filesystem` shim, which shims the `wasi:filesystem` filesystem interface
40+
- on the browser, a virtual filesystem is mounted, the I/O operations are forwarded via a [local fork](./packages/web-host/overrides/@bytecodealliance/preview2-shim) of `@bytecodealliance/preview2-shim/filesystem` shim, which shims the `wasi:filesystem` filesystem interface
4141

4242
<p align="center"><a href="https://topheman.github.io/webassembly-component-model-experiments/"><img src="./packages/web-host/public/wasi.png" alt="Demo" /></a></p>
4343
<p align="center">
@@ -410,6 +410,13 @@ pluginlab\
410410
- formating the rust code
411411
- typechecking the TypeScript code
412412

413+
### Local fork of `@bytecodealliance/preview2-shim`
414+
415+
- The original [`@bytecodealliance/preview2-shim`](https://github.com/bytecodealliance/jco/tree/main/packages/preview2-shim) for the **browser** doesn't support **WRITE** operations on the filesystem
416+
- A fork was created in [`packages/web-host/overrides/@bytecodealliance/preview2-shim`](./packages/web-host/overrides/@bytecodealliance/preview2-shim) to fix this issue
417+
418+
Everything is described in [PR#15 Support plugin-tee in the web host](https://github.com/topheman/webassembly-component-model-experiments/pull/15) (must read 😉).
419+
413420
## Resources
414421

415422
### Optional tools

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"!**/node_modules",
77
"!**/dist",
88
"!**/build",
9-
"!**/package.json"
9+
"!**/package.json",
10+
"!packages/web-host/overrides/**"
1011
]
1112
},
1213
"formatter": {

lint-staged.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
export default {
3-
'packages/**': 'biome check --write',
3+
'packages/*/!(overrides)/**': 'biome check --write',
44
'packages/plugin-echo/**/*.ts': () => 'npm run typecheck --workspace=packages/plugin-echo',
55
'packages/web-host/**/*.{ts,tsx}': () => 'npm run typecheck --workspace=packages/web-host',
66
// `cargo fmt doesn't accept files

packages/web-host/clis/prepareFilesystem.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ function assertPathIsString(path: string): asserts path is string {
5353
}
5454
}
5555

56+
function template(data: VirtualFs): string {
57+
return `// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY
58+
59+
// It is meant to be used for mounting a virtual filesystem in the browser
60+
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
61+
//
62+
// The \`fs\` calls like \`read\`, \`readDir\` ... in rust or other languages will be redirected to this virtual filesystem
63+
// and will interact as if the filesystem was a real one.
64+
65+
export function makeVirtualFs() { return ${JSON.stringify(data, null, 2)}; }`;
66+
}
67+
5668
function run() {
5769
program
5870
.description("Prepare wasm files for the web host")
@@ -65,17 +77,7 @@ function run() {
6577
if (options.format === "json") {
6678
console.log(JSON.stringify(virtualFs, null, 2));
6779
} else if (options.format === "ts") {
68-
console.log(
69-
`// THIS FILE IS GENERATED BY THE prepareVirtualFs COMMAND, DO NOT EDIT IT MANUALLY
70-
71-
// It is meant to be used for mounting a virtual filesystem in the browser
72-
// interacting with @bytecodealliance/preview2-shim/filesystem , the shim for wasi:filesystem
73-
//
74-
// The \`fs\` calls like \`read\`, \`readDir\` ... in rust or other languages will be redirected to this virtual filesystem
75-
// and will interact as if the filesystem was a real one.
76-
77-
export function makeVirtualFs() { return ${JSON.stringify(virtualFs, null, 2)}; }`,
78-
);
80+
console.log(template(virtualFs));
7981
}
8082
});
8183

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Preview2 Shim
2+
3+
WASI Preview2 implementations for Node.js & browsers.
4+
5+
Node.js support is fully tested and conformant against the Wasmtime test suite.
6+
7+
Browser support is considered experimental, and not currently suitable for production applications.
8+
9+
# License
10+
11+
This project is licensed under the Apache 2.0 license with the LLVM exception.
12+
See [LICENSE](LICENSE) for more details.
13+
14+
### Contribution
15+
16+
Unless you explicitly state otherwise, any contribution intentionally submitted
17+
for inclusion in this project by you, as defined in the Apache-2.0 license,
18+
shall be licensed as above, without any additional terms or conditions.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { _setCwd as fsSetCwd } from './filesystem.js';
2+
import { streams } from './io.js';
3+
const { InputStream, OutputStream } = streams;
4+
5+
const symbolDispose = Symbol.dispose ?? Symbol.for('dispose');
6+
7+
let _env = [], _args = [], _cwd = "/";
8+
export function _setEnv (envObj) {
9+
_env = Object.entries(envObj);
10+
}
11+
export function _setArgs (args) {
12+
_args = args;
13+
}
14+
15+
export function _setCwd (cwd) {
16+
fsSetCwd(_cwd = cwd);
17+
}
18+
19+
export const environment = {
20+
getEnvironment () {
21+
return _env;
22+
},
23+
getArguments () {
24+
return _args;
25+
},
26+
initialCwd () {
27+
return _cwd;
28+
}
29+
};
30+
31+
class ComponentExit extends Error {
32+
constructor(code) {
33+
super(`Component exited ${code === 0 ? 'successfully' : 'with error'}`);
34+
this.exitError = true;
35+
this.code = code;
36+
}
37+
}
38+
39+
export const exit = {
40+
exit (status) {
41+
throw new ComponentExit(status.tag === 'err' ? 1 : 0);
42+
},
43+
exitWithCode (code) {
44+
throw new ComponentExit(code);
45+
}
46+
};
47+
48+
/**
49+
* @param {import('../common/io.js').InputStreamHandler} handler
50+
*/
51+
export function _setStdin (handler) {
52+
stdinStream.handler = handler;
53+
}
54+
/**
55+
* @param {import('../common/io.js').OutputStreamHandler} handler
56+
*/
57+
export function _setStderr (handler) {
58+
stderrStream.handler = handler;
59+
}
60+
/**
61+
* @param {import('../common/io.js').OutputStreamHandler} handler
62+
*/
63+
export function _setStdout (handler) {
64+
stdoutStream.handler = handler;
65+
}
66+
67+
const stdinStream = new InputStream({
68+
blockingRead (_len) {
69+
// TODO
70+
},
71+
subscribe () {
72+
// TODO
73+
},
74+
[symbolDispose] () {
75+
// TODO
76+
}
77+
});
78+
let textDecoder = new TextDecoder();
79+
const stdoutStream = new OutputStream({
80+
write (contents) {
81+
if (contents[contents.length - 1] == 10) {
82+
// console.log already appends a new line
83+
contents = contents.subarray(0, contents.length - 1);
84+
}
85+
console.log(textDecoder.decode(contents));
86+
},
87+
blockingFlush () {
88+
},
89+
[symbolDispose] () {
90+
}
91+
});
92+
const stderrStream = new OutputStream({
93+
write (contents) {
94+
if (contents[contents.length - 1] == 10) {
95+
// console.error already appends a new line
96+
contents = contents.subarray(0, contents.length - 1);
97+
}
98+
console.error(textDecoder.decode(contents));
99+
},
100+
blockingFlush () {
101+
},
102+
[symbolDispose] () {
103+
104+
}
105+
});
106+
107+
export const stdin = {
108+
InputStream,
109+
getStdin () {
110+
return stdinStream;
111+
}
112+
};
113+
114+
export const stdout = {
115+
OutputStream,
116+
getStdout () {
117+
return stdoutStream;
118+
}
119+
};
120+
121+
export const stderr = {
122+
OutputStream,
123+
getStderr () {
124+
return stderrStream;
125+
}
126+
};
127+
128+
class TerminalInput {}
129+
class TerminalOutput {}
130+
131+
const terminalStdoutInstance = new TerminalOutput();
132+
const terminalStderrInstance = new TerminalOutput();
133+
const terminalStdinInstance = new TerminalInput();
134+
135+
export const terminalInput = {
136+
TerminalInput
137+
};
138+
139+
export const terminalOutput = {
140+
TerminalOutput
141+
};
142+
143+
export const terminalStderr = {
144+
TerminalOutput,
145+
getTerminalStderr () {
146+
return terminalStderrInstance;
147+
}
148+
};
149+
150+
export const terminalStdin = {
151+
TerminalInput,
152+
getTerminalStdin () {
153+
return terminalStdinInstance;
154+
}
155+
};
156+
157+
export const terminalStdout = {
158+
TerminalOutput,
159+
getTerminalStdout () {
160+
return terminalStdoutInstance;
161+
}
162+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export const monotonicClock = {
2+
resolution() {
3+
// usually we dont get sub-millisecond accuracy in the browser
4+
// Note: is there a better way to determine this?
5+
return 1e6;
6+
},
7+
now () {
8+
// performance.now() is in milliseconds, but we want nanoseconds
9+
return BigInt(Math.floor(performance.now() * 1e6));
10+
},
11+
subscribeInstant (instant) {
12+
instant = BigInt(instant);
13+
const now = this.now();
14+
if (instant <= now)
15+
return this.subscribeDuration(0);
16+
return this.subscribeDuration(instant - now);
17+
},
18+
subscribeDuration (_duration) {
19+
_duration = BigInt(_duration);
20+
console.log(`[monotonic-clock] subscribe`);
21+
}
22+
};
23+
24+
export const wallClock = {
25+
now() {
26+
let now = Date.now(); // in milliseconds
27+
const seconds = BigInt(Math.floor(now / 1e3));
28+
const nanoseconds = (now % 1e3) * 1e6;
29+
return { seconds, nanoseconds };
30+
},
31+
resolution() {
32+
return { seconds: 0n, nanoseconds: 1e6 };
33+
}
34+
};

0 commit comments

Comments
 (0)