@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 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 && socket.readyState === 1)
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 = sid ? `${roomPath.includes("?") ? "&" : "?"}wovoon_play_sid=${sid}` : "";
154
- socket = new WebSocket(`${scheme}://${origin.replace(/^https?:\/\//, "")}${roomPath}${query}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wovoon/polaris",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "wovoon Polaris SDK——wovoon 大后台云能力客户端(身份/数据集合/云函数/房间实时),协议 1",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",