Skip to content

WebSocket Configuration Guide

This chapter provides a detailed introduction on how to configure and develop WebSocket Mock services using vite-plugin-mock-dev-server.

Overview

WebSocket provides a full-duplex communication channel, suitable for real-time chat, real-time data push, online collaboration, and other scenarios. The plugin supports WebSocket Mock through the ws option.

Basic Configuration

Enable WebSocket Support

Specify the WebSocket path prefix in the plugin configuration:

vite.config.ts
ts
import { defineConfig } from 'vite'
import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'

export default defineConfig({
  plugins: [
    mockDevServerPlugin({
      // HTTP Mock path prefix
      prefix: ['/api'],
      // WebSocket Mock path prefix
      wsPrefix: ['/ws', '/socket.io']
    })
  ]
})

Important

Paths in wsPrefix should not be configured in vite.config.ts's server.proxy at the same time, as this will cause WebSocket conflicts.

Basic WebSocket Mock

Create a WebSocket Mock file:

mock/chat.mock.ts
ts
import { defineMock } from 'vite-plugin-mock-dev-server'

export default defineMock({
  url: '/ws/chat',
  ws: true,
  setup(wss) {
    // Listen for connection events
    wss.on('connection', (ws, req) => {
      console.log('Client connected:', req.url)

      // Send welcome message
      ws.send(JSON.stringify({
        type: 'system',
        message: 'Welcome to chat!'
      }))

      // Listen for messages
      ws.on('message', (data) => {
        const message = JSON.parse(data.toString())
        console.log('Received:', message)

        // Broadcast to all clients
        wss.clients.forEach((client) => {
          if (client.readyState === 1) {
            client.send(JSON.stringify({
              type: 'message',
              data: message
            }))
          }
        })
      })

      // Listen for close
      ws.on('close', () => {
        console.log('Client disconnected')
      })
    })
  }
})

Configuration Options

ws Option

Enable WebSocket support:

ts
export default defineMock({
  url: '/ws/endpoint',
  ws: true, // Enable WebSocket
  setup(wss, options) {
    // WebSocket server logic
  }
})

setup Function

The core configuration function for the WebSocket server:

ts
function setup(wss, options): void

Parameter descriptions:

  • wss: WebSocketServer - WebSocket server instance
  • options: Contains the onCleanup cleanup callback registration function

wss (WebSocketServer)

WebSocket server instance, providing the following features:

Property/MethodTypeDescription
clientsSet<WebSocket>All connected clients
on(event, callback)FunctionListen for events
emit(event, data)FunctionEmit events

options.onCleanup

Register cleanup callbacks, executed during hot reload:

ts
export default defineMock({
  url: '/ws/chat',
  ws: true,
  setup(wss, { onCleanup }) {
    const interval = setInterval(() => {
      // Regularly broadcast heartbeat
      wss.clients.forEach((client) => {
        if (client.readyState === 1) {
          client.send(JSON.stringify({ type: 'heartbeat' }))
        }
      })
    }, 30000)

    // Register cleanup function
    onCleanup(() => {
      clearInterval(interval)
      console.log('Cleanup WebSocket resources')
    })
  }
})

WebSocket Events

Server Events

connection Event

Triggered when a new client connects:

ts
wss.on('connection', (ws, req) => {
  // ws: WebSocket instance
  // req: HTTP request object

  console.log('New connection from:', req.socket.remoteAddress)
  console.log('URL:', req.url)
  console.log('Headers:', req.headers)
})

Client Events

message Event

Triggered when a message is received from the client:

ts
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    // Process binary data
    console.log('Binary data received:', data)
  }
  else {
    // Process text data
    const text = data.toString()
    console.log('Text message:', text)
  }
})

close Event

Triggered when the client disconnects:

ts
ws.on('close', (code, reason) => {
  console.log(`Connection closed: ${code} - ${reason}`)
})

error Event

Triggered when an error occurs:

ts
ws.on('error', (error) => {
  console.error('WebSocket error:', error)
})

Real-world Application Scenarios

Scenario 1: Real-time Chat Room

mock/chat-room.mock.ts
ts
import { defineMock, defineMockData } from 'vite-plugin-mock-dev-server'

