Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
Webview.rs

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