Skip to content

Commit 8e0977c

Browse files
committed
clients/web: use dedicated library to connect to SSE
The builtin EventSource class doesn't support to pass custom headers to the request, like Authorization. From what I read, this class is somewhat deprecated and browser developers encourage to use `fetch` instead. The thing is, it's a much lower level API which doesn't do the work of reading and parsing the stream. Ref: whatwg/html#2177 I found `event-source-plus`, a library which does all that work on top of `fetch`.
1 parent c27ee1f commit 8e0977c

File tree

3 files changed

+48
-16
lines changed

3 files changed

+48
-16
lines changed

clients/apps/web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"d3": "^7.9.0",
6060
"d3-scale-chromatic": "^3.1.0",
6161
"date-fns": "^3.6.0",
62+
"event-source-plus": "^0.1.8",
6263
"eventemitter3": "^5.0.1",
6364
"framer-motion": "^10.18.0",
6465
"geist": "^1.3.1",
+23-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getServerURL } from '@/utils/api'
2+
import { EventSourcePlus } from 'event-source-plus'
23
import EventEmitter from 'eventemitter3'
34
import { useEffect } from 'react'
45
import { onBenefitGranted, onBenefitRevoked } from './benefits'
@@ -16,29 +17,30 @@ const ACTIONS: {
1617

1718
const emitter = new EventEmitter()
1819

19-
const useSSE = (streamURL: string): EventEmitter => {
20+
const useSSE = (streamURL: string, token?: string): EventEmitter => {
2021
useEffect(() => {
21-
const connection = new EventSource(streamURL, {
22-
withCredentials: true,
22+
const eventSource = new EventSourcePlus(streamURL, {
23+
credentials: 'include',
24+
headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) },
25+
})
26+
27+
const controller = eventSource.listen({
28+
onMessage: async (message) => {
29+
const data = JSON.parse(message.data)
30+
const handler = ACTIONS[data.key]
31+
if (handler) {
32+
await handler(data.payload)
33+
}
34+
emitter.emit(data.key, data.payload)
35+
},
2336
})
2437

2538
const cleanup = () => {
26-
connection.close()
27-
}
28-
// TODO: Add types for event. Just want to get the structure
29-
// up and running first before getting stuck in protocol land.
30-
connection.onmessage = async (event) => {
31-
const data = JSON.parse(event.data)
32-
const handler = ACTIONS[data.key]
33-
if (handler) {
34-
await handler(data.payload)
35-
}
36-
emitter.emit(data.key, data.payload)
39+
controller.abort()
3740
}
3841

39-
connection.onerror = (_event) => cleanup
4042
return cleanup
41-
}, [streamURL])
43+
}, [streamURL, token])
4244

4345
return emitter
4446
}
@@ -48,3 +50,8 @@ export const useOrganizationSSE = (organizationId: string) =>
4850
useSSE(getServerURL(`/v1/stream/organizations/${organizationId}`))
4951
export const useCheckoutClientSSE = (clientSecret: string) =>
5052
useSSE(getServerURL(`/v1/checkouts/custom/client/${clientSecret}/stream`))
53+
export const useCustomerSSE = (customerSessionToken?: string) =>
54+
useSSE(
55+
getServerURL('/v1/customer-portal/customers/stream'),
56+
customerSessionToken,
57+
)

clients/pnpm-lock.yaml

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)