fix: declare plugin service websocket authority

This commit is contained in:
Keisuke Hirata 2026-06-25 16:52:20 +09:00
parent 7a4fd97526
commit 6c8998878d
No known key found for this signature in database
5 changed files with 44 additions and 5 deletions

View File

@ -72,6 +72,14 @@ fn rust_component_service_template_has_event_output_pattern() {
plugin["runtime"]["world"].as_str(), plugin["runtime"]["world"].as_str(),
Some("yoi:plugin/instance@1.0.0") Some("yoi:plugin/instance@1.0.0")
); );
assert!(
plugin["permissions"]
.as_array()
.expect("permissions array")
.iter()
.any(|permission| permission["kind"].as_str() == Some("host_api")
&& permission["api"].as_str() == Some("websocket"))
);
assert_eq!( assert_eq!(
plugin["services"].as_array().expect("services array").len(), plugin["services"].as_array().expect("services array").len(),
1 1
@ -89,10 +97,17 @@ fn rust_component_service_template_has_event_output_pattern() {
.as_array() .as_array()
.expect("sources") .expect("sources")
.iter() .iter()
.any(|source| source .any(|source| source.as_str() == Some("websocket:wss://example.com/socket"))
.as_str() );
.unwrap_or_default() let websocket = &plugin["websocket"].as_array().expect("websocket targets")[0];
.starts_with("websocket:wss://")) assert_eq!(websocket["scheme"].as_str(), Some("wss"));
assert_eq!(websocket["host"].as_str(), Some("example.com"));
assert!(
websocket["path_prefixes"]
.as_array()
.expect("websocket path prefixes")
.iter()
.any(|prefix| prefix.as_str() == Some("/socket"))
); );
assert!(SERVICE_TEMPLATE_LIB.contains("world: \"instance\"")); assert!(SERVICE_TEMPLATE_LIB.contains("world: \"instance\""));

View File

@ -2188,6 +2188,10 @@ mod tests {
assert!(manifest.contains("kind = \"wasm-component\"")); assert!(manifest.contains("kind = \"wasm-component\""));
assert!(manifest.contains("[[services]]")); assert!(manifest.contains("[[services]]"));
assert!(manifest.contains("[[ingresses]]")); assert!(manifest.contains("[[ingresses]]"));
assert!(manifest.contains("{ kind = \"host_api\", api = \"websocket\" }"));
assert!(manifest.contains("[[websocket]]"));
assert!(manifest.contains("host = \"example.com\""));
assert!(manifest.contains("path_prefixes = [\"/socket\"]"));
let source = fs::read_to_string(service_destination.join("src/lib.rs")).unwrap(); let source = fs::read_to_string(service_destination.join("src/lib.rs")).unwrap();
assert!(source.contains("ServiceOutput::websocket_send")); assert!(source.contains("ServiceOutput::websocket_send"));
assert!(!source.contains("recv(timeout")); assert!(!source.contains("recv(timeout"));

View File

@ -352,6 +352,13 @@ A minimal manifest shape is:
```toml ```toml
surfaces = ["tool", "service", "ingress"] surfaces = ["tool", "service", "ingress"]
permissions = [
{ kind = "surface", surface = "service" },
{ kind = "service", name = "example_service" },
{ kind = "surface", surface = "ingress" },
{ kind = "ingress", name = "example_ws" },
{ kind = "host_api", api = "websocket" },
]
[runtime] [runtime]
kind = "wasm-component" kind = "wasm-component"
@ -369,8 +376,15 @@ description = "Handles host-owned WebSocket text events."
event_kinds = ["websocket_text", "websocket_close", "websocket_error"] event_kinds = ["websocket_text", "websocket_close", "websocket_error"]
sources = ["websocket:wss://gateway.example.com/gateway"] sources = ["websocket:wss://gateway.example.com/gateway"]
input_schema = { type = "object" } input_schema = { type = "object" }
[[websocket]]
scheme = "wss"
host = "gateway.example.com"
path_prefixes = ["/gateway"]
``` ```
The `host_api.websocket` permission and `[[websocket]]` target are required for `websocket_send` output commands. Runtime enablement grants must explicitly allow the same WebSocket target; the manifest declaration alone is not authority.
Generate a fuller example with `yoi plugin new rust-component-service ./my-service-plugin`. Generate a fuller example with `yoi plugin new rust-component-service ./my-service-plugin`.
## `websocket` host API ## `websocket` host API

View File

@ -5,6 +5,6 @@ This template targets the Component Model-only runtime (`runtime.kind = "wasm-co
It demonstrates both authoring surfaces supported by a shared Plugin instance: It demonstrates both authoring surfaces supported by a shared Plugin instance:
- `example_echo` is an ordinary request/response Tool handler. - `example_echo` is an ordinary request/response Tool handler.
- `example_ws` is a Service ingress handler. The host owns WebSocket receive/reconnect work and dispatches bounded `websocket_text` events into `handle_ingress`. The guest replies by returning a `websocket_send` output command in `ServiceOutput`; do not run a guest-side `recv(timeout)` polling loop. - `example_ws` is a Service ingress handler. The host owns WebSocket receive/reconnect work and dispatches bounded `websocket_text` events into `handle_ingress`. The guest replies by returning a `websocket_send` output command in `ServiceOutput`; do not run a guest-side `recv(timeout)` polling loop. The manifest declares `host_api.websocket` plus a matching `[[websocket]]` target for the example URL. Enablement grants must explicitly allow the same WebSocket target before the host will send output commands.
Build with `cargo component build --release` (or the project-specific build command used by your Plugin packaging flow), then run `yoi plugin check` / `yoi plugin pack` from the generated Plugin directory. Build with `cargo component build --release` (or the project-specific build command used by your Plugin packaging flow), then run `yoi plugin check` / `yoi plugin pack` from the generated Plugin directory.

View File

@ -11,6 +11,7 @@ permissions = [
{ kind = "service", name = "example_service" }, { kind = "service", name = "example_service" },
{ kind = "surface", surface = "ingress" }, { kind = "surface", surface = "ingress" },
{ kind = "ingress", name = "example_ws" }, { kind = "ingress", name = "example_ws" },
{ kind = "host_api", api = "websocket" },
] ]
[runtime] [runtime]
@ -34,3 +35,8 @@ description = "Handles host-owned WebSocket text events and returns websocket_se
event_kinds = ["websocket_text"] event_kinds = ["websocket_text"]
sources = ["websocket:wss://example.com/socket"] sources = ["websocket:wss://example.com/socket"]
input_schema = { type = "object" } input_schema = { type = "object" }
[[websocket]]
scheme = "wss"
host = "example.com"
path_prefixes = ["/socket"]