Skip to content

Commit

Permalink
comply with SSE spec regarding nulls, colons, and partial messages (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Jan 10, 2022
1 parent c62e49c commit 9715f5d
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 9 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ start-contract-test-service-bg:
@make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 &

run-contract-tests:
@curl -s https://raw.githubusercontent.com/launchdarkly/sse-contract-tests/v0.0.3/downloader/run.sh \
| VERSION=v0 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end" sh
@curl -s https://raw.githubusercontent.com/launchdarkly/sse-contract-tests/v2.0.0/downloader/run.sh \
| VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end" sh

contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests

Expand Down
7 changes: 4 additions & 3 deletions contract-tests/streamEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ function StreamEntity(options) {
s.doCommand = params => {
switch (params.command) {
case 'listen':
if (!listeningForType[params.type]) {
listeningForType[params.type] = true;
s.sse.addEventListener(params.type, s.onMessage);
const eventType = params.listen.type;
if (!listeningForType[eventType]) {
listeningForType[eventType] = true;
s.sse.addEventListener(eventType, s.onMessage);
}
return true;

Expand Down
17 changes: 13 additions & 4 deletions lib/eventsource.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ function EventSource (url, eventSourceInitDict) {
}

var discardTrailingNewline = false
var data = ''
var eventName = ''
var data, eventName, eventId

var reconnectUrl = null
var retryDelayStrategy = new retryDelay.RetryDelayStrategy(
Expand Down Expand Up @@ -207,6 +206,10 @@ function EventSource (url, eventSourceInitDict) {
return
}

data = ''
eventName = ''
eventId = undefined

readyState = EventSource.OPEN
res.on('close', function () {
res.removeAllListeners('close')
Expand Down Expand Up @@ -336,16 +339,20 @@ function EventSource (url, eventSourceInitDict) {
if (lineLength === 0) {
if (data.length > 0) {
var type = eventName || 'message'
if (eventId !== undefined) {
lastEventId = eventId
}
var event = new MessageEvent(type, {
data: data.slice(0, -1), // remove trailing newline
lastEventId: lastEventId,
origin: original(url)
})
data = ''
eventId = undefined
receivedEvent(event)
}
eventName = void 0
} else if (fieldLength > 0) {
} else {
var noValue = fieldLength < 0
var step = 0
var field = buf.slice(pos, pos + (noValue ? lineLength : fieldLength)).toString()
Expand All @@ -367,7 +374,9 @@ function EventSource (url, eventSourceInitDict) {
} else if (field === 'event') {
eventName = value
} else if (field === 'id') {
lastEventId = value
if (!value.includes("\u0000")) {
eventId = value
}
} else if (field === 'retry') {
var retry = parseInt(value, 10)
if (!Number.isNaN(retry)) {
Expand Down
25 changes: 25 additions & 0 deletions test/eventsource_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,19 @@ describe('Parser', () => {
})
})

it('treats field name without colon as a field with an empty value', async () => {
await withServer(async server => {
server.byDefault(writeEvents(['data\n\ndata\ndata\n\n']))

await withEventSource(server, async es => {
await shouldReceiveMessages(es, [
{ data: '' },
{ data: '\n' }
])
})
})
})

it('causes entire event to be ignored for empty event field', async () => {
await withServer(async server => {
server.byDefault(writeEvents(['event:\n\ndata: Hello\n\n']))
Expand Down Expand Up @@ -954,6 +967,18 @@ describe('Events', function () {
})
})

it('ignores event ID that contains a null', async () => {
await withServer(async server => {
server.byDefault(writeEvents(['id: 12\u00003\ndata: hello\n\n']))

await withEventSource(server, async es => {
const messages = startMessageQueue(es)
const m = await messages.take()
assert.equal(m.lastEventId, '')
})
})
})

it('populates messages with enumerable properties so they can be inspected via console.log().', async () => {
await withServer(async server => {
server.byDefault(writeEvents(['data: World\n\n']))
Expand Down

0 comments on commit 9715f5d

Please sign in to comment.