mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-03 00:21:34 +03:00
feat(tauri) add httpRequest API (#589)
This commit is contained in:
parent
b9277ea53f
commit
4b54cc1564
160
cli/tauri.js/api/http.js
Normal file
160
cli/tauri.js/api/http.js
Normal file
@ -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<any>} 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<any>} 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<any>} 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<any>} 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<any>} 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<any>} 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
|
||||
}
|
@ -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<any>}
|
||||
*/
|
||||
<% } %>
|
||||
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
|
||||
|
@ -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]
|
||||
|
147
tauri-api/src/http.rs
Normal file
147
tauri-api/src/http.rs
Normal file
@ -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<HashMap<String, Value>>,
|
||||
/// The request headers
|
||||
pub headers: Option<HashMap<String, Value>>,
|
||||
/// The request body
|
||||
pub body: Option<Value>,
|
||||
/// Whether to follow redirects or not
|
||||
pub follow_redirects: Option<bool>,
|
||||
/// Max number of redirections to follow
|
||||
pub max_redirections: Option<u32>,
|
||||
/// Connect timeout for the request
|
||||
pub connect_timeout: Option<u64>,
|
||||
/// Read timeout for the request
|
||||
pub read_timeout: Option<u64>,
|
||||
/// Timeout for the whole request
|
||||
pub timeout: Option<u64>,
|
||||
/// Whether the request will announce that it accepts compression
|
||||
pub allow_compression: Option<bool>,
|
||||
/// The body type (defaults to Auto)
|
||||
pub body_type: Option<BodyType>,
|
||||
/// The response type (defaults to Json)
|
||||
pub response_type: Option<ResponseType>,
|
||||
}
|
||||
|
||||
/// 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<String> {
|
||||
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<Vec<u8>, _> = 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()))
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
23
tauri/examples/communication/dist/http.js
vendored
Normal file
23
tauri/examples/communication/dist/http.js
vendored
Normal file
@ -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)
|
||||
})
|
14
tauri/examples/communication/dist/index.html
vendored
14
tauri/examples/communication/dist/index.html
vendored
@ -41,6 +41,19 @@
|
||||
<button id="save-dialog">Open save dialog</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 24px">
|
||||
<select id="request-method">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
</select>
|
||||
<input id="request-url" placeholder="Type the request URL...">
|
||||
<textarea id="request-body" placeholder="Request body"></textarea>
|
||||
<button id="make-request">Make request</button>
|
||||
</div>
|
||||
|
||||
<div id="response"></div>
|
||||
|
||||
<script>
|
||||
@ -76,5 +89,6 @@
|
||||
<script src="fs.js"></script>
|
||||
<script src="window.js"></script>
|
||||
<script src="dialog.js"></script>
|
||||
<script src="http.js"></script>
|
||||
</body>
|
||||
</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<any>}
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> </body></html>
|
||||
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div style="margin-top:24px"> <select id="request-method"> <option value="GET">GET</option> <option value="POST">POST</option> <option value="PUT">PUT</option> <option value="PATCH">PATCH</option> <option value="DELETE">DELETE</option> </select> <input id="request-url" placeholder="Type the request URL..."> <textarea id="request-body" placeholder="Request body"></textarea> <button id="make-request">Make request</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> <script></script> </body></html>
|
@ -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<String> },
|
||||
PerformRequest { endpoint: String, body: RequestBody, callback: String, error: String },
|
||||
LogOperation {
|
||||
event: String,
|
||||
payload: Option<String>,
|
||||
},
|
||||
PerformRequest {
|
||||
endpoint: String,
|
||||
body: RequestBody,
|
||||
callback: String,
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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<bool, String> {
|
||||
pub(crate) fn run_invoke_handler(
|
||||
&mut self,
|
||||
webview: &mut WebView<'_, ()>,
|
||||
arg: &str,
|
||||
) -> Result<bool, String> {
|
||||
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<InvokeHandler>,
|
||||
setup: Option<Setup>,
|
||||
splashscreen_html: Option<String>
|
||||
splashscreen_html: Option<String>,
|
||||
}
|
||||
|
||||
impl AppBuilder {
|
||||
|
@ -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<String>,
|
||||
splashscreen_content: Option<Content<String>>
|
||||
splashscreen_content: Option<Content<String>>,
|
||||
) -> crate::Result<WebView<'_, ()>> {
|
||||
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
|
||||
)
|
||||
|
@ -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<T: 'static>(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<T: 'static>(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,
|
||||
|
@ -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<String>,
|
||||
#[serde(default)]
|
||||
@ -24,6 +26,7 @@ pub struct OpenDialogOptions {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveDialogOptions {
|
||||
pub filter: Option<String>,
|
||||
pub default_path: Option<String>,
|
||||
@ -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,
|
||||
|
@ -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<T: 'static>(
|
||||
callback,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -52,14 +52,15 @@ pub fn copy_file<T: 'static>(
|
||||
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<T: 'static>(
|
||||
|
||||
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<T: 'static>(
|
||||
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<T: 'static>(
|
||||
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<T: 'static>(
|
||||
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,
|
||||
|
27
tauri/src/endpoints/http.rs
Normal file
27
tauri/src/endpoints/http.rs
Normal file
@ -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<T: 'static>(
|
||||
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,
|
||||
);
|
||||
}
|
@ -50,6 +50,10 @@ error_chain! {
|
||||
description("FileSystem Error")
|
||||
display("FileSystem Error: '{}'", t)
|
||||
}
|
||||
Http(t: String) {
|
||||
description("Http Error")
|
||||
display("Http Error: '{}'", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user