fix(platform): recognize session faults across realms

This commit is contained in:
2026-09-05 06:49:59 +08:00
parent 224df1c21c
commit 77b2bff40a
4 changed files with 181 additions and 3 deletions
@@ -1,7 +1,7 @@
import type { GameHost } from '../sdk/contracts/index.ts';
import {
GameSessionHost,
GameSessionOpenFault,
isGameSessionOpenFault,
} from '../sdk/runtime/game-session-host.ts';
import {
parseLoginResponse,
@@ -170,7 +170,7 @@ export class RuntimeSession {
});
if (reconnect.present) this.options.gameSession.restore(reconnect.value);
} catch (error) {
const errors = error instanceof GameSessionOpenFault ? [...error.errors] : [error];
const errors = isGameSessionOpenFault(error) ? [...error.errors] : [error];
this.throwFault(operation, [...errors, ...this.releaseGame()]);
}
}
@@ -7,6 +7,9 @@ import type {
} from '../contracts/index.ts';
const RESERVED_ROUTES = new Set(['platform', 'agent', 'room']);
const GAME_SESSION_OPEN_FAULT_BRAND = Symbol.for(
'youle.framework.sdk.GameSessionOpenFault',
);
export type GameSessionState =
| 'idle'
@@ -22,10 +25,33 @@ export class GameSessionOpenFault extends Error {
constructor(primary: unknown, cleanup: unknown) {
super('Game session open failed and module cleanup also failed');
this.name = 'GameSessionOpenFault';
Object.defineProperty(this, GAME_SESSION_OPEN_FAULT_BRAND, { value: true });
this.errors = Object.freeze([primary, cleanup]);
}
}
export interface GameSessionOpenFaultShape {
readonly errors: readonly [unknown, unknown];
}
/** Recognizes open faults across realms and duplicate module instances. */
export function isGameSessionOpenFault(value: unknown): value is GameSessionOpenFaultShape {
if (value === null || typeof value !== 'object') return false;
try {
const brand = ownDataDescriptor(value, GAME_SESSION_OPEN_FAULT_BRAND);
const errors = ownDataDescriptor(value, 'errors');
if (brand?.value !== true || !errors || !Array.isArray(errors.value)) return false;
const errorList = errors.value;
return ownDataDescriptor(errorList, 'length')?.value === 2
&& ownDataDescriptor(errorList, '0') !== undefined
&& ownDataDescriptor(errorList, '1') !== undefined;
} catch {
return false;
}
}
/** Validates the compile-time game identity before it reaches the runtime. */
export function assertGameEntry(entry: GameEntry): void {
assertNonEmptyText(entry.key, 'key');
@@ -195,3 +221,10 @@ function disposeModuleIfPresent(module: unknown): void {
const dispose = (module as Record<string, unknown>).dispose;
if (typeof dispose === 'function') dispose.call(module);
}
function ownDataDescriptor(target: object, key: PropertyKey): PropertyDescriptor | undefined {
const descriptor = Object.getOwnPropertyDescriptor(target, key);
return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
? descriptor
: undefined;
}