diff --git a/packages/core/js-sdk/package.json b/packages/core/js-sdk/package.json index eec9957449..ffdebe04ca 100644 --- a/packages/core/js-sdk/package.json +++ b/packages/core/js-sdk/package.json @@ -28,6 +28,7 @@ "typescript": "^5.1.6" }, "dependencies": { + "fetch-event-stream": "^0.1.5", "qs": "^6.12.1" }, "scripts": { diff --git a/packages/core/js-sdk/src/client.ts b/packages/core/js-sdk/src/client.ts index fd12db8634..2365cf22a8 100644 --- a/packages/core/js-sdk/src/client.ts +++ b/packages/core/js-sdk/src/client.ts @@ -1,5 +1,13 @@ import qs from "qs" -import { ClientFetch, Config, FetchArgs, FetchInput, Logger } from "./types" +import { events } from "fetch-event-stream" +import { + ClientFetch, + Config, + FetchArgs, + FetchInput, + FetchStreamResponse, + Logger, +} from "./types" export const PUBLISHABLE_KEY_HEADER = "x-publishable-api-key" @@ -110,11 +118,39 @@ export class Client { * @param init: FetchArgs * @returns Promise */ - fetch(input: FetchInput, init?: FetchArgs): Promise { return this.fetch_(input, init) as unknown as Promise } + /** + * `fetchStream` is a helper method to deal with server-sent events. It returns an object with a stream and an abort function. + * It follows a very similar interface to `fetch`, with the return value being an async generator. + * The stream is an async generator that yields `ServerSentEventMessage` objects, which contains the event name, stringified data, and few other properties. + * The caller is responsible for handling `disconnect` events and aborting the stream. The caller is also responsible for parsing the data field. + * + * @param input: FetchInput + * @param init: FetchArgs + * @returns FetchStreamResponse + */ + async fetchStream( + input: FetchInput, + init?: FetchArgs + ): Promise { + let abort = new AbortController() + + let res = await this.fetch_(input, { + ...init, + signal: abort.signal, + headers: { ...init?.headers, accept: "text/event-stream" }, + }) + + if (res.ok) { + return { stream: events(res, abort.signal), abort: abort.abort } + } + + return { stream: null, abort: abort.abort } + } + setToken(token: string) { this.setToken_(token) } diff --git a/packages/core/js-sdk/src/types.ts b/packages/core/js-sdk/src/types.ts index f9eb72a530..b2ada0255e 100644 --- a/packages/core/js-sdk/src/types.ts +++ b/packages/core/js-sdk/src/types.ts @@ -37,3 +37,22 @@ export type ClientFetch = ( input: FetchInput, init?: FetchArgs ) => Promise + +// Defined in deno's standard library, and returned by fetch-event-stream package. +export interface ServerSentEventMessage { + /** Ignored by the client. */ + comment?: string + /** A string identifying the type of event described. */ + event?: string + /** The data field for the message. Split by new lines. */ + data?: string + /** The event ID to set the {@linkcode EventSource} object's last event ID value. */ + id?: string | number + /** The reconnection time. */ + retry?: number +} + +export interface FetchStreamResponse { + stream: AsyncGenerator | null + abort: () => void +} diff --git a/yarn.lock b/yarn.lock index 86336e8e9a..f1078cac83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6023,6 +6023,7 @@ __metadata: dependencies: "@medusajs/types": ^1.11.16 cross-env: ^5.2.1 + fetch-event-stream: ^0.1.5 jest: ^29.7.0 msw: ^2.3.0 qs: ^6.12.1 @@ -19801,6 +19802,13 @@ __metadata: languageName: node linkType: hard +"fetch-event-stream@npm:^0.1.5": + version: 0.1.5 + resolution: "fetch-event-stream@npm:0.1.5" + checksum: f5979cbff7bcfc554ba9d80783c972fab99d5e0ab0e0f24bad14d461b31ae847d510bb53a5a06c7ec6e243a7b397475441bc23b6d1e325edc3eda4d0f50f7bbd + languageName: node + linkType: hard + "fetch-retry@npm:^5.0.2": version: 5.0.6 resolution: "fetch-retry@npm:5.0.6"