@editframe/elements 0.17.6-beta.0 → 0.18.3-beta.0

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 (218) hide show
  1. package/dist/EF_FRAMEGEN.js +1 -1
  2. package/dist/ScrubTrackManager.d.ts +2 -2
  3. package/dist/elements/EFAudio.d.ts +21 -2
  4. package/dist/elements/EFAudio.js +41 -11
  5. package/dist/elements/EFImage.d.ts +1 -0
  6. package/dist/elements/EFImage.js +11 -3
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
  8. package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +47 -0
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +116 -0
  11. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
  12. package/dist/elements/EFMedia/BaseMediaEngine.js +96 -0
  13. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +159 -0
  15. package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
  16. package/dist/elements/EFMedia/JitMediaEngine.d.ts +31 -0
  17. package/dist/elements/EFMedia/JitMediaEngine.js +62 -0
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +9 -0
  19. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +16 -0
  20. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +48 -0
  21. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +3 -0
  22. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +138 -0
  23. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +9 -0
  24. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +4 -0
  25. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +16 -0
  26. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +9 -0
  27. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +3 -0
  28. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +22 -0
  29. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
  30. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +24 -0
  31. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
  32. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +18 -0
  33. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
  34. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +16 -0
  35. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
  36. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +104 -0
  37. package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +22 -0
  38. package/dist/elements/EFMedia/services/AudioElementFactory.js +72 -0
  39. package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +1 -0
  40. package/dist/elements/EFMedia/services/MediaSourceService.d.ts +47 -0
  41. package/dist/elements/EFMedia/services/MediaSourceService.js +73 -0
  42. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
  43. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
  44. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
  45. package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
  46. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
  47. package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +1 -0
  48. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
  49. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +1 -0
  50. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
  51. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
  52. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +1 -0
  53. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
  54. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
  55. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
  56. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
  57. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
  58. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
  59. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
  60. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
  61. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
  62. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
  63. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +25 -0
  64. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
  65. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
  66. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +18 -0
  67. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
  68. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
  69. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +16 -0
  70. package/dist/elements/EFMedia.browsertest.d.ts +1 -0
  71. package/dist/elements/EFMedia.d.ts +75 -111
  72. package/dist/elements/EFMedia.js +141 -1111
  73. package/dist/elements/EFTemporal.d.ts +1 -1
  74. package/dist/elements/EFTemporal.js +1 -1
  75. package/dist/elements/EFTimegroup.d.ts +11 -0
  76. package/dist/elements/EFTimegroup.js +88 -13
  77. package/dist/elements/EFVideo.d.ts +60 -29
  78. package/dist/elements/EFVideo.js +103 -203
  79. package/dist/elements/EFWaveform.js +2 -2
  80. package/dist/elements/SampleBuffer.d.ts +14 -0
  81. package/dist/elements/SampleBuffer.js +52 -0
  82. package/dist/getRenderInfo.d.ts +2 -2
  83. package/dist/getRenderInfo.js +2 -1
  84. package/dist/gui/ContextMixin.js +17 -70
  85. package/dist/gui/EFFilmstrip.d.ts +3 -3
  86. package/dist/gui/EFFilmstrip.js +1 -1
  87. package/dist/gui/EFFitScale.d.ts +2 -2
  88. package/dist/gui/TWMixin.js +1 -1
  89. package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +1 -0
  90. package/dist/gui/services/ElementConnectionManager.d.ts +59 -0
  91. package/dist/gui/services/ElementConnectionManager.js +128 -0
  92. package/dist/gui/services/PlaybackController.browsertest.d.ts +1 -0
  93. package/dist/gui/services/PlaybackController.d.ts +103 -0
  94. package/dist/gui/services/PlaybackController.js +290 -0
  95. package/dist/services/MediaSourceManager.d.ts +62 -0
  96. package/dist/services/MediaSourceManager.js +211 -0
  97. package/dist/style.css +1 -1
  98. package/dist/transcoding/cache/CacheManager.d.ts +73 -0
  99. package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
  100. package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
  101. package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
  102. package/dist/transcoding/types/index.d.ts +242 -0
  103. package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
  104. package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
  105. package/dist/transcoding/utils/UrlGenerator.js +45 -0
  106. package/dist/transcoding/utils/constants.d.ts +27 -0
  107. package/dist/utils/LRUCache.d.ts +34 -0
  108. package/dist/utils/LRUCache.js +115 -0
  109. package/package.json +3 -2
  110. package/src/elements/EFAudio.browsertest.ts +183 -43
  111. package/src/elements/EFAudio.ts +59 -13
  112. package/src/elements/EFImage.browsertest.ts +42 -0
  113. package/src/elements/EFImage.ts +23 -3
  114. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
  115. package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
  116. package/src/elements/EFMedia/AssetMediaEngine.ts +210 -0
  117. package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
  118. package/src/elements/EFMedia/BaseMediaEngine.ts +170 -0
  119. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +400 -0
  120. package/src/elements/EFMedia/BufferedSeekingInput.ts +267 -0
  121. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
  122. package/src/elements/EFMedia/JitMediaEngine.ts +110 -0
  123. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
  124. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
  125. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +241 -0
  126. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
  127. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
  129. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +35 -0
  130. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +42 -0
  131. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +34 -0
  132. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +23 -0
  133. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +174 -0
  134. package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +325 -0
  135. package/src/elements/EFMedia/services/AudioElementFactory.ts +119 -0
  136. package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +257 -0
  137. package/src/elements/EFMedia/services/MediaSourceService.ts +102 -0
  138. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
  139. package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
  140. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
  141. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
  142. package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
  143. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
  144. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
  145. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
  146. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
  147. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
  148. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
  149. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
  150. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
  151. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
  152. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +44 -0
  153. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
  154. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +32 -0
  155. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
  156. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +23 -0
  157. package/src/elements/EFMedia.browsertest.ts +658 -265
  158. package/src/elements/EFMedia.ts +173 -1763
  159. package/src/elements/EFTemporal.ts +3 -4
  160. package/src/elements/EFTimegroup.browsertest.ts +6 -3
  161. package/src/elements/EFTimegroup.ts +152 -21
  162. package/src/elements/EFVideo.browsertest.ts +115 -37
  163. package/src/elements/EFVideo.ts +123 -452
  164. package/src/elements/EFWaveform.ts +1 -1
  165. package/src/elements/MediaController.ts +2 -12
  166. package/src/elements/SampleBuffer.ts +97 -0
  167. package/src/gui/ContextMixin.ts +23 -104
  168. package/src/gui/services/ElementConnectionManager.browsertest.ts +263 -0
  169. package/src/gui/services/ElementConnectionManager.ts +224 -0
  170. package/src/gui/services/PlaybackController.browsertest.ts +437 -0
  171. package/src/gui/services/PlaybackController.ts +521 -0
  172. package/src/services/MediaSourceManager.ts +333 -0
  173. package/src/transcoding/cache/CacheManager.ts +208 -0
  174. package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
  175. package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
  176. package/src/transcoding/types/index.ts +265 -0
  177. package/src/transcoding/utils/MediaUtils.ts +63 -0
  178. package/src/transcoding/utils/UrlGenerator.ts +68 -0
  179. package/src/transcoding/utils/constants.ts +36 -0
  180. package/src/utils/LRUCache.ts +153 -0
  181. package/test/EFVideo.framegen.browsertest.ts +38 -29
  182. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/data.bin +0 -0
  183. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +21 -0
  184. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/data.bin +0 -0
  185. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +21 -0
  186. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/data.bin +0 -0
  187. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +21 -0
  188. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/data.bin +0 -0
  189. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +21 -0
  190. package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/data.bin +0 -0
  191. package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/metadata.json +21 -0
  192. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/data.bin +0 -0
  193. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +21 -0
  194. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
  195. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +21 -0
  196. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
  197. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +21 -0
  198. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
  199. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +21 -0
  200. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
  201. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +21 -0
  202. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
  203. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
  204. package/test/createJitTestClips.ts +320 -188
  205. package/test/recordReplayProxyPlugin.js +302 -0
  206. package/test/useAssetMSW.ts +1 -1
  207. package/test/useMSW.ts +35 -22
  208. package/types.json +1 -1
  209. package/dist/JitTranscodingClient.d.ts +0 -167
  210. package/dist/JitTranscodingClient.js +0 -373
  211. package/dist/ScrubTrackManager.js +0 -216
  212. package/dist/elements/printTaskStatus.js +0 -11
  213. package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
  214. package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
  215. /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
  216. /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
  217. /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
  218. /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/services/AudioElementFactory.browsertest.d.ts} +0 -0
