diff --git a/Cargo.lock b/Cargo.lock index 5e38d56..e36e251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,7 +1202,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "which", ] @@ -1861,10 +1861,47 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2271,6 +2308,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "database" version = "0.0.0" @@ -2354,7 +2397,6 @@ dependencies = [ "proxy", "query", "rand 0.9.2", - "reqwest 0.0.0", "ring", "rusqlite", "serde", @@ -2366,6 +2408,7 @@ dependencies = [ "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", "tauri-plugin-fs", + "tauri-plugin-http", "tauri-plugin-opener", "tauri-plugin-process", "tauri-plugin-single-instance", @@ -3507,9 +3550,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -4174,6 +4219,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "tower-service", + "webpki-roots", ] [[package]] @@ -5090,6 +5136,12 @@ dependencies = [ "hashbrown 0.15.3", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -5458,9 +5510,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -6695,6 +6747,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + [[package]] name = "psm" version = "0.1.26" @@ -6724,6 +6782,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "query" version = "0.0.0" @@ -6751,6 +6819,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.2", + "rustls 0.23.35", + "socket2 0.6.0", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.2", + "rustls 0.23.35", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -7102,13 +7225,17 @@ checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64 0.22.1", "bytes", + "cookie", + "cookie_store 0.21.1", "encoding_rs", "futures-core", "futures-util", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", + "hyper-rustls 0.27.5", "hyper-tls", "hyper-util", "ipnet", @@ -7120,13 +7247,18 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.35", "rustls-pemfile 2.2.0", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", "tower-service", @@ -7135,6 +7267,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", + "webpki-roots", "windows-registry 0.4.0", ] @@ -7465,6 +7598,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -7590,6 +7729,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] @@ -8839,9 +8979,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" dependencies = [ "anyhow", "dunce", @@ -8857,8 +8997,32 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-http" +version = "2.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd512048e1985b7ec78f96d99083e2ddaf7e0d906b2b63c44ce5bb8b894067" +dependencies = [ + "bytes", + "cookie_store 0.22.1", + "data-url", + "http 1.3.1", + "regex", + "reqwest 0.12.15", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "tokio", "url", + "urlpattern", ] [[package]] @@ -9188,30 +9352,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -9393,6 +9557,21 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + [[package]] name = "toml_datetime" version = "0.6.9" @@ -9411,6 +9590,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -10149,6 +10337,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "2.0.2" diff --git a/Cargo.toml b/Cargo.toml index 8d10b1e..c71d37e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ tauri-plugin-dialog = "2.7.0" tauri-plugin-fs = "2.5.0" tauri-plugin-process = "2.3.1" tauri-plugin-clipboard-manager = "2.3.2" +tauri-plugin-http = "2.5.9" serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } @@ -47,7 +48,6 @@ sqlstmt = { path = "./src-crates/sqlstmt" } kvdb = { path = "./src-crates/kvdb" } backup = { path = "./src-crates/backup" } proxy = { path = "./src-crates/proxy" } -reqwest = { path = "./src-crates/reqwest" } client-store = { path = "./src-crates/client-store" } dir = { path = "./src-crates/dir" } whoami = "1.6.0" diff --git a/capabilities/default.json b/capabilities/default.json index b2d138a..8ae4986 100644 --- a/capabilities/default.json +++ b/capabilities/default.json @@ -29,6 +29,15 @@ "path": "**" } ] + }, + { + "identifier": "http:default", + "allow": [ + { "url": "https://*" }, + { "url": "https://*:*" }, + { "url": "http://*" }, + { "url": "http://*:*" } + ] } ] } diff --git a/package.json b/package.json index 7c04fca..547a3a7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.7.0", "@tauri-apps/plugin-fs": "^2.5.0", + "@tauri-apps/plugin-http": "^2.5.9", "@tauri-apps/plugin-opener": "^2.5.3", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2275b17..1c7c1f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@tauri-apps/plugin-fs': specifier: ^2.5.0 version: 2.5.0 + '@tauri-apps/plugin-http': + specifier: ^2.5.9 + version: 2.5.9 '@tauri-apps/plugin-opener': specifier: ^2.5.3 version: 2.5.3 @@ -937,6 +940,9 @@ packages: '@tauri-apps/api@2.10.1': resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} + '@tauri-apps/api@2.11.0': + resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} + '@tauri-apps/cli-darwin-arm64@2.10.1': resolution: {integrity: sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==} engines: {node: '>= 10'} @@ -1022,6 +1028,9 @@ packages: '@tauri-apps/plugin-fs@2.5.0': resolution: {integrity: sha512-c83kbz61AK+rKjhS+je9+stIO27nXj7p9cqeg36TwkIUtxpCFTttlHHtqon6h6FN54cXjyAjlMPOJcW3mwE5XQ==} + '@tauri-apps/plugin-http@2.5.9': + resolution: {integrity: sha512-lCiY0+vs4HvIUSvZrBs8TC3TiCB0MOPRmiUjTq4prW7SlcJE2jdLeT6KBsJrT9Tlplufl7W1pY6SFAO3gCWxDA==} + '@tauri-apps/plugin-opener@2.5.3': resolution: {integrity: sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==} @@ -3019,6 +3028,8 @@ snapshots: '@tauri-apps/api@2.10.1': {} + '@tauri-apps/api@2.11.0': {} + '@tauri-apps/cli-darwin-arm64@2.10.1': optional: true @@ -3078,6 +3089,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.10.1 + '@tauri-apps/plugin-http@2.5.9': + dependencies: + '@tauri-apps/api': 2.11.0 + '@tauri-apps/plugin-opener@2.5.3': dependencies: '@tauri-apps/api': 2.10.1 diff --git a/src-tauri/http.rs b/src-tauri/http.rs deleted file mode 100644 index aa795c6..0000000 --- a/src-tauri/http.rs +++ /dev/null @@ -1,55 +0,0 @@ -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use tauri::command; - -#[derive(Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum Method { - Get, - Post, -} - -#[derive(Serialize)] -pub struct Response { - pub status: u16, - pub body: String, -} - -#[command] -pub async fn fetch( - method: Method, - url: String, - headers: Option>, - body: Option, -) -> Result { - // fetch is called very rarely, so we create a new Client each time; if needed in the future, a global Client can be created in LazyLock - let client = Client::new(); - - let mut request = match method { - Method::Get => client.get(&url), - Method::Post => client.post(&url), - }; - if let Some(headers) = headers { - for (key, value) in headers { - request = request.header(key, value); - } - } - - if let Some(body) = body { - request = request.body(body); - } - - let response = request - .send() - .await - .map_err(|e| format!("HTTP request failed: {:?}", e))?; - - let status = response.status().as_u16(); - let body = response - .text() - .await - .map_err(|e| format!("Failed to read HTTP response: {:?}", e))?; - - Ok(Response { status, body }) -} diff --git a/src-tauri/main.rs b/src-tauri/main.rs index 6d82363..bae886e 100644 --- a/src-tauri/main.rs +++ b/src-tauri/main.rs @@ -7,7 +7,6 @@ mod backup; mod client; mod database; mod device; -mod http; mod ipc; mod keychain; mod lifecycle; @@ -42,6 +41,7 @@ fn main() { .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_updater::Builder::new().build()); @@ -61,8 +61,6 @@ fn main() { // Device device::hostname, device::font_families, - // HTTP - http::fetch, // Keychain keychain::set_password, keychain::get_password, diff --git a/src-web/pages/database/ai/services.ts b/src-web/pages/database/ai/services.ts index d285422..a1a4e94 100644 --- a/src-web/pages/database/ai/services.ts +++ b/src-web/pages/database/ai/services.ts @@ -1,6 +1,7 @@ import { createAnthropic } from '@ai-sdk/anthropic' import { createGoogleGenerativeAI } from '@ai-sdk/google' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' +import { fetch as tauriFetch } from '@tauri-apps/plugin-http' import { ChatAddToolApproveResponseFunction, ChatRequestOptions, @@ -94,12 +95,17 @@ const createLanguageModel = (config: ProviderConfig, modelID: string): LanguageM headers: { 'anthropic-dangerous-direct-browser-access': 'true', 'dangerouslyAllowBrowser': 'true' - } + }, + fetch: tauriFetch }) return anthropic(modelID) } case ProviderType.GoogleGemini: { - const googleGemini = createGoogleGenerativeAI({ apiKey: config.apiKey, baseURL }) + const googleGemini = createGoogleGenerativeAI({ + apiKey: config.apiKey, + baseURL, + fetch: tauriFetch + }) return googleGemini(modelID) } case ProviderType.DeepSeek: @@ -115,7 +121,8 @@ const createLanguageModel = (config: ProviderConfig, modelID: string): LanguageM const openai = createOpenAICompatible({ name: 'openai-compatible', apiKey: config.apiKey, - baseURL: baseURL ?? defaultBaseURL(config.type) + baseURL: baseURL ?? defaultBaseURL(config.type), + fetch: tauriFetch }) return openai(modelID) } diff --git a/src-web/pages/settings/provider/api.ts b/src-web/pages/settings/provider/api.ts index 617e70b..ec3c88e 100644 --- a/src-web/pages/settings/provider/api.ts +++ b/src-web/pages/settings/provider/api.ts @@ -1,5 +1,6 @@ +import { fetch as tauriFetch } from '@tauri-apps/plugin-http' import z, { ZodType } from 'zod' -import { Http, ProviderConfig, ProviderModelConfig, ProviderType } from '../../../tauri' +import { ProviderConfig, ProviderModelConfig, ProviderType } from '../../../tauri' export const defaultBaseURL = (type: ProviderType): string => { return { @@ -28,11 +29,11 @@ export const normalizeBaseURL = (url: string, emptyFallback: T): string | T = export const fetchModels = async (config: ProviderConfig): Promise => { const fetchJSON = async (url: string, schema: T, headers?: Record) => { - const res = await Http.fetch(url, { method: 'GET', headers }) + const res = await tauriFetch(url, { method: 'GET', headers }) if (res.status !== 200) { throw `StatusCode: ${res.status}\nFrom: ${url}` } - const parsed = schema.safeParse(res.json()) + const parsed = schema.safeParse(await res.json()) if (!parsed.success) { const issue = parsed.error.issues[0] const path = issue.path.length > 0 ? ` at '${issue.path.join('.')}'` : '' @@ -101,7 +102,12 @@ export const fetchModels = async (config: ProviderConfig): Promise json.data) } case ProviderType.GoogleGemini: { diff --git a/src-web/tauri/http.ts b/src-web/tauri/http.ts deleted file mode 100644 index 49f8261..0000000 --- a/src-web/tauri/http.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { invoke } from '@tauri-apps/api/core' - -interface FetchData { - status: number - body: string -} - -class FetchResponse { - private readonly data: FetchData - - constructor(data: FetchData) { - this.data = data - } - - get status(): number { - return this.data.status - } - - public text(): string { - return this.data.body - } - - public json(): T { - return JSON.parse(this.data.body) as T - } -} - -export class Http { - public static fetch( - url: string, - { - method, - headers, - body - }: { - method: 'GET' | 'POST' - headers?: Record - body?: string - } - ) { - return invoke('fetch', { - method, - url, - headers: headers ?? {}, - body: body ?? null - }).then((data) => new FetchResponse(data)) - } -} diff --git a/src-web/tauri/index.ts b/src-web/tauri/index.ts index 87ae0de..de0f7e8 100644 --- a/src-web/tauri/index.ts +++ b/src-web/tauri/index.ts @@ -11,4 +11,3 @@ export * from './lifecycle' export * from './keychain' export * from './event' export * from './backup' -export * from './http' diff --git a/src-web/utils/license.ts b/src-web/utils/license.ts index a509555..be5e260 100644 --- a/src-web/utils/license.ts +++ b/src-web/utils/license.ts @@ -1,4 +1,5 @@ -import { getPassword, setPassword, deletePassword, Http } from '../tauri' +import { fetch as tauriFetch } from '@tauri-apps/plugin-http' +import { getPassword, setPassword, deletePassword } from '../tauri' export interface License { key: string @@ -66,7 +67,7 @@ export class LicenseApi { } return new Promise(async (success, error) => { try { - let res = await Http.fetch(url.toString(), { + let res = await tauriFetch(url.toString(), { method: data.method, headers: { 'Content-Type': 'application/json' @@ -74,10 +75,10 @@ export class LicenseApi { body }) if (res.status !== 200) { - const rst: { message: string } = res.json() + const rst: { message: string } = await res.json() return error(rst.message) } - success(res.json()) + success(await res.json()) } catch (err: any) { error(err.toString()) }