处理 WebSocket 事件

了解如何拦截和模拟 WebSocket 事件。

MSW 支持使用其指定的 ws API 拦截和模拟 WebSocket 连接。本页将指导你了解处理 WebSocket 事件的基础知识,解释使用双工连接时 MSW 背后的思维模型,并详细说明库提供的默认设置,以确保良好的开发者体验。

¥MSW supports intercepting and mocking WebSocket connections using its designated ws API. This page will guide you through the basics of handling WebSocket events, explain the mental model behind MSW when working with duplex connections, and elaborate on the defaults the library ships to ensure great developer experience.

遵守标准

¥Respecting standards

Mock Service Worker 致力于尊重、推广和教授你有关 Web 标准的知识。你拦截和模拟 WebSocket 通信的方式将根据 WHATWG WebSocket 标准,这意味着将客户端视为 EventTarget,监听 "message""close" 等事件,并从 MessageEvent 对象读取发送和接收的数据。

¥Mock Service Worker is dedicated to respecting, promoting, and teaching you about the web standards. The way you intercept and mock WebSocket communications will be according to the WHATWG WebSocket Standard, which means treating clients as EventTarget, listening to events like "message" and "close", and reading the sent and received data from the MessageEvent objects.

我们没有计划支持自定义 WebSocket 协议,例如使用 HTTP 轮询或 XMLHttpRequest 的协议。这些是实现它们的第三方工具的专有功能,并且 MSW 没有可靠的方法来拦截此类协议,而无需引入非标准的、特定于库的逻辑。

¥We have no plans of supporting custom WebSocket protocols, such as those using HTTP polling or XMLHttpRequest. Those are proprietary to the third-party tooling that implements them, and there is no reliable way for MSW to intercept such protocols without introducing non-standard, library-specific logic.

话虽如此,我们承认标准 WebSocket 接口很少按原样用于生产系统。通常,它被用作更方便的第三方抽象(如 SocketIO 或 PartyKit)的底层实现细节。我们旨在通过 绑定 解决这一体验差距。

¥That being said, we acknowledge that the standard WebSocket interface is rarely used in production systems as-is. Often, it’s used as the underlying implementation detail for more convenient third-party abstractions, like SocketIO or PartyKit. We aim to address that experience gap via Bindings.

事件类型

¥Event types

WebSocket 通信是双工的,这意味着客户端和服务器都可以独立且同时发送和接收事件。你可以使用 MSW 处理两种类型的事件:

¥A WebSocket communication is duplex, which means that both the client and the server may send and receive events independently and simultaneously. There are two types of events you can handle with MSW:

  • 传出的客户端事件。这些是你的应用发送到 WebSocket 服务器的事件;

    ¥Outgoing client events. These are the events your application sends to the WebSocket server;

  • 传入的服务器事件。这些是原始服务器发送的事件,客户端通过其 "message" 事件监听器接收。

    ¥Incoming server events. These are the events the original server sends and the client receives via its "message" event listener.

拦截连接

¥Intercepting connections

为了支持 WebSocket 通信的双工特性并允许你拦截客户端发送和服务器发送的事件,MSW 有效地充当了位于客户端和 WebSocket 服务器之间的中间件层。

¥To support the duplex nature of the WebSocket communication and allow you to intercept both client-sent and server-sent events, MSW effectively acts as a middleware layer that sits between your client and a WebSocket server.

client ⇄ MSW ⇄ server

你可以控制如何使用 MSW。它可以成为模拟优先开发中 WebSocket 服务器的完全替代品,充当代理来观察和修改来自生产服务器的事件,或模拟客户端发送的事件以测试各种服务器行为。

¥You are in control of how you want to utilize MSW. It can become a full substitute for a WebSocket server in a mock-first development, act as a proxy to observe and modify the events coming from the production server, or emulate client-sent events to test various server behaviors.

处理 WebSocket 事件首先要定义客户端连接到的服务器 URL。这是使用 ws.link() 方法完成的。

