Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
Webview.rs

1//! # Webview Effect (CreateEffectForRequest)
2//!
3//! Effect constructors for webview-related RPC methods from the Cocoon
4//! extension host. Maps webview method names (e.g. `webview.create`,
5//! `$webview:setHtml`) to Sky event channels (e.g. `sky://webview/create`,
6//! `sky://webview/set-html`).
7//!
8//! ## Methods handled
9//!
10//! | Method | Sky Event Channel |
11//! |---|---|
12//! | `$webview:create` / `webview.create` | `sky://webview/create` |
13//! | `webview.setHtml` | `sky://webview/set-html` |
14//! | `webview.setOptions` | `sky://webview/set-options` |
15//! | `webview.postMessage` | `sky://webview/post-message` |
16//! | `webview.reveal` | `sky://webview/reveal` |
17//! | `webview.dispose` | `sky://webview/dispose` |
18//! | `webview.registerView` | `sky://webview/register-view` |
19//! | `webview.unregisterView` | `sky://webview/unregister-view` |
20//! | `webview.registerCustomEditor` | `sky://webview/register-custom-editor` |
21//! | `webview.unregisterCustomEditor` | `sky://webview/unregister-custom-editor` |
22//! | `$resolveCustomEditor` | Direct call to `CustomEditorProvider` trait |
23
24use std::sync::Arc;
25
26use CommonLibrary::{CustomEditor::CustomEditorProvider::CustomEditorProvider, Environment::Requires::Requires};
27use serde_json::{Value, json};
28use tauri::Runtime;
29use url::Url;
30
31use crate::{
32	IPC::SkyEmit::LogSkyEmit,
33	RunTime::ApplicationRunTime::ApplicationRunTime,
34	Track::Effect::{
35		CreateEffectForRequest::Utilities::Params::{str_at, string_at},
36		MappedEffectType::MappedEffect,
37	},
38	dev_log,
39};
40
41pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
42	match MethodName {
43		"$webview:create"
44		| "webview.create"
45		| "webview.setHtml"
46		| "webview.setOptions"
47		| "webview.postMessage"
48		| "webview.reveal"
49		| "webview.dispose"
50		| "webview.registerView"
51		| "webview.unregisterView"
52		| "webview.registerCustomEditor"
53		| "webview.unregisterCustomEditor" => {
54			dev_log!("ipc", "[WebviewEffect] dispatch-enter method={}", MethodName);
55
56			let Method = MethodName.to_string();
57
58			crate::effect!(run_time, {
59				let Method = Method.clone();
60
61				let RawSuffix = Method.trim_start_matches("$webview:").trim_start_matches("webview.");
62
63				let Suffix:&str = match RawSuffix {
64					"setHtml" => "set-html",
65					"postMessage" => "postMessage",
66					Other => Other,
67				};
68
69				let Payload:Value = if Parameters.is_object() {
70					Parameters.clone()
71				} else if let Some(First) = Parameters.get(0) {
72					if First.is_object() {
73						First.clone()
74					} else {
75						let mut Object = serde_json::Map::new();
76
77						Object.insert("method".to_string(), Value::String(Method.clone()));
78
79						Object.insert("handle".to_string(), First.clone());
80
81						Object.insert("args".to_string(), Parameters.clone());
82
83						if let Some(Second) = Parameters.get(1) {
84							let Alias = match Method.as_str() {
85								"webview.setHtml" => "html",
86								"webview.postMessage" => "message",
87								"webview.registerView" | "webview.unregisterView" => "viewId",
88								"webview.registerCustomEditor"
89								| "webview.unregisterCustomEditor"
90								| "webview.create" => "viewType",
91								_ => "value",
92							};
93
94							Object.insert(Alias.to_string(), Second.clone());
95
96							if Method.as_str() == "webview.create" {
97								if let Some(Third) = Parameters.get(2) {
98									Object.insert("title".to_string(), Third.clone());
99								}
100							}
101						}
102
103						Value::Object(Object)
104					}
105				} else {
106					json!({
107						"method": Method,
108						"handle": Parameters.clone(),
109					})
110				};
111
112				let EventName = format!("sky://webview/{}", Suffix);
113
114				if let Err(Error) = LogSkyEmit(&run_time.Environment.ApplicationHandle, &EventName, &Payload) {
115					dev_log!("ipc", "warn: [WebviewEffect] emit {} failed: {}", EventName, Error);
116				}
117
118				Ok(json!(null))
119			})
120		},
121
122		"$resolveCustomEditor" => {
123			crate::effect!(run_time, {
124				let provider:Arc<dyn CustomEditorProvider> = run_time.Environment.Require();
125
126				let view_type = string_at(&Parameters, 0);
127
128				let resource_uri_str = str_at(&Parameters, 1);
129
130				// Do not substitute a fallback path for a missing
131				// or malformed URI. A silent swap to
132				// `file:///tmp/test.txt` would:
133				//   - create that file on disk on every bad call,
134				//   - return success to Cocoon so the extension never receives an error,
135				//   - make the log undiagnosable (every failure shows the same sentinel path).
136				// Return Err instead so the grpc layer logs the
137				// real caller input.
138				if resource_uri_str.is_empty() {
139					dev_log!(
140						"grpc",
141						"warn: [$resolveCustomEditor] empty resource URI view_type={}",
142						view_type
143					);
144
145					return Err(format!("$resolveCustomEditor: empty resource URI for view_type={}", view_type));
146				}
147
148				let resource_uri = match Url::parse(resource_uri_str) {
149					Ok(u) => u,
150					Err(parse_err) => {
151						dev_log!(
152							"grpc",
153							"warn: [$resolveCustomEditor] invalid URI uri={} err={} view_type={}",
154							resource_uri_str,
155							parse_err,
156							view_type
157						);
158
159						return Err(format!(
160							"$resolveCustomEditor: invalid resource URI '{}': {}",
161							resource_uri_str, parse_err
162						));
163					},
164				};
165
166				let webview_handle = string_at(&Parameters, 2);
167
168				if webview_handle.is_empty() {
169					dev_log!(
170						"grpc",
171						"warn: [$resolveCustomEditor] empty webview handle uri={} view_type={}",
172						resource_uri_str,
173						view_type
174					);
175
176					return Err(format!(
177						"$resolveCustomEditor: empty webview handle for view_type={} uri={}",
178						view_type, resource_uri_str
179					));
180				}
181
182				provider
183					.ResolveCustomEditor(view_type, resource_uri, webview_handle)
184					.await
185					.map(|_| json!(null))
186					.map_err(|e| e.to_string())
187			})
188		},
189
190		_ => None,
191	}
192}