diff --git a/.changeset/itchy-terms-peel.md b/.changeset/itchy-terms-peel.md new file mode 100644 index 000000000..14ab4c27e --- /dev/null +++ b/.changeset/itchy-terms-peel.md @@ -0,0 +1,5 @@ +--- +'@gitbook/api': minor +--- + +Use fetch and a custom EventSource parsing to support node/browser/worker for streaming operations diff --git a/package-lock.json b/package-lock.json index 028d1e242..09925a202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5003,11 +5003,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/@microsoft/fetch-event-source": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", - "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" - }, "node_modules/@miniflare/cache": { "version": "2.13.0", "dev": true, @@ -8391,6 +8386,14 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.1.tgz", + "integrity": "sha512-3Ej2iLj6ZnX+5CMxqyUb8syl9yVZwcwm8IIMrOJlF7I51zxOOrRlU3zxSb/6hFbl03ts1ZxHAGJdWLZOLyKG7w==", + "engines": { + "node": ">=14.18" + } + }, "node_modules/execa": { "version": "6.1.0", "dev": true, @@ -13938,8 +13941,8 @@ "name": "@gitbook/api", "version": "0.31.0", "dependencies": { - "@microsoft/fetch-event-source": "^2.0.1", - "event-iterator": "^2.0.0" + "event-iterator": "^2.0.0", + "eventsource-parser": "^1.1.1" }, "devDependencies": { "@gitbook/eslint-config": "*", diff --git a/packages/api/package.json b/packages/api/package.json index a98959557..8e037c91f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,8 +12,8 @@ "dist/**" ], "dependencies": { - "@microsoft/fetch-event-source": "^2.0.1", - "event-iterator": "^2.0.0" + "event-iterator": "^2.0.0", + "eventsource-parser": "^1.1.1" }, "devDependencies": { "swagger-typescript-api": "^13.0.3", diff --git a/packages/api/templates/http-client.ejs b/packages/api/templates/http-client.ejs index 78360f9f3..4dfc9f7d4 100644 --- a/packages/api/templates/http-client.ejs +++ b/packages/api/templates/http-client.ejs @@ -2,7 +2,7 @@ const { apiConfig, generateResponses, config } = it; %> import { EventIterator } from 'event-iterator'; -import { fetchEventSource } from '@microsoft/fetch-event-source'; +import { createParser, type ParsedEvent, type ReconnectInterval } from 'eventsource-parser'; export type QueryParamsType = Record; export type ResponseFormat = keyof Omit; @@ -241,28 +241,55 @@ export class HttpClient { const { query: requestQuery, ...requestParams } = this.mergeRequestParams(params, secureParams); const queryString = requestQuery ? this.toQueryString(requestQuery) : undefined; - await fetchEventSource( + const response = await this.customFetch( `${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { ...requestParams, signal: cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal, - onmessage(event) { - if (event.data === 'done') { - queue.stop(); - } else { - const data = JSON.parse(event.data); - queue.push(data); - } - }, - onclose() { - queue.stop(); - }, - onerror(error) { - queue.fail(error); + } + ) + + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText}`); + return; + } + + const reader = response.body.getReader(); + + const stop = () => { + reader.cancel(); + queue.stop(); + } + + const parser = createParser((event: ParsedEvent | ReconnectInterval) => { + if (event.type === 'event') { + if (event.data === 'done') { + stop(); + } else { + const data = JSON.parse(event.data); + queue.push(data); } - }, - ); - })(); + } + }) + + const decoder = new TextDecoder(); + while (true) { + const { done, value } = await reader.read(); + + if (value) { + parser.feed(decoder.decode(value)) + } + + if (done) { + break; + } + } + + stop(); + })() + .catch(error => { + queue.fail(error); + }); } ); };