@lzpenguin/server 1.1.0 → 1.1.2
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 +55 -106
- package/index.d.ts +2 -6
- package/index.js +29 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,196 +13,145 @@ npm install @lzpenguin/server
|
|
|
13
13
|
```javascript
|
|
14
14
|
import { RiffleServer } from '@lzpenguin/server';
|
|
15
15
|
|
|
16
|
+
// 创建服务器实例
|
|
17
|
+
// 注意:post_id 和 token 会自动从 URL 参数中读取
|
|
18
|
+
// URL 格式:https://example.com/game.html?post_id=123456&token=your-token
|
|
16
19
|
const server = new RiffleServer({
|
|
17
|
-
url: 'wss://api.riffle.app',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
url: 'wss://api.riffle.app', // 必需:WebSocket 服务器地址
|
|
21
|
+
timestamp: 1234567890, // 必需:游戏时间戳(数字类型),直接使用上下文中给出的值
|
|
22
|
+
autoReconnect: true, // 可选:自动重连,默认 true
|
|
23
|
+
reconnectInterval: 3000 // 可选:重连间隔(毫秒),默认 3000
|
|
20
24
|
});
|
|
21
25
|
|
|
22
|
-
//
|
|
26
|
+
// 1. 监听数据推送(init 成功后每 0.2 秒自动推送,调用 init/update 后立即推送)
|
|
23
27
|
server.onData((data) => {
|
|
24
|
-
console.log('World:', data.world);
|
|
25
|
-
console.log('Self:', data.self);
|
|
26
|
-
console.log('Players:', data.players);
|
|
28
|
+
console.log('World:', data.world); // 世界数据
|
|
29
|
+
console.log('Self:', data.self); // 自己的公开数据
|
|
30
|
+
console.log('Players:', data.players); // 其他玩家的公开数据
|
|
31
|
+
// 注意:self 和 players[i] 结构相同,都只包含公开数据
|
|
27
32
|
});
|
|
28
33
|
|
|
29
|
-
//
|
|
34
|
+
// 2. 初始化服务器(必需,连接后必须先调用)
|
|
30
35
|
server.init({
|
|
31
|
-
hash: 'game-version-hash-123',
|
|
32
36
|
world: { score: 0, level: 1 },
|
|
33
37
|
self: {
|
|
34
38
|
public: { name: 'Player1', x: 100, y: 100 }
|
|
35
39
|
}
|
|
36
40
|
});
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## API
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
postId: 123456, // 必需:游戏贴文 ID
|
|
47
|
-
token: 'your-token-here', // 必需:认证 token
|
|
48
|
-
autoReconnect: true, // 可选:是否自动重连,默认 true
|
|
49
|
-
reconnectInterval: 3000 // 可选:重连间隔(毫秒),默认 3000
|
|
50
|
-
})
|
|
42
|
+
// 3. 更新数据(部分更新,合并到现有数据)
|
|
43
|
+
server.update({
|
|
44
|
+
world: { score: 200 },
|
|
45
|
+
self: { public: { x: 150, y: 150 } }
|
|
46
|
+
});
|
|
51
47
|
```
|
|
52
48
|
|
|
53
|
-
##
|
|
49
|
+
## 数据格式
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
初始化服务器(必需,连接后必须先调用)。hash 相同则返回现有数据,hash 不同则删除旧服务器并重新创建。
|
|
58
|
-
|
|
59
|
-
```javascript
|
|
60
|
-
server.init({
|
|
61
|
-
hash: 'game-version-hash-123', // 必需:游戏版本哈希值
|
|
62
|
-
world: { score: 0, level: 1 }, // 可选:世界初始数据
|
|
63
|
-
self: {
|
|
64
|
-
public: { name: 'Player1', x: 100, y: 100 } // 可选:公开数据
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
```
|
|
51
|
+
服务器推送的数据格式(通过 `onData` 接收):
|
|
68
52
|
|
|
69
|
-
**响应数据(通过 onData 接收):**
|
|
70
53
|
```json
|
|
71
54
|
{
|
|
72
55
|
"world": { "score": 0, "level": 1 },
|
|
73
56
|
"self": { "name": "Player1", "x": 100, "y": 100 },
|
|
74
57
|
"players": [
|
|
75
|
-
{ "name": "Player2", "x": 10, "y": 20 }
|
|
58
|
+
{ "name": "Player2", "x": 10, "y": 20 },
|
|
59
|
+
{ "name": "Player3", "x": 30, "y": 40 }
|
|
76
60
|
]
|
|
77
61
|
}
|
|
78
62
|
```
|
|
79
63
|
|
|
80
|
-
|
|
64
|
+
## API 说明
|
|
65
|
+
|
|
66
|
+
### init(data) - 初始化服务器
|
|
81
67
|
|
|
82
|
-
|
|
83
|
-
- **hash 相同**:传入的值不生效,直接返回现有数据
|
|
84
|
-
- **hash 不同**:删除旧服务器并重新创建,使用传入的值初始化数据
|
|
85
|
-
- **何时更新 hash**:如果更改了游戏,且改动了角色或世界字段的设计(如新增/删除字段、改变字段类型),就需要更新 hash 让服务器重新初始化,避免数据结构不兼容
|
|
68
|
+
必需,连接后必须先调用。根据 timestamp 判断是否重新初始化。
|
|
86
69
|
|
|
87
|
-
|
|
70
|
+
```javascript
|
|
71
|
+
server.init({
|
|
72
|
+
world: { score: 0, level: 1 }, // 可选:世界初始数据
|
|
73
|
+
self: {
|
|
74
|
+
public: { name: 'Player1', x: 100, y: 100 } // 可选:玩家公开数据
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
```
|
|
88
78
|
|
|
89
|
-
###
|
|
79
|
+
### update(data) - 更新数据
|
|
90
80
|
|
|
91
|
-
|
|
81
|
+
部分更新数据(合并,不替换)。只更新提供的字段,未提供的字段保持不变。
|
|
92
82
|
|
|
93
83
|
```javascript
|
|
94
84
|
// 更新世界数据
|
|
95
85
|
server.update({ world: { score: 200 } });
|
|
96
86
|
|
|
97
87
|
// 更新玩家数据
|
|
98
|
-
server.update({
|
|
99
|
-
self: {
|
|
100
|
-
public: { x: 15, y: 25, rotation: 90 }
|
|
101
|
-
}
|
|
102
|
-
});
|
|
88
|
+
server.update({ self: { public: { x: 15, y: 25 } } });
|
|
103
89
|
|
|
104
|
-
//
|
|
90
|
+
// 同时更新
|
|
105
91
|
server.update({
|
|
106
92
|
world: { score: 200 },
|
|
107
93
|
self: { public: { x: 15, y: 25 } }
|
|
108
94
|
});
|
|
109
95
|
```
|
|
110
96
|
|
|
111
|
-
|
|
112
|
-
```json
|
|
113
|
-
{
|
|
114
|
-
"world": { "score": 200, "level": 1 },
|
|
115
|
-
"self": { "name": "Player1", "x": 15, "y": 25, "rotation": 90 },
|
|
116
|
-
"players": [{ "name": "Player2", "x": 10, "y": 20 }]
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**注意:** 只更新提供的字段,未提供的字段保持不变,更新后立即推送最新数据
|
|
121
|
-
|
|
122
|
-
### 3. onData() - 监听数据推送
|
|
97
|
+
### onData(callback) - 监听数据推送
|
|
123
98
|
|
|
124
|
-
|
|
99
|
+
监听服务器推送的数据。返回取消监听函数。
|
|
125
100
|
|
|
126
101
|
```javascript
|
|
127
102
|
const unsubscribe = server.onData((data) => {
|
|
128
|
-
|
|
129
|
-
console.log('Self:', data.self);
|
|
130
|
-
console.log('Players:', data.players);
|
|
103
|
+
// 处理数据
|
|
131
104
|
});
|
|
132
105
|
|
|
133
106
|
// 取消监听
|
|
134
107
|
unsubscribe();
|
|
135
108
|
```
|
|
136
109
|
|
|
137
|
-
|
|
138
|
-
```json
|
|
139
|
-
{
|
|
140
|
-
"world": { "score": 200, "level": 1 },
|
|
141
|
-
"self": { "name": "Player1", "x": 15, "y": 25 },
|
|
142
|
-
"players": [
|
|
143
|
-
{ "name": "Player2", "x": 10, "y": 20 },
|
|
144
|
-
{ "name": "Player3", "x": 30, "y": 40 }
|
|
145
|
-
]
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**注意:** `self` 和 `players[i]` 具有相同的数据结构,都只包含公开数据。
|
|
110
|
+
**推送时机:** init 成功后每 0.2 秒自动推送,调用 init/update 后立即推送
|
|
150
111
|
|
|
151
|
-
|
|
112
|
+
## 关于 timestamp
|
|
152
113
|
|
|
153
|
-
|
|
114
|
+
- **类型要求**:必须是数字类型(number/int),不能是字符串
|
|
115
|
+
- **如何获取**:直接使用上下文中给出的 timestamp 值
|
|
116
|
+
- **较小或相等**:不生效,返回现有数据
|
|
117
|
+
- **较大**:删除旧服务器并重新创建
|
|
118
|
+
- **何时更新**:改动角色或世界字段设计(新增/删除/改变类型)时,需使用更大的 timestamp 重新初始化
|
|
154
119
|
|
|
155
|
-
|
|
120
|
+
## 完整示例
|
|
156
121
|
|
|
157
122
|
```javascript
|
|
158
123
|
import { RiffleServer } from '@lzpenguin/server';
|
|
159
124
|
|
|
160
125
|
const server = new RiffleServer({
|
|
161
126
|
url: 'wss://api.riffle.app',
|
|
162
|
-
|
|
163
|
-
token: 'your-token-here'
|
|
127
|
+
timestamp: 1234567890
|
|
164
128
|
});
|
|
165
129
|
|
|
166
|
-
//
|
|
130
|
+
// 监听推送
|
|
167
131
|
server.onData((data) => {
|
|
168
|
-
// 更新自己的位置(self 现在直接是公开数据对象)
|
|
169
132
|
const myPosition = { x: data.self.x, y: data.self.y };
|
|
170
|
-
|
|
171
|
-
// 更新其他玩家列表
|
|
172
133
|
const otherPlayers = data.players || [];
|
|
173
|
-
|
|
174
|
-
// 渲染游戏画面
|
|
175
134
|
renderPlayers(myPosition, otherPlayers);
|
|
176
135
|
|
|
177
|
-
// 检查游戏状态
|
|
178
136
|
if (data.world?.gameOver) {
|
|
179
137
|
console.log('游戏结束!分数:', data.world.score);
|
|
180
138
|
}
|
|
181
139
|
});
|
|
182
140
|
|
|
183
|
-
//
|
|
141
|
+
// 初始化
|
|
184
142
|
server.init({
|
|
185
|
-
hash: 'v1.0.0',
|
|
186
143
|
world: { score: 0, level: 1 },
|
|
187
|
-
self: {
|
|
188
|
-
public: { name: 'Player1', x: 0, y: 0 }
|
|
189
|
-
}
|
|
144
|
+
self: { public: { name: 'Player1', x: 0, y: 0 } }
|
|
190
145
|
});
|
|
191
146
|
|
|
192
147
|
// 更新玩家位置
|
|
193
148
|
function movePlayer(x, y) {
|
|
194
|
-
server.update({
|
|
195
|
-
self: {
|
|
196
|
-
public: { x, y }
|
|
197
|
-
}
|
|
198
|
-
});
|
|
149
|
+
server.update({ self: { public: { x, y } } });
|
|
199
150
|
}
|
|
200
151
|
|
|
201
152
|
// 更新分数
|
|
202
153
|
function updateScore(score) {
|
|
203
|
-
server.update({
|
|
204
|
-
world: { score }
|
|
205
|
-
});
|
|
154
|
+
server.update({ world: { score } });
|
|
206
155
|
}
|
|
207
156
|
```
|
|
208
157
|
|
package/index.d.ts
CHANGED
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
export interface RiffleServerOptions {
|
|
9
9
|
/** WebSocket 服务器 URL */
|
|
10
10
|
url: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
/** 认证 token */
|
|
14
|
-
token: string;
|
|
11
|
+
/** 游戏时间戳(用于判断是否需要重新初始化,新 timestamp 必须大于旧的才会重置) */
|
|
12
|
+
timestamp: number;
|
|
15
13
|
/** 是否自动重连,默认 true */
|
|
16
14
|
autoReconnect?: boolean;
|
|
17
15
|
/** 重连间隔(毫秒),默认 3000 */
|
|
@@ -33,8 +31,6 @@ export interface PlayerSelfData {
|
|
|
33
31
|
* 初始化数据
|
|
34
32
|
*/
|
|
35
33
|
export interface InitData {
|
|
36
|
-
/** 游戏版本哈希值(必需) */
|
|
37
|
-
hash: string;
|
|
38
34
|
/** 世界初始数据(可选) */
|
|
39
35
|
world?: Record<string, any>;
|
|
40
36
|
/** 玩家初始数据(可选) */
|
package/index.js
CHANGED
|
@@ -37,8 +37,9 @@ if (typeof window !== 'undefined' && window.WebSocket) {
|
|
|
37
37
|
*
|
|
38
38
|
* const server = new RiffleServer({
|
|
39
39
|
* url: 'wss://api.riffle.app',
|
|
40
|
-
*
|
|
41
|
-
* token
|
|
40
|
+
* timestamp: Date.now()
|
|
41
|
+
* // post_id 和 token 会自动从浏览器 URL 参数中读取
|
|
42
|
+
* // 例如:https://example.com/game.html?post_id=123456&token=xxx
|
|
42
43
|
* });
|
|
43
44
|
*
|
|
44
45
|
* // 监听服务器推送的最新数据
|
|
@@ -69,28 +70,46 @@ if (typeof window !== 'undefined' && window.WebSocket) {
|
|
|
69
70
|
export class RiffleServer {
|
|
70
71
|
/**
|
|
71
72
|
* @param {Object} options - 配置选项
|
|
72
|
-
* @param {string} options.url - WebSocket 服务器 URL(例如:'wss://api.riffle.app'
|
|
73
|
-
* @param {number} options.
|
|
74
|
-
* @param {string} options.token - 认证 token
|
|
73
|
+
* @param {string} options.url - WebSocket 服务器 URL(例如:'wss://api.riffle.app')
|
|
74
|
+
* @param {number} options.timestamp - 游戏时间戳(用于判断是否需要重新初始化,新 timestamp 必须大于旧的才会重置)
|
|
75
75
|
* @param {boolean} [options.autoReconnect=true] - 是否自动重连
|
|
76
76
|
* @param {number} [options.reconnectInterval=3000] - 重连间隔(毫秒)
|
|
77
77
|
*/
|
|
78
78
|
constructor(options) {
|
|
79
|
-
const { url,
|
|
79
|
+
const { url, timestamp, autoReconnect = true, reconnectInterval = 3000 } = options;
|
|
80
80
|
|
|
81
81
|
if (!url) {
|
|
82
82
|
throw new Error('URL is required');
|
|
83
83
|
}
|
|
84
|
+
if (!timestamp) {
|
|
85
|
+
throw new Error('timestamp is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 从浏览器 URL 参数中读取 post_id 和 token
|
|
89
|
+
let postId;
|
|
90
|
+
let token;
|
|
91
|
+
|
|
92
|
+
if (isBrowser && typeof window !== 'undefined' && window.location) {
|
|
93
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
94
|
+
if (urlParams.has('post_id')) {
|
|
95
|
+
postId = parseInt(urlParams.get('post_id'), 10);
|
|
96
|
+
}
|
|
97
|
+
if (urlParams.has('token')) {
|
|
98
|
+
token = urlParams.get('token');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
84
102
|
if (!postId) {
|
|
85
|
-
throw new Error('
|
|
103
|
+
throw new Error('post_id not found in browser URL parameters. Please ensure URL contains ?post_id=xxx');
|
|
86
104
|
}
|
|
87
105
|
if (!token) {
|
|
88
|
-
throw new Error('token
|
|
106
|
+
throw new Error('token not found in browser URL parameters. Please ensure URL contains &token=xxx');
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
this.url = url;
|
|
92
110
|
this.postId = postId;
|
|
93
111
|
this.token = token;
|
|
112
|
+
this.timestamp = timestamp;
|
|
94
113
|
this.autoReconnect = autoReconnect;
|
|
95
114
|
this.reconnectInterval = reconnectInterval;
|
|
96
115
|
|
|
@@ -264,7 +283,6 @@ export class RiffleServer {
|
|
|
264
283
|
/**
|
|
265
284
|
* 初始化服务器(必需,连接后必须先调用)
|
|
266
285
|
* @param {Object} initData - 初始化数据
|
|
267
|
-
* @param {string} initData.hash - 游戏版本哈希值(必需)
|
|
268
286
|
* @param {Object} [initData.world] - 世界初始数据(可选)
|
|
269
287
|
* @param {Object} [initData.self] - 玩家初始数据(可选)
|
|
270
288
|
* @param {Object} [initData.self.public] - 公开数据(可选,旧格式)
|
|
@@ -272,7 +290,6 @@ export class RiffleServer {
|
|
|
272
290
|
* @example
|
|
273
291
|
* // 推荐:直接传递公开数据
|
|
274
292
|
* server.init({
|
|
275
|
-
* hash: 'game-version-hash-123',
|
|
276
293
|
* world: { score: 0, level: 1 },
|
|
277
294
|
* self: {
|
|
278
295
|
* public: { name: 'Player1', x: 100, y: 100 }
|
|
@@ -282,19 +299,13 @@ export class RiffleServer {
|
|
|
282
299
|
* @example
|
|
283
300
|
* // 也支持直接传递对象(服务端会将其作为 public 数据处理)
|
|
284
301
|
* server.init({
|
|
285
|
-
* hash: 'game-version-hash-123',
|
|
286
302
|
* world: { score: 0 },
|
|
287
303
|
* self: {
|
|
288
304
|
* public: { name: 'Player1' }
|
|
289
305
|
* }
|
|
290
306
|
* });
|
|
291
307
|
*/
|
|
292
|
-
init(initData) {
|
|
293
|
-
if (!initData || !initData.hash) {
|
|
294
|
-
console.error('[RiffleServer] hash is required for init');
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
308
|
+
init(initData = {}) {
|
|
298
309
|
// 如果还未连接,保存 init 请求,连接建立后自动执行
|
|
299
310
|
if (!this.isConnected || this.ws?.readyState !== WebSocketImpl.OPEN) {
|
|
300
311
|
console.log('[RiffleServer] Not connected yet, will init after connection');
|
|
@@ -314,7 +325,7 @@ export class RiffleServer {
|
|
|
314
325
|
_doInit(initData) {
|
|
315
326
|
try {
|
|
316
327
|
const initMessage = {
|
|
317
|
-
|
|
328
|
+
timestamp: this.timestamp,
|
|
318
329
|
world: initData.world || undefined,
|
|
319
330
|
self: initData.self || undefined
|
|
320
331
|
};
|