@@ -0,0 +1,302 @@
1
+ import crypto from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const CACHE_DIR = join(__dirname, "__cache__");
9
+ const TARGET_HOST = "host.docker.internal";
10
+ const TARGET_PORT = 3000;
11
+
12
+ /**
13
+ * Vite plugin that adds record-and-replay proxy middleware
14
+ * This proxy intercepts requests to /api/v1/transcode/*, caches responses to disk,
15
+ * and serves cached responses when the real server is unavailable.
16
+ */
17
+ export function recordReplayProxyPlugin() {
18
+ return {
19
+ name: "record-replay-proxy",
20
+
21
+ configureServer(server) {
22
+ console.log(
23
+ "[Proxy Plugin] Configuring record-replay proxy middleware...",
24
+ );
25
+
26
+ // Initialize cache directory
27
+ mkdir(CACHE_DIR, { recursive: true }).catch(console.error);
28
+
29
+ // Add middleware to handle /api/v1/transcode/* requests
30
+ server.middlewares.use("/api/v1/transcode", async (req, res, next) => {
31
+ await handleProxyRequest(req, res, next);
32
+ });
33
+
34
+ console.log("[Proxy Plugin] Proxy middleware configured");
35
+ console.log(`[Proxy Plugin] Cache directory: ${CACHE_DIR}`);
36
+ },
37
+ };
38
+
39
+ // Create cache key from request
40
+ function getCacheKey(method, url, headers) {
41
+ const range = headers.range || "";
42
+ const key = `${method}_${url}_${range}`;
43
+ const hash = crypto.createHash("md5").update(key).digest("hex");
44
+ const sanitized = key.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 100);
45
+ return `${sanitized}_${hash}`; // Returns directory name
46
+ }
47
+
48
+ // Serve cached response
49
+ async function serveCachedResponse(res, cacheDir, req) {
50
+ try {
51
+ const metadataFile = join(cacheDir, "metadata.json");
52
+ const dataFile = join(cacheDir, "data.bin");
53
+
54
+ const metadata = JSON.parse(await readFile(metadataFile, "utf-8"));
55
+ let body = await readFile(dataFile);
56
+
57
+ // Rewrite URLs in cached JSON/text responses to point back to current proxy
58
+ const responseHeaders = { ...metadata.headers };
59
+ const contentType = responseHeaders["content-type"] || "";
60
+ if (
61
+ contentType.includes("application/json") ||
62
+ contentType.includes("text/")
63
+ ) {
64
+ try {
65
+ const originalHost = `http://${TARGET_HOST}:${TARGET_PORT}`;
66
+ const proxyHost = `${req.headers["x-forwarded-proto"] || "http"}://${req.headers.host}`;
67
+ const bodyText = body.toString("utf-8");
68
+ const rewrittenText = bodyText.replace(
69
+ new RegExp(originalHost, "g"),
70
+ proxyHost,
71
+ );
72
+ body = Buffer.from(rewrittenText, "utf-8");
73
+
74
+ // Update content-length if it changed
75
+ if (bodyText.length !== rewrittenText.length) {
76
+ responseHeaders["content-length"] = body.length.toString();
77
+ }
78
+
79
+ console.log(
80
+ `[Proxy] ✓ Rewrote cached URLs: ${originalHost} → ${proxyHost}`,
81
+ );
82
+ } catch (error) {
83
+ console.warn(
84
+ `[Proxy] Failed to rewrite cached URLs: ${error.message}`,
85
+ );
86
+ // Continue with original body on error
87
+ }
88
+ }
89
+
90
+ res.writeHead(metadata.statusCode, responseHeaders);
91
+ res.end(body);
92
+ } catch (error) {
93
+ console.error(
94
+ `[Proxy] Failed to serve cached response: ${error.message}`,
95
+ );
96
+ res.writeHead(500, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify({ error: "Failed to read cache" }));
98
+ }
99
+ }
100
+
101
+ // Save response to cache
102
+ async function cacheResponse(
103
+ cacheDir,
104
+ statusCode,
105
+ headers,
106
+ body,
107
+ method,
108
+ url,
109
+ range,
110
+ ) {
111
+ try {
112
+ await mkdir(cacheDir, { recursive: true }); // Create cache directory
113
+
114
+ const metadata = {
115
+ statusCode,
116
+ headers,
117
+ url,
118
+ method,
119
+ range: range || null,
120
+ timestamp: new Date().toISOString(),
121
+ };
122
+ const metadataFile = join(cacheDir, "metadata.json");
123
+ await writeFile(metadataFile, JSON.stringify(metadata, null, 2));
124
+
125
+ const dataFile = join(cacheDir, "data.bin");
126
+ await writeFile(dataFile, body); // Write raw binary data
127
+
128
+ console.log("[Proxy] ✓ Cached response");
129
+ } catch (error) {
130
+ console.warn(`[Proxy] Failed to cache: ${error.message}`);
131
+ }
132
+ }
133
+
134
+ // Handle proxy request as middleware
135
+ async function handleProxyRequest(req, res, next) {
136
+ const fullPath = `/api/v1/transcode${req.url}`;
137
+ console.log(`[Proxy] → ${req.method} ${fullPath}`);
138
+ if (req.headers.range) {
139
+ console.log(`[Proxy] Range: ${req.headers.range}`);
140
+ }
141
+
142
+ // Set CORS headers
143
+ res.setHeader("Access-Control-Allow-Origin", "*");
144
+ res.setHeader(
145
+ "Access-Control-Allow-Methods",
146
+ "GET, POST, PUT, DELETE, OPTIONS",
147
+ );
148
+ res.setHeader(
149
+ "Access-Control-Allow-Headers",
150
+ "Content-Type, Range, Authorization",
151
+ );
152
+
153
+ if (req.method === "OPTIONS") {
154
+ res.writeHead(200);
155
+ res.end();
156
+ return;
157
+ }
158
+
159
+ const cacheKey = getCacheKey(req.method, fullPath, req.headers);
160
+ const cacheDir = join(CACHE_DIR, cacheKey);
161
+
162
+ try {
163
+ // Collect request body
164
+ const requestChunks = [];
165
+ req.on("data", (chunk) => {
166
+ requestChunks.push(chunk);
167
+ });
168
+
169
+ req.on("end", async () => {
170
+ try {
171
+ const requestBody = Buffer.concat(requestChunks);
172
+ // Reconstruct the full path since middleware strips the prefix
173
+ const fullPath = `/api/v1/transcode${req.url}`;
174
+ const targetUrl = `http://${TARGET_HOST}:${TARGET_PORT}${fullPath}`;
175
+
176
+ const fetchOptions = {
177
+ method: req.method,
178
+ headers: req.headers,
179
+ body: requestBody.length > 0 ? requestBody : undefined,
180
+ };
181
+
182
+ const response = await fetch(targetUrl, fetchOptions);
183
+ let body = Buffer.from(await response.arrayBuffer());
184
+
185
+ const responseHeaders = {};
186
+ response.headers.forEach((value, key) => {
187
+ responseHeaders[key] = value;
188
+ });
189
+
190
+ // Rewrite URLs in JSON/text responses to point back to proxy
191
+ const contentType = responseHeaders["content-type"] || "";
192
+ if (
193
+ contentType.includes("application/json") ||
194
+ contentType.includes("text/")
195
+ ) {
196
+ try {
197
+ const originalHost = `http://${TARGET_HOST}:${TARGET_PORT}`;
198
+ const proxyHost = `${req.headers["x-forwarded-proto"] || "http"}://${req.headers.host}`;
199
+ const bodyText = body.toString("utf-8");
200
+ const rewrittenText = bodyText.replace(
201
+ new RegExp(originalHost, "g"),
202
+ proxyHost,
203
+ );
204
+ body = Buffer.from(rewrittenText, "utf-8");
205
+
206
+ // Update content-length if it changed
207
+ if (bodyText.length !== rewrittenText.length) {
208
+ responseHeaders["content-length"] = body.length.toString();
209
+ }
210
+
211
+ console.log(
212
+ `[Proxy] ✓ Rewrote URLs: ${originalHost} → ${proxyHost}`,
213
+ );
214
+ } catch (error) {
215
+ console.warn(`[Proxy] Failed to rewrite URLs: ${error.message}`);
216
+ // Continue with original body on error
217
+ }
218
+ }
219
+
220
+ // If we get a 404, try to serve from cache first
221
+ if (response.status === 404) {
222
+ console.log("[Proxy] ✗ Target server returned 404");
223
+ console.log(`[Proxy] Checking cache: ${cacheKey}`);
224
+
225
+ if (existsSync(cacheDir)) {
226
+ try {
227
+ const metadataFile = join(cacheDir, "metadata.json");
228
+ if (existsSync(metadataFile)) {
229
+ const metadata = JSON.parse(
230
+ await readFile(metadataFile, "utf-8"),
231
+ );
232
+ console.log(
233
+ `[Proxy] ✓ Serving from cache instead of 404 (${metadata.timestamp})`,
234
+ );
235
+ await serveCachedResponse(res, cacheDir, req);
236
+ return;
237
+ }
238
+ } catch (cacheError) {
239
+ console.error(
240
+ `[Proxy] Failed to read cache: ${cacheError.message}`,
241
+ );
242
+ }
243
+ }
244
+
245
+ console.log("[Proxy] ✗ No cache available, passing through 404");
246
+ }
247
+
248
+ res.writeHead(response.status, responseHeaders);
249
+ res.end(body);
250
+
251
+ if (response.status >= 200 && response.status < 300) {
252
+ await cacheResponse(
253
+ cacheDir,
254
+ response.status,
255
+ responseHeaders,
256
+ body,
257
+ req.method,
258
+ fullPath,
259
+ req.headers.range,
260
+ );
261
+ }
262
+ } catch (err) {
263
+ console.log(`[Proxy] ✗ Real server failed: ${err.message}`);
264
+ console.log(`[Proxy] Checking cache: ${cacheKey}`);
265
+
266
+ if (existsSync(cacheDir)) {
267
+ try {
268
+ const metadataFile = join(cacheDir, "metadata.json");
269
+ if (existsSync(metadataFile)) {
270
+ const metadata = JSON.parse(
271
+ await readFile(metadataFile, "utf-8"),
272
+ );
273
+ console.log(
274
+ `[Proxy] ✓ Serving from cache (${metadata.timestamp})`,
275
+ );
276
+ await serveCachedResponse(res, cacheDir, req);
277
+ return;
278
+ }
279
+ } catch (cacheError) {
280
+ console.error(
281
+ `[Proxy] Failed to read cache: ${cacheError.message}`,
282
+ );
283
+ }
284
+ }
285
+
286
+ console.log("[Proxy] ✗ No cache available, failing request");
287
+ res.writeHead(500, { "Content-Type": "application/json" });
288
+ res.end(
289
+ JSON.stringify({
290
+ error: "Server unavailable and no cache found",
291
+ cacheKey,
292
+ originalError: err.message,
293
+ }),
294
+ );
295
+ }
296
+ });
297
+ } catch (error) {
298
+ console.error(`[Proxy] Middleware error: ${error.message}`);
299
+ next();
300
+ }
301
+ }
302
+ }
@@ -3,7 +3,7 @@
3
3
  * Provides pre-configured handlers for asset fragment indexes and track data
