diff --git a/src/Client.ts b/src/Client.ts index e4e8668..faa3fa3 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -118,7 +118,16 @@ export class Client { * @param rootSchema (optional) Concrete root schema definition * @returns Promise */ - public async reconnect(reconnectionToken: string, rootSchema?: SchemaConstructor) { + public async reconnect(reconnectionToken: string, rootSchema?: SchemaConstructor) : Promise>; + /** + * Re-establish connection with a room this client was previously connected to. + * + * @param room The previously connected room. + * @returns Promise - The room that was passed in. + */ + public async reconnect(room: Room) : Promise>; + public async reconnect(value: string | Room, rootSchema?: SchemaConstructor) { + const { reconnectionToken, room } = typeof (value) === "object" ? { reconnectionToken: value.reconnectionToken, room: value } : { reconnectionToken: value, room: undefined }; if (typeof (reconnectionToken) === "string" && typeof (rootSchema) === "string") { throw new Error("DEPRECATED: .reconnect() now only accepts 'reconnectionToken' as argument.\nYou can get this token from previously connected `room.reconnectionToken`"); } @@ -126,7 +135,7 @@ export class Client { if (!roomId || !token) { throw new Error("Invalid reconnection token format.\nThe format should be roomId:reconnectionToken"); } - return await this.createMatchMakeRequest('reconnect', roomId, { reconnectionToken: token }, rootSchema); + return await this.createMatchMakeRequest('reconnect', roomId, { reconnectionToken: token }, rootSchema, room); } public async getAvailableRooms(roomName: string = ""): Promise[]> { @@ -142,9 +151,11 @@ export class Client { public async consumeSeatReservation( response: any, rootSchema?: SchemaConstructor, - reuseRoomInstance?: Room // used in devMode + reuseRoomInstance?: Room, // used in devMode and when reconnecting in place + simpleConnect: boolean = false ): Promise> { - const room = this.createRoom(response.room.name, rootSchema); + + const room = reuseRoomInstance ?? this.createRoom(response.room.name, rootSchema); room.roomId = response.room.roomId; room.sessionId = response.sessionId; @@ -156,32 +167,38 @@ export class Client { } const targetRoom = reuseRoomInstance || room; - room.connect(this.buildEndpoint(response.room, options), response.devMode && (async () => { - console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x1F504)} Re-establishing connection with room id '${room.roomId}'...`); // 🔄 - let retryCount = 0; - let retryMaxRetries = 8; + // true if reconnecting in place + if (simpleConnect) { + room.connection.connect(this.buildEndpoint(response.room, options)); + } else { + room.connect(this.buildEndpoint(response.room, options), response.devMode && (async () => { + console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x1F504)} Re-establishing connection with room id '${room.roomId}'...`); // 🔄 - const retryReconnection = async () => { - retryCount++; + let retryCount = 0; + let retryMaxRetries = 8; - try { - await this.consumeSeatReservation(response, rootSchema, targetRoom); - console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x2705)} Successfully re-established connection with room '${room.roomId}'`); // ✅ + const retryReconnection = async () => { + retryCount++; - } catch (e) { - if (retryCount < retryMaxRetries) { - console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x1F504)} retrying... (${retryCount} out of ${retryMaxRetries})`); // 🔄 - setTimeout(retryReconnection, 2000); + try { + await this.consumeSeatReservation(response, rootSchema, targetRoom); + console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x2705)} Successfully re-established connection with room '${room.roomId}'`); // ✅ - } else { - console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x274C)} Failed to reconnect. Is your server running? Please check server logs.`); // ❌ + } catch (e) { + if (retryCount < retryMaxRetries) { + console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x1F504)} retrying... (${retryCount} out of ${retryMaxRetries})`); // 🔄 + setTimeout(retryReconnection, 2000); + + } else { + console.info(`[Colyseus devMode]: ${String.fromCodePoint(0x274C)} Failed to reconnect. Is your server running? Please check server logs.`); // ❌ + } } - } - }; + }; - setTimeout(retryReconnection, 2000); - }), targetRoom); + setTimeout(retryReconnection, 2000); + }), targetRoom); + } return new Promise((resolve, reject) => { const onError = (code, message) => reject(new ServerError(code, message)); @@ -221,7 +238,7 @@ export class Client { response.reconnectionToken = options.reconnectionToken; } - return await this.consumeSeatReservation(response, rootSchema, reuseRoomInstance); + return await this.consumeSeatReservation(response, rootSchema, reuseRoomInstance, !!reuseRoomInstance); } protected createRoom(roomName: string, rootSchema?: SchemaConstructor) { diff --git a/src/Room.ts b/src/Room.ts index 7be05f7..1596e8b 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -32,6 +32,7 @@ export class Room { // Public signals public onStateChange = createSignal<(state: State) => void>(); public onError = createSignal<(code: number, message?: string) => void>(); + public onClose = createSignal<(code: number) => void>(); public onLeave = createSignal<(code: number) => void>(); protected onJoin = createSignal(); @@ -77,6 +78,12 @@ export class Room { room.onError.invoke(e.code, e.reason); return; } + // allow the user to throw an error in onClose to cancel leaving + try { + room.onClose.invoke(e.code); + } catch (error) { + return; + } if (e.code === CloseCode.DEVMODE_RESTART && devModeCloseCallback) { devModeCloseCallback(); } else {