diff --git a/Cargo.lock b/Cargo.lock index 1154189d..488d41f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6659,7 +6659,7 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.3.0" +version = "2.4.0" dependencies = [ "data-url", "http", diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index 9aa49e0e..76d94d5a 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin-http" -version = "2.3.0" +version = "2.4.0" description = "Access an HTTP client written in Rust." edition = { workspace = true } authors = { workspace = true } diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index bea18e44..6c163509 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -26,7 +26,7 @@ * @module */ -import { invoke } from '@tauri-apps/api/core' +import { Channel, invoke } from '@tauri-apps/api/core' /** * Configuration of a proxy that a Client should pass requests to. @@ -106,6 +106,20 @@ export interface DangerousSettings { acceptInvalidHostnames?: boolean } +/** + * Stream Packet for IPC + */ +export interface StreamMessage { + /** + * The chunk - an array of bytes sent from Rust. + */ + value?: ArrayBuffer | number[] + /** + * Is the stream done. + */ + done: boolean +} + const ERROR_REQUEST_CANCELLED = 'Request canceled' /** @@ -186,6 +200,19 @@ export async function fetch( throw new Error(ERROR_REQUEST_CANCELLED) } + const streamChannel = new Channel() + + const readableStreamBody = new ReadableStream({ + start: (controller) => { + streamChannel.onmessage = (res: StreamMessage) => { + // close early if aborted + if (signal?.aborted) controller.error(ERROR_REQUEST_CANCELLED) + if (res.done) controller.close() + controller.enqueue(res.value) + } + } + }) + const rid = await invoke('plugin:http|fetch', { clientConfig: { method: req.method, @@ -196,7 +223,8 @@ export async function fetch( connectTimeout, proxy, danger - } + }, + streamChannel }) const abort = () => invoke('plugin:http|fetch_cancel', { rid }) @@ -223,30 +251,15 @@ export async function fetch( status, statusText, url, - headers: responseHeaders, - rid: responseRid + headers: responseHeaders } = await invoke('plugin:http|fetch_send', { rid }) - const body = await invoke( - 'plugin:http|fetch_read_body', - { - rid: responseRid - } - ) - - const res = new Response( - body instanceof ArrayBuffer && body.byteLength !== 0 - ? body - : body instanceof Array && body.length > 0 - ? new Uint8Array(body) - : null, - { - status, - statusText - } - ) + const res = new Response(readableStreamBody, { + status, + statusText + }) // url and headers are read only properties // but seems like we can set them like this diff --git a/plugins/http/package.json b/plugins/http/package.json index 02ea80bf..561bd228 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/plugin-http", - "version": "2.3.0", + "version": "2.4.0", "license": "MIT OR Apache-2.0", "authors": [ "Tauri Programme within The Commons Conservancy" @@ -24,6 +24,7 @@ "LICENSE" ], "dependencies": { - "@tauri-apps/api": "^2.0.0" + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-http": "link:" } } diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 3dc0297e..b0c6aab7 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT + use std::{future::Future, pin::Pin, str::FromStr, sync::Arc, time::Duration}; use http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; @@ -10,7 +11,7 @@ use serde::{Deserialize, Serialize}; use tauri::{ async_runtime::Mutex, command, - ipc::{CommandScope, GlobalScope}, + ipc::{Channel, CommandScope, GlobalScope}, Manager, ResourceId, ResourceTable, Runtime, State, Webview, }; use tokio::sync::oneshot::{channel, Receiver, Sender}; @@ -22,6 +23,8 @@ use crate::{ const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); +// reqwest::Response is never read, but might be needed for future use. +#[allow(dead_code)] struct ReqwestResponse(reqwest::Response); impl tauri::Resource for ReqwestResponse {} @@ -126,6 +129,12 @@ pub struct BasicAuth { password: String, } +#[derive(Clone, Serialize)] +pub struct StreamMessage { + value: Option>, + done: bool, +} + #[inline] fn proxy_creator( url_or_config: UrlOrConfig, @@ -181,6 +190,7 @@ pub async fn fetch( client_config: ClientConfig, command_scope: CommandScope, global_scope: GlobalScope, + stream_channel: Channel ) -> crate::Result { let ClientConfig { method, @@ -314,7 +324,24 @@ pub async fn fetch( #[cfg(feature = "tracing")] tracing::trace!("{:?}", request); - let fut = async move { request.send().await.map_err(Into::into) }; + let fut = async move { + let mut res = request.send().await?; + + // send response through IPC channel + while let Some(chunk) = res.chunk().await? { + stream_channel.send(StreamMessage{ + value: Some(chunk.to_vec()), + done: false, + })?; + } + + stream_channel.send(StreamMessage { value: None, done: true })?; + + // return that response + Ok(res) + }; + + let mut resources_table = webview.resources_table(); let rid = resources_table.add_request(Box::pin(fut)); @@ -410,19 +437,6 @@ pub async fn fetch_send( }) } -#[tauri::command] -pub(crate) async fn fetch_read_body( - webview: Webview, - rid: ResourceId, -) -> crate::Result { - let res = { - let mut resources_table = webview.resources_table(); - resources_table.take::(rid)? - }; - let res = Arc::into_inner(res).unwrap().0; - Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec())) -} - // forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers #[cfg(not(feature = "unsafe-headers"))] fn is_unsafe_header(header: &HeaderName) -> bool { diff --git a/plugins/http/src/lib.rs b/plugins/http/src/lib.rs index d775760c..4e11e561 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -36,8 +36,7 @@ pub fn init() -> TauriPlugin { .invoke_handler(tauri::generate_handler![ commands::fetch, commands::fetch_cancel, - commands::fetch_send, - commands::fetch_read_body, + commands::fetch_send ]) .build() } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e5a71c..256958a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,6 +229,9 @@ importers: '@tauri-apps/api': specifier: ^2.0.0 version: 2.3.0 + '@tauri-apps/plugin-http': + specifier: 'link:' + version: 'link:' plugins/log: dependencies: @@ -2283,9 +2286,9 @@ snapshots: - encoding - mocha - '@covector/assemble@0.12.0': + '@covector/assemble@0.12.0(mocha@10.8.2)': dependencies: - '@covector/command': 0.8.0 + '@covector/command': 0.8.0(mocha@10.8.2) '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) js-yaml: 4.1.0 @@ -2296,9 +2299,10 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding + - mocha - supports-color - '@covector/changelog@0.12.0': + '@covector/changelog@0.12.0(mocha@10.8.2)': dependencies: '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) @@ -2308,14 +2312,16 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding + - mocha - supports-color - '@covector/command@0.8.0': + '@covector/command@0.8.0(mocha@10.8.2)': dependencies: - '@effection/process': 2.1.4 + '@effection/process': 2.1.4(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2) transitivePeerDependencies: - encoding + - mocha '@covector/files@0.8.0': dependencies: @@ -2362,10 +2368,8 @@ snapshots: dependencies: effection: 2.0.8(mocha@10.8.2) mocha: 10.8.2 - transitivePeerDependencies: - - encoding - '@effection/process@2.1.4': + '@effection/process@2.1.4(mocha@10.8.2)': dependencies: cross-spawn: 7.0.6 ctrlc-windows: 2.2.0 @@ -2373,6 +2377,7 @@ snapshots: shellwords: 0.1.1 transitivePeerDependencies: - encoding + - mocha '@effection/stream@2.0.6': dependencies: @@ -3162,9 +3167,9 @@ snapshots: dependencies: '@clack/prompts': 0.7.0 '@covector/apply': 0.10.0(mocha@10.8.2) - '@covector/assemble': 0.12.0 - '@covector/changelog': 0.12.0 - '@covector/command': 0.8.0 + '@covector/assemble': 0.12.0(mocha@10.8.2) + '@covector/changelog': 0.12.0(mocha@10.8.2) + '@covector/command': 0.8.0(mocha@10.8.2) '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) globby: 11.1.0