diff --git a/package-lock.json b/package-lock.json index b43b1c6..52e957f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "colyseus.js", - "version": "0.16.14", + "version": "0.16.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "colyseus.js", - "version": "0.16.14", + "version": "0.16.15", "license": "MIT", "dependencies": { "@colyseus/msgpackr": "^1.10.5", diff --git a/src/Client.ts b/src/Client.ts index 94b7d9e..697a10a 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,10 +1,10 @@ +import { discordURLBuilder } from './3rd_party/discord'; +import { Auth } from './Auth'; import { ServerError } from './errors/ServerError'; -import { Room } from './Room'; -import { SchemaConstructor } from './serializer/SchemaSerializer'; import { HTTP } from "./HTTP"; -import { Auth } from './Auth'; import { SeatReservation } from './Protocol'; -import { discordURLBuilder } from './3rd_party/discord'; +import { Room, splitURL } from './Room'; +import { SchemaConstructor } from './serializer/SchemaSerializer'; export type JoinOptions = any; @@ -33,7 +33,7 @@ export interface EndpointSettings { export interface ClientOptions { headers?: { [id: string]: string }; - urlBuilder?: (url: URL) => string; + urlBuilder?: (url: URL | ReturnType) => string; } export class Client { @@ -43,7 +43,7 @@ export class Client { public auth: Auth; protected settings: EndpointSettings; - protected urlBuilder: (url: URL) => string; + protected urlBuilder: (url: URL | ReturnType) => string; constructor( settings: string | EndpointSettings = DEFAULT_ENDPOINT, @@ -55,8 +55,8 @@ export class Client { // endpoint by url // const url = (settings.startsWith("/")) - ? new URL(settings, DEFAULT_ENDPOINT) - : new URL(settings); + ? splitURL(settings, DEFAULT_ENDPOINT) + : splitURL(settings); const secure = (url.protocol === "https:" || url.protocol === "wss:"); const port = Number(url.port || (secure ? 443 : 80)); @@ -254,7 +254,7 @@ export class Client { const endpointURL = `${endpoint}/${room.processId}/${room.roomId}?${params.join('&')}`; return (this.urlBuilder) - ? this.urlBuilder(new URL(endpointURL)) + ? this.urlBuilder(splitURL(endpointURL)) : endpointURL; } @@ -262,7 +262,7 @@ export class Client { const path = segments.startsWith("/") ? segments : `/${segments}`; const endpointURL = `${(this.settings.secure) ? "https" : "http"}://${this.settings.hostname}${this.getEndpointPort()}${this.settings.pathname}${path}`; return (this.urlBuilder) - ? this.urlBuilder(new URL(endpointURL)) + ? this.urlBuilder(splitURL(endpointURL)) : endpointURL; } diff --git a/src/Room.ts b/src/Room.ts index b532f95..3644cce 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -8,11 +8,57 @@ import { createNanoEvents } from './core/nanoevents'; import { createSignal } from './core/signal'; import { decode, encode, Iterator } from '@colyseus/schema'; -import { SchemaConstructor, SchemaSerializer } from './serializer/SchemaSerializer'; import { CloseCode } from './errors/ServerError'; +import { SchemaConstructor, SchemaSerializer } from './serializer/SchemaSerializer'; import { Packr, unpack } from '@colyseus/msgpackr'; +export function splitURL(url: string, base?: string) { + // 检查 URL 是否为空或非字符串 + if (!url || typeof url !== 'string') { + throw new Error("URL must be a non-empty string"); + } + + // 尝试使用全局 URL 构造函数 + if (typeof globalThis !== 'undefined' && globalThis.URL) { + try { + return base ? new URL(url, base) : new URL(url); + } catch (e) { + // URL 构造函数可能会因为无效 URL 抛出错误 + // 继续使用备选方案 + } + } + + // 如果有基础 URL 且 url 是相对路径,先进行合并 + if (base && url.startsWith('/')) { + // 简单合并,可能需要更复杂的逻辑来处理各种情况 + let baseUrl = base; + if (baseUrl.endsWith('/')) { + baseUrl = baseUrl.slice(0, -1); + } + url = baseUrl + url; + } + + // 备选方案:使用正则表达式解析 URL + var urlPattern = /^(?:([A-Za-z]+):)?(?:\/\/)?(?:([0-9.\-A-Za-z]+)(?::(\d+))?)?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/; + var matches = url.match(urlPattern); + if (!matches) { + throw new Error("Invalid URL"); + } + + return { + protocol: matches[1] ? matches[1] + ":" : "", + hostname: matches[2] || "", + port: matches[3] || "", + pathname: matches[4] || "/", + search: matches[5] ? "?" + matches[5] : "", + hash: matches[6] ? "#" + matches[6] : "", + href: url, + origin: (matches[1] ? matches[1] + "://" : "//") + (matches[2] || "") + (matches[3] ? ":" + matches[3] : "") + }; +} + + export interface RoomAvailable { name: string; roomId: string; @@ -21,7 +67,7 @@ export interface RoomAvailable { metadata?: Metadata; } -export class Room { +export class Room { public roomId: string; public sessionId: string; public reconnectionToken: string; @@ -97,7 +143,7 @@ export class Room { // FIXME: refactor this. if (options.protocol === "h3") { - const url = new URL(endpoint); + const url = splitURL(endpoint); connection.connect(url.origin, options); } else { @@ -135,7 +181,7 @@ export class Room { const it: Iterator = { offset: 1 }; this.packr.buffer[0] = Protocol.ROOM_DATA; - if (typeof(type) === "string") { + if (typeof (type) === "string") { encode.string(this.packr.buffer, type, it); } else { @@ -156,7 +202,7 @@ export class Room { const it: Iterator = { offset: 1 }; this.packr.buffer[0] = Protocol.ROOM_DATA; - if (typeof(type) === "string") { + if (typeof (type) === "string") { encode.string(this.packr.buffer, type, it); } else { @@ -177,7 +223,7 @@ export class Room { const it: Iterator = { offset: 1 }; this.packr.buffer[0] = Protocol.ROOM_DATA_BYTES; - if (typeof(type) === "string") { + if (typeof (type) === "string") { encode.string(this.packr.buffer, type, it); } else { @@ -196,7 +242,7 @@ export class Room { this.connection.send(this.packr.buffer.subarray(0, it.offset + bytes.byteLength)); } - public get state (): State { + public get state(): State { return this.serializer.getState(); } @@ -293,14 +339,14 @@ export class Room { } } - private destroy () { + private destroy() { if (this.serializer) { this.serializer.teardown(); } } private getMessageHandlerKey(type: string | number): string { - switch (typeof(type)) { + switch (typeof (type)) { // string case "string": return type; diff --git a/src/transport/WebSocketTransport.ts b/src/transport/WebSocketTransport.ts index 82f8069..e25d5da 100644 --- a/src/transport/WebSocketTransport.ts +++ b/src/transport/WebSocketTransport.ts @@ -7,9 +7,21 @@ export class WebSocketTransport implements ITransport { ws: WebSocket | NodeWebSocket; protocols?: string | string[]; - constructor(public events: ITransportEventMap) {} + constructor(public events: ITransportEventMap) { } public send(data: Buffer | Uint8Array): void { + // 微信小程序不支持直接发送Uint8Array + // WeChat Mini Program does not support sending Uint8Array directly + if (globalThis?.wx) { + if (data instanceof Uint8Array) { + this.ws.send(data.slice().buffer); + } else if (Array.isArray(data)){ + this.ws.send((new Uint8Array(data)).buffer); + } else { + this.ws.send(data); + } + return; + } this.ws.send(data); } @@ -23,6 +35,8 @@ export class WebSocketTransport implements ITransport { */ public connect(url: string, headers?: any): void { try { + if (globalThis?.wx) throw new Error("WeChat Mini Program does not support custom headers"); + // Node or Bun environments (supports custom headers) this.ws = new WebSocket(url, { headers, protocols: this.protocols });