Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dispatcher option to EventSource #3119

Merged
merged 3 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 27 additions & 3 deletions docs/docs/api/EventSource.md
Original file line number Diff line number Diff line change
@@ -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).

Expand All @@ -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).
[MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
17 changes: 15 additions & 2 deletions lib/web/eventsource/eventsource.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class EventSource extends EventTarget {
#request = null
#controller = null

#dispatcher

/**
* @type {object}
* @property {string} lastEventId
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 = {
Expand Down
38 changes: 38 additions & 0 deletions test/eventsource/eventsource-custom-dispatcher.js
Original file line number Diff line number Diff line change
@@ -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
})
11 changes: 7 additions & 4 deletions test/types/event-source-d.ts
Original file line number Diff line number Diff line change
@@ -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<string>(eventSource.url)
Expand All @@ -18,3 +17,7 @@ expectType<EventSource>(new EventSource('https://example.com', {}))
expectType<EventSource>(new EventSource('https://example.com', {
withCredentials: true,
}))

expectAssignable<EventSourceInit>({ dispatcher: agent })
expectAssignable<EventSourceInit>({ withCredentials: true })
expectAssignable<EventSourceInit>({})
4 changes: 3 additions & 1 deletion types/eventsource.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MessageEvent, ErrorEvent } from './websocket'
import Dispatcher from './dispatcher'

import {
EventTarget,
Expand Down Expand Up @@ -57,5 +58,6 @@ export declare const EventSource: {
}

interface EventSourceInit {
withCredentials?: boolean
withCredentials?: boolean,
dispatcher?: Dispatcher
}