Expand description
Daemon plugins: run inside standardd on the machines the plugin is
enabled on, never render, and may reach the machine through granted
system interfaces (process, watch, panes; with the wasi
feature, files, sockets and HTTP).
A daemon plugin has an async DaemonPlugin::run loop driven by a
small single-threaded executor: the host calls the component’s drive
export when the loop’s next timer is due and after every event, and the
loop awaits Context::next_event, Context::sleep and, with wasi,
WASI pollables (daemon::io). What happens on the machine arrives as
events too: Event::FileChanged for a
watch, Event::ProcessOutput and
Event::ProcessExited for a
process, Event::PaneChanged after
panes::subscribe. A companion is a daemon plugin that answers its
UI plugin’s calls in DaemonPlugin::call;
Context::caller says who asked.
Plugin methods take &self: the run loop and call handlers share the
plugin, so keep mutable state in a RefCell or Cell.
//! The companion daemon of `together_ui.rs`: the same plugin id, kind
//! `companion`. It runs once (the singleton daemon), does the work, stores
//! the result as a durable value and announces it with an event; the UI
//! halves in every viewer read the value.
//!
//! Manifest:
//!
//! ```json
//! {
//! "apiVersion": 2, "kind": "companion", "id": "together", "version": "0.1.0",
//! "module": "together_companion.wasm",
//! "grants": { "process.exec:make": "Runs make in your project when you ask for a build" }
//! }
//! ```
#![no_std]
extern crate alloc;
use alloc::rc::Rc;
use core::cell::Cell;
use serde::Serialize;
use standard_plugin::daemon::{Context, DaemonPlugin, process};
use standard_plugin::prelude::{Error, Json, Target, format};
#[derive(Serialize)]
struct Status<'a> {
state: &'a str,
builds: u32,
}
/// Stores the status where every viewer's UI half reads it, and tells
/// them it changed.
fn publish(cx: &Context, state: &str, builds: u32) {
let _ = cx
.values()
.set("together.status", &Status { state, builds });
let _ = cx.events().emit("together.changed", &(), Target::Viewers);
}
/// One build, run as a task on the daemon's executor.
async fn rebuild(cx: Context, builds: Rc<Cell<u32>>, running: Rc<Cell<bool>>) {
publish(&cx, "building", builds.get());
let passed = process::spawn("make", &[], None)
.and_then(|child| child.wait())
.is_ok_and(|status| status == 0);
builds.set(builds.get() + 1);
running.set(false);
publish(&cx, if passed { "passed" } else { "failed" }, builds.get());
}
#[standard_plugin::daemon]
struct Companion {
builds: Rc<Cell<u32>>,
running: Rc<Cell<bool>>,
}
impl DaemonPlugin for Companion {
fn activate(_cx: &Context) -> Self {
Self {
builds: Rc::new(Cell::new(0)),
running: Rc::new(Cell::new(false)),
}
}
async fn run(&self, cx: Context) {
publish(&cx, "idle", self.builds.get());
}
/// Calls arrive from the UI half. A handler answers at once; longer
/// work goes to a task, which runs when the host next drives the
/// plugin (right after the call).
fn call(&self, method: &str, _request: Json, cx: &Context) -> Result<Json, Error> {
match method {
"rebuild" => {
let started = !self.running.replace(true);
if started {
cx.spawn(rebuild(
cx.clone(),
self.builds.clone(),
self.running.clone(),
));
}
Json::from_value(&started)
}
other => Err(Error::Invalid(format!("unknown method {other}"))),
}
}
}Modules§
- env
- The plugin’s environment: the daemon user’s login environment reduced to
HOME,USER,LOGNAME,PATH(the login shell’s),LANG,LC_*,TMPDIRandSHELL(all of it undermachine.full). Children spawned throughprocessinherit exactly these; the daemon’s own environment never reaches a plugin. No grant. - http
- HTTP: over
wasi:httpin a component (thewasifeature), and through the test host natively. An HTTP client overwasi:http(wasifeature): any method, request headers and body, a timeout, the response’s status, headers and body, and a cap on how much body is read. Grant:fetch:<host>(orfetch:<scheme>://<host>[:<port>],network.full,machine.full); the host refuses any other host before a connection is made, and the plugin seesError::GrantDenied { grant: "fetch:<host>" }. - net
- WebSockets the host holds for the plugin (
daemon-net). Grant:socket.connect:<host>:<port>for each server (wss://only). News arrives ascrate::Event::Pluginnamednet::WEBSOCKET_EVENT;net::SocketEvent::from_eventreads it. - panes
- Panes on the daemon’s machine. Grants:
panes.read,panes.write. - process
- Spawn programs on the daemon’s machine. Grant:
process.exec:<program>naming the program exactly as spawned (process.exec:*for any), ormachine.full. Children are killed when the plugin stops. - watch
- File and directory change notifications, debounced by the host and
delivered as
Event::FileChanged. Grant:fs.read:<path>covering the path. At most 256 watches per plugin.
Structs§
- Caller
- Who made a call, as the account routed it.
- Context
- A daemon plugin’s handle to the host, its configuration and its executor. Cheap to clone.
- Interest
- Which of the plugin’s UI surfaces some viewer on the account shows now:
the union over every viewer, kept by the account and pushed to the
daemon half whenever it changes (a surface shown or hidden, a viewer
that opens or goes away). A daemon half that reads an outside source
only for its UI reads it while
Interest::anyholds and stops when it goes away: nothing on screen, nothing read. Instances count as their surface (workers@<machine>isworkers). - Next
Event - The next event the host delivers.
- Next
Event Until - The next event, or
Noneonce the daemon’s clock reaches a deadline (Context::next_event_until). - Release
- The Standard Code build a daemon runs (
Context::release). - Sleep
- Completes at an instant on the daemon’s clock. Dropped before it completes (it lost a race with an event), it takes its timer with it, so the host is not asked to drive the plugin at an instant nobody waits for.
Enums§
- Pane
Change Kind - How a pane changed (
Event::PaneChanged).
Constants§
- EVENT_
QUEUE - Events kept while nothing awaits them.
- INTEREST_
EVENT - The plugin event the host delivers the viewers’ interest as
(
{ "surfaces": [...] }); the runtime turns it intoEvent::Interest.
Traits§
- Daemon
Plugin - A daemon (or companion) plugin.