From 565eae7d130fde852be2d5dce6be20af3b280e74 Mon Sep 17 00:00:00 2001 From: Khafra Date: Sun, 14 Apr 2024 16:49:28 -0400 Subject: [PATCH 1/3] add dispatcher option to EventSource --- lib/web/eventsource/eventsource.js | 18 ++++++++- .../eventsource-custom-dispatcher.js | 38 +++++++++++++++++++ test/types/event-source-d.ts | 11 ++++-- types/eventsource.d.ts | 4 +- 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 test/eventsource/eventsource-custom-dispatcher.js diff --git a/lib/web/eventsource/eventsource.js b/lib/web/eventsource/eventsource.js index 708caef1258..1d10c1018d1 100644 --- a/lib/web/eventsource/eventsource.js +++ b/lib/web/eventsource/eventsource.js @@ -11,6 +11,7 @@ const { MessageEvent } = require('../websocket/events') const { isNetworkError } = require('../fetch/response') const { delay } = require('./util') const { kEnumerableProperty } = require('../../core/util') +const { getGlobalDispatcher } = require('../../global') let experimentalWarned = false @@ -94,6 +95,8 @@ class EventSource extends EventTarget { #request = null #controller = null + #dispatcher + /** * @type {object} * @property {string} lastEventId @@ -124,6 +127,8 @@ class EventSource extends EventTarget { url = webidl.converters.USVString(url) eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict) + this.#dispatcher = eventSourceInitDict.dispatcher ?? getGlobalDispatcher() + // 2. Let settings be ev's relevant settings object. // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object this.#settings = { @@ -226,7 +231,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 +477,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 } From f149dbf223aceadebccf755042d0e15b60b38507 Mon Sep 17 00:00:00 2001 From: Khafra Date: Sun, 14 Apr 2024 16:59:07 -0400 Subject: [PATCH 2/3] update docs --- docs/docs/api/EventSource.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) 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). From 1e336f0a3dc7e64ff4e8271dae31a7daf4be8b3b Mon Sep 17 00:00:00 2001 From: Khafra Date: Sun, 14 Apr 2024 17:00:37 -0400 Subject: [PATCH 3/3] fixup --- lib/web/eventsource/eventsource.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/web/eventsource/eventsource.js b/lib/web/eventsource/eventsource.js index 1d10c1018d1..e6fee88b04b 100644 --- a/lib/web/eventsource/eventsource.js +++ b/lib/web/eventsource/eventsource.js @@ -11,7 +11,6 @@ const { MessageEvent } = require('../websocket/events') const { isNetworkError } = require('../fetch/response') const { delay } = require('./util') const { kEnumerableProperty } = require('../../core/util') -const { getGlobalDispatcher } = require('../../global') let experimentalWarned = false @@ -127,7 +126,7 @@ class EventSource extends EventTarget { url = webidl.converters.USVString(url) eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict) - this.#dispatcher = eventSourceInitDict.dispatcher ?? getGlobalDispatcher() + this.#dispatcher = eventSourceInitDict.dispatcher // 2. Let settings be ev's relevant settings object. // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object