@yuanlu_yl/sveltekit-ws
v1.5.2
Published
WebSocket integration for SvelteKit without external server
Maintainers
Readme
SvelteKit WebSocket
本包 fork 自 ketarketir 的 sveltekit-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 无法连接
- 确保
createWebSocketHandler在server.listen()之前调用 - 确认端口已正确暴露
- 防火墙允许 WebSocket 连接
- 反向代理(nginx)已配置 WebSocket upgrade 支持
贡献
欢迎贡献代码!请阅读 CONTRIBUTING.md。
许可证
MIT © 2024
