@livepeer-frameworks/player-core 0.0.4 → 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.
Files changed (82) hide show
  1. package/README.md +21 -6
  2. package/dist/cjs/index.js +792 -146
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/esm/index.js +792 -146
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/player.css +185 -373
  7. package/dist/types/core/GatewayClient.d.ts +3 -4
  8. package/dist/types/core/InteractionController.d.ts +12 -0
  9. package/dist/types/core/MetaTrackManager.d.ts +1 -1
  10. package/dist/types/core/PlayerController.d.ts +18 -2
  11. package/dist/types/core/PlayerInterface.d.ts +10 -0
  12. package/dist/types/core/SeekingUtils.d.ts +3 -1
  13. package/dist/types/core/StreamStateClient.d.ts +1 -1
  14. package/dist/types/players/HlsJsPlayer.d.ts +8 -0
  15. package/dist/types/players/MewsWsPlayer/index.d.ts +1 -1
  16. package/dist/types/players/VideoJsPlayer.d.ts +12 -4
  17. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +1 -1
  18. package/dist/types/players/WebCodecsPlayer/index.d.ts +11 -0
  19. package/dist/types/players/WebCodecsPlayer/types.d.ts +25 -3
  20. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +20 -2
  21. package/dist/types/types.d.ts +32 -1
  22. package/dist/types/vanilla/FrameWorksPlayer.d.ts +5 -5
  23. package/dist/types/vanilla/index.d.ts +3 -3
  24. package/dist/workers/decoder.worker.js +183 -6
  25. package/dist/workers/decoder.worker.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/core/ABRController.ts +38 -36
  28. package/src/core/CodecUtils.ts +50 -47
  29. package/src/core/Disposable.ts +4 -4
  30. package/src/core/EventEmitter.ts +1 -1
  31. package/src/core/GatewayClient.ts +48 -48
  32. package/src/core/InteractionController.ts +89 -82
  33. package/src/core/LiveDurationProxy.ts +14 -16
  34. package/src/core/MetaTrackManager.ts +74 -66
  35. package/src/core/MistReporter.ts +72 -45
  36. package/src/core/MistSignaling.ts +59 -56
  37. package/src/core/PlayerController.ts +724 -375
  38. package/src/core/PlayerInterface.ts +89 -59
  39. package/src/core/PlayerManager.ts +118 -123
  40. package/src/core/PlayerRegistry.ts +59 -42
  41. package/src/core/QualityMonitor.ts +38 -31
  42. package/src/core/ScreenWakeLockManager.ts +8 -9
  43. package/src/core/SeekingUtils.ts +31 -22
  44. package/src/core/StreamStateClient.ts +75 -69
  45. package/src/core/SubtitleManager.ts +25 -23
  46. package/src/core/TelemetryReporter.ts +34 -31
  47. package/src/core/TimeFormat.ts +13 -17
  48. package/src/core/TimerManager.ts +25 -9
  49. package/src/core/UrlUtils.ts +20 -17
  50. package/src/core/detector.ts +44 -44
  51. package/src/core/index.ts +57 -48
  52. package/src/core/scorer.ts +137 -138
  53. package/src/core/selector.ts +2 -6
  54. package/src/global.d.ts +1 -1
  55. package/src/index.ts +46 -35
  56. package/src/players/DashJsPlayer.ts +175 -114
  57. package/src/players/HlsJsPlayer.ts +154 -76
  58. package/src/players/MewsWsPlayer/SourceBufferManager.ts +44 -39
  59. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -10
  60. package/src/players/MewsWsPlayer/index.ts +196 -154
  61. package/src/players/MewsWsPlayer/types.ts +21 -21
  62. package/src/players/MistPlayer.ts +46 -27
  63. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  64. package/src/players/NativePlayer.ts +203 -143
  65. package/src/players/VideoJsPlayer.ts +200 -146
  66. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  67. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  68. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  69. package/src/players/WebCodecsPlayer/SyncController.ts +46 -55
  70. package/src/players/WebCodecsPlayer/WebSocketController.ts +67 -69
  71. package/src/players/WebCodecsPlayer/index.ts +280 -220
  72. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  73. package/src/players/WebCodecsPlayer/types.ts +81 -53
  74. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +255 -192
  75. package/src/players/WebCodecsPlayer/worker/types.ts +33 -29
  76. package/src/players/index.ts +8 -8
  77. package/src/styles/animations.css +2 -1
  78. package/src/styles/player.css +182 -356
  79. package/src/styles/tailwind.css +473 -159
  80. package/src/types.ts +75 -33
  81. package/src/vanilla/FrameWorksPlayer.ts +34 -19
  82. package/src/vanilla/index.ts +7 -7
