@livepeer-frameworks/player-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/player.css +182 -42
- package/package.json +1 -1
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +49 -46
- package/src/core/Disposable.ts +4 -4
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +41 -39
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -15
- package/src/core/MetaTrackManager.ts +73 -65
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +527 -384
- package/src/core/PlayerInterface.ts +83 -59
- package/src/core/PlayerManager.ts +79 -133
- package/src/core/PlayerRegistry.ts +59 -42
- package/src/core/QualityMonitor.ts +38 -31
- package/src/core/ScreenWakeLockManager.ts +8 -9
- package/src/core/SeekingUtils.ts +31 -22
- package/src/core/StreamStateClient.ts +74 -68
- package/src/core/SubtitleManager.ts +24 -22
- package/src/core/TelemetryReporter.ts +34 -31
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +24 -8
- package/src/core/UrlUtils.ts +20 -17
- package/src/core/detector.ts +44 -44
- package/src/core/index.ts +57 -48
- package/src/core/scorer.ts +136 -141
- package/src/core/selector.ts +2 -6
- package/src/global.d.ts +1 -1
- package/src/index.ts +46 -35
- package/src/players/DashJsPlayer.ts +164 -115
- package/src/players/HlsJsPlayer.ts +132 -78
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
- package/src/players/MewsWsPlayer/index.ts +192 -152
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +45 -26
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +170 -118
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
- package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
- package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
- package/src/players/WebCodecsPlayer/index.ts +263 -221
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +56 -56
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
- package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
- package/src/players/index.ts +8 -8
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +182 -42
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +43 -43
- package/src/vanilla/FrameWorksPlayer.ts +29 -14
- package/src/vanilla/index.ts +4 -4
package/src/core/CodecUtils.ts
CHANGED
|
@@ -40,61 +40,61 @@ export function translateCodec(track: TrackInfo): string {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Audio codecs
|
|
43
|
-
if (track.type ===
|
|
43
|
+
if (track.type === "audio") {
|
|
44
44
|
switch (codec) {
|
|
45
|
-
case
|
|
46
|
-
case
|
|
47
|
-
return
|
|
48
|
-
case
|
|
49
|
-
return
|
|
50
|
-
case
|
|
51
|
-
case
|
|
52
|
-
return
|
|
53
|
-
case
|
|
54
|
-
case
|
|
55
|
-
case
|
|
56
|
-
case
|
|
57
|
-
return
|
|
58
|
-
case
|
|
59
|
-
return
|
|
60
|
-
case
|
|
61
|
-
return
|
|
62
|
-
case
|
|
63
|
-
return
|
|
64
|
-
case
|
|
65
|
-
case
|
|
66
|
-
return
|
|
45
|
+
case "AAC":
|
|
46
|
+
case "MP4A":
|
|
47
|
+
return "mp4a.40.2"; // AAC-LC
|
|
48
|
+
case "MP3":
|
|
49
|
+
return "mp4a.40.34"; // MP3 in MP4 container
|
|
50
|
+
case "AC3":
|
|
51
|
+
case "AC-3":
|
|
52
|
+
return "ac-3";
|
|
53
|
+
case "EAC3":
|
|
54
|
+
case "EC3":
|
|
55
|
+
case "E-AC3":
|
|
56
|
+
case "EC-3":
|
|
57
|
+
return "ec-3";
|
|
58
|
+
case "OPUS":
|
|
59
|
+
return "opus";
|
|
60
|
+
case "VORBIS":
|
|
61
|
+
return "vorbis";
|
|
62
|
+
case "FLAC":
|
|
63
|
+
return "flac";
|
|
64
|
+
case "PCM":
|
|
65
|
+
case "PCMS16LE":
|
|
66
|
+
return "pcm";
|
|
67
67
|
default:
|
|
68
68
|
return codec.toLowerCase();
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Video codecs
|
|
73
|
-
if (track.type ===
|
|
73
|
+
if (track.type === "video") {
|
|
74
74
|
switch (codec) {
|
|
75
|
-
case
|
|
76
|
-
case
|
|
77
|
-
case
|
|
75
|
+
case "H264":
|
|
76
|
+
case "AVC":
|
|
77
|
+
case "AVC1": {
|
|
78
78
|
// Try to extract profile/level from init data
|
|
79
79
|
const profileLevel = extractH264Profile(track.init);
|
|
80
|
-
return profileLevel ||
|
|
80
|
+
return profileLevel || "avc1.42E01E"; // Default: Baseline Profile, Level 3.0
|
|
81
81
|
}
|
|
82
|
-
case
|
|
83
|
-
case
|
|
84
|
-
case
|
|
85
|
-
case
|
|
82
|
+
case "H265":
|
|
83
|
+
case "HEVC":
|
|
84
|
+
case "HEV1":
|
|
85
|
+
case "HVC1": {
|
|
86
86
|
// Try to extract profile/level from init data
|
|
87
87
|
const profileLevel = extractHEVCProfile(track.init);
|
|
88
|
-
return profileLevel ||
|
|
88
|
+
return profileLevel || "hev1.1.6.L93.B0"; // Default: Main Profile, Level 3.1
|
|
89
89
|
}
|
|
90
|
-
case
|
|
91
|
-
return
|
|
92
|
-
case
|
|
93
|
-
return
|
|
94
|
-
case
|
|
95
|
-
return
|
|
96
|
-
case
|
|
97
|
-
return
|
|
90
|
+
case "VP8":
|
|
91
|
+
return "vp8";
|
|
92
|
+
case "VP9":
|
|
93
|
+
return "vp09.00.10.08"; // Profile 0, Level 1.0, 8-bit
|
|
94
|
+
case "AV1":
|
|
95
|
+
return "av01.0.01M.08"; // Main Profile, Level 2.1, 8-bit
|
|
96
|
+
case "THEORA":
|
|
97
|
+
return "theora";
|
|
98
98
|
default:
|
|
99
99
|
return codec.toLowerCase();
|
|
100
100
|
}
|
|
@@ -199,7 +199,7 @@ function extractHEVCProfile(init?: string): string | null {
|
|
|
199
199
|
* Convert byte to 2-digit hex string
|
|
200
200
|
*/
|
|
201
201
|
function toHex(byte: number): string {
|
|
202
|
-
return byte.toString(16).padStart(2,
|
|
202
|
+
return byte.toString(16).padStart(2, "0").toUpperCase();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -221,8 +221,8 @@ function base64ToBytes(base64: string): Uint8Array {
|
|
|
221
221
|
* @param containerType - Container type (default: 'video/mp4')
|
|
222
222
|
* @returns true if supported
|
|
223
223
|
*/
|
|
224
|
-
export function isCodecSupported(codecString: string, containerType =
|
|
225
|
-
if (typeof MediaSource ===
|
|
224
|
+
export function isCodecSupported(codecString: string, containerType = "video/mp4"): boolean {
|
|
225
|
+
if (typeof MediaSource === "undefined" || !MediaSource.isTypeSupported) {
|
|
226
226
|
return false;
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -237,8 +237,11 @@ export function isCodecSupported(codecString: string, containerType = 'video/mp4
|
|
|
237
237
|
* @param type - Track type to filter ('video' or 'audio')
|
|
238
238
|
* @returns Best supported track or null
|
|
239
239
|
*/
|
|
240
|
-
export function getBestSupportedTrack(
|
|
241
|
-
|
|
240
|
+
export function getBestSupportedTrack(
|
|
241
|
+
tracks: TrackInfo[],
|
|
242
|
+
type: "video" | "audio"
|
|
243
|
+
): TrackInfo | null {
|
|
244
|
+
const filteredTracks = tracks.filter((t) => t.type === type);
|
|
242
245
|
|
|
243
246
|
for (const track of filteredTracks) {
|
|
244
247
|
const codecString = translateCodec(track);
|
package/src/core/Disposable.ts
CHANGED
|
@@ -57,7 +57,7 @@ export abstract class BaseDisposable implements Disposable {
|
|
|
57
57
|
* Throw if this object has been disposed.
|
|
58
58
|
* Use at the start of methods that shouldn't run after disposal.
|
|
59
59
|
*/
|
|
60
|
-
protected throwIfDisposed(operation: string =
|
|
60
|
+
protected throwIfDisposed(operation: string = "operation"): void {
|
|
61
61
|
if (this._disposed) {
|
|
62
62
|
throw new Error(`Cannot perform ${operation} on disposed object`);
|
|
63
63
|
}
|
|
@@ -80,7 +80,7 @@ export function disposeAll(...disposables: (Disposable | null | undefined)[]): v
|
|
|
80
80
|
try {
|
|
81
81
|
d.dispose();
|
|
82
82
|
} catch (err) {
|
|
83
|
-
console.warn(
|
|
83
|
+
console.warn("[Disposable] Error during disposal:", err);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -104,13 +104,13 @@ export function createCompositeDisposable(
|
|
|
104
104
|
|
|
105
105
|
for (const d of disposables) {
|
|
106
106
|
try {
|
|
107
|
-
if (typeof d ===
|
|
107
|
+
if (typeof d === "function") {
|
|
108
108
|
d();
|
|
109
109
|
} else if (d && !d.disposed) {
|
|
110
110
|
d.dispose();
|
|
111
111
|
}
|
|
112
112
|
} catch (err) {
|
|
113
|
-
console.warn(
|
|
113
|
+
console.warn("[CompositeDisposable] Error during disposal:", err);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
},
|
package/src/core/EventEmitter.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class TypedEventEmitter<Events extends Record<string, any>> {
|
|
|
77
77
|
* @param data - The event payload
|
|
78
78
|
*/
|
|
79
79
|
protected emit<K extends keyof Events>(event: K, data: Events[K]): void {
|
|
80
|
-
this.listeners.get(event)?.forEach(listener => {
|
|
80
|
+
this.listeners.get(event)?.forEach((listener) => {
|
|
81
81
|
try {
|
|
82
82
|
listener(data);
|
|
83
83
|
} catch (e) {
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Extracted from useViewerEndpoints.ts for use in headless core.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { TypedEventEmitter } from
|
|
9
|
-
import type { ContentEndpoints, ContentType } from
|
|
8
|
+
import { TypedEventEmitter } from "./EventEmitter";
|
|
9
|
+
import type { ContentEndpoints, ContentType } from "../types";
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Types
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
|
-
export type GatewayStatus =
|
|
15
|
+
export type GatewayStatus = "idle" | "loading" | "ready" | "error";
|
|
16
16
|
|
|
17
17
|
export interface GatewayClientConfig {
|
|
18
18
|
/** Gateway GraphQL endpoint URL */
|
|
@@ -45,10 +45,10 @@ const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
|
45
45
|
// F2: Cache TTL for resolved endpoints
|
|
46
46
|
const DEFAULT_CACHE_TTL_MS = 10000;
|
|
47
47
|
// F3: Circuit breaker constants
|
|
48
|
-
const CIRCUIT_BREAKER_THRESHOLD = 5;
|
|
49
|
-
const CIRCUIT_BREAKER_TIMEOUT_MS = 30000;
|
|
48
|
+
const CIRCUIT_BREAKER_THRESHOLD = 5; // Open after 5 consecutive failures
|
|
49
|
+
const CIRCUIT_BREAKER_TIMEOUT_MS = 30000; // Half-open after 30 seconds
|
|
50
50
|
|
|
51
|
-
type CircuitBreakerState =
|
|
51
|
+
type CircuitBreakerState = "closed" | "open" | "half-open";
|
|
52
52
|
|
|
53
53
|
const RESOLVE_VIEWER_QUERY = `
|
|
54
54
|
query ResolveViewer($contentId: String!) {
|
|
@@ -80,7 +80,7 @@ async function fetchWithRetry(
|
|
|
80
80
|
const response = await fetch(url, options);
|
|
81
81
|
return response;
|
|
82
82
|
} catch (e) {
|
|
83
|
-
lastError = e instanceof Error ? e : new Error(
|
|
83
|
+
lastError = e instanceof Error ? e : new Error("Fetch failed");
|
|
84
84
|
|
|
85
85
|
// Don't retry on abort
|
|
86
86
|
if (options.signal?.aborted) {
|
|
@@ -91,12 +91,12 @@ async function fetchWithRetry(
|
|
|
91
91
|
if (attempt < maxRetries - 1) {
|
|
92
92
|
const delay = initialDelay * Math.pow(2, attempt);
|
|
93
93
|
console.warn(`[GatewayClient] Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms`);
|
|
94
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
throw lastError ?? new Error(
|
|
99
|
+
throw lastError ?? new Error("Gateway unreachable after retries");
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// ============================================================================
|
|
@@ -121,7 +121,7 @@ async function fetchWithRetry(
|
|
|
121
121
|
*/
|
|
122
122
|
export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
123
123
|
private config: GatewayClientConfig;
|
|
124
|
-
private status: GatewayStatus =
|
|
124
|
+
private status: GatewayStatus = "idle";
|
|
125
125
|
private endpoints: ContentEndpoints | null = null;
|
|
126
126
|
private error: string | null = null;
|
|
127
127
|
private abortController: AbortController | null = null;
|
|
@@ -133,7 +133,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
133
133
|
private cacheTtlMs: number;
|
|
134
134
|
|
|
135
135
|
// F3: Circuit breaker state
|
|
136
|
-
private circuitState: CircuitBreakerState =
|
|
136
|
+
private circuitState: CircuitBreakerState = "closed";
|
|
137
137
|
private consecutiveFailures = 0;
|
|
138
138
|
private circuitOpenedAt = 0;
|
|
139
139
|
|
|
@@ -159,7 +159,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
159
159
|
|
|
160
160
|
// F3: Check circuit breaker
|
|
161
161
|
if (!this.canAttemptRequest()) {
|
|
162
|
-
throw new Error(
|
|
162
|
+
throw new Error("Circuit breaker is open - too many recent failures");
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// F2: Return in-flight request if one exists (deduplication)
|
|
@@ -214,18 +214,18 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
214
214
|
*/
|
|
215
215
|
private canAttemptRequest(): boolean {
|
|
216
216
|
switch (this.circuitState) {
|
|
217
|
-
case
|
|
217
|
+
case "closed":
|
|
218
218
|
return true;
|
|
219
219
|
|
|
220
|
-
case
|
|
220
|
+
case "open":
|
|
221
221
|
// Check if enough time has passed to try half-open
|
|
222
222
|
if (Date.now() - this.circuitOpenedAt >= CIRCUIT_BREAKER_TIMEOUT_MS) {
|
|
223
|
-
this.circuitState =
|
|
223
|
+
this.circuitState = "half-open";
|
|
224
224
|
return true;
|
|
225
225
|
}
|
|
226
226
|
return false;
|
|
227
227
|
|
|
228
|
-
case
|
|
228
|
+
case "half-open":
|
|
229
229
|
// Allow one request to test the circuit
|
|
230
230
|
return true;
|
|
231
231
|
}
|
|
@@ -236,7 +236,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
236
236
|
*/
|
|
237
237
|
private onSuccess(): void {
|
|
238
238
|
this.consecutiveFailures = 0;
|
|
239
|
-
this.circuitState =
|
|
239
|
+
this.circuitState = "closed";
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/**
|
|
@@ -245,15 +245,17 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
245
245
|
private onFailure(): void {
|
|
246
246
|
this.consecutiveFailures++;
|
|
247
247
|
|
|
248
|
-
if (this.circuitState ===
|
|
248
|
+
if (this.circuitState === "half-open") {
|
|
249
249
|
// Failed during half-open - re-open the circuit
|
|
250
|
-
this.circuitState =
|
|
250
|
+
this.circuitState = "open";
|
|
251
251
|
this.circuitOpenedAt = Date.now();
|
|
252
252
|
} else if (this.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
253
253
|
// Threshold reached - open the circuit
|
|
254
|
-
this.circuitState =
|
|
254
|
+
this.circuitState = "open";
|
|
255
255
|
this.circuitOpenedAt = Date.now();
|
|
256
|
-
console.warn(
|
|
256
|
+
console.warn(
|
|
257
|
+
`[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`
|
|
258
|
+
);
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
|
|
@@ -264,7 +266,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
264
266
|
return {
|
|
265
267
|
state: this.circuitState,
|
|
266
268
|
failures: this.consecutiveFailures,
|
|
267
|
-
openedAt: this.circuitState ===
|
|
269
|
+
openedAt: this.circuitState === "open" ? this.circuitOpenedAt : null,
|
|
268
270
|
};
|
|
269
271
|
}
|
|
270
272
|
|
|
@@ -272,7 +274,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
272
274
|
* F3: Manually reset the circuit breaker
|
|
273
275
|
*/
|
|
274
276
|
resetCircuitBreaker(): void {
|
|
275
|
-
this.circuitState =
|
|
277
|
+
this.circuitState = "closed";
|
|
276
278
|
this.consecutiveFailures = 0;
|
|
277
279
|
this.circuitOpenedAt = 0;
|
|
278
280
|
}
|
|
@@ -295,25 +297,25 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
295
297
|
|
|
296
298
|
// Validate required params
|
|
297
299
|
if (!gatewayUrl || !contentId) {
|
|
298
|
-
const error =
|
|
299
|
-
this.setStatus(
|
|
300
|
+
const error = "Missing required parameters: gatewayUrl or contentId";
|
|
301
|
+
this.setStatus("error", error);
|
|
300
302
|
throw new Error(error);
|
|
301
303
|
}
|
|
302
304
|
|
|
303
|
-
this.setStatus(
|
|
305
|
+
this.setStatus("loading");
|
|
304
306
|
|
|
305
307
|
const ac = new AbortController();
|
|
306
308
|
this.abortController = ac;
|
|
307
309
|
|
|
308
310
|
try {
|
|
309
|
-
const graphqlEndpoint = gatewayUrl.replace(/\/$/,
|
|
311
|
+
const graphqlEndpoint = gatewayUrl.replace(/\/$/, "");
|
|
310
312
|
|
|
311
313
|
const res = await fetchWithRetry(
|
|
312
314
|
graphqlEndpoint,
|
|
313
315
|
{
|
|
314
|
-
method:
|
|
316
|
+
method: "POST",
|
|
315
317
|
headers: {
|
|
316
|
-
|
|
318
|
+
"Content-Type": "application/json",
|
|
317
319
|
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
318
320
|
},
|
|
319
321
|
body: JSON.stringify({
|
|
@@ -333,7 +335,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
333
335
|
const payload = await res.json();
|
|
334
336
|
|
|
335
337
|
if (payload.errors?.length) {
|
|
336
|
-
throw new Error(payload.errors[0]?.message ||
|
|
338
|
+
throw new Error(payload.errors[0]?.message || "GraphQL error");
|
|
337
339
|
}
|
|
338
340
|
|
|
339
341
|
const resp = payload.data?.resolveViewerEndpoint;
|
|
@@ -341,7 +343,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
341
343
|
const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];
|
|
342
344
|
|
|
343
345
|
if (!primary) {
|
|
344
|
-
throw new Error(
|
|
346
|
+
throw new Error("No endpoints available");
|
|
345
347
|
}
|
|
346
348
|
|
|
347
349
|
const endpoints: ContentEndpoints = {
|
|
@@ -353,19 +355,19 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
353
355
|
this.endpoints = endpoints;
|
|
354
356
|
// F2: Update cache timestamp
|
|
355
357
|
this.cacheTimestamp = Date.now();
|
|
356
|
-
this.setStatus(
|
|
357
|
-
this.emit(
|
|
358
|
+
this.setStatus("ready");
|
|
359
|
+
this.emit("endpointsResolved", { endpoints });
|
|
358
360
|
|
|
359
361
|
return endpoints;
|
|
360
362
|
} catch (e) {
|
|
361
363
|
// Ignore abort errors
|
|
362
364
|
if (ac.signal.aborted) {
|
|
363
|
-
throw new Error(
|
|
365
|
+
throw new Error("Request aborted");
|
|
364
366
|
}
|
|
365
367
|
|
|
366
|
-
const message = e instanceof Error ? e.message :
|
|
367
|
-
console.error(
|
|
368
|
-
this.setStatus(
|
|
368
|
+
const message = e instanceof Error ? e.message : "Unknown gateway error";
|
|
369
|
+
console.error("[GatewayClient] Gateway resolution failed:", message);
|
|
370
|
+
this.setStatus("error", message);
|
|
369
371
|
throw new Error(message);
|
|
370
372
|
}
|
|
371
373
|
}
|
|
@@ -416,7 +418,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
416
418
|
this.inFlightRequest = null;
|
|
417
419
|
// F3: Reset circuit breaker for new config
|
|
418
420
|
this.resetCircuitBreaker();
|
|
419
|
-
this.setStatus(
|
|
421
|
+
this.setStatus("idle");
|
|
420
422
|
}
|
|
421
423
|
|
|
422
424
|
/**
|
|
@@ -430,7 +432,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
|
|
|
430
432
|
private setStatus(status: GatewayStatus, error?: string): void {
|
|
431
433
|
this.status = status;
|
|
432
434
|
this.error = error ?? null;
|
|
433
|
-
this.emit(
|
|
435
|
+
this.emit("statusChange", { status, error });
|
|
434
436
|
}
|
|
435
437
|
}
|
|
436
438
|
|