Skip to content

Commit

Permalink
fix(rest): support ReadableStream as response body
Browse files Browse the repository at this point in the history
  • Loading branch information
Aprillion committed Nov 7, 2021
1 parent f6fbd6c commit 44834f2
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 0 deletions.
37 changes: 37 additions & 0 deletions src/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ async function getResponse(event, client, requestId) {
)
}

case 'MOCK_STREAM_START': {
return respondWithStream(clientMessage)
}

case 'MOCK_NOT_FOUND': {
return getOriginalResponse()
}
Expand Down Expand Up @@ -309,6 +313,22 @@ function sendToClient(client, message) {
return reject(event.data.error)
}

switch (event.data?.type) {
// message 'MOCK_STREAM_START' resolves the Promise, but then the client sends
// more messages from a ReadableStream (see issue #581)
case 'MOCK_STREAM_CHUNK': {
console.log('chunk')
globalThis.currentStreamController.enqueue(event.data?.payload?.chunk)
return
}

case 'MOCK_STREAM_END': {
console.log('end')
globalThis.currentStreamController.close()
return
}
}

resolve(event.data)
}

Expand All @@ -329,6 +349,23 @@ function respondWithMock(clientMessage) {
})
}

function respondWithStream(clientMessage) {
const stream = new ReadableStream({
start(controller) {
globalThis.currentStreamController = controller
},
}).pipeThrough(
// from https://web.dev/fetch-upload-streaming/#streaming-request-bodies
// > Each chunk of a [response] body needs to be a Uint8Array
new TextEncoderStream(),
)

return new Response(stream, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
})
}

function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0
Expand Down
3 changes: 3 additions & 0 deletions src/setupWorker/glossary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export type ServiceWorkerOutgoingEventTypes =
*/
export type ServiceWorkerFetchEventTypes =
| 'MOCK_SUCCESS'
| 'MOCK_STREAM_START'
| 'MOCK_STREAM_CHUNK'
| 'MOCK_STREAM_END'
| 'MOCK_NOT_FOUND'
| 'NETWORK_ERROR'
| 'INTERNAL_ERROR'
Expand Down
29 changes: 29 additions & 0 deletions src/utils/worker/createRequestListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ export const createRequestListener = (
})
},
onMockedResponse(response) {
if (response.body instanceof ReadableStream) {
channel.send({
type: 'MOCK_STREAM_START',
payload: { ...response, body: '__mocked_stream__' },
})

const reader = response.body.getReader()
const sendChunk = () => {
reader.read().then(({ done, value }) => {
if (done) {
channel.send({
type: 'MOCK_STREAM_END',
})
} else {
channel.send({
type: 'MOCK_STREAM_CHUNK',
payload: {
chunk: value,
},
})
sendChunk()
}
})
}
sendChunk()

return
}

channel.send({
type: 'MOCK_SUCCESS',
payload: response,
Expand Down
24 changes: 24 additions & 0 deletions test/rest-api/response/body/body-stream.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { setupWorker, rest } from 'msw'

const stream = new ReadableStream({
start(controller) {
controller.enqueue('line1\n')

setTimeout(() => {
controller.enqueue('line2\n')
controller.close()
}, 0)
},
})

const worker = setupWorker(
rest.get('/sse', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.set('Content-Type', 'text/plain'),
ctx.body(stream),
)
}),
)

worker.start()
7 changes: 7 additions & 0 deletions test/rest-api/response/body/body-stream.node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @jest-environment node
*/

test('Node does not support ReadableStream yet', () => {
expect(typeof ReadableStream).toBe('undefined')
})
17 changes: 17 additions & 0 deletions test/rest-api/response/body/body-stream.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as path from 'path'
import { pageWith } from 'page-with'

test('responds with a ReadableStream response body', async () => {
const { request } = await pageWith({
example: path.resolve(__dirname, 'body-stream.mocks.ts'),
})

const res = await request('/sse')
const status = res.status()
const headers = await res.allHeaders()
const text = await res.text()

expect(status).toBe(200)
expect(headers['content-type']).toBe('text/plain')
expect(text).toBe('line1\nline2\n')
})

0 comments on commit 44834f2

Please sign in to comment.