health-relay-server
v1.0.12
Published
HealthClaw WebSocket Relay Server
Readme
health-relay-server 技术文档
中继服务端 - WebSocket 消息路由
1. 项目信息
| 属性 | 值 |
|------|-----|
| 项目名 | health-relay-server |
| 语言 | Node.js + TypeScript |
| 框架 | ws (WebSocket) |
| 部署 | 云服务器 (119.45.24.29) |
| 端口 | 5201 |
| 进程管理 | PM2 |
2. 核心职责
- WebSocket 中继:接收 Mac mini 和 iOS 的 WebSocket 连接
- 消息路由:在 Mac mini 和 iOS 之间转发消息
- 认证管理:验证连接密码 (accessCode)
- 连接管理:维护连接状态,断线清理
- ⚠️ 不存储任何数据:纯转发
3. 项目结构
health-relay-server/
├── src/
│ ├── index.ts # 入口
│ ├── server.ts # WebSocket 服务
│ ├── router.ts # 消息路由
│ ├── auth.ts # 认证
│ ├── types.ts # 类型定义
│ └── utils/
│ └── logger.ts # 日志
├── package.json
├── tsconfig.json
├── ecosystem.config.js # PM2 配置
└── README.md4. 协议设计
4.1 连接 URL
ws://119.45.24.29:5201/relay/{appId}?secret={accessCode}4.2 appId 命名规则
| 端 | appId 格式 | 示例 |
|---|-----------|------|
| Mac mini | health-mac-{hostname} | health-mac-LeoMac-mini |
| iOS | health-ios-{deviceId} | health-ios-ABC123 |
4.3 预共享密钥
health-secret-code-20245. 消息格式
5.1 基础消息
interface BaseMessage {
type: 'req' | 'res' | 'event' | 'ping' | 'pong';
id?: string;
timestamp: number;
}5.2 请求消息 (iOS → Mac)
interface RequestMessage extends BaseMessage {
type: 'req';
method: string; // 'openclaw.status' | 'health.latest' | ...
params?: Record<string, any>;
}5.3 响应消息 (Mac → iOS)
interface ResponseMessage extends BaseMessage {
type: 'res';
ok: boolean;
data?: any;
error?: { message: string };
}5.4 事件消息 (Mac → iOS)
interface EventMessage extends BaseMessage {
type: 'event';
event: string; // 'data.update' | 'health.update' | ...
payload?: any;
}6. 路由流程
┌─────────────────────────────────────────────────────────────────┐
│ health-relay-server │
│ │
│ 1. Mac mini 连接 │
│ ws://.../relay/health-mac-LeoMac-mini?secret=xxx │
│ → server 注册: { 'health-mac-LeoMac-mini' → ws1 } │
│ │
│ 2. iOS 连接 │
│ ws://.../relay/health-ios-ABC123?secret=xxx │
│ → server 注册: { 'health-ios-ABC123' → ws2 } │
│ │
│ 3. iOS 发送请求 → server 查找配对的 mac → 转发 │
│ │
│ 4. Mac 处理 → 发送响应 → server 转发给 iOS │
│ │
└─────────────────────────────────────────────────────────────────┘6.1 配对规则
- iOS 连接带有
health-ios-*前缀 - Mac 连接带有
health-mac-*前缀 - 同一 session 下,server 自动路由它们之间的消息
7. 核心代码模块
7.1 auth.ts - 认证模块
// 验证 accessCode
function validateAccessCode(secret: string): boolean {
const validCodes = process.env.ACCESS_CODES?.split(',') || [];
return validCodes.includes(secret);
}
// 从 URL 解析 appId 和 secret
function parseConnectionParams(url: string, query: Record<string, string>): {
appId: string;
secret: string;
error?: string;
}7.2 router.ts - 路由模块
// 连接注册
function registerConnection(appId: string, ws: WebSocket): void;
// 消息转发
function routeMessage(fromAppId: string, message: BaseMessage): void;
// 配对查找(通过 appId 前缀匹配)
function findPairedDevice(appId: string): WebSocket | null;
// 断开清理
function unregisterConnection(appId: string): void;7.3 server.ts - WebSocket 服务
import { WebSocketServer, WebSocket } from 'ws';
const wss = new WebSocketServer({ port: Number(process.env.PORT) || 5201 });
wss.on('connection', (ws, req) => {
// 1. 解析 URL 和 query
const { appId, secret, error } = parseConnectionParams(req.url, query);
// 2. 认证
if (!validateAccessCode(secret)) {
ws.close(4001, 'Unauthorized');
return;
}
// 3. 注册连接
registerConnection(appId, ws);
// 4. 监听消息并路由
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
routeMessage(appId, msg);
});
// 5. 断开清理
ws.on('close', () => unregisterConnection(appId));
});8. 环境变量
# .env
PORT=5201
ACCESS_CODES=health-secret-code-2024
LOG_LEVEL=info
MAX_CONNECTIONS=100
PING_INTERVAL=30000
PING_TIMEOUT=600009. PM2 部署
9.1 ecosystem.config.js
module.exports = {
apps: [{
name: 'health-relay-server',
script: './dist/index.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '200M',
env: {
NODE_ENV: 'production',
PORT: 5201,
ACCESS_CODES: 'health-secret-code-2024',
LOG_LEVEL: 'info'
}
}]
};9.2 部署命令
# 在云服务器上
cd /opt/health-relay-server
npm install
npm run build
pm2 start ecosystem.config.js
pm2 save
pm2 startup # 开机自启10. 依赖
{
"dependencies": {
"ws": "^8.18.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/ws": "^8.5.12",
"tsx": "^4.19.2",
"typescript": "^5.9.3"
}
}11. 注意事项
- 不存储数据:server 是纯中继,不做持久化
- 断线重连:client 侧负责重连机制
- 心跳保活:client 每 30 秒发 ping
- 版本协商:未来可在连接时交换版本号
