From 4b54cc156496bf7f102785fc631e7ba5ebb80966 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 11 May 2020 17:03:58 -0300 Subject: [PATCH] feat(tauri) add httpRequest API (#589) --- cli/tauri.js/api/http.js | 160 ++++++++++++++++++ cli/tauri.js/templates/tauri.js | 58 +++++-- tauri-api/Cargo.toml | 3 + tauri-api/src/http.rs | 147 ++++++++++++++++ tauri-api/src/lib.rs | 5 + tauri/examples/communication/dist/http.js | 23 +++ tauri/examples/communication/dist/index.html | 14 ++ .../communication/dist/index.tauri.html | 55 ++++-- .../communication/src-tauri/src/cmd.rs | 14 +- .../communication/src-tauri/src/main.rs | 19 ++- tauri/src/app.rs | 11 +- tauri/src/app/runner.rs | 29 ++-- tauri/src/endpoints.rs | 18 +- tauri/src/endpoints/cmd.rs | 13 +- tauri/src/endpoints/dialog.rs | 6 +- tauri/src/endpoints/file_system.rs | 34 ++-- tauri/src/endpoints/http.rs | 27 +++ tauri/src/lib.rs | 4 + 18 files changed, 565 insertions(+), 75 deletions(-) create mode 100644 cli/tauri.js/api/http.js create mode 100644 tauri-api/src/http.rs create mode 100644 tauri/examples/communication/dist/http.js create mode 100644 tauri/src/endpoints/http.rs diff --git a/cli/tauri.js/api/http.js b/cli/tauri.js/api/http.js new file mode 100644 index 000000000..a739fe3fe --- /dev/null +++ b/cli/tauri.js/api/http.js @@ -0,0 +1,160 @@ +import tauri from './tauri' + +/** + * @typedef {number} ResponseType + */ +/** + * @enum {ResponseType} + */ +const ResponseType = { + JSON: 1, + Text: 2, + Binary: 3 +} + +/** + * @typedef {number} BodyType + */ +/** + * @enum {BodyType} + */ +const BodyType = { + Form: 1, + File: 2, + Auto: 3 +} + +/** + * @typedef {Object} HttpOptions + * @property {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE + * @property {String} options.url the request URL + * @property {Object} [options.headers] the request headers + * @property {Object} [options.propertys] the request query propertys + * @property {Object|String|Binary} [options.body] the request body + * @property {Boolean} followRedirects whether to follow redirects or not + * @property {Number} maxRedirections max number of redirections + * @property {Number} connectTimeout request connect timeout + * @property {Number} readTimeout request read timeout + * @property {Number} timeout request timeout + * @property {Boolean} allowCompression + * @property {ResponseType} [responseType=1] response type + * @property {BodyType} [bodyType=3] body type +*/ + +/** + * makes a HTTP request + * + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function request (options) { + return tauri.httpRequest(options) +} + +/** + * makes a GET request + * + * @param {String} url request URL + * @param {String|Object|Binary} body request body + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function get (url, options = {}) { + return request({ + method: 'GET', + url, + ...options + }) +} + +/** + * makes a POST request + * + * @param {String} url request URL + * @param {String|Object|Binary} body request body + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function post (url, body = void 0, options = {}) { + return request({ + method: 'POST', + url, + body, + ...options + }) +} + +/** + * makes a PUT request + * + * @param {String} url request URL + * @param {String|Object|Binary} body request body + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function put (url, body = void 0, options = {}) { + return request({ + method: 'PUT', + url, + body, + ...options + }) +} + +/** + * makes a PATCH request + * + * @param {String} url request URL + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function patch (url, options = {}) { + return request({ + method: 'PATCH', + url, + ...options + }) +} + +/** + * makes a DELETE request + * + * @param {String} url request URL + * @param {HttpOptions} options request options + * + * @return {Promise} promise resolving to the response + */ +function deleteRequest (url, options = {}) { + return request({ + method: 'DELETE', + url, + ...options + }) +} + +export { + request, + get, + post, + put, + patch, + deleteRequest, + ResponseType, + BodyType +} + +export default { + request, + get, + post, + put, + patch, + delete: deleteRequest, + ResponseType, + BodyType +} diff --git a/cli/tauri.js/templates/tauri.js b/cli/tauri.js/templates/tauri.js index 4bb4e387d..3065c8538 100644 --- a/cli/tauri.js/templates/tauri.js +++ b/cli/tauri.js/templates/tauri.js @@ -100,6 +100,9 @@ var Dir = { } <% if (ctx.dev) { %> +function camelToKebab (string) { + return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase() +} /** * @name return __whitelistWarning * @description Present a stylish warning to the developer that their API @@ -108,7 +111,7 @@ var Dir = { * @private */ var __whitelistWarning = function (func) { - console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ') + console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ') return __reject() } <% } %> @@ -467,8 +470,8 @@ window.tauri = { <% if (tauri.whitelist.renameFile === true || tauri.whitelist.all === true) { %> return this.promisified({ cmd: 'renameFile', - old_path: oldPath, - new_path: newPath, + oldPath: oldPath, + newPath: newPath, options: options }); <% } else { %> @@ -611,13 +614,48 @@ window.tauri = { <% } %> }, -loadAsset: function loadAsset(assetName, assetType) { - return this.promisified({ - cmd: 'loadAsset', - asset: assetName, - asset_type: assetType || 'unknown' - }) -} + <% if (ctx.dev) { %> + /** + * @name httpRequest + * @description Makes an HTTP request + * @param {Object} options + * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE + * @param {String} options.url the request URL + * @param {Object} [options.headers] the request headers + * @param {Object} [options.params] the request query params + * @param {Object|String|Binary} [options.body] the request body + * @param {Boolean} followRedirects whether to follow redirects or not + * @param {Number} maxRedirections max number of redirections + * @param {Number} connectTimeout request connect timeout + * @param {Number} readTimeout request read timeout + * @param {Number} timeout request timeout + * @param {Boolean} allowCompression + * @param {Number} [responseType=1] 1 - JSON, 2 - Text, 3 - Binary + * @param {Number} [bodyType=3] 1 - Form, 2 - File, 3 - Auto + * @returns {Promise} + */ + <% } %> + httpRequest: function httpRequest(options) { + <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %> + return this.promisified({ + cmd: 'httpRequest', + options: options + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('httpRequest') + <% } %> + return __reject() + <% } %> + }, + + loadAsset: function loadAsset(assetName, assetType) { + return this.promisified({ + cmd: 'loadAsset', + asset: assetName, + assetType: assetType || 'unknown' + }) + } }; // init tauri API diff --git a/tauri-api/Cargo.toml b/tauri-api/Cargo.toml index 49568873a..0111311e6 100644 --- a/tauri-api/Cargo.toml +++ b/tauri-api/Cargo.toml @@ -11,6 +11,7 @@ exclude = ["test/fixture/**"] [dependencies] serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" serde_repr = "0.1" dirs = "2.0.2" ignore = "0.4.11" @@ -24,6 +25,8 @@ flate2 = "1" error-chain = "0.12" rand = "0.7" nfd = "0.0.4" +attohttpc = {version = "0.13.0", features=["json", "form" ]} +http = "0.2" tauri-utils = {version = "0.5", path = "../tauri-utils"} [dev-dependencies] diff --git a/tauri-api/src/http.rs b/tauri-api/src/http.rs new file mode 100644 index 000000000..65fc462ab --- /dev/null +++ b/tauri-api/src/http.rs @@ -0,0 +1,147 @@ +use std::time::Duration; +use std::fs::File; +use std::collections::HashMap; +use attohttpc::{RequestBuilder, Method}; +use http::header::HeaderName; +use serde_json::Value; +use serde::Deserialize; +use serde_repr::{Serialize_repr, Deserialize_repr}; + +#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)] +#[repr(u16)] +/// The request's body type +pub enum BodyType { + /// Send request body as application/x-www-form-urlencoded + Form = 1, + /// Send request body (which is a path to a file) as application/octet-stream + File, + /// Detects the body type automatically + /// - if the body is a byte array, send is as bytes (application/octet-stream) + /// - if the body is an object or array, send it as JSON (application/json with UTF-8 charset) + /// - if the body is a string, send it as text (text/plain with UTF-8 charset) + Auto +} + +#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)] +#[repr(u16)] +/// The request's response type +pub enum ResponseType { + /// Read the response as JSON + Json = 1, + /// Read the response as text + Text, + /// Read the response as binary + Binary +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +/// The configuration object of an HTTP request +pub struct HttpRequestOptions { + /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE) + pub method: String, + /// The request URL + pub url: String, + /// The request query params + pub params: Option>, + /// The request headers + pub headers: Option>, + /// The request body + pub body: Option, + /// Whether to follow redirects or not + pub follow_redirects: Option, + /// Max number of redirections to follow + pub max_redirections: Option, + /// Connect timeout for the request + pub connect_timeout: Option, + /// Read timeout for the request + pub read_timeout: Option, + /// Timeout for the whole request + pub timeout: Option, + /// Whether the request will announce that it accepts compression + pub allow_compression: Option, + /// The body type (defaults to Auto) + pub body_type: Option, + /// The response type (defaults to Json) + pub response_type: Option, +} + +/// Executes an HTTP request +/// +/// The response will be transformed to String, +/// If reading the response as binary, the byte array will be serialized using serde_json +pub fn make_request(options: HttpRequestOptions) -> crate::Result { + let method = Method::from_bytes(options.method.to_uppercase().as_bytes())?; + let mut builder = RequestBuilder::new(method, options.url); + if let Some(params) = options.params { + for (param, param_value) in params.iter() { + builder = builder.param(param, param_value); + } + } + + if let Some(headers) = options.headers { + for (header, header_value) in headers.iter() { + builder = builder.header(HeaderName::from_bytes(header.as_bytes())?, format!("{}", header_value)); + } + } + + if let Some(follow_redirects) = options.follow_redirects { + builder = builder.follow_redirects(follow_redirects); + } + if let Some(max_redirections) = options.max_redirections { + builder = builder.max_redirections(max_redirections); + } + if let Some(connect_timeout) = options.connect_timeout { + builder = builder.connect_timeout(Duration::from_secs(connect_timeout)); + } + if let Some(read_timeout) = options.read_timeout { + builder = builder.read_timeout(Duration::from_secs(read_timeout)); + } + if let Some(timeout) = options.timeout { + builder = builder.timeout(Duration::from_secs(timeout)); + } + if let Some(allow_compression) = options.allow_compression { + builder = builder.allow_compression(allow_compression); + } + builder = builder.danger_accept_invalid_certs(true).danger_accept_invalid_hostnames(true); + + let response = if let Some(body) = options.body { + match options.body_type.unwrap_or(BodyType::Auto) { + BodyType::Form => builder.form(&body)?.send(), + BodyType::File => { + if let Some(path) = body.as_str() { + builder.file(File::open(path)?).send() + } else { + return Err(crate::Error::from("Body must be the path to the file")); + } + }, + BodyType::Auto => { + if body.is_object() { + builder.json(&body)?.send() + } else if let Some(text) = body.as_str() { + builder.text(&text).send() + } else if body.is_array() { + let u: Result, _> = serde_json::from_value(body.clone()); + match u { + Ok(vec) => builder.bytes(&vec).send(), + Err(_) => builder.json(&body)?.send() + } + } else { + builder.send() + } + } + } + } else { builder.send() }; + + let response = response?; + if response.is_success() { + let response_data = match options.response_type.unwrap_or(ResponseType::Json) { + ResponseType::Json => response.json()?, + ResponseType::Text => response.text()?, + ResponseType::Binary => serde_json::to_string(&response.bytes()?)? + }; + Ok(response_data) + } else { + Err(crate::Error::from(response.status().as_str())) + } +} \ No newline at end of file diff --git a/tauri-api/src/lib.rs b/tauri-api/src/lib.rs index 3924a57fb..d4c923a6b 100644 --- a/tauri-api/src/lib.rs +++ b/tauri-api/src/lib.rs @@ -11,6 +11,7 @@ pub mod version; pub mod tcp; pub mod dialog; pub mod path; +pub mod http; pub use tauri_utils::*; @@ -22,6 +23,10 @@ error_chain! { ZipError(::zip::result::ZipError); SemVer(::semver::SemVerError); Platform(::tauri_utils::Error); + Json(::serde_json::Error); + Http(::attohttpc::Error); + HttpMethod(::http::method::InvalidMethod); + HttpHeaderName(::http::header::InvalidHeaderName); } errors { Extract(t: String) { diff --git a/tauri/examples/communication/dist/http.js b/tauri/examples/communication/dist/http.js new file mode 100644 index 000000000..3ed91f95a --- /dev/null +++ b/tauri/examples/communication/dist/http.js @@ -0,0 +1,23 @@ +const methodSelect = document.getElementById('request-method') +const requestUrlInput = document.getElementById('request-url') +const requestBodyInput = document.getElementById('request-body') + +document.getElementById('make-request').addEventListener('click', function () { + const method = methodSelect.value || 'GET' + const url = requestUrlInput.value || '' + + const options = { + url: url, + method: method + } + + let body = requestBodyInput.value || '' + if ((body.startsWith('{') && body.endsWith('}')) || (body.startsWith('[') && body.endsWith(']'))) { + body = JSON.parse(body) + } else if (body.startsWith('/') || body.match(/\S:\//g)) { + options.bodyAsFile = true + } + options.body = body + + window.tauri.httpRequest(options).then(registerResponse).catch(registerResponse) +}) \ No newline at end of file diff --git a/tauri/examples/communication/dist/index.html b/tauri/examples/communication/dist/index.html index 9de9fa009..f11e55c0c 100644 --- a/tauri/examples/communication/dist/index.html +++ b/tauri/examples/communication/dist/index.html @@ -41,6 +41,19 @@ +
+ + + + +
+
+ diff --git a/tauri/examples/communication/dist/index.tauri.html b/tauri/examples/communication/dist/index.tauri.html index 6e638964a..dff5ca124 100644 --- a/tauri/examples/communication/dist/index.tauri.html +++ b/tauri/examples/communication/dist/index.tauri.html @@ -100,6 +100,9 @@ var Dir = { } +function camelToKebab (string) { + return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase() +} /** * @name return __whitelistWarning * @description Present a stylish warning to the developer that their API @@ -108,7 +111,7 @@ var Dir = { * @private */ var __whitelistWarning = function (func) { - console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ') + console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ') return __reject() } @@ -417,8 +420,8 @@ window.tauri = { return this.promisified({ cmd: 'renameFile', - old_path: oldPath, - new_path: newPath, + oldPath: oldPath, + newPath: newPath, options: options }); @@ -531,13 +534,43 @@ window.tauri = { }, -loadAsset: function loadAsset(assetName, assetType) { - return this.promisified({ - cmd: 'loadAsset', - asset: assetName, - asset_type: assetType || 'unknown' - }) -} + + /** + * @name httpRequest + * @description Makes an HTTP request + * @param {Object} options + * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE + * @param {String} options.url the request URL + * @param {Object} [options.headers] the request headers + * @param {Object} [options.params] the request query params + * @param {Object|String|Binary} [options.body] the request body + * @param {Boolean} followRedirects whether to follow redirects or not + * @param {Number} maxRedirections max number of redirections + * @param {Number} connectTimeout request connect timeout + * @param {Number} readTimeout request read timeout + * @param {Number} timeout request timeout + * @param {Boolean} allowCompression + * @param {Boolean} bodyAsForm send the body as application/x-www-form-urlencoded + * @param {Boolean} bodyAsFile send the body as a file (body must be the path to the file) + * @returns {Promise} + */ + + httpRequest: function httpRequest(options) { + + return this.promisified({ + cmd: 'httpRequest', + options: options + }); + + }, + + loadAsset: function loadAsset(assetName, assetType) { + return this.promisified({ + cmd: 'loadAsset', + asset: assetName, + assetType: assetType || 'unknown' + }) + } }; // init tauri API @@ -591,4 +624,4 @@ if (document.readyState === 'complete' || document.readyState === 'interactive') __openLinks() }, true) } -
\ No newline at end of file +
\ No newline at end of file diff --git a/tauri/examples/communication/src-tauri/src/cmd.rs b/tauri/examples/communication/src-tauri/src/cmd.rs index 71c08cef3..9b60e632c 100644 --- a/tauri/examples/communication/src-tauri/src/cmd.rs +++ b/tauri/examples/communication/src-tauri/src/cmd.rs @@ -3,12 +3,20 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct RequestBody { id: i32, - name: String + name: String, } #[derive(Deserialize)] #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { - LogOperation { event: String, payload: Option }, - PerformRequest { endpoint: String, body: RequestBody, callback: String, error: String }, + LogOperation { + event: String, + payload: Option, + }, + PerformRequest { + endpoint: String, + body: RequestBody, + callback: String, + error: String, + }, } diff --git a/tauri/examples/communication/src-tauri/src/main.rs b/tauri/examples/communication/src-tauri/src/main.rs index f37b5057c..cd833b36b 100644 --- a/tauri/examples/communication/src-tauri/src/main.rs +++ b/tauri/examples/communication/src-tauri/src/main.rs @@ -9,7 +9,7 @@ use serde::Serialize; #[derive(Serialize)] struct Reply { - data: String + data: String, } fn main() { @@ -32,15 +32,18 @@ fn main() { .invoke_handler(|_webview, arg| { use cmd::Cmd::*; match serde_json::from_str(arg) { - Err(e) => { - Err(e.to_string()) - } + Err(e) => Err(e.to_string()), Ok(command) => { match command { LogOperation { event, payload } => { println!("{} {:?}", event, payload); - }, - PerformRequest { endpoint, body, callback, error } => { + } + PerformRequest { + endpoint, + body, + callback, + error, + } => { // tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function // so you can easily communicate between JS and Rust with promises tauri::execute_promise( @@ -54,9 +57,9 @@ fn main() { Ok("{ key: 'response', value: [{ id: 3 }] }".to_string()) }, callback, - error + error, ) - }, + } } Ok(()) } diff --git a/tauri/src/app.rs b/tauri/src/app.rs index 05f24d1ad..e5568f9ad 100644 --- a/tauri/src/app.rs +++ b/tauri/src/app.rs @@ -16,10 +16,13 @@ impl App { runner::run(&mut self).expect("Failed to build webview"); } - pub(crate) fn run_invoke_handler(&mut self, webview: &mut WebView<'_, ()>, arg: &str) -> Result { + pub(crate) fn run_invoke_handler( + &mut self, + webview: &mut WebView<'_, ()>, + arg: &str, + ) -> Result { if let Some(ref mut invoke_handler) = self.invoke_handler { - invoke_handler(webview, arg) - .map(|_| true) + invoke_handler(webview, arg).map(|_| true) } else { Ok(false) } @@ -40,7 +43,7 @@ impl App { pub struct AppBuilder { invoke_handler: Option, setup: Option, - splashscreen_html: Option + splashscreen_html: Option, } impl AppBuilder { diff --git a/tauri/src/app/runner.rs b/tauri/src/app/runner.rs index 0912bc8ef..a2bf34666 100644 --- a/tauri/src/app/runner.rs +++ b/tauri/src/app/runner.rs @@ -4,9 +4,9 @@ use std::{fs::read_to_string, path::Path, process::Stdio, thread::spawn}; use web_view::{builder, Content, WebView}; use super::App; -use crate::config::{get, Config}; #[cfg(feature = "embedded-server")] use crate::api::tcp::{get_available_port, port_is_available}; +use crate::config::{get, Config}; // Main entry point function for running the Webview pub(crate) fn run(application: &mut App) -> crate::Result<()> { @@ -32,7 +32,12 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> { config, main_content, if application.splashscreen_html().is_some() { - Some(Content::Html(application.splashscreen_html().expect("failed to get splashscreen_html").to_string())) + Some(Content::Html( + application + .splashscreen_html() + .expect("failed to get splashscreen_html") + .to_string(), + )) } else { None }, @@ -153,7 +158,7 @@ fn build_webview( application: &mut App, config: Config, content: Content, - splashscreen_content: Option> + splashscreen_content: Option>, ) -> crate::Result> { let content_clone = match content { Content::Html(ref html) => Content::Html(html.clone()), @@ -201,16 +206,18 @@ fn build_webview( Some(e.replace("'", "\\'")) } else { let handled = handled_by_app.expect("failed to check if the invoke was handled"); - if handled { None } else { Some(tauri_handle_error_str) } + if handled { + None + } else { + Some(tauri_handle_error_str) + } }; } else { handler_error = Some(tauri_handle_error_str); } if let Some(handler_error_message) = handler_error { - webview.eval( - &get_api_error_message(arg, handler_error_message) - )?; + webview.eval(&get_api_error_message(arg, handler_error_message))?; } } } @@ -229,16 +236,16 @@ fn build_webview( if has_splashscreen { // inject the tauri.js entry point webview - .handle() - .dispatch(|_webview| _webview.eval(include_str!(concat!(env!("TAURI_DIR"), "/tauri.js"))))?; + .handle() + .dispatch(|_webview| _webview.eval(include_str!(concat!(env!("TAURI_DIR"), "/tauri.js"))))?; } - + Ok(webview) } fn get_api_error_message(arg: &str, handler_error_message: String) -> String { format!( - r#"console.error('failed to match a command for {}, {}')"#, + r#"console.error('failed to match a command for {}, {}')"#, arg.replace("'", "\\'"), handler_error_message ) diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index 60f9f2b0b..ec051415c 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -1,8 +1,8 @@ mod cmd; -mod salt; -#[allow(dead_code)] -mod file_system; mod dialog; +mod file_system; +mod http; +mod salt; #[cfg(any(feature = "embedded-server", feature = "no-server"))] use std::path::PathBuf; @@ -149,7 +149,7 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra OpenDialog { options, callback, - error + error, } => { dialog::open(webview, options, callback, error); } @@ -161,7 +161,15 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra } => { dialog::save(webview, options, callback, error); } - #[cfg(any(feature = "embedded-server", feature = "no-server"))] + #[cfg(any(feature = "all-api", feature = "http-request"))] + HttpRequest { + options, + callback, + error, + } => { + http::make_request(webview, options, callback, error); + } + #[cfg(any(feature = "embedded-server", feature = "no-server"))] LoadAsset { asset, asset_type, diff --git a/tauri/src/endpoints/cmd.rs b/tauri/src/endpoints/cmd.rs index 6b58e0329..e24a942c7 100644 --- a/tauri/src/endpoints/cmd.rs +++ b/tauri/src/endpoints/cmd.rs @@ -1,5 +1,6 @@ -use serde::Deserialize; use crate::api::path::BaseDirectory; +use serde::Deserialize; +use tauri_api::http::HttpRequestOptions; #[derive(Deserialize)] pub struct DirOperationOptions { @@ -14,6 +15,7 @@ pub struct FileOperationOptions { } #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] pub struct OpenDialogOptions { pub filter: Option, #[serde(default)] @@ -24,6 +26,7 @@ pub struct OpenDialogOptions { } #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SaveDialogOptions { pub filter: Option, pub default_path: Option, @@ -91,6 +94,7 @@ pub enum Cmd { callback: String, error: String, }, + #[serde(rename_all = "camelCase")] #[cfg(any(feature = "all-api", feature = "rename-file"))] RenameFile { old_path: String, @@ -142,6 +146,13 @@ pub enum Cmd { callback: String, error: String, }, + #[cfg(any(feature = "all-api", feature = "http-request"))] + HttpRequest { + options: HttpRequestOptions, + callback: String, + error: String, + }, + #[serde(rename_all = "camelCase")] #[cfg(any(feature = "embedded-server", feature = "no-server"))] LoadAsset { asset: String, diff --git a/tauri/src/endpoints/dialog.rs b/tauri/src/endpoints/dialog.rs index 0b68261ea..cfa774a00 100644 --- a/tauri/src/endpoints/dialog.rs +++ b/tauri/src/endpoints/dialog.rs @@ -1,12 +1,12 @@ -use crate::api::dialog::{select, select_multiple, save_file, pick_folder, Response}; use super::cmd::{OpenDialogOptions, SaveDialogOptions}; +use crate::api::dialog::{pick_folder, save_file, select, select_multiple, Response}; use web_view::WebView; fn map_response(response: Response) -> String { match response { Response::Okay(path) => format!(r#""{}""#, path).replace("\\", "\\\\"), Response::OkayMultiple(paths) => format!("{:?}", paths), - Response::Cancel => panic!("unexpected response type") + Response::Cancel => panic!("unexpected response type"), } } @@ -51,4 +51,4 @@ pub fn save( callback, error, ); -} \ No newline at end of file +} diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index 0067e1b23..fc77f0f20 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/tauri/src/endpoints/file_system.rs @@ -52,14 +52,15 @@ pub fn copy_file( webview, move || { let (src, dest) = match options.and_then(|o| o.dir) { - Some(dir) => { - (resolve_path(source, Some(dir.clone()))?, resolve_path(destination, Some(dir))?) - } - None => (source, destination) + Some(dir) => ( + resolve_path(source, Some(dir.clone()))?, + resolve_path(destination, Some(dir))?, + ), + None => (source, destination), }; fs::copy(src, dest) - .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) - .map(|_| "".to_string()) + .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) + .map(|_| "".to_string()) }, callback, error, @@ -90,7 +91,7 @@ pub fn create_dir( response .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) - .map(|_| "".to_string()) + .map(|_| "".to_string()) }, callback, error, @@ -160,10 +161,11 @@ pub fn rename_file( webview, move || { let (old, new) = match options.and_then(|o| o.dir) { - Some(dir) => { - (resolve_path(old_path, Some(dir.clone()))?, resolve_path(new_path, Some(dir))?) - } - None => (old_path, new_path) + Some(dir) => ( + resolve_path(old_path, Some(dir.clone()))?, + resolve_path(new_path, Some(dir))?, + ), + None => (old_path, new_path), }; fs::rename(old, new) .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) @@ -210,10 +212,7 @@ pub fn read_text_file( move || { file::read_string(resolve_path(path, options.and_then(|o| o.dir))?) .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) - .and_then(|f| { - serde_json::to_string(&f) - .map_err(|err| err.into()) - }) + .and_then(|f| serde_json::to_string(&f).map_err(|err| err.into())) }, callback, error, @@ -232,10 +231,7 @@ pub fn read_binary_file( move || { file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?) .map_err(|e| crate::ErrorKind::FileSystem(e.to_string()).into()) - .and_then(|f| { - serde_json::to_string(&f) - .map_err(|err| err.into()) - }) + .and_then(|f| serde_json::to_string(&f).map_err(|err| err.into())) }, callback, error, diff --git a/tauri/src/endpoints/http.rs b/tauri/src/endpoints/http.rs new file mode 100644 index 000000000..b5bcaa425 --- /dev/null +++ b/tauri/src/endpoints/http.rs @@ -0,0 +1,27 @@ +use tauri_api::http::{make_request as request, HttpRequestOptions, ResponseType}; +use web_view::WebView; + +/// Makes a HTTP request and resolves the response to the webview +pub fn make_request( + webview: &mut WebView<'_, T>, + options: HttpRequestOptions, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + let response_type = options.response_type.clone(); + request(options) + .map_err(|e| crate::ErrorKind::Http(e.to_string()).into()) + .map(|response| { + match response_type.unwrap_or(ResponseType::Json) { + ResponseType::Text => format!(r#""{}""#, response), + _ => response + } + }) + }, + callback, + error, + ); +} diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index 73d956db3..03d0acf47 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -50,6 +50,10 @@ error_chain! { description("FileSystem Error") display("FileSystem Error: '{}'", t) } + Http(t: String) { + description("Http Error") + display("Http Error: '{}'", t) + } } }