stream.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // using tauri command to send request
  2. // see src-tauri/src/stream.rs, and src-tauri/src/main.rs
  3. // 1. invoke('stream_fetch', {url, method, headers, body}), get response with headers.
  4. // 2. listen event: `stream-response` multi times to get body
  5. type ResponseEvent = {
  6. id: number;
  7. payload: {
  8. request_id: number;
  9. status?: number;
  10. chunk?: number[];
  11. };
  12. };
  13. type StreamResponse = {
  14. request_id: number;
  15. status: number;
  16. status_text: string;
  17. headers: Record<string, string>;
  18. };
  19. export function fetch(url: string, options?: RequestInit): Promise<any> {
  20. if (window.__TAURI__) {
  21. const {
  22. signal,
  23. method = "GET",
  24. headers: _headers = {},
  25. body = [],
  26. } = options || {};
  27. let unlisten: Function | undefined;
  28. let request_id = 0;
  29. const ts = new TransformStream();
  30. const writer = ts.writable.getWriter();
  31. const close = () => {
  32. unlisten && unlisten();
  33. writer.ready.then(() => {
  34. try {
  35. writer.releaseLock();
  36. ts.writable.close();
  37. } catch (e) {
  38. console.error(e);
  39. }
  40. });
  41. };
  42. if (signal) {
  43. signal.addEventListener("abort", () => close());
  44. }
  45. // @ts-ignore 2. listen response multi times, and write to Response.body
  46. window.__TAURI__.event
  47. .listen("stream-response", (e: ResponseEvent) => {
  48. const { request_id: rid, chunk, status } = e?.payload || {};
  49. if (request_id != rid) {
  50. return;
  51. }
  52. if (chunk) {
  53. writer &&
  54. writer.ready.then(() => {
  55. writer && writer.write(new Uint8Array(chunk));
  56. });
  57. } else if (status === 0) {
  58. // end of body
  59. close();
  60. }
  61. })
  62. .then((u: Function) => (unlisten = u));
  63. const headers = {
  64. Accept: "*",
  65. Connection: "close",
  66. Origin: "http://localhost:3000",
  67. Referer: "http://localhost:3000/",
  68. "Sec-Fetch-Dest": "empty",
  69. "Sec-Fetch-Mode": "cors",
  70. "Sec-Fetch-Site": "cross-site",
  71. "User-Agent": navigator.userAgent,
  72. };
  73. for (const item of new Headers(_headers || {})) {
  74. headers[item[0]] = item[1];
  75. }
  76. return window.__TAURI__
  77. .invoke("stream_fetch", {
  78. method,
  79. url,
  80. headers,
  81. // TODO FormData
  82. body:
  83. typeof body === "string"
  84. ? Array.from(new TextEncoder().encode(body))
  85. : [],
  86. })
  87. .then((res: StreamResponse) => {
  88. request_id = res.request_id;
  89. const { status, status_text: statusText, headers } = res;
  90. return new Response(ts.readable, { status, statusText, headers });
  91. })
  92. .catch((e) => {
  93. console.error("stream error", e);
  94. throw e;
  95. });
  96. }
  97. return window.fetch(url, options);
  98. }