¥Handling WebSocket events starts by defining the server URL that the client connects to. This is done using the ws.link() method.

import { ws } from 'msw'
 
const chat = ws.link('wss://chat.example.com')

你可以对 WebSocket 使用与 http 处理程序相同的 URL 谓词:相对和绝对 URL、正则表达式以及带有参数和通配符的路径。

¥You can use the same URL predicate for WebSocket as you use for the http handlers: relative and absolute URLs, regular expressions, and paths with parameters and wildcards.

接下来,将事件处理程序添加到处理程序列表中:

¥Next, add an event handler to the list of your handlers:

export const handlers = [
  chat.addEventListener('connection', () => {
    console.log('outgoing WebSocket connection')
  }),
]

你将在 "connection" 事件监听器中处理客户端发送和服务器发送的事件。

¥You will be handling both client-sent and server-sent events within the "connection" event listener.

重要默认值

¥Important defaults

MSW 实现了一组默认行为,以确保在涉及 WebSockets 的不同测试和开发场景中获得良好的开发者体验。你可以选择退出所有这些,并微调拦截行为以满足你的需求。

¥MSW implements a set of default behaviors to ensure good developer experience in different testing and development scenarios concerning WebSockets. You can opt-out from all of those, and fine-tune the interception behavior to suit your needs.

客户端连接

¥Client connections

默认情况下,不会打开任何被拦截的 WebSocket 连接。这鼓励模拟优先开发,并使管理与不存在的服务器的连接变得更加容易。你可以通过调用 server.connect()建立实际服务器连接

¥By default, no intercepted WebSocket connections are opened. This encourages mock-first development and makes it easier to manage connections to non-existing servers. You can establish the actual server connection by calling server.connect().

客户端到服务器事件转发

¥Client-to-server event forwarding

默认情况下,一旦你 建立实际服务器连接,传出的客户端事件都会转发到原始服务器。如果尚未建立服务器连接,则不会发生转发(无处转发)。你可以通过在客户端消息事件上调用 event.preventDefault() 来选择退出此行为。

¥By default, once you establish the actual server connection, outgoing client events are forwarded to the original server. If the server connection hasn’t been established, no forwarding occurs (nowhere to forward). You can opt-out from this behavior by calling event.preventDefault() on the client message event.

了解有关 客户端到服务器转发 的更多信息。

¥Learn more about client-to-server forwarding.

服务器到客户端事件转发

¥Server-to-client event forwarding

默认情况下,一旦你 建立实际服务器连接,所有传入的服务器事件都会转发到客户端。你可以通过在服务器消息事件上调用 event.preventDefault() 来选择退出此行为。

¥By default, once you establish the actual server connection, all incoming server events are forwarded to the client. You can opt-out from this behavior by calling event.preventDefault() on the server message event.

了解有关 服务器到客户端转发 的更多信息。

¥Learn more about server-to-client forwarding.

客户端事件

¥Client events

拦截客户端事件

¥Intercepting client events

要拦截传出的客户端事件,请从 "connection" 事件监听器参数中获取 client 对象,并在该对象上添加 "message" 监听器。

¥To intercept an outgoing client event, get the client object from the "connection" event listener argument and add a "message" listener on that object.

chat.addEventListener('connection', ({ client }) => {
  client.addEventListener('message', (event) => {
    console.log('from client:', event.data)
  })
})

现在,每当 WebSocket 客户端通过 .send() 方法发送数据时,都会调用此处理程序中的 "message" 监听器。监听器公开一个 event 参数,它是从客户端接收的 MessageEvent,发送的数据可用作 event.data

¥Now, whenever a WebSocket client sends data via the .send() method, the "message" listener in this handler will be called. The listener exposes a single event argument, which is a MessageEvent received from the client, with the sent data available as event.data.

向客户端发送数据

¥Sending data to the client

要将数据发送到连接的客户端,请从 "connection" 事件监听器参数中获取 client 对象,并使用要发送的数据调用其 .send() 方法。

