npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yuanlu_yl/sveltekit-ws

v1.5.2

Published

WebSocket integration for SvelteKit without external server

Readme

SvelteKit WebSocket

本包 fork 自 ketarketirsveltekit-ws,基于 MIT 协议。

无需外部服务器的 SvelteKit WebSocket 集成方案,在开发和生产环境中均可无缝运行。

特性

  • 🚀 零配置 - 开箱即用,与 SvelteKit 完美集成
  • 🔄 自动重连 - 内置重连逻辑,可配置重试次数
  • 💪 类型安全 - 完整的 TypeScript 支持
  • 🔌 无需外部服务器 - 直接集成到 Vite 开发服务器和生产环境
  • 🎯 消息类型路由 - 多处理器按消息类型并行触发
  • 💗 心跳支持 - 自动 ping/pong 保持连接活跃
  • 🛡️ 客户端验证 - 自定义认证/鉴权钩子
  • 📦 体积小巧 - 极少的外部依赖

安装

npm install @yuanlu_yl/sveltekit-ws
# 或
pnpm add @yuanlu_yl/sveltekit-ws

使用方法

1. Vite 插件(vite.config.ts)

注册 WebSocket 传输层,此处只配置路径和选项,不注册处理器。

import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { viteWebSocketServer } from "@yuanlu_yl/sveltekit-ws/server";

export default defineConfig({
  plugins: [
    // ⚠️ WebSocket 插件必须在 sveltekit() 之前
    viteWebSocketServer({ path: "/ws" }),
    sveltekit(),
  ],
});

2. 服务端初始化(hooks.server.ts)

在 SvelteKit 的 init 钩子中注册处理器并初始化管理器。每个处理器声明自己处理的消息类型。

import type { ServerInit } from "@sveltejs/kit";
import { getWebSocketManager } from "@yuanlu_yl/sveltekit-ws/server";

export const init: ServerInit = async () => {
  const manager = getWebSocketManager();

  // 注册处理器,声明它处理的消息类型
  manager.addHandler(["chat/send", "chat/join"], {
    onConnect(connection) {
      manager.send(connection.id, {
        type: "welcome",
        data: { message: "已连接!" },
      });
    },
    onMessage(connection, message) {
      // 只会收到 type 为 "chat/send" 或 "chat/join" 的消息
      manager.broadcast({ type: "chat", data: message.data }, [connection.id]);
    },
    onDisconnect(connection) {
      console.log("已断开:", connection.id);
    },
  });

  manager.init((type, obj, msg, ...args) => {
    if (type === "error") console.error("[WS]", obj, msg, ...args);
    else if (type === "bad_msg") console.warn("[WS]", obj, msg, ...args);
  });
};

3. 生产环境配置(server.js)

使用 @sveltejs/adapter-node 的生产环境配置:

import { handler } from "./build/handler.js";
import { createServer } from "http";
import { createWebSocketHandler } from "@yuanlu_yl/sveltekit-ws/server";

const server = createServer(handler);

// 配置 WebSocket 传输层(处理器已在 hooks.server.ts 中注册)
createWebSocketHandler(server, { path: "/ws" });

const PORT = process.env.PORT || 3000;
server.listen(PORT);

4. 客户端

连接后直接发送消息即可,无需频道选择步骤。

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { browser } from '$app/environment';

  let ws: WebSocket | null = null;
  let messages: any[] = [];
  let connected = false;

  onMount(() => {
    if (!browser) return;

    ws = new WebSocket(`ws://${window.location.host}/ws`);

    ws.onopen = () => {
      connected = true;
      // 直接发送业务消息,服务端按消息类型自动路由
    };

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      messages = [...messages, message];
    };

    ws.onclose = () => { connected = false; };
  });

  onDestroy(() => ws?.close());

  function sendMessage(text: string) {
    ws?.send(JSON.stringify({ type: "chat/send", data: { text } }));
  }
</script>

自定义主处理器

默认情况下,WebSocket 事件触发时,框架会遍历所有注册的处理器并行调用。你可以通过 setMainHandler 替换这一行为,在处理器执行前注入自定义逻辑,例如设置 AsyncLocalStorage 上下文:

import { AsyncLocalStorage } from "node:async_hooks";
import { getWebSocketManager, defaultHandler } from "@yuanlu_yl/sveltekit-ws/server";

const als = new AsyncLocalStorage<{ connectionId: string }>();

const manager = getWebSocketManager();
manager.setMainHandler({
  async onConnect(connection) {
    await als.run({ connectionId: connection.id }, () =>
      defaultHandler.onConnect!(connection),
    );
  },
  async onMessage(connection, message) {
    await als.run({ connectionId: connection.id }, () =>
      defaultHandler.onMessage!(connection, message),
    );
  },
  async onDisconnect(connection) {
    await als.run({ connectionId: connection.id }, () =>
      defaultHandler.onDisconnect!(connection),
    );
  },
});

// 重置为默认行为
// manager.resetMainHandler();

消息类型路由

处理器在注册时声明自己处理的消息类型。当消息到达时,框架将其分发给所有注册了该消息类型的处理器。多个处理器可以处理同一消息类型,单个处理器也可以处理多种类型。

// 注册多个处理器,各自声明处理的消息类型
manager.addHandler(["chat/send", "chat/join"], chatHandlers);
manager.addHandler(["notification/push"], notificationHandlers);
manager.addHandler(["game/move", "game/join"], gameHandlers);

