eventsource-client
Advanced tools
| /// <reference types="node" /> | ||
| import type {ReadableStream as ReadableStream_2} from 'node:stream/web' | ||
| /** | ||
| * ReadyState representing a connection that has been closed (manually, or due to an error). | ||
| * @public | ||
| */ | ||
| export declare const CLOSED = 'closed' | ||
| /** | ||
| * ReadyState representing a connection that is connecting or has been scheduled to reconnect. | ||
| * @public | ||
| */ | ||
| export declare const CONNECTING = 'connecting' | ||
| /** | ||
| * Creates a new EventSource client. | ||
| * | ||
| * @param options - Options for the client | ||
| * @returns A new EventSource client instance | ||
| * @public | ||
| */ | ||
| export declare function createEventSource(options: EventSourceOptions): EventSourceClient | ||
| /** | ||
| * EventSource client. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare interface EventSourceClient { | ||
| /** Close the connection and prevent the client from reconnecting automatically. */ | ||
| close(): void | ||
| /** Connect to the event source. Automatically called on creation - you only need to call this after manually calling `close()`, when server has sent an HTTP 204, or the server responded with a non-retryable error. */ | ||
| connect(): void | ||
| /** Async iterator of messages received */ | ||
| [Symbol.asyncIterator](): AsyncIterableIterator<EventSourceMessage> | ||
| /** Last seen event ID, or the `initialLastEventId` if none has been received yet. */ | ||
| readonly lastEventId: string | undefined | ||
| /** Current URL. Usually the same as `url`, but in the case of allowed redirects, it will reflect the new URL. */ | ||
| readonly url: string | ||
| /** Ready state of the connection */ | ||
| readonly readyState: ReadyState_2 | ||
| } | ||
| /** | ||
| * A message received from the server. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare interface EventSourceMessage { | ||
| /** The data received for this message. */ | ||
| data: string | ||
| /** Event name sent from the server, or `undefined` if none is set for this message. */ | ||
| event?: string | ||
| /** ID of the message, if any was provided by the server. */ | ||
| id?: string | ||
| } | ||
| /** | ||
| * Options for the eventsource client. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare interface EventSourceOptions { | ||
| /** URL to connect to. */ | ||
| url: string | URL | ||
| /** Callback that fires each time a new event is received. */ | ||
| onMessage?: (event: EventSourceMessage) => void | ||
| /** Callback that fires each time the connection is established (multiple times in the case of reconnects). */ | ||
| onConnect?: () => void | ||
| /** Callback that fires each time we schedule a new reconnect attempt. Will include an object with information on how many milliseconds it will attempt to delay before doing the reconnect. */ | ||
| onScheduleReconnect?: (info: {delay: number}) => void | ||
| /** Callback that fires each time the connection is broken (will still attempt to reconnect, unless `close()` is called). */ | ||
| onDisconnect?: () => void | ||
| /** A string to use for the initial `Last-Event-ID` header when connecting. Only used until the first message with a new ID is received. */ | ||
| initialLastEventId?: string | ||
| /** Fetch implementation to use for performing requests. Defaults to `globalThis.fetch`. Throws if no implementation can be found. */ | ||
| fetch?: FetchLike | ||
| /** An object literal to set request's headers. */ | ||
| headers?: Record<string, string> | ||
| /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */ | ||
| mode?: 'cors' | 'no-cors' | 'same-origin' | ||
| /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */ | ||
| credentials?: 'include' | 'omit' | 'same-origin' | ||
| /** A BodyInit object or null to set request's body. */ | ||
| body?: any | ||
| /** A string to set request's method. */ | ||
| method?: string | ||
| /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ | ||
| redirect?: 'error' | 'follow' | ||
| /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */ | ||
| referrer?: string | ||
| /** A referrer policy to set request's referrerPolicy. */ | ||
| referrerPolicy?: ReferrerPolicy | ||
| } | ||
| /** | ||
| * Stripped down version of `fetch()`, only defining the parts we care about. | ||
| * This ensures it should work with "most" fetch implementations. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare type FetchLike = ( | ||
| url: string | URL, | ||
| init?: FetchLikeInit, | ||
| ) => Promise<FetchLikeResponse> | ||
| /** | ||
| * Stripped down version of `RequestInit`, only defining the parts we care about. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare interface FetchLikeInit { | ||
| /** A string to set request's method. */ | ||
| method?: string | ||
| /** An AbortSignal to set request's signal. Typed as `any` because of polyfill inconsistencies. */ | ||
| signal?: | ||
| | { | ||
| aborted: boolean | ||
| } | ||
| | any | ||
| /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ | ||
| headers?: Record<string, string> | ||
| /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */ | ||
| mode?: 'cors' | 'no-cors' | 'same-origin' | ||
| /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */ | ||
| credentials?: 'include' | 'omit' | 'same-origin' | ||
| /** Controls how the request is cached. */ | ||
| cache?: 'no-store' | ||
| /** Request body. */ | ||
| body?: any | ||
| /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ | ||
| redirect?: 'error' | 'follow' | ||
| /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */ | ||
| referrer?: string | ||
| /** A referrer policy to set request's referrerPolicy. */ | ||
| referrerPolicy?: ReferrerPolicy | ||
| } | ||
| /** | ||
| * Minimal version of the `Response` type returned by `fetch()`. | ||
| * | ||
| * @public | ||
| */ | ||
| export declare interface FetchLikeResponse { | ||
| readonly body: NodeJS.ReadableStream | ReadableStream_2<any> | Response['body'] | null | ||
| readonly url: string | ||
| readonly status: number | ||
| readonly redirected: boolean | ||
| } | ||
| /** | ||
| * ReadyState representing a connection that is open, eg connected. | ||
| * @public | ||
| */ | ||
| export declare const OPEN = 'open' | ||
| /** | ||
| * Ready state for a connection. | ||
| * | ||
| * @public | ||
| */ | ||
| declare type ReadyState_2 = 'open' | 'connecting' | 'closed' | ||
| export {ReadyState_2 as ReadyState} | ||
| export {} |
| import { createParser } from 'eventsource-parser'; | ||
| const CONNECTING = "connecting"; | ||
| const OPEN = "open"; | ||
| const CLOSED = "closed"; | ||
| const noop = () => {}; | ||
| function createEventSource$1(options, _ref) { | ||
| let { | ||
| getStream | ||
| } = _ref; | ||
| const { | ||
| onMessage, | ||
| onConnect = noop, | ||
| onDisconnect = noop, | ||
| onScheduleReconnect = noop | ||
| } = options; | ||
| const { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| } = validate(options); | ||
| const requestHeaders = { | ||
| ...options.headers | ||
| }; | ||
| const onCloseSubscribers = []; | ||
| const subscribers = onMessage ? [onMessage] : []; | ||
| const emit = event => subscribers.forEach(fn => fn(event)); | ||
| const parser = createParser(onParsedMessage); | ||
| let request; | ||
| let currentUrl = url.toString(); | ||
| let controller = new AbortController(); | ||
| let lastEventId = initialLastEventId; | ||
| let reconnectMs = 2e3; | ||
| let reconnectTimer; | ||
| let readyState = CLOSED; | ||
| connect(); | ||
| return { | ||
| close, | ||
| connect, | ||
| [Symbol.asyncIterator]: getEventIterator, | ||
| get lastEventId() { | ||
| return lastEventId; | ||
| }, | ||
| get url() { | ||
| return currentUrl; | ||
| }, | ||
| get readyState() { | ||
| return readyState; | ||
| } | ||
| }; | ||
| function connect() { | ||
| if (request) { | ||
| return; | ||
| } | ||
| readyState = CONNECTING; | ||
| controller = new AbortController(); | ||
| request = fetch(url, getRequestOptions()).then(onFetchResponse).catch(err => { | ||
| request = null; | ||
| if (err.name !== "AbortError" && err.type !== "aborted") { | ||
| throw err; | ||
| } | ||
| scheduleReconnect(); | ||
| }); | ||
| } | ||
| function close() { | ||
| readyState = CLOSED; | ||
| controller.abort(); | ||
| parser.reset(); | ||
| clearTimeout(reconnectTimer); | ||
| onCloseSubscribers.forEach(fn => fn()); | ||
| } | ||
| function getEventIterator() { | ||
| const pullQueue = []; | ||
| const pushQueue = []; | ||
| function pullValue() { | ||
| return new Promise(resolve => { | ||
| const value = pushQueue.shift(); | ||
| if (value) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pullQueue.push(resolve); | ||
| } | ||
| }); | ||
| } | ||
| const pushValue = function (value) { | ||
| const resolve = pullQueue.shift(); | ||
| if (resolve) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pushQueue.push(value); | ||
| } | ||
| }; | ||
| function unsubscribe() { | ||
| subscribers.splice(subscribers.indexOf(pushValue), 1); | ||
| while (pullQueue.shift()) {} | ||
| while (pushQueue.shift()) {} | ||
| } | ||
| function onClose() { | ||
| const resolve = pullQueue.shift(); | ||
| if (!resolve) { | ||
| return; | ||
| } | ||
| resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| unsubscribe(); | ||
| } | ||
| onCloseSubscribers.push(onClose); | ||
| subscribers.push(pushValue); | ||
| return { | ||
| next() { | ||
| return readyState === CLOSED ? this.return() : pullValue(); | ||
| }, | ||
| return() { | ||
| unsubscribe(); | ||
| return Promise.resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| }, | ||
| throw(error) { | ||
| unsubscribe(); | ||
| return Promise.reject(error); | ||
| }, | ||
| [Symbol.asyncIterator]() { | ||
| return this; | ||
| } | ||
| }; | ||
| } | ||
| function scheduleReconnect() { | ||
| onScheduleReconnect({ | ||
| delay: reconnectMs | ||
| }); | ||
| readyState = CONNECTING; | ||
| reconnectTimer = setTimeout(connect, reconnectMs); | ||
| } | ||
| async function onFetchResponse(response) { | ||
| onConnect(); | ||
| parser.reset(); | ||
| const { | ||
| body, | ||
| redirected, | ||
| status | ||
| } = response; | ||
| if (status === 204) { | ||
| onDisconnect(); | ||
| close(); | ||
| return; | ||
| } | ||
| if (!body) { | ||
| throw new Error("Missing response body"); | ||
| } | ||
| if (redirected) { | ||
| currentUrl = response.url; | ||
| } | ||
| const bodyStream = getStream(body); | ||
| const stream = bodyStream.pipeThrough(new TextDecoderStream()); | ||
| const reader = stream.getReader(); | ||
| let open = true; | ||
| readyState = OPEN; | ||
| do { | ||
| const { | ||
| done, | ||
| value | ||
| } = await reader.read(); | ||
| if (!done) { | ||
| parser.feed(value); | ||
| continue; | ||
| } | ||
| open = false; | ||
| request = null; | ||
| parser.reset(); | ||
| onDisconnect(); | ||
| scheduleReconnect(); | ||
| } while (open); | ||
| } | ||
| function onParsedMessage(msg) { | ||
| if (msg.type === "reconnect-interval") { | ||
| reconnectMs = msg.value; | ||
| return; | ||
| } | ||
| if (typeof msg.id === "string") { | ||
| lastEventId = msg.id; | ||
| } | ||
| emit({ | ||
| id: msg.id, | ||
| data: msg.data, | ||
| event: msg.event | ||
| }); | ||
| } | ||
| function getRequestOptions() { | ||
| const { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy | ||
| } = options; | ||
| const lastEvent = lastEventId ? { | ||
| "Last-Event-ID": lastEventId | ||
| } : void 0; | ||
| const headers = { | ||
| Accept: "text/event-stream", | ||
| ...requestHeaders, | ||
| ...lastEvent | ||
| }; | ||
| return { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy, | ||
| headers, | ||
| cache: "no-store", | ||
| signal: controller.signal | ||
| }; | ||
| } | ||
| } | ||
| function validate(options) { | ||
| const fetch = options.fetch || globalThis.fetch; | ||
| if (!isFetchLike(fetch)) { | ||
| throw new Error("No fetch implementation provided, and one was not found on the global object."); | ||
| } | ||
| if (typeof AbortController !== "function") { | ||
| throw new Error("Missing AbortController implementation"); | ||
| } | ||
| const { | ||
| url, | ||
| initialLastEventId | ||
| } = options; | ||
| if (typeof url !== "string" && !(url instanceof URL)) { | ||
| throw new Error("Invalid URL provided - must be string or URL instance"); | ||
| } | ||
| if (typeof initialLastEventId !== "string" && initialLastEventId !== void 0) { | ||
| throw new Error("Invalid initialLastEventId provided - must be string or undefined"); | ||
| } | ||
| return { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| }; | ||
| } | ||
| function isFetchLike(fetch) { | ||
| return typeof fetch === "function"; | ||
| } | ||
| const defaultAbstractions = { | ||
| getStream | ||
| }; | ||
| function createEventSource(options) { | ||
| return createEventSource$1(options, defaultAbstractions); | ||
| } | ||
| function getStream(body) { | ||
| if (!(body instanceof ReadableStream)) { | ||
| throw new Error("Invalid stream, expected a web ReadableStream"); | ||
| } | ||
| return body; | ||
| } | ||
| export { CLOSED, CONNECTING, OPEN, createEventSource }; | ||
| //# sourceMappingURL=default.esm.js.map |
| {"version":3,"file":"default.esm.js","sources":["../src/constants.ts","../src/client.ts","../src/default.ts"],"sourcesContent":["// ReadyStates, mirrors WhatWG spec, but uses strings instead of numbers.\n// Why make it harder to read than it needs to be?\n\n/**\n * ReadyState representing a connection that is connecting or has been scheduled to reconnect.\n * @public\n */\nexport const CONNECTING = 'connecting'\n\n/**\n * ReadyState representing a connection that is open, eg connected.\n * @public\n */\nexport const OPEN = 'open'\n\n/**\n * ReadyState representing a connection that has been closed (manually, or due to an error).\n * @public\n */\nexport const CLOSED = 'closed'\n","import {createParser, type ParseEvent} from 'eventsource-parser'\nimport {CLOSED, CONNECTING, OPEN} from './constants'\nimport type {EnvAbstractions, EventSourceAsyncValueResolver} from './abstractions'\nimport type {\n EventSourceClient,\n EventSourceMessage,\n EventSourceOptions,\n FetchLike,\n FetchLikeInit,\n FetchLikeResponse,\n ReadyState,\n} from './types'\n\n/**\n * Intentional noop function for eased control flow\n */\nconst noop = () => {\n /* intentional noop */\n}\n\n/**\n * Creates a new EventSource client. Used internally by the environment-specific entry points,\n * and should not be used directly by consumers.\n *\n * @param options - Options for the client.\n * @param abstractions - Abstractions for the environments.\n * @returns A new EventSource client instance\n * @internal\n */\nexport function createEventSource(\n options: EventSourceOptions,\n {getStream}: EnvAbstractions,\n): EventSourceClient {\n const {onMessage, onConnect = noop, onDisconnect = noop, onScheduleReconnect = noop} = options\n const {fetch, url, initialLastEventId} = validate(options)\n const requestHeaders = {...options.headers} // Prevent post-creation mutations to headers\n\n const onCloseSubscribers: (() => void)[] = []\n const subscribers: ((event: EventSourceMessage) => void)[] = onMessage ? [onMessage] : []\n const emit = (event: EventSourceMessage) => subscribers.forEach((fn) => fn(event))\n const parser = createParser(onParsedMessage)\n\n // Client state\n let request: Promise<unknown> | null\n let currentUrl = url.toString()\n let controller = new AbortController()\n let lastEventId = initialLastEventId\n let reconnectMs = 2000\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined\n let readyState: ReadyState = CLOSED\n\n // Let's go!\n connect()\n\n return {\n close,\n connect,\n [Symbol.asyncIterator]: getEventIterator,\n get lastEventId() {\n return lastEventId\n },\n get url() {\n return currentUrl\n },\n get readyState() {\n return readyState\n },\n }\n\n function connect() {\n if (request) {\n return\n }\n\n readyState = CONNECTING\n controller = new AbortController()\n request = fetch(url, getRequestOptions())\n .then(onFetchResponse)\n .catch((err: Error & {type: string}) => {\n request = null\n\n // We expect abort errors when the user manually calls `close()` - ignore those\n if (err.name !== 'AbortError' && err.type !== 'aborted') {\n throw err\n }\n\n scheduleReconnect()\n })\n }\n\n function close() {\n readyState = CLOSED\n controller.abort()\n parser.reset()\n clearTimeout(reconnectTimer)\n onCloseSubscribers.forEach((fn) => fn())\n }\n\n function getEventIterator(): AsyncGenerator<EventSourceMessage, void> {\n const pullQueue: EventSourceAsyncValueResolver[] = []\n const pushQueue: EventSourceMessage[] = []\n\n function pullValue() {\n return new Promise<IteratorResult<EventSourceMessage, void>>((resolve) => {\n const value = pushQueue.shift()\n if (value) {\n resolve({value, done: false})\n } else {\n pullQueue.push(resolve)\n }\n })\n }\n\n const pushValue = function (value: EventSourceMessage) {\n const resolve = pullQueue.shift()\n if (resolve) {\n resolve({value, done: false})\n } else {\n pushQueue.push(value)\n }\n }\n\n function unsubscribe() {\n subscribers.splice(subscribers.indexOf(pushValue), 1)\n while (pullQueue.shift()) {}\n while (pushQueue.shift()) {}\n }\n\n function onClose() {\n const resolve = pullQueue.shift()\n if (!resolve) {\n return\n }\n\n resolve({done: true, value: undefined})\n unsubscribe()\n }\n\n onCloseSubscribers.push(onClose)\n subscribers.push(pushValue)\n\n return {\n next() {\n return readyState === CLOSED ? this.return() : pullValue()\n },\n return() {\n unsubscribe()\n return Promise.resolve({done: true, value: undefined})\n },\n throw(error) {\n unsubscribe()\n return Promise.reject(error)\n },\n [Symbol.asyncIterator]() {\n return this\n },\n }\n }\n\n function scheduleReconnect() {\n onScheduleReconnect({delay: reconnectMs})\n readyState = CONNECTING\n reconnectTimer = setTimeout(connect, reconnectMs)\n }\n\n async function onFetchResponse(response: FetchLikeResponse) {\n onConnect()\n parser.reset()\n\n const {body, redirected, status} = response\n\n // HTTP 204 means \"close the connection, no more data will be sent\"\n if (status === 204) {\n onDisconnect()\n close()\n return\n }\n\n if (!body) {\n throw new Error('Missing response body')\n }\n\n if (redirected) {\n currentUrl = response.url\n }\n\n // Ensure that the response stream is a web stream\n // @todo Figure out a way to make this work without casting\n const bodyStream = getStream(body as any)\n\n // EventSources are always UTF-8 per spec\n const stream = bodyStream.pipeThrough<string>(new TextDecoderStream())\n const reader = stream.getReader()\n let open = true\n\n readyState = OPEN\n\n do {\n const {done, value} = await reader.read()\n if (!done) {\n parser.feed(value)\n continue\n }\n\n open = false\n request = null\n parser.reset()\n\n onDisconnect()\n\n // EventSources never close unless explicitly handled with `.close()`:\n // Implementors should send an `done`/`complete`/`disconnect` event and\n // explicitly handle it in client code, or send an HTTP 204.\n scheduleReconnect()\n } while (open)\n }\n\n function onParsedMessage(msg: ParseEvent): void {\n if (msg.type === 'reconnect-interval') {\n reconnectMs = msg.value\n return\n }\n\n if (typeof msg.id === 'string') {\n lastEventId = msg.id\n }\n\n emit({\n id: msg.id,\n data: msg.data,\n event: msg.event,\n })\n }\n\n function getRequestOptions(): FetchLikeInit {\n // @todo allow interception of options, but don't allow overriding signal\n const {mode, credentials, body, method, redirect, referrer, referrerPolicy} = options\n const lastEvent = lastEventId ? {'Last-Event-ID': lastEventId} : undefined\n const headers = {Accept: 'text/event-stream', ...requestHeaders, ...lastEvent}\n return {\n mode,\n credentials,\n body,\n method,\n redirect,\n referrer,\n referrerPolicy,\n headers,\n cache: 'no-store',\n signal: controller.signal,\n }\n }\n}\n\nfunction validate(options: EventSourceOptions): {\n fetch: FetchLike\n url: string | URL\n initialLastEventId: string | undefined\n} {\n const fetch = options.fetch || globalThis.fetch\n if (!isFetchLike(fetch)) {\n throw new Error('No fetch implementation provided, and one was not found on the global object.')\n }\n\n if (typeof AbortController !== 'function') {\n throw new Error('Missing AbortController implementation')\n }\n\n const {url, initialLastEventId} = options\n\n if (typeof url !== 'string' && !(url instanceof URL)) {\n throw new Error('Invalid URL provided - must be string or URL instance')\n }\n\n if (typeof initialLastEventId !== 'string' && initialLastEventId !== undefined) {\n throw new Error('Invalid initialLastEventId provided - must be string or undefined')\n }\n\n return {fetch, url, initialLastEventId}\n}\n\n// This is obviously naive, but hard to probe for full compatibility\nfunction isFetchLike(fetch: FetchLike | typeof globalThis.fetch): fetch is FetchLike {\n return typeof fetch === 'function'\n}\n","import type {EnvAbstractions} from './abstractions'\nimport type {EventSourceClient, EventSourceOptions} from './types'\nimport {createEventSource as createSource} from './client'\n\nexport * from './types'\nexport * from './constants'\n\n/**\n * Default \"abstractions\", eg when all the APIs are globally available\n */\nconst defaultAbstractions: EnvAbstractions = {\n getStream,\n}\n\n/**\n * Creates a new EventSource client.\n *\n * @param options - Options for the client\n * @returns A new EventSource client instance\n * @public\n */\nexport function createEventSource(options: EventSourceOptions): EventSourceClient {\n return createSource(options, defaultAbstractions)\n}\n\n/**\n * Returns a ReadableStream (Web Stream) from either an existing ReadableStream.\n * Only defined because of environment abstractions - is actually a 1:1 (passthrough).\n *\n * @param body - The body to convert\n * @returns A ReadableStream\n * @private\n */\nfunction getStream(\n body: NodeJS.ReadableStream | ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n if (!(body instanceof ReadableStream)) {\n throw new Error('Invalid stream, expected a web ReadableStream')\n }\n\n return body\n}\n"],"names":["CONNECTING","OPEN","CLOSED","noop","createEventSource","createEventSource$1","options","_ref","getStream","onMessage","onConnect","onDisconnect","onScheduleReconnect","fetch","url","initialLastEventId","validate","requestHeaders","headers","onCloseSubscribers","subscribers","emit","event","forEach","fn","parser","createParser","onParsedMessage","request","currentUrl","toString","controller","AbortController","lastEventId","reconnectMs","reconnectTimer","readyState","connect","close","Symbol","asyncIterator","getEventIterator","getRequestOptions","then","onFetchResponse","catch","err","name","type","scheduleReconnect","abort","reset","clearTimeout","pullQueue","pushQueue","pullValue","Promise","resolve","value","shift","done","push","pushValue","unsubscribe","splice","indexOf","onClose","next","return","throw","error","reject","delay","setTimeout","response","body","redirected","status","Error","bodyStream","stream","pipeThrough","TextDecoderStream","reader","getReader","open","read","feed","msg","id","data","mode","credentials","method","redirect","referrer","referrerPolicy","lastEvent","Accept","cache","signal","globalThis","isFetchLike","URL","defaultAbstractions","createSource","ReadableStream"],"mappings":"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"} |
+277
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); | ||
| var eventsourceParser = require('eventsource-parser'); | ||
| const CONNECTING = "connecting"; | ||
| const OPEN = "open"; | ||
| const CLOSED = "closed"; | ||
| const noop = () => {}; | ||
| function createEventSource$1(options, _ref) { | ||
| let { | ||
| getStream | ||
| } = _ref; | ||
| const { | ||
| onMessage, | ||
| onConnect = noop, | ||
| onDisconnect = noop, | ||
| onScheduleReconnect = noop | ||
| } = options; | ||
| const { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| } = validate(options); | ||
| const requestHeaders = { | ||
| ...options.headers | ||
| }; | ||
| const onCloseSubscribers = []; | ||
| const subscribers = onMessage ? [onMessage] : []; | ||
| const emit = event => subscribers.forEach(fn => fn(event)); | ||
| const parser = eventsourceParser.createParser(onParsedMessage); | ||
| let request; | ||
| let currentUrl = url.toString(); | ||
| let controller = new AbortController(); | ||
| let lastEventId = initialLastEventId; | ||
| let reconnectMs = 2e3; | ||
| let reconnectTimer; | ||
| let readyState = CLOSED; | ||
| connect(); | ||
| return { | ||
| close, | ||
| connect, | ||
| [Symbol.asyncIterator]: getEventIterator, | ||
| get lastEventId() { | ||
| return lastEventId; | ||
| }, | ||
| get url() { | ||
| return currentUrl; | ||
| }, | ||
| get readyState() { | ||
| return readyState; | ||
| } | ||
| }; | ||
| function connect() { | ||
| if (request) { | ||
| return; | ||
| } | ||
| readyState = CONNECTING; | ||
| controller = new AbortController(); | ||
| request = fetch(url, getRequestOptions()).then(onFetchResponse).catch(err => { | ||
| request = null; | ||
| if (err.name !== "AbortError" && err.type !== "aborted") { | ||
| throw err; | ||
| } | ||
| scheduleReconnect(); | ||
| }); | ||
| } | ||
| function close() { | ||
| readyState = CLOSED; | ||
| controller.abort(); | ||
| parser.reset(); | ||
| clearTimeout(reconnectTimer); | ||
| onCloseSubscribers.forEach(fn => fn()); | ||
| } | ||
| function getEventIterator() { | ||
| const pullQueue = []; | ||
| const pushQueue = []; | ||
| function pullValue() { | ||
| return new Promise(resolve => { | ||
| const value = pushQueue.shift(); | ||
| if (value) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pullQueue.push(resolve); | ||
| } | ||
| }); | ||
| } | ||
| const pushValue = function (value) { | ||
| const resolve = pullQueue.shift(); | ||
| if (resolve) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pushQueue.push(value); | ||
| } | ||
| }; | ||
| function unsubscribe() { | ||
| subscribers.splice(subscribers.indexOf(pushValue), 1); | ||
| while (pullQueue.shift()) {} | ||
| while (pushQueue.shift()) {} | ||
| } | ||
| function onClose() { | ||
| const resolve = pullQueue.shift(); | ||
| if (!resolve) { | ||
| return; | ||
| } | ||
| resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| unsubscribe(); | ||
| } | ||
| onCloseSubscribers.push(onClose); | ||
| subscribers.push(pushValue); | ||
| return { | ||
| next() { | ||
| return readyState === CLOSED ? this.return() : pullValue(); | ||
| }, | ||
| return() { | ||
| unsubscribe(); | ||
| return Promise.resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| }, | ||
| throw(error) { | ||
| unsubscribe(); | ||
| return Promise.reject(error); | ||
| }, | ||
| [Symbol.asyncIterator]() { | ||
| return this; | ||
| } | ||
| }; | ||
| } | ||
| function scheduleReconnect() { | ||
| onScheduleReconnect({ | ||
| delay: reconnectMs | ||
| }); | ||
| readyState = CONNECTING; | ||
| reconnectTimer = setTimeout(connect, reconnectMs); | ||
| } | ||
| async function onFetchResponse(response) { | ||
| onConnect(); | ||
| parser.reset(); | ||
| const { | ||
| body, | ||
| redirected, | ||
| status | ||
| } = response; | ||
| if (status === 204) { | ||
| onDisconnect(); | ||
| close(); | ||
| return; | ||
| } | ||
| if (!body) { | ||
| throw new Error("Missing response body"); | ||
| } | ||
| if (redirected) { | ||
| currentUrl = response.url; | ||
| } | ||
| const bodyStream = getStream(body); | ||
| const stream = bodyStream.pipeThrough(new TextDecoderStream()); | ||
| const reader = stream.getReader(); | ||
| let open = true; | ||
| readyState = OPEN; | ||
| do { | ||
| const { | ||
| done, | ||
| value | ||
| } = await reader.read(); | ||
| if (!done) { | ||
| parser.feed(value); | ||
| continue; | ||
| } | ||
| open = false; | ||
| request = null; | ||
| parser.reset(); | ||
| onDisconnect(); | ||
| scheduleReconnect(); | ||
| } while (open); | ||
| } | ||
| function onParsedMessage(msg) { | ||
| if (msg.type === "reconnect-interval") { | ||
| reconnectMs = msg.value; | ||
| return; | ||
| } | ||
| if (typeof msg.id === "string") { | ||
| lastEventId = msg.id; | ||
| } | ||
| emit({ | ||
| id: msg.id, | ||
| data: msg.data, | ||
| event: msg.event | ||
| }); | ||
| } | ||
| function getRequestOptions() { | ||
| const { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy | ||
| } = options; | ||
| const lastEvent = lastEventId ? { | ||
| "Last-Event-ID": lastEventId | ||
| } : void 0; | ||
| const headers = { | ||
| Accept: "text/event-stream", | ||
| ...requestHeaders, | ||
| ...lastEvent | ||
| }; | ||
| return { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy, | ||
| headers, | ||
| cache: "no-store", | ||
| signal: controller.signal | ||
| }; | ||
| } | ||
| } | ||
| function validate(options) { | ||
| const fetch = options.fetch || globalThis.fetch; | ||
| if (!isFetchLike(fetch)) { | ||
| throw new Error("No fetch implementation provided, and one was not found on the global object."); | ||
| } | ||
| if (typeof AbortController !== "function") { | ||
| throw new Error("Missing AbortController implementation"); | ||
| } | ||
| const { | ||
| url, | ||
| initialLastEventId | ||
| } = options; | ||
| if (typeof url !== "string" && !(url instanceof URL)) { | ||
| throw new Error("Invalid URL provided - must be string or URL instance"); | ||
| } | ||
| if (typeof initialLastEventId !== "string" && initialLastEventId !== void 0) { | ||
| throw new Error("Invalid initialLastEventId provided - must be string or undefined"); | ||
| } | ||
| return { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| }; | ||
| } | ||
| function isFetchLike(fetch) { | ||
| return typeof fetch === "function"; | ||
| } | ||
| const defaultAbstractions = { | ||
| getStream | ||
| }; | ||
| function createEventSource(options) { | ||
| return createEventSource$1(options, defaultAbstractions); | ||
| } | ||
| function getStream(body) { | ||
| if (!(body instanceof ReadableStream)) { | ||
| throw new Error("Invalid stream, expected a web ReadableStream"); | ||
| } | ||
| return body; | ||
| } | ||
| exports.CLOSED = CLOSED; | ||
| exports.CONNECTING = CONNECTING; | ||
| exports.OPEN = OPEN; | ||
| exports.createEventSource = createEventSource; | ||
| //# sourceMappingURL=default.js.map |
| {"version":3,"file":"default.js","sources":["../src/constants.ts","../src/client.ts","../src/default.ts"],"sourcesContent":["// ReadyStates, mirrors WhatWG spec, but uses strings instead of numbers.\n// Why make it harder to read than it needs to be?\n\n/**\n * ReadyState representing a connection that is connecting or has been scheduled to reconnect.\n * @public\n */\nexport const CONNECTING = 'connecting'\n\n/**\n * ReadyState representing a connection that is open, eg connected.\n * @public\n */\nexport const OPEN = 'open'\n\n/**\n * ReadyState representing a connection that has been closed (manually, or due to an error).\n * @public\n */\nexport const CLOSED = 'closed'\n","import {createParser, type ParseEvent} from 'eventsource-parser'\nimport {CLOSED, CONNECTING, OPEN} from './constants'\nimport type {EnvAbstractions, EventSourceAsyncValueResolver} from './abstractions'\nimport type {\n EventSourceClient,\n EventSourceMessage,\n EventSourceOptions,\n FetchLike,\n FetchLikeInit,\n FetchLikeResponse,\n ReadyState,\n} from './types'\n\n/**\n * Intentional noop function for eased control flow\n */\nconst noop = () => {\n /* intentional noop */\n}\n\n/**\n * Creates a new EventSource client. Used internally by the environment-specific entry points,\n * and should not be used directly by consumers.\n *\n * @param options - Options for the client.\n * @param abstractions - Abstractions for the environments.\n * @returns A new EventSource client instance\n * @internal\n */\nexport function createEventSource(\n options: EventSourceOptions,\n {getStream}: EnvAbstractions,\n): EventSourceClient {\n const {onMessage, onConnect = noop, onDisconnect = noop, onScheduleReconnect = noop} = options\n const {fetch, url, initialLastEventId} = validate(options)\n const requestHeaders = {...options.headers} // Prevent post-creation mutations to headers\n\n const onCloseSubscribers: (() => void)[] = []\n const subscribers: ((event: EventSourceMessage) => void)[] = onMessage ? [onMessage] : []\n const emit = (event: EventSourceMessage) => subscribers.forEach((fn) => fn(event))\n const parser = createParser(onParsedMessage)\n\n // Client state\n let request: Promise<unknown> | null\n let currentUrl = url.toString()\n let controller = new AbortController()\n let lastEventId = initialLastEventId\n let reconnectMs = 2000\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined\n let readyState: ReadyState = CLOSED\n\n // Let's go!\n connect()\n\n return {\n close,\n connect,\n [Symbol.asyncIterator]: getEventIterator,\n get lastEventId() {\n return lastEventId\n },\n get url() {\n return currentUrl\n },\n get readyState() {\n return readyState\n },\n }\n\n function connect() {\n if (request) {\n return\n }\n\n readyState = CONNECTING\n controller = new AbortController()\n request = fetch(url, getRequestOptions())\n .then(onFetchResponse)\n .catch((err: Error & {type: string}) => {\n request = null\n\n // We expect abort errors when the user manually calls `close()` - ignore those\n if (err.name !== 'AbortError' && err.type !== 'aborted') {\n throw err\n }\n\n scheduleReconnect()\n })\n }\n\n function close() {\n readyState = CLOSED\n controller.abort()\n parser.reset()\n clearTimeout(reconnectTimer)\n onCloseSubscribers.forEach((fn) => fn())\n }\n\n function getEventIterator(): AsyncGenerator<EventSourceMessage, void> {\n const pullQueue: EventSourceAsyncValueResolver[] = []\n const pushQueue: EventSourceMessage[] = []\n\n function pullValue() {\n return new Promise<IteratorResult<EventSourceMessage, void>>((resolve) => {\n const value = pushQueue.shift()\n if (value) {\n resolve({value, done: false})\n } else {\n pullQueue.push(resolve)\n }\n })\n }\n\n const pushValue = function (value: EventSourceMessage) {\n const resolve = pullQueue.shift()\n if (resolve) {\n resolve({value, done: false})\n } else {\n pushQueue.push(value)\n }\n }\n\n function unsubscribe() {\n subscribers.splice(subscribers.indexOf(pushValue), 1)\n while (pullQueue.shift()) {}\n while (pushQueue.shift()) {}\n }\n\n function onClose() {\n const resolve = pullQueue.shift()\n if (!resolve) {\n return\n }\n\n resolve({done: true, value: undefined})\n unsubscribe()\n }\n\n onCloseSubscribers.push(onClose)\n subscribers.push(pushValue)\n\n return {\n next() {\n return readyState === CLOSED ? this.return() : pullValue()\n },\n return() {\n unsubscribe()\n return Promise.resolve({done: true, value: undefined})\n },\n throw(error) {\n unsubscribe()\n return Promise.reject(error)\n },\n [Symbol.asyncIterator]() {\n return this\n },\n }\n }\n\n function scheduleReconnect() {\n onScheduleReconnect({delay: reconnectMs})\n readyState = CONNECTING\n reconnectTimer = setTimeout(connect, reconnectMs)\n }\n\n async function onFetchResponse(response: FetchLikeResponse) {\n onConnect()\n parser.reset()\n\n const {body, redirected, status} = response\n\n // HTTP 204 means \"close the connection, no more data will be sent\"\n if (status === 204) {\n onDisconnect()\n close()\n return\n }\n\n if (!body) {\n throw new Error('Missing response body')\n }\n\n if (redirected) {\n currentUrl = response.url\n }\n\n // Ensure that the response stream is a web stream\n // @todo Figure out a way to make this work without casting\n const bodyStream = getStream(body as any)\n\n // EventSources are always UTF-8 per spec\n const stream = bodyStream.pipeThrough<string>(new TextDecoderStream())\n const reader = stream.getReader()\n let open = true\n\n readyState = OPEN\n\n do {\n const {done, value} = await reader.read()\n if (!done) {\n parser.feed(value)\n continue\n }\n\n open = false\n request = null\n parser.reset()\n\n onDisconnect()\n\n // EventSources never close unless explicitly handled with `.close()`:\n // Implementors should send an `done`/`complete`/`disconnect` event and\n // explicitly handle it in client code, or send an HTTP 204.\n scheduleReconnect()\n } while (open)\n }\n\n function onParsedMessage(msg: ParseEvent): void {\n if (msg.type === 'reconnect-interval') {\n reconnectMs = msg.value\n return\n }\n\n if (typeof msg.id === 'string') {\n lastEventId = msg.id\n }\n\n emit({\n id: msg.id,\n data: msg.data,\n event: msg.event,\n })\n }\n\n function getRequestOptions(): FetchLikeInit {\n // @todo allow interception of options, but don't allow overriding signal\n const {mode, credentials, body, method, redirect, referrer, referrerPolicy} = options\n const lastEvent = lastEventId ? {'Last-Event-ID': lastEventId} : undefined\n const headers = {Accept: 'text/event-stream', ...requestHeaders, ...lastEvent}\n return {\n mode,\n credentials,\n body,\n method,\n redirect,\n referrer,\n referrerPolicy,\n headers,\n cache: 'no-store',\n signal: controller.signal,\n }\n }\n}\n\nfunction validate(options: EventSourceOptions): {\n fetch: FetchLike\n url: string | URL\n initialLastEventId: string | undefined\n} {\n const fetch = options.fetch || globalThis.fetch\n if (!isFetchLike(fetch)) {\n throw new Error('No fetch implementation provided, and one was not found on the global object.')\n }\n\n if (typeof AbortController !== 'function') {\n throw new Error('Missing AbortController implementation')\n }\n\n const {url, initialLastEventId} = options\n\n if (typeof url !== 'string' && !(url instanceof URL)) {\n throw new Error('Invalid URL provided - must be string or URL instance')\n }\n\n if (typeof initialLastEventId !== 'string' && initialLastEventId !== undefined) {\n throw new Error('Invalid initialLastEventId provided - must be string or undefined')\n }\n\n return {fetch, url, initialLastEventId}\n}\n\n// This is obviously naive, but hard to probe for full compatibility\nfunction isFetchLike(fetch: FetchLike | typeof globalThis.fetch): fetch is FetchLike {\n return typeof fetch === 'function'\n}\n","import type {EnvAbstractions} from './abstractions'\nimport type {EventSourceClient, EventSourceOptions} from './types'\nimport {createEventSource as createSource} from './client'\n\nexport * from './types'\nexport * from './constants'\n\n/**\n * Default \"abstractions\", eg when all the APIs are globally available\n */\nconst defaultAbstractions: EnvAbstractions = {\n getStream,\n}\n\n/**\n * Creates a new EventSource client.\n *\n * @param options - Options for the client\n * @returns A new EventSource client instance\n * @public\n */\nexport function createEventSource(options: EventSourceOptions): EventSourceClient {\n return createSource(options, defaultAbstractions)\n}\n\n/**\n * Returns a ReadableStream (Web Stream) from either an existing ReadableStream.\n * Only defined because of environment abstractions - is actually a 1:1 (passthrough).\n *\n * @param body - The body to convert\n * @returns A ReadableStream\n * @private\n */\nfunction getStream(\n body: NodeJS.ReadableStream | ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n if (!(body instanceof ReadableStream)) {\n throw new Error('Invalid stream, expected a web ReadableStream')\n }\n\n return body\n}\n"],"names":["CONNECTING","OPEN","CLOSED","noop","createEventSource","createEventSource$1","options","_ref","getStream","onMessage","onConnect","onDisconnect","onScheduleReconnect","fetch","url","initialLastEventId","validate","requestHeaders","headers","onCloseSubscribers","subscribers","emit","event","forEach","fn","parser","createParser","onParsedMessage","request","currentUrl","toString","controller","AbortController","lastEventId","reconnectMs","reconnectTimer","readyState","connect","close","Symbol","asyncIterator","getEventIterator","getRequestOptions","then","onFetchResponse","catch","err","name","type","scheduleReconnect","abort","reset","clearTimeout","pullQueue","pushQueue","pullValue","Promise","resolve","value","shift","done","push","pushValue","unsubscribe","splice","indexOf","onClose","next","return","throw","error","reject","delay","setTimeout","response","body","redirected","status","Error","bodyStream","stream","pipeThrough","TextDecoderStream","reader","getReader","open","read","feed","msg","id","data","mode","credentials","method","redirect","referrer","referrerPolicy","lastEvent","Accept","cache","signal","globalThis","isFetchLike","URL","defaultAbstractions","createSource","ReadableStream"],"mappings":"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"} |
| import cjs from './node.js'; | ||
| export const CLOSED = cjs.CLOSED; | ||
| export const CONNECTING = cjs.CONNECTING; | ||
| export const OPEN = cjs.OPEN; | ||
| export const createEventSource = cjs.createEventSource; | ||
+284
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); | ||
| var node_stream = require('node:stream'); | ||
| var eventsourceParser = require('eventsource-parser'); | ||
| const CONNECTING = "connecting"; | ||
| const OPEN = "open"; | ||
| const CLOSED = "closed"; | ||
| const noop = () => {}; | ||
| function createEventSource$1(options, _ref) { | ||
| let { | ||
| getStream | ||
| } = _ref; | ||
| const { | ||
| onMessage, | ||
| onConnect = noop, | ||
| onDisconnect = noop, | ||
| onScheduleReconnect = noop | ||
| } = options; | ||
| const { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| } = validate(options); | ||
| const requestHeaders = { | ||
| ...options.headers | ||
| }; | ||
| const onCloseSubscribers = []; | ||
| const subscribers = onMessage ? [onMessage] : []; | ||
| const emit = event => subscribers.forEach(fn => fn(event)); | ||
| const parser = eventsourceParser.createParser(onParsedMessage); | ||
| let request; | ||
| let currentUrl = url.toString(); | ||
| let controller = new AbortController(); | ||
| let lastEventId = initialLastEventId; | ||
| let reconnectMs = 2e3; | ||
| let reconnectTimer; | ||
| let readyState = CLOSED; | ||
| connect(); | ||
| return { | ||
| close, | ||
| connect, | ||
| [Symbol.asyncIterator]: getEventIterator, | ||
| get lastEventId() { | ||
| return lastEventId; | ||
| }, | ||
| get url() { | ||
| return currentUrl; | ||
| }, | ||
| get readyState() { | ||
| return readyState; | ||
| } | ||
| }; | ||
| function connect() { | ||
| if (request) { | ||
| return; | ||
| } | ||
| readyState = CONNECTING; | ||
| controller = new AbortController(); | ||
| request = fetch(url, getRequestOptions()).then(onFetchResponse).catch(err => { | ||
| request = null; | ||
| if (err.name !== "AbortError" && err.type !== "aborted") { | ||
| throw err; | ||
| } | ||
| scheduleReconnect(); | ||
| }); | ||
| } | ||
| function close() { | ||
| readyState = CLOSED; | ||
| controller.abort(); | ||
| parser.reset(); | ||
| clearTimeout(reconnectTimer); | ||
| onCloseSubscribers.forEach(fn => fn()); | ||
| } | ||
| function getEventIterator() { | ||
| const pullQueue = []; | ||
| const pushQueue = []; | ||
| function pullValue() { | ||
| return new Promise(resolve => { | ||
| const value = pushQueue.shift(); | ||
| if (value) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pullQueue.push(resolve); | ||
| } | ||
| }); | ||
| } | ||
| const pushValue = function (value) { | ||
| const resolve = pullQueue.shift(); | ||
| if (resolve) { | ||
| resolve({ | ||
| value, | ||
| done: false | ||
| }); | ||
| } else { | ||
| pushQueue.push(value); | ||
| } | ||
| }; | ||
| function unsubscribe() { | ||
| subscribers.splice(subscribers.indexOf(pushValue), 1); | ||
| while (pullQueue.shift()) {} | ||
| while (pushQueue.shift()) {} | ||
| } | ||
| function onClose() { | ||
| const resolve = pullQueue.shift(); | ||
| if (!resolve) { | ||
| return; | ||
| } | ||
| resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| unsubscribe(); | ||
| } | ||
| onCloseSubscribers.push(onClose); | ||
| subscribers.push(pushValue); | ||
| return { | ||
| next() { | ||
| return readyState === CLOSED ? this.return() : pullValue(); | ||
| }, | ||
| return() { | ||
| unsubscribe(); | ||
| return Promise.resolve({ | ||
| done: true, | ||
| value: void 0 | ||
| }); | ||
| }, | ||
| throw(error) { | ||
| unsubscribe(); | ||
| return Promise.reject(error); | ||
| }, | ||
| [Symbol.asyncIterator]() { | ||
| return this; | ||
| } | ||
| }; | ||
| } | ||
| function scheduleReconnect() { | ||
| onScheduleReconnect({ | ||
| delay: reconnectMs | ||
| }); | ||
| readyState = CONNECTING; | ||
| reconnectTimer = setTimeout(connect, reconnectMs); | ||
| } | ||
| async function onFetchResponse(response) { | ||
| onConnect(); | ||
| parser.reset(); | ||
| const { | ||
| body, | ||
| redirected, | ||
| status | ||
| } = response; | ||
| if (status === 204) { | ||
| onDisconnect(); | ||
| close(); | ||
| return; | ||
| } | ||
| if (!body) { | ||
| throw new Error("Missing response body"); | ||
| } | ||
| if (redirected) { | ||
| currentUrl = response.url; | ||
| } | ||
| const bodyStream = getStream(body); | ||
| const stream = bodyStream.pipeThrough(new TextDecoderStream()); | ||
| const reader = stream.getReader(); | ||
| let open = true; | ||
| readyState = OPEN; | ||
| do { | ||
| const { | ||
| done, | ||
| value | ||
| } = await reader.read(); | ||
| if (!done) { | ||
| parser.feed(value); | ||
| continue; | ||
| } | ||
| open = false; | ||
| request = null; | ||
| parser.reset(); | ||
| onDisconnect(); | ||
| scheduleReconnect(); | ||
| } while (open); | ||
| } | ||
| function onParsedMessage(msg) { | ||
| if (msg.type === "reconnect-interval") { | ||
| reconnectMs = msg.value; | ||
| return; | ||
| } | ||
| if (typeof msg.id === "string") { | ||
| lastEventId = msg.id; | ||
| } | ||
| emit({ | ||
| id: msg.id, | ||
| data: msg.data, | ||
| event: msg.event | ||
| }); | ||
| } | ||
| function getRequestOptions() { | ||
| const { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy | ||
| } = options; | ||
| const lastEvent = lastEventId ? { | ||
| "Last-Event-ID": lastEventId | ||
| } : void 0; | ||
| const headers = { | ||
| Accept: "text/event-stream", | ||
| ...requestHeaders, | ||
| ...lastEvent | ||
| }; | ||
| return { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy, | ||
| headers, | ||
| cache: "no-store", | ||
| signal: controller.signal | ||
| }; | ||
| } | ||
| } | ||
| function validate(options) { | ||
| const fetch = options.fetch || globalThis.fetch; | ||
| if (!isFetchLike(fetch)) { | ||
| throw new Error("No fetch implementation provided, and one was not found on the global object."); | ||
| } | ||
| if (typeof AbortController !== "function") { | ||
| throw new Error("Missing AbortController implementation"); | ||
| } | ||
| const { | ||
| url, | ||
| initialLastEventId | ||
| } = options; | ||
| if (typeof url !== "string" && !(url instanceof URL)) { | ||
| throw new Error("Invalid URL provided - must be string or URL instance"); | ||
| } | ||
| if (typeof initialLastEventId !== "string" && initialLastEventId !== void 0) { | ||
| throw new Error("Invalid initialLastEventId provided - must be string or undefined"); | ||
| } | ||
| return { | ||
| fetch, | ||
| url, | ||
| initialLastEventId | ||
| }; | ||
| } | ||
| function isFetchLike(fetch) { | ||
| return typeof fetch === "function"; | ||
| } | ||
| const nodeAbstractions = { | ||
| getStream | ||
| }; | ||
| function createEventSource(options) { | ||
| return createEventSource$1(options, nodeAbstractions); | ||
| } | ||
| function getStream(body) { | ||
| if ("getReader" in body) { | ||
| return body; | ||
| } | ||
| if (typeof body.pipe !== "function" || typeof body.on !== "function") { | ||
| throw new Error("Invalid response body, expected a web or node.js stream"); | ||
| } | ||
| if (typeof node_stream.Readable.toWeb !== "function") { | ||
| throw new Error("Node.js 18 or higher required (`Readable.toWeb()` not defined)"); | ||
| } | ||
| return node_stream.Readable.toWeb(node_stream.Readable.from(body)); | ||
| } | ||
| exports.CLOSED = CLOSED; | ||
| exports.CONNECTING = CONNECTING; | ||
| exports.OPEN = OPEN; | ||
| exports.createEventSource = createEventSource; | ||
| //# sourceMappingURL=node.js.map |
| {"version":3,"file":"node.js","sources":["../src/constants.ts","../src/client.ts","../src/node.ts"],"sourcesContent":["// ReadyStates, mirrors WhatWG spec, but uses strings instead of numbers.\n// Why make it harder to read than it needs to be?\n\n/**\n * ReadyState representing a connection that is connecting or has been scheduled to reconnect.\n * @public\n */\nexport const CONNECTING = 'connecting'\n\n/**\n * ReadyState representing a connection that is open, eg connected.\n * @public\n */\nexport const OPEN = 'open'\n\n/**\n * ReadyState representing a connection that has been closed (manually, or due to an error).\n * @public\n */\nexport const CLOSED = 'closed'\n","import {createParser, type ParseEvent} from 'eventsource-parser'\nimport {CLOSED, CONNECTING, OPEN} from './constants'\nimport type {EnvAbstractions, EventSourceAsyncValueResolver} from './abstractions'\nimport type {\n EventSourceClient,\n EventSourceMessage,\n EventSourceOptions,\n FetchLike,\n FetchLikeInit,\n FetchLikeResponse,\n ReadyState,\n} from './types'\n\n/**\n * Intentional noop function for eased control flow\n */\nconst noop = () => {\n /* intentional noop */\n}\n\n/**\n * Creates a new EventSource client. Used internally by the environment-specific entry points,\n * and should not be used directly by consumers.\n *\n * @param options - Options for the client.\n * @param abstractions - Abstractions for the environments.\n * @returns A new EventSource client instance\n * @internal\n */\nexport function createEventSource(\n options: EventSourceOptions,\n {getStream}: EnvAbstractions,\n): EventSourceClient {\n const {onMessage, onConnect = noop, onDisconnect = noop, onScheduleReconnect = noop} = options\n const {fetch, url, initialLastEventId} = validate(options)\n const requestHeaders = {...options.headers} // Prevent post-creation mutations to headers\n\n const onCloseSubscribers: (() => void)[] = []\n const subscribers: ((event: EventSourceMessage) => void)[] = onMessage ? [onMessage] : []\n const emit = (event: EventSourceMessage) => subscribers.forEach((fn) => fn(event))\n const parser = createParser(onParsedMessage)\n\n // Client state\n let request: Promise<unknown> | null\n let currentUrl = url.toString()\n let controller = new AbortController()\n let lastEventId = initialLastEventId\n let reconnectMs = 2000\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined\n let readyState: ReadyState = CLOSED\n\n // Let's go!\n connect()\n\n return {\n close,\n connect,\n [Symbol.asyncIterator]: getEventIterator,\n get lastEventId() {\n return lastEventId\n },\n get url() {\n return currentUrl\n },\n get readyState() {\n return readyState\n },\n }\n\n function connect() {\n if (request) {\n return\n }\n\n readyState = CONNECTING\n controller = new AbortController()\n request = fetch(url, getRequestOptions())\n .then(onFetchResponse)\n .catch((err: Error & {type: string}) => {\n request = null\n\n // We expect abort errors when the user manually calls `close()` - ignore those\n if (err.name !== 'AbortError' && err.type !== 'aborted') {\n throw err\n }\n\n scheduleReconnect()\n })\n }\n\n function close() {\n readyState = CLOSED\n controller.abort()\n parser.reset()\n clearTimeout(reconnectTimer)\n onCloseSubscribers.forEach((fn) => fn())\n }\n\n function getEventIterator(): AsyncGenerator<EventSourceMessage, void> {\n const pullQueue: EventSourceAsyncValueResolver[] = []\n const pushQueue: EventSourceMessage[] = []\n\n function pullValue() {\n return new Promise<IteratorResult<EventSourceMessage, void>>((resolve) => {\n const value = pushQueue.shift()\n if (value) {\n resolve({value, done: false})\n } else {\n pullQueue.push(resolve)\n }\n })\n }\n\n const pushValue = function (value: EventSourceMessage) {\n const resolve = pullQueue.shift()\n if (resolve) {\n resolve({value, done: false})\n } else {\n pushQueue.push(value)\n }\n }\n\n function unsubscribe() {\n subscribers.splice(subscribers.indexOf(pushValue), 1)\n while (pullQueue.shift()) {}\n while (pushQueue.shift()) {}\n }\n\n function onClose() {\n const resolve = pullQueue.shift()\n if (!resolve) {\n return\n }\n\n resolve({done: true, value: undefined})\n unsubscribe()\n }\n\n onCloseSubscribers.push(onClose)\n subscribers.push(pushValue)\n\n return {\n next() {\n return readyState === CLOSED ? this.return() : pullValue()\n },\n return() {\n unsubscribe()\n return Promise.resolve({done: true, value: undefined})\n },\n throw(error) {\n unsubscribe()\n return Promise.reject(error)\n },\n [Symbol.asyncIterator]() {\n return this\n },\n }\n }\n\n function scheduleReconnect() {\n onScheduleReconnect({delay: reconnectMs})\n readyState = CONNECTING\n reconnectTimer = setTimeout(connect, reconnectMs)\n }\n\n async function onFetchResponse(response: FetchLikeResponse) {\n onConnect()\n parser.reset()\n\n const {body, redirected, status} = response\n\n // HTTP 204 means \"close the connection, no more data will be sent\"\n if (status === 204) {\n onDisconnect()\n close()\n return\n }\n\n if (!body) {\n throw new Error('Missing response body')\n }\n\n if (redirected) {\n currentUrl = response.url\n }\n\n // Ensure that the response stream is a web stream\n // @todo Figure out a way to make this work without casting\n const bodyStream = getStream(body as any)\n\n // EventSources are always UTF-8 per spec\n const stream = bodyStream.pipeThrough<string>(new TextDecoderStream())\n const reader = stream.getReader()\n let open = true\n\n readyState = OPEN\n\n do {\n const {done, value} = await reader.read()\n if (!done) {\n parser.feed(value)\n continue\n }\n\n open = false\n request = null\n parser.reset()\n\n onDisconnect()\n\n // EventSources never close unless explicitly handled with `.close()`:\n // Implementors should send an `done`/`complete`/`disconnect` event and\n // explicitly handle it in client code, or send an HTTP 204.\n scheduleReconnect()\n } while (open)\n }\n\n function onParsedMessage(msg: ParseEvent): void {\n if (msg.type === 'reconnect-interval') {\n reconnectMs = msg.value\n return\n }\n\n if (typeof msg.id === 'string') {\n lastEventId = msg.id\n }\n\n emit({\n id: msg.id,\n data: msg.data,\n event: msg.event,\n })\n }\n\n function getRequestOptions(): FetchLikeInit {\n // @todo allow interception of options, but don't allow overriding signal\n const {mode, credentials, body, method, redirect, referrer, referrerPolicy} = options\n const lastEvent = lastEventId ? {'Last-Event-ID': lastEventId} : undefined\n const headers = {Accept: 'text/event-stream', ...requestHeaders, ...lastEvent}\n return {\n mode,\n credentials,\n body,\n method,\n redirect,\n referrer,\n referrerPolicy,\n headers,\n cache: 'no-store',\n signal: controller.signal,\n }\n }\n}\n\nfunction validate(options: EventSourceOptions): {\n fetch: FetchLike\n url: string | URL\n initialLastEventId: string | undefined\n} {\n const fetch = options.fetch || globalThis.fetch\n if (!isFetchLike(fetch)) {\n throw new Error('No fetch implementation provided, and one was not found on the global object.')\n }\n\n if (typeof AbortController !== 'function') {\n throw new Error('Missing AbortController implementation')\n }\n\n const {url, initialLastEventId} = options\n\n if (typeof url !== 'string' && !(url instanceof URL)) {\n throw new Error('Invalid URL provided - must be string or URL instance')\n }\n\n if (typeof initialLastEventId !== 'string' && initialLastEventId !== undefined) {\n throw new Error('Invalid initialLastEventId provided - must be string or undefined')\n }\n\n return {fetch, url, initialLastEventId}\n}\n\n// This is obviously naive, but hard to probe for full compatibility\nfunction isFetchLike(fetch: FetchLike | typeof globalThis.fetch): fetch is FetchLike {\n return typeof fetch === 'function'\n}\n","import {Readable} from 'node:stream'\nimport {createEventSource as createSource} from './client'\nimport type {EventSourceClient, EventSourceOptions} from './types'\nimport type {EnvAbstractions} from './abstractions'\n\nexport * from './types'\nexport * from './constants'\n\nconst nodeAbstractions: EnvAbstractions = {\n getStream,\n}\n\n/**\n * Creates a new EventSource client.\n *\n * @param options - Options for the client\n * @returns A new EventSource client instance\n * @public\n */\nexport function createEventSource(options: EventSourceOptions): EventSourceClient {\n return createSource(options, nodeAbstractions)\n}\n\n/**\n * Returns a ReadableStream (Web Stream) from either an existing ReadableStream,\n * or a node.js Readable stream. Ensures that it works with more `fetch()` polyfills.\n *\n * @param body - The body to convert\n * @returns A ReadableStream\n * @private\n */\nfunction getStream(\n body: NodeJS.ReadableStream | ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n if ('getReader' in body) {\n // Already a web stream\n return body\n }\n\n if (typeof body.pipe !== 'function' || typeof body.on !== 'function') {\n throw new Error('Invalid response body, expected a web or node.js stream')\n }\n\n // Available as of Node 17, and module requires Node 18\n if (typeof Readable.toWeb !== 'function') {\n throw new Error('Node.js 18 or higher required (`Readable.toWeb()` not defined)')\n }\n\n // @todo Figure out if we can prevent casting\n return Readable.toWeb(Readable.from(body)) as ReadableStream<Uint8Array>\n}\n"],"names":["CONNECTING","OPEN","CLOSED","noop","createEventSource","createEventSource$1","options","_ref","getStream","onMessage","onConnect","onDisconnect","onScheduleReconnect","fetch","url","initialLastEventId","validate","requestHeaders","headers","onCloseSubscribers","subscribers","emit","event","forEach","fn","parser","createParser","onParsedMessage","request","currentUrl","toString","controller","AbortController","lastEventId","reconnectMs","reconnectTimer","readyState","connect","close","Symbol","asyncIterator","getEventIterator","getRequestOptions","then","onFetchResponse","catch","err","name","type","scheduleReconnect","abort","reset","clearTimeout","pullQueue","pushQueue","pullValue","Promise","resolve","value","shift","done","push","pushValue","unsubscribe","splice","indexOf","onClose","next","return","throw","error","reject","delay","setTimeout","response","body","redirected","status","Error","bodyStream","stream","pipeThrough","TextDecoderStream","reader","getReader","open","read","feed","msg","id","data","mode","credentials","method","redirect","referrer","referrerPolicy","lastEvent","Accept","cache","signal","globalThis","isFetchLike","URL","nodeAbstractions","createSource","pipe","on","Readable","toWeb","from"],"mappings":"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"} |
Sorry, the diff of this file is too big to display
+21
| MIT License | ||
| Copyright (c) 2023 Espen Hovlandsdal <espen@hovlandsdal.com> | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+104
| # eventsource-client | ||
| [](http://npmjs.org/package/eventsource-client)[](https://bundlephobia.com/result?p=eventsource-client) | ||
| A modern, streaming client for [server-sent events/eventsource](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). | ||
| ## Another one? | ||
| Yes! There are indeed lots of different EventSource clients and polyfills out there. In fact, I am a co-maintainer of [the most popular one](https://github.com/eventsource/eventsource). This one is different in a few ways, however: | ||
| - Works in both Node.js and browsers with minimal amount of differences in code | ||
| - Ships with both ESM and CommonJS versions | ||
| - Uses modern APIs such as the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [Web Streams](https://streams.spec.whatwg.org/) | ||
| - Does **NOT** attempt to be API-compatible with the browser EventSource API: | ||
| - Supports async iterator pattern | ||
| - Supports any request method (POST, PATCH, DELETE etc) | ||
| - Supports setting custom headers | ||
| - Supports sending a request body | ||
| - Supports configurable reconnection policies | ||
| - Supports subscribing to any event (eg if event names are not known) | ||
| - Supports subscribing to events named `error` | ||
| - Supports setting initial last event ID | ||
| ## Installation | ||
| ```bash | ||
| npm install --save eventsource-client | ||
| ``` | ||
| ## Supported engines | ||
| - Node.js >= 18 | ||
| - Chrome >= 71 | ||
| - Safari >= 14.1 | ||
| - Firefox >= 105 | ||
| - Edge >= 79 | ||
| Basically, any environment that supports the [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) (with [pipeThrough](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/pipeThrough) support) and the [TextDecoderStream](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream) APIs. | ||
| ## Usage (async iterator) | ||
| ```ts | ||
| import {createEventSource} from 'eventsource-client' | ||
| const es = createEventSource({ | ||
| url: 'https://my-server.com/sse', | ||
| // your `fetch()` implementation of choice, or `globalThis.fetch` if not set | ||
| fetch: myFetch, | ||
| }) | ||
| let seenMessages = 0 | ||
| for await (const {data, event, id} of es) { | ||
| console.log('Data: %s', data) | ||
| console.log('Event ID: %s', id) // Note: can be undefined | ||
| console.log('Event: %s', event) // Note: can be undefined | ||
| if (++seenMessages === 10) { | ||
| break | ||
| } | ||
| } | ||
| // IMPORTANT: EventSource is _not_ closed automatically when breaking out of | ||
| // loop. You must manually call `close()` to close the connection. | ||
| es.close() | ||
| ``` | ||
| ## Usage (`onMessage` callback) | ||
| ```ts | ||
| import {createEventSource} from 'eventsource-client' | ||
| const es = createEventSource({ | ||
| url: 'https://my-server.com/sse', | ||
| onMessage: ({data, event, id}) => { | ||
| console.log('Data: %s', data) | ||
| console.log('Event ID: %s', id) // Note: can be undefined | ||
| console.log('Event: %s', event) // Note: can be undefined | ||
| }, | ||
| // your `fetch()` implementation of choice, or `globalThis.fetch` if not set | ||
| fetch: myFetch, | ||
| }) | ||
| console.log(es.readyState) // `open`, `closed` or `connecting` | ||
| console.log(es.lastEventId) | ||
| // Later, to terminate and prevent reconnections: | ||
| es.close() | ||
| ``` | ||
| ## Todo | ||
| - [ ] Figure out what to do on broken connection on request body | ||
| - [ ] Configurable stalled connection detection (eg no data) | ||
| - [ ] Configurable reconnection policy | ||
| - [ ] Deno support/tests | ||
| - [ ] Bun support/tests (blocked: no `TextDecoderStream` support) | ||
| - [ ] Consider legacy build | ||
| ## License | ||
| MIT © [Espen Hovlandsdal](https://espen.codes/) |
| import type {EventSourceMessage} from './types' | ||
| /** | ||
| * Internal abstractions over environment-specific APIs, to keep node-specifics | ||
| * out of browser bundles and vice versa. | ||
| * | ||
| * @internal | ||
| */ | ||
| export interface EnvAbstractions { | ||
| getStream(body: NodeJS.ReadableStream | ReadableStream<Uint8Array>): ReadableStream<Uint8Array> | ||
| } | ||
| /** | ||
| * Resolver function that emits an (async) event source message value. | ||
| * Used internally by AsyncIterator implementation, not for external use. | ||
| * | ||
| * @internal | ||
| */ | ||
| export type EventSourceAsyncValueResolver = ( | ||
| value: | ||
| | IteratorResult<EventSourceMessage, void> | ||
| | PromiseLike<IteratorResult<EventSourceMessage, void>>, | ||
| ) => void |
+285
| import {createParser, type ParseEvent} from 'eventsource-parser' | ||
| import {CLOSED, CONNECTING, OPEN} from './constants' | ||
| import type {EnvAbstractions, EventSourceAsyncValueResolver} from './abstractions' | ||
| import type { | ||
| EventSourceClient, | ||
| EventSourceMessage, | ||
| EventSourceOptions, | ||
| FetchLike, | ||
| FetchLikeInit, | ||
| FetchLikeResponse, | ||
| ReadyState, | ||
| } from './types' | ||
| /** | ||
| * Intentional noop function for eased control flow | ||
| */ | ||
| const noop = () => { | ||
| /* intentional noop */ | ||
| } | ||
| /** | ||
| * Creates a new EventSource client. Used internally by the environment-specific entry points, | ||
| * and should not be used directly by consumers. | ||
| * | ||
| * @param options - Options for the client. | ||
| * @param abstractions - Abstractions for the environments. | ||
| * @returns A new EventSource client instance | ||
| * @internal | ||
| */ | ||
| export function createEventSource( | ||
| options: EventSourceOptions, | ||
| {getStream}: EnvAbstractions, | ||
| ): EventSourceClient { | ||
| const {onMessage, onConnect = noop, onDisconnect = noop, onScheduleReconnect = noop} = options | ||
| const {fetch, url, initialLastEventId} = validate(options) | ||
| const requestHeaders = {...options.headers} // Prevent post-creation mutations to headers | ||
| const onCloseSubscribers: (() => void)[] = [] | ||
| const subscribers: ((event: EventSourceMessage) => void)[] = onMessage ? [onMessage] : [] | ||
| const emit = (event: EventSourceMessage) => subscribers.forEach((fn) => fn(event)) | ||
| const parser = createParser(onParsedMessage) | ||
| // Client state | ||
| let request: Promise<unknown> | null | ||
| let currentUrl = url.toString() | ||
| let controller = new AbortController() | ||
| let lastEventId = initialLastEventId | ||
| let reconnectMs = 2000 | ||
| let reconnectTimer: ReturnType<typeof setTimeout> | undefined | ||
| let readyState: ReadyState = CLOSED | ||
| // Let's go! | ||
| connect() | ||
| return { | ||
| close, | ||
| connect, | ||
| [Symbol.asyncIterator]: getEventIterator, | ||
| get lastEventId() { | ||
| return lastEventId | ||
| }, | ||
| get url() { | ||
| return currentUrl | ||
| }, | ||
| get readyState() { | ||
| return readyState | ||
| }, | ||
| } | ||
| function connect() { | ||
| if (request) { | ||
| return | ||
| } | ||
| readyState = CONNECTING | ||
| controller = new AbortController() | ||
| request = fetch(url, getRequestOptions()) | ||
| .then(onFetchResponse) | ||
| .catch((err: Error & {type: string}) => { | ||
| request = null | ||
| // We expect abort errors when the user manually calls `close()` - ignore those | ||
| if (err.name !== 'AbortError' && err.type !== 'aborted') { | ||
| throw err | ||
| } | ||
| scheduleReconnect() | ||
| }) | ||
| } | ||
| function close() { | ||
| readyState = CLOSED | ||
| controller.abort() | ||
| parser.reset() | ||
| clearTimeout(reconnectTimer) | ||
| onCloseSubscribers.forEach((fn) => fn()) | ||
| } | ||
| function getEventIterator(): AsyncGenerator<EventSourceMessage, void> { | ||
| const pullQueue: EventSourceAsyncValueResolver[] = [] | ||
| const pushQueue: EventSourceMessage[] = [] | ||
| function pullValue() { | ||
| return new Promise<IteratorResult<EventSourceMessage, void>>((resolve) => { | ||
| const value = pushQueue.shift() | ||
| if (value) { | ||
| resolve({value, done: false}) | ||
| } else { | ||
| pullQueue.push(resolve) | ||
| } | ||
| }) | ||
| } | ||
| const pushValue = function (value: EventSourceMessage) { | ||
| const resolve = pullQueue.shift() | ||
| if (resolve) { | ||
| resolve({value, done: false}) | ||
| } else { | ||
| pushQueue.push(value) | ||
| } | ||
| } | ||
| function unsubscribe() { | ||
| subscribers.splice(subscribers.indexOf(pushValue), 1) | ||
| while (pullQueue.shift()) {} | ||
| while (pushQueue.shift()) {} | ||
| } | ||
| function onClose() { | ||
| const resolve = pullQueue.shift() | ||
| if (!resolve) { | ||
| return | ||
| } | ||
| resolve({done: true, value: undefined}) | ||
| unsubscribe() | ||
| } | ||
| onCloseSubscribers.push(onClose) | ||
| subscribers.push(pushValue) | ||
| return { | ||
| next() { | ||
| return readyState === CLOSED ? this.return() : pullValue() | ||
| }, | ||
| return() { | ||
| unsubscribe() | ||
| return Promise.resolve({done: true, value: undefined}) | ||
| }, | ||
| throw(error) { | ||
| unsubscribe() | ||
| return Promise.reject(error) | ||
| }, | ||
| [Symbol.asyncIterator]() { | ||
| return this | ||
| }, | ||
| } | ||
| } | ||
| function scheduleReconnect() { | ||
| onScheduleReconnect({delay: reconnectMs}) | ||
| readyState = CONNECTING | ||
| reconnectTimer = setTimeout(connect, reconnectMs) | ||
| } | ||
| async function onFetchResponse(response: FetchLikeResponse) { | ||
| onConnect() | ||
| parser.reset() | ||
| const {body, redirected, status} = response | ||
| // HTTP 204 means "close the connection, no more data will be sent" | ||
| if (status === 204) { | ||
| onDisconnect() | ||
| close() | ||
| return | ||
| } | ||
| if (!body) { | ||
| throw new Error('Missing response body') | ||
| } | ||
| if (redirected) { | ||
| currentUrl = response.url | ||
| } | ||
| // Ensure that the response stream is a web stream | ||
| // @todo Figure out a way to make this work without casting | ||
| const bodyStream = getStream(body as any) | ||
| // EventSources are always UTF-8 per spec | ||
| const stream = bodyStream.pipeThrough<string>(new TextDecoderStream()) | ||
| const reader = stream.getReader() | ||
| let open = true | ||
| readyState = OPEN | ||
| do { | ||
| const {done, value} = await reader.read() | ||
| if (!done) { | ||
| parser.feed(value) | ||
| continue | ||
| } | ||
| open = false | ||
| request = null | ||
| parser.reset() | ||
| onDisconnect() | ||
| // EventSources never close unless explicitly handled with `.close()`: | ||
| // Implementors should send an `done`/`complete`/`disconnect` event and | ||
| // explicitly handle it in client code, or send an HTTP 204. | ||
| scheduleReconnect() | ||
| } while (open) | ||
| } | ||
| function onParsedMessage(msg: ParseEvent): void { | ||
| if (msg.type === 'reconnect-interval') { | ||
| reconnectMs = msg.value | ||
| return | ||
| } | ||
| if (typeof msg.id === 'string') { | ||
| lastEventId = msg.id | ||
| } | ||
| emit({ | ||
| id: msg.id, | ||
| data: msg.data, | ||
| event: msg.event, | ||
| }) | ||
| } | ||
| function getRequestOptions(): FetchLikeInit { | ||
| // @todo allow interception of options, but don't allow overriding signal | ||
| const {mode, credentials, body, method, redirect, referrer, referrerPolicy} = options | ||
| const lastEvent = lastEventId ? {'Last-Event-ID': lastEventId} : undefined | ||
| const headers = {Accept: 'text/event-stream', ...requestHeaders, ...lastEvent} | ||
| return { | ||
| mode, | ||
| credentials, | ||
| body, | ||
| method, | ||
| redirect, | ||
| referrer, | ||
| referrerPolicy, | ||
| headers, | ||
| cache: 'no-store', | ||
| signal: controller.signal, | ||
| } | ||
| } | ||
| } | ||
| function validate(options: EventSourceOptions): { | ||
| fetch: FetchLike | ||
| url: string | URL | ||
| initialLastEventId: string | undefined | ||
| } { | ||
| const fetch = options.fetch || globalThis.fetch | ||
| if (!isFetchLike(fetch)) { | ||
| throw new Error('No fetch implementation provided, and one was not found on the global object.') | ||
| } | ||
| if (typeof AbortController !== 'function') { | ||
| throw new Error('Missing AbortController implementation') | ||
| } | ||
| const {url, initialLastEventId} = options | ||
| if (typeof url !== 'string' && !(url instanceof URL)) { | ||
| throw new Error('Invalid URL provided - must be string or URL instance') | ||
| } | ||
| if (typeof initialLastEventId !== 'string' && initialLastEventId !== undefined) { | ||
| throw new Error('Invalid initialLastEventId provided - must be string or undefined') | ||
| } | ||
| return {fetch, url, initialLastEventId} | ||
| } | ||
| // This is obviously naive, but hard to probe for full compatibility | ||
| function isFetchLike(fetch: FetchLike | typeof globalThis.fetch): fetch is FetchLike { | ||
| return typeof fetch === 'function' | ||
| } |
| // ReadyStates, mirrors WhatWG spec, but uses strings instead of numbers. | ||
| // Why make it harder to read than it needs to be? | ||
| /** | ||
| * ReadyState representing a connection that is connecting or has been scheduled to reconnect. | ||
| * @public | ||
| */ | ||
| export const CONNECTING = 'connecting' | ||
| /** | ||
| * ReadyState representing a connection that is open, eg connected. | ||
| * @public | ||
| */ | ||
| export const OPEN = 'open' | ||
| /** | ||
| * ReadyState representing a connection that has been closed (manually, or due to an error). | ||
| * @public | ||
| */ | ||
| export const CLOSED = 'closed' |
| import type {EnvAbstractions} from './abstractions' | ||
| import type {EventSourceClient, EventSourceOptions} from './types' | ||
| import {createEventSource as createSource} from './client' | ||
| export * from './types' | ||
| export * from './constants' | ||
| /** | ||
| * Default "abstractions", eg when all the APIs are globally available | ||
| */ | ||
| const defaultAbstractions: EnvAbstractions = { | ||
| getStream, | ||
| } | ||
| /** | ||
| * Creates a new EventSource client. | ||
| * | ||
| * @param options - Options for the client | ||
| * @returns A new EventSource client instance | ||
| * @public | ||
| */ | ||
| export function createEventSource(options: EventSourceOptions): EventSourceClient { | ||
| return createSource(options, defaultAbstractions) | ||
| } | ||
| /** | ||
| * Returns a ReadableStream (Web Stream) from either an existing ReadableStream. | ||
| * Only defined because of environment abstractions - is actually a 1:1 (passthrough). | ||
| * | ||
| * @param body - The body to convert | ||
| * @returns A ReadableStream | ||
| * @private | ||
| */ | ||
| function getStream( | ||
| body: NodeJS.ReadableStream | ReadableStream<Uint8Array>, | ||
| ): ReadableStream<Uint8Array> { | ||
| if (!(body instanceof ReadableStream)) { | ||
| throw new Error('Invalid stream, expected a web ReadableStream') | ||
| } | ||
| return body | ||
| } |
+51
| import {Readable} from 'node:stream' | ||
| import {createEventSource as createSource} from './client' | ||
| import type {EventSourceClient, EventSourceOptions} from './types' | ||
| import type {EnvAbstractions} from './abstractions' | ||
| export * from './types' | ||
| export * from './constants' | ||
| const nodeAbstractions: EnvAbstractions = { | ||
| getStream, | ||
| } | ||
| /** | ||
| * Creates a new EventSource client. | ||
| * | ||
| * @param options - Options for the client | ||
| * @returns A new EventSource client instance | ||
| * @public | ||
| */ | ||
| export function createEventSource(options: EventSourceOptions): EventSourceClient { | ||
| return createSource(options, nodeAbstractions) | ||
| } | ||
| /** | ||
| * Returns a ReadableStream (Web Stream) from either an existing ReadableStream, | ||
| * or a node.js Readable stream. Ensures that it works with more `fetch()` polyfills. | ||
| * | ||
| * @param body - The body to convert | ||
| * @returns A ReadableStream | ||
| * @private | ||
| */ | ||
| function getStream( | ||
| body: NodeJS.ReadableStream | ReadableStream<Uint8Array>, | ||
| ): ReadableStream<Uint8Array> { | ||
| if ('getReader' in body) { | ||
| // Already a web stream | ||
| return body | ||
| } | ||
| if (typeof body.pipe !== 'function' || typeof body.on !== 'function') { | ||
| throw new Error('Invalid response body, expected a web or node.js stream') | ||
| } | ||
| // Available as of Node 17, and module requires Node 18 | ||
| if (typeof Readable.toWeb !== 'function') { | ||
| throw new Error('Node.js 18 or higher required (`Readable.toWeb()` not defined)') | ||
| } | ||
| // @todo Figure out if we can prevent casting | ||
| return Readable.toWeb(Readable.from(body)) as ReadableStream<Uint8Array> | ||
| } |
+160
| import type {ReadableStream as NodeWebReadableStream} from 'node:stream/web' | ||
| /** | ||
| * Ready state for a connection. | ||
| * | ||
| * @public | ||
| */ | ||
| export type ReadyState = 'open' | 'connecting' | 'closed' | ||
| /** | ||
| * EventSource client. | ||
| * | ||
| * @public | ||
| */ | ||
| export interface EventSourceClient { | ||
| /** Close the connection and prevent the client from reconnecting automatically. */ | ||
| close(): void | ||
| /** Connect to the event source. Automatically called on creation - you only need to call this after manually calling `close()`, when server has sent an HTTP 204, or the server responded with a non-retryable error. */ | ||
| connect(): void | ||
| /** Async iterator of messages received */ | ||
| [Symbol.asyncIterator](): AsyncIterableIterator<EventSourceMessage> | ||
| /** Last seen event ID, or the `initialLastEventId` if none has been received yet. */ | ||
| readonly lastEventId: string | undefined | ||
| /** Current URL. Usually the same as `url`, but in the case of allowed redirects, it will reflect the new URL. */ | ||
| readonly url: string | ||
| /** Ready state of the connection */ | ||
| readonly readyState: ReadyState | ||
| } | ||
| /** | ||
| * Options for the eventsource client. | ||
| * | ||
| * @public | ||
| */ | ||
| export interface EventSourceOptions { | ||
| /** URL to connect to. */ | ||
| url: string | URL | ||
| /** Callback that fires each time a new event is received. */ | ||
| onMessage?: (event: EventSourceMessage) => void | ||
| /** Callback that fires each time the connection is established (multiple times in the case of reconnects). */ | ||
| onConnect?: () => void | ||
| /** Callback that fires each time we schedule a new reconnect attempt. Will include an object with information on how many milliseconds it will attempt to delay before doing the reconnect. */ | ||
| onScheduleReconnect?: (info: {delay: number}) => void | ||
| /** Callback that fires each time the connection is broken (will still attempt to reconnect, unless `close()` is called). */ | ||
| onDisconnect?: () => void | ||
| /** A string to use for the initial `Last-Event-ID` header when connecting. Only used until the first message with a new ID is received. */ | ||
| initialLastEventId?: string | ||
| /** Fetch implementation to use for performing requests. Defaults to `globalThis.fetch`. Throws if no implementation can be found. */ | ||
| fetch?: FetchLike | ||
| // --- request-related follow --- // | ||
| /** An object literal to set request's headers. */ | ||
| headers?: Record<string, string> | ||
| /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */ | ||
| mode?: 'cors' | 'no-cors' | 'same-origin' | ||
| /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */ | ||
| credentials?: 'include' | 'omit' | 'same-origin' | ||
| /** A BodyInit object or null to set request's body. */ | ||
| body?: any | ||
| /** A string to set request's method. */ | ||
| method?: string | ||
| /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ | ||
| redirect?: 'error' | 'follow' | ||
| /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */ | ||
| referrer?: string | ||
| /** A referrer policy to set request's referrerPolicy. */ | ||
| referrerPolicy?: ReferrerPolicy | ||
| } | ||
| /** | ||
| * A message received from the server. | ||
| * | ||
| * @public | ||
| */ | ||
| export interface EventSourceMessage { | ||
| /** The data received for this message. */ | ||
| data: string | ||
| /** Event name sent from the server, or `undefined` if none is set for this message. */ | ||
| event?: string | ||
| /** ID of the message, if any was provided by the server. */ | ||
| id?: string | ||
| } | ||
| /** | ||
| * Stripped down version of `fetch()`, only defining the parts we care about. | ||
| * This ensures it should work with "most" fetch implementations. | ||
| * | ||
| * @public | ||
| */ | ||
| export type FetchLike = (url: string | URL, init?: FetchLikeInit) => Promise<FetchLikeResponse> | ||
| /** | ||
| * Stripped down version of `RequestInit`, only defining the parts we care about. | ||
| * | ||
| * @public | ||
| */ | ||
| export interface FetchLikeInit { | ||
| /** A string to set request's method. */ | ||
| method?: string | ||
| /** An AbortSignal to set request's signal. Typed as `any` because of polyfill inconsistencies. */ | ||
| signal?: {aborted: boolean} | any | ||
| /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ | ||
| headers?: Record<string, string> | ||
| /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */ | ||
| mode?: 'cors' | 'no-cors' | 'same-origin' | ||
| /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */ | ||
| credentials?: 'include' | 'omit' | 'same-origin' | ||
| /** Controls how the request is cached. */ | ||
| cache?: 'no-store' | ||
| /** Request body. */ | ||
| body?: any | ||
| /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ | ||
| redirect?: 'error' | 'follow' | ||
| /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */ | ||
| referrer?: string | ||
| /** A referrer policy to set request's referrerPolicy. */ | ||
| referrerPolicy?: ReferrerPolicy | ||
| } | ||
| /** | ||
| * Minimal version of the `Response` type returned by `fetch()`. | ||
| * | ||
| * @public | ||
| */ | ||
| export interface FetchLikeResponse { | ||
| readonly body: NodeJS.ReadableStream | NodeWebReadableStream<any> | Response['body'] | null | ||
| readonly url: string | ||
| readonly status: number | ||
| readonly redirected: boolean | ||
| } |
+107
-7
| { | ||
| "name": "eventsource-client", | ||
| "version": "0.0.1", | ||
| "description": "", | ||
| "main": "index.js", | ||
| "version": "1.0.0", | ||
| "description": "Modern EventSource client for browsers and Node.js", | ||
| "sideEffects": false, | ||
| "types": "./dist/default.d.ts", | ||
| "source": "./src/default.ts", | ||
| "module": "./dist/default.esm.js", | ||
| "main": "./dist/default.js", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/default.d.ts", | ||
| "source": "./src/default.ts", | ||
| "node": { | ||
| "import": "./dist/node.cjs.mjs", | ||
| "require": "./dist/node.js" | ||
| }, | ||
| "require": "./dist/default.js", | ||
| "import": "./dist/default.esm.js", | ||
| "default": "./dist/default.esm.js" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| "build": "pkg-utils build && pkg-utils --strict", | ||
| "clean": "rimraf dist coverage", | ||
| "lint": "eslint . && tsc --noEmit", | ||
| "posttest": "npm run lint", | ||
| "prebuild": "npm run clean", | ||
| "prepublishOnly": "npm run build", | ||
| "test": "npm run test:node && npm run test:browser", | ||
| "test:node": "ts-node test/node/client.node.test.ts", | ||
| "test:browser": "ts-node test/browser/client.browser.test.ts" | ||
| }, | ||
| "keywords": [], | ||
| "author": "Espen Hovlandsdal", | ||
| "license": "MIT" | ||
| "files": [ | ||
| "dist", | ||
| "src" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+ssh://git@github.com/rexxars/eventsource-client.git" | ||
| }, | ||
| "keywords": [ | ||
| "sse", | ||
| "eventsource", | ||
| "server-sent-events" | ||
| ], | ||
| "author": "Espen Hovlandsdal <espen@hovlandsdal.com>", | ||
| "license": "MIT", | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| }, | ||
| "dependencies": { | ||
| "eventsource-parser": "^1.1.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@sanity/pkg-utils": "^3.2.3", | ||
| "@sanity/semantic-release-preset": "^4.1.6", | ||
| "@types/express": "^4.17.21", | ||
| "@types/node": "^18.0.0", | ||
| "@types/sinon": "^17.0.1", | ||
| "@typescript-eslint/eslint-plugin": "^6.11.0", | ||
| "@typescript-eslint/parser": "^6.11.0", | ||
| "esbuild": "^0.19.5", | ||
| "eslint": "^8.53.0", | ||
| "eslint-config-prettier": "^9.0.0", | ||
| "eslint-config-sanity": "^7.0.1", | ||
| "playwright": "^1.39.0", | ||
| "prettier": "^3.1.0", | ||
| "rimraf": "^5.0.5", | ||
| "rollup-plugin-visualizer": "^5.9.2", | ||
| "semantic-release": "^22.0.7", | ||
| "sinon": "^17.0.1", | ||
| "ts-node": "^10.9.1", | ||
| "typescript": "^5.2.2", | ||
| "undici": "^5.27.2" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/rexxars/eventsource-client/issues" | ||
| }, | ||
| "homepage": "https://github.com/rexxars/eventsource-client#readme", | ||
| "prettier": { | ||
| "semi": false, | ||
| "printWidth": 100, | ||
| "bracketSpacing": false, | ||
| "singleQuote": true | ||
| }, | ||
| "eslintConfig": { | ||
| "parserOptions": { | ||
| "ecmaVersion": 9, | ||
| "sourceType": "module", | ||
| "ecmaFeatures": { | ||
| "modules": true | ||
| } | ||
| }, | ||
| "extends": [ | ||
| "sanity", | ||
| "sanity/typescript", | ||
| "prettier" | ||
| ], | ||
| "ignorePatterns": [ | ||
| "lib/**/" | ||
| ], | ||
| "globals": { | ||
| "globalThis": false | ||
| }, | ||
| "rules": { | ||
| "no-undef": "off", | ||
| "no-empty": "off" | ||
| } | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
No website
QualityPackage does not have a website.
257834
103447.79%18
1700%1470
Infinity%0
-100%0
-100%0
-100%105
Infinity%0
-100%1
Infinity%20
Infinity%42
4100%+ Added
+ Added