@lzpenguin/server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -0
- package/index.js +315 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# @lzpenguin/server
|
|
2
|
+
|
|
3
|
+
Riffle 游戏服务器 WebSocket 客户端 SDK
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lzpenguin/server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { RiffleServer } from '@lzpenguin/server';
|
|
15
|
+
|
|
16
|
+
const server = new RiffleServer({
|
|
17
|
+
url: 'wss://api.riffle.app',
|
|
18
|
+
postId: 123456,
|
|
19
|
+
token: 'your-token-here'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 监听数据更新
|
|
23
|
+
server.onData((data) => {
|
|
24
|
+
console.log('World:', data.world);
|
|
25
|
+
console.log('Self:', data.self);
|
|
26
|
+
console.log('Players:', data.players);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 初始化服务器
|
|
30
|
+
server.init({
|
|
31
|
+
hash: 'game-version-hash-123',
|
|
32
|
+
world: { score: 0, level: 1 },
|
|
33
|
+
self: {
|
|
34
|
+
public: { name: 'Player1' },
|
|
35
|
+
private: { health: 100 }
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### 构造函数
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
new RiffleServer({
|
|
46
|
+
url: 'wss://api.riffle.app', // 必需:WebSocket 服务器 URL
|
|
47
|
+
postId: 123456, // 必需:游戏贴文 ID
|
|
48
|
+
token: 'your-token-here', // 必需:认证 token
|
|
49
|
+
autoReconnect: true, // 可选:是否自动重连,默认 true
|
|
50
|
+
reconnectInterval: 3000 // 可选:重连间隔(毫秒),默认 3000
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 三种消息类型
|
|
55
|
+
|
|
56
|
+
### 1. init() - 初始化服务器
|
|
57
|
+
|
|
58
|
+
初始化服务器(必需,连接后必须先调用)。hash 相同则返回现有数据,hash 不同则删除旧服务器并重新创建。
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
server.init({
|
|
62
|
+
hash: 'game-version-hash-123', // 必需:游戏版本哈希值
|
|
63
|
+
world: { score: 0, level: 1 }, // 可选:世界初始数据
|
|
64
|
+
self: {
|
|
65
|
+
public: { name: 'Player1' }, // 可选:公开数据
|
|
66
|
+
private: { health: 100 } // 可选:私有数据
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**响应数据(通过 onData 接收):**
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"world": { "score": 0, "level": 1 },
|
|
75
|
+
"self": {
|
|
76
|
+
"public": { "name": "Player1" },
|
|
77
|
+
"private": { "health": 100 }
|
|
78
|
+
},
|
|
79
|
+
"players": [
|
|
80
|
+
{ "name": "Player2", "position": { "x": 10, "y": 20 } }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**关于 hash:**
|
|
86
|
+
- **hash 相同**:传入的值不生效,直接返回现有数据
|
|
87
|
+
- **hash 不同**:删除旧服务器并重新创建,使用传入的值初始化数据
|
|
88
|
+
- **何时更新 hash**:如果更改了游戏,且改动了角色或世界字段的设计(如新增/删除字段、改变字段类型),就需要更新 hash 让服务器重新初始化,避免数据结构不兼容
|
|
89
|
+
|
|
90
|
+
**注意:** init 成功后服务器每 0.2 秒自动推送最新数据
|
|
91
|
+
|
|
92
|
+
### 2. update() - 更新数据
|
|
93
|
+
|
|
94
|
+
部分更新数据(合并到现有数据,不替换)。
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// 更新世界数据
|
|
98
|
+
server.update({ world: { score: 200 } });
|
|
99
|
+
|
|
100
|
+
// 更新玩家数据
|
|
101
|
+
server.update({
|
|
102
|
+
self: {
|
|
103
|
+
public: { position: { x: 15, y: 25 } },
|
|
104
|
+
private: { health: 90 }
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 同时更新世界和玩家数据
|
|
109
|
+
server.update({
|
|
110
|
+
world: { score: 200 },
|
|
111
|
+
self: { public: { position: { x: 15, y: 25 } } }
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**响应数据(通过 onData 接收):**
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"world": { "score": 200, "level": 1 },
|
|
119
|
+
"self": {
|
|
120
|
+
"public": { "name": "Player1", "position": { "x": 15, "y": 25 } },
|
|
121
|
+
"private": { "health": 90, "mana": 50 }
|
|
122
|
+
},
|
|
123
|
+
"players": [{ "name": "Player2", "position": { "x": 10, "y": 20 } }]
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**注意:** 只更新提供的字段,未提供的字段保持不变,更新后立即推送最新数据
|
|
128
|
+
|
|
129
|
+
### 3. onData() - 监听数据推送
|
|
130
|
+
|
|
131
|
+
监听服务器推送的最新数据。init 成功后每 0.2 秒自动推送,调用 init/update 后立即推送。
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const unsubscribe = server.onData((data) => {
|
|
135
|
+
console.log('World:', data.world);
|
|
136
|
+
console.log('Self:', data.self);
|
|
137
|
+
console.log('Players:', data.players);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// 取消监听
|
|
141
|
+
unsubscribe();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**数据格式:**
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"world": { "score": 200, "level": 1 },
|
|
148
|
+
"self": {
|
|
149
|
+
"public": { "name": "Player1", "position": { "x": 15, "y": 25 } },
|
|
150
|
+
"private": { "health": 90, "mana": 50 }
|
|
151
|
+
},
|
|
152
|
+
"players": [
|
|
153
|
+
{ "name": "Player2", "position": { "x": 10, "y": 20 } },
|
|
154
|
+
{ "name": "Player3", "position": { "x": 30, "y": 40 } }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**推送时机:** init 响应、定时推送(每 0.2 秒)、init/update 后立即推送
|
|
160
|
+
|
|
161
|
+
## 联机游戏示例
|
|
162
|
+
|
|
163
|
+
以下示例展示如何使用本库实现多人在线游戏:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
import { RiffleServer } from '@lzpenguin/server';
|
|
167
|
+
|
|
168
|
+
const server = new RiffleServer({
|
|
169
|
+
url: 'wss://api.riffle.app',
|
|
170
|
+
postId: 123456,
|
|
171
|
+
token: 'your-token-here'
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 监听服务器推送,更新游戏状态
|
|
175
|
+
server.onData((data) => {
|
|
176
|
+
// 更新自己的位置
|
|
177
|
+
const myPosition = data.self?.public?.position;
|
|
178
|
+
|
|
179
|
+
// 更新其他玩家列表
|
|
180
|
+
const otherPlayers = data.players || [];
|
|
181
|
+
|
|
182
|
+
// 渲染游戏画面
|
|
183
|
+
renderPlayers(myPosition, otherPlayers);
|
|
184
|
+
|
|
185
|
+
// 检查游戏状态
|
|
186
|
+
if (data.world?.gameOver) {
|
|
187
|
+
console.log('游戏结束!分数:', data.world.score);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 初始化游戏
|
|
192
|
+
server.init({
|
|
193
|
+
hash: 'v1.0.0',
|
|
194
|
+
world: { score: 0, level: 1 },
|
|
195
|
+
self: {
|
|
196
|
+
public: { name: 'Player1', position: { x: 0, y: 0 } },
|
|
197
|
+
private: { health: 100 }
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// 更新玩家位置
|
|
202
|
+
function movePlayer(x, y) {
|
|
203
|
+
server.update({
|
|
204
|
+
self: {
|
|
205
|
+
public: { position: { x, y } }
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 更新分数
|
|
211
|
+
function updateScore(score) {
|
|
212
|
+
server.update({
|
|
213
|
+
world: { score }
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
ISC
|
package/index.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RiffleServer - 游戏服务器 WebSocket 客户端
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```js
|
|
8
|
+
* import { RiffleServer } from '@riffle/server';
|
|
9
|
+
*
|
|
10
|
+
* const server = new RiffleServer({
|
|
11
|
+
* url: 'wss://api.riffle.app',
|
|
12
|
+
* postId: 123456,
|
|
13
|
+
* token: 'your-token-here'
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // 监听服务器推送的最新数据
|
|
17
|
+
* server.onData((data) => {
|
|
18
|
+
* console.log('World:', data.world);
|
|
19
|
+
* console.log('Self:', data.self);
|
|
20
|
+
* console.log('Players:', data.players);
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // 初始化服务器(必需,连接后必须先调用)
|
|
24
|
+
* server.init({
|
|
25
|
+
* hash: 'game-version-hash-123', // 游戏版本哈希值
|
|
26
|
+
* world: { score: 0, level: 1 }, // 世界初始数据(可选)
|
|
27
|
+
* self: {
|
|
28
|
+
* public: { name: 'Player1' }, // 公开数据(可选)
|
|
29
|
+
* private: { health: 100 } // 私有数据(可选)
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // 更新数据
|
|
34
|
+
* server.update({
|
|
35
|
+
* world: { score: 200 },
|
|
36
|
+
* self: {
|
|
37
|
+
* public: { position: { x: 15, y: 25 } },
|
|
38
|
+
* private: { health: 90 }
|
|
39
|
+
* }
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class RiffleServer {
|
|
44
|
+
/**
|
|
45
|
+
* @param {Object} options - 配置选项
|
|
46
|
+
* @param {string} options.url - WebSocket 服务器 URL(例如:'wss://api.riffle.app' 或 'ws://localhost:8000')
|
|
47
|
+
* @param {number} options.postId - 游戏贴文 ID
|
|
48
|
+
* @param {string} options.token - 认证 token
|
|
49
|
+
* @param {boolean} [options.autoReconnect=true] - 是否自动重连
|
|
50
|
+
* @param {number} [options.reconnectInterval=3000] - 重连间隔(毫秒)
|
|
51
|
+
*/
|
|
52
|
+
constructor(options) {
|
|
53
|
+
const { url, postId, token, autoReconnect = true, reconnectInterval = 3000 } = options;
|
|
54
|
+
|
|
55
|
+
if (!url) {
|
|
56
|
+
throw new Error('URL is required');
|
|
57
|
+
}
|
|
58
|
+
if (!postId) {
|
|
59
|
+
throw new Error('postId is required');
|
|
60
|
+
}
|
|
61
|
+
if (!token) {
|
|
62
|
+
throw new Error('token is required');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.url = url;
|
|
66
|
+
this.postId = postId;
|
|
67
|
+
this.token = token;
|
|
68
|
+
this.autoReconnect = autoReconnect;
|
|
69
|
+
this.reconnectInterval = reconnectInterval;
|
|
70
|
+
|
|
71
|
+
this.ws = null;
|
|
72
|
+
this.isConnected = false;
|
|
73
|
+
this.isConnecting = false;
|
|
74
|
+
this.isInitialized = false;
|
|
75
|
+
this.reconnectTimer = null;
|
|
76
|
+
this.dataCallbacks = [];
|
|
77
|
+
this.pendingInit = null; // 待执行的 init 请求
|
|
78
|
+
|
|
79
|
+
// 当前服务器数据缓存
|
|
80
|
+
this.currentData = null;
|
|
81
|
+
|
|
82
|
+
// 连接状态
|
|
83
|
+
this.connect();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 连接到 WebSocket 服务器
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
connect() {
|
|
91
|
+
if (this.isConnecting || (this.isConnected && this.ws?.readyState === WebSocket.OPEN)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.isConnecting = true;
|
|
96
|
+
|
|
97
|
+
// 构建 WebSocket URL(包含 post_id 和 token)
|
|
98
|
+
let wsUrl = `${this.url}?post_id=${this.postId}&token=${this.token}`;
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
this.ws = new WebSocket(wsUrl);
|
|
103
|
+
|
|
104
|
+
this.ws.on('open', () => {
|
|
105
|
+
this.isConnected = true;
|
|
106
|
+
this.isConnecting = false;
|
|
107
|
+
this.isInitialized = false; // 连接后重置初始化状态
|
|
108
|
+
console.log('[RiffleServer] Connected');
|
|
109
|
+
|
|
110
|
+
// 如果有待执行的 init 请求,自动执行
|
|
111
|
+
if (this.pendingInit) {
|
|
112
|
+
const initData = this.pendingInit;
|
|
113
|
+
this.pendingInit = null;
|
|
114
|
+
this._doInit(initData);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.ws.on('message', (data) => {
|
|
119
|
+
try {
|
|
120
|
+
const message = JSON.parse(data.toString());
|
|
121
|
+
|
|
122
|
+
// 检查是否是错误消息
|
|
123
|
+
if (message.error) {
|
|
124
|
+
console.error('[RiffleServer] Server error:', message.error);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 检查是否是数据消息(包含 world、self、players 字段)
|
|
129
|
+
if (message.world !== undefined && message.self !== undefined && message.players !== undefined) {
|
|
130
|
+
// 如果还未初始化,这可能是 init 响应
|
|
131
|
+
if (!this.isInitialized && message.self) {
|
|
132
|
+
this.isInitialized = true;
|
|
133
|
+
console.log('[RiffleServer] Initialized');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 更新缓存并触发数据回调
|
|
137
|
+
this.currentData = message;
|
|
138
|
+
this.dataCallbacks.forEach(cb => cb(message));
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('[RiffleServer] Failed to parse message:', error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.ws.on('error', (error) => {
|
|
146
|
+
console.error('[RiffleServer] WebSocket error:', error);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.ws.on('close', () => {
|
|
150
|
+
this.isConnected = false;
|
|
151
|
+
this.isConnecting = false;
|
|
152
|
+
this.isInitialized = false; // 断开连接后重置初始化状态
|
|
153
|
+
console.log('[RiffleServer] Disconnected');
|
|
154
|
+
|
|
155
|
+
// 自动重连
|
|
156
|
+
if (this.autoReconnect && !this.reconnectTimer) {
|
|
157
|
+
this.reconnectTimer = setTimeout(() => {
|
|
158
|
+
this.reconnectTimer = null;
|
|
159
|
+
this.connect();
|
|
160
|
+
}, this.reconnectInterval);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.isConnecting = false;
|
|
165
|
+
console.error('[RiffleServer] Connection error:', error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 断开连接
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
disconnect() {
|
|
174
|
+
this.autoReconnect = false;
|
|
175
|
+
if (this.reconnectTimer) {
|
|
176
|
+
clearTimeout(this.reconnectTimer);
|
|
177
|
+
this.reconnectTimer = null;
|
|
178
|
+
}
|
|
179
|
+
if (this.ws) {
|
|
180
|
+
this.ws.close();
|
|
181
|
+
this.ws = null;
|
|
182
|
+
}
|
|
183
|
+
this.isConnected = false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 发送更新请求
|
|
188
|
+
* @private
|
|
189
|
+
* @param {Object} updateData - 更新数据
|
|
190
|
+
*/
|
|
191
|
+
sendUpdate(updateData) {
|
|
192
|
+
if (!this.isConnected || this.ws?.readyState !== WebSocket.OPEN) {
|
|
193
|
+
console.warn('[RiffleServer] Not connected, cannot send update');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!this.isInitialized) {
|
|
198
|
+
console.warn('[RiffleServer] Not initialized, please call init() first');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
this.ws.send(JSON.stringify(updateData));
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('[RiffleServer] Failed to send update:', error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 初始化服务器(必需,连接后必须先调用)
|
|
211
|
+
* @param {Object} initData - 初始化数据
|
|
212
|
+
* @param {string} initData.hash - 游戏版本哈希值(必需)
|
|
213
|
+
* @param {Object} [initData.world] - 世界初始数据(可选)
|
|
214
|
+
* @param {Object} [initData.self] - 玩家初始数据(可选)
|
|
215
|
+
* @param {Object} [initData.self.public] - 公开数据(可选)
|
|
216
|
+
* @param {Object} [initData.self.private] - 私有数据(可选)
|
|
217
|
+
* @example
|
|
218
|
+
* server.init({
|
|
219
|
+
* hash: 'game-version-hash-123',
|
|
220
|
+
* world: { score: 0, level: 1 },
|
|
221
|
+
* self: {
|
|
222
|
+
* public: { name: 'Player1' },
|
|
223
|
+
* private: { health: 100 }
|
|
224
|
+
* }
|
|
225
|
+
* });
|
|
226
|
+
*/
|
|
227
|
+
init(initData) {
|
|
228
|
+
if (!initData || !initData.hash) {
|
|
229
|
+
console.error('[RiffleServer] hash is required for init');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 如果还未连接,保存 init 请求,连接建立后自动执行
|
|
234
|
+
if (!this.isConnected || this.ws?.readyState !== WebSocket.OPEN) {
|
|
235
|
+
console.log('[RiffleServer] Not connected yet, will init after connection');
|
|
236
|
+
this.pendingInit = initData;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 已连接,直接执行
|
|
241
|
+
this._doInit(initData);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 执行初始化请求
|
|
246
|
+
* @private
|
|
247
|
+
* @param {Object} initData - 初始化数据
|
|
248
|
+
*/
|
|
249
|
+
_doInit(initData) {
|
|
250
|
+
try {
|
|
251
|
+
const initMessage = {
|
|
252
|
+
hash: initData.hash,
|
|
253
|
+
world: initData.world || undefined,
|
|
254
|
+
self: initData.self || undefined
|
|
255
|
+
};
|
|
256
|
+
this.ws.send(JSON.stringify(initMessage));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('[RiffleServer] Failed to send init:', error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 更新数据(部分更新)
|
|
264
|
+
* @param {Object} updateData - 更新数据
|
|
265
|
+
* @param {Object} [updateData.world] - 世界数据(可选)
|
|
266
|
+
* @param {Object} [updateData.self] - 玩家数据(可选)
|
|
267
|
+
* @param {Object} [updateData.self.public] - 公开数据(可选)
|
|
268
|
+
* @param {Object} [updateData.self.private] - 私有数据(可选)
|
|
269
|
+
* @example
|
|
270
|
+
* server.update({
|
|
271
|
+
* world: { score: 200 },
|
|
272
|
+
* self: {
|
|
273
|
+
* public: { position: { x: 15, y: 25 } },
|
|
274
|
+
* private: { health: 90 }
|
|
275
|
+
* }
|
|
276
|
+
* });
|
|
277
|
+
*/
|
|
278
|
+
update(updateData) {
|
|
279
|
+
this.sendUpdate(updateData);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 监听服务器推送的最新数据
|
|
284
|
+
* @param {Function} callback - 回调函数,接收服务器推送的数据
|
|
285
|
+
* @returns {Function} 取消监听的函数
|
|
286
|
+
* @example
|
|
287
|
+
* const unsubscribe = server.onData((data) => {
|
|
288
|
+
* console.log('World:', data.world);
|
|
289
|
+
* console.log('Self:', data.self);
|
|
290
|
+
* console.log('Players:', data.players);
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* // 取消监听
|
|
294
|
+
* unsubscribe();
|
|
295
|
+
*/
|
|
296
|
+
onData(callback) {
|
|
297
|
+
if (typeof callback !== 'function') {
|
|
298
|
+
throw new Error('Callback must be a function');
|
|
299
|
+
}
|
|
300
|
+
this.dataCallbacks.push(callback);
|
|
301
|
+
|
|
302
|
+
// 如果已有缓存数据,立即调用一次
|
|
303
|
+
if (this.currentData) {
|
|
304
|
+
callback(this.currentData);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 返回取消监听的函数
|
|
308
|
+
return () => {
|
|
309
|
+
const index = this.dataCallbacks.indexOf(callback);
|
|
310
|
+
if (index > -1) {
|
|
311
|
+
this.dataCallbacks.splice(index, 1);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lzpenguin/server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Riffle 游戏服务器 WebSocket 客户端 SDK",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "lzpenguin",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"riffle",
|
|
20
|
+
"websocket",
|
|
21
|
+
"game-server"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"ws": "^8.14.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.10.0",
|
|
28
|
+
"@types/ws": "^8.5.10"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"registry": "https://registry.npmjs.org/"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|