fix(platform): preserve session failures and URL syntax
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import type { GameHost } from '../sdk/contracts/index.ts';
|
||||
import { GameSessionHost } from '../sdk/runtime/game-session-host.ts';
|
||||
import {
|
||||
GameSessionHost,
|
||||
GameSessionOpenFault,
|
||||
} from '../sdk/runtime/game-session-host.ts';
|
||||
import {
|
||||
parseLoginResponse,
|
||||
parseSelfJoinRoomResponse,
|
||||
@@ -167,7 +170,8 @@ export class RuntimeSession {
|
||||
});
|
||||
if (reconnect.present) this.options.gameSession.restore(reconnect.value);
|
||||
} catch (error) {
|
||||
this.throwFault(operation, [error, ...this.releaseGame()]);
|
||||
const errors = error instanceof GameSessionOpenFault ? [...error.errors] : [error];
|
||||
this.throwFault(operation, [...errors, ...this.releaseGame()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,9 @@ function parseSwitchTarget(address: string, path: string): string {
|
||||
if (authority === undefined || authority.length === 0) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address with a non-empty host`);
|
||||
}
|
||||
if (target.includes('#') || authority.includes('@')) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address without fragment or userinfo`);
|
||||
}
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
|
||||
@@ -16,6 +16,16 @@ export type GameSessionState =
|
||||
| 'disposing'
|
||||
| 'disposed';
|
||||
|
||||
export class GameSessionOpenFault extends Error {
|
||||
readonly errors: readonly unknown[];
|
||||
|
||||
constructor(primary: unknown, cleanup: unknown) {
|
||||
super('Game session open failed and module cleanup also failed');
|
||||
this.name = 'GameSessionOpenFault';
|
||||
this.errors = Object.freeze([primary, cleanup]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Validates the compile-time game identity before it reaches the runtime. */
|
||||
export function assertGameEntry(entry: GameEntry): void {
|
||||
assertNonEmptyText(entry.key, 'key');
|
||||
@@ -38,7 +48,7 @@ export function assertGameEntry(entry: GameEntry): void {
|
||||
try {
|
||||
assertGameModule(module);
|
||||
} finally {
|
||||
disposeValidationModule(module);
|
||||
disposeModuleIfPresent(module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +81,20 @@ export class GameSessionHost {
|
||||
this.module = module;
|
||||
module.attach(host);
|
||||
this.currentState = 'active';
|
||||
} catch (error) {
|
||||
} catch (primaryError) {
|
||||
this.module = undefined;
|
||||
let cleanupFailed = false;
|
||||
let cleanupError: unknown;
|
||||
try {
|
||||
module?.dispose();
|
||||
disposeModuleIfPresent(module);
|
||||
} catch (error) {
|
||||
cleanupFailed = true;
|
||||
cleanupError = error;
|
||||
} finally {
|
||||
this.currentState = 'idle';
|
||||
}
|
||||
throw error;
|
||||
if (cleanupFailed) throw new GameSessionOpenFault(primaryError, cleanupError);
|
||||
throw primaryError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +190,7 @@ function assertGameModule(module: unknown): asserts module is GameModule {
|
||||
}
|
||||
}
|
||||
|
||||
function disposeValidationModule(module: unknown): void {
|
||||
function disposeModuleIfPresent(module: unknown): void {
|
||||
if (!module || typeof module !== 'object') return;
|
||||
const dispose = (module as Record<string, unknown>).dispose;
|
||||
if (typeof dispose === 'function') dispose.call(module);
|
||||
|
||||
Reference in New Issue
Block a user