interface ChatMessage {
  id: string
  userId: string
  username: string
  content: string
  timestamp: number
}

// Store chat history
const chatHistory = defineMockData<ChatMessage[]>('chat-history', [])

// Store online users
const onlineUsers = defineMockData<Map<string, { username: string, joinTime: number }>>
('online-users', new Map())

export default defineMock({
  url: '/ws/chat-room',
  ws: true,
  setup(wss, { onCleanup }) {
    // Broadcast message to all clients
    const broadcast = (message: any, excludeWs?: WebSocket) => {
      const data = JSON.stringify(message)
      wss.clients.forEach((client) => {
        if (client !== excludeWs && client.readyState === 1) {
          client.send(data)
        }
      })
    }

    wss.on('connection', (ws, req) => {
      let userId: string | null = null

      ws.on('message', (data) => {
        try {
          const message = JSON.parse(data.toString())

          switch (message.type) {
            case 'join':
              userId = message.userId
              onlineUsers.value.set(userId, {
                username: message.username,
                joinTime: Date.now()
              })

              // Send history
              ws.send(JSON.stringify({
                type: 'history',
                data: chatHistory.value.slice(-50) // Last 50 messages
              }))

              // Broadcast user joined
              broadcast({
                type: 'user-joined',
                userId,
                username: message.username,
                onlineCount: onlineUsers.value.size
              })
              break

            case 'message': {
              if (!userId)
                return

              const chatMessage: ChatMessage = {
                id: generateId(),
                userId,
                username: message.username,
                content: message.content,
                timestamp: Date.now()
              }

              // Save to history
              chatHistory.value = [...chatHistory.value, chatMessage]

              // Broadcast message
              broadcast({
                type: 'new-message',
                data: chatMessage
              })
              break
            }

            case 'typing': {
              broadcast({
                type: 'user-typing',
                userId,
                username: message.username
              }, ws)
              break
            }
          }
        }
        catch (error) {
          ws.send(JSON.stringify({
            type: 'error',
            message: 'Invalid message format'
          }))
        }
      })

      ws.on('close', () => {
        if (userId) {
          const user = onlineUsers.value.get(userId)
          onlineUsers.value.delete(userId)

          broadcast({
            type: 'user-left',
            userId,
            username: user?.username,
            onlineCount: onlineUsers.value.size
          })
        }
      })
    })

    // Cleanup
    onCleanup(() => {
      onlineUsers.value.clear()
    })
  }
})

function generateId(): string {
  return Math.random().toString(36).substring(2, 15)
}

Scenario 2: Real-time Data Push

mock/realtime-data.mock.ts
ts
import { defineMock } from 'vite-plugin-mock-dev-server'

export default defineMock({
  url: '/ws/realtime',
  ws: true,
  setup(wss, { onCleanup }) {
    // Simulate real-time data
    const generateMetrics = () => ({
      cpu: Math.random() * 100,
      memory: Math.random() * 100,
      network: Math.random() * 1000,
      timestamp: Date.now()
    })

    // Push data periodically
    const interval = setInterval(() => {
      const data = generateMetrics()
      const message = JSON.stringify({
        type: 'metrics',
        data
      })

      wss.clients.forEach((client) => {
        if (client.readyState === 1) {
          client.send(message)
        }
      })
    }, 1000)

    wss.on('connection', (ws) => {
      // Send current data immediately
      ws.send(JSON.stringify({
        type: 'metrics',
        data: generateMetrics()
      }))
    })

    onCleanup(() => {
      clearInterval(interval)
    })
  }
})

Scenario 3: Online Collaborative Editing

mock/collaboration.mock.ts
ts
import { defineMock, defineMockData } from 'vite-plugin-mock-dev-server'

interface Operation {
  id: string
  userId: string
  type: 'insert' | 'delete'
  position: number
  content?: string
  timestamp: number
}

const documentContent = defineMockData<string>('doc-content', '')
const operations = defineMockData<Operation[]>('operations', [])

