make example plugin slightly better to show examples
This commit is contained in:
+1
-1
@@ -333,7 +333,7 @@ Current Plugin 2.0 samples:
|
|||||||
|--------|---------|
|
|--------|---------|
|
||||||
| `sample-wasm` | C/WASI file bridge sample using `pluginData`, `downloads`, and `overlord_host_info`. |
|
| `sample-wasm` | C/WASI file bridge sample using `pluginData`, `downloads`, and `overlord_host_info`. |
|
||||||
| `sample-wasm-hostinfo` | TinyGo sample that queries HostInfo from a universal WASM module. |
|
| `sample-wasm-hostinfo` | TinyGo sample that queries HostInfo from a universal WASM module. |
|
||||||
| `sample-wasm-platform-note` | Rust sample that branches by `os`/`arch` inside one WASM module and writes a platform-specific note. |
|
| `sample-wasm-platform-note` | Rust sample that branches by `os`/`arch`, writes a platform-specific note, and probes Desktop list/read access. |
|
||||||
| `sample-ts-fullstack` | TypeScript UI plus TypeScript server runtime with shared local modules. |
|
| `sample-ts-fullstack` | TypeScript UI plus TypeScript server runtime with shared local modules. |
|
||||||
|
|
||||||
Legacy native samples remain available:
|
Legacy native samples remain available:
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Sample WASM Platform Note Plugin
|
# Sample WASM Platform Note Plugin
|
||||||
|
|
||||||
This Plugin 2.0 sample is written in Rust. It reads HostInfo, branches on `os` and `arch`, and writes a small note into the approved `pluginData` bucket.
|
This Plugin 2.0 sample is written in Rust. It reads HostInfo, branches on `os` and `arch`, writes a small note into the approved `pluginData` bucket, then lists the user's Desktop and attempts to read a small sample from one regular file to prove the file bridge is working.
|
||||||
|
|
||||||
|
The manifest requests `pluginData` write/mkdir access and `desktop` list/read access, so changing this sample's permissions will require approving the new needs hash before load.
|
||||||
|
|
||||||
On Windows, install the Rust WASI target first:
|
On Windows, install the Rust WASI target first:
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,22 @@ use std::str;
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
fn overlord_emit(event: *const u8, event_len: i32, payload: *const u8, payload_len: i32) -> i32;
|
fn overlord_emit(event: *const u8, event_len: i32, payload: *const u8, payload_len: i32) -> i32;
|
||||||
fn overlord_host_info(out: *mut u8, out_len: i32) -> i32;
|
fn overlord_host_info(out: *mut u8, out_len: i32) -> i32;
|
||||||
|
fn overlord_fs_list(
|
||||||
|
bucket: *const u8,
|
||||||
|
bucket_len: i32,
|
||||||
|
path: *const u8,
|
||||||
|
path_len: i32,
|
||||||
|
out: *mut u8,
|
||||||
|
out_len: i32,
|
||||||
|
) -> i32;
|
||||||
|
fn overlord_fs_read(
|
||||||
|
bucket: *const u8,
|
||||||
|
bucket_len: i32,
|
||||||
|
path: *const u8,
|
||||||
|
path_len: i32,
|
||||||
|
out: *mut u8,
|
||||||
|
out_len: i32,
|
||||||
|
) -> i32;
|
||||||
fn overlord_fs_mkdir(bucket: *const u8, bucket_len: i32, path: *const u8, path_len: i32) -> i32;
|
fn overlord_fs_mkdir(bucket: *const u8, bucket_len: i32, path: *const u8, path_len: i32) -> i32;
|
||||||
fn overlord_fs_write(
|
fn overlord_fs_write(
|
||||||
bucket: *const u8,
|
bucket: *const u8,
|
||||||
@@ -102,9 +118,14 @@ pub extern "C" fn overlord_on_event(
|
|||||||
note.len() as i32,
|
note.len() as i32,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let desktop = probe_desktop(host);
|
||||||
|
|
||||||
let response = format!(
|
let response = format!(
|
||||||
r#"{{"os":"{os}","arch":"{arch}","mkdir":{mkdir_result},"write":{write_result}}}"#
|
r#"{{"os":"{os}","arch":"{arch}","mkdir":{mkdir_result},"write":{write_result},"desktopList":{},"desktopRead":{},"desktopFile":"{}","desktopSize":{}}}"#,
|
||||||
|
desktop.list_result,
|
||||||
|
desktop.read_result,
|
||||||
|
json_escape(&desktop.file),
|
||||||
|
desktop.size,
|
||||||
);
|
);
|
||||||
emit("platform_note", response.as_bytes());
|
emit("platform_note", response.as_bytes());
|
||||||
0
|
0
|
||||||
@@ -124,6 +145,155 @@ fn emit(event: &str, payload: &[u8]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DesktopProbe {
|
||||||
|
file: String,
|
||||||
|
size: i64,
|
||||||
|
list_result: i32,
|
||||||
|
read_result: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn probe_desktop(seed: &str) -> DesktopProbe {
|
||||||
|
let bucket = "desktop";
|
||||||
|
let path = "";
|
||||||
|
let mut listing = Vec::new();
|
||||||
|
let mut list_result = -4;
|
||||||
|
for size in [8 * 1024, 64 * 1024, 256 * 1024, 1024 * 1024] {
|
||||||
|
listing.resize(size, 0);
|
||||||
|
list_result = unsafe {
|
||||||
|
overlord_fs_list(
|
||||||
|
bucket.as_ptr(),
|
||||||
|
bucket.len() as i32,
|
||||||
|
path.as_ptr(),
|
||||||
|
path.len() as i32,
|
||||||
|
listing.as_mut_ptr(),
|
||||||
|
listing.len() as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if list_result != -4 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if list_result <= 0 {
|
||||||
|
return DesktopProbe {
|
||||||
|
file: String::new(),
|
||||||
|
size: -1,
|
||||||
|
list_result,
|
||||||
|
read_result: -3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let listing = str::from_utf8(&listing[..list_result as usize]).unwrap_or("");
|
||||||
|
let files = desktop_files_from_listing(listing);
|
||||||
|
if files.is_empty() {
|
||||||
|
return DesktopProbe {
|
||||||
|
file: String::new(),
|
||||||
|
size: -1,
|
||||||
|
list_result,
|
||||||
|
read_result: -3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let seed_sum = seed.bytes().fold(0usize, |acc, b| acc.wrapping_add(b as usize));
|
||||||
|
let selected = &files[seed_sum % files.len()];
|
||||||
|
let mut buf = [0u8; 128];
|
||||||
|
let read_result = unsafe {
|
||||||
|
overlord_fs_read(
|
||||||
|
bucket.as_ptr(),
|
||||||
|
bucket.len() as i32,
|
||||||
|
selected.path.as_ptr(),
|
||||||
|
selected.path.len() as i32,
|
||||||
|
buf.as_mut_ptr(),
|
||||||
|
buf.len() as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
DesktopProbe {
|
||||||
|
file: selected.path.clone(),
|
||||||
|
size: selected.size,
|
||||||
|
list_result,
|
||||||
|
read_result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DesktopFile {
|
||||||
|
path: String,
|
||||||
|
size: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desktop_files_from_listing(listing: &str) -> Vec<DesktopFile> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
let mut rest = listing;
|
||||||
|
while let Some(path_key) = rest.find(r#""path":"#) {
|
||||||
|
rest = &rest[path_key + 7..];
|
||||||
|
let Some((path, after_path)) = parse_json_string(rest) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
rest = after_path;
|
||||||
|
let next_path = rest.find(r#""path":"#).unwrap_or(rest.len());
|
||||||
|
let entry = &rest[..next_path];
|
||||||
|
if !entry.contains(r#""isDir":false"#) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let size = parse_json_i64_after(entry, r#""size":"#).unwrap_or(-1);
|
||||||
|
files.push(DesktopFile { path, size });
|
||||||
|
}
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_json_string(input: &str) -> Option<(String, &str)> {
|
||||||
|
let input = input.strip_prefix('"')?;
|
||||||
|
let mut out = String::new();
|
||||||
|
let mut escaped = false;
|
||||||
|
for (idx, ch) in input.char_indices() {
|
||||||
|
if escaped {
|
||||||
|
match ch {
|
||||||
|
'"' => out.push('"'),
|
||||||
|
'\\' => out.push('\\'),
|
||||||
|
'/' => out.push('/'),
|
||||||
|
'b' => out.push('\u{0008}'),
|
||||||
|
'f' => out.push('\u{000c}'),
|
||||||
|
'n' => out.push('\n'),
|
||||||
|
'r' => out.push('\r'),
|
||||||
|
't' => out.push('\t'),
|
||||||
|
other => out.push(other),
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match ch {
|
||||||
|
'\\' => escaped = true,
|
||||||
|
'"' => return Some((out, &input[idx + 1..])),
|
||||||
|
other => out.push(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_json_i64_after(input: &str, key: &str) -> Option<i64> {
|
||||||
|
let start = input.find(key)? + key.len();
|
||||||
|
let bytes = input.as_bytes();
|
||||||
|
let mut end = start;
|
||||||
|
while end < bytes.len() && (bytes[end].is_ascii_digit() || bytes[end] == b'-') {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
input[start..end].parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_escape(input: &str) -> String {
|
||||||
|
let mut out = String::with_capacity(input.len());
|
||||||
|
for ch in input.chars() {
|
||||||
|
match ch {
|
||||||
|
'"' => out.push_str("\\\""),
|
||||||
|
'\\' => out.push_str("\\\\"),
|
||||||
|
'\n' => out.push_str("\\n"),
|
||||||
|
'\r' => out.push_str("\\r"),
|
||||||
|
'\t' => out.push_str("\\t"),
|
||||||
|
other => out.push(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn memset(dest: *mut u8, value: i32, len: usize) -> *mut u8 {
|
pub extern "C" fn memset(dest: *mut u8, value: i32, len: usize) -> *mut u8 {
|
||||||
unsafe { ptr::write_bytes(dest, value as u8, len) };
|
unsafe { ptr::write_bytes(dest, value as u8, len) };
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"rustc_fingerprint":2568544898921049753,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"5946400966674136012":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"p1\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"wasi\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-wasip1`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-wasip1`\n\nwarning: 2 warnings emitted\n\n"},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}
|
{"rustc_fingerprint":2568544898921049753,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"5946400966674136012":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"p1\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"wasi\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-wasip1`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-wasip1`\n\nwarning: 2 warnings emitted\n\n"}},"successes":{}}
|
||||||
@@ -7,6 +7,10 @@
|
|||||||
<div class="sample-wasm-platform-note-field"><span>Arch</span><strong id="sample-wasm-platform-note-arch">-</strong></div>
|
<div class="sample-wasm-platform-note-field"><span>Arch</span><strong id="sample-wasm-platform-note-arch">-</strong></div>
|
||||||
<div class="sample-wasm-platform-note-field"><span>mkdir</span><strong id="sample-wasm-platform-note-mkdir">-</strong></div>
|
<div class="sample-wasm-platform-note-field"><span>mkdir</span><strong id="sample-wasm-platform-note-mkdir">-</strong></div>
|
||||||
<div class="sample-wasm-platform-note-field"><span>write</span><strong id="sample-wasm-platform-note-write">-</strong></div>
|
<div class="sample-wasm-platform-note-field"><span>write</span><strong id="sample-wasm-platform-note-write">-</strong></div>
|
||||||
|
<div class="sample-wasm-platform-note-field"><span>Desktop file</span><strong id="sample-wasm-platform-note-desktop-file">-</strong></div>
|
||||||
|
<div class="sample-wasm-platform-note-field"><span>Desktop size</span><strong id="sample-wasm-platform-note-desktop-size">-</strong></div>
|
||||||
|
<div class="sample-wasm-platform-note-field"><span>Desktop list</span><strong id="sample-wasm-platform-note-desktop-list">-</strong></div>
|
||||||
|
<div class="sample-wasm-platform-note-field"><span>Desktop read</span><strong id="sample-wasm-platform-note-desktop-read">-</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<pre id="sample-wasm-platform-note-output" class="text-xs text-slate-300 whitespace-pre-wrap"></pre>
|
<pre id="sample-wasm-platform-note-output" class="text-xs text-slate-300 whitespace-pre-wrap"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
arch: document.getElementById("sample-wasm-platform-note-arch"),
|
arch: document.getElementById("sample-wasm-platform-note-arch"),
|
||||||
mkdir: document.getElementById("sample-wasm-platform-note-mkdir"),
|
mkdir: document.getElementById("sample-wasm-platform-note-mkdir"),
|
||||||
write: document.getElementById("sample-wasm-platform-note-write"),
|
write: document.getElementById("sample-wasm-platform-note-write"),
|
||||||
|
desktopFile: document.getElementById("sample-wasm-platform-note-desktop-file"),
|
||||||
|
desktopSize: document.getElementById("sample-wasm-platform-note-desktop-size"),
|
||||||
|
desktopList: document.getElementById("sample-wasm-platform-note-desktop-list"),
|
||||||
|
desktopRead: document.getElementById("sample-wasm-platform-note-desktop-read"),
|
||||||
};
|
};
|
||||||
const clientId = new URLSearchParams(window.location.search).get("clientId");
|
const clientId = new URLSearchParams(window.location.search).get("clientId");
|
||||||
const pluginId = "sample-wasm-platform-note";
|
const pluginId = "sample-wasm-platform-note";
|
||||||
@@ -34,6 +38,10 @@
|
|||||||
if (fields.arch) fields.arch.textContent = info.arch || "-";
|
if (fields.arch) fields.arch.textContent = info.arch || "-";
|
||||||
if (fields.mkdir) fields.mkdir.textContent = String(info.mkdir ?? "-");
|
if (fields.mkdir) fields.mkdir.textContent = String(info.mkdir ?? "-");
|
||||||
if (fields.write) fields.write.textContent = String(info.write ?? "-");
|
if (fields.write) fields.write.textContent = String(info.write ?? "-");
|
||||||
|
if (fields.desktopFile) fields.desktopFile.textContent = info.desktopFile || "(no readable file found)";
|
||||||
|
if (fields.desktopSize) fields.desktopSize.textContent = Number.isFinite(info.desktopSize) && info.desktopSize >= 0 ? `${info.desktopSize} bytes` : "-";
|
||||||
|
if (fields.desktopList) fields.desktopList.textContent = String(info.desktopList ?? "-");
|
||||||
|
if (fields.desktopRead) fields.desktopRead.textContent = String(info.desktopRead ?? "-");
|
||||||
setStatus(info.write === 0 ? "Platform note written" : "Platform note result received");
|
setStatus(info.write === 0 ? "Platform note written" : "Platform note result received");
|
||||||
log(JSON.stringify(info, null, 2));
|
log(JSON.stringify(info, null, 2));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user