Mountain/Vine/Server/Notification/WebviewLifecycle.rs
1//! Cocoon → Mountain `webview.setTitle` / `webview.setIconPath` /
2//! `webview.setHtml` / `webview.postMessage` / `webview.updateView` /
3//! `webview.viewState` / `webview.dispose` notifications. Shared atom
4//! because the methods all map to the same suffix-split pattern; keeping
5//! them in one file avoids near-identical 5-line files while still
6//! pinning the handler to a discoverable filename.
7//!
8//! Wire-shape canonicalisation MIRRORS `Track/Effect/CreateEffectForRequest/
9//! Webview.rs` so notification-path payloads land on the same named-key
10//! shapes as the request path. SkyBridge's listeners read `Payload.viewId`,
11//! `Payload.html`, `Payload.message` etc. directly; without this Cocoon's
12//! legacy positional `[Handle, Value]` notifications would emit a payload
13//! whose only top-level keys are `0`/`1` (array indices), the listener
14//! would early-return on the missing named keys, and the iframe would
15//! stay blank even when the request path canonicalised correctly.
16//!
17//! For per-extension isolation and payload inspection, split this into
18//! per-method atoms (`WebviewSetTitle`, `WebviewSetIconPath`, etc.) when
19//! the divergence is worth it.
20
21use serde_json::{Map, Value, json};
22use tauri::Emitter;
23
24use crate::{Vine::Server::MountainVinegRPCService::MountainVinegRPCService, dev_log};
25
26pub async fn WebviewLifecycle(Service:&MountainVinegRPCService, MethodName:&str, Parameter:&Value) {
27 // Suffix mapping: stock VS Code wire methods are camelCase
28 // (`webview.setHtml`, `webview.setIconPath`), but Sky's canonical
29 // channel registry (`Common/Source/IPC/SkyEvent.rs`) standardises
30 // kebab-case for set-html (`sky://webview/set-html`) since the
31 // `setWebviewHtml` typed-RPC and `Window.rs::SetHtml` request both
32 // emit kebab. Translate `setHtml` and `postMessage` here so every
33 // producer of those wire shapes lands on the same Sky channel; other
34 // suffixes pass through camelCase (Sky listeners use camel for
35 // `setTitle` / `setIconPath`).
36 let RawSuffix = &MethodName["webview.".len()..];
37
38 let Suffix = match RawSuffix {
39 "setHtml" => "set-html",
40
41 "postMessage" => "post-message",
42
43 Other => Other,
44 };
45
46 let EventName = format!("sky://webview/{}", Suffix);
47
48 // Canonicalise payload shapes to the same named-key form the request
49 // path produces. Three observed cases (matching `Webview.rs`):
50 // 1. Object: pass through (Cocoon's modern named-key
51 // `SendToMountain("webview.setHtml", { handle, viewId, html })`).
52 // 2. Array `[<obj>]`: unwrap.
53 // 3. Array `[Handle, Second?, ...]`: positional - preserve the original args
54 // slot AND project to the per-method named alias so listeners that read
55 // `Payload.html` / `Payload.viewId` / `Payload.message` etc. stay
56 // decoupled from the wire shape.
57 let CanonicalPayload:Value = if Parameter.is_object() {
58 Parameter.clone()
59 } else if let Some(First) = Parameter.get(0) {
60 if First.is_object() {
61 First.clone()
62 } else {
63 let mut Object = Map::new();
64
65 Object.insert("method".to_string(), Value::String(MethodName.to_string()));
66
67 Object.insert("handle".to_string(), First.clone());
68
69 Object.insert("args".to_string(), Parameter.clone());
70
71 if let Some(Second) = Parameter.get(1) {
72 let Alias = match MethodName {
73 "webview.setHtml" => "html",
74
75 "webview.postMessage" => "message",
76
77 "webview.registerView" | "webview.unregisterView" => "viewId",
78
79 "webview.registerCustomEditor" | "webview.unregisterCustomEditor" | "webview.create" => "viewType",
80
81 _ => "value",
82 };
83
84 Object.insert(Alias.to_string(), Second.clone());
85
86 if MethodName == "webview.create" {
87 if let Some(Third) = Parameter.get(2) {
88 Object.insert("title".to_string(), Third.clone());
89 }
90 }
91 }
92
93 Value::Object(Object)
94 }
95 } else {
96 json!({
97 "method": MethodName,
98 "handle": Parameter.clone(),
99 })
100 };
101
102 if let Err(Error) = Service.ApplicationHandle().emit(&EventName, &CanonicalPayload) {
103 dev_log!("grpc", "warn: [MountainVinegRPCService] {} emit failed: {}", EventName, Error);
104 }
105}