From 90e49b2c1abaacbde44aed2ff65dceb371453695 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Wed, 4 Jun 2025 10:25:15 +0200 Subject: [PATCH 1/9] document the basics of sse --- .../mswjs.io/src/content/buildDocsNavTree.ts | 5 ++ .../mswjs.io/src/content/docs/sse/index.mdx | 82 +++++++++++++++++++ .../docs/sse/intercepting-sources/index.mdx | 23 ++++++ .../content/docs/sse/server-events/index.mdx | 5 ++ .../components/docs/DocsRootSection.astro | 4 +- 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 websites/mswjs.io/src/content/docs/sse/index.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/index.mdx diff --git a/websites/mswjs.io/src/content/buildDocsNavTree.ts b/websites/mswjs.io/src/content/buildDocsNavTree.ts index b97257ca..878b7aac 100644 --- a/websites/mswjs.io/src/content/buildDocsNavTree.ts +++ b/websites/mswjs.io/src/content/buildDocsNavTree.ts @@ -18,6 +18,11 @@ export function buildDocsNavTree( title: 'Mocking HTTP', children: builder.get('http/**/*.mdx'), }, + { + kind: 'group', + title: 'Mocking SSE', + children: builder.get('sse/**/*.mdx'), + }, { kind: 'group', title: 'Mocking GraphQL', diff --git a/websites/mswjs.io/src/content/docs/sse/index.mdx b/websites/mswjs.io/src/content/docs/sse/index.mdx new file mode 100644 index 00000000..26911611 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/index.mdx @@ -0,0 +1,82 @@ +--- +order: -1 +standalone: true +title: Introduction +displayTitle: Mocking Server-Sent Events +description: Intercept and mock Server-Sent Events (SSE). +keywords: + - sse + - server-sent events + - eventsource + - mock + - intercept + - api +--- + +Mock Service Worker ships with a first-class API for intercepting and mocking [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) (SSE) via the `sse` namespace provided by the library: + +```ts +import { sse } from 'msw/browser' +``` + +import { PageCard } from '@mswjs/shared/components/react/pageCard' +import { CubeTransparentIcon } from '@heroicons/react/24/outline' + + + +## Difference from `http` + +The stream of server events is still a regular HTTP connection, which means you can intercept and mock with the `http` namespace as you would any other HTTP request: + +```ts {5-10} /stream/2,3 +import { http } from 'msw' + +export const handlers = [ + http.get('/stream', ({ request }) => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('data: Hello World\n\n')) + controller.close() + }, + }) + + return new Response(stream, { + headers: { + 'content-type': 'text/event-stream', + }, + }) + }), +] +``` + +Handling SSE via the `http` namespace, however, has a number of disadvantages: + +- You have to manually construct the stream; +- You have to encode sent messages and make sure they adhere to the standard; + +In comparison, the `sse` namespace provides you a type-safe, high-level experience of working with SSE: + +```ts {5,8} /sse/ +import { sse } from 'msw/browser' + +export const handlers = [ + sse<{ message: string }>('/stream', ({ client }) => { + client.send({ data: 'hello world' }) + + queueMicrotask(() => { + client.close() + }) + }), +] +``` + +The implementation details of the comnunication, such as managing the stream and queuing messages, are abstracted away, allowing you to focus on describing the network behavior you want. The `sse` namespace also enables additional feature otherwise unavailable when working with `EventSource` directly, such as forwarding of the original server events with ambiguous names. + +## Next steps + +The following sections will guide you through everything you need to know about intercepting and mocking Server-Sent Events: diff --git a/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx new file mode 100644 index 00000000..d68c6d5f --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx @@ -0,0 +1,23 @@ +--- +order: 2 +title: Intercepting sources +description: Intercepting and handling event sources +--- + +You can intercept any `EventSource` in your applicatio by defining a request handler for it using the `sse` namespace. For example, consider this event source: + +```ts +new EventSource('https://api.example.com/events') +``` + +Intercept it by its URL and start handling the connection: + +```ts +import { sse } from 'msw/browser' + +export const handlers = [ + sse('https://api.example.com/events', ({ request, client, server }) => { + console.log(request.url) + }), +] +``` diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx new file mode 100644 index 00000000..e689acd0 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx @@ -0,0 +1,5 @@ +--- +order: 3 +title: Server events +description: Mocking events from the server +--- diff --git a/websites/shared/components/docs/DocsRootSection.astro b/websites/shared/components/docs/DocsRootSection.astro index fa8da19e..4b4b53ed 100644 --- a/websites/shared/components/docs/DocsRootSection.astro +++ b/websites/shared/components/docs/DocsRootSection.astro @@ -8,7 +8,8 @@ import { TrophyIcon, CommandLineIcon, ServerIcon, - ChatBubbleBottomCenterIcon + ChatBubbleBottomCenterIcon, + CloudArrowDownIcon as ServerSentEventsIcon, } from '@heroicons/react/24/solid' import type { DocsItem } from '../../utils/buildNavTree' import DocsNavTree from './DocsNavTree.astro' @@ -26,6 +27,7 @@ const rootSectionIcons: Record< React.FC<{ className?: string }> > = { 'Mocking HTTP': ServerIcon, + 'Mocking SSE': ServerSentEventsIcon, 'Mocking GraphQL': GraphQLIcon, 'Mocking WebSocket': ChatBubbleBottomCenterIcon, From 1ccb82bef62066229ad97f238184dd8433ac51bb Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 14 Jun 2025 16:06:12 +0200 Subject: [PATCH 2/9] retry, messages --- .../mswjs.io/src/content/docs/sse/index.mdx | 4 +-- .../docs/sse/intercepting-sources/index.mdx | 2 +- .../docs/sse/server-events/custom-events.mdx | 28 +++++++++++++++++ .../docs/sse/server-events/message-events.mdx | 26 ++++++++++++++++ .../content/docs/sse/server-events/retry.mdx | 31 +++++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx diff --git a/websites/mswjs.io/src/content/docs/sse/index.mdx b/websites/mswjs.io/src/content/docs/sse/index.mdx index 26911611..6b34ac07 100644 --- a/websites/mswjs.io/src/content/docs/sse/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/index.mdx @@ -16,7 +16,7 @@ keywords: Mock Service Worker ships with a first-class API for intercepting and mocking [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) (SSE) via the `sse` namespace provided by the library: ```ts -import { sse } from 'msw/browser' +import { sse } from 'msw' ``` import { PageCard } from '@mswjs/shared/components/react/pageCard' @@ -62,7 +62,7 @@ Handling SSE via the `http` namespace, however, has a number of disadvantages: In comparison, the `sse` namespace provides you a type-safe, high-level experience of working with SSE: ```ts {5,8} /sse/ -import { sse } from 'msw/browser' +import { sse } from 'msw' export const handlers = [ sse<{ message: string }>('/stream', ({ client }) => { diff --git a/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx index d68c6d5f..d31c3747 100644 --- a/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx @@ -13,7 +13,7 @@ new EventSource('https://api.example.com/events') Intercept it by its URL and start handling the connection: ```ts -import { sse } from 'msw/browser' +import { sse } from 'msw' export const handlers = [ sse('https://api.example.com/events', ({ request, client, server }) => { diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx new file mode 100644 index 00000000..716b6e9c --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx @@ -0,0 +1,28 @@ +--- +order: 2 +title: Custom events +description: Mocking custom server events. +--- + +You can specify the custom event type by providing the `event` key on the client message: + +```ts {6} +import { sse } from 'msw' + +export const handlers = [ + sse('/stream', ({ client }) => { + client.send({ + event: 'greeting', + data: 'hello world', + }) + }), +] +``` + +This will result in the following message received by the client: + +``` +event:greeting +data:hello world + +``` diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx new file mode 100644 index 00000000..f8c5cae6 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx @@ -0,0 +1,26 @@ +--- +order: 1 +title: Message events +description: Mocking server message events. +--- + +You can send a mock message event to the client by calling `client.send()` and providing it with the message data: + +```ts {5} +import { sse } from 'msw' + +export const handlers = [ + sse('/stream', ({ client }) => { + client.send({ data: 'hello world' }) + }), +] +``` + +> Providing an explicit `event: 'message'` has no effect and is equivalent to not providing the `event` property at all. + +This will result in the following message received by the client: + +``` +data:hello world + +``` diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx new file mode 100644 index 00000000..6909d266 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx @@ -0,0 +1,31 @@ +--- +order: 3 +title: Retry +description: Mocking the reconnection time. +--- + +You can mock the reconnection time instruction from the server by sending the [`retry`](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry) message to the client. The value of the `retry` key will control the reconnection time if the underlying connection is lost. + +```ts {5} +import { sse } from 'msw' + +export const handlers = [ + sse('/stream', ({ client }) => { + client.send({ retry: 1000 }) + }), +] +``` + +import { Warning } from '@mswjs/shared/components/react/warning' + + + If present, the `retry` key must be the only key on the object provided to + `client.send()`. All the other keys will be ignored. + + +This will result in the following message received by the client: + +``` +retry:1000 + +``` From 3d2dd9b9f2685b9469c9552c945eb820e91335f1 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 14 Jun 2025 17:40:02 +0200 Subject: [PATCH 3/9] add resolver args, related materials, next steps --- .../docs/http/intercepting-requests/index.mdx | 14 ++--- .../docs/http/mocking-responses/index.mdx | 2 +- .../docs/sse/intercepting-sources/index.mdx | 56 ++++++++++++++++++- .../content/docs/sse/server-events/index.mdx | 2 +- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx b/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx index 74067ed6..774ba92d 100644 --- a/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx +++ b/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx @@ -28,7 +28,7 @@ http.get('/resource', () => {}) A predicate decides which requests to intercept, and a resolver decides what to do with those requests. On this page, we will take a look at the different ways to define a predicate for your request handlers. You can learn more about handling requests in the "Mocked responses" section: -import { ServerIcon } from '@heroicons/react/24/outline' +import { ServerIcon } from '@heroicons/react/24/solid' import { PageCard } from '@mswjs/shared/components/react/pageCard' ` | Request [path parameters](/docs/http/intercepting-requests/path-parameters) (e.g. `:userId`). | -| `cookies` | `Record` | Parsed [request cookies](/docs/http/intercepting-requests/cookies). | +| Property | Type | Description | +| ----------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `request` | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) | Fetch API `Request` representation of the intercepted request. | +| `requestId` | `string` | UUID representing the intercepted request. | +| `params` | `Record` | Request [path parameters](/docs/http/intercepting-requests/path-parameters) (e.g. `:userId`). | +| `cookies` | `Record` | Parsed [request cookies](/docs/http/intercepting-requests/cookies). | ```ts /request/ /params/#g /cookies/#v http.get('/resource', ({ request, params, cookies }) => {}) diff --git a/websites/mswjs.io/src/content/docs/http/mocking-responses/index.mdx b/websites/mswjs.io/src/content/docs/http/mocking-responses/index.mdx index 1da5d882..11315cc0 100644 --- a/websites/mswjs.io/src/content/docs/http/mocking-responses/index.mdx +++ b/websites/mswjs.io/src/content/docs/http/mocking-responses/index.mdx @@ -99,7 +99,7 @@ http.get('/resource', () => { Please explore the "Mocking HTTP" section for more recipes and advanced mocking scenarios. Here are a few noteworthy ones: -import { ServerIcon } from '@heroicons/react/24/outline' +import { ServerIcon } from '@heroicons/react/24/solid'
{ - console.log(request.url) + client.send({ data: 'hello world' }) }), ] ``` + +## Predicate + +You can use the same predicate types for Server-Sent Events as you would use for [HTTP requests](/docs/http/intercepting-requests/#predicate), which includes: + +- Relative URLs; +- Absolute URLs; +- Regular expressions. + +For example, here's how you can intercept an `EventSource` to a remote server with a dynamic path segment `id`: + +```ts /:id/1#g /id/2#g +sse('https://api.example.com/events/:id', ({ params }) => { + console.log(params.id) +}) +``` + +## Response resolver + +The following properties are available on the response resolver object argument for the `sse` handler: + +| Property | Type | Description | +| ----------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `client` | `ServerSentEventClient` | Intercepted `EventSource` client connection object. | +| `server` | `ServerSentEventServer` | Actual `EventSource` server connection object. | +| `request` | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) | Fetch API representation of the intercepted request. | +| `requestId` | `string` | UUID representing the intercepted request. | +| `params` | `Record` | Request [path parameters](/docs/http/intercepting-requests/path-parameters) (e.g. `:userId`). | +| `cookies` | `Record` | Parsed [request cookies](/docs/http/intercepting-requests/cookies). | + +## Related materials + +Much like Server-Sent Events utilize HTTP, working with the `sse` namespace has a significant overlap with the `http` namespace. We highly recommend getting yourself familiar with intercepting HTTP requests with MSW since that will answer a lot of questions you might have around Server-Sent Events. + + + +## Next steps + +Now that you know how to intercept `EventSource` connections, it's time to learn more about handling them (sending mock events, connecting to the real server, etc). + +import { ServerIcon, CloudArrowDownIcon } from '@heroicons/react/24/solid' +import { PageCard } from '@mswjs/shared/components/react/pageCard' + + diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx index e689acd0..f327feff 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx @@ -1,5 +1,5 @@ --- order: 3 title: Server events -description: Mocking events from the server +description: Mocking Server-Sent Events. --- From ce13e982de8f9d0afa7104ee44b65bbeb0f412e7 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 14 Jun 2025 17:43:34 +0200 Subject: [PATCH 4/9] add missing page references --- websites/mswjs.io/src/content/docs/sse/index.mdx | 16 ++++++++++++++++ .../docs/sse/intercepting-sources/index.mdx | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/websites/mswjs.io/src/content/docs/sse/index.mdx b/websites/mswjs.io/src/content/docs/sse/index.mdx index 6b34ac07..7a0d059b 100644 --- a/websites/mswjs.io/src/content/docs/sse/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/index.mdx @@ -80,3 +80,19 @@ The implementation details of the comnunication, such as managing the stream and ## Next steps The following sections will guide you through everything you need to know about intercepting and mocking Server-Sent Events: + +import { CloudArrowDownIcon } from '@heroicons/react/24/solid' + + + + diff --git a/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx index 9582b000..3bcd084d 100644 --- a/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/intercepting-sources/index.mdx @@ -1,7 +1,7 @@ --- order: 2 title: Intercepting sources -description: Intercepting and handling event sources +description: Intercepting and handling event sources. --- You can intercept any `EventSource` in your applicatio by defining a request handler for it using the `sse` namespace. For example, consider this event source: From 33d7b3c9f7dd9c6f5d00bf3d3bc40daf3e014eb3 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 14 Jun 2025 17:44:45 +0200 Subject: [PATCH 5/9] add trailing slash to urls --- .../mswjs.io/src/content/docs/api/http-response.mdx | 2 +- .../docs/graphql/intercepting-operations/index.mdx | 2 +- .../docs/http/intercepting-requests/index.mdx | 2 +- websites/mswjs.io/src/content/docs/runbook.mdx | 2 +- websites/mswjs.io/vercel.json | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/websites/mswjs.io/src/content/docs/api/http-response.mdx b/websites/mswjs.io/src/content/docs/api/http-response.mdx index dcdb3ec0..3eeaea15 100644 --- a/websites/mswjs.io/src/content/docs/api/http-response.mdx +++ b/websites/mswjs.io/src/content/docs/api/http-response.mdx @@ -180,7 +180,7 @@ import { NewspaperIcon } from '@heroicons/react/24/outline' diff --git a/websites/mswjs.io/src/content/docs/graphql/intercepting-operations/index.mdx b/websites/mswjs.io/src/content/docs/graphql/intercepting-operations/index.mdx index 4328b797..4207d9c6 100644 --- a/websites/mswjs.io/src/content/docs/graphql/intercepting-operations/index.mdx +++ b/websites/mswjs.io/src/content/docs/graphql/intercepting-operations/index.mdx @@ -131,7 +131,7 @@ import { GraphQLIcon } from '../../../../components/react/icons/graphql' diff --git a/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx b/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx index 774ba92d..de040612 100644 --- a/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx +++ b/websites/mswjs.io/src/content/docs/http/intercepting-requests/index.mdx @@ -117,7 +117,7 @@ Now that you know how to intercept HTTP requests, proceed by learning how to han diff --git a/websites/mswjs.io/src/content/docs/runbook.mdx b/websites/mswjs.io/src/content/docs/runbook.mdx index 32cba8c8..07fe49a0 100644 --- a/websites/mswjs.io/src/content/docs/runbook.mdx +++ b/websites/mswjs.io/src/content/docs/runbook.mdx @@ -128,7 +128,7 @@ If unsure, please read about mocking responses with MSW: diff --git a/websites/mswjs.io/vercel.json b/websites/mswjs.io/vercel.json index 08539d37..8f689847 100644 --- a/websites/mswjs.io/vercel.json +++ b/websites/mswjs.io/vercel.json @@ -64,7 +64,7 @@ }, { "source": "/docs/basics/response-transformer", - "destination": "/docs/http/mocking-responses", + "destination": "/docs/http/mocking-responses/", "statusCode": 301 }, { @@ -85,7 +85,7 @@ }, { "source": "/docs/api/response", - "destination": "/docs/basics/mocking-responses", + "destination": "/docs/basics/mocking-responses/", "statusCode": 301 }, { @@ -130,17 +130,17 @@ }, { "source": "/docs/api/context/data", - "destination": "/docs/graphql/mocking-responses", + "destination": "/docs/graphql/mocking-responses/", "statusCode": 301 }, { "source": "/docs/api/context/extensions", - "destination": "/docs/graphql/mocking-responses", + "destination": "/docs/graphql/mocking-responses/", "statusCode": 301 }, { "source": "/docs/api/context/errors", - "destination": "/docs/graphql/mocking-responses", + "destination": "/docs/graphql/mocking-responses/", "statusCode": 301 }, { @@ -196,7 +196,7 @@ "statusCode": 301 }, { - "source": "/basics/mocking-responses", + "source": "/basics/mocking-responses/", "destination": "/docs/http/mocking-responses/", "statusCode": 301 }, From 7d090147a4e6a47c895b2100a74e522d38b9c650 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 14 Jun 2025 17:47:10 +0200 Subject: [PATCH 6/9] add missing http headers to sse example --- websites/mswjs.io/src/content/docs/sse/index.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/websites/mswjs.io/src/content/docs/sse/index.mdx b/websites/mswjs.io/src/content/docs/sse/index.mdx index 7a0d059b..f7264e5f 100644 --- a/websites/mswjs.io/src/content/docs/sse/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/index.mdx @@ -47,7 +47,9 @@ export const handlers = [ return new Response(stream, { headers: { + connection: 'keep-alive', 'content-type': 'text/event-stream', + 'cache-control': 'no-cache', }, }) }), From 3a05a09fabe002488aad81e76f74dcac68e01ac5 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Fri, 11 Jul 2025 18:38:57 +0200 Subject: [PATCH 7/9] finish with "mocking sse" --- .../server-events/closing-the-connection.mdx | 20 ++++++ .../docs/sse/server-events/custom-events.mdx | 37 ++++++++++ .../server-events/erroring-the-connection.mdx | 23 +++++++ .../establishing-server-connection.mdx | 63 +++++++++++++++++ .../content/docs/sse/server-events/index.mdx | 67 ++++++++++++++++++- .../docs/sse/server-events/message-events.mdx | 35 ++++++++++ .../content/docs/sse/server-events/retry.mdx | 6 ++ 7 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/closing-the-connection.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/erroring-the-connection.mdx create mode 100644 websites/mswjs.io/src/content/docs/sse/server-events/establishing-server-connection.mdx diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/closing-the-connection.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/closing-the-connection.mdx new file mode 100644 index 00000000..fe5331ee --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/closing-the-connection.mdx @@ -0,0 +1,20 @@ +--- +order: 5 +title: Closing the connection +description: Mocking SSE connection closure. +keywords: + - sse + - close + - connection + - mock +--- + +You can close the intercepted client connection by calling `client.close()` at any time in your request handler. + +```ts {2} +sse('/stream', ({ client }) => { + client.close() +}) +``` + +Unlike [`client.error()`](/docs/sse/server-events/erroring-the-connection), calling `client.close()` produces a _graceful_ connection closure and will not emit the `error` event on the underlying `EventSource` instance. diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx index 716b6e9c..29731c6a 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/custom-events.mdx @@ -2,6 +2,11 @@ order: 2 title: Custom events description: Mocking custom server events. +keywords: + - sse + - custom + - event + - mock --- You can specify the custom event type by providing the `event` key on the client message: @@ -26,3 +31,35 @@ event:greeting data:hello world ``` + +## Custom event ID + +You can attach a [custom ID field](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation) to the sent custom event by providing the `id` property alongside the sent event: + +```ts {2} +client.send({ + id: '1', + event: 'greeting', + data: 'hello world', +}) +``` + +``` +id:1 +event:greeting +data:hello world + +``` + +## TypeScript + +You can annotate the data type of your custom events by providing a type argument object to the `sse` function and including the event names as keys on that object: + +```ts /{ greeting: 'hello world' }/1 +sse<{ greeting: 'hello world' }>('/stream', ({ client }) => { + client.send({ greeting: 'hello world' }) // ✅ + client.send({ greeting: 'goodbye cosmos' }) // ❌ +}) +``` + +> Note that the `message` key is reserved for the default message data type. You can combine both the default `message` data type and custom events within the same type. diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/erroring-the-connection.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/erroring-the-connection.mdx new file mode 100644 index 00000000..161b4bfb --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/erroring-the-connection.mdx @@ -0,0 +1,23 @@ +--- +order: 4 +title: Erroring the connection +description: Mocking SSE connection error. +keywords: + - sse + - error + - abort + - connection + - mock +--- + +You can error the intercepted client connection by calling `client.error()` in your request handler: + +```ts {2} +sse('/stream', ({ client }) => { + client.error() +}) +``` + +> Client connection errors do not accept the closure reason. + +Unlike [`client.close()`](/docs/sse/server-events/closing-the-connection), calling `client.error()` aborts the connection, which will dispatch the `error` event on the underlying `EventSource` instance. diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/establishing-server-connection.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/establishing-server-connection.mdx new file mode 100644 index 00000000..68d78293 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/sse/server-events/establishing-server-connection.mdx @@ -0,0 +1,63 @@ +--- +order: 6 +title: Establishing server connection +keywords: + - sse + - connect + - server + - bypass + - patch +--- + +Similar to the [WebSocket API](/docs/websocket/server-events/establishing-server-connection), you can establish an actual connection to the server by calling `server.connect()`: + +```ts {2} /server/ +sse('/stream', ({ server }) => { + const source = server.connect() +}) +``` + +The result of calling `server.connect()` is an [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) instance that you can use to listen and handle the events received from the actual production server. + +## Event forwarding + +By default, all server events are forwarded to the client. That means that calling `server.connect()` turns your request handler into a _transparent proxy_. You can use that to inspect the incoming server traffic and react to it, e.g. by emitting WebSocket events. + +```ts {11-17} +import { sse, ws } from 'msw' + +const chat = ws.link('https://api.example.com/chat') + +export const handlers = [ + chat.addEventListener('connection', () => {}), + + sse('/stream', ({ server }) => { + const source = server.connect() + + source.addEventListener('message', (event) => { + // If the actual server sends a certain event, + // broadcast mock data on the WebSocket connection. + if (event.data === 'hello') { + chat.broadcast('Hello from the server!') + } + }) + }), +] +``` + +You can opt-out from the default server-to-client event forwarding by calling `event.preventDefault()` on the received server event. For example, here's how you can prevent a server event with a particular payload and send another, mocked payload to the client instead: + +```ts {5-8} /client/#g +sse('/stream', ({ server, client }) => { + const source = server.connect() + + source.addEventListener('message', (event) => { + if (event.data === 'hello') { + event.preventDefault() + client.send({ data: 'Hello from the mock!' }) + } + }) +}) +``` + +> Preventing the default prevents the event forwarding for all events: `open`, `message`, and `error`. diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx index f327feff..a80ae52b 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/index.mdx @@ -1,5 +1,70 @@ --- order: 3 title: Server events -description: Mocking Server-Sent Events. +keywords: + - sse + - mock + - event + - payload --- + +Mocks must resemble actual usage. That is a core philosophy when it comes to designing APIs in MSW. While Server-Sent Events are consumed via the `EventSource` standard API, there is no standard way of defining such events from the server. Because of that, MSW takes the liberty of designing a custom API that remains standard-based while providing a type-safe and ergonomic way of working with Server-Sent Events. + +## Event object + +A server event is an object of the following shape: + +| Property | Type | Description | +| -------- | ------------------- | ------------------ | +| `id` | `string` (Optional) | Custom event ID. | +| `data` | `string` | Event payload. | +| `event` | `string` (Optional) | Custom event name. | +| `retry` | `number` (Optional) | Reconnection time. | + +## Sending events + +To send a mock server-sent event, call `client.send()` and provide it with the [event object](#event-object): + +```ts {2} /client/#g +sse('/stream', ({ client }) => { + client.send({ data: 'hello world' }) +}) +``` + +Creating this request handler will send the "hello world" event to the `EventSource` matching the handler's predicate (`/stream`): + +```ts +// your-app.ts +const source = new EventSource('/stream') +event.addEventListener('message', (event) => { + console.log(event.data) // "hello world" +}) +``` + +## Next steps + +Explore what you can do with the `sse` API on the following pages: + +import { PageCard } from '@mswjs/shared/components/react/pageCard' +import { CloudArrowDownIcon } from '@heroicons/react/24/solid' + + + + + + diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx index f8c5cae6..5fc5fcc1 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx @@ -2,6 +2,11 @@ order: 1 title: Message events description: Mocking server message events. +keywords: + - sse + - message + - default + - payload --- You can send a mock message event to the client by calling `client.send()` and providing it with the message data: @@ -24,3 +29,33 @@ This will result in the following message received by the client: data:hello world ``` + +## Custom event ID + +You can attach a [custom ID field](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation) to the sent message event by providing the `id` property alongside the sent event: + +```ts {2} +client.send({ + id: '1', + data: 'hello world', +}) +``` + +``` +id:1 +data:hello world + +``` + +> Note: event ID must be a string. + +## TypeScript + +You can annotate the data type of the message event by providing a type argument object to the `sse` function and including the `message` key in it: + +```ts /{ message: 'hello world' }/ +sse<{ message: 'hello world' }>('/stream', ({ client }) => { + client.send({ data: 'hello world' }) // ✅ + client.send({ data: 'goodbye cosmos' }) // ❌ +}) +``` diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx index 6909d266..f85d08cc 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/retry.mdx @@ -2,6 +2,12 @@ order: 3 title: Retry description: Mocking the reconnection time. +keywords: + - sse + - retry + - reconnect + - time + - failure --- You can mock the reconnection time instruction from the server by sending the [`retry`](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry) message to the client. The value of the `retry` key will control the reconnection time if the underlying connection is lost. From 1a8973d4b7ec8f23c8fbb265ad786916c33623d2 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 12 Jul 2025 18:12:02 +0200 Subject: [PATCH 8/9] sse: mention payload types --- .../docs/sse/server-events/message-events.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx index 5fc5fcc1..04358a77 100644 --- a/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx +++ b/websites/mswjs.io/src/content/docs/sse/server-events/message-events.mdx @@ -30,6 +30,21 @@ data:hello world ``` +## Payload + +You can send any serializable payload as the message data. For example, you can send a JSON: + +```ts {2-7} +client.send({ + data: { + user: { + id: 'abc-123', + name: 'John', + }, + }, +}) +``` + ## Custom event ID You can attach a [custom ID field](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation) to the sent message event by providing the `id` property alongside the sent event: From 0030acc1018dde4cf9696ccb998d1b06704dd858 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 11 Oct 2025 12:27:48 +0200 Subject: [PATCH 9/9] wip sse --- .../src/content/docs/api/http-response.mdx | 2 +- .../mswjs.io/src/content/docs/api/http.mdx | 11 +- .../mswjs.io/src/content/docs/api/sse.mdx | 111 ++++++++++++++++++ 3 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 websites/mswjs.io/src/content/docs/api/sse.mdx diff --git a/websites/mswjs.io/src/content/docs/api/http-response.mdx b/websites/mswjs.io/src/content/docs/api/http-response.mdx index 3eeaea15..7a10729f 100644 --- a/websites/mswjs.io/src/content/docs/api/http-response.mdx +++ b/websites/mswjs.io/src/content/docs/api/http-response.mdx @@ -1,5 +1,5 @@ --- -order: 5 +order: 6 title: HttpResponse keywords: - response diff --git a/websites/mswjs.io/src/content/docs/api/http.mdx b/websites/mswjs.io/src/content/docs/api/http.mdx index 1e81e48c..35cada09 100644 --- a/websites/mswjs.io/src/content/docs/api/http.mdx +++ b/websites/mswjs.io/src/content/docs/api/http.mdx @@ -147,11 +147,12 @@ export const handlers = [ The response resolver function for every `http.*` method has the following keys in its argument object: -| Name | Type | Description | -| --------- | --------------------------------------------------------------------- | ---------------------------------------------------------------- | -| `request` | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) | Entire request reference. | -| `params` | `object` | Request's [path parameters](/docs/http#reading-path-parameters). | -| `cookies` | `object` | Request's [cookies](/docs/http#reading-request-cookies). | +| Name | Type | Description | +| ----------- | --------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `request` | [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) | Entire request reference. | +| `requestId` | `string` | Unique ID of the intercepted request. | +| `params` | `object` | Request's [path parameters](/docs/http#reading-path-parameters). | +| `cookies` | `object` | Request's [cookies](/docs/http#reading-request-cookies). | You access these arguments on the response resolver argument object. diff --git a/websites/mswjs.io/src/content/docs/api/sse.mdx b/websites/mswjs.io/src/content/docs/api/sse.mdx new file mode 100644 index 00000000..c1a40034 --- /dev/null +++ b/websites/mswjs.io/src/content/docs/api/sse.mdx @@ -0,0 +1,111 @@ +--- +order: 5 +title: sse +description: Intercept Server-Sent Events. +keywords: + - sse + - event + - server + - handler + - api +--- + +The `sse` function is a subset of the [`http`](/docs/api/http) namespace that helps you create request handlers to intercept Server-Sent Events. + +## Call signature + +```ts +sse(predicate: Path, resolver: ServerSentEventResolver) +``` + +import { PageCard } from '../../../components/react/pageCard' +import { CodeBracketSquareIcon } from '@heroicons/react/24/outline' + + + +## Resolver argument + +In addition to all the arguments exposed by the [`http`](/docs/api/http/#resolver-argument) namespace, the `sse` handler has the following response resolver properties: + +| Name | Type | Description | +| -------- | ------------------------------------------------- | --------------------------------------------------------- | +| `client` | [`ServerSentEventClient`](#serversenteventclient) | Representation of the intercepted `EventSource` instance. | +| `server` | [`ServerSentEventServer`](#serversenteventserver) | Actual server connection object. | + +## `ServerSentEventClient` + +The `ServerSentEventClient` object represents the intercepted `EventSource` instance. You use this object to send mock messages to the client, close or error the underlying connection. + +### `.send(payload)` + +- `payload` + - `id`, `string` (Optional), a custom event ID. + - `data`, `string`, the sent data of this event. + - `event`, `string` (Optional), a custom event type. + - `retry`, `number` (Optional), a custom reconnection time. If this option is provided, no other properties must be set on the `payload` argument. + +Sends a mocked event to the client. + +```ts +// Send a default "message" event. +client.send({ data: 'hello world' }) + +// Send a custom "greeting" event. +client.send({ + event: 'greeting' + data: 'Hello, John!', +}) + +// Send an event with a custom ID. +client.send({ + id: 'abc-123', + event: 'greeting' + data: 'Hello, John!', +}) +``` + +### `.error()` + +Errors the underlying HTTP connection. + +```ts +client.error() +``` + +### `.close()` + +Gracefully closes the underlying HTTP connection. + +```ts +client.close() +``` + +## `ServerSentEventServer` + +The `ServerSentEventClient` object represents a connection to the actual server. Use it to establish that connection and listen to the received events from the server. + +### `.connect()` + +...TODO + +```ts +const source = server.connect() +``` + +> ...TODO What is returned as `source` and what you can use it for (listen to messages, close the connection). + +## Related materials + +import { NodejsIcon } from '../../../components/react/icons/nodejs' + +