diff --git a/docs/docs/api/EventSource.md b/docs/docs/api/EventSource.md index 27b146785b9..8244aa77ed9 100644 --- a/docs/docs/api/EventSource.md +++ b/docs/docs/api/EventSource.md @@ -1,5 +1,7 @@ # EventSource +> ⚠️ Warning: the EventSource API is experimental. + Undici exposes a WHATWG spec-compliant implementation of [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) for [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). @@ -11,11 +13,33 @@ follows: ```mjs import { EventSource } from 'undici' -const evenSource = new EventSource('http://localhost:3000') -evenSource.onmessage = (event) => { +const eventSource = new EventSource('http://localhost:3000') +eventSource.onmessage = (event) => { console.log(event.data) } ``` +## Using a custom Dispatcher + +undici allows you to set your own Dispatcher in the EventSource constructor. + +An example which allows you to modify the request headers is: + +```mjs +import { EventSource, Agent } from 'undici' + +class CustomHeaderAgent extends Agent { + dispatch (opts) { + opts.headers['x-custom-header'] = 'hello world' + return super.dispatch(...arguments) + } +} + +const eventSource = new EventSource('http://localhost:3000', { + dispatcher: new CustomHeaderAgent() +}) + +``` + More information about the EventSource API can be found on -[MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). \ No newline at end of file +[MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). diff --git a/lib/web/eventsource/eventsource.js b/lib/web/eventsource/eventsource.js index 708caef1258..e6fee88b04b 100644 --- a/lib/web/eventsource/eventsource.js +++ b/lib/web/eventsource/eventsource.js @@ -94,6 +94,8 @@ class EventSource extends EventTarget { #request = null #controller = null + #dispatcher + /** * @type {object} * @property {string} lastEventId @@ -124,6 +126,8 @@ class EventSource extends EventTarget { url = webidl.converters.USVString(url) eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict) + this.#dispatcher = eventSourceInitDict.dispatcher + // 2. Let settings be ev's relevant settings object. // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object this.#settings = { @@ -226,7 +230,8 @@ class EventSource extends EventTarget { this.#readyState = CONNECTING const fetchParam = { - request: this.#request + request: this.#request, + dispatcher: this.#dispatcher } // 14. Let processEventSourceEndOfBody given response res be the following step: if res is not a network error, then reestablish the connection. @@ -471,7 +476,15 @@ Object.defineProperties(EventSource.prototype, { }) webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([ - { key: 'withCredentials', converter: webidl.converters.boolean, defaultValue: false } + { + key: 'withCredentials', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'dispatcher', // undici only + converter: webidl.converters.any + } ]) module.exports = { diff --git a/test/eventsource/eventsource-custom-dispatcher.js b/test/eventsource/eventsource-custom-dispatcher.js new file mode 100644 index 00000000000..e04c2f80673 --- /dev/null +++ b/test/eventsource/eventsource-custom-dispatcher.js @@ -0,0 +1,38 @@ +'use strict' + +const { createServer } = require('node:http') +const { once } = require('node:events') +const { Agent, EventSource } = require('../..') +const { tspl } = require('@matteo.collina/tspl') +const { test } = require('node:test') + +test('EventSource allows setting custom dispatcher.', async (t) => { + const { completed, deepStrictEqual } = tspl(t, { plan: 1 }) + + const server = createServer(async (req, res) => { + res.writeHead(200, 'OK', { 'Content-Type': 'text/event-stream' }) + deepStrictEqual(req.headers['x-customer-header'], 'hello world') + + res.end() + }).listen(0) + + t.after(() => { + server.close() + eventSourceInstance.close() + }) + + await once(server, 'listening') + + class CustomHeaderAgent extends Agent { + dispatch (opts) { + opts.headers['x-customer-header'] = 'hello world' + return super.dispatch(...arguments) + } + } + + const eventSourceInstance = new EventSource(`http://localhost:${server.address().port}`, { + dispatcher: new CustomHeaderAgent() + }) + + await completed +}) diff --git a/test/types/event-source-d.ts b/test/types/event-source-d.ts index 58cfa9364e8..9a3cc8c31af 100644 --- a/test/types/event-source-d.ts +++ b/test/types/event-source-d.ts @@ -1,11 +1,10 @@ import { URL } from 'url' -import { expectType } from 'tsd' +import { expectType, expectAssignable } from 'tsd' -import { - EventSource, -} from '../../' +import { EventSource, EventSourceInit, Dispatcher } from '../../' declare const eventSource: EventSource +declare const agent: Dispatcher expectType<() => void>(eventSource.close) expectType(eventSource.url) @@ -18,3 +17,7 @@ expectType(new EventSource('https://example.com', {})) expectType(new EventSource('https://example.com', { withCredentials: true, })) + +expectAssignable({ dispatcher: agent }) +expectAssignable({ withCredentials: true }) +expectAssignable({}) diff --git a/types/eventsource.d.ts b/types/eventsource.d.ts index af8b92e626c..eecda8c0151 100644 --- a/types/eventsource.d.ts +++ b/types/eventsource.d.ts @@ -1,4 +1,5 @@ import { MessageEvent, ErrorEvent } from './websocket' +import Dispatcher from './dispatcher' import { EventTarget, @@ -57,5 +58,6 @@ export declare const EventSource: { } interface EventSourceInit { - withCredentials?: boolean + withCredentials?: boolean, + dispatcher?: Dispatcher }