@livepeer-frameworks/player-core 0.1.0 → 0.1.2

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 (294) hide show
  1. package/README.md +11 -9
  2. package/dist/cjs/core/ABRController.js +456 -0
  3. package/dist/cjs/core/ABRController.js.map +1 -0
  4. package/dist/cjs/core/CodecUtils.js +195 -0
  5. package/dist/cjs/core/CodecUtils.js.map +1 -0
  6. package/dist/cjs/core/ErrorClassifier.js +410 -0
  7. package/dist/cjs/core/ErrorClassifier.js.map +1 -0
  8. package/dist/cjs/core/EventEmitter.js +108 -0
  9. package/dist/cjs/core/EventEmitter.js.map +1 -0
  10. package/dist/cjs/core/GatewayClient.js +342 -0
  11. package/dist/cjs/core/GatewayClient.js.map +1 -0
  12. package/dist/cjs/core/InteractionController.js +606 -0
  13. package/dist/cjs/core/InteractionController.js.map +1 -0
  14. package/dist/cjs/core/LiveDurationProxy.js +186 -0
  15. package/dist/cjs/core/LiveDurationProxy.js.map +1 -0
  16. package/dist/cjs/core/MetaTrackManager.js +624 -0
  17. package/dist/cjs/core/MetaTrackManager.js.map +1 -0
  18. package/dist/cjs/core/MistReporter.js +449 -0
  19. package/dist/cjs/core/MistReporter.js.map +1 -0
  20. package/dist/cjs/core/MistSignaling.js +264 -0
  21. package/dist/cjs/core/MistSignaling.js.map +1 -0
  22. package/dist/cjs/core/PlayerController.js +2658 -0
  23. package/dist/cjs/core/PlayerController.js.map +1 -0
  24. package/dist/cjs/core/PlayerInterface.js +269 -0
  25. package/dist/cjs/core/PlayerInterface.js.map +1 -0
  26. package/dist/cjs/core/PlayerManager.js +806 -0
  27. package/dist/cjs/core/PlayerManager.js.map +1 -0
  28. package/dist/cjs/core/PlayerRegistry.js +270 -0
  29. package/dist/cjs/core/PlayerRegistry.js.map +1 -0
  30. package/dist/cjs/core/QualityMonitor.js +474 -0
  31. package/dist/cjs/core/QualityMonitor.js.map +1 -0
  32. package/dist/cjs/core/SeekingUtils.js +292 -0
  33. package/dist/cjs/core/SeekingUtils.js.map +1 -0
  34. package/dist/cjs/core/StreamStateClient.js +381 -0
  35. package/dist/cjs/core/StreamStateClient.js.map +1 -0
  36. package/dist/cjs/core/SubtitleManager.js +227 -0
  37. package/dist/cjs/core/SubtitleManager.js.map +1 -0
  38. package/dist/cjs/core/TelemetryReporter.js +258 -0
  39. package/dist/cjs/core/TelemetryReporter.js.map +1 -0
  40. package/dist/cjs/core/TimeFormat.js +176 -0
  41. package/dist/cjs/core/TimeFormat.js.map +1 -0
  42. package/dist/cjs/core/TimerManager.js +176 -0
  43. package/dist/cjs/core/TimerManager.js.map +1 -0
  44. package/dist/cjs/core/UrlUtils.js +160 -0
  45. package/dist/cjs/core/UrlUtils.js.map +1 -0
  46. package/dist/cjs/core/detector.js +293 -0
  47. package/dist/cjs/core/detector.js.map +1 -0
  48. package/dist/cjs/core/scorer.js +443 -0
  49. package/dist/cjs/core/scorer.js.map +1 -0
  50. package/dist/cjs/index.js +121 -20134
  51. package/dist/cjs/index.js.map +1 -1
  52. package/dist/cjs/lib/utils.js +11 -0
  53. package/dist/cjs/lib/utils.js.map +1 -0
  54. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +6 -0
  55. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  56. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3042 -0
  57. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  58. package/dist/cjs/players/DashJsPlayer.js +638 -0
  59. package/dist/cjs/players/DashJsPlayer.js.map +1 -0
  60. package/dist/cjs/players/HlsJsPlayer.js +482 -0
  61. package/dist/cjs/players/HlsJsPlayer.js.map +1 -0
  62. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js +522 -0
  63. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  64. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js +215 -0
  65. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  66. package/dist/cjs/players/MewsWsPlayer/index.js +987 -0
  67. package/dist/cjs/players/MewsWsPlayer/index.js.map +1 -0
  68. package/dist/cjs/players/MistPlayer.js +185 -0
  69. package/dist/cjs/players/MistPlayer.js.map +1 -0
  70. package/dist/cjs/players/MistWebRTCPlayer/index.js +635 -0
  71. package/dist/cjs/players/MistWebRTCPlayer/index.js.map +1 -0
  72. package/dist/cjs/players/NativePlayer.js +762 -0
  73. package/dist/cjs/players/NativePlayer.js.map +1 -0
  74. package/dist/cjs/players/VideoJsPlayer.js +585 -0
  75. package/dist/cjs/players/VideoJsPlayer.js.map +1 -0
  76. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js +236 -0
  77. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  78. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js +143 -0
  79. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  80. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js +96 -0
  81. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  82. package/dist/cjs/players/WebCodecsPlayer/SyncController.js +359 -0
  83. package/dist/cjs/players/WebCodecsPlayer/SyncController.js.map +1 -0
  84. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js +460 -0
  85. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  86. package/dist/cjs/players/WebCodecsPlayer/index.js +1467 -0
  87. package/dist/cjs/players/WebCodecsPlayer/index.js.map +1 -0
  88. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +320 -0
  89. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  90. package/dist/cjs/styles/index.js +57 -0
  91. package/dist/cjs/styles/index.js.map +1 -0
  92. package/dist/cjs/vanilla/FrameWorksPlayer.js +269 -0
  93. package/dist/cjs/vanilla/FrameWorksPlayer.js.map +1 -0
  94. package/dist/cjs/vanilla.js +11 -0
  95. package/dist/cjs/vanilla.js.map +1 -0
  96. package/dist/esm/core/ABRController.js +454 -0
  97. package/dist/esm/core/ABRController.js.map +1 -0
  98. package/dist/esm/core/CodecUtils.js +193 -0
  99. package/dist/esm/core/CodecUtils.js.map +1 -0
  100. package/dist/esm/core/ErrorClassifier.js +408 -0
  101. package/dist/esm/core/ErrorClassifier.js.map +1 -0
  102. package/dist/esm/core/EventEmitter.js +106 -0
  103. package/dist/esm/core/EventEmitter.js.map +1 -0
  104. package/dist/esm/core/GatewayClient.js +340 -0
  105. package/dist/esm/core/GatewayClient.js.map +1 -0
  106. package/dist/esm/core/InteractionController.js +604 -0
  107. package/dist/esm/core/InteractionController.js.map +1 -0
  108. package/dist/esm/core/LiveDurationProxy.js +184 -0
  109. package/dist/esm/core/LiveDurationProxy.js.map +1 -0
  110. package/dist/esm/core/MetaTrackManager.js +622 -0
  111. package/dist/esm/core/MetaTrackManager.js.map +1 -0
  112. package/dist/esm/core/MistReporter.js +447 -0
  113. package/dist/esm/core/MistReporter.js.map +1 -0
  114. package/dist/esm/core/MistSignaling.js +262 -0
  115. package/dist/esm/core/MistSignaling.js.map +1 -0
  116. package/dist/esm/core/PlayerController.js +2651 -0
  117. package/dist/esm/core/PlayerController.js.map +1 -0
  118. package/dist/esm/core/PlayerInterface.js +267 -0
  119. package/dist/esm/core/PlayerInterface.js.map +1 -0
  120. package/dist/esm/core/PlayerManager.js +804 -0
  121. package/dist/esm/core/PlayerManager.js.map +1 -0
  122. package/dist/esm/core/PlayerRegistry.js +264 -0
  123. package/dist/esm/core/PlayerRegistry.js.map +1 -0
  124. package/dist/esm/core/QualityMonitor.js +471 -0
  125. package/dist/esm/core/QualityMonitor.js.map +1 -0
  126. package/dist/esm/core/SeekingUtils.js +280 -0
  127. package/dist/esm/core/SeekingUtils.js.map +1 -0
  128. package/dist/esm/core/StreamStateClient.js +379 -0
  129. package/dist/esm/core/StreamStateClient.js.map +1 -0
  130. package/dist/esm/core/SubtitleManager.js +225 -0
  131. package/dist/esm/core/SubtitleManager.js.map +1 -0
  132. package/dist/esm/core/TelemetryReporter.js +256 -0
  133. package/dist/esm/core/TelemetryReporter.js.map +1 -0
  134. package/dist/esm/core/TimeFormat.js +169 -0
  135. package/dist/esm/core/TimeFormat.js.map +1 -0
  136. package/dist/esm/core/TimerManager.js +174 -0
  137. package/dist/esm/core/TimerManager.js.map +1 -0
  138. package/dist/esm/core/UrlUtils.js +151 -0
  139. package/dist/esm/core/UrlUtils.js.map +1 -0
  140. package/dist/esm/core/detector.js +279 -0
  141. package/dist/esm/core/detector.js.map +1 -0
  142. package/dist/esm/core/scorer.js +422 -0
  143. package/dist/esm/core/scorer.js.map +1 -0
  144. package/dist/esm/index.js +26 -20043
  145. package/dist/esm/index.js.map +1 -1
  146. package/dist/esm/lib/utils.js +9 -0
  147. package/dist/esm/lib/utils.js.map +1 -0
  148. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +4 -0
  149. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  150. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3036 -0
  151. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  152. package/dist/esm/players/DashJsPlayer.js +636 -0
  153. package/dist/esm/players/DashJsPlayer.js.map +1 -0
  154. package/dist/esm/players/HlsJsPlayer.js +480 -0
  155. package/dist/esm/players/HlsJsPlayer.js.map +1 -0
  156. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js +520 -0
  157. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  158. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js +213 -0
  159. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  160. package/dist/esm/players/MewsWsPlayer/index.js +985 -0
  161. package/dist/esm/players/MewsWsPlayer/index.js.map +1 -0
  162. package/dist/esm/players/MistPlayer.js +183 -0
  163. package/dist/esm/players/MistPlayer.js.map +1 -0
  164. package/dist/esm/players/MistWebRTCPlayer/index.js +633 -0
  165. package/dist/esm/players/MistWebRTCPlayer/index.js.map +1 -0
  166. package/dist/esm/players/NativePlayer.js +759 -0
  167. package/dist/esm/players/NativePlayer.js.map +1 -0
  168. package/dist/esm/players/VideoJsPlayer.js +583 -0
  169. package/dist/esm/players/VideoJsPlayer.js.map +1 -0
  170. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js +233 -0
  171. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  172. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js +134 -0
  173. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  174. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js +91 -0
  175. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  176. package/dist/esm/players/WebCodecsPlayer/SyncController.js +357 -0
  177. package/dist/esm/players/WebCodecsPlayer/SyncController.js.map +1 -0
  178. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js +458 -0
  179. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  180. package/dist/esm/players/WebCodecsPlayer/index.js +1458 -0
  181. package/dist/esm/players/WebCodecsPlayer/index.js.map +1 -0
  182. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +315 -0
  183. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  184. package/dist/esm/styles/index.js +54 -0
  185. package/dist/esm/styles/index.js.map +1 -0
  186. package/dist/esm/vanilla/FrameWorksPlayer.js +264 -0
  187. package/dist/esm/vanilla/FrameWorksPlayer.js.map +1 -0
  188. package/dist/esm/vanilla.js +2 -0
  189. package/dist/esm/vanilla.js.map +1 -0
  190. package/dist/player.css +185 -42
  191. package/dist/types/core/ABRController.d.ts +4 -4
  192. package/dist/types/core/CodecUtils.d.ts +1 -1
  193. package/dist/types/core/ErrorClassifier.d.ts +77 -0
  194. package/dist/types/core/GatewayClient.d.ts +4 -4
  195. package/dist/types/core/MetaTrackManager.d.ts +2 -2
  196. package/dist/types/core/MistReporter.d.ts +3 -3
  197. package/dist/types/core/MistSignaling.d.ts +12 -12
  198. package/dist/types/core/PlayerController.d.ts +19 -14
  199. package/dist/types/core/PlayerInterface.d.ts +100 -2
  200. package/dist/types/core/PlayerManager.d.ts +36 -9
  201. package/dist/types/core/PlayerRegistry.d.ts +11 -11
  202. package/dist/types/core/QualityMonitor.d.ts +2 -2
  203. package/dist/types/core/SeekingUtils.d.ts +2 -2
  204. package/dist/types/core/StreamStateClient.d.ts +2 -2
  205. package/dist/types/core/TelemetryReporter.d.ts +1 -1
  206. package/dist/types/core/TimerManager.d.ts +1 -1
  207. package/dist/types/core/detector.d.ts +1 -1
  208. package/dist/types/core/index.d.ts +44 -44
  209. package/dist/types/core/scorer.d.ts +1 -1
  210. package/dist/types/core/selector.d.ts +2 -2
  211. package/dist/types/index.d.ts +35 -34
  212. package/dist/types/players/DashJsPlayer.d.ts +3 -3
  213. package/dist/types/players/HlsJsPlayer.d.ts +3 -3
  214. package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +1 -1
  215. package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +1 -1
  216. package/dist/types/players/MewsWsPlayer/index.d.ts +2 -2
  217. package/dist/types/players/MewsWsPlayer/types.d.ts +15 -15
  218. package/dist/types/players/MistPlayer.d.ts +2 -2
  219. package/dist/types/players/MistWebRTCPlayer/index.d.ts +3 -3
  220. package/dist/types/players/NativePlayer.d.ts +3 -3
  221. package/dist/types/players/VideoJsPlayer.d.ts +3 -3
  222. package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +3 -3
  223. package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +1 -1
  224. package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +2 -2
  225. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +2 -2
  226. package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +3 -3
  227. package/dist/types/players/WebCodecsPlayer/index.d.ts +9 -9
  228. package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +1 -1
  229. package/dist/types/players/WebCodecsPlayer/types.d.ts +49 -49
  230. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +31 -31
  231. package/dist/types/players/index.d.ts +5 -8
  232. package/dist/types/types.d.ts +15 -15
  233. package/dist/types/vanilla/FrameWorksPlayer.d.ts +2 -2
  234. package/dist/types/vanilla/index.d.ts +4 -4
  235. package/dist/workers/decoder.worker.js +129 -122
  236. package/dist/workers/decoder.worker.js.map +1 -1
  237. package/package.json +31 -15
  238. package/src/core/ABRController.ts +38 -36
  239. package/src/core/CodecUtils.ts +49 -46
  240. package/src/core/Disposable.ts +4 -4
  241. package/src/core/ErrorClassifier.ts +499 -0
  242. package/src/core/EventEmitter.ts +1 -1
  243. package/src/core/GatewayClient.ts +41 -39
  244. package/src/core/InteractionController.ts +89 -82
  245. package/src/core/LiveDurationProxy.ts +14 -15
  246. package/src/core/MetaTrackManager.ts +73 -65
  247. package/src/core/MistReporter.ts +72 -45
  248. package/src/core/MistSignaling.ts +59 -56
  249. package/src/core/PlayerController.ts +542 -384
  250. package/src/core/PlayerInterface.ts +192 -59
  251. package/src/core/PlayerManager.ts +354 -164
  252. package/src/core/PlayerRegistry.ts +238 -87
  253. package/src/core/QualityMonitor.ts +38 -31
  254. package/src/core/ScreenWakeLockManager.ts +8 -9
  255. package/src/core/SeekingUtils.ts +31 -22
  256. package/src/core/StreamStateClient.ts +74 -68
  257. package/src/core/SubtitleManager.ts +24 -22
  258. package/src/core/TelemetryReporter.ts +38 -32
  259. package/src/core/TimeFormat.ts +13 -17
  260. package/src/core/TimerManager.ts +24 -8
  261. package/src/core/UrlUtils.ts +20 -17
  262. package/src/core/detector.ts +44 -44
  263. package/src/core/index.ts +57 -48
  264. package/src/core/scorer.ts +136 -141
  265. package/src/core/selector.ts +2 -6
  266. package/src/global.d.ts +1 -1
  267. package/src/index.ts +56 -36
  268. package/src/players/DashJsPlayer.ts +164 -115
  269. package/src/players/HlsJsPlayer.ts +132 -78
  270. package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
  271. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
  272. package/src/players/MewsWsPlayer/index.ts +192 -152
  273. package/src/players/MewsWsPlayer/types.ts +21 -21
  274. package/src/players/MistPlayer.ts +45 -26
  275. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  276. package/src/players/NativePlayer.ts +203 -143
  277. package/src/players/VideoJsPlayer.ts +170 -118
  278. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  279. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  280. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  281. package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
  282. package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
  283. package/src/players/WebCodecsPlayer/index.ts +265 -223
  284. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  285. package/src/players/WebCodecsPlayer/types.ts +56 -56
  286. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
  287. package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
  288. package/src/players/index.ts +5 -16
  289. package/src/styles/animations.css +2 -1
  290. package/src/styles/player.css +185 -42
  291. package/src/styles/tailwind.css +473 -159
  292. package/src/types.ts +43 -43
  293. package/src/vanilla/FrameWorksPlayer.ts +26 -14
  294. package/src/vanilla/index.ts +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorClassifier.js","sources":["../../../../src/core/ErrorClassifier.ts"],"sourcesContent":["/**\n * ErrorClassifier - Centralized error classification and recovery orchestration\n *\n * Implements a 4-tier error handling system:\n * - Tier 1 (TRANSIENT): Silent retry with exponential backoff\n * - Tier 2 (RECOVERABLE): Protocol/player swap with toast notification\n * - Tier 3 (DEGRADED): Quality drop with informational toast\n * - Tier 4 (FATAL): Blocking error modal\n */\n\nimport {\n ErrorSeverity,\n ErrorCode,\n type ClassifiedError,\n type ErrorHandlingEvents,\n} from \"./PlayerInterface\";\n\n/** Retry configuration for each error code */\ninterface RetryConfig {\n maxAttempts: number;\n baseDelayMs: number;\n maxDelayMs: number;\n jitterPercent: number;\n}\n\n/** Default retry configurations by error code */\nconst RETRY_CONFIGS: Record<ErrorCode, RetryConfig> = {\n // Tier 1: Silent recovery\n [ErrorCode.NETWORK_TIMEOUT]: {\n maxAttempts: 3,\n baseDelayMs: 500,\n maxDelayMs: 4000,\n jitterPercent: 20,\n },\n [ErrorCode.WEBSOCKET_DISCONNECT]: {\n maxAttempts: 5,\n baseDelayMs: 500,\n maxDelayMs: 5000,\n jitterPercent: 20,\n },\n [ErrorCode.SEGMENT_LOAD_ERROR]: {\n maxAttempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 2000,\n jitterPercent: 10,\n },\n [ErrorCode.ICE_DISCONNECTED]: {\n maxAttempts: 3,\n baseDelayMs: 500,\n maxDelayMs: 3000,\n jitterPercent: 20,\n },\n [ErrorCode.BUFFER_UNDERRUN]: {\n maxAttempts: 1,\n baseDelayMs: 5000,\n maxDelayMs: 5000,\n jitterPercent: 0,\n },\n [ErrorCode.CODEC_DECODE_ERROR]: {\n maxAttempts: 3,\n baseDelayMs: 100,\n maxDelayMs: 1000,\n jitterPercent: 10,\n },\n\n // Tier 2: Protocol swap (no internal retry, just count for tracking)\n [ErrorCode.PROTOCOL_UNSUPPORTED]: {\n maxAttempts: 1,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n [ErrorCode.CODEC_INCOMPATIBLE]: {\n maxAttempts: 1,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n [ErrorCode.ICE_FAILED]: { maxAttempts: 1, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.MANIFEST_STALE]: { maxAttempts: 1, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.PLAYER_INIT_FAILED]: {\n maxAttempts: 1,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n\n // Tier 3: Quality (not retried, just tracked)\n [ErrorCode.QUALITY_DROPPED]: { maxAttempts: 0, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.BANDWIDTH_LIMITED]: {\n maxAttempts: 0,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n\n // Tier 4: Fatal (not retried)\n [ErrorCode.ALL_PROTOCOLS_EXHAUSTED]: {\n maxAttempts: 0,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n [ErrorCode.ALL_PROTOCOLS_BLACKLISTED]: {\n maxAttempts: 0,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n [ErrorCode.STREAM_OFFLINE]: { maxAttempts: 0, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.AUTH_REQUIRED]: { maxAttempts: 0, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.GEO_BLOCKED]: { maxAttempts: 0, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.DRM_ERROR]: { maxAttempts: 0, baseDelayMs: 0, maxDelayMs: 0, jitterPercent: 0 },\n [ErrorCode.CONTENT_UNAVAILABLE]: {\n maxAttempts: 0,\n baseDelayMs: 0,\n maxDelayMs: 0,\n jitterPercent: 0,\n },\n [ErrorCode.UNKNOWN]: { maxAttempts: 1, baseDelayMs: 1000, maxDelayMs: 1000, jitterPercent: 0 },\n};\n\n/** Maps error codes to their default severity */\nconst CODE_TO_SEVERITY: Record<ErrorCode, ErrorSeverity> = {\n // Tier 1\n [ErrorCode.NETWORK_TIMEOUT]: ErrorSeverity.TRANSIENT,\n [ErrorCode.WEBSOCKET_DISCONNECT]: ErrorSeverity.TRANSIENT,\n [ErrorCode.SEGMENT_LOAD_ERROR]: ErrorSeverity.TRANSIENT,\n [ErrorCode.ICE_DISCONNECTED]: ErrorSeverity.TRANSIENT,\n [ErrorCode.BUFFER_UNDERRUN]: ErrorSeverity.TRANSIENT,\n [ErrorCode.CODEC_DECODE_ERROR]: ErrorSeverity.TRANSIENT,\n\n // Tier 2\n [ErrorCode.PROTOCOL_UNSUPPORTED]: ErrorSeverity.RECOVERABLE,\n [ErrorCode.CODEC_INCOMPATIBLE]: ErrorSeverity.RECOVERABLE,\n [ErrorCode.ICE_FAILED]: ErrorSeverity.RECOVERABLE,\n [ErrorCode.MANIFEST_STALE]: ErrorSeverity.RECOVERABLE,\n [ErrorCode.PLAYER_INIT_FAILED]: ErrorSeverity.RECOVERABLE,\n\n // Tier 3\n [ErrorCode.QUALITY_DROPPED]: ErrorSeverity.DEGRADED,\n [ErrorCode.BANDWIDTH_LIMITED]: ErrorSeverity.DEGRADED,\n\n // Tier 4\n [ErrorCode.ALL_PROTOCOLS_EXHAUSTED]: ErrorSeverity.FATAL,\n [ErrorCode.ALL_PROTOCOLS_BLACKLISTED]: ErrorSeverity.FATAL,\n [ErrorCode.STREAM_OFFLINE]: ErrorSeverity.FATAL,\n [ErrorCode.AUTH_REQUIRED]: ErrorSeverity.FATAL,\n [ErrorCode.GEO_BLOCKED]: ErrorSeverity.FATAL,\n [ErrorCode.DRM_ERROR]: ErrorSeverity.FATAL,\n [ErrorCode.CONTENT_UNAVAILABLE]: ErrorSeverity.FATAL,\n [ErrorCode.UNKNOWN]: ErrorSeverity.FATAL,\n};\n\n/** User-friendly messages for each error code */\nconst CODE_TO_MESSAGE: Record<ErrorCode, string> = {\n [ErrorCode.NETWORK_TIMEOUT]: \"Network timeout\",\n [ErrorCode.WEBSOCKET_DISCONNECT]: \"Connection lost\",\n [ErrorCode.SEGMENT_LOAD_ERROR]: \"Failed to load video segment\",\n [ErrorCode.ICE_DISCONNECTED]: \"Connection interrupted\",\n [ErrorCode.BUFFER_UNDERRUN]: \"Buffering\",\n [ErrorCode.CODEC_DECODE_ERROR]: \"Decode error\",\n [ErrorCode.PROTOCOL_UNSUPPORTED]: \"Protocol not supported\",\n [ErrorCode.CODEC_INCOMPATIBLE]: \"Codec not supported\",\n [ErrorCode.ICE_FAILED]: \"Connection failed\",\n [ErrorCode.MANIFEST_STALE]: \"Stream manifest outdated\",\n [ErrorCode.PLAYER_INIT_FAILED]: \"Player initialization failed\",\n [ErrorCode.QUALITY_DROPPED]: \"Quality reduced\",\n [ErrorCode.BANDWIDTH_LIMITED]: \"Bandwidth limited\",\n [ErrorCode.ALL_PROTOCOLS_EXHAUSTED]: \"Unable to play video\",\n [ErrorCode.ALL_PROTOCOLS_BLACKLISTED]: \"No compatible playback protocols available\",\n [ErrorCode.STREAM_OFFLINE]: \"Stream is offline\",\n [ErrorCode.AUTH_REQUIRED]: \"Sign in to watch\",\n [ErrorCode.GEO_BLOCKED]: \"Not available in your region\",\n [ErrorCode.DRM_ERROR]: \"Playback not supported\",\n [ErrorCode.CONTENT_UNAVAILABLE]: \"Content unavailable\",\n [ErrorCode.UNKNOWN]: \"Playback error\",\n};\n\ntype EventListener<K extends keyof ErrorHandlingEvents> = (data: ErrorHandlingEvents[K]) => void;\n\nexport type RecoveryAction =\n | { type: \"retry\"; delayMs: number }\n | { type: \"swap\"; reason: string }\n | { type: \"toast\"; message: string }\n | { type: \"fatal\"; error: ClassifiedError };\n\nexport interface ErrorClassifierOptions {\n /** Number of alternative player/protocol combos available */\n alternativesCount?: number;\n /** Enable debug logging */\n debug?: boolean;\n}\n\n/**\n * Centralized error classifier that tracks retry state and determines recovery actions.\n */\nexport class ErrorClassifier {\n private retryCounts: Map<ErrorCode, number> = new Map();\n private lastErrorTime: Map<ErrorCode, number> = new Map();\n private alternativesRemaining: number;\n private listeners: Map<string, Set<Function>> = new Map();\n private debug: boolean;\n\n // Debounce tracking for quality toasts\n private lastQualityToastTime = 0;\n private static readonly QUALITY_TOAST_DEBOUNCE_MS = 10000;\n\n constructor(options: ErrorClassifierOptions = {}) {\n this.alternativesRemaining = options.alternativesCount ?? 0;\n this.debug = options.debug ?? false;\n }\n\n /**\n * Update the count of remaining alternatives (called after a swap attempt)\n */\n setAlternativesRemaining(count: number): void {\n this.alternativesRemaining = count;\n }\n\n /**\n * Reset retry counts (call when playback successfully resumes)\n */\n reset(): void {\n this.retryCounts.clear();\n this.lastErrorTime.clear();\n this.lastQualityToastTime = 0;\n this.log(\"Error state reset\");\n }\n\n /**\n * Classify a raw error and determine the appropriate recovery action.\n *\n * @param code - Error code identifying the error type\n * @param originalError - Original error object or message\n * @returns The recovery action to take\n */\n classify(code: ErrorCode, originalError?: Error | string): RecoveryAction {\n const config = RETRY_CONFIGS[code];\n const currentAttempt = (this.retryCounts.get(code) ?? 0) + 1;\n const retriesRemaining = Math.max(0, config.maxAttempts - currentAttempt);\n\n // Update retry count\n this.retryCounts.set(code, currentAttempt);\n this.lastErrorTime.set(code, Date.now());\n\n const classified: ClassifiedError = {\n severity: CODE_TO_SEVERITY[code],\n code,\n message: CODE_TO_MESSAGE[code],\n retriesRemaining,\n alternativesRemaining: this.alternativesRemaining,\n originalError,\n timestamp: Date.now(),\n };\n\n this.log(\n `Classified error: ${code}, attempt ${currentAttempt}/${config.maxAttempts}, severity ${classified.severity}`\n );\n\n // Tier 1: Silent retry if retries remaining\n if (classified.severity === ErrorSeverity.TRANSIENT && retriesRemaining > 0) {\n const delayMs = this.calculateBackoff(code, currentAttempt);\n this.emit(\"recoveryAttempted\", {\n code,\n attempt: currentAttempt,\n maxAttempts: config.maxAttempts,\n });\n return { type: \"retry\", delayMs };\n }\n\n // Tier 1 exhausted or Tier 2: Try protocol swap if alternatives exist\n if (\n (classified.severity === ErrorSeverity.TRANSIENT && retriesRemaining === 0) ||\n classified.severity === ErrorSeverity.RECOVERABLE\n ) {\n if (this.alternativesRemaining > 0) {\n return { type: \"swap\", reason: classified.message };\n }\n // No alternatives: escalate to fatal\n const originalCode = classified.code;\n const originalMessage = classified.message;\n classified.severity = ErrorSeverity.FATAL;\n classified.code = ErrorCode.ALL_PROTOCOLS_EXHAUSTED;\n classified.message = `${originalMessage} (no alternatives remaining)`;\n classified.details = {\n ...classified.details,\n originalCode,\n originalMessage,\n };\n }\n\n // Tier 3: Quality degradation toast (debounced)\n if (classified.severity === ErrorSeverity.DEGRADED) {\n const now = Date.now();\n if (now - this.lastQualityToastTime >= ErrorClassifier.QUALITY_TOAST_DEBOUNCE_MS) {\n this.lastQualityToastTime = now;\n this.emit(\"qualityChanged\", {\n direction: \"down\",\n reason: classified.message,\n });\n return { type: \"toast\", message: classified.message };\n }\n // Debounced: no action needed\n return { type: \"toast\", message: \"\" };\n }\n\n // Tier 4: Fatal error\n this.emit(\"playbackFailed\", classified);\n return { type: \"fatal\", error: classified };\n }\n\n classifyWithDetails(\n code: ErrorCode,\n message: string,\n details?: ClassifiedError[\"details\"],\n originalError?: Error | string\n ): RecoveryAction {\n if (CODE_TO_SEVERITY[code] === ErrorSeverity.FATAL) {\n const classified: ClassifiedError = {\n severity: ErrorSeverity.FATAL,\n code,\n message,\n retriesRemaining: 0,\n alternativesRemaining: this.alternativesRemaining,\n originalError,\n timestamp: Date.now(),\n details,\n };\n this.emit(\"playbackFailed\", classified);\n return { type: \"fatal\", error: classified };\n }\n\n const action = this.classify(code, originalError);\n if (action.type === \"fatal\") {\n action.error.message = message;\n action.error.details = details;\n }\n return action;\n }\n\n /**\n * Notify classifier that a protocol swap occurred (for event emission)\n */\n notifyProtocolSwap(\n fromPlayer: string,\n toPlayer: string,\n fromProtocol: string,\n toProtocol: string,\n reason: string\n ): void {\n // Note: alternativesRemaining is managed by PlayerManager.setAlternativesRemaining()\n // Don't decrement here to avoid double-counting\n this.emit(\"protocolSwapped\", {\n fromPlayer,\n toPlayer,\n fromProtocol,\n toProtocol,\n reason,\n });\n }\n\n /**\n * Calculate exponential backoff delay with jitter\n */\n private calculateBackoff(code: ErrorCode, attempt: number): number {\n const config = RETRY_CONFIGS[code];\n const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt - 1);\n const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);\n\n // Add jitter\n const jitterRange = cappedDelay * (config.jitterPercent / 100);\n const jitter = (Math.random() * 2 - 1) * jitterRange;\n\n return Math.round(cappedDelay + jitter);\n }\n\n /**\n * Map common error patterns to error codes\n */\n static mapErrorToCode(error: Error | string): ErrorCode {\n const message = typeof error === \"string\" ? error : error.message;\n const lowerMessage = message.toLowerCase();\n\n // Network errors\n if (lowerMessage.includes(\"timeout\") || lowerMessage.includes(\"timed out\")) {\n return ErrorCode.NETWORK_TIMEOUT;\n }\n if (lowerMessage.includes(\"websocket\") || lowerMessage.includes(\"socket\")) {\n return ErrorCode.WEBSOCKET_DISCONNECT;\n }\n if (lowerMessage.includes(\"fetch\") || lowerMessage.includes(\"network\")) {\n return ErrorCode.NETWORK_TIMEOUT;\n }\n\n // Stream state - check before segment errors (404 can mean offline)\n if (\n lowerMessage.includes(\"offline\") ||\n lowerMessage.includes(\"not found\") ||\n lowerMessage.includes(\"stream not found\")\n ) {\n return ErrorCode.STREAM_OFFLINE;\n }\n\n // Segment/manifest errors (only if not a stream-level 404)\n if (lowerMessage.includes(\"segment\")) {\n return ErrorCode.SEGMENT_LOAD_ERROR;\n }\n if (lowerMessage.includes(\"manifest\") || lowerMessage.includes(\"playlist\")) {\n // Manifest 404 = stream offline, not stale\n if (lowerMessage.includes(\"404\")) {\n return ErrorCode.STREAM_OFFLINE;\n }\n return ErrorCode.MANIFEST_STALE;\n }\n\n // ICE/WebRTC errors\n if (lowerMessage.includes(\"ice\") && lowerMessage.includes(\"disconnect\")) {\n return ErrorCode.ICE_DISCONNECTED;\n }\n if (lowerMessage.includes(\"ice\") && lowerMessage.includes(\"fail\")) {\n return ErrorCode.ICE_FAILED;\n }\n\n // Codec errors\n if (lowerMessage.includes(\"codec\") || lowerMessage.includes(\"decode\")) {\n return ErrorCode.CODEC_DECODE_ERROR;\n }\n if (lowerMessage.includes(\"not supported\") || lowerMessage.includes(\"unsupported\")) {\n return ErrorCode.PROTOCOL_UNSUPPORTED;\n }\n\n // Buffer errors\n if (lowerMessage.includes(\"buffer\") || lowerMessage.includes(\"underrun\")) {\n return ErrorCode.BUFFER_UNDERRUN;\n }\n\n // Auth errors\n if (\n lowerMessage.includes(\"401\") ||\n lowerMessage.includes(\"auth\") ||\n lowerMessage.includes(\"unauthorized\")\n ) {\n return ErrorCode.AUTH_REQUIRED;\n }\n if (\n lowerMessage.includes(\"403\") ||\n lowerMessage.includes(\"forbidden\") ||\n lowerMessage.includes(\"geo\")\n ) {\n return ErrorCode.GEO_BLOCKED;\n }\n\n // DRM\n if (\n lowerMessage.includes(\"drm\") ||\n lowerMessage.includes(\"eme\") ||\n lowerMessage.includes(\"key\")\n ) {\n return ErrorCode.DRM_ERROR;\n }\n\n return ErrorCode.UNKNOWN;\n }\n\n // Event emitter methods\n on<K extends keyof ErrorHandlingEvents>(event: K, listener: EventListener<K>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n off<K extends keyof ErrorHandlingEvents>(event: K, listener: EventListener<K>): void {\n const eventListeners = this.listeners.get(event);\n if (eventListeners) {\n eventListeners.delete(listener);\n }\n }\n\n private emit<K extends keyof ErrorHandlingEvents>(event: K, data: ErrorHandlingEvents[K]): void {\n const eventListeners = this.listeners.get(event);\n if (eventListeners) {\n eventListeners.forEach((listener) => {\n try {\n (listener as EventListener<K>)(data);\n } catch (e) {\n console.error(`Error in ${event} listener:`, e);\n }\n });\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[ErrorClassifier] ${message}`);\n }\n }\n}\n"],"names":["ErrorCode","ErrorSeverity"],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * EventEmitter.ts