¥To send data to the connected client, get the client object from the "connection" event listener argument and call its .send() method with the data you wish to send.

chat.addEventListener('connection', ({ client }) => {
  client.send('Hello from the server!')
})

MSW 支持发送字符串、BlobArrayBuffer

¥MSW supports sending strings, Blob, and ArrayBuffer.

client.send(data)

The `client.send()` API.

向客户端广播数据

¥Broadcasting data to clients

要将数据广播到所有连接的客户端,请在事件处理程序对象(从 ws.link() 调用返回的对象)上使用 .broadcast() 方法,并为其提供你希望广播的数据。

¥To broadcast data to all connected clients, use the .broadcast() method on the event handler object (the one returned from the ws.link() call) and provide it with the data you wish to broadcast.

chat.addEventListener('connection', () => {
  chat.broadcast('Hello everyone!')
})

你还可以通过在事件处理程序对象上使用 .boardcastExcept() 方法将数据广播到除部分客户端之外的所有客户端。

¥You can also broadcast data to all clients except a subset of clients by using the .boardcastExcept() method on the event handler object.

chat.addEventListener('connection', ({ client }) => {
  // Broadcast data to all clients except the current one.
  chat.broadcastExcept(client, 'Hello everyone except you!')
 
  // Broadcast data to all the clients matching a predicate.
  chat.boardcastExcept(chat.clients.filter((client) => {
    return client
  }, "Hello to some of you!")
})

.broadcast(data)

The `.broadcast()` API.

.broadcastExcept(clients, data)

The `.broadcastExcept()` API.

关闭客户端连接

¥Closing client connections

你可以通过调用 client.close() 随时关闭现有客户端连接。

¥You can close an existing client connection at any time by calling client.close().

chat.addEventListener('connection', ({ client }) => {
  client.close()
})

默认情况下,.close() 方法将导致连接正常关闭(1000 代码)。你可以通过向 .close() 方法提供自定义 codereason 参数来控制连接关闭的性质。

¥By default, the .close() method will result in a graceful closure of the connection (1000 code). You can control the nature of the connection closure by providing the custom code and reason arguments to the .close() method.

chat.addEventListener('connection', ({ client }) => {
  client.addEventListener('message', (event) => {
    if (event.data === 'hello') {
      client.close(1003)
    }
  })
})

例如,在此处理程序中,一旦客户端发送 "hello" 消息,其连接将以 1003 代码(服务器无法接受的接收数据)终止。

¥For example, in this handler, once the client sends a "hello" message, its connection will be terminated with the 1003 code (received data the server cannot accept).

WebSocket.prototype.close() 方法不同,client 连接上的 .close() 方法甚至可以接受非用户可配置的关闭代码,如 1001、1002、1003 等,这为你描述 WebSocket 通信提供了更大的灵活性。

¥Unlike the WebSocket.prototype.close() method, the .close() method on the client connection can accept even non user-configurable closure codes like 1001, 1002, 1003, etc, which gives you more flexibility in describing the WebSocket communication.

client.close(code, reason)

The `client.close()` API.

服务器事件

¥Server events

建立服务器连接

¥Establishing server connection

要建立与实际 WebSocket 服务器的连接,请从 "connection" 事件监听器参数中获取 server 对象并调用其 .connect() 方法。

¥To establish the connection to the actual WebSocket server, get the server object from the "connection" event listener argument and call its .connect() method.

chat.addEventListener('connection', ({ server }) => {
  server.connect()
})

server.connect()

The `server.connect()` API.

客户端到服务器转发

¥Client-to-server forwarding

一旦建立服务器连接,所有传出的客户端消息事件都会转发到服务器。要防止此行为,请在客户端消息事件上调用 event.preventDefault()。你可以使用它在客户端发送的数据到达服务器之前对其进行修改,或者完全忽略它。

¥Once the server connection has been established, all outgoing client message events are forwarded to the server. To prevent this behavior, call event.preventDefault() on the client message event. You can use this to modify the client-sent data before it reaches the server or ignore it completely.

chat.addEventListener('connection', ({ client }) => {
  client.addEventListener('message', (event) => {
    // Prevent the default client-to-server forwarding.
    event.preventDefault()
 
    // Modify the original client-sent data and send
    // it to the server instead.
    server.send(event.data + 'mocked')
  })
})

拦截服务器事件

¥Intercepting server events

要拦截来自实际服务器的传入事件,请从 "connection" 事件监听器参数中获取 server 对象,并在该对象上添加 "message" 事件监听器。

¥To intercept an incoming event from the actual sever, get the server object from the "connection" event listener argument and add a "message" event listener on that object.

chat.addEventListener('connection', ({ server }) => {
  server.addEventListener('message', (event) => {
    console.log('from server:', event.data)
  })
})

现在,每当实际服务器发送数据时,都会调用此处理程序中的 "message" 监听器。监听器公开一个 event 参数,它是从客户端接收的 MessageEvent,发送的数据可用作 event.data

¥Now, whenever the actual server sends data, the "message" listener in this handler will be called. The listener exposes a single event argument, which is a MessageEvent received from the client, with the sent data available as event.data.

服务器到客户端转发

¥Server-to-client forwarding

默认情况下,所有服务器事件都转发到连接的客户端。你可以通过在服务器消息事件上调用 event.preventDefault() 来选择退出此行为。如果你希望在服务器发送的数据到达客户端之前对其进行修改,或者阻止某些服务器事件完全到达客户端,这将非常方便。

¥By default, all server events are forwarded to the connected client. You can opt-out from this behavior by calling event.preventDefault() on the server message event. This is handy if you wish to modify the server-sent data before it reaches the client or prevent some server events from arriving at the client completely.

chat.addEventListener('connection', ({ client, server }) => {
  server.addEventListener('message', (event) => {
    // Prevent the default server-to-client forwarding.
    event.preventDefault()
 
    // Modify the original server-sent data and send
    // it to the client instead.
    client.send(event.data + 'mocked')
  })
})

向服务器发送数据

¥Sending data to the server

要将数据发送到实际服务器,请从 "connection" 事件监听器参数中获取 server 对象,并使用你希望发送到服务器的数据调用其 .send() 方法。

¥To send data to the actual server, get the server object from the "connection" event listener argument and call its .send() method with the data you wish to send to the server.

chat.addEventListener('connection', ({ server }) => {
  server.send('hello from client!')
}

这相当于客户端将该数据发送到服务器。

¥This is equivalent to a client sending that data to the server.

server.send(data)

The `server.send()` API.

关闭服务器连接

¥Closing server connections

你可以通过调用 server.close() 关闭先前建立的原始 WebSocket 服务器连接。

¥You can close a previously established original WebSocket server connection by calling server.close().

chat.addEventListener('connection', ({ server }) => {
  server.connect()
  server.close()
})

调用 server.close() 将关闭服务器连接并在 server 对象上分派 close 事件。

¥Calling server.close() will close the server connection and dispatch the close event on the server object.

日志记录

¥Logging

由于 MSW 实现了 WebSocket 拦截模拟优先,因此除非你明确说明,否则不会建立任何实际连接。这意味着模拟的场景不会作为网络条目出现在浏览器的 DevTools 中,你将无法在那里观察到它们。

¥Since MSW implements the WebSocket interception mock-first, no actual connections will be established until you explicitly say so. This means that the mocked scenarios won’t appear as network entries in your browser’s DevTools and you won’t be able to observe them there.

MSW 为浏览器中的模拟和原始 WebSocket 连接提供自定义日志记录。

¥MSW provides custom logging for both mocked and original WebSocket connections in the browser.

读取日志输出

¥Reading the log output

记录器将打印出 WebSocket 通信期间发生的各种事件,作为浏览器控制台中的折叠控制台组。

¥The logger will print out various events occurring during the WebSocket communication as collapsed console groups in your browser’s console.

你可以观察到四种类型的日志:

¥There are four types of logs you can observe:

  1. ▶■×System events;

  2. ⬆⇡Client events;

  3. ⬇⇣Server events.

  4. ⬆⬇Mocked events.

系统事件

¥System events

连接已打开

¥ Connection opened

[MSW] 12:34:56 ▶ wss://example.com

当连接打开时分派(即 WebSocket 客户端发出 open 事件)。

¥Dispatched when the connection is open (i.e. the WebSocket client emits the open event).

× 连接错误

¥× Connection errored

[MSW] 12:34:56 × wss://example.com

当客户端收到错误时分派(即 WebSocket 客户端发出 error 事件)。

¥Dispatched when the client receives an error (i.e. the WebSocket client emits the error event).

连接已关闭

¥ Connection closed

[MSW] 12:34:56 ■ wss://example.com

当连接关闭时分派(即 WebSocket 客户端发出 close 事件)。

¥Dispatched when the connection is closed (i.e. the WebSocket client emits the close event).

消息事件

¥Message events

任何消息,无论是传出消息还是传入消息,都遵循相同的结构:

¥Any message, be it outgoing or incoming message, follows the same structure:

        timestamp         sent data
[MSW] 00:00:00.000  hello from client 17
                  icon              byte length

二进制消息打印发送的二进制文件的文本预览及其完整字节长度:

¥Binary messages print a text preview of the sent binary alongside its full byte length:

[MSW] 12:34:56.789 ⬆ Blob(hello world) 11
[MSW] 12:34:56.789 ⬆ ArrayBuffer(preview) 7

长文本消息和文本预览被截断:

¥Long text messages and text previews are truncated:

[MSW] 12:34:56.789 ⬆ this is a very long stri… 17

你可以通过单击其控制台组并检查原始 MessageEvent 实例来访问完整消息。

¥You can access the full message by clicking on its console group and inspecting the original MessageEvent instance.

⬆⇡ 传出客户端消息

¥⬆⇡ Outgoing client message

[MSW] 12:34:56.789 ⬆ hello from client 17

应用中的 WebSocket 客户端发送的原始消息。如果箭头为虚线,则表示事件处理程序已阻止转发此消息事件。

¥A raw message sent by the WebSocket client in your application. If the arrow is dashed, the forwarding of this message event has been prevented in the event handler.

传出模拟客户端消息

¥ Outgoing mocked client message

[MSW] 12:34:56.789 ⬆ hello from mock 15

事件处理程序通过 server.send() 从客户端发送的消息。需要 打开服务器连接

¥A message sent from the client by the event handler via server.send(). Requires an open server connection.

⬇⇣ 传入服务器消息

¥⬇⇣ Incoming server message

[MSW] 12:34:56.789 ⬇ hello from server 17

从原始服务器发送的传入消息。需要 打开服务器连接。如果箭头为虚线,则表示事件处理程序已阻止转发此消息事件。

¥An incoming message sent from the original server. Requires an open server connection. If the arrow is dashed, the forwarding of this message event has been prevented in the event handler.

传入模拟服务器消息

¥ Incoming mocked server message

[MSW] 12:34:56.789 ⬇ hello from mock 15

通过 client.send() 从事件处理程序发送到客户端的模拟消息。

¥A mocked message sent to the client from the event handler via client.send().

事件流

¥Event flow

与 WebSocket 通信非常相似,使用 MSW 处理它是基于事件的。 ​​你模拟 WebSocket 的经验将涉及对 EventTarget 的理解以及事件在 JavaScript 中的工作方式。让我们快速回顾一下。

¥Much like the WebSocket communication, handling it with MSW is event-based. Your experience mocking WebSockets will involve the understanding of EventTarget and how events work in JavaScript. Let’s have a quick reminder.

当你的应用建立 WebSocket 连接时,connection 事件将在所有匹配的 WebSocket 链接上发出。

¥When your application establishes a WebSocket connection, the connection event will be emitted on all matching WebSocket links.

const chat = ws.link('wss://example.com')
 
export const handlers = [
  chat.addEventListener('connection', () => console.log('This is called')),
  chat.addEventListener('connection', () => console.log('This is also called')),
]

这样,快乐路径和运行时处理程序都可以对同一连接做出反应。

¥This way, both the happy path and the runtime handlers can react to the same connection.

客户端/服务器事件也会在同一连接的所有 clientserver 对象上分派。同时,你可以将多个监听器附加到同一个对象,或跨不同处理程序附加到不同的对象。

¥The client/server events are dispatched on all client and server objects of the same connection as well. Meanwhile, you can attach multiple listeners to the same object, or to different objects across different handlers.

export const handlers = [
  chat.addEventListener('connection', ({ client }) => {
    // Attaching multiple message listeners to the same `client` object.
    client.addEventListener('message', () => console.log('This is called'))
    client.addEventListener('message', () => console.log('This is also called'))
  }),
  chat.addEventListener('connection', ({ client }) => {
    // Attaching another message listener to a different `client` object.
    client.addEventListener('message', () =>
      console.log('Hey, this gets called too!'),
    )
  }),
]

由于这些事件是针对同一事件目标分派的,因此你可以利用它来阻止它们。这在创建运行时处理程序(即网络行为覆盖)时非常有用,因为你可以控制覆盖是增强还是完全覆盖特定事件处理。

¥Since these events are dispatched against the same event target, you can utilize that to prevent them. That comes in handy when creating runtime handlers (i.e. network behavior overrides), as you can control whether your override augments or completely overrides particular event handling.

const server = setupServer(
  chat.addEventListener('connection', ({ client }) => {
    client.addEventListener('message', (event) => {
      // In a happy path handler, send back the event data
      // received from the WebSocket client.
      client.send(event.data)
    })
  }),
)
 
it('handles error payload', async () => {
  server.use(
    chat.addEventListener('connection', ({ client }) => {
      client.addEventListener('message', (event) => {
        // In this runtime handler, prevent the "message" client from
        // propagating to another event target (the happy path handler).
        // Then, send a completely different message to the client.
        event.stopPropagation()
        client.send('error-payload')
      })
    }),
  )
})

省略 event.stopPropagation() 将导致在收到相同事件时向客户端发送两条消息 - 首先是 'error-payload',然后是原始的 event.data

¥Omitting event.stopPropagation() will result in two messages being sent to the client upon receiving the same event—the 'error-payload' first, then the original event.data second.

就像使用常规 EventTarget 一样,你可以利用 event.preventImmediatePropagation() 阻止事件在同级监听器之间传播。例如,在处理特定的 WebSocket 事件时,可以使用它来短路任何其他原本会被调用的事件监听器。

¥Just like with a regular EventTarget, you can utilize event.preventImmediatePropagation() to stop an event from propagating across sibling listeners. For example, when handling a particular WebSocket event, you can use that to short-circuit any other event listeners that would otherwise be called.

chat.addEventListener('connection', ({ client }) => {
  client.addEventListener('message', (event) => {
    if (event.data === 'special-scenario') {
      // This prevents this "message" event from propagating
      // to the "message" event listener below.
      event.stopImmediatePropagation()
      client.close()
    }
  })
 
  client.addEventListener('message', (event) => {
    client.send(event.data)
  })
})

如果客户端在消息中发送 'special-scenario' 有效负载,它将被关闭,并且永远不会调用第二个事件监听器的 client.send(event.data) 逻辑。

¥If the client sends a 'special-scenario' payload in the message, it will be closed, and the client.send(event.data) logic from the second event listener will never be called.

类型安全

¥Type safety

使用 TypeScript 时,为 WebSocket 通信带来类型安全至关重要,这也包括你的处理程序!话虽如此,MSW 有意不支持任何类型参数来注释传出/传入事件:

¥Bringing type safety to the WebSocket communication is essential when using TypeScript, and that includes your handlers too! That being said, MSW intentionally doesn’t support any type arguments to annotate the outgoing/incoming events:

import { ws } from 'msw'
 
ws.link<Arg1, Arg2>(url)
//     ^^^^^^^^^^^^ Type error!

此决定背后的原因有两个:

¥The reasoning behing this decision is twofold:

  1. 缩小数据类型并不能保证不会通过网络发送不同的数据类型(经典类型与运行时争论);

    ¥Narrowing down the data type doesn’t guarantee that a different data type wouldn’t be sent over the network (the classic types vs runtime debate);

  2. 你在消息事件监听器中收到的 event.data 值将始终为 string | Blob | ArrayBuffer 类型,因为 MSW 不提供消息解析。

    ¥The event.data value you receive in the message event listener will always be of type string | Blob | ArrayBuffer because MSW provides no message parsing.

如果你使用对象与 WebSocket 服务器通信,则在发送和接收这些对象时必须分别对这些对象进行字符串化和解析,这已经意味着你的应用中存在解析层。

¥If you are using objects to communicate with a WebSocket server, those objects have to be stringified and parsed when sending and receiving them, respectively, which already implies a parsing layer being present in your application.

你可以通过引入解析实用程序在 WebSockets 中实现适当的类型和运行时安全性。像 Zod 这样的库可以极大地帮助你实现类型和运行时安全。

¥You can achieve a proper type and runtime safety in WebSockets by introducing parsing utilities. Libraries like Zod can help you greatly in achieving type and runtime safety.

import { z } from 'zod'
 
// Define a Zod schema for the incoming events.
// Here, our WebSocket communication supports two
// events: "chat/join" and "chat/message".
const incomingSchema = z.union([
  z.object({
    type: z.literal('chat/join'),
    user: userSchema,
  }),
  z.object({
    type: z.literal('chat/message'),
    message: z.object({
      text: z.string(),
      sentAt: z.string().datetime(),
    }),
  }),
])
 
chat.addEventListener('connection', ({ client, server }) => {
  client.addEventListener('message', (event) => {
    const result = incomingSchema.safeParse(event.data)
 
    // Ignore non-matching events.
    if (!result.success) {
      return
    }
 
    const message = result.data
 
    // Handle incoming events in type-safe way.
    switch (message.type) {
      case 'chat/join': {
        // ...
        break
      }
 
      case 'chat/message': {
        // ...
        break
      }
    }
  })
})

请随意为消息事件引入一个高阶监听器,以抽象该解析,帮助你在处理程序中重用它。

¥Feel free to introduce a higher-order listener for the message event that abstracts that parsing, helping you reuse it across your handlers.

绑定

¥Bindings

为了在模拟第三方 WebSocket 客户端时提供更熟悉的体验,MSW 使用了绑定。绑定是标准 WebSocket 类的封装器,它封装了第三方特定的行为(例如消息解析),并为你提供了类似于绑定的第三方库的公共 API。

¥To provide a more familiar experience when mocking third-party WebSocket clients, MSW uses bindings. A binding is a wrapper over the standard WebSocket class that encapsulates the third-party-specific behaviors, such as message parsing, and gives you a public API similar to that of the bound third-party library.

例如,以下是使用 MSW 和指定的 SocketIO 绑定处理 SocketIO 通信的方法:

¥For example, here’s how to handle SocketIO communication using MSW and a designated SocketIO binding:

import { ws } from 'msw'
import { bind } from '@mswjs/socket.io-binding'
 
const chat = ws.link('wss://chat.example.com')
 
export const handlers = [
  chat.addEventListener('connection', (connection) => {
    const io = bind(connection)
 
    io.client.on('hello', (username) => {
      io.client.emit('message', `hello, ${username}!`)
    })
  }),
]

@mswjs/socket.io-binding

Connection wrapper for mocking Socket.IO with MSW.

请注意,绑定并不意味着涵盖相应第三方库的所有公共 API。除非该库提供绑定,否则无法保持完全兼容性。

¥Note that binding is not meant to cover all the public APIs of the respective third-party library. Unless the binding is shipped by that library, maintaining full compatibility is not feasible.