Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions src/Client.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -33,7 +33,7 @@ export interface EndpointSettings {

export interface ClientOptions {
headers?: { [id: string]: string };
urlBuilder?: (url: URL) => string;
urlBuilder?: (url: URL | ReturnType<typeof splitURL>) => string;
}

export class Client {
Expand All @@ -43,7 +43,7 @@ export class Client {
public auth: Auth;

protected settings: EndpointSettings;
protected urlBuilder: (url: URL) => string;
protected urlBuilder: (url: URL | ReturnType<typeof splitURL>) => string;

constructor(
settings: string | EndpointSettings = DEFAULT_ENDPOINT,
Expand All @@ -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));
Expand Down Expand Up @@ -254,15 +254,15 @@ 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;
}

protected getHttpEndpoint(segments: string = '') {
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;
}

Expand Down
64 changes: 55 additions & 9 deletions src/Room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Metadata = any> {
name: string;
roomId: string;
Expand All @@ -21,7 +67,7 @@ export interface RoomAvailable<Metadata = any> {
metadata?: Metadata;
}

export class Room<State= any> {
export class Room<State = any> {
public roomId: string;
public sessionId: string;
public reconnectionToken: string;
Expand Down Expand Up @@ -97,7 +143,7 @@ export class Room<State= any> {

// FIXME: refactor this.
if (options.protocol === "h3") {
const url = new URL(endpoint);
const url = splitURL(endpoint);
connection.connect(url.origin, options);

} else {
Expand Down Expand Up @@ -135,7 +181,7 @@ export class Room<State= any> {
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 {
Expand All @@ -156,7 +202,7 @@ export class Room<State= any> {
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 {
Expand All @@ -177,7 +223,7 @@ export class Room<State= any> {
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 {
Expand All @@ -196,7 +242,7 @@ export class Room<State= any> {
this.connection.send(this.packr.buffer.subarray(0, it.offset + bytes.byteLength));
}

public get state (): State {
public get state(): State {
return this.serializer.getState();
}

Expand Down Expand Up @@ -293,14 +339,14 @@ export class Room<State= any> {
}
}

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;

Expand Down
16 changes: 15 additions & 1 deletion src/transport/WebSocketTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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 });

Expand Down