// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT /** * Make HTTP requests with the Rust backend. * * ## Security * * This API has a scope configuration that forces you to restrict the URLs and paths that can be accessed using glob patterns. * * For instance, this scope configuration only allows making HTTP requests to the GitHub API for the `tauri-apps` organization: * ```json * { * "plugins": { * "http": { * "scope": ["https://api.github.com/repos/tauri-apps/*"] * } * } * } * ``` * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access. * * @module */ import { invoke } from "@tauri-apps/api/core"; /** * Configuration of a proxy that a Client should pass requests to. * * @since 2.0.0 */ export interface Proxy { /** * Proxy all traffic to the passed URL. */ all?: string | ProxyConfig; /** * Proxy all HTTP traffic to the passed URL. */ http?: string | ProxyConfig; /** * Proxy all HTTPS traffic to the passed URL. */ https?: string | ProxyConfig; } export interface ProxyConfig { /** * The URL of the proxy server. */ url: string; /** * Set the `Proxy-Authorization` header using Basic auth. */ basicAuth?: { username: string; password: string; }; /** * A configuration for filtering out requests that shouldn't be proxied. * Entries are expected to be comma-separated (whitespace between entries is ignored) */ noProxy?: string; } /** * Options to configure the Rust client used to make fetch requests * * @since 2.0.0 */ export interface ClientOptions { /** * Defines the maximum number of redirects the client should follow. * If set to 0, no redirects will be followed. */ maxRedirections?: number; /** Timeout in milliseconds */ connectTimeout?: number; /** * Configuration of a proxy that a Client should pass requests to. */ proxy?: Proxy; } /** * Fetch a resource from the network. It returns a `Promise` that resolves to the * `Response` to that `Request`, whether it is successful or not. * * @example * ```typescript * const response = await fetch("http://my.json.host/data.json"); * console.log(response.status); // e.g. 200 * console.log(response.statusText); // e.g. "OK" * const jsonData = await response.json(); * ``` * * @since 2.0.0 */ export async function fetch( input: URL | Request | string, init?: RequestInit & ClientOptions, ): Promise { const maxRedirections = init?.maxRedirections; const connectTimeout = init?.connectTimeout; const proxy = init?.proxy; // Remove these fields before creating the request if (init) { delete init.maxRedirections; delete init.connectTimeout; delete init.proxy; } const signal = init?.signal; const headers = init?.headers ? init.headers instanceof Headers ? init.headers : new Headers(init.headers) : new Headers(); const req = new Request(input, init); const buffer = await req.arrayBuffer(); const data = buffer.byteLength !== 0 ? Array.from(new Uint8Array(buffer)) : null; // append new headers created by the browser `Request` implementation, // if not already declared by the caller of this function for (const [key, value] of req.headers) { if (!headers.get(key)) { headers.set(key, value); } } const headersArray = headers instanceof Headers ? Array.from(headers.entries()) : Array.isArray(headers) ? headers : Object.entries(headers); const mappedHeaders: Array<[string, string]> = headersArray.map( ([name, val]) => [ name, // we need to ensure we have all header values as strings // eslint-disable-next-line typeof val === "string" ? val : (val as any).toString(), ], ); const rid = await invoke("plugin:http|fetch", { clientConfig: { method: req.method, url: req.url, headers: mappedHeaders, data, maxRedirections, connectTimeout, proxy, }, }); signal?.addEventListener("abort", () => { void invoke("plugin:http|fetch_cancel", { rid, }); }); 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 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, { headers: responseHeaders, status, statusText, }, ); // url is read only but seems like we can do this Object.defineProperty(res, "url", { value: url }); return res; }