108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
// using tauri command to send request
|
|
// see src-tauri/src/stream.rs, and src-tauri/src/main.rs
|
|
// 1. invoke('stream_fetch', {url, method, headers, body}), get response with headers.
|
|
// 2. listen event: `stream-response` multi times to get body
|
|
|
|
type ResponseEvent = {
|
|
id: number;
|
|
payload: {
|
|
request_id: number;
|
|
status?: number;
|
|
chunk?: number[];
|
|
};
|
|
};
|
|
|
|
type StreamResponse = {
|
|
request_id: number;
|
|
status: number;
|
|
status_text: string;
|
|
headers: Record<string, string>;
|
|
};
|
|
|
|
export function fetch(url: string, options?: RequestInit): Promise<any> {
|
|
if (window.__TAURI__) {
|
|
const {
|
|
signal,
|
|
method = "GET",
|
|
headers: _headers = {},
|
|
body = [],
|
|
} = options || {};
|
|
let unlisten: Function | undefined;
|
|
let setRequestId: Function | undefined;
|
|
const requestIdPromise = new Promise((resolve) => (setRequestId = resolve));
|
|
const ts = new TransformStream();
|
|
const writer = ts.writable.getWriter();
|
|
|
|
let closed = false;
|
|
const close = () => {
|
|
if (closed) return;
|
|
closed = true;
|
|
unlisten && unlisten();
|
|
writer.ready.then(() => {
|
|
writer.close().catch((e) => console.error(e));
|
|
});
|
|
};
|
|
|
|
if (signal) {
|
|
signal.addEventListener("abort", () => close());
|
|
}
|
|
// @ts-ignore 2. listen response multi times, and write to Response.body
|
|
window.__TAURI__.event
|
|
.listen("stream-response", (e: ResponseEvent) =>
|
|
requestIdPromise.then((request_id) => {
|
|
const { request_id: rid, chunk, status } = e?.payload || {};
|
|
if (request_id != rid) {
|
|
return;
|
|
}
|
|
if (chunk) {
|
|
writer.ready.then(() => {
|
|
writer.write(new Uint8Array(chunk));
|
|
});
|
|
} else if (status === 0) {
|
|
// end of body
|
|
close();
|
|
}
|
|
}),
|
|
)
|
|
.then((u: Function) => (unlisten = u));
|
|
|
|
const headers: Record<string, string> = {
|
|
Accept: "application/json, text/plain, */*",
|
|
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
"User-Agent": navigator.userAgent,
|
|
};
|
|
for (const item of new Headers(_headers || {})) {
|
|
headers[item[0]] = item[1];
|
|
}
|
|
return window.__TAURI__
|
|
.invoke("stream_fetch", {
|
|
method: method.toUpperCase(),
|
|
url,
|
|
headers,
|
|
// TODO FormData
|
|
body:
|
|
typeof body === "string"
|
|
? Array.from(new TextEncoder().encode(body))
|
|
: [],
|
|
})
|
|
.then((res: StreamResponse) => {
|
|
const { request_id, status, status_text: statusText, headers } = res;
|
|
setRequestId?.(request_id);
|
|
const response = new Response(ts.readable, {
|
|
status,
|
|
statusText,
|
|
headers,
|
|
});
|
|
if (status >= 300) {
|
|
setTimeout(close, 100);
|
|
}
|
|
return response;
|
|
})
|
|
.catch((e) => {
|
|
console.error("stream error", e);
|
|
throw e;
|
|
});
|
|
}
|
|
return window.fetch(url, options);
|
|
}
|