4
4
  */
5
5
 
6
- import { http, HttpResponse } from "msw";
6
+ import { HttpResponse, http } from "msw";
7
7
 
8
8
  /**
9
9
  * Asset MSW handlers that redirect requests to real test assets
package/test/useMSW.ts CHANGED
@@ -1,31 +1,44 @@
1
1
  /**
2
- * MSW server setup utility for testing
3
- * Provides a properly configured MSW worker with lifecycle management for browser tests
2
+ * MSW integration for Vitest Browser Mode
3
+ * Based on: https://mswjs.io/docs/recipes/vitest-browser-mode/
4
4
  */
5
5
 
6
6
  import { setupWorker } from "msw/browser";
7
- import { afterAll, afterEach, beforeAll } from "vitest";
7
+ import { test as testBase } from "vitest";
8
8
 
9
- /**
10
- * Set up MSW worker for browser testing with proper lifecycle
11
- */
12
- export function useMSW() {
13
- const worker = setupWorker();
9
+ // Create the worker instance that will be shared across tests
10
+ const worker = setupWorker();
14
11
 
15
- beforeAll(async () => {
16
- await worker.start({
17
- onUnhandledRequest: "bypass", // Don't warn about unhandled requests
18
- quiet: true, // Disable logging of matched requests
19
- });
20
- });
12
+ // Start the worker once when this module is loaded
13
+ let workerStarted = false;
21
14
 
22
- afterEach(() => {
23
- worker.resetHandlers();
24
- });
15
+ /**
16
+ * Extended test with MSW worker integration for Vitest Browser Mode
17
+ * This follows the official MSW recommendation for Vitest Browser Mode
18
+ */
19
+ export const test = testBase.extend<{
20
+ worker: typeof worker;
21
+ }>({
22
+ worker: [
23
+ async ({}, use) => {
24
+ // Only start the worker once
25
+ if (!workerStarted) {
26
+ await worker.start({
27
+ onUnhandledRequest: "bypass", // Allow unhandled requests to pass through to server
28
+ quiet: true, // Enable logging to see what's being intercepted
29
+ });
30
+ workerStarted = true;
31
+ }
25
32
 
26
- afterAll(() => {
27
- worker.stop();
28
- });
33
+ // Expose the worker object on the test's context
34
+ await use(worker);
29
35
 
30
- return worker;
31
- }
36
+ // Remove any request handlers added in individual test cases
37
+ // This prevents them from affecting unrelated tests
38
+ worker.resetHandlers();
39
+ },
40
+ {
41
+ auto: true, // Critical: Auto-start MSW for all tests
42
+ },
43
+ ],
44
+ });