utils.rs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. use crate::{
  2. api::{assets::Assets, config::WindowUrl},
  3. app::Icon,
  4. ApplicationExt, WebviewBuilderExt,
  5. };
  6. use super::{
  7. webview::{CustomProtocol, WebviewBuilderExtPrivate, WebviewRpcHandler},
  8. App, Context, InvokeMessage, InvokePayload, PageLoadPayload, RpcRequest, Webview, WebviewManager,
  9. };
  10. use serde_json::Value as JsonValue;
  11. use std::{
  12. borrow::Cow,
  13. sync::{Arc, Mutex},
  14. };
  15. // setup content for dev-server
  16. #[cfg(dev)]
  17. pub(super) fn get_url(context: &Context) -> String {
  18. let config = &context.config;
  19. if config.build.dev_path.starts_with("http") {
  20. config.build.dev_path.clone()
  21. } else {
  22. let path = "index.html";
  23. format!(
  24. "data:text/html;base64,{}",
  25. base64::encode(
  26. context
  27. .assets
  28. .get(&path)
  29. .ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
  30. .map(Cow::into_owned)
  31. .expect("Unable to find `index.html` under your devPath folder")
  32. )
  33. )
  34. }
  35. }
  36. #[cfg(custom_protocol)]
  37. pub(super) fn get_url(context: &Context) -> String {
  38. // Custom protocol doesn't require any setup, so just return URL
  39. format!("tauri://{}", context.config.tauri.bundle.identifier)
  40. }
  41. // spawn an updater process.
  42. #[cfg(feature = "updater")]
  43. #[allow(dead_code)]
  44. pub(super) fn spawn_updater() {
  45. std::thread::spawn(|| {
  46. tauri_api::command::spawn_relative_command(
  47. "updater".to_string(),
  48. Vec::new(),
  49. std::process::Stdio::inherit(),
  50. )
  51. .expect("Unable to spawn relative command");
  52. });
  53. }
  54. pub(super) fn initialization_script(
  55. plugin_initialization_script: &str,
  56. with_global_tauri: bool,
  57. ) -> String {
  58. format!(
  59. r#"
  60. {bundle_script}
  61. {core_script}
  62. {event_initialization_script}
  63. if (window.rpc) {{
  64. window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
  65. }} else {{
  66. window.addEventListener('DOMContentLoaded', function () {{
  67. window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
  68. }})
  69. }}
  70. {plugin_initialization_script}
  71. "#,
  72. core_script = include_str!("../../scripts/core.js"),
  73. bundle_script = if with_global_tauri {
  74. include_str!("../../scripts/bundle.js")
  75. } else {
  76. ""
  77. },
  78. event_initialization_script = event_initialization_script(),
  79. plugin_initialization_script = plugin_initialization_script
  80. )
  81. }
  82. fn event_initialization_script() -> String {
  83. return format!(
  84. "
  85. window['{queue}'] = [];
  86. window['{fn}'] = function (eventData, salt, ignoreQueue) {{
  87. const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
  88. if (!ignoreQueue && listeners.length === 0) {{
  89. window['{queue}'].push({{
  90. eventData: eventData,
  91. salt: salt
  92. }})
  93. }}
  94. if (listeners.length > 0) {{
  95. window.__TAURI__.invoke('tauri', {{
  96. __tauriModule: 'Internal',
  97. message: {{
  98. cmd: 'validateSalt',
  99. salt: salt
  100. }}
  101. }}).then(function (flag) {{
  102. if (flag) {{
  103. for (let i = listeners.length - 1; i >= 0; i--) {{
  104. const listener = listeners[i]
  105. eventData.id = listener.id
  106. listener.handler(eventData)
  107. }}
  108. }}
  109. }})
  110. }}
  111. }}
  112. ",
  113. fn = crate::event::emit_function_name(),
  114. queue = crate::event::event_queue_object_name(),
  115. listeners = crate::event::event_listeners_object_name()
  116. );
  117. }
  118. pub(super) type BuiltWebview<A> = (
  119. <A as ApplicationExt>::WebviewBuilder,
  120. Option<WebviewRpcHandler<<A as ApplicationExt>::Dispatcher>>,
  121. Option<CustomProtocol>,
  122. );
  123. // build the webview.
  124. pub(super) fn build_webview<A: ApplicationExt + 'static>(
  125. application: Arc<Mutex<App<A>>>,
  126. webview: Webview<A>,
  127. webview_manager: &WebviewManager<A>,
  128. content_url: &str,
  129. window_labels: &[String],
  130. plugin_initialization_script: &str,
  131. context: &Context,
  132. ) -> crate::Result<BuiltWebview<A>> {
  133. let webview_url = match &webview.url {
  134. WindowUrl::App => content_url.to_string(),
  135. WindowUrl::Custom(url) => url.to_string(),
  136. };
  137. let is_local = match webview.url {
  138. WindowUrl::App => true,
  139. WindowUrl::Custom(url) => &url[0..8] == "tauri://",
  140. };
  141. let (webview_builder, rpc_handler, custom_protocol) = if is_local {
  142. let mut webview_builder = webview.builder.url(webview_url)
  143. .initialization_script(&initialization_script(plugin_initialization_script, context.config.build.with_global_tauri))
  144. .initialization_script(&format!(
  145. r#"
  146. window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
  147. window.__TAURI__.__currentWindow = {{ label: "{current_window_label}" }}
  148. "#,
  149. window_labels_array =
  150. serde_json::to_string(&window_labels).unwrap(),
  151. current_window_label = webview.label,
  152. ));
  153. if !webview_builder.has_icon() {
  154. if let Some(default_window_icon) = &context.default_window_icon {
  155. webview_builder = webview_builder.icon(Icon::Raw(default_window_icon.to_vec()))?;
  156. }
  157. }
  158. let webview_manager_ = webview_manager.clone();
  159. let rpc_handler: Box<dyn Fn(<A as ApplicationExt>::Dispatcher, RpcRequest) + Send> =
  160. Box::new(move |_, request: RpcRequest| {
  161. let command = request.command.clone();
  162. let arg = request
  163. .params
  164. .unwrap()
  165. .as_array_mut()
  166. .unwrap()
  167. .first_mut()
  168. .unwrap_or(&mut JsonValue::Null)
  169. .take();
  170. let webview_manager = webview_manager_.clone();
  171. match serde_json::from_value::<InvokePayload>(arg) {
  172. Ok(message) => {
  173. let _ = on_message(application.clone(), webview_manager, command, message);
  174. }
  175. Err(e) => {
  176. if let Ok(dispatcher) = webview_manager.current_webview() {
  177. let error: crate::Error = e.into();
  178. let _ = dispatcher.eval(&format!(
  179. r#"console.error({})"#,
  180. JsonValue::String(error.to_string())
  181. ));
  182. }
  183. }
  184. }
  185. });
  186. let bundle_identifier = context.config.tauri.bundle.identifier.clone();
  187. #[cfg(debug_assertions)]
  188. let dist_dir = std::path::PathBuf::from(context.config.build.dist_dir.clone());
  189. #[cfg(not(debug_assertions))]
  190. let assets = context.assets;
  191. let custom_protocol = CustomProtocol {
  192. name: "tauri".into(),
  193. handler: Box::new(move |path| {
  194. let mut path = path
  195. .to_string()
  196. .replace(&format!("tauri://{}", bundle_identifier), "");
  197. if path.ends_with('/') {
  198. path.pop();
  199. }
  200. let path = if path.is_empty() {
  201. // if the url is `tauri://${appId}`, we should load `index.html`
  202. "index.html".to_string()
  203. } else {
  204. // skip leading `/`
  205. path.chars().skip(1).collect::<String>()
  206. };
  207. // In development builds, resolve, read and directly serve assets in the configured dist folder.
  208. #[cfg(debug_assertions)]
  209. {
  210. dist_dir
  211. .canonicalize()
  212. .or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
  213. .and_then(|pathbuf| {
  214. pathbuf
  215. .join(path.clone())
  216. .canonicalize()
  217. .or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
  218. .and_then(|pathbuf| {
  219. if pathbuf.is_file() && pathbuf.starts_with(&dist_dir) {
  220. match std::fs::read(pathbuf) {
  221. Ok(asset) => return Ok(asset),
  222. Err(e) => {
  223. #[cfg(debug_assertions)]
  224. eprintln!("Error reading asset from dist: {:?}", e); // TODO log::error!
  225. }
  226. }
  227. }
  228. Err(crate::Error::AssetNotFound(path))
  229. })
  230. })
  231. }
  232. // In release builds, fetch + serve decompressed embedded assets.
  233. #[cfg(not(debug_assertions))]
  234. {
  235. assets
  236. .get(&path)
  237. .ok_or(crate::Error::AssetNotFound(path))
  238. .map(Cow::into_owned)
  239. }
  240. }),
  241. };
  242. (webview_builder, Some(rpc_handler), Some(custom_protocol))
  243. } else {
  244. (webview.builder.url(webview_url), None, None)
  245. };
  246. Ok((webview_builder, rpc_handler, custom_protocol))
  247. }
  248. fn on_message<A: ApplicationExt + 'static>(
  249. application: Arc<Mutex<App<A>>>,
  250. webview_manager: WebviewManager<A>,
  251. command: String,
  252. payload: InvokePayload,
  253. ) -> crate::Result<()> {
  254. let message = InvokeMessage::new(webview_manager.clone(), command.to_string(), payload);
  255. if &command == "__initialized" {
  256. let payload: PageLoadPayload = serde_json::from_value(message.payload())?;
  257. application
  258. .lock()
  259. .unwrap()
  260. .run_on_page_load(&webview_manager, payload.clone());
  261. crate::plugin::on_page_load(A::plugin_store(), &webview_manager, payload);
  262. } else if let Some(module) = &message.payload.tauri_module {
  263. let module = module.to_string();
  264. crate::endpoints::handle(
  265. &webview_manager,
  266. module,
  267. message,
  268. &application.lock().unwrap().context,
  269. );
  270. } else if command.starts_with("plugin:") {
  271. crate::plugin::extend_api(A::plugin_store(), &webview_manager, command, message);
  272. } else {
  273. application
  274. .lock()
  275. .unwrap()
  276. .run_invoke_handler(&webview_manager, message);
  277. }
  278. Ok(())
  279. }
  280. #[cfg(test)]
  281. mod test {
  282. use crate::{generate_context, Context};
  283. #[test]
  284. fn check_get_url() {
  285. let context = generate_context!("test/fixture/src-tauri/tauri.conf.json");
  286. let context = Context::new(context);
  287. let res = super::get_url(&context);
  288. #[cfg(custom_protocol)]
  289. assert!(res == "tauri://studio.tauri.example");
  290. #[cfg(dev)]
  291. {
  292. let config = &context.config;
  293. assert_eq!(res, config.build.dev_path);
  294. }
  295. }
  296. }