Skip to content

Commit

Permalink
add dispatcher option to EventSource (#3119)
Browse files Browse the repository at this point in the history
* add dispatcher option to EventSource

* update docs

* fixup
  • Loading branch information
KhafraDev committed Apr 14, 2024
1 parent a2939bc commit f7729b5
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 10 deletions.
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
}

0 comments on commit f7729b5

Please sign in to comment.