export default defineMock({
  url: '/ws/collaboration/:docId',
  ws: true,
  setup(wss) {
    wss.on('connection', (ws, req) => {
      const docId = req.url?.split('/').pop()

      // Send current document content
      ws.send(JSON.stringify({
        type: 'init',
        content: documentContent.value,
        docId
      }))

      ws.on('message', (data) => {
        const message = JSON.parse(data.toString())

        if (message.type === 'operation') {
          const op: Operation = {
            id: generateId(),
            userId: message.userId,
            type: message.operation.type,
            position: message.operation.position,
            content: message.operation.content,
            timestamp: Date.now()
          }

          // Apply operation
          applyOperation(op)
          operations.value = [...operations.value, op]

          // Broadcast to other clients
          wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === 1) {
              client.send(JSON.stringify({
                type: 'operation',
                data: op
              }))
            }
          })
        }
      })
    })

    function applyOperation(op: Operation) {
      const content = documentContent.value
      if (op.type === 'insert' && op.content) {
        documentContent.value
          = content.slice(0, op.position)
            + op.content
            + content.slice(op.position)
      }
      else if (op.type === 'delete') {
        documentContent.value
          = content.slice(0, op.position)
            + content.slice(op.position + (op.content?.length || 1))
      }
    }

    function generateId(): string {
      return Date.now().toString(36) + Math.random().toString(36).substr(2)
    }
  }
})

Client Usage Examples

Native WebSocket

client.ts
ts
const ws = new WebSocket('ws://localhost:5173/ws/chat')

// Connection established
ws.onopen = () => {
  console.log('Connected')

  // Send join message
  ws.send(JSON.stringify({
    type: 'join',
    userId: 'user-123',
    username: 'John'
  }))
}

// Receive message
ws.onmessage = (event) => {
  const message = JSON.parse(event.data)

  switch (message.type) {
    case 'history':
      console.log('Chat history:', message.data)
      break
    case 'new-message':
      console.log('New message:', message.data)
      break
    case 'user-joined':
      console.log(`${message.username} joined`)
      break
  }
}

// Connection closed
ws.onclose = () => {
  console.log('Disconnected')
}

// Send message
function sendMessage(content: string) {
  ws.send(JSON.stringify({
    type: 'message',
    username: 'John',
    content
  }))
}

Using Socket.io

If using the Socket.io client:

ts
import { io } from 'socket.io-client'

const socket = io('ws://localhost:5173', {
  path: '/ws/chat'
})

socket.on('connect', () => {
  console.log('Connected')
})

socket.on('new-message', (data) => {
  console.log('New message:', data)
})

socket.emit('message', {
  username: 'John',
  content: 'Hello!'
})

Debugging Tips

Enable Debug Logging

ts
mockDevServerPlugin({
  log: 'debug'
})

Using Chrome DevTools

  1. Open Chrome DevTools
  2. Switch to Network panel
  3. Select WS (WebSocket) filter
  4. View WebSocket connections and messages

Command Line Testing

Test using the wscat tool:

bash
# Install wscat
npm install -g wscat

# Connect to WebSocket
wscat -c ws://localhost:5173/ws/chat

# Send message
> {"type":"join","userId":"1","username":"test"}

Important Notes

  1. Connection Limit: Browsers have a limit on the number of WebSocket connections to the same domain (usually 6)
  2. Heartbeat Mechanism: It is recommended to implement heartbeat detection to prevent connections from being disconnected by middleware
  3. Error Handling: Always add error handling to avoid uncaught exceptions
  4. Resource Cleanup: Use onCleanup to clean up timers and resources
  5. Message Format: It is recommended to use JSON format uniformly for easier processing

FAQ

Q: WebSocket connection failed?

Check the following:

  1. Is the wsPrefix configuration correct?
  2. Ensure the same path is not configured in vite.config.ts's proxy
  3. Use ws:// or wss:// protocol, not http://

Q: How to broadcast messages?

ts
wss.clients.forEach((client) => {
  if (client.readyState === 1) { // Ensure connection is open
    client.send(message)
  }
})

Q: How to get client information?

ts
wss.on('connection', (ws, req) => {
  console.log('IP:', req.socket.remoteAddress)
  console.log('URL:', req.url)
  console.log('Headers:', req.headers)
})

Released under the MIT License