manager.init(logger);

基于 Schema 的消息校验

addWssSchemaHandler 使用 Standard Schema V1 对入站消息进行自动校验,提供类型安全的 message.data 和可配置的失败处理。支持 Zod、Valibot、ArkType 等任何兼容 Standard Schema V1 的库。

基本用法

import { addWssSchemaHandler } from "@yuanlu_yl/sveltekit-ws/server";
import { z } from "zod";

addWssSchemaHandler(
  {
    "chat/send": z.object({ text: z.string(), roomId: z.string() }),
    "chat/join": z.object({ roomId: z.string() }),
  },
  {
    onMessage(connection, message) {
      if (message.type === "chat/send") {
        // message.data 类型推导为 { text: string; roomId: string }
        manager.broadcast({
          type: "chat/receive",
          data: { text: message.data.text, roomId: message.data.roomId },
        });
      } else if (message.type === "chat/join") {
        // message.data 类型推导为 { roomId: string }
        connection.locals.currentRoom = message.data.roomId;
      }
    },
  },
  { sendError: "validation_error" },
);

校验失败策略

addWssSchemaHandler 提供多种策略处理校验失败消息,默认行为为静默丢弃:

| 策略 | 说明 | |------|------| | 'ignore'(默认) | 静默丢弃非法消息 | | 'disconnect' | 立即断开连接 | | { sendError: string } | 自动回发错误消息,类型为指定的 string | | { sendError, getData } | 自定义错误载荷内容 | | 自定义函数 | 完全控制失败处理逻辑 |

// 自定义函数示例
addWssSchemaHandler(
  { "chat/send": z.object({ text: z.string() }) },
  { onMessage(connection, message) { ... } },
  (connection, message, issues) => {
    console.warn("校验失败:", message.type, issues);
    connection.send({ type: "error", data: { reason: issues[0].message } });
  },
);

核心优势

  • 通过 Schema 推导实现类型安全的 message.data
  • 兼容任何 Standard Schema V1 库(Zod、Valibot 等)
  • 零运行时依赖 —— Standard Schema 接口直接内嵌在源码中
  • Tree-shake 友好 —— 未引用时自动被摇掉
  • 可配置的校验失败处理策略

与 addHandler 的对比

  • addHandler:直接访问所有消息,无校验,更灵活
  • addWssSchemaHandler:自动校验、类型安全、结构化错误处理

连接管理

import { getWebSocketManager } from "@yuanlu_yl/sveltekit-ws/server";

const manager = getWebSocketManager();

// 发送给指定客户端
manager.send("connection-id", { type: "private", data: { message: "仅对你可见" } });

// 广播给所有人
manager.broadcast({ type: "announcement", data: { message: "大家好!" } });

// 广播时排除部分连接
manager.broadcast({ type: "message", data: "Hello!" }, ["id-1", "id-2"]);

// 获取所有连接
const connections = manager.getConnections();

// 断开某个客户端
manager.disconnect("connection-id");

// 移除处理器
manager.removeHandler(chatHandlers);

// 获取连接数
const count = manager.size();

配置项

interface WSServerOptions {
  path?: string;              // 默认: '/ws'
  maxPayload?: number;        // 默认: 1MB
  heartbeat?: boolean;        // 默认: true
  heartbeatInterval?: number; // 默认: 30000ms
  verifyClient?: (info: { origin: string; secure: boolean; req: any }) => boolean | Promise<boolean>;
}

自定义客户端验证

viteWebSocketServer({
  path: "/ws",
  verifyClient: async ({ origin, secure, req }) => {
    const token = req.headers["authorization"];
    if (!token) return false;
    try {
      return !!(await verifyToken(token));
    } catch {
      return false;
    }
  },
});

消息类型

公共类型可从根导出引入:

import { WSMessage, isWSMessage } from "@yuanlu_yl/sveltekit-ws";

服务端类型从 server 导出引入:

import type { WSConnection, WSManager, WSHandlers, defaultHandler } from "@yuanlu_yl/sveltekit-ws/server";
interface WSMessage<Data = any, Type extends string = string> {
  type: Type;
  data: Data;
  timestamp?: number;
}

interface WSConnection<ResponseType extends WSMessage = WSMessage> {
  ws: WebSocket;
  id: string;
  readonly metadata: WSConnectionMetadata;
  readonly locals: Partial<WSConnectionLocals>;
  readonly handlers: ReadonlyArray<WSHandlers>;
  readonly msgHandler: ReadonlyMap<string, WSHandlers[]>;
}

示例

查看 /examples 目录,其中包含一个完整的聊天应用示例。

部署

Vercel / Cloudflare / Netlify

Serverless 平台不支持 WebSocket,请使用 adapter-node 配合 VPS 或独立服务器部署。

Docker

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "server.js"]

Nginx 配置

location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

常见问题

生产环境 WebSocket 无法连接

  1. 确保 createWebSocketHandlerserver.listen() 之前调用
  2. 确认端口已正确暴露
  3. 防火墙允许 WebSocket 连接
  4. 反向代理(nginx)已配置 WebSocket upgrade 支持

贡献

欢迎贡献代码!请阅读 CONTRIBUTING.md

许可证

MIT © 2024

致谢

灵感来源于 ubermanu/sveltekit-websocket