stream.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // using tauri register_uri_scheme_protocol, register `stream:` protocol
  2. // see src-tauri/src/stream.rs, and src-tauri/src/main.rs
  3. // 1. window.fetch(`stream://localhost/${fetchUrl}`), get request_id
  4. // 2. listen event: `stream-response` multi times to get response headers and body
  5. type ResponseEvent = {
  6. id: number;
  7. payload: {
  8. request_id: number;
  9. status?: number;
  10. error?: string;
  11. name?: string;
  12. value?: string;
  13. chunk?: number[];
  14. };
  15. };
  16. export function fetch(url: string, options?: RequestInit): Promise<any> {
  17. if (window.__TAURI__) {
  18. const tauriUri = window.__TAURI__.convertFileSrc(url, "stream");
  19. const { signal, ...rest } = options || {};
  20. return window
  21. .fetch(tauriUri, rest)
  22. .then((r) => r.text())
  23. .then((rid) => parseInt(rid))
  24. .then((request_id: number) => {
  25. // 1. using event to get status and statusText and headers, and resolve it
  26. let resolve: Function | undefined;
  27. let reject: Function | undefined;
  28. let status: number;
  29. let writable: WritableStream | undefined;
  30. let writer: WritableStreamDefaultWriter | undefined;
  31. const headers = new Headers();
  32. let unlisten: Function | undefined;
  33. if (signal) {
  34. signal.addEventListener("abort", () => {
  35. // Reject the promise with the abort reason.
  36. unlisten && unlisten();
  37. reject && reject(signal.reason);
  38. });
  39. }
  40. // @ts-ignore 2. listen response multi times, and write to Response.body
  41. window.__TAURI__.event
  42. .listen("stream-response", (e: ResponseEvent) => {
  43. const { id, payload } = e;
  44. const {
  45. request_id: rid,
  46. status: _status,
  47. name,
  48. value,
  49. error,
  50. chunk,
  51. } = payload;
  52. if (request_id != rid) {
  53. return;
  54. }
  55. /**
  56. * 1. get status code
  57. * 2. get headers
  58. * 3. start get body, then resolve response
  59. * 4. get body chunk
  60. */
  61. if (error) {
  62. unlisten && unlisten();
  63. return reject && reject(error);
  64. } else if (_status) {
  65. status = _status;
  66. } else if (name && value) {
  67. headers.append(name, value);
  68. } else if (chunk) {
  69. if (resolve) {
  70. const ts = new TransformStream();
  71. writable = ts.writable;
  72. writer = writable.getWriter();
  73. resolve(new Response(ts.readable, { status, headers }));
  74. resolve = undefined;
  75. }
  76. writer &&
  77. writer.ready.then(() => {
  78. writer && writer.write(new Uint8Array(chunk));
  79. });
  80. } else if (_status === 0) {
  81. // end of body
  82. unlisten && unlisten();
  83. writer &&
  84. writer.ready.then(() => {
  85. writer && writer.releaseLock();
  86. writable && writable.close();
  87. });
  88. }
  89. })
  90. .then((u: Function) => (unlisten = u));
  91. return new Promise(
  92. (_resolve, _reject) => ([resolve, reject] = [_resolve, _reject]),
  93. );
  94. });
  95. }
  96. return window.fetch(url, options);
  97. }