@wovoon/polaris 0.4.3 → 0.6.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/dist/client.d.ts +32 -1
- package/dist/client.js +114 -4
- package/dist/protocol.js +1 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -70,7 +70,9 @@ export declare class PolarisClient {
|
|
|
70
70
|
* 重连安全:同一客户端对同一房间再次调用 room(roomId) 会自动断开旧连接——
|
|
71
71
|
* 做"重连房间"按钮时直接再调一次 room() 即可,无需(但也不妨)先 leave() 旧的。
|
|
72
72
|
*/
|
|
73
|
-
room(roomId: string
|
|
73
|
+
room(roomId: string, options?: {
|
|
74
|
+
mode?: string;
|
|
75
|
+
}): {
|
|
74
76
|
send: (data: unknown) => void;
|
|
75
77
|
leave: () => void;
|
|
76
78
|
onMessage: (callback: (payload: {
|
|
@@ -80,12 +82,41 @@ export declare class PolarisClient {
|
|
|
80
82
|
onPresence: (callback: (frame: unknown) => void) => void;
|
|
81
83
|
onClose: (callback: (payload: unknown) => void) => void;
|
|
82
84
|
};
|
|
85
|
+
/** 世界总线:全服事件流(权威房间 ctx.worldBroadcast 广播到此),用法同房间。 */
|
|
86
|
+
world(): {
|
|
87
|
+
send: (data: unknown) => void;
|
|
88
|
+
leave: () => void;
|
|
89
|
+
onMessage: (callback: (payload: {
|
|
90
|
+
from: string;
|
|
91
|
+
data: unknown;
|
|
92
|
+
}) => void) => void;
|
|
93
|
+
onPresence: (callback: (frame: unknown) => void) => void;
|
|
94
|
+
onClose: (callback: (payload: unknown) => void) => void;
|
|
95
|
+
};
|
|
96
|
+
/** 内置匹配:凑满 exports.matchSize 人成局 → resolve 目标房间号(再 room(roomId, {mode}) 进入)。 */
|
|
97
|
+
match(mode: string): Promise<string>;
|
|
98
|
+
/** 房间列表(大厅/分线选择)。 */
|
|
99
|
+
rooms(options?: {
|
|
100
|
+
mode?: string;
|
|
101
|
+
}): Promise<Array<{
|
|
102
|
+
roomId: string;
|
|
103
|
+
mode: string;
|
|
104
|
+
players: number;
|
|
105
|
+
world: boolean;
|
|
106
|
+
}>>;
|
|
83
107
|
collection<T = Record<string, unknown>>(name: string): {
|
|
84
108
|
query: (options?: PolarisQueryOptions) => Promise<PolarisDocumentView[]>;
|
|
85
109
|
get: (id: string) => Promise<PolarisDocumentView>;
|
|
86
110
|
add: (data: T) => Promise<PolarisDocumentView>;
|
|
87
111
|
set: (id: string, data: T) => Promise<PolarisDocumentView>;
|
|
88
112
|
remove: (id: string) => Promise<unknown>;
|
|
113
|
+
/** onChange(cb):集合被写入(玩家/云函数/权威房间)时回调 {action,id}——收到后重查。 */
|
|
114
|
+
onChange: (cb: (notice: {
|
|
115
|
+
action: string;
|
|
116
|
+
id: string;
|
|
117
|
+
}) => void) => {
|
|
118
|
+
close: () => void;
|
|
119
|
+
};
|
|
89
120
|
};
|
|
90
121
|
}
|
|
91
122
|
export { parseData };
|
package/dist/client.js
CHANGED
|
@@ -102,7 +102,7 @@ export class PolarisClient {
|
|
|
102
102
|
* 重连安全:同一客户端对同一房间再次调用 room(roomId) 会自动断开旧连接——
|
|
103
103
|
* 做"重连房间"按钮时直接再调一次 room() 即可,无需(但也不妨)先 leave() 旧的。
|
|
104
104
|
*/
|
|
105
|
-
room(roomId) {
|
|
105
|
+
room(roomId, options) {
|
|
106
106
|
if (!roomId)
|
|
107
107
|
throw new PolarisError("room(roomId) 需要房间号", "WOVOON_ARGUMENT");
|
|
108
108
|
this.activeRooms.get(roomId)?.leave();
|
|
@@ -121,7 +121,13 @@ export class PolarisClient {
|
|
|
121
121
|
};
|
|
122
122
|
const handle = {
|
|
123
123
|
send: (data) => {
|
|
124
|
-
if (socket
|
|
124
|
+
if (!socket || socket.readyState !== 1)
|
|
125
|
+
return;
|
|
126
|
+
if (data instanceof ArrayBuffer)
|
|
127
|
+
socket.send(data);
|
|
128
|
+
else if (data instanceof Uint8Array)
|
|
129
|
+
socket.send(data.buffer);
|
|
130
|
+
else
|
|
125
131
|
socket.send(JSON.stringify({ type: "msg", data }));
|
|
126
132
|
},
|
|
127
133
|
leave: () => {
|
|
@@ -150,10 +156,19 @@ export class PolarisClient {
|
|
|
150
156
|
const scheme = origin.startsWith("https") ? "wss" : "ws";
|
|
151
157
|
const roomPath = `/api/cloud/rooms/${data.appId}/${encodeURIComponent(roomId)}`;
|
|
152
158
|
const sid = /[?&#]wovoon_play_sid=([0-9a-fA-F-]{36})(?:&|$)/.exec(String(location.hash ?? ""))?.[1];
|
|
153
|
-
const query =
|
|
154
|
-
|
|
159
|
+
const query = [];
|
|
160
|
+
if (sid)
|
|
161
|
+
query.push(`wovoon_play_sid=${sid}`);
|
|
162
|
+
if (options?.mode)
|
|
163
|
+
query.push(`mode=${encodeURIComponent(options.mode)}`);
|
|
164
|
+
socket = new WebSocket(`${scheme}://${origin.replace(/^https?:\/\//, "")}${roomPath}${query.length ? "?" + query.join("&") : ""}`);
|
|
165
|
+
socket.binaryType = "arraybuffer";
|
|
155
166
|
socket.onopen = () => { this.activeRooms.set(roomId, handle); };
|
|
156
167
|
socket.onmessage = (event) => {
|
|
168
|
+
if (event.data instanceof ArrayBuffer) {
|
|
169
|
+
emit("message", { binary: true, data: new Uint8Array(event.data) });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
157
172
|
let frame = null;
|
|
158
173
|
try {
|
|
159
174
|
frame = JSON.parse(String(event.data));
|
|
@@ -169,6 +184,8 @@ export class PolarisClient {
|
|
|
169
184
|
emit("presence", frame);
|
|
170
185
|
else if (frame.type === "error")
|
|
171
186
|
emit("message", { error: frame.message });
|
|
187
|
+
else if (frame.type === "matched")
|
|
188
|
+
emit("message", frame);
|
|
172
189
|
};
|
|
173
190
|
socket.onclose = () => {
|
|
174
191
|
if (this.activeRooms.get(roomId) === handle)
|
|
@@ -183,6 +200,48 @@ export class PolarisClient {
|
|
|
183
200
|
}).catch((error) => emit("close", { error: error.message }));
|
|
184
201
|
return handle;
|
|
185
202
|
}
|
|
203
|
+
/** 世界总线:全服事件流(权威房间 ctx.worldBroadcast 广播到此),用法同房间。 */
|
|
204
|
+
world() {
|
|
205
|
+
return this.room("__world");
|
|
206
|
+
}
|
|
207
|
+
/** 内置匹配:凑满 exports.matchSize 人成局 → resolve 目标房间号(再 room(roomId, {mode}) 进入)。 */
|
|
208
|
+
match(mode) {
|
|
209
|
+
if (!mode)
|
|
210
|
+
throw new PolarisError("match(mode) 需要模式名", "WOVOON_ARGUMENT");
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
this.session().then(() => {
|
|
213
|
+
const queue = this.room(`__match_${mode}`);
|
|
214
|
+
let done = false;
|
|
215
|
+
const timer = setTimeout(() => {
|
|
216
|
+
if (done)
|
|
217
|
+
return;
|
|
218
|
+
done = true;
|
|
219
|
+
try {
|
|
220
|
+
queue.leave();
|
|
221
|
+
}
|
|
222
|
+
catch { /* 已关 */ }
|
|
223
|
+
reject(new PolarisError("匹配等待超时(120 秒)", "WOVOON_MATCH_TIMEOUT"));
|
|
224
|
+
}, 120000);
|
|
225
|
+
queue.onMessage((payload) => {
|
|
226
|
+
const frame = payload;
|
|
227
|
+
if (done || frame?.type !== "matched" || !frame.roomId)
|
|
228
|
+
return;
|
|
229
|
+
done = true;
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
try {
|
|
232
|
+
queue.leave();
|
|
233
|
+
}
|
|
234
|
+
catch { /* 已关 */ }
|
|
235
|
+
resolve(frame.roomId);
|
|
236
|
+
});
|
|
237
|
+
}).catch(reject);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/** 房间列表(大厅/分线选择)。 */
|
|
241
|
+
rooms(options) {
|
|
242
|
+
const query = options?.mode ? `?mode=${encodeURIComponent(options.mode)}` : "";
|
|
243
|
+
return this.session().then((data) => this.request("GET", `/api/cloud/${data.appId}/rooms${query}`));
|
|
244
|
+
}
|
|
186
245
|
collection(name) {
|
|
187
246
|
if (!name)
|
|
188
247
|
throw new PolarisError("collection(name) 需要集合名", "WOVOON_ARGUMENT");
|
|
@@ -207,7 +266,58 @@ export class PolarisClient {
|
|
|
207
266
|
add: (data) => docUrl().then((url) => client.request("POST", url, { data: JSON.stringify(data ?? {}) })),
|
|
208
267
|
set: (id, data) => docUrl(id).then((url) => client.request("PUT", url, { data: JSON.stringify(data ?? {}) })),
|
|
209
268
|
remove: (id) => docUrl(id).then((url) => client.request("DELETE", url)),
|
|
269
|
+
/** onChange(cb):集合被写入(玩家/云函数/权威房间)时回调 {action,id}——收到后重查。 */
|
|
270
|
+
onChange: (cb) => {
|
|
271
|
+
return watchCollections(client, name, cb);
|
|
272
|
+
},
|
|
210
273
|
};
|
|
211
274
|
}
|
|
212
275
|
}
|
|
276
|
+
const collectionsWatch = { listeners: [], socket: null };
|
|
277
|
+
function attachCollectionsWatch(client) {
|
|
278
|
+
if (collectionsWatch.socket || typeof WebSocket === "undefined")
|
|
279
|
+
return;
|
|
280
|
+
client.session().then((data) => {
|
|
281
|
+
const wsBase = String(globalThis.__wovoonRuntimeWsBase ?? "").replace(/\/+$/, "");
|
|
282
|
+
const origin = wsBase || `${location.protocol}//${location.host}`;
|
|
283
|
+
const scheme = origin.startsWith("https") ? "wss" : "ws";
|
|
284
|
+
const sid = /[?&#]wovoon_play_sid=([0-9a-fA-F-]{36})(?:&|$)/.exec(String(location.hash ?? ""))?.[1];
|
|
285
|
+
const query = sid ? `?wovoon_play_sid=${sid}` : "";
|
|
286
|
+
const socket = new WebSocket(`${scheme}://${origin.replace(/^https?:\/\//, "")}/api/cloud/rooms/${data.appId}/__collections${query}`);
|
|
287
|
+
collectionsWatch.socket = socket;
|
|
288
|
+
socket.onmessage = (event) => {
|
|
289
|
+
try {
|
|
290
|
+
const frame = JSON.parse(String(event.data));
|
|
291
|
+
if (frame?.type !== "docs")
|
|
292
|
+
return;
|
|
293
|
+
for (const listener of collectionsWatch.listeners) {
|
|
294
|
+
if (listener.collection === frame.collection && frame.action && frame.id) {
|
|
295
|
+
try {
|
|
296
|
+
listener.cb({ action: frame.action, id: frame.id });
|
|
297
|
+
}
|
|
298
|
+
catch { /* 监听器异常不中断 */ }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch { /* 非 JSON 帧忽略 */ }
|
|
303
|
+
};
|
|
304
|
+
socket.onclose = () => {
|
|
305
|
+
collectionsWatch.socket = null;
|
|
306
|
+
if (collectionsWatch.listeners.length)
|
|
307
|
+
setTimeout(() => attachCollectionsWatch(client), 2000);
|
|
308
|
+
};
|
|
309
|
+
}).catch(() => { });
|
|
310
|
+
}
|
|
311
|
+
function watchCollections(client, collection, cb) {
|
|
312
|
+
const listener = { collection, cb };
|
|
313
|
+
collectionsWatch.listeners.push(listener);
|
|
314
|
+
attachCollectionsWatch(client);
|
|
315
|
+
return {
|
|
316
|
+
close: () => {
|
|
317
|
+
const index = collectionsWatch.listeners.indexOf(listener);
|
|
318
|
+
if (index >= 0)
|
|
319
|
+
collectionsWatch.listeners.splice(index, 1);
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
213
323
|
export { parseData };
|
package/dist/protocol.js
CHANGED
|
@@ -9,7 +9,7 @@ export function isValidCollectionName(name) {
|
|
|
9
9
|
return COLLECTION_NAME_PATTERN.test(name);
|
|
10
10
|
}
|
|
11
11
|
/** manifest 里单个能力名的完整合法形态(与平台 AssetSecurityScanner.parseContract 的校验一致)。 */
|
|
12
|
-
export const CAPABILITY_PATTERN = /^(identity|function|room|collection:[a-z][a-z0-9_]{1,31})$/;
|
|
12
|
+
export const CAPABILITY_PATTERN = /^(identity|function|room|room:competitive|gamelogic|collection:[a-z][a-z0-9_]{1,31})$/;
|
|
13
13
|
export function isValidCapability(value) {
|
|
14
14
|
return CAPABILITY_PATTERN.test(value);
|
|
15
15
|
}
|