|
1 | 1 | // Attempt to use readable-stream if available, attempt to use the built-in stream module.
|
2 | 2 |
|
3 | 3 | const ApiError = require("./error");
|
| 4 | +const { EventSourceParserStream } = require("eventsource-parser/stream"); |
4 | 5 |
|
5 | 6 | /**
|
6 | 7 | * A server-sent event.
|
@@ -38,87 +39,49 @@ class ServerSentEvent {
|
38 | 39 | *
|
39 | 40 | * @param {object} config
|
40 | 41 | * @param {string} config.url The URL to connect to.
|
41 |
| - * @param {any} [config.EventSource] A standards compliant EventSource implementation. |
42 |
| - * @param {any} [config.ReadableStream] A standards compliant ReadableStream implementation. |
| 42 | + * @param {typeof fetch} [config.fetch] The URL to connect to. |
43 | 43 | * @param {object} [config.options] The EventSource options.
|
44 | 44 | * @returns {Promise<ReadableStream & AsyncIterable<ServerSentEvent>>}
|
45 | 45 | */
|
46 |
| -async function createReadableStream({ |
47 |
| - url, |
48 |
| - EventSource = globalThis.EventSource, |
49 |
| - ReadableStream = globalThis.ReadableStream, |
50 |
| - options = {}, |
51 |
| -}) { |
52 |
| - const EventSourceClass = EventSource |
53 |
| - ? EventSource |
54 |
| - : (await import("eventsource")).default; |
55 |
| - const source = new EventSourceClass(url, options); |
56 |
| - |
57 |
| - const stream = new ReadableStream({ |
58 |
| - cancel() { |
59 |
| - source.close(); |
60 |
| - }, |
61 |
| - |
| 46 | +function createReadableStream({ url, fetch, options = {} }) { |
| 47 | + return new ReadableStream({ |
62 | 48 | async start(controller) {
|
63 |
| - return new Promise((resolve) => { |
64 |
| - source.addEventListener("output", (evt) => { |
65 |
| - const entry = new ServerSentEvent( |
66 |
| - evt.type, |
67 |
| - evt.data, |
68 |
| - evt.lastEventId |
69 |
| - ); |
70 |
| - controller.enqueue(entry); |
71 |
| - }); |
72 |
| - |
73 |
| - source.addEventListener("done", (evt) => { |
74 |
| - const entry = new ServerSentEvent( |
75 |
| - evt.type, |
76 |
| - evt.data, |
77 |
| - evt.lastEventId |
78 |
| - ); |
79 |
| - controller.enqueue(entry); |
80 |
| - source.close(); |
81 |
| - controller.close(); |
82 |
| - }); |
| 49 | + const init = { |
| 50 | + ...options, |
| 51 | + headers: { |
| 52 | + ...options.headers, |
| 53 | + Accept: "text/event-stream", |
| 54 | + }, |
| 55 | + }; |
| 56 | + const response = await fetch(url, init); |
83 | 57 |
|
84 |
| - source.addEventListener("open", (_evt) => { |
85 |
| - resolve(); |
86 |
| - }); |
| 58 | + if (!response.ok) { |
| 59 | + const text = await response.text(); |
| 60 | + const request = new Request(url, init); |
| 61 | + controller.error( |
| 62 | + new ApiError( |
| 63 | + `Request to ${url} failed with status ${response.status}`, |
| 64 | + request, |
| 65 | + response |
| 66 | + ) |
| 67 | + ); |
| 68 | + } |
87 | 69 |
|
88 |
| - source.addEventListener("error", (evt) => { |
89 |
| - // HTTP Error |
90 |
| - if (typeof evt.status === "number") { |
91 |
| - source.close(); |
92 |
| - controller.error( |
93 |
| - new ApiError( |
94 |
| - `Request to ${url} failed with status ${evt.status} ${ |
95 |
| - evt.message ?? "" |
96 |
| - }`.trim() |
97 |
| - ) |
98 |
| - ); |
99 |
| - return; |
100 |
| - } |
101 |
| - |
102 |
| - // Connection closed |
103 |
| - if (!evt.message && source.readyState === 0) { |
104 |
| - controller.close(); |
105 |
| - source.close(); |
106 |
| - return; |
107 |
| - } |
108 |
| - |
109 |
| - // Other |
110 |
| - source.close(); |
111 |
| - controller.error(new Error(evt.message ?? "Unexpected Error")); |
112 |
| - }); |
113 |
| - |
114 |
| - options.signal?.addEventListener("abort", () => { |
115 |
| - source.close(); |
116 |
| - }); |
117 |
| - }); |
| 70 | + const stream = response.body |
| 71 | + .pipeThrough(new TextDecoderStream()) |
| 72 | + .pipeThrough(new EventSourceParserStream()); |
| 73 | + for await (const event of stream) { |
| 74 | + if (event.event === "error") { |
| 75 | + controller.error(new Error(event.data)); |
| 76 | + } else { |
| 77 | + controller.enqueue( |
| 78 | + new ServerSentEvent(event.event, event.data, event.id) |
| 79 | + ); |
| 80 | + } |
| 81 | + } |
| 82 | + controller.close(); |
118 | 83 | },
|
119 | 84 | });
|
120 |
| - |
121 |
| - return stream; |
122 | 85 | }
|
123 | 86 |
|
124 | 87 | module.exports = {
|
|
0 commit comments