From 643039c17e107bbb575d1af678e32243b29a5fe1 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Sun, 2 Mar 2025 21:47:47 +0800 Subject: [PATCH 01/30] feat: add stream support --- Cargo.lock | 2 +- plugins/http/Cargo.toml | 2 +- plugins/http/guest-js/index.ts | 57 +++++++++++++++++++++------------- plugins/http/package.json | 5 +-- plugins/http/src/commands.rs | 44 +++++++++++++++++--------- plugins/http/src/lib.rs | 3 +- pnpm-lock.yaml | 27 +++++++++------- 7 files changed, 86 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1154189dea..488d41f34e 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 9aa49e0e44..76d94d5a5f 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 bea18e4486..6c163509a5 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 02ea80bfc1..561bd22879 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 3dc0297e43..b0c6aab7d9 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 d775760cd3..4e11e5612e 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 c7e5a71cdf..256958a614 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 From 5edea816802fa8637982db96505832abbc7c450f Mon Sep 17 00:00:00 2001 From: adrieljss Date: Sun, 2 Mar 2025 23:07:44 +0800 Subject: [PATCH 02/30] feat: add stream support --- Cargo.lock | 2 +- plugins/http/Cargo.toml | 2 +- plugins/http/guest-js/index.ts | 57 +++++++++++++--------------------- plugins/http/package.json | 5 ++- plugins/http/src/commands.rs | 44 +++++++++----------------- plugins/http/src/lib.rs | 3 +- pnpm-lock.yaml | 27 +++++++--------- 7 files changed, 54 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 488d41f34e..1154189dea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6659,7 +6659,7 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.4.0" +version = "2.3.0" dependencies = [ "data-url", "http", diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index 76d94d5a5f..9aa49e0e44 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin-http" -version = "2.4.0" +version = "2.3.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 6c163509a5..bea18e4486 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -26,7 +26,7 @@ * @module */ -import { Channel, invoke } from '@tauri-apps/api/core' +import { invoke } from '@tauri-apps/api/core' /** * Configuration of a proxy that a Client should pass requests to. @@ -106,20 +106,6 @@ 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' /** @@ -200,19 +186,6 @@ 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, @@ -223,8 +196,7 @@ export async function fetch( connectTimeout, proxy, danger - }, - streamChannel + } }) const abort = () => invoke('plugin:http|fetch_cancel', { rid }) @@ -251,15 +223,30 @@ export async function fetch( status, statusText, url, - headers: responseHeaders + headers: responseHeaders, + rid: responseRid } = await invoke('plugin:http|fetch_send', { rid }) - const res = new Response(readableStreamBody, { - status, - statusText - }) + 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 + } + ) // 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 561bd22879..02ea80bfc1 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/plugin-http", - "version": "2.4.0", + "version": "2.3.0", "license": "MIT OR Apache-2.0", "authors": [ "Tauri Programme within The Commons Conservancy" @@ -24,7 +24,6 @@ "LICENSE" ], "dependencies": { - "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-http": "link:" + "@tauri-apps/api": "^2.0.0" } } diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index b0c6aab7d9..3dc0297e43 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -2,7 +2,6 @@ // 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}; @@ -11,7 +10,7 @@ use serde::{Deserialize, Serialize}; use tauri::{ async_runtime::Mutex, command, - ipc::{Channel, CommandScope, GlobalScope}, + ipc::{CommandScope, GlobalScope}, Manager, ResourceId, ResourceTable, Runtime, State, Webview, }; use tokio::sync::oneshot::{channel, Receiver, Sender}; @@ -23,8 +22,6 @@ 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 {} @@ -129,12 +126,6 @@ pub struct BasicAuth { password: String, } -#[derive(Clone, Serialize)] -pub struct StreamMessage { - value: Option>, - done: bool, -} - #[inline] fn proxy_creator( url_or_config: UrlOrConfig, @@ -190,7 +181,6 @@ pub async fn fetch( client_config: ClientConfig, command_scope: CommandScope, global_scope: GlobalScope, - stream_channel: Channel ) -> crate::Result { let ClientConfig { method, @@ -324,24 +314,7 @@ pub async fn fetch( #[cfg(feature = "tracing")] tracing::trace!("{:?}", request); - 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 fut = async move { request.send().await.map_err(Into::into) }; let mut resources_table = webview.resources_table(); let rid = resources_table.add_request(Box::pin(fut)); @@ -437,6 +410,19 @@ 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 4e11e5612e..d775760cd3 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -36,7 +36,8 @@ pub fn init() -> TauriPlugin { .invoke_handler(tauri::generate_handler![ commands::fetch, commands::fetch_cancel, - commands::fetch_send + commands::fetch_send, + commands::fetch_read_body, ]) .build() } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 256958a614..c7e5a71cdf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,9 +229,6 @@ importers: '@tauri-apps/api': specifier: ^2.0.0 version: 2.3.0 - '@tauri-apps/plugin-http': - specifier: 'link:' - version: 'link:' plugins/log: dependencies: @@ -2286,9 +2283,9 @@ snapshots: - encoding - mocha - '@covector/assemble@0.12.0(mocha@10.8.2)': + '@covector/assemble@0.12.0': dependencies: - '@covector/command': 0.8.0(mocha@10.8.2) + '@covector/command': 0.8.0 '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) js-yaml: 4.1.0 @@ -2299,10 +2296,9 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding - - mocha - supports-color - '@covector/changelog@0.12.0(mocha@10.8.2)': + '@covector/changelog@0.12.0': dependencies: '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) @@ -2312,16 +2308,14 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding - - mocha - supports-color - '@covector/command@0.8.0(mocha@10.8.2)': + '@covector/command@0.8.0': dependencies: - '@effection/process': 2.1.4(mocha@10.8.2) + '@effection/process': 2.1.4 effection: 2.0.8(mocha@10.8.2) transitivePeerDependencies: - encoding - - mocha '@covector/files@0.8.0': dependencies: @@ -2368,8 +2362,10 @@ snapshots: dependencies: effection: 2.0.8(mocha@10.8.2) mocha: 10.8.2 + transitivePeerDependencies: + - encoding - '@effection/process@2.1.4(mocha@10.8.2)': + '@effection/process@2.1.4': dependencies: cross-spawn: 7.0.6 ctrlc-windows: 2.2.0 @@ -2377,7 +2373,6 @@ snapshots: shellwords: 0.1.1 transitivePeerDependencies: - encoding - - mocha '@effection/stream@2.0.6': dependencies: @@ -3167,9 +3162,9 @@ snapshots: dependencies: '@clack/prompts': 0.7.0 '@covector/apply': 0.10.0(mocha@10.8.2) - '@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/assemble': 0.12.0 + '@covector/changelog': 0.12.0 + '@covector/command': 0.8.0 '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) globby: 11.1.0 From bdbb3d716bc17c4119c31fce06bb8a7f042880d6 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Sun, 2 Mar 2025 23:09:13 +0800 Subject: [PATCH 03/30] Revert "feat: add stream support" This reverts commit 5edea816802fa8637982db96505832abbc7c450f. --- Cargo.lock | 2 +- plugins/http/Cargo.toml | 2 +- plugins/http/guest-js/index.ts | 57 +++++++++++++++++++++------------- plugins/http/package.json | 5 +-- plugins/http/src/commands.rs | 44 +++++++++++++++++--------- plugins/http/src/lib.rs | 3 +- pnpm-lock.yaml | 27 +++++++++------- 7 files changed, 86 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1154189dea..488d41f34e 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 9aa49e0e44..76d94d5a5f 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 bea18e4486..6c163509a5 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 02ea80bfc1..561bd22879 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 3dc0297e43..b0c6aab7d9 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 d775760cd3..4e11e5612e 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 c7e5a71cdf..256958a614 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 From 39639eae2be881f776acef8f194cd026e7ec3c65 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Sun, 2 Mar 2025 23:09:56 +0800 Subject: [PATCH 04/30] feat: add stream support --- Cargo.lock | 2 +- plugins/http/Cargo.toml | 2 +- plugins/http/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 488d41f34e..1154189dea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6659,7 +6659,7 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.4.0" +version = "2.3.0" dependencies = [ "data-url", "http", diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index 76d94d5a5f..9aa49e0e44 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin-http" -version = "2.4.0" +version = "2.3.0" description = "Access an HTTP client written in Rust." edition = { workspace = true } authors = { workspace = true } diff --git a/plugins/http/package.json b/plugins/http/package.json index 561bd22879..f7f700197f 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/plugin-http", - "version": "2.4.0", + "version": "2.3.0", "license": "MIT OR Apache-2.0", "authors": [ "Tauri Programme within The Commons Conservancy" From 44ee2796012727a55a29739869b61c743a754621 Mon Sep 17 00:00:00 2001 From: Fabian-Lars Date: Sun, 2 Mar 2025 16:30:59 +0100 Subject: [PATCH 05/30] Discard changes to pnpm-lock.yaml --- pnpm-lock.yaml | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 256958a614..c7e5a71cdf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,9 +229,6 @@ importers: '@tauri-apps/api': specifier: ^2.0.0 version: 2.3.0 - '@tauri-apps/plugin-http': - specifier: 'link:' - version: 'link:' plugins/log: dependencies: @@ -2286,9 +2283,9 @@ snapshots: - encoding - mocha - '@covector/assemble@0.12.0(mocha@10.8.2)': + '@covector/assemble@0.12.0': dependencies: - '@covector/command': 0.8.0(mocha@10.8.2) + '@covector/command': 0.8.0 '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) js-yaml: 4.1.0 @@ -2299,10 +2296,9 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding - - mocha - supports-color - '@covector/changelog@0.12.0(mocha@10.8.2)': + '@covector/changelog@0.12.0': dependencies: '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) @@ -2312,16 +2308,14 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - encoding - - mocha - supports-color - '@covector/command@0.8.0(mocha@10.8.2)': + '@covector/command@0.8.0': dependencies: - '@effection/process': 2.1.4(mocha@10.8.2) + '@effection/process': 2.1.4 effection: 2.0.8(mocha@10.8.2) transitivePeerDependencies: - encoding - - mocha '@covector/files@0.8.0': dependencies: @@ -2368,8 +2362,10 @@ snapshots: dependencies: effection: 2.0.8(mocha@10.8.2) mocha: 10.8.2 + transitivePeerDependencies: + - encoding - '@effection/process@2.1.4(mocha@10.8.2)': + '@effection/process@2.1.4': dependencies: cross-spawn: 7.0.6 ctrlc-windows: 2.2.0 @@ -2377,7 +2373,6 @@ snapshots: shellwords: 0.1.1 transitivePeerDependencies: - encoding - - mocha '@effection/stream@2.0.6': dependencies: @@ -3167,9 +3162,9 @@ snapshots: dependencies: '@clack/prompts': 0.7.0 '@covector/apply': 0.10.0(mocha@10.8.2) - '@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/assemble': 0.12.0 + '@covector/changelog': 0.12.0 + '@covector/command': 0.8.0 '@covector/files': 0.8.0 effection: 2.0.8(mocha@10.8.2) globby: 11.1.0 From fd7bd417e1ebd5b599923243a0bd6109c9124db6 Mon Sep 17 00:00:00 2001 From: Fabian-Lars Date: Sun, 2 Mar 2025 16:31:05 +0100 Subject: [PATCH 06/30] Discard changes to plugins/http/package.json --- plugins/http/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/http/package.json b/plugins/http/package.json index f7f700197f..02ea80bfc1 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -24,7 +24,6 @@ "LICENSE" ], "dependencies": { - "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-http": "link:" + "@tauri-apps/api": "^2.0.0" } } From ca3651b55d5a144757a620eab2dba6b2f7abc10f Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 12:37:46 +0800 Subject: [PATCH 07/30] fix(stream): change IPC packet --- Cargo.lock | 20 +++++++++++++++----- plugins/http/Cargo.toml | 1 + plugins/http/src/commands.rs | 12 +++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1154189dea..c7ecb516e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1847,7 +1847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3349,7 +3349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4840,7 +4840,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5524,6 +5524,15 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.215" @@ -6667,6 +6676,7 @@ dependencies = [ "reqwest", "schemars", "serde", + "serde_bytes", "serde_json", "tauri", "tauri-plugin", @@ -7096,7 +7106,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8126,7 +8136,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index 9aa49e0e44..d37d418e55 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -30,6 +30,7 @@ regex = "1" [dependencies] serde = { workspace = true } +serde_bytes = "0.11" serde_json = { workspace = true } tauri = { workspace = true } thiserror = { workspace = true } diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index b0c6aab7d9..1a86da5755 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -131,8 +131,8 @@ pub struct BasicAuth { #[derive(Clone, Serialize)] pub struct StreamMessage { - value: Option>, - done: bool, + #[serde(with = "serde_bytes")] + bytes: Vec } #[inline] @@ -329,13 +329,11 @@ pub async fn fetch( // 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{bytes: chunk.to_vec()})?; } - stream_channel.send(StreamMessage { value: None, done: true })?; + // send empty vector when done + stream_channel.send(StreamMessage{bytes: Vec::new()})?; // return that response Ok(res) From 0a0a4280946a3de0d15984c42d532c6451fffe69 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 12:50:02 +0800 Subject: [PATCH 08/30] fix: update stream message guest-js --- plugins/http/guest-js/index.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 6c163509a5..836f2aad6c 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -113,11 +113,7 @@ export interface StreamMessage { /** * The chunk - an array of bytes sent from Rust. */ - value?: ArrayBuffer | number[] - /** - * Is the stream done. - */ - done: boolean + bytes: Uint8Array } const ERROR_REQUEST_CANCELLED = 'Request canceled' @@ -207,8 +203,11 @@ export async function fetch( 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) + if (!res.bytes.length) { + controller.close() + return + } + controller.enqueue(res) } } }) From ba122e63962dae7ad97c768018d62df1dea16cc2 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 12:51:43 +0800 Subject: [PATCH 09/30] fix: return early when aborted --- plugins/http/guest-js/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 836f2aad6c..7787da7eff 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -202,11 +202,16 @@ export async function fetch( start: (controller) => { streamChannel.onmessage = (res: StreamMessage) => { // close early if aborted - if (signal?.aborted) controller.error(ERROR_REQUEST_CANCELLED) + if (signal?.aborted) { + controller.error(ERROR_REQUEST_CANCELLED) + return + } + if (!res.bytes.length) { controller.close() return } + controller.enqueue(res) } } From 0a2645ffe8476e926c6971449495e3703daf103f Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 13:42:30 +0800 Subject: [PATCH 10/30] fix: use InvokeResponseBody as packet --- plugins/http/guest-js/index.ts | 16 +++------------- plugins/http/src/commands.rs | 12 +++--------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 7787da7eff..760f0648ea 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -106,16 +106,6 @@ export interface DangerousSettings { acceptInvalidHostnames?: boolean } -/** - * Stream Packet for IPC - */ -export interface StreamMessage { - /** - * The chunk - an array of bytes sent from Rust. - */ - bytes: Uint8Array -} - const ERROR_REQUEST_CANCELLED = 'Request canceled' /** @@ -196,18 +186,18 @@ export async function fetch( throw new Error(ERROR_REQUEST_CANCELLED) } - const streamChannel = new Channel() + const streamChannel = new Channel() const readableStreamBody = new ReadableStream({ start: (controller) => { - streamChannel.onmessage = (res: StreamMessage) => { + streamChannel.onmessage = (res: Uint8Array) => { // close early if aborted if (signal?.aborted) { controller.error(ERROR_REQUEST_CANCELLED) return } - if (!res.bytes.length) { + if (!res.length) { controller.close() return } diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 1a86da5755..0563166014 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -129,12 +129,6 @@ pub struct BasicAuth { password: String, } -#[derive(Clone, Serialize)] -pub struct StreamMessage { - #[serde(with = "serde_bytes")] - bytes: Vec -} - #[inline] fn proxy_creator( url_or_config: UrlOrConfig, @@ -190,7 +184,7 @@ pub async fn fetch( client_config: ClientConfig, command_scope: CommandScope, global_scope: GlobalScope, - stream_channel: Channel + stream_channel: Channel ) -> crate::Result { let ClientConfig { method, @@ -329,11 +323,11 @@ pub async fn fetch( // send response through IPC channel while let Some(chunk) = res.chunk().await? { - stream_channel.send(StreamMessage{bytes: chunk.to_vec()})?; + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?; } // send empty vector when done - stream_channel.send(StreamMessage{bytes: Vec::new()})?; + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?; // return that response Ok(res) From e7472125f5f25b3d60776dec1535b2d4a71b66e9 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 13:43:26 +0800 Subject: [PATCH 11/30] fix: remove serde_bytes --- Cargo.lock | 10 ---------- plugins/http/Cargo.toml | 1 - 2 files changed, 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7ecb516e7..85e174a824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5524,15 +5524,6 @@ dependencies = [ "typeid", ] -[[package]] -name = "serde_bytes" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.215" @@ -6676,7 +6667,6 @@ dependencies = [ "reqwest", "schemars", "serde", - "serde_bytes", "serde_json", "tauri", "tauri-plugin", diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index d37d418e55..9aa49e0e44 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -30,7 +30,6 @@ regex = "1" [dependencies] serde = { workspace = true } -serde_bytes = "0.11" serde_json = { workspace = true } tauri = { workspace = true } thiserror = { workspace = true } From 9b472c201518c43481d0685aea2fcd8a27610af9 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 20:30:36 +0800 Subject: [PATCH 12/30] fix: remove reqwest response --- plugins/http/src/commands.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 0563166014..7d5c1424d2 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -23,9 +23,6 @@ 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 {} type CancelableResponseResult = Result; From 57eb09b35074af384b6ed0997b6b026d808b61d7 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 20:38:20 +0800 Subject: [PATCH 13/30] fix: content conversion bug --- plugins/http/guest-js/index.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 760f0648ea..d0e14d4240 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -186,23 +186,31 @@ export async function fetch( throw new Error(ERROR_REQUEST_CANCELLED) } - const streamChannel = new Channel() + const streamChannel = new Channel() const readableStreamBody = new ReadableStream({ start: (controller) => { - streamChannel.onmessage = (res: Uint8Array) => { + streamChannel.onmessage = (res: ArrayBuffer | number[]) => { // close early if aborted if (signal?.aborted) { controller.error(ERROR_REQUEST_CANCELLED) + controller.close() return } - if (!res.length) { + // close when the signal to close (an empty chunk) + // is sent from the IPC. + if (res instanceof ArrayBuffer + ? res.byteLength == 0 + : res.length == 0) { controller.close() return } - - controller.enqueue(res) + + // the content conversion (like .text(), .json(), etc.) in Response + // must have Uint8Array as its content, else it will + // have untraceable error that's hard to debug. + controller.enqueue(new Uint8Array(res)) } } }) From acafdd84d87f33379876e8a85c09690be8d5246b Mon Sep 17 00:00:00 2001 From: adrieljss Date: Mon, 3 Mar 2025 20:45:38 +0800 Subject: [PATCH 14/30] fix: remove ReqwestResponses along with its implementations --- plugins/http/src/commands.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 7d5c1424d2..687cb8f20f 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -23,8 +23,6 @@ use crate::{ const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); -impl tauri::Resource for ReqwestResponse {} - type CancelableResponseResult = Result; type CancelableResponseFuture = Pin + Send + Sync>>; @@ -414,9 +412,6 @@ pub async fn fetch_send( )); } - let mut resources_table = webview.resources_table(); - let rid = resources_table.add(ReqwestResponse(res)); - Ok(FetchResponse { status: status.as_u16(), status_text: status.canonical_reason().unwrap_or_default().to_string(), From d75de7bc729a049058a89afc6e117251cc6d3754 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 4 Mar 2025 00:25:07 +0800 Subject: [PATCH 15/30] formatting and update changelog --- .changes/http-stream-support.md | 6 ++++++ plugins/http/guest-js/index.ts | 6 +++--- plugins/http/src/commands.rs | 4 +--- 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 .changes/http-stream-support.md diff --git a/.changes/http-stream-support.md b/.changes/http-stream-support.md new file mode 100644 index 0000000000..d9e38a6b75 --- /dev/null +++ b/.changes/http-stream-support.md @@ -0,0 +1,6 @@ +--- +"http": minor +"http-js": minor +--- + +Add stream support for HTTP stream responses. diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index d0e14d4240..2d86e9d842 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -200,9 +200,9 @@ export async function fetch( // close when the signal to close (an empty chunk) // is sent from the IPC. - if (res instanceof ArrayBuffer - ? res.byteLength == 0 - : res.length == 0) { + if ( + res instanceof ArrayBuffer ? res.byteLength == 0 : res.length == 0 + ) { controller.close() return } diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 687cb8f20f..17e514e257 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -2,7 +2,6 @@ // 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}; @@ -179,7 +178,7 @@ pub async fn fetch( client_config: ClientConfig, command_scope: CommandScope, global_scope: GlobalScope, - stream_channel: Channel + stream_channel: Channel, ) -> crate::Result { let ClientConfig { method, @@ -328,7 +327,6 @@ pub async fn fetch( Ok(res) }; - let mut resources_table = webview.resources_table(); let rid = resources_table.add_request(Box::pin(fut)); From 194fdeed96d45fbef19ed819abacaeb3b25d22b7 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 4 Mar 2025 00:49:03 +0800 Subject: [PATCH 16/30] build api-iife.js --- plugins/http/api-iife.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http/api-iife.js b/plugins/http/api-iife.js index 76b498ada5..bb2cb09b7a 100644 --- a/plugins/http/api-iife.js +++ b/plugins/http/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r="Request canceled";return e.fetch=async function(e,n){const a=n?.signal;if(a?.aborted)throw new Error(r);const o=n?.maxRedirections,s=n?.connectTimeout,i=n?.proxy,d=n?.danger;n&&(delete n.maxRedirections,delete n.connectTimeout,delete n.proxy,delete n.danger);const c=n?.headers?n.headers instanceof Headers?n.headers:new Headers(n.headers):new Headers,u=new Request(e,n),f=await u.arrayBuffer(),_=0!==f.byteLength?Array.from(new Uint8Array(f)):null;for(const[e,t]of u.headers)c.get(e)||c.set(e,t);const h=(c instanceof Headers?Array.from(c.entries()):Array.isArray(c)?c:Object.entries(c)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(a?.aborted)throw new Error(r);const l=await t("plugin:http|fetch",{clientConfig:{method:u.method,url:u.url,headers:h,data:_,maxRedirections:o,connectTimeout:s,proxy:i,danger:d}}),p=()=>t("plugin:http|fetch_cancel",{rid:l});if(a?.aborted)throw p(),new Error(r);a?.addEventListener("abort",(()=>{p()}));const{status:w,statusText:y,url:g,headers:T,rid:A}=await t("plugin:http|fetch_send",{rid:l}),R=await t("plugin:http|fetch_read_body",{rid:A}),b=new Response(R instanceof ArrayBuffer&&0!==R.byteLength?R:R instanceof Array&&R.length>0?new Uint8Array(R):null,{status:w,statusText:y});return Object.defineProperty(b,"url",{value:g}),Object.defineProperty(b,"headers",{value:new Headers(T)}),b},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[i]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,i=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const f=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,h=new Request(e,t),_=await h.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of h.headers)f.get(e)||f.set(e,t);const l=(f instanceof Headers?Array.from(f.entries()):Array.isArray(f)?f:Object.entries(f)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new o,p=new ReadableStream({start:e=>{w.onmessage=t=>{if(r?.aborted)return e.error(d),void e.close();(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()}}}),m=await c("plugin:http|fetch",{clientConfig:{method:h.method,url:h.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:i},streamChannel:w}),y=()=>c("plugin:http|fetch_cancel",{rid:m});if(r?.aborted)throw y(),new Error(d);r?.addEventListener("abort",(()=>{y()}));const{status:T,statusText:g,url:A,headers:R}=await c("plugin:http|fetch_send",{rid:m}),b=new Response(p,{status:T,statusText:g});return Object.defineProperty(b,"url",{value:A}),Object.defineProperty(b,"headers",{value:new Headers(R)}),b},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} From 86ad81fb5b32f0f79ce4ecfa88b8bfd015b70ece Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:03:52 +0800 Subject: [PATCH 17/30] fix(http): fixes blocking body fetch function --- .changes/http-stream-support.md | 2 +- plugins/http/api-iife.js | 2 +- plugins/http/guest-js/index.ts | 14 +++++++--- plugins/http/package.json | 3 ++- plugins/http/src/commands.rs | 45 +++++++++++++++++++++------------ plugins/http/src/lib.rs | 3 ++- pnpm-lock.yaml | 27 ++++++++++++-------- 7 files changed, 61 insertions(+), 35 deletions(-) diff --git a/.changes/http-stream-support.md b/.changes/http-stream-support.md index d9e38a6b75..a0db5bfda5 100644 --- a/.changes/http-stream-support.md +++ b/.changes/http-stream-support.md @@ -3,4 +3,4 @@ "http-js": minor --- -Add stream support for HTTP stream responses. +Fix HTTP stream not streaming the body. \ No newline at end of file diff --git a/plugins/http/api-iife.js b/plugins/http/api-iife.js index bb2cb09b7a..5dc00b96e4 100644 --- a/plugins/http/api-iife.js +++ b/plugins/http/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[i]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,i=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const f=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,h=new Request(e,t),_=await h.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of h.headers)f.get(e)||f.set(e,t);const l=(f instanceof Headers?Array.from(f.entries()):Array.isArray(f)?f:Object.entries(f)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new o,p=new ReadableStream({start:e=>{w.onmessage=t=>{if(r?.aborted)return e.error(d),void e.close();(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()}}}),m=await c("plugin:http|fetch",{clientConfig:{method:h.method,url:h.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:i},streamChannel:w}),y=()=>c("plugin:http|fetch_cancel",{rid:m});if(r?.aborted)throw y(),new Error(d);r?.addEventListener("abort",(()=>{y()}));const{status:T,statusText:g,url:A,headers:R}=await c("plugin:http|fetch_send",{rid:m}),b=new Response(p,{status:T,statusText:g});return Object.defineProperty(b,"url",{value:A}),Object.defineProperty(b,"headers",{value:new Headers(R)}),b},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class i{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:o})=>{if(o==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[o]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,o=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const h=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),_=await f.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of f.headers)h.get(e)||h.set(e,t);const l=(h instanceof Headers?Array.from(h.entries()):Array.isArray(h)?h:Object.entries(h)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new ReadableStream({start:e=>{const t=new i;t.onmessage=t=>{if(r?.aborted)return e.error(d),void e.close();(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()},c("plugin:http|fetch_stream_body",{streamChannel:t}).catch((t=>{e.error(t),e.close()}))}}),p=await c("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:o}}),m=()=>c("plugin:http|fetch_cancel",{rid:p});if(r?.aborted)throw m(),new Error(d);r?.addEventListener("abort",(()=>{m()}));const{status:y,statusText:T,url:g,headers:A}=await c("plugin:http|fetch_send",{rid:p}),R=new Response(w,{status:y,statusText:T});return Object.defineProperty(R,"url",{value:g}),Object.defineProperty(R,"headers",{value:new Headers(A)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 2d86e9d842..424c3871ab 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -186,10 +186,9 @@ export async function fetch( throw new Error(ERROR_REQUEST_CANCELLED) } - const streamChannel = new Channel() - const readableStreamBody = new ReadableStream({ start: (controller) => { + const streamChannel = new Channel() streamChannel.onmessage = (res: ArrayBuffer | number[]) => { // close early if aborted if (signal?.aborted) { @@ -212,6 +211,14 @@ export async function fetch( // have untraceable error that's hard to debug. controller.enqueue(new Uint8Array(res)) } + + // run a non-blocking body stream fetch + invoke('plugin:http|fetch_stream_body', { + streamChannel + }).catch((e) => { + controller.error(e) + controller.close() + }) } }) @@ -225,8 +232,7 @@ export async function fetch( connectTimeout, proxy, danger - }, - streamChannel + } }) const abort = () => invoke('plugin:http|fetch_cancel', { rid }) diff --git a/plugins/http/package.json b/plugins/http/package.json index 02ea80bfc1..f7f700197f 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -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 17e514e257..4ed7bee0cf 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -22,6 +22,9 @@ use crate::{ const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); +struct ReqwestResponse(reqwest::Response); +impl tauri::Resource for ReqwestResponse {} + type CancelableResponseResult = Result; type CancelableResponseFuture = Pin + Send + Sync>>; @@ -178,7 +181,6 @@ pub async fn fetch( client_config: ClientConfig, command_scope: CommandScope, global_scope: GlobalScope, - stream_channel: Channel, ) -> crate::Result { let ClientConfig { method, @@ -312,20 +314,7 @@ pub async fn fetch( #[cfg(feature = "tracing")] tracing::trace!("{:?}", request); - 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(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?; - } - - // send empty vector when done - stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?; - - // return that response - Ok(res) - }; + let fut = async move { request.send().await.map_err(Into::into) }; let mut resources_table = webview.resources_table(); let rid = resources_table.add_request(Box::pin(fut)); @@ -370,7 +359,31 @@ pub fn fetch_cancel(webview: Webview, rid: ResourceId) -> crate:: Ok(()) } -#[tauri::command] +#[command] +pub async fn fetch_stream_body( + webview: Webview, + rid: ResourceId, + stream_channel: Channel, +) -> crate::Result<()> { + let res = { + let mut resources_table = webview.resources_table(); + resources_table.take::(rid)? + }; + + let mut res = Arc::into_inner(res).unwrap().0; + + // send response through IPC channel + while let Some(chunk) = res.chunk().await? { + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?; + } + + // send empty vector when done + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?; + + Ok(()) +} + +#[command] pub async fn fetch_send( webview: Webview, rid: ResourceId, diff --git a/plugins/http/src/lib.rs b/plugins/http/src/lib.rs index 4e11e5612e..d9613ed533 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -36,7 +36,8 @@ pub fn init() -> TauriPlugin { .invoke_handler(tauri::generate_handler![ commands::fetch, commands::fetch_cancel, - commands::fetch_send + commands::fetch_send, + commands::fetch_stream_body, ]) .build() } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e5a71cdf..256958a614 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 From 6a9dde50fa29967374ada4cf70f572aca2e87533 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:17:33 +0800 Subject: [PATCH 18/30] fix: remove close after error and inject rid to invoke --- plugins/http/guest-js/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 424c3871ab..6b48e730e8 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -193,7 +193,6 @@ export async function fetch( // close early if aborted if (signal?.aborted) { controller.error(ERROR_REQUEST_CANCELLED) - controller.close() return } @@ -214,10 +213,10 @@ export async function fetch( // run a non-blocking body stream fetch invoke('plugin:http|fetch_stream_body', { + rid, streamChannel }).catch((e) => { controller.error(e) - controller.close() }) } }) From daadfd9c3930b35ff4e6e5fc89e4720d7810dde4 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:48:32 +0800 Subject: [PATCH 19/30] build and format --- plugins/http/api-iife.js | 2 +- pnpm-lock.yaml | 324 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 317 insertions(+), 9 deletions(-) diff --git a/plugins/http/api-iife.js b/plugins/http/api-iife.js index 5dc00b96e4..83b09d7698 100644 --- a/plugins/http/api-iife.js +++ b/plugins/http/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class i{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:o})=>{if(o==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[o]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,o=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const h=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),_=await f.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of f.headers)h.get(e)||h.set(e,t);const l=(h instanceof Headers?Array.from(h.entries()):Array.isArray(h)?h:Object.entries(h)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new ReadableStream({start:e=>{const t=new i;t.onmessage=t=>{if(r?.aborted)return e.error(d),void e.close();(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()},c("plugin:http|fetch_stream_body",{streamChannel:t}).catch((t=>{e.error(t),e.close()}))}}),p=await c("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:o}}),m=()=>c("plugin:http|fetch_cancel",{rid:p});if(r?.aborted)throw m(),new Error(d);r?.addEventListener("abort",(()=>{m()}));const{status:y,statusText:T,url:g,headers:A}=await c("plugin:http|fetch_send",{rid:p}),R=new Response(w,{status:y,statusText:T});return Object.defineProperty(R,"url",{value:g}),Object.defineProperty(R,"headers",{value:new Headers(A)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[i]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,i=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const h=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),_=await f.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of f.headers)h.get(e)||h.set(e,t);const l=(h instanceof Headers?Array.from(h.entries()):Array.isArray(h)?h:Object.entries(h)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new ReadableStream({start:e=>{const t=new o;t.onmessage=t=>{r?.aborted?e.error(d):(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()},c("plugin:http|fetch_stream_body",{rid:p,streamChannel:t}).catch((t=>{e.error(t)}))}}),p=await c("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:i}}),m=()=>c("plugin:http|fetch_cancel",{rid:p});if(r?.aborted)throw m(),new Error(d);r?.addEventListener("abort",(()=>{m()}));const{status:y,statusText:T,url:g,headers:A}=await c("plugin:http|fetch_send",{rid:p}),R=new Response(w,{status:y,statusText:T});return Object.defineProperty(R,"url",{value:g}),Object.defineProperty(R,"headers",{value:new Headers(A)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 256958a614..372220fb78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: link:../../plugins/cli '@tauri-apps/plugin-clipboard-manager': specifier: ^2.2.1 - version: link:../../plugins/clipboard-manager + version: 2.2.2 '@tauri-apps/plugin-dialog': specifier: ^2.2.0 version: link:../../plugins/dialog @@ -76,28 +76,28 @@ importers: version: link:../../plugins/fs '@tauri-apps/plugin-geolocation': specifier: ^2.2.0 - version: link:../../plugins/geolocation + version: 2.2.4 '@tauri-apps/plugin-global-shortcut': specifier: ^2.2.0 version: link:../../plugins/global-shortcut '@tauri-apps/plugin-haptics': specifier: ^2.2.0 - version: link:../../plugins/haptics + version: 2.2.4 '@tauri-apps/plugin-http': specifier: ^2.3.0 - version: link:../../plugins/http + version: 2.4.0 '@tauri-apps/plugin-nfc': specifier: ^2.2.0 version: link:../../plugins/nfc '@tauri-apps/plugin-notification': specifier: ^2.2.1 - version: link:../../plugins/notification + version: 2.2.2 '@tauri-apps/plugin-opener': specifier: ^2.2.6 version: link:../../plugins/opener '@tauri-apps/plugin-os': specifier: ^2.2.0 - version: link:../../plugins/os + version: 2.2.1 '@tauri-apps/plugin-process': specifier: ^2.2.0 version: link:../../plugins/process @@ -109,7 +109,7 @@ importers: version: link:../../plugins/store '@tauri-apps/plugin-updater': specifier: ^2.5.1 - version: link:../../plugins/updater + version: 2.6.0 '@zerodevx/svelte-json-view': specifier: 1.0.11 version: 1.0.11(svelte@5.20.4) @@ -452,150 +452,300 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.1': + resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.0': resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.1': + resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.0': resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.1': + resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.0': resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.1': + resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.0': resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.1': + resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.0': resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.1': + resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.0': resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.1': + resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.1': + resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.0': resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.1': + resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.0': resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.1': + resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.0': resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.1': + resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.0': resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.1': + resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.0': resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.1': + resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.0': resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.1': + resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.0': resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.1': + resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.0': resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.1': + resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.0': resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.1': + resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.0': resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.1': + resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.1': + resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.0': resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.1': + resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.1': + resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.25.0': resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.1': + resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.0': resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.1': + resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.0': resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.1': + resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.0': resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.1': + resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -916,6 +1066,27 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-clipboard-manager@2.2.2': + resolution: {integrity: sha512-bZvDLMqfcNmsw7Ag8I49jlaCjdpDvvlJHnpp6P+Gg/3xtpSERdwlDxm7cKGbs2mj46dsw4AuG3RoAgcpwgioUA==} + + '@tauri-apps/plugin-geolocation@2.2.4': + resolution: {integrity: sha512-c/kqxMW3DjCl+EzFRRBc2B9J22DqzmQOt02i9IWy3WYXIRF8/krTqZpqNIi2Db2/eWS5dEtS6z5S9sBk6UIr2g==} + + '@tauri-apps/plugin-haptics@2.2.4': + resolution: {integrity: sha512-h+YapjI0o6B1daj4gL7j8qKZUoxuZ09RWq+W8C62TXO5GHxc4YaywzGNGqq/3kJTYwv/3fw5o/pWO3AsN58RuQ==} + + '@tauri-apps/plugin-http@2.4.0': + resolution: {integrity: sha512-Y3D7ymHDQA/OtdFbDWFUQRaevxaAUbQPbBS9UWlgphwXrjoebT7gVKgKymM617dAgT5MpaIG1Nagx/x17gImRg==} + + '@tauri-apps/plugin-notification@2.2.2': + resolution: {integrity: sha512-d71rJdtkFUTcG4dqydnv6d7ZwlNZVcdjrVOPwc9GsF6y9DgVN1WCZ9T/vbfD2qrJslf7ai+rnNJc62TLLC2IdA==} + + '@tauri-apps/plugin-os@2.2.1': + resolution: {integrity: sha512-cNYpNri2CCc6BaNeB6G/mOtLvg8dFyFQyCUdf2y0K8PIAKGEWdEcu8DECkydU2B+oj4OJihDPD2de5K6cbVl9A==} + + '@tauri-apps/plugin-updater@2.6.0': + resolution: {integrity: sha512-j74RUolLIhQDQwrff6R28xIewYVXME1gFU+d+4LYN1dLRzLD+ySa7VHqzyWYxWEvm+TPZ7lkUxa5a9uH9Ist3A==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1324,6 +1495,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.1: + resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2391,78 +2567,153 @@ snapshots: '@esbuild/aix-ppc64@0.25.0': optional: true + '@esbuild/aix-ppc64@0.25.1': + optional: true + '@esbuild/android-arm64@0.25.0': optional: true + '@esbuild/android-arm64@0.25.1': + optional: true + '@esbuild/android-arm@0.25.0': optional: true + '@esbuild/android-arm@0.25.1': + optional: true + '@esbuild/android-x64@0.25.0': optional: true + '@esbuild/android-x64@0.25.1': + optional: true + '@esbuild/darwin-arm64@0.25.0': optional: true + '@esbuild/darwin-arm64@0.25.1': + optional: true + '@esbuild/darwin-x64@0.25.0': optional: true + '@esbuild/darwin-x64@0.25.1': + optional: true + '@esbuild/freebsd-arm64@0.25.0': optional: true + '@esbuild/freebsd-arm64@0.25.1': + optional: true + '@esbuild/freebsd-x64@0.25.0': optional: true + '@esbuild/freebsd-x64@0.25.1': + optional: true + '@esbuild/linux-arm64@0.25.0': optional: true + '@esbuild/linux-arm64@0.25.1': + optional: true + '@esbuild/linux-arm@0.25.0': optional: true + '@esbuild/linux-arm@0.25.1': + optional: true + '@esbuild/linux-ia32@0.25.0': optional: true + '@esbuild/linux-ia32@0.25.1': + optional: true + '@esbuild/linux-loong64@0.25.0': optional: true + '@esbuild/linux-loong64@0.25.1': + optional: true + '@esbuild/linux-mips64el@0.25.0': optional: true + '@esbuild/linux-mips64el@0.25.1': + optional: true + '@esbuild/linux-ppc64@0.25.0': optional: true + '@esbuild/linux-ppc64@0.25.1': + optional: true + '@esbuild/linux-riscv64@0.25.0': optional: true + '@esbuild/linux-riscv64@0.25.1': + optional: true + '@esbuild/linux-s390x@0.25.0': optional: true + '@esbuild/linux-s390x@0.25.1': + optional: true + '@esbuild/linux-x64@0.25.0': optional: true + '@esbuild/linux-x64@0.25.1': + optional: true + '@esbuild/netbsd-arm64@0.25.0': optional: true + '@esbuild/netbsd-arm64@0.25.1': + optional: true + '@esbuild/netbsd-x64@0.25.0': optional: true + '@esbuild/netbsd-x64@0.25.1': + optional: true + '@esbuild/openbsd-arm64@0.25.0': optional: true + '@esbuild/openbsd-arm64@0.25.1': + optional: true + '@esbuild/openbsd-x64@0.25.0': optional: true + '@esbuild/openbsd-x64@0.25.1': + optional: true + '@esbuild/sunos-x64@0.25.0': optional: true + '@esbuild/sunos-x64@0.25.1': + optional: true + '@esbuild/win32-arm64@0.25.0': optional: true + '@esbuild/win32-arm64@0.25.1': + optional: true + '@esbuild/win32-ia32@0.25.0': optional: true + '@esbuild/win32-ia32@0.25.1': + optional: true + '@esbuild/win32-x64@0.25.0': optional: true + '@esbuild/win32-x64@0.25.1': + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0(jiti@2.4.2))': dependencies: eslint: 9.21.0(jiti@2.4.2) @@ -2736,6 +2987,34 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.3.1 '@tauri-apps/cli-win32-x64-msvc': 2.3.1 + '@tauri-apps/plugin-clipboard-manager@2.2.2': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-geolocation@2.2.4': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-haptics@2.2.4': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-http@2.4.0': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-notification@2.2.2': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-os@2.2.1': + dependencies: + '@tauri-apps/api': 2.3.0 + + '@tauri-apps/plugin-updater@2.6.0': + dependencies: + '@tauri-apps/api': 2.3.0 + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} @@ -3274,6 +3553,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.0 '@esbuild/win32-x64': 0.25.0 + esbuild@0.25.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.1 + '@esbuild/android-arm': 0.25.1 + '@esbuild/android-arm64': 0.25.1 + '@esbuild/android-x64': 0.25.1 + '@esbuild/darwin-arm64': 0.25.1 + '@esbuild/darwin-x64': 0.25.1 + '@esbuild/freebsd-arm64': 0.25.1 + '@esbuild/freebsd-x64': 0.25.1 + '@esbuild/linux-arm': 0.25.1 + '@esbuild/linux-arm64': 0.25.1 + '@esbuild/linux-ia32': 0.25.1 + '@esbuild/linux-loong64': 0.25.1 + '@esbuild/linux-mips64el': 0.25.1 + '@esbuild/linux-ppc64': 0.25.1 + '@esbuild/linux-riscv64': 0.25.1 + '@esbuild/linux-s390x': 0.25.1 + '@esbuild/linux-x64': 0.25.1 + '@esbuild/netbsd-arm64': 0.25.1 + '@esbuild/netbsd-x64': 0.25.1 + '@esbuild/openbsd-arm64': 0.25.1 + '@esbuild/openbsd-x64': 0.25.1 + '@esbuild/sunos-x64': 0.25.1 + '@esbuild/win32-arm64': 0.25.1 + '@esbuild/win32-ia32': 0.25.1 + '@esbuild/win32-x64': 0.25.1 + optional: true + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -4018,7 +4326,7 @@ snapshots: tsx@4.19.2: dependencies: - esbuild: 0.25.0 + esbuild: 0.25.1 get-tsconfig: 4.10.0 optionalDependencies: fsevents: 2.3.3 From 70501935b05691c3fb07fa07e3df988642370cdc Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:50:17 +0800 Subject: [PATCH 20/30] fix: change permission command --- plugins/http/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http/build.rs b/plugins/http/build.rs index a4b802adf5..b44e052348 100644 --- a/plugins/http/build.rs +++ b/plugins/http/build.rs @@ -6,7 +6,7 @@ #[allow(dead_code)] mod scope; -const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_read_body"]; +const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_stream_body"]; /// HTTP scope entry. #[derive(schemars::JsonSchema)] From 1ec0de83fa94b57a7f0d3bdc1d5a293f7e0ba5d8 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:53:08 +0800 Subject: [PATCH 21/30] update permissions --- .../commands/fetch_stream_body.toml | 13 ++++++++++ .../permissions/autogenerated/reference.md | 26 +++++++++++++++++++ plugins/http/permissions/default.toml | 2 +- plugins/http/permissions/schemas/schema.json | 16 ++++++------ 4 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml diff --git a/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml b/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml new file mode 100644 index 0000000000..5f711fd5e1 --- /dev/null +++ b/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fetch-stream-body" +description = "Enables the fetch_stream_body command without any pre-configured scope." +commands.allow = ["fetch_stream_body"] + +[[permission]] +identifier = "deny-fetch-stream-body" +description = "Denies the fetch_stream_body command without any pre-configured scope." +commands.deny = ["fetch_stream_body"] diff --git a/plugins/http/permissions/autogenerated/reference.md b/plugins/http/permissions/autogenerated/reference.md index 4126f0b9ea..aade2fdab1 100644 --- a/plugins/http/permissions/autogenerated/reference.md +++ b/plugins/http/permissions/autogenerated/reference.md @@ -128,6 +128,32 @@ Enables the fetch_send command without any pre-configured scope. Denies the fetch_send command without any pre-configured scope. + + + + + + +`http:allow-fetch-stream-body` + + + + +Enables the fetch_stream_body command without any pre-configured scope. + + + + + + + +`http:deny-fetch-stream-body` + + + + +Denies the fetch_stream_body command without any pre-configured scope. + diff --git a/plugins/http/permissions/default.toml b/plugins/http/permissions/default.toml index b469536ddc..9c8818db09 100644 --- a/plugins/http/permissions/default.toml +++ b/plugins/http/permissions/default.toml @@ -17,6 +17,6 @@ All fetch operations are enabled. permissions = [ "allow-fetch", "allow-fetch-cancel", - "allow-fetch-read-body", + "allow-fetch-stream-body", "allow-fetch-send", ] diff --git a/plugins/http/permissions/schemas/schema.json b/plugins/http/permissions/schemas/schema.json index 794ee20480..eb7e4763a5 100644 --- a/plugins/http/permissions/schemas/schema.json +++ b/plugins/http/permissions/schemas/schema.json @@ -315,24 +315,24 @@ "const": "deny-fetch-cancel" }, { - "description": "Enables the fetch_read_body command without any pre-configured scope.", + "description": "Enables the fetch_send command without any pre-configured scope.", "type": "string", - "const": "allow-fetch-read-body" + "const": "allow-fetch-send" }, { - "description": "Denies the fetch_read_body command without any pre-configured scope.", + "description": "Denies the fetch_send command without any pre-configured scope.", "type": "string", - "const": "deny-fetch-read-body" + "const": "deny-fetch-send" }, { - "description": "Enables the fetch_send command without any pre-configured scope.", + "description": "Enables the fetch_stream_body command without any pre-configured scope.", "type": "string", - "const": "allow-fetch-send" + "const": "allow-fetch-stream-body" }, { - "description": "Denies the fetch_send command without any pre-configured scope.", + "description": "Denies the fetch_stream_body command without any pre-configured scope.", "type": "string", - "const": "deny-fetch-send" + "const": "deny-fetch-stream-body" }, { "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n", From 6cf36fb56637264ca1a65683f9fcc412d8345291 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Tue, 11 Mar 2025 23:57:44 +0800 Subject: [PATCH 22/30] fix: revert command back to fetch_read_body to comply with permissions --- plugins/http/guest-js/index.ts | 28 +++++++++---------- .../permissions/autogenerated/reference.md | 2 +- plugins/http/permissions/schemas/schema.json | 10 +++++++ plugins/http/src/commands.rs | 2 +- plugins/http/src/lib.rs | 2 +- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 6b48e730e8..770cbc5f86 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -186,6 +186,19 @@ export async function fetch( throw new Error(ERROR_REQUEST_CANCELLED) } + const rid = await invoke('plugin:http|fetch', { + clientConfig: { + method: req.method, + url: req.url, + headers: mappedHeaders, + data, + maxRedirections, + connectTimeout, + proxy, + danger + } + }) + const readableStreamBody = new ReadableStream({ start: (controller) => { const streamChannel = new Channel() @@ -212,7 +225,7 @@ export async function fetch( } // run a non-blocking body stream fetch - invoke('plugin:http|fetch_stream_body', { + invoke('plugin:http|fetch_read_body', { rid, streamChannel }).catch((e) => { @@ -221,19 +234,6 @@ export async function fetch( } }) - const rid = await invoke('plugin:http|fetch', { - clientConfig: { - method: req.method, - url: req.url, - headers: mappedHeaders, - data, - maxRedirections, - connectTimeout, - proxy, - danger - } - }) - const abort = () => invoke('plugin:http|fetch_cancel', { rid }) // abort early here if needed diff --git a/plugins/http/permissions/autogenerated/reference.md b/plugins/http/permissions/autogenerated/reference.md index aade2fdab1..19a3c4ebc1 100644 --- a/plugins/http/permissions/autogenerated/reference.md +++ b/plugins/http/permissions/autogenerated/reference.md @@ -15,7 +15,7 @@ All fetch operations are enabled. - `allow-fetch` - `allow-fetch-cancel` -- `allow-fetch-read-body` +- `allow-fetch-stream-body` - `allow-fetch-send` ## Permission Table diff --git a/plugins/http/permissions/schemas/schema.json b/plugins/http/permissions/schemas/schema.json index eb7e4763a5..03caf56e23 100644 --- a/plugins/http/permissions/schemas/schema.json +++ b/plugins/http/permissions/schemas/schema.json @@ -314,6 +314,16 @@ "type": "string", "const": "deny-fetch-cancel" }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "allow-fetch-read-body" + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "deny-fetch-read-body" + }, { "description": "Enables the fetch_send command without any pre-configured scope.", "type": "string", diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 4ed7bee0cf..b6113c29ed 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -360,7 +360,7 @@ pub fn fetch_cancel(webview: Webview, rid: ResourceId) -> crate:: } #[command] -pub async fn fetch_stream_body( +pub async fn fetch_read_body( webview: Webview, rid: ResourceId, stream_channel: Channel, diff --git a/plugins/http/src/lib.rs b/plugins/http/src/lib.rs index d9613ed533..d775760cd3 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -37,7 +37,7 @@ pub fn init() -> TauriPlugin { commands::fetch, commands::fetch_cancel, commands::fetch_send, - commands::fetch_stream_body, + commands::fetch_read_body, ]) .build() } From 33242bf5a3db996f9ff402a7275f073d832570db Mon Sep 17 00:00:00 2001 From: adrieljss Date: Wed, 12 Mar 2025 00:01:30 +0800 Subject: [PATCH 23/30] fix: revert read_body name permission command --- plugins/http/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http/build.rs b/plugins/http/build.rs index b44e052348..a4b802adf5 100644 --- a/plugins/http/build.rs +++ b/plugins/http/build.rs @@ -6,7 +6,7 @@ #[allow(dead_code)] mod scope; -const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_stream_body"]; +const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_read_body"]; /// HTTP scope entry. #[derive(schemars::JsonSchema)] From 99bd751e91ae148c9579fbfe1ca8b1367f51a3a1 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Wed, 12 Mar 2025 09:42:31 +0800 Subject: [PATCH 24/30] feat: add back ReqwestResponse implementation --- plugins/http/src/commands.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index b6113c29ed..1c9e3f0e5c 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -423,6 +423,9 @@ pub async fn fetch_send( )); } + let mut resources_table = webview.resources_table(); + let rid = resources_table.add(ReqwestResponse(res)); + Ok(FetchResponse { status: status.as_u16(), status_text: status.canonical_reason().unwrap_or_default().to_string(), From bfb468d97a444e24dd82bb26784c7e04871c9567 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Wed, 12 Mar 2025 09:55:36 +0800 Subject: [PATCH 25/30] fix: use responseRid in invoke and build js --- plugins/http/guest-js/index.ts | 61 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 770cbc5f86..623781a4ed 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -199,6 +199,36 @@ export async function fetch( } }) + const abort = () => invoke('plugin:http|fetch_cancel', { rid }) + + // abort early here if needed + if (signal?.aborted) { + // we don't care about the result of this proimse + // eslint-disable-next-line @typescript-eslint/no-floating-promises + abort() + throw new Error(ERROR_REQUEST_CANCELLED) + } + + signal?.addEventListener('abort', () => void abort()) + + interface FetchSendResponse { + status: number + statusText: string + headers: [[string, string]] + url: string + rid: number + } + + const { + status, + statusText, + url, + headers: responseHeaders, + rid: responseRid + } = await invoke('plugin:http|fetch_send', { + rid + }) + const readableStreamBody = new ReadableStream({ start: (controller) => { const streamChannel = new Channel() @@ -226,7 +256,7 @@ export async function fetch( // run a non-blocking body stream fetch invoke('plugin:http|fetch_read_body', { - rid, + rid: responseRid, streamChannel }).catch((e) => { controller.error(e) @@ -234,35 +264,6 @@ export async function fetch( } }) - const abort = () => invoke('plugin:http|fetch_cancel', { rid }) - - // abort early here if needed - if (signal?.aborted) { - // we don't care about the result of this proimse - // eslint-disable-next-line @typescript-eslint/no-floating-promises - abort() - throw new Error(ERROR_REQUEST_CANCELLED) - } - - signal?.addEventListener('abort', () => void abort()) - - interface FetchSendResponse { - status: number - statusText: string - headers: [[string, string]] - url: string - rid: number - } - - const { - status, - statusText, - url, - headers: responseHeaders - } = await invoke('plugin:http|fetch_send', { - rid - }) - const res = new Response(readableStreamBody, { status, statusText From 73bb96b33f826695d487dc99310fda1fc2a70412 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Wed, 12 Mar 2025 12:26:01 +0800 Subject: [PATCH 26/30] cleanup previous permissions --- plugins/http/package.json | 3 +- .../commands/fetch_stream_body.toml | 13 --------- .../permissions/autogenerated/reference.md | 28 +------------------ plugins/http/permissions/default.toml | 2 +- plugins/http/permissions/schemas/schema.json | 10 ------- 5 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml diff --git a/plugins/http/package.json b/plugins/http/package.json index f7f700197f..02ea80bfc1 100644 --- a/plugins/http/package.json +++ b/plugins/http/package.json @@ -24,7 +24,6 @@ "LICENSE" ], "dependencies": { - "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-http": "link:" + "@tauri-apps/api": "^2.0.0" } } diff --git a/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml b/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml deleted file mode 100644 index 5f711fd5e1..0000000000 --- a/plugins/http/permissions/autogenerated/commands/fetch_stream_body.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-fetch-stream-body" -description = "Enables the fetch_stream_body command without any pre-configured scope." -commands.allow = ["fetch_stream_body"] - -[[permission]] -identifier = "deny-fetch-stream-body" -description = "Denies the fetch_stream_body command without any pre-configured scope." -commands.deny = ["fetch_stream_body"] diff --git a/plugins/http/permissions/autogenerated/reference.md b/plugins/http/permissions/autogenerated/reference.md index 19a3c4ebc1..4126f0b9ea 100644 --- a/plugins/http/permissions/autogenerated/reference.md +++ b/plugins/http/permissions/autogenerated/reference.md @@ -15,7 +15,7 @@ All fetch operations are enabled. - `allow-fetch` - `allow-fetch-cancel` -- `allow-fetch-stream-body` +- `allow-fetch-read-body` - `allow-fetch-send` ## Permission Table @@ -128,32 +128,6 @@ Enables the fetch_send command without any pre-configured scope. Denies the fetch_send command without any pre-configured scope. - - - - - - -`http:allow-fetch-stream-body` - - - - -Enables the fetch_stream_body command without any pre-configured scope. - - - - - - - -`http:deny-fetch-stream-body` - - - - -Denies the fetch_stream_body command without any pre-configured scope. - diff --git a/plugins/http/permissions/default.toml b/plugins/http/permissions/default.toml index 9c8818db09..b469536ddc 100644 --- a/plugins/http/permissions/default.toml +++ b/plugins/http/permissions/default.toml @@ -17,6 +17,6 @@ All fetch operations are enabled. permissions = [ "allow-fetch", "allow-fetch-cancel", - "allow-fetch-stream-body", + "allow-fetch-read-body", "allow-fetch-send", ] diff --git a/plugins/http/permissions/schemas/schema.json b/plugins/http/permissions/schemas/schema.json index 03caf56e23..794ee20480 100644 --- a/plugins/http/permissions/schemas/schema.json +++ b/plugins/http/permissions/schemas/schema.json @@ -334,16 +334,6 @@ "type": "string", "const": "deny-fetch-send" }, - { - "description": "Enables the fetch_stream_body command without any pre-configured scope.", - "type": "string", - "const": "allow-fetch-stream-body" - }, - { - "description": "Denies the fetch_stream_body command without any pre-configured scope.", - "type": "string", - "const": "deny-fetch-stream-body" - }, { "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n", "type": "string", From bb66d444e1c7911173a4d1afe4466e681a4aa440 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Fri, 14 Mar 2025 11:54:35 +0800 Subject: [PATCH 27/30] fix: add fetch_read_body to lib.rs --- plugins/http/src/commands.rs | 48 ++++++++++++++++++------------------ plugins/http/src/lib.rs | 3 ++- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 1c9e3f0e5c..18953121b7 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -359,30 +359,6 @@ pub fn fetch_cancel(webview: Webview, rid: ResourceId) -> crate:: Ok(()) } -#[command] -pub async fn fetch_read_body( - webview: Webview, - rid: ResourceId, - stream_channel: Channel, -) -> crate::Result<()> { - let res = { - let mut resources_table = webview.resources_table(); - resources_table.take::(rid)? - }; - - let mut res = Arc::into_inner(res).unwrap().0; - - // send response through IPC channel - while let Some(chunk) = res.chunk().await? { - stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?; - } - - // send empty vector when done - stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?; - - Ok(()) -} - #[command] pub async fn fetch_send( webview: Webview, @@ -435,6 +411,30 @@ pub async fn fetch_send( }) } +#[command] +pub async fn fetch_read_body( + webview: Webview, + rid: ResourceId, + stream_channel: Channel, +) -> crate::Result<()> { + let res = { + let mut resources_table = webview.resources_table(); + resources_table.take::(rid)? + }; + + let mut res = Arc::into_inner(res).unwrap().0; + + // send response through IPC channel + while let Some(chunk) = res.chunk().await? { + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk.to_vec()))?; + } + + // send empty vector when done + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(Vec::new()))?; + + Ok(()) +} + // 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 4e11e5612e..65829e0987 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -36,7 +36,8 @@ pub fn init() -> TauriPlugin { .invoke_handler(tauri::generate_handler![ commands::fetch, commands::fetch_cancel, - commands::fetch_send + commands::fetch_send, + commands::fetch_read_body ]) .build() } From 17311a8483f2d27f553f866b628a024b54f79e65 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Fri, 14 Mar 2025 12:06:17 +0800 Subject: [PATCH 28/30] run linting and build --- plugins/http/guest-js/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http/guest-js/index.ts b/plugins/http/guest-js/index.ts index 623781a4ed..4c33f07268 100644 --- a/plugins/http/guest-js/index.ts +++ b/plugins/http/guest-js/index.ts @@ -106,7 +106,7 @@ export interface DangerousSettings { acceptInvalidHostnames?: boolean } -const ERROR_REQUEST_CANCELLED = 'Request canceled' +const ERROR_REQUEST_CANCELLED = 'Request cancelled' /** * Fetch a resource from the network. It returns a `Promise` that resolves to the From bb746768c64945a2bfb14f666b297fbad8a08ed1 Mon Sep 17 00:00:00 2001 From: adrieljss Date: Sat, 15 Mar 2025 00:40:52 +0800 Subject: [PATCH 29/30] build api-iife.js --- plugins/http/api-iife.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http/api-iife.js b/plugins/http/api-iife.js index 83b09d7698..07653dee5b 100644 --- a/plugins/http/api-iife.js +++ b/plugins/http/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[i]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request canceled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,i=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const h=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),_=await f.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of f.headers)h.get(e)||h.set(e,t);const l=(h instanceof Headers?Array.from(h.entries()):Array.isArray(h)?h:Object.entries(h)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=new ReadableStream({start:e=>{const t=new o;t.onmessage=t=>{r?.aborted?e.error(d):(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()},c("plugin:http|fetch_stream_body",{rid:p,streamChannel:t}).catch((t=>{e.error(t)}))}}),p=await c("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:i}}),m=()=>c("plugin:http|fetch_cancel",{rid:p});if(r?.aborted)throw m(),new Error(d);r?.addEventListener("abort",(()=>{m()}));const{status:y,statusText:T,url:g,headers:A}=await c("plugin:http|fetch_send",{rid:p}),R=new Response(w,{status:y,statusText:T});return Object.defineProperty(R,"url",{value:g}),Object.defineProperty(R,"headers",{value:new Headers(A)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,a;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),s.set(this,0),a.set(this,[]),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i==t(this,s,"f"))for(t(this,n,"f").call(this,e),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,a,"f");){const e=t(this,a,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,a,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}else t(this,a,"f")[i]=e}))}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,a=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function c(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const d="Request cancelled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(d);const n=t?.maxRedirections,s=t?.connectTimeout,a=t?.proxy,i=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const h=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),_=await f.arrayBuffer(),u=0!==_.byteLength?Array.from(new Uint8Array(_)):null;for(const[e,t]of f.headers)h.get(e)||h.set(e,t);const l=(h instanceof Headers?Array.from(h.entries()):Array.isArray(h)?h:Object.entries(h)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(d);const w=await c("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:l,data:u,maxRedirections:n,connectTimeout:s,proxy:a,danger:i}}),p=()=>c("plugin:http|fetch_cancel",{rid:w});if(r?.aborted)throw p(),new Error(d);r?.addEventListener("abort",(()=>{p()}));const{status:y,statusText:m,url:T,headers:g,rid:A}=await c("plugin:http|fetch_send",{rid:w}),R=new ReadableStream({start:e=>{const t=new o;t.onmessage=t=>{r?.aborted?e.error(d):(t instanceof ArrayBuffer?0!=t.byteLength:0!=t.length)?e.enqueue(new Uint8Array(t)):e.close()},c("plugin:http|fetch_read_body",{rid:A,streamChannel:t}).catch((t=>{e.error(t)}))}}),b=new Response(R,{status:y,statusText:m});return Object.defineProperty(b,"url",{value:T}),Object.defineProperty(b,"headers",{value:new Headers(g)}),b},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} From 344a355015303f0176ab8664f6d2d5979abb6507 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Sat, 15 Mar 2025 02:05:31 +0200 Subject: [PATCH 30/30] Update http-stream-support.md --- .changes/http-stream-support.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changes/http-stream-support.md b/.changes/http-stream-support.md index a0db5bfda5..8c33f64648 100644 --- a/.changes/http-stream-support.md +++ b/.changes/http-stream-support.md @@ -1,6 +1,6 @@ --- -"http": minor -"http-js": minor +"http": patch +"http-js": patch --- -Fix HTTP stream not streaming the body. \ No newline at end of file +Fix `fetch` blocking until the whole response is read even if it was a streaming response.