@@ -40,61 +40,61 @@ export function translateCodec(track: TrackInfo): string {
40
40
  }
41
41
 
42
42
  // Audio codecs
43
- if (track.type === 'audio') {
43
+ if (track.type === "audio") {
44
44
  switch (codec) {
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';
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 === 'video') {
73
+ if (track.type === "video") {
74
74
  switch (codec) {
75
- case 'H264':
76
- case 'AVC':
77
- case 'AVC1': {
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 || 'avc1.42E01E'; // Default: Baseline Profile, Level 3.0
80
+ return profileLevel || "avc1.42E01E"; // Default: Baseline Profile, Level 3.0
81
81
  }
82
- case 'H265':
83
- case 'HEVC':
84
- case 'HEV1':
85
- case 'HVC1': {
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 || 'hev1.1.6.L93.B0'; // Default: Main Profile, Level 3.1
88
+ return profileLevel || "hev1.1.6.L93.B0"; // Default: Main Profile, Level 3.1
89
89
  }
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';
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
  }
@@ -180,7 +180,7 @@ function extractHEVCProfile(init?: string): string | null {
180
180
  const profileIdc = bytes[i];
181
181
  if (profileIdc >= 1 && profileIdc <= 5) {
182
182
  // Valid profile IDC (1=Main, 2=Main10, 3=MainStill, 4=Range Extensions, 5=High Throughput)
183
- const tierFlag = 0; // Assume main tier
183
+ // tierFlag assumed to be 0 (main tier)
184
184
  const levelIdc = bytes[i + 1] || 93; // Default to level 3.1
185
185
 
186
186
  // Format: hev1.{profile}.{tier_flag}{compatibility}.L{level}.{constraints}
@@ -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, '0').toUpperCase();
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 = 'video/mp4'): boolean {
225
- if (typeof MediaSource === 'undefined' || !MediaSource.isTypeSupported) {
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(tracks: TrackInfo[], type: 'video' | 'audio'): TrackInfo | null {
241
- const filteredTracks = tracks.filter(t => t.type === type);
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);
@@ -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 = 'operation'): void {
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('[Disposable] Error during disposal:', err);
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 === 'function') {
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('[CompositeDisposable] Error during disposal:', err);
113
+ console.warn("[CompositeDisposable] Error during disposal:", err);
114
114
  }
115
115
  }
116
116
  },
@@ -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,22 +5,22 @@
5
5
  * Extracted from useViewerEndpoints.ts for use in headless core.
6
6
  */
7
7
 
8
- import { TypedEventEmitter } from './EventEmitter';
9
- import type { ContentEndpoints, ContentType } from '../types';
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 = 'idle' | 'loading' | 'ready' | 'error';
15
+ export type GatewayStatus = "idle" | "loading" | "ready" | "error";
16
16
 
17
17
  export interface GatewayClientConfig {
18
18
  /** Gateway GraphQL endpoint URL */
19
19
  gatewayUrl: string;
20
- /** Content type to resolve */
21
- contentType: ContentType;
22
20
  /** Content identifier (stream name) */
23
21
  contentId: string;
22
+ /** Optional content type (no longer required for resolution) */
23
+ contentType?: ContentType;
24
24
  /** Optional auth token for private streams */
25
25
  authToken?: string;
26
26
  /** Maximum retry attempts (default: 3) */
@@ -45,14 +45,14 @@ 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; // Open after 5 consecutive failures
49
- const CIRCUIT_BREAKER_TIMEOUT_MS = 30000; // Half-open after 30 seconds
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 = 'closed' | 'open' | 'half-open';
51
+ type CircuitBreakerState = "closed" | "open" | "half-open";
52
52
 
53
53
  const RESOLVE_VIEWER_QUERY = `
54
- query ResolveViewer($contentType: String!, $contentId: String!) {
55
- resolveViewerEndpoint(contentType: $contentType, contentId: $contentId) {
54
+ query ResolveViewer($contentId: String!) {
55
+ resolveViewerEndpoint(contentId: $contentId) {
56
56
  primary { nodeId baseUrl protocol url geoDistance loadScore outputs }
57
57
  fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }
58
58
  metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }
@@ -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('Fetch failed');
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('Gateway unreachable after retries');
99
+ throw lastError ?? new Error("Gateway unreachable after retries");
100
100
  }
101
101
 
102
102
  // ============================================================================
@@ -110,8 +110,7 @@ async function fetchWithRetry(
110
110
  * ```typescript
111
111
  * const client = new GatewayClient({
112
112
  * gatewayUrl: 'https://gateway.example.com/graphql',
113
- * contentType: 'live',
114
- * contentId: 'my-stream',
113
+ * contentId: 'pk_...', // playbackId (view key)
115
114
  * });
116
115
  *
117
116
  * client.on('statusChange', ({ status }) => console.log('Status:', status));
@@ -122,7 +121,7 @@ async function fetchWithRetry(
122
121
  */
123
122
  export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
124
123
  private config: GatewayClientConfig;
125
- private status: GatewayStatus = 'idle';
124
+ private status: GatewayStatus = "idle";
126
125
  private endpoints: ContentEndpoints | null = null;
127
126
  private error: string | null = null;
128
127
  private abortController: AbortController | null = null;
@@ -134,7 +133,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
134
133
  private cacheTtlMs: number;
135
134
 
136
135
  // F3: Circuit breaker state
137
- private circuitState: CircuitBreakerState = 'closed';
136
+ private circuitState: CircuitBreakerState = "closed";
138
137
  private consecutiveFailures = 0;
139
138
  private circuitOpenedAt = 0;
140
139
 
@@ -160,7 +159,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
160
159
 
161
160
  // F3: Check circuit breaker
162
161
  if (!this.canAttemptRequest()) {
163
- throw new Error('Circuit breaker is open - too many recent failures');
162
+ throw new Error("Circuit breaker is open - too many recent failures");
164
163
  }
165
164
 
166
165
  // F2: Return in-flight request if one exists (deduplication)
@@ -215,18 +214,18 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
215
214
  */
216
215
  private canAttemptRequest(): boolean {
217
216
  switch (this.circuitState) {
218
- case 'closed':
217
+ case "closed":
219
218
  return true;
220
219
 
221
- case 'open':
220
+ case "open":
222
221
  // Check if enough time has passed to try half-open
223
222
  if (Date.now() - this.circuitOpenedAt >= CIRCUIT_BREAKER_TIMEOUT_MS) {
224
- this.circuitState = 'half-open';
223
+ this.circuitState = "half-open";
225
224
  return true;
226
225
  }
227
226
  return false;
228
227
 
229
- case 'half-open':
228
+ case "half-open":
230
229
  // Allow one request to test the circuit
231
230
  return true;
232
231
  }
@@ -237,7 +236,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
237
236
  */
238
237
  private onSuccess(): void {
239
238
  this.consecutiveFailures = 0;
240
- this.circuitState = 'closed';
239
+ this.circuitState = "closed";
241
240
  }
242
241
 
243
242
  /**
@@ -246,15 +245,17 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
246
245
  private onFailure(): void {
247
246
  this.consecutiveFailures++;
248
247
 
249
- if (this.circuitState === 'half-open') {
248
+ if (this.circuitState === "half-open") {
250
249
  // Failed during half-open - re-open the circuit
251
- this.circuitState = 'open';
250
+ this.circuitState = "open";
252
251
  this.circuitOpenedAt = Date.now();
253
252
  } else if (this.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
254
253
  // Threshold reached - open the circuit
255
- this.circuitState = 'open';
254
+ this.circuitState = "open";
256
255
  this.circuitOpenedAt = Date.now();
257
- console.warn(`[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`);
256
+ console.warn(
257
+ `[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`
258
+ );
258
259
  }
259
260
  }
260
261
 
@@ -265,7 +266,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
265
266
  return {
266
267
  state: this.circuitState,
267
268
  failures: this.consecutiveFailures,
268
- openedAt: this.circuitState === 'open' ? this.circuitOpenedAt : null,
269
+ openedAt: this.circuitState === "open" ? this.circuitOpenedAt : null,
269
270
  };
270
271
  }
271
272
 
@@ -273,7 +274,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
273
274
  * F3: Manually reset the circuit breaker
274
275
  */
275
276
  resetCircuitBreaker(): void {
276
- this.circuitState = 'closed';
277
+ this.circuitState = "closed";
277
278
  this.consecutiveFailures = 0;
278
279
  this.circuitOpenedAt = 0;
279
280
  }
@@ -288,7 +289,6 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
288
289
 
289
290
  const {
290
291
  gatewayUrl,
291
- contentType,
292
292
  contentId,
293
293
  authToken,
294
294
  maxRetries = DEFAULT_MAX_RETRIES,
@@ -296,31 +296,31 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
296
296
  } = this.config;
297
297
 
298
298
  // Validate required params
299
- if (!gatewayUrl || !contentType || !contentId) {
300
- const error = 'Missing required parameters: gatewayUrl, contentType, or contentId';
301
- this.setStatus('error', error);
299
+ if (!gatewayUrl || !contentId) {
300
+ const error = "Missing required parameters: gatewayUrl or contentId";
301
+ this.setStatus("error", error);
302
302
  throw new Error(error);
303
303
  }
304
304
 
305
- this.setStatus('loading');
305
+ this.setStatus("loading");
306
306
 
307
307
  const ac = new AbortController();
308
308
  this.abortController = ac;
309
309
 
310
310
  try {
311
- const graphqlEndpoint = gatewayUrl.replace(/\/$/, '');
311
+ const graphqlEndpoint = gatewayUrl.replace(/\/$/, "");
312
312
 
313
313
  const res = await fetchWithRetry(
314
314
  graphqlEndpoint,
315
315
  {
316
- method: 'POST',
316
+ method: "POST",
317
317
  headers: {
318
- 'Content-Type': 'application/json',
318
+ "Content-Type": "application/json",
319
319
  ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
320
320
  },
321
321
  body: JSON.stringify({
322
322
  query: RESOLVE_VIEWER_QUERY,
323
- variables: { contentType, contentId },
323
+ variables: { contentId },
324
324
  }),
325
325
  signal: ac.signal,
326
326
  },
@@ -335,7 +335,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
335
335
  const payload = await res.json();
336
336
 
337
337
  if (payload.errors?.length) {
338
- throw new Error(payload.errors[0]?.message || 'GraphQL error');
338
+ throw new Error(payload.errors[0]?.message || "GraphQL error");
339
339
  }
340
340
 
341
341
  const resp = payload.data?.resolveViewerEndpoint;
@@ -343,7 +343,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
343
343
  const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];
344
344
 
345
345
  if (!primary) {
346
- throw new Error('No endpoints available');
346
+ throw new Error("No endpoints available");
347
347
  }
348
348
 
349
349
  const endpoints: ContentEndpoints = {
@@ -355,19 +355,19 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
355
355
  this.endpoints = endpoints;
356
356
  // F2: Update cache timestamp
357
357
  this.cacheTimestamp = Date.now();
358
- this.setStatus('ready');
359
- this.emit('endpointsResolved', { endpoints });
358
+ this.setStatus("ready");
359
+ this.emit("endpointsResolved", { endpoints });
360
360
 
361
361
  return endpoints;
362
362
  } catch (e) {
363
363
  // Ignore abort errors
364
364
  if (ac.signal.aborted) {
365
- throw new Error('Request aborted');
365
+ throw new Error("Request aborted");
366
366
  }
367
367
 
368
- const message = e instanceof Error ? e.message : 'Unknown gateway error';
369
- console.error('[GatewayClient] Gateway resolution failed:', message);
370
- this.setStatus('error', message);
368
+ const message = e instanceof Error ? e.message : "Unknown gateway error";
369
+ console.error("[GatewayClient] Gateway resolution failed:", message);
370
+ this.setStatus("error", message);
371
371
  throw new Error(message);
372
372
  }
373
373
  }
@@ -418,7 +418,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
418
418
  this.inFlightRequest = null;
419
419
  // F3: Reset circuit breaker for new config
420
420
  this.resetCircuitBreaker();
421
- this.setStatus('idle');
421
+ this.setStatus("idle");
422
422
  }
423
423
 
424
424
  /**
@@ -432,7 +432,7 @@ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
432
432
  private setStatus(status: GatewayStatus, error?: string): void {
433
433
  this.status = status;
434
434
  this.error = error ?? null;
435
- this.emit('statusChange', { status, error });
435
+ this.emit("statusChange", { status, error });
436
436
  }
437
437
  }
438
438