UNPKG

fetch-sse

Version:

An easy API for making Event Source requests, with all the features of fetch(), Supports browsers and node.

1 lines 12.7 kB
{"version":3,"sources":["../src/index.ts","../src/sse.ts","../src/utils.ts","../src/fetch.ts"],"sourcesContent":["export * from './fetch';\nexport * from './interface';","import { Bytes, ServerSentEvent, LinesResult } from './interface';\n\nexport const NewLineChars = {\n NewLine: 10,\n CarriageReturn: 13,\n Space: 32,\n Colon: 58\n};\n\nexport async function parseServerSentEvent(stream: ReadableStream<Uint8Array>, onMessage: (event: ServerSentEvent) => void) {\n const lineDecoder = new LineDecoder();\n\n await getBytes(stream, (chunk: Uint8Array) => {\n const decoder = new MessageDecoder();\n // get string lines, newline-separated should be \\n,\\r,\\r\\n\n const list = lineDecoder.getLines(chunk);\n for (const data of list) {\n const source = decoder.decode(data.line, data.fieldLength);\n if (source) onMessage(source);\n }\n });\n}\n\n/**\n * Converts a ReadableStream into a callback pattern.\n */\nasync function getBytes(stream: ReadableStream<Uint8Array>, onChunk: (arr: Uint8Array) => void) {\n const reader = stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n onChunk(value);\n }\n}\n\n/**\n * decode string lines to ServerSentEvent\n */\nexport class MessageDecoder {\n private data: string[];\n private event: string | null;\n private chunks: string[];\n\n constructor() {\n this.event = null;\n this.data = [];\n this.chunks = [];\n }\n\n\n public decode(line: Uint8Array, filedLength: number) {\n if (line.length === 0) {\n // empty line denotes end of message. return event data and start a new message:\n const sse: ServerSentEvent = {\n event: this.event,\n data: this.data.join('\\n'),\n raw: this.chunks,\n };\n\n // new message\n this.event = null;\n this.data = [];\n this.chunks = [];\n\n return sse;\n } else if (filedLength > 0) {\n // line is of format \"<field>:<value>\" or \"<field>: <value>\"\n const field = this.decodeText(line.subarray(0, filedLength));\n const valueOffset = filedLength + (line[filedLength + 1] === NewLineChars.Space ? 2 : 1);\n const value = this.decodeText(line.subarray(valueOffset));\n\n this.chunks.push(value);\n switch (field) {\n case 'event':\n this.event = value;\n break;\n case 'data':\n this.data.push(value);\n break;\n }\n }\n }\n\n private decodeText(bytes: Bytes): string {\n // Node:\n if (typeof Buffer !== 'undefined') {\n if (bytes instanceof Buffer) {\n return bytes.toString('utf-8');\n }\n if (bytes instanceof Uint8Array) {\n return Buffer.from(bytes).toString('utf-8');\n }\n\n throw new Error(\n `Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global \"Buffer\" defined, which this library assumes to be Node. Please report this error.`,\n );\n }\n\n // Browser\n if (typeof TextDecoder !== 'undefined') {\n if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) {\n const decoder = new TextDecoder('utf8');\n return decoder.decode(bytes);\n }\n\n throw new Error(\n `Unexpected: received non-Uint8Array/ArrayBuffer (${\n (bytes as any).constructor.name\n }) in a web platform. Please report this error.`,\n );\n }\n\n throw new Error(\n 'Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.',\n );\n }\n}\n\n/**\n * Parses any byte chunks into EventSource line buffers.\n */\nexport class LineDecoder {\n private buffer: Uint8Array | undefined;\n private position: number;\n private fieldLength: number;\n private trailingNewLine: boolean;\n\n constructor() {\n this.position = 0;\n this.fieldLength = -1;\n this.buffer = undefined;\n this.trailingNewLine = false;\n }\n\n getLines(chunk: Uint8Array): LinesResult[] {\n if (this.buffer === undefined) {\n this.buffer = chunk;\n this.position = 0;\n this.fieldLength = -1;\n } else {\n const buffer = new Uint8Array(this.buffer.length + chunk.length);\n buffer.set(this.buffer);\n buffer.set(chunk, this.buffer.length);\n this.buffer = buffer;\n }\n\n const { buffer } = this;\n\n const bufLength = this.buffer.length;\n let lineStart = 0;\n let resultBuf: Uint8Array = new Uint8Array();\n let resultFieldLength = -1;\n const list: LinesResult[] = [];\n while (this.position < bufLength) {\n // check new line char, if checked, skip to next char\n if (this.trailingNewLine) {\n if (buffer[this.position] === NewLineChars.NewLine) {\n lineStart = ++this.position;\n }\n\n this.trailingNewLine = false;\n }\n\n let lineEnd = -1;\n for (; this.position < bufLength && lineEnd === -1; ++this.position) {\n switch (buffer[this.position]) {\n case NewLineChars.Colon:\n if (this.fieldLength === -1) this.fieldLength = this.position - lineStart;\n break;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - this case ('\\r') should fallthrough to NewLine '\\n'\n case NewLineChars.CarriageReturn:\n this.trailingNewLine = true;\n // eslint-disable-next-line no-fallthrough\n case NewLineChars.NewLine:\n lineEnd = this.position;\n break;\n }\n }\n\n if (lineEnd === -1) {\n // the line has not ended, so we need to the next line and continue parsing.\n break;\n }\n\n // got the data\n resultBuf = this.buffer.subarray(lineStart, lineEnd);\n resultFieldLength = this.fieldLength;\n list.push({ fieldLength: resultFieldLength, line: resultBuf });\n lineStart = this.position;\n this.fieldLength = -1;\n }\n\n if (lineStart === bufLength) {\n this.buffer = undefined;\n } else if (lineStart !== 0) {\n this.buffer = this.buffer.subarray(lineStart);\n this.position -= lineStart;\n }\n\n \n return list;\n }\n}\n","export const checkOk = async (response: Response): Promise<void> => {\n if (!response.ok) {\n const defaultMessage = `Error ${response.status}: ${response.statusText}`;\n let message = '';\n if (response.headers.get('content-type')?.includes('application/json')) {\n try {\n const errorData = await response.json();\n message = errorData.message || errorData.error || defaultMessage;\n } catch(error) {\n throw new Error('Failed to parse error response as JSON');\n }\n } else {\n try {\n const textData = await response.text();\n message = textData || defaultMessage;\n } catch(error) {\n throw new Error('Failed to parse error response as text');\n }\n }\n\n throw new Error(message);\n }\n};\n","import { IFetchOptions } from './interface';\nimport { parseServerSentEvent } from './sse';\nimport { checkOk } from './utils';\n\nexport async function fetchEventData(url: string, options: IFetchOptions = {}): Promise<void> {\n const { method, data = null, headers = {}, signal, onMessage, onError, onOpen, onClose } = options;\n const defaultHeaders = {\n Accept: 'text/event-stream',\n 'Content-Type': 'application/json'\n };\n const mergedHeaders = {\n ...defaultHeaders,\n ...headers\n };\n let body: BodyInit | null;\n if (isPlainObject(data)) {\n body = JSON.stringify(data as Record<string, any>);\n } else {\n body = data as (BodyInit | null);\n }\n try {\n const res = await fetch(url, {\n method,\n headers: mergedHeaders,\n body,\n signal: signal\n });\n await checkOk(res);\n onOpen?.(res);\n // consumes data\n if (typeof onMessage === 'function' && res.body) {\n await parseServerSentEvent(res.body, (event) => {\n onMessage(event);\n });\n onClose?.();\n }\n } catch (err: any) {\n onError?.(err);\n }\n}\n\nfunction isPlainObject(obj: any): boolean {\n if (obj === null || typeof obj !== 'object') {\n return false;\n }\n const proto = Object.getPrototypeOf(obj);\n return proto === Object.prototype || proto === null;\n}\n"],"mappings":"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","names":["buffer"]}