UNPKG

msw

Version:

Seamless REST/GraphQL API mocking library for browser and Node.js.

260 lines (232 loc) 7.35 kB
import type { WebSocketClientConnection, WebSocketConnectionData, WebSocketData, } from '@mswjs/interceptors/WebSocket' import { devUtils } from '../../utils/internal/devUtils' import { getTimestamp } from '../../utils/logging/getTimestamp' import { toPublicUrl } from '../../utils/request/toPublicUrl' import { getMessageLength } from './getMessageLength' import { getPublicData } from './getPublicData' const colors = { system: '#3b82f6', outgoing: '#22c55e', incoming: '#ef4444', mocked: '#ff6a33', } export function attachWebSocketLogger( connection: WebSocketConnectionData, ): void { const { client, server } = connection logConnectionOpen(client) // Log the events sent from the WebSocket client. // WebSocket client connection object is written from the // server's perspective so these message events are outgoing. /** * @todo Provide the reference to the exact event handler * that called this `client.send()`. */ client.addEventListener('message', (event) => { logOutgoingClientMessage(event) }) client.addEventListener('close', (event) => { logConnectionClose(event) }) // Log client errors (connection closures due to errors). client.socket.addEventListener('error', (event) => { logClientError(event) }) client.send = new Proxy(client.send, { apply(target, thisArg, args) { const [data] = args const messageEvent = new MessageEvent('message', { data }) Object.defineProperties(messageEvent, { currentTarget: { enumerable: true, writable: false, value: client.socket, }, target: { enumerable: true, writable: false, value: client.socket, }, }) queueMicrotask(() => { logIncomingMockedClientMessage(messageEvent) }) return Reflect.apply(target, thisArg, args) }, }) server.addEventListener( 'open', () => { server.addEventListener('message', (event) => { logIncomingServerMessage(event) }) }, { once: true }, ) // Log outgoing client events initiated by the event handler. // The actual client never sent these but the handler did. server.send = new Proxy(server.send, { apply(target, thisArg, args) { const [data] = args const messageEvent = new MessageEvent('message', { data }) Object.defineProperties(messageEvent, { currentTarget: { enumerable: true, writable: false, value: server.socket, }, target: { enumerable: true, writable: false, value: server.socket, }, }) logOutgoingMockedClientMessage(messageEvent) return Reflect.apply(target, thisArg, args) }, }) } /** * Prints the WebSocket connection. * This is meant to be logged by every WebSocket handler * that intercepted this connection. This helps you see * what handlers observe this connection. */ export function logConnectionOpen(client: WebSocketClientConnection) { const publicUrl = toPublicUrl(client.url) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage(`${getTimestamp()} %c▶%c ${publicUrl}`), `color:${colors.system}`, 'color:inherit', ) // eslint-disable-next-line no-console console.log('Client:', client.socket) // eslint-disable-next-line no-console console.groupEnd() } function logConnectionClose(event: CloseEvent) { const target = event.target as WebSocket const publicUrl = toPublicUrl(target.url) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c■%c ${publicUrl}`, ), `color:${colors.system}`, 'color:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() } function logClientError(event: Event) { const socket = event.target as WebSocket const publicUrl = toPublicUrl(socket.url) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c\u00D7%c ${publicUrl}`, ), `color:${colors.system}`, 'color:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() } /** * Prints the outgoing client message. */ async function logOutgoingClientMessage(event: MessageEvent<WebSocketData>) { const byteLength = getMessageLength(event.data) const publicData = await getPublicData(event.data) const arrow = event.defaultPrevented ? '⇡' : '⬆' // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c${arrow}%c ${publicData} %c${byteLength}%c`, ), `color:${colors.outgoing}`, 'color:inherit', 'color:gray;font-weight:normal', 'color:inherit;font-weight:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() } /** * Prints the outgoing client message initiated * by `server.send()` in the event handler. */ async function logOutgoingMockedClientMessage( event: MessageEvent<WebSocketData>, ) { const byteLength = getMessageLength(event.data) const publicData = await getPublicData(event.data) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c⬆%c ${publicData} %c${byteLength}%c`, ), `color:${colors.mocked}`, 'color:inherit', 'color:gray;font-weight:normal', 'color:inherit;font-weight:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() } /** * Prints the outgoing client message initiated * by `client.send()` in the event handler. */ async function logIncomingMockedClientMessage( event: MessageEvent<WebSocketData>, ) { const byteLength = getMessageLength(event.data) const publicData = await getPublicData(event.data) // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c⬇%c ${publicData} %c${byteLength}%c`, ), `color:${colors.mocked}`, 'color:inherit', 'color:gray;font-weight:normal', 'color:inherit;font-weight:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() } async function logIncomingServerMessage(event: MessageEvent<WebSocketData>) { const byteLength = getMessageLength(event.data) const publicData = await getPublicData(event.data) const arrow = event.defaultPrevented ? '⇣' : '⬇' // eslint-disable-next-line no-console console.groupCollapsed( devUtils.formatMessage( `${getTimestamp({ milliseconds: true })} %c${arrow}%c ${publicData} %c${byteLength}%c`, ), `color:${colors.incoming}`, 'color:inherit', 'color:gray;font-weight:normal', 'color:inherit;font-weight:inherit', ) // eslint-disable-next-line no-console console.log(event) // eslint-disable-next-line no-console console.groupEnd() }