Skip to content

Commit

Permalink
fix: prevent double data serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Sep 30, 2024
1 parent 8c37fe0 commit 7075686
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 20 deletions.
56 changes: 38 additions & 18 deletions src/browser/sse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Constructor } from 'type-fest'
import type { ResponseResolver } from '~/core/handlers/RequestHandler'
import {
HttpHandler,
Expand Down Expand Up @@ -85,6 +84,8 @@ class ServerSentEventHandler extends HttpHandler {
}
}

const kSend = Symbol('kSend')

class ServerSentEventClient {
private encoder: TextEncoder
protected controller: ReadableStreamDefaultController
Expand All @@ -98,13 +99,32 @@ class ServerSentEventClient {
* Dispatches the given event to the underlying `EventSource`.
*/
public send(payload: { id?: string; event?: string; data: unknown }): void {
this[kSend]({
id: payload.id,
event: payload.event,
data: JSON.stringify(payload.data),
})
}

/**
* Errors the underlying `EventSource`, closing the connection.
*/
public error(): void {
this.controller.error()
}

private [kSend](payload: {
id?: string
event?: string
data: string
}): void {
const frames: Array<string> = []

if (payload.event) {
frames.push(`event:${payload.event}`)
}

frames.push(`data:${JSON.stringify(payload.data)}`)
frames.push(`data:${payload.data}`)

if (payload.id) {
frames.push(`id:${payload.id}`)
Expand All @@ -113,13 +133,6 @@ class ServerSentEventClient {
frames.push('', '')
this.controller.enqueue(this.encoder.encode(frames.join('\n')))
}

/**
* Errors the underlying `EventSource`, closing the connection.
*/
public error(): void {
this.controller.error()
}
}

const kListener = Symbol('kListener')
Expand Down Expand Up @@ -181,14 +194,24 @@ class ServerSentEventServer {
const { client } = this

return function (this: EventSource, event: Event) {
const EventConstructor = event.constructor as Constructor<Event>
const cancelableEvent = new EventConstructor(event.type, {
...event,
cancelable: true,
const cancelableEvent =
event instanceof MessageEvent
? new MessageEvent(event.type, {
data: event.data,
cancelable: true,
})
: new Event(event.type, {
cancelable: true,
})
Object.defineProperties(cancelableEvent, {
target: { enumerable: true, value: this, writable: false },
currentTarget: { enumerable: true, value: this, writable: false },
srcElement: { enumerable: true, value: this, writable: false },
})

listener.call(this, cancelableEvent)

// The "open" event cannot be prevented so ignore it.
if (event.type === 'open') {
return
}
Expand All @@ -202,13 +225,10 @@ class ServerSentEventServer {

default: {
if (event instanceof MessageEvent) {
client.send({
// Use the internal send method to avoid data serialization.
client[kSend]({
id: event.lastEventId,
event: event.type === 'message' ? undefined : event.type,
/**
* @fixme Data will already be stringified.
* `client.send()` will stringify it again.
*/
data: event.data,
})
}
Expand Down
4 changes: 2 additions & 2 deletions test/browser/sse-api/sse.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ test('errors the connected source', async ({ loadExample, page, waitFor }) => {
})
})

test.only('forwards original server message events to the client', async ({
test('forwards original server message events to the client', async ({
loadExample,
page,
}) => {
Expand All @@ -186,7 +186,7 @@ test.only('forwards original server message events to the client', async ({
const source = server.connect()

source.addEventListener('message', (event) => {
event.preventDefault()
console.log(event)
})
}),
)
Expand Down

0 comments on commit 7075686

Please sign in to comment.