fix: report combined local transport failures once
This commit is contained in:
+19
-12
@@ -14,6 +14,16 @@ interface LocalLoginTransportOptions {
|
||||
record: EvidenceSink;
|
||||
}
|
||||
|
||||
/** ES2020 aggregate: retain originals before notifying a terminal observer once. */
|
||||
class LocalLoginTransportFault extends Error {
|
||||
readonly errors: readonly unknown[];
|
||||
constructor(errors: readonly unknown[]) {
|
||||
super('Local login transport failed with an additional cleanup error');
|
||||
this.name = 'LocalLoginTransportFault';
|
||||
this.errors = Object.freeze([...errors]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Local-only socket boundary; raw evidence precedes protocol decoding. */
|
||||
export class LocalLoginTransport implements Transport {
|
||||
private socket: WebSocket | null = null;
|
||||
@@ -53,8 +63,7 @@ export class LocalLoginTransport implements Transport {
|
||||
try {
|
||||
this.options.record({ kind: 'transport-receive', data: event.data });
|
||||
} catch (error) {
|
||||
this.stopWithFault(error);
|
||||
throw error;
|
||||
throw this.stopWithFault(error);
|
||||
}
|
||||
if (this.socket === socket) this.messageCallback?.(event.data);
|
||||
};
|
||||
@@ -68,8 +77,7 @@ export class LocalLoginTransport implements Transport {
|
||||
try {
|
||||
this.options.record({ kind: 'transport-error', data: event });
|
||||
} catch (error) {
|
||||
this.stopWithFault(error);
|
||||
throw error;
|
||||
throw this.stopWithFault(error);
|
||||
}
|
||||
if (this.socket !== socket || this.generation !== generation) return;
|
||||
// Invalidate before close: browsers may dispatch close after error.
|
||||
@@ -93,8 +101,7 @@ export class LocalLoginTransport implements Transport {
|
||||
socket.send(frame);
|
||||
this.options.record({ kind: 'transport-send', data: frame });
|
||||
} catch (error) {
|
||||
this.stopWithFault(error);
|
||||
throw error;
|
||||
throw this.stopWithFault(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,17 +122,17 @@ export class LocalLoginTransport implements Transport {
|
||||
onMessage(callback: (frame: string) => void): void { this.messageCallback = callback; }
|
||||
onClose(callback: () => void): void { this.closeCallback = callback; }
|
||||
|
||||
private stopWithFault(error: unknown): void {
|
||||
private stopWithFault(error: unknown): unknown {
|
||||
const socket = this.socket;
|
||||
this.socket = null;
|
||||
const cleanupErrors: unknown[] = [];
|
||||
const errors: unknown[] = [error];
|
||||
try {
|
||||
socket?.close();
|
||||
} catch (cleanupError) {
|
||||
cleanupErrors.push(cleanupError);
|
||||
errors.push(cleanupError);
|
||||
}
|
||||
this.options.onFault(error);
|
||||
// Preserve both independent failures, including non-Error thrown values.
|
||||
for (const cleanupError of cleanupErrors) this.options.onFault(cleanupError);
|
||||
const fault = errors.length === 1 ? error : new LocalLoginTransportFault(errors);
|
||||
this.options.onFault(fault);
|
||||
return fault;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user