From 2fc3a485f079736fd8a75a32f698fdd86c543b3d Mon Sep 17 00:00:00 2001 From: Jvillegasd Date: Sat, 28 Feb 2026 00:42:23 -0500 Subject: [PATCH 1/3] refactor: convert VideoFormat string union to string enum Replace `type VideoFormat = "direct" | "hls" | "m3u8" | "unknown"` with a string enum following the existing DownloadStage/MessageType pattern. Updates all 40+ comparison sites across 13 files to use enum values (VideoFormat.DIRECT, .HLS, .M3U8, .UNKNOWN) instead of magic strings. Lowercase string values preserve backward compatibility with IndexedDB. Co-Authored-By: Claude Sonnet 4.6 --- src/content.ts | 4 +-- src/core/detection/detection-manager.ts | 6 ++--- .../direct/direct-detection-handler.ts | 4 +-- .../detection/hls/hls-detection-handler.ts | 10 ++++---- src/core/downloader/download-manager.ts | 8 +++--- src/core/types.ts | 7 +++++- src/core/utils/url-utils.ts | 14 +++++------ src/popup/popup.ts | 4 +-- src/popup/render-downloads.ts | 10 ++++---- src/popup/render-manifest.ts | 6 ++--- src/popup/render-videos.ts | 8 +++--- src/popup/utils.ts | 8 +++--- src/service-worker.ts | 25 +++++++++++++------ 13 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/content.ts b/src/content.ts index 7a7cdf6..1d3dd4d 100644 --- a/src/content.ts +++ b/src/content.ts @@ -4,7 +4,7 @@ */ import { MessageType } from "./shared/messages"; -import { VideoMetadata } from "./core/types"; +import { VideoMetadata, VideoFormat } from "./core/types"; import { DetectionManager } from "./core/detection/detection-manager"; import { normalizeUrl } from "./core/utils/url-utils"; import { logger } from "./core/utils/logger"; @@ -68,7 +68,7 @@ function removeDetectedVideo(url: string): void { */ function addDetectedVideo(video: VideoMetadata) { // Reject unknown formats - don't show them in UI - if (video.format === "unknown") { + if (video.format === VideoFormat.UNKNOWN) { return; } diff --git a/src/core/detection/detection-manager.ts b/src/core/detection/detection-manager.ts index fb3cba5..ab79f68 100644 --- a/src/core/detection/detection-manager.ts +++ b/src/core/detection/detection-manager.ts @@ -20,7 +20,7 @@ * @module DetectionManager */ -import { VideoMetadata } from "../types"; +import { VideoMetadata, VideoFormat } from "../types"; import { logger } from "../utils/logger"; import { detectFormatFromUrl } from "../utils/url-utils"; import { DirectDetectionHandler } from "./direct/direct-detection-handler"; @@ -68,12 +68,12 @@ export class DetectionManager { const format = detectFormatFromUrl(url); switch (format) { - case "direct": + case VideoFormat.DIRECT: logger.debug("[Media Bridge] Direct video detected", { url }); this.directHandler.handleNetworkRequest(url); break; - case "hls": + case VideoFormat.HLS: logger.debug("[Media Bridge] HLS video detected", { url }); this.hlsHandler.handleNetworkRequest(url); break; diff --git a/src/core/detection/direct/direct-detection-handler.ts b/src/core/detection/direct/direct-detection-handler.ts index 48fd302..90bad85 100644 --- a/src/core/detection/direct/direct-detection-handler.ts +++ b/src/core/detection/direct/direct-detection-handler.ts @@ -23,7 +23,7 @@ * @module DirectDetectionHandler */ -import { VideoMetadata } from "../../types"; +import { VideoMetadata, VideoFormat } from "../../types"; import { detectFormatFromUrl } from "../../utils/url-utils"; import { extractThumbnail } from "../../utils/thumbnail-utils"; @@ -339,7 +339,7 @@ export class DirectDetectionHandler { const format = detectFormatFromUrl(url); // Reject unknown formats - if (format === "unknown") { + if (format === VideoFormat.UNKNOWN) { return null; } diff --git a/src/core/detection/hls/hls-detection-handler.ts b/src/core/detection/hls/hls-detection-handler.ts index 3917ca8..661997c 100644 --- a/src/core/detection/hls/hls-detection-handler.ts +++ b/src/core/detection/hls/hls-detection-handler.ts @@ -25,7 +25,7 @@ * @module HlsDetectionHandler */ -import { VideoMetadata } from "../../types"; +import { VideoMetadata, VideoFormat } from "../../types"; import { isMasterPlaylist, isMediaPlaylist, @@ -168,7 +168,7 @@ export class HlsDetectionHandler { // A media playlist without #EXT-X-ENDLIST is a live stream const isLive = !playlistText.includes("#EXT-X-ENDLIST"); logger.info("[Media Bridge] Detected standalone M3U8 media playlist", url); - return await this.addDetectedVideo(url, "m3u8", playlistText, isLive); + return await this.addDetectedVideo(url, VideoFormat.M3U8, playlistText, isLive); } /** @@ -197,7 +197,7 @@ export class HlsDetectionHandler { // Determine liveness by fetching the first variant playlist and checking for #EXT-X-ENDLIST const isLive = await this.checkMasterIsLive(levels); - return await this.addDetectedVideo(url, "hls", playlistText, isLive); + return await this.addDetectedVideo(url, VideoFormat.HLS, playlistText, isLive); } /** @@ -268,7 +268,7 @@ export class HlsDetectionHandler { */ private async addDetectedVideo( url: string, - format: "hls" | "m3u8", + format: VideoFormat.HLS | VideoFormat.M3U8, playlistText: string, isLive: boolean = false, ): Promise { @@ -372,7 +372,7 @@ export class HlsDetectionHandler { */ private async extractMetadata( url: string, - format: "hls" | "m3u8" = "hls", + format: VideoFormat.HLS | VideoFormat.M3U8 = VideoFormat.HLS, playlistText: string, isLive: boolean = false, ): Promise { diff --git a/src/core/downloader/download-manager.ts b/src/core/downloader/download-manager.ts index 7b5396e..0be4fd3 100644 --- a/src/core/downloader/download-manager.ts +++ b/src/core/downloader/download-manager.ts @@ -115,7 +115,7 @@ export class DownloadManager { ); // Validate format from metadata (should already be set by detection) - if (metadata.format === "unknown") { + if (metadata.format === VideoFormat.UNKNOWN) { const error = new Error(`Video format is unknown for URL: ${url}`); return await this.createFailedState( state.id, @@ -136,7 +136,7 @@ export class DownloadManager { const actualVideoUrl = metadata.url; // Route to appropriate download handler based on format - if (format === "direct") { + if (format === VideoFormat.DIRECT) { // Use direct download handler with Chrome downloads API // AbortSignal is used to cancel the HEAD request for extension detection // The actual Chrome download is cancelled via chrome.downloads.cancel @@ -146,7 +146,7 @@ export class DownloadManager { state.id, abortSignal, ); - } else if (format === "hls") { + } else if (format === VideoFormat.HLS) { // Use HLS download handler await this.hlsDownloadHandler.download( actualVideoUrl, @@ -156,7 +156,7 @@ export class DownloadManager { abortSignal, metadata.pageUrl, ); - } else if (format === "m3u8") { + } else if (format === VideoFormat.M3U8) { // Use M3U8 download handler await this.m3u8DownloadHandler.download( actualVideoUrl, diff --git a/src/core/types.ts b/src/core/types.ts index b0d2fff..6ec995f 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -2,7 +2,12 @@ * Type definitions for Media Bridge Extension */ -export type VideoFormat = "direct" | "hls" | "m3u8" | "unknown"; +export enum VideoFormat { + DIRECT = "direct", + HLS = "hls", + M3U8 = "m3u8", + UNKNOWN = "unknown", +} export interface VideoMetadata { url: string; diff --git a/src/core/utils/url-utils.ts b/src/core/utils/url-utils.ts index 9fa066c..71d494a 100644 --- a/src/core/utils/url-utils.ts +++ b/src/core/utils/url-utils.ts @@ -27,26 +27,26 @@ export function normalizeUrl(url: string): string { export function detectFormatFromUrl(url: string): VideoFormat { // Handle blob URLs - these are already video blobs, treat as direct if (url.startsWith("blob:")) { - return "direct"; + return VideoFormat.DIRECT; } // Handle data URLs - treat as direct if (url.startsWith("data:")) { - return "direct"; + return VideoFormat.DIRECT; } let urlObj: URL; try { urlObj = new URL(url); } catch (error) { - return "unknown"; + return VideoFormat.UNKNOWN; } const pathnameLower = urlObj.pathname.toLowerCase(); // Check for HLS playlist files (.m3u8) on pathname only if (pathnameLower.endsWith(".m3u8")) { - return "hls"; + return VideoFormat.HLS; } // Check for common video extensions on pathname only @@ -61,14 +61,14 @@ export function detectFormatFromUrl(url: string): VideoFormat { ".wmv", ]; if (videoExtensions.some((ext) => pathnameLower.endsWith(ext))) { - return "direct"; + return VideoFormat.DIRECT; } // If no clear format detected but it looks like a video URL, assume direct // Many video CDNs don't include file extensions if (urlObj.pathname.match(/\/(video|stream|media|v|embed)\//i)) { - return "direct"; + return VideoFormat.DIRECT; } - return "unknown"; + return VideoFormat.UNKNOWN; } diff --git a/src/popup/popup.ts b/src/popup/popup.ts index 203294d..489b35d 100644 --- a/src/popup/popup.ts +++ b/src/popup/popup.ts @@ -2,7 +2,7 @@ * Popup entry point: initialization, tab switching, theme, and message routing. */ -import { VideoMetadata, DownloadStage } from "../core/types"; +import { VideoMetadata, DownloadStage, VideoFormat } from "../core/types"; import { getAllDownloads, deleteDownload } from "../core/database/downloads"; import { MessageType } from "../shared/messages"; import { normalizeUrl } from "../core/utils/url-utils"; @@ -232,7 +232,7 @@ function removeDetectedVideo(url: string | undefined): void { } function addDetectedVideo(video: VideoMetadata): void { - if (video.format === "unknown") { + if (video.format === VideoFormat.UNKNOWN) { const normalizedUrl = normalizeUrl(video.url); if (detectedVideos[normalizedUrl]) { delete detectedVideos[normalizedUrl]; diff --git a/src/popup/render-downloads.ts b/src/popup/render-downloads.ts index 3e438b9..b2b8e46 100644 --- a/src/popup/render-downloads.ts +++ b/src/popup/render-downloads.ts @@ -2,7 +2,7 @@ * Downloads tab rendering with incremental DOM updates. */ -import { DownloadState, DownloadStage } from "../core/types"; +import { DownloadState, DownloadStage, VideoFormat } from "../core/types"; import { storeDownload } from "../core/database/downloads"; import { dom, downloadStates } from "./state"; import { @@ -47,7 +47,7 @@ function updateDownloadCardProgress(card: HTMLElement, download: DownloadState): const isRecording = stage === DownloadStage.RECORDING; const isManifestDownload = - (download.metadata.format === "hls" || download.metadata.format === "m3u8") && + (download.metadata.format === VideoFormat.HLS || download.metadata.format === VideoFormat.M3U8) && (stage === DownloadStage.DOWNLOADING || stage === DownloadStage.MERGING); if (isRecording) { @@ -173,8 +173,8 @@ function renderDownloadItem(download: DownloadState): string { let progressBar = ""; const isRecording = stage === DownloadStage.RECORDING; const isManifestDownload = - (download.metadata.format === "hls" || - download.metadata.format === "m3u8") && + (download.metadata.format === VideoFormat.HLS || + download.metadata.format === VideoFormat.M3U8) && (stage === DownloadStage.DOWNLOADING || stage === DownloadStage.MERGING); if (isRecording) { @@ -287,7 +287,7 @@ function renderDownloadItem(download: DownloadState): string { `; } else { const isDownloading = download.progress.stage === DownloadStage.DOWNLOADING; - const isManifestType = download.metadata.format === "hls" || download.metadata.format === "m3u8"; + const isManifestType = download.metadata.format === VideoFormat.HLS || download.metadata.format === VideoFormat.M3U8; actionButtons = `
${isDownloading && isManifestType ? `` : ``} diff --git a/src/popup/render-manifest.ts b/src/popup/render-manifest.ts index 9b2923f..a86ac34 100644 --- a/src/popup/render-manifest.ts +++ b/src/popup/render-manifest.ts @@ -2,7 +2,7 @@ * Manual manifest tab: load playlist, quality selection, start download. */ -import { VideoMetadata, DownloadStage } from "../core/types"; +import { VideoMetadata, DownloadStage, VideoFormat } from "../core/types"; import { normalizeUrl, detectFormatFromUrl } from "../core/utils/url-utils"; import { parseMasterPlaylist, isMasterPlaylist, isMediaPlaylist } from "../core/utils/m3u8-parser"; import { hasDrm, canDecrypt } from "../core/utils/drm-utils"; @@ -130,7 +130,7 @@ export async function handleLoadManifestPlaylist(): Promise { const normalizedUrl = normalizeUrl(rawUrl); const format = detectFormatFromUrl(normalizedUrl); - if (format !== "hls") { + if (format !== VideoFormat.HLS) { alert("Please enter a valid manifest URL (.m3u8 or .mpd)"); return; } @@ -339,7 +339,7 @@ export async function handleStartManifestDownload(): Promise { const metadata: VideoMetadata = { url: playlistUrl, - format: isMediaPlaylistMode ? "m3u8" : "hls", + format: isMediaPlaylistMode ? VideoFormat.M3U8 : VideoFormat.HLS, title: tabTitle || "Manifest Video", pageUrl: pageUrl || window.location.href, isLive: isLiveManifest, diff --git a/src/popup/render-videos.ts b/src/popup/render-videos.ts index 3f0ec7b..315fd46 100644 --- a/src/popup/render-videos.ts +++ b/src/popup/render-videos.ts @@ -2,7 +2,7 @@ * Detected videos tab rendering. */ -import { DownloadState, DownloadStage, VideoMetadata } from "../core/types"; +import { DownloadState, DownloadStage, VideoMetadata, VideoFormat } from "../core/types"; import { normalizeUrl } from "../core/utils/url-utils"; import { MessageType } from "../shared/messages"; import { dom, detectedVideos, downloadStates } from "./state"; @@ -63,7 +63,7 @@ function updateVideoCardProgress(card: HTMLElement, video: VideoMetadata): boole } const isManifestDownload = - (video.format === "hls" || video.format === "m3u8") && + (video.format === VideoFormat.HLS || video.format === VideoFormat.M3U8) && (stage === DownloadStage.DOWNLOADING || stage === DownloadStage.MERGING); if (isManifestDownload && stage === DownloadStage.DOWNLOADING) { @@ -357,7 +357,7 @@ function renderVideoItem(video: VideoMetadata): string { } const isManifestDownload = - (video.format === "hls" || video.format === "m3u8") && + (video.format === VideoFormat.HLS || video.format === VideoFormat.M3U8) && (stage === DownloadStage.DOWNLOADING || stage === DownloadStage.MERGING); if (stage === DownloadStage.RECORDING) { @@ -460,7 +460,7 @@ function renderVideoItem(video: VideoMetadata): string { ${buttonText} ` : ""} - ${(video.format === "hls" || video.format === "m3u8") && !hasDrm && !unsupported ? ` + ${(video.format === VideoFormat.HLS || video.format === VideoFormat.M3U8) && !hasDrm && !unsupported ? `