5
+ *
6
+ * A lightweight, typed event emitter for framework-agnostic components.
7
+ * Used by GatewayClient, StreamStateClient, and PlayerController.
8
+ */
9
+ /**
10
+ * Typed event emitter that provides type-safe event handling.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * interface MyEvents {
15
+ * stateChange: { state: string };
16
+ * error: { message: string };
17
+ * }
18
+ *
19
+ * class MyClass extends TypedEventEmitter<MyEvents> {
20
+ * doSomething() {
21
+ * this.emit('stateChange', { state: 'ready' });
22
+ * }
23
+ * }
24
+ *
25
+ * const instance = new MyClass();
26
+ * const unsub = instance.on('stateChange', ({ state }) => console.log(state));
27
+ * unsub(); // unsubscribe
28
+ * ```
29
+ */
30
+ class TypedEventEmitter {
31
+ constructor() {
32
+ this.listeners = new Map();
33
+ }
34
+ /**
35
+ * Subscribe to an event.
36
+ * @param event - The event name to listen for
37
+ * @param listener - Callback function invoked when the event is emitted
38
+ * @returns Unsubscribe function
39
+ */
40
+ on(event, listener) {
41
+ if (!this.listeners.has(event)) {
42
+ this.listeners.set(event, new Set());
43
+ }
44
+ this.listeners.get(event).add(listener);
45
+ // Return unsubscribe function
46
+ return () => this.off(event, listener);
47
+ }
48
+ /**
49
+ * Subscribe to an event only once.
50
+ * The listener is automatically removed after the first invocation.
51
+ * @param event - The event name to listen for
52
+ * @param listener - Callback function invoked when the event is emitted
53
+ * @returns Unsubscribe function
54
+ */
55
+ once(event, listener) {
56
+ const onceListener = (data) => {
57
+ this.off(event, onceListener);
58
+ listener(data);
59
+ };
60
+ return this.on(event, onceListener);
61
+ }
62
+ /**
63
+ * Unsubscribe from an event.
64
+ * @param event - The event name
65
+ * @param listener - The callback to remove
66
+ */
67
+ off(event, listener) {
68
+ this.listeners.get(event)?.delete(listener);
69
+ }
70
+ /**
71
+ * Emit an event to all subscribers.
72
+ * @param event - The event name
73
+ * @param data - The event payload
74
+ */
75
+ emit(event, data) {
76
+ this.listeners.get(event)?.forEach((listener) => {
77
+ try {
78
+ listener(data);
79
+ }
80
+ catch (e) {
81
+ console.error(`[EventEmitter] Error in ${String(event)} listener:`, e);
82
+ }
83
+ });
84
+ }
85
+ /**
86
+ * Remove all listeners for all events.
87
+ */
88
+ removeAllListeners() {
89
+ this.listeners.clear();
90
+ }
91
+ /**
92
+ * Remove all listeners for a specific event.
93
+ * @param event - The event name
94
+ */
95
+ removeListeners(event) {
96
+ this.listeners.delete(event);
97
+ }
98
+ /**
99
+ * Check if there are any listeners for an event.
100
+ * @param event - The event name
101
+ */
102
+ hasListeners(event) {
103
+ return (this.listeners.get(event)?.size ?? 0) > 0;
104
+ }
105
+ }
106
+
107
+ exports.TypedEventEmitter = TypedEventEmitter;
108
+ //# sourceMappingURL=EventEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventEmitter.js","sources":["../../../../src/core/EventEmitter.ts"],"sourcesContent":["/**\n * EventEmitter.ts\n *\n * A lightweight, typed event emitter for framework-agnostic components.\n * Used by GatewayClient, StreamStateClient, and PlayerController.\n */\n\ntype Listener<T> = (data: T) => void;\n\n/**\n * Typed event emitter that provides type-safe event handling.\n *\n * @example\n * ```typescript\n * interface MyEvents {\n * stateChange: { state: string };\n * error: { message: string };\n * }\n *\n * class MyClass extends TypedEventEmitter<MyEvents> {\n * doSomething() {\n * this.emit('stateChange', { state: 'ready' });\n * }\n * }\n *\n * const instance = new MyClass();\n * const unsub = instance.on('stateChange', ({ state }) => console.log(state));\n * unsub(); // unsubscribe\n * ```\n */\nexport class TypedEventEmitter<Events extends Record<string, any>> {\n private listeners = new Map<keyof Events, Set<Function>>();\n\n /**\n * Subscribe to an event.\n * @param event - The event name to listen for\n * @param listener - Callback function invoked when the event is emitted\n * @returns Unsubscribe function\n */\n on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n\n // Return unsubscribe function\n return () => this.off(event, listener);\n }\n\n /**\n * Subscribe to an event only once.\n * The listener is automatically removed after the first invocation.\n * @param event - The event name to listen for\n * @param listener - Callback function invoked when the event is emitted\n * @returns Unsubscribe function\n */\n once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): () => void {\n const onceListener = (data: Events[K]) => {\n this.off(event, onceListener);\n listener(data);\n };\n return this.on(event, onceListener);\n }\n\n /**\n * Unsubscribe from an event.\n * @param event - The event name\n * @param listener - The callback to remove\n */\n off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void {\n this.listeners.get(event)?.delete(listener);\n }\n\n /**\n * Emit an event to all subscribers.\n * @param event - The event name\n * @param data - The event payload\n */\n protected emit<K extends keyof Events>(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (e) {\n console.error(`[EventEmitter] Error in ${String(event)} listener:`, e);\n }\n });\n }\n\n /**\n * Remove all listeners for all events.\n */\n removeAllListeners(): void {\n this.listeners.clear();\n }\n\n /**\n * Remove all listeners for a specific event.\n * @param event - The event name\n */\n removeListeners<K extends keyof Events>(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Check if there are any listeners for an event.\n * @param event - The event name\n */\n hasListeners<K extends keyof Events>(event: K): boolean {\n return (this.listeners.get(event)?.size ?? 0) > 0;\n }\n}\n\nexport default TypedEventEmitter;\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAIH;;;;;;;;;;;;;;;;;;;;AAoBG;MACU,iBAAiB,CAAA;AAA9B,IAAA,WAAA,GAAA;AACU,QAAA,IAAA,CAAA,SAAS,GAAG,IAAI,GAAG,EAA+B;IA+E5D;AA7EE;;;;;AAKG;IACH,EAAE,CAAyB,KAAQ,EAAE,QAA6B,EAAA;QAChE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC;QACtC;AACA,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,GAAG,CAAC,QAAQ,CAAC;;QAGxC,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC;IACxC;AAEA;;;;;;AAMG;IACH,IAAI,CAAyB,KAAQ,EAAE,QAA6B,EAAA;AAClE,QAAA,MAAM,YAAY,GAAG,CAAC,IAAe,KAAI;AACvC,YAAA,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC;AAChB,QAAA,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;IACrC;AAEA;;;;AAIG;IACH,GAAG,CAAyB,KAAQ,EAAE,QAA6B,EAAA;AACjE,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC7C;AAEA;;;;AAIG;IACO,IAAI,CAAyB,KAAQ,EAAE,IAAe,EAAA;AAC9D,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,KAAI;AAC9C,YAAA,IAAI;gBACF,QAAQ,CAAC,IAAI,CAAC;YAChB;YAAE,OAAO,CAAC,EAAE;AACV,gBAAA,OAAO,CAAC,KAAK,CAAC,CAAA,wBAAA,EAA2B,MAAM,CAAC,KAAK,CAAC,CAAA,UAAA,CAAY,EAAE,CAAC,CAAC;YACxE;AACF,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;IACH,kBAAkB,GAAA;AAChB,QAAA,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE;IACxB;AAEA;;;AAGG;AACH,IAAA,eAAe,CAAyB,KAAQ,EAAA;AAC9C,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC;IAC9B;AAEA;;;AAGG;AACH,IAAA,YAAY,CAAyB,KAAQ,EAAA;AAC3C,QAAA,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC;IACnD;AACD;;;;"}
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ var EventEmitter = require('./EventEmitter.js');
4
+
5
+ /**
6
+ * GatewayClient.ts
7
+ *
8
+ * Framework-agnostic client for resolving viewer endpoints from the Gateway GraphQL API.
9
+ * Extracted from useViewerEndpoints.ts for use in headless core.
10
+ */
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+ const DEFAULT_MAX_RETRIES = 3;
15
+ const DEFAULT_INITIAL_DELAY_MS = 500;
16
+ // F2: Cache TTL for resolved endpoints
17
+ const DEFAULT_CACHE_TTL_MS = 10000;
18
+ // F3: Circuit breaker constants
19
+ const CIRCUIT_BREAKER_THRESHOLD = 5; // Open after 5 consecutive failures
20
+ const CIRCUIT_BREAKER_TIMEOUT_MS = 30000; // Half-open after 30 seconds
21
+ const RESOLVE_VIEWER_QUERY = `
22
+ query ResolveViewer($contentId: String!) {
23
+ resolveViewerEndpoint(contentId: $contentId) {
24
+ primary { nodeId baseUrl protocol url geoDistance loadScore outputs }
25
+ fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }
26
+ metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }
27
+ }
28
+ }
29
+ `;
30
+ // ============================================================================
31
+ // Helper Functions
32
+ // ============================================================================
33
+ /**
34
+ * Fetch with exponential backoff retry logic.
35
+ */
36
+ async function fetchWithRetry(url, options, maxRetries, initialDelay) {
37
+ let lastError = null;
38
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
39
+ try {
40
+ const response = await fetch(url, options);
41
+ return response;
42
+ }
43
+ catch (e) {
44
+ lastError = e instanceof Error ? e : new Error("Fetch failed");
45
+ // Don't retry on abort
46
+ if (options.signal?.aborted) {
47
+ throw lastError;
48
+ }
49
+ // Wait before retrying (exponential backoff)
50
+ if (attempt < maxRetries - 1) {
51
+ const delay = initialDelay * Math.pow(2, attempt);
52
+ console.warn(`[GatewayClient] Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms`);
53
+ await new Promise((resolve) => setTimeout(resolve, delay));
54
+ }
55
+ }
56
+ }
57
+ throw lastError ?? new Error("Gateway unreachable after retries");
58
+ }
59
+ // ============================================================================
60
+ // GatewayClient Class
61
+ // ============================================================================
62
+ /**
63
+ * Client for resolving viewer endpoints from the Gateway GraphQL API.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const client = new GatewayClient({
68
+ * gatewayUrl: 'https://gateway.example.com/graphql',
69
+ * contentId: 'pk_...', // playbackId (view key)
70
+ * });
71
+ *
72
+ * client.on('statusChange', ({ status }) => console.log('Status:', status));
73
+ * client.on('endpointsResolved', ({ endpoints }) => console.log('Endpoints:', endpoints));
74
+ *
75
+ * const endpoints = await client.resolve();
76
+ * ```
77
+ */
78
+ class GatewayClient extends EventEmitter.TypedEventEmitter {
79
+ constructor(config) {
80
+ super();
81
+ this.status = "idle";
82
+ this.endpoints = null;
83
+ this.error = null;
84
+ this.abortController = null;
85
+ // F2: Request deduplication - in-flight request tracking
86
+ this.inFlightRequest = null;
87
+ // F2: Cache with TTL for resolved endpoints
88
+ this.cacheTimestamp = 0;
89
+ // F3: Circuit breaker state
90
+ this.circuitState = "closed";
91
+ this.consecutiveFailures = 0;
92
+ this.circuitOpenedAt = 0;
93
+ this.config = config;
94
+ this.cacheTtlMs = DEFAULT_CACHE_TTL_MS;
95
+ }
96
+ /**
97
+ * Resolve endpoints from the gateway.
98
+ * F2: Returns cached result if still valid, deduplicates concurrent requests.
99
+ * F3: Respects circuit breaker state.
100
+ * @param forceRefresh - If true, bypasses cache and fetches fresh data
101
+ * @returns Promise resolving to ContentEndpoints
102
+ * @throws Error if resolution fails after retries or circuit is open
103
+ */
104
+ async resolve(forceRefresh = false) {
105
+ // F2: Return cached result if still valid
106
+ if (!forceRefresh && this.endpoints && this.isCacheValid()) {
107
+ return this.endpoints;
108
+ }
109
+ // F3: Check circuit breaker
110
+ if (!this.canAttemptRequest()) {
111
+ throw new Error("Circuit breaker is open - too many recent failures");
112
+ }
113
+ // F2: Return in-flight request if one exists (deduplication)
114
+ if (this.inFlightRequest) {
115
+ return this.inFlightRequest;
116
+ }
117
+ // Create a new request and track it
118
+ this.inFlightRequest = this.doResolve();
119
+ try {
120
+ const result = await this.inFlightRequest;
121
+ // F3: Success - close circuit
122
+ this.onSuccess();
123
+ return result;
124
+ }
125
+ catch (e) {
126
+ // F3: Failure - record for circuit breaker
127
+ this.onFailure();
128
+ throw e;
129
+ }
130
+ finally {
131
+ this.inFlightRequest = null;
132
+ }
133
+ }
134
+ /**
135
+ * F2: Check if cache is still valid
136
+ */
137
+ isCacheValid() {
138
+ return Date.now() - this.cacheTimestamp < this.cacheTtlMs;
139
+ }
140
+ /**
141
+ * F2: Set cache TTL (for testing or custom requirements)
142
+ */
143
+ setCacheTtl(ttlMs) {
144
+ this.cacheTtlMs = ttlMs;
145
+ }
146
+ /**
147
+ * F2: Invalidate the cache manually
148
+ */
149
+ invalidateCache() {
150
+ this.cacheTimestamp = 0;
151
+ }
152
+ // ==========================================================================
153
+ // F3: Circuit Breaker Methods
154
+ // ==========================================================================
155
+ /**
156
+ * F3: Check if a request can be attempted based on circuit state
157
+ */
158
+ canAttemptRequest() {
159
+ switch (this.circuitState) {
160
+ case "closed":
161
+ return true;
162
+ case "open":
163
+ // Check if enough time has passed to try half-open
164
+ if (Date.now() - this.circuitOpenedAt >= CIRCUIT_BREAKER_TIMEOUT_MS) {
165
+ this.circuitState = "half-open";
166
+ return true;
167
+ }
168
+ return false;
169
+ case "half-open":
170
+ // Allow one request to test the circuit
171
+ return true;
172
+ }
173
+ }
174
+ /**
175
+ * F3: Record a successful request
176
+ */
177
+ onSuccess() {
178
+ this.consecutiveFailures = 0;
179
+ this.circuitState = "closed";
180
+ }
181
+ /**
182
+ * F3: Record a failed request
183
+ */
184
+ onFailure() {
185
+ this.consecutiveFailures++;
186
+ if (this.circuitState === "half-open") {
187
+ // Failed during half-open - re-open the circuit
188
+ this.circuitState = "open";
189
+ this.circuitOpenedAt = Date.now();
190
+ }
191
+ else if (this.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
192
+ // Threshold reached - open the circuit
193
+ this.circuitState = "open";
194
+ this.circuitOpenedAt = Date.now();
195
+ console.warn(`[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`);
196
+ }
197
+ }
198
+ /**
199
+ * F3: Get current circuit breaker state (for monitoring/debugging)
200
+ */
201
+ getCircuitState() {
202
+ return {
203
+ state: this.circuitState,
204
+ failures: this.consecutiveFailures,
205
+ openedAt: this.circuitState === "open" ? this.circuitOpenedAt : null,
206
+ };
207
+ }
208
+ /**
209
+ * F3: Manually reset the circuit breaker
210
+ */
211
+ resetCircuitBreaker() {
212
+ this.circuitState = "closed";
213
+ this.consecutiveFailures = 0;
214
+ this.circuitOpenedAt = 0;
215
+ }
216
+ /**
217
+ * Internal method to perform the actual resolution.
218
+ * @returns Promise resolving to ContentEndpoints
219
+ */
220
+ async doResolve() {
221
+ // Abort any in-flight fetch (different from inFlightRequest promise tracking)
222
+ this.abort();
223
+ const { gatewayUrl, contentId, authToken, maxRetries = DEFAULT_MAX_RETRIES, initialDelayMs = DEFAULT_INITIAL_DELAY_MS, } = this.config;
224
+ // Validate required params
225
+ if (!gatewayUrl || !contentId) {
226
+ const error = "Missing required parameters: gatewayUrl or contentId";
227
+ this.setStatus("error", error);
228
+ throw new Error(error);
229
+ }
230
+ this.setStatus("loading");
231
+ const ac = new AbortController();
232
+ this.abortController = ac;
233
+ try {
234
+ const graphqlEndpoint = gatewayUrl.replace(/\/$/, "");
235
+ const res = await fetchWithRetry(graphqlEndpoint, {
236
+ method: "POST",
237
+ headers: {
238
+ "Content-Type": "application/json",
239
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
240
+ },
241
+ body: JSON.stringify({
242
+ query: RESOLVE_VIEWER_QUERY,
243
+ variables: { contentId },
244
+ }),
245
+ signal: ac.signal,
246
+ }, maxRetries, initialDelayMs);
247
+ if (!res.ok) {
248
+ throw new Error(`Gateway GQL error ${res.status}`);
249
+ }
250
+ const payload = await res.json();
251
+ if (payload.errors?.length) {
252
+ throw new Error(payload.errors[0]?.message || "GraphQL error");
253
+ }
254
+ const resp = payload.data?.resolveViewerEndpoint;
255
+ const primary = resp?.primary;
256
+ const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];
257
+ if (!primary) {
258
+ throw new Error("No endpoints available");
259
+ }
260
+ const endpoints = {
261
+ primary,
262
+ fallbacks,
263
+ metadata: resp?.metadata,
264
+ };
265
+ this.endpoints = endpoints;
266
+ // F2: Update cache timestamp
267
+ this.cacheTimestamp = Date.now();
268
+ this.setStatus("ready");
269
+ this.emit("endpointsResolved", { endpoints });
270
+ return endpoints;
271
+ }
272
+ catch (e) {
273
+ // Ignore abort errors
274
+ if (ac.signal.aborted) {
275
+ throw new Error("Request aborted");
276
+ }
277
+ const message = e instanceof Error ? e.message : "Unknown gateway error";
278
+ console.error("[GatewayClient] Gateway resolution failed:", message);
279
+ this.setStatus("error", message);
280
+ throw new Error(message);
281
+ }
282
+ }
283
+ /**
284
+ * Abort any in-flight request.
285
+ */
286
+ abort() {
287
+ if (this.abortController) {
288
+ this.abortController.abort();
289
+ this.abortController = null;
290
+ }
291
+ }
292
+ /**
293
+ * Get current status.
294
+ */
295
+ getStatus() {
296
+ return this.status;
297
+ }
298
+ /**
299
+ * Get resolved endpoints (null if not yet resolved).
300
+ */
301
+ getEndpoints() {
302
+ return this.endpoints;
303
+ }
304
+ /**
305
+ * Get error message (null if no error).
306
+ */
307
+ getError() {
308
+ return this.error;
309
+ }
310
+ /**
311
+ * Update configuration and reset state.
312
+ * F2: Also clears cache and in-flight request
313
+ * F3: Resets circuit breaker (new config = fresh start)
314
+ */
315
+ updateConfig(config) {
316
+ this.abort();
317
+ this.config = { ...this.config, ...config };
318
+ this.endpoints = null;
319
+ this.error = null;
320
+ // F2: Clear cache and in-flight tracking
321
+ this.cacheTimestamp = 0;
322
+ this.inFlightRequest = null;
323
+ // F3: Reset circuit breaker for new config
324
+ this.resetCircuitBreaker();
325
+ this.setStatus("idle");
326
+ }
327
+ /**
328
+ * Clean up resources.
329
+ */
330
+ destroy() {
331
+ this.abort();
332
+ this.removeAllListeners();
333
+ }
334
+ setStatus(status, error) {
335
+ this.status = status;
336
+ this.error = error ?? null;
337
+ this.emit("statusChange", { status, error });
338
+ }
339
+ }
340
+
341
+ exports.GatewayClient = GatewayClient;
342
+ //# sourceMappingURL=GatewayClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GatewayClient.js","sources":["../../../../src/core/GatewayClient.ts"],"sourcesContent":["/**\n * GatewayClient.ts\n *\n * Framework-agnostic client for resolving viewer endpoints from the Gateway GraphQL API.\n * Extracted from useViewerEndpoints.ts for use in headless core.\n */\n\nimport { TypedEventEmitter } from \"./EventEmitter\";\nimport type { ContentEndpoints, ContentType } from \"../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type GatewayStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\nexport interface GatewayClientConfig {\n /** Gateway GraphQL endpoint URL */\n gatewayUrl: string;\n /** Content identifier (stream name) */\n contentId: string;\n /** Optional content type (no longer required for resolution) */\n contentType?: ContentType;\n /** Optional auth token for private streams */\n authToken?: string;\n /** Maximum retry attempts (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in ms (default: 500) */\n initialDelayMs?: number;\n}\n\nexport interface GatewayClientEvents {\n /** Emitted when status changes */\n statusChange: { status: GatewayStatus; error?: string };\n /** Emitted when endpoints are successfully resolved */\n endpointsResolved: { endpoints: ContentEndpoints };\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 500;\n// F2: Cache TTL for resolved endpoints\nconst DEFAULT_CACHE_TTL_MS = 10000;\n// F3: Circuit breaker constants\nconst CIRCUIT_BREAKER_THRESHOLD = 5; // Open after 5 consecutive failures\nconst CIRCUIT_BREAKER_TIMEOUT_MS = 30000; // Half-open after 30 seconds\n\ntype CircuitBreakerState = \"closed\" | \"open\" | \"half-open\";\n\nconst RESOLVE_VIEWER_QUERY = `\n query ResolveViewer($contentId: String!) {\n resolveViewerEndpoint(contentId: $contentId) {\n primary { nodeId baseUrl protocol url geoDistance loadScore outputs }\n fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }\n metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }\n }\n }\n`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Fetch with exponential backoff retry logic.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n maxRetries: number,\n initialDelay: number\n): Promise<Response> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const response = await fetch(url, options);\n return response;\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(\"Fetch failed\");\n\n // Don't retry on abort\n if (options.signal?.aborted) {\n throw lastError;\n }\n\n // Wait before retrying (exponential backoff)\n if (attempt < maxRetries - 1) {\n const delay = initialDelay * Math.pow(2, attempt);\n console.warn(`[GatewayClient] Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError ?? new Error(\"Gateway unreachable after retries\");\n}\n\n// ============================================================================\n// GatewayClient Class\n// ============================================================================\n\n/**\n * Client for resolving viewer endpoints from the Gateway GraphQL API.\n *\n * @example\n * ```typescript\n * const client = new GatewayClient({\n * gatewayUrl: 'https://gateway.example.com/graphql',\n * contentId: 'pk_...', // playbackId (view key)\n * });\n *\n * client.on('statusChange', ({ status }) => console.log('Status:', status));\n * client.on('endpointsResolved', ({ endpoints }) => console.log('Endpoints:', endpoints));\n *\n * const endpoints = await client.resolve();\n * ```\n */\nexport class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {\n private config: GatewayClientConfig;\n private status: GatewayStatus = \"idle\";\n private endpoints: ContentEndpoints | null = null;\n private error: string | null = null;\n private abortController: AbortController | null = null;\n\n // F2: Request deduplication - in-flight request tracking\n private inFlightRequest: Promise<ContentEndpoints> | null = null;\n // F2: Cache with TTL for resolved endpoints\n private cacheTimestamp = 0;\n private cacheTtlMs: number;\n\n // F3: Circuit breaker state\n private circuitState: CircuitBreakerState = \"closed\";\n private consecutiveFailures = 0;\n private circuitOpenedAt = 0;\n\n constructor(config: GatewayClientConfig) {\n super();\n this.config = config;\n this.cacheTtlMs = DEFAULT_CACHE_TTL_MS;\n }\n\n /**\n * Resolve endpoints from the gateway.\n * F2: Returns cached result if still valid, deduplicates concurrent requests.\n * F3: Respects circuit breaker state.\n * @param forceRefresh - If true, bypasses cache and fetches fresh data\n * @returns Promise resolving to ContentEndpoints\n * @throws Error if resolution fails after retries or circuit is open\n */\n async resolve(forceRefresh = false): Promise<ContentEndpoints> {\n // F2: Return cached result if still valid\n if (!forceRefresh && this.endpoints && this.isCacheValid()) {\n return this.endpoints;\n }\n\n // F3: Check circuit breaker\n if (!this.canAttemptRequest()) {\n throw new Error(\"Circuit breaker is open - too many recent failures\");\n }\n\n // F2: Return in-flight request if one exists (deduplication)\n if (this.inFlightRequest) {\n return this.inFlightRequest;\n }\n\n // Create a new request and track it\n this.inFlightRequest = this.doResolve();\n\n try {\n const result = await this.inFlightRequest;\n // F3: Success - close circuit\n this.onSuccess();\n return result;\n } catch (e) {\n // F3: Failure - record for circuit breaker\n this.onFailure();\n throw e;\n } finally {\n this.inFlightRequest = null;\n }\n }\n\n /**\n * F2: Check if cache is still valid\n */\n private isCacheValid(): boolean {\n return Date.now() - this.cacheTimestamp < this.cacheTtlMs;\n }\n\n /**\n * F2: Set cache TTL (for testing or custom requirements)\n */\n setCacheTtl(ttlMs: number): void {\n this.cacheTtlMs = ttlMs;\n }\n\n /**\n * F2: Invalidate the cache manually\n */\n invalidateCache(): void {\n this.cacheTimestamp = 0;\n }\n\n // ==========================================================================\n // F3: Circuit Breaker Methods\n // ==========================================================================\n\n /**\n * F3: Check if a request can be attempted based on circuit state\n */\n private canAttemptRequest(): boolean {\n switch (this.circuitState) {\n case \"closed\":\n return true;\n\n case \"open\":\n // Check if enough time has passed to try half-open\n if (Date.now() - this.circuitOpenedAt >= CIRCUIT_BREAKER_TIMEOUT_MS) {\n this.circuitState = \"half-open\";\n return true;\n }\n return false;\n\n case \"half-open\":\n // Allow one request to test the circuit\n return true;\n }\n }\n\n /**\n * F3: Record a successful request\n */\n private onSuccess(): void {\n this.consecutiveFailures = 0;\n this.circuitState = \"closed\";\n }\n\n /**\n * F3: Record a failed request\n */\n private onFailure(): void {\n this.consecutiveFailures++;\n\n if (this.circuitState === \"half-open\") {\n // Failed during half-open - re-open the circuit\n this.circuitState = \"open\";\n this.circuitOpenedAt = Date.now();\n } else if (this.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {\n // Threshold reached - open the circuit\n this.circuitState = \"open\";\n this.circuitOpenedAt = Date.now();\n console.warn(\n `[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`\n );\n }\n }\n\n /**\n * F3: Get current circuit breaker state (for monitoring/debugging)\n */\n getCircuitState(): { state: CircuitBreakerState; failures: number; openedAt: number | null } {\n return {\n state: this.circuitState,\n failures: this.consecutiveFailures,\n openedAt: this.circuitState === \"open\" ? this.circuitOpenedAt : null,\n };\n }\n\n /**\n * F3: Manually reset the circuit breaker\n */\n resetCircuitBreaker(): void {\n this.circuitState = \"closed\";\n this.consecutiveFailures = 0;\n this.circuitOpenedAt = 0;\n }\n\n /**\n * Internal method to perform the actual resolution.\n * @returns Promise resolving to ContentEndpoints\n */\n private async doResolve(): Promise<ContentEndpoints> {\n // Abort any in-flight fetch (different from inFlightRequest promise tracking)\n this.abort();\n\n const {\n gatewayUrl,\n contentId,\n authToken,\n maxRetries = DEFAULT_MAX_RETRIES,\n initialDelayMs = DEFAULT_INITIAL_DELAY_MS,\n } = this.config;\n\n // Validate required params\n if (!gatewayUrl || !contentId) {\n const error = \"Missing required parameters: gatewayUrl or contentId\";\n this.setStatus(\"error\", error);\n throw new Error(error);\n }\n\n this.setStatus(\"loading\");\n\n const ac = new AbortController();\n this.abortController = ac;\n\n try {\n const graphqlEndpoint = gatewayUrl.replace(/\\/$/, \"\");\n\n const res = await fetchWithRetry(\n graphqlEndpoint,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n body: JSON.stringify({\n query: RESOLVE_VIEWER_QUERY,\n variables: { contentId },\n }),\n signal: ac.signal,\n },\n maxRetries,\n initialDelayMs\n );\n\n if (!res.ok) {\n throw new Error(`Gateway GQL error ${res.status}`);\n }\n\n const payload = await res.json();\n\n if (payload.errors?.length) {\n throw new Error(payload.errors[0]?.message || \"GraphQL error\");\n }\n\n const resp = payload.data?.resolveViewerEndpoint;\n const primary = resp?.primary;\n const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];\n\n if (!primary) {\n throw new Error(\"No endpoints available\");\n }\n\n const endpoints: ContentEndpoints = {\n primary,\n fallbacks,\n metadata: resp?.metadata,\n };\n\n this.endpoints = endpoints;\n // F2: Update cache timestamp\n this.cacheTimestamp = Date.now();\n this.setStatus(\"ready\");\n this.emit(\"endpointsResolved\", { endpoints });\n\n return endpoints;\n } catch (e) {\n // Ignore abort errors\n if (ac.signal.aborted) {\n throw new Error(\"Request aborted\");\n }\n\n const message = e instanceof Error ? e.message : \"Unknown gateway error\";\n console.error(\"[GatewayClient] Gateway resolution failed:\", message);\n this.setStatus(\"error\", message);\n throw new Error(message);\n }\n }\n\n /**\n * Abort any in-flight request.\n */\n abort(): void {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n /**\n * Get current status.\n */\n getStatus(): GatewayStatus {\n return this.status;\n }\n\n /**\n * Get resolved endpoints (null if not yet resolved).\n */\n getEndpoints(): ContentEndpoints | null {\n return this.endpoints;\n }\n\n /**\n * Get error message (null if no error).\n */\n getError(): string | null {\n return this.error;\n }\n\n /**\n * Update configuration and reset state.\n * F2: Also clears cache and in-flight request\n * F3: Resets circuit breaker (new config = fresh start)\n */\n updateConfig(config: Partial<GatewayClientConfig>): void {\n this.abort();\n this.config = { ...this.config, ...config };\n this.endpoints = null;\n this.error = null;\n // F2: Clear cache and in-flight tracking\n this.cacheTimestamp = 0;\n this.inFlightRequest = null;\n // F3: Reset circuit breaker for new config\n this.resetCircuitBreaker();\n this.setStatus(\"idle\");\n }\n\n /**\n * Clean up resources.\n */\n destroy(): void {\n this.abort();\n this.removeAllListeners();\n }\n\n private setStatus(status: GatewayStatus, error?: string): void {\n this.status = status;\n this.error = error ?? null;\n this.emit(\"statusChange\", { status, error });\n }\n}\n\nexport default GatewayClient;\n"],"names":["TypedEventEmitter"],"mappings":"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"}