Merge pull request 'Add manifest URL field, reset keys when manifest changes, organize repo, update to Manifest v3' (#3) from voldemort/CDRM-Extension:main into main
Reviewed-on: #3
This commit is contained in:
		
						commit
						e9212dfbf9
					
				
							
								
								
									
										30
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					# Logs
 | 
				
			||||||
 | 
					logs
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					npm-debug.log*
 | 
				
			||||||
 | 
					yarn-debug.log*
 | 
				
			||||||
 | 
					yarn-error.log*
 | 
				
			||||||
 | 
					pnpm-debug.log*
 | 
				
			||||||
 | 
					lerna-debug.log*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					dist
 | 
				
			||||||
 | 
					dist-ssr
 | 
				
			||||||
 | 
					*.local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					frontend/node_modules
 | 
				
			||||||
 | 
					frontend/dist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Editor directories and files
 | 
				
			||||||
 | 
					.vscode/*
 | 
				
			||||||
 | 
					!.vscode/extensions.json
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					*.suo
 | 
				
			||||||
 | 
					*.ntvs*
 | 
				
			||||||
 | 
					*.njsproj
 | 
				
			||||||
 | 
					*.sln
 | 
				
			||||||
 | 
					*.sw?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# extension release folder
 | 
				
			||||||
 | 
					extension-release
 | 
				
			||||||
							
								
								
									
										3
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					react/
 | 
				
			||||||
 | 
					frontend/dist/
 | 
				
			||||||
 | 
					frontend/src/assets/
 | 
				
			||||||
							
								
								
									
										8
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "trailingComma": "es5",
 | 
				
			||||||
 | 
					    "tabWidth": 4,
 | 
				
			||||||
 | 
					    "semi": true,
 | 
				
			||||||
 | 
					    "singleQuote": false,
 | 
				
			||||||
 | 
					    "useTabs": false,
 | 
				
			||||||
 | 
					    "printWidth": 100
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					# CDRM Extension
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An extension to show keys from DRM protected content, which are used to decrypt content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Keep these extension core files inside `src`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `background.js`
 | 
				
			||||||
 | 
					- `content.js`
 | 
				
			||||||
 | 
					- `inject.js`
 | 
				
			||||||
 | 
					- `manifest.json`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `mv2` folder is for Manifest v2 backup for legacy reasons.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Frontend React source stays in `frontend`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The build process will take care of everything into `extension-release`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To update the version across the entire project, simply change the version number in the root `package.json`. The build script will handle version sync automatically to both the extension's version and the frontend's title bar.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Build instructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Node.js v21 or higher. [Download Node.js here](https://nodejs.org/en/download).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How to build by yourself
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Open terminal at the project root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Run the build script:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					npm install
 | 
				
			||||||
 | 
					npm run buildext
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This will:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Sync the version number from the root `package.json` to `src/manifest.json` and `frontend/package.json`
 | 
				
			||||||
 | 
					- Install frontend dependencies if needed
 | 
				
			||||||
 | 
					- Build the React frontend
 | 
				
			||||||
 | 
					- Clean and prepare the `extension-release` folder
 | 
				
			||||||
 | 
					- Copy extension files in `src`, built frontend assets, and icons into `extension-release`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How to load the extension in Google Chrome or Chromium browsers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Go to `chrome://extensions/`
 | 
				
			||||||
 | 
					2. Enable **Developer mode**
 | 
				
			||||||
 | 
					3. Click **Load unpacked** and select the `extension-release` folder
 | 
				
			||||||
 | 
					4. Verify the extension is working by clicking its icon or opening the developer console (F12) to check for any logs or errors.
 | 
				
			||||||
@ -1,84 +0,0 @@
 | 
				
			|||||||
// Open popout window when the extension icon is clicked
 | 
					 | 
				
			||||||
chrome.browserAction.onClicked.addListener(() => {
 | 
					 | 
				
			||||||
  chrome.windows.create({
 | 
					 | 
				
			||||||
    url: chrome.runtime.getURL("react/index.html"),
 | 
					 | 
				
			||||||
    type: "popup",  // opens as a floating window
 | 
					 | 
				
			||||||
    width: 800,
 | 
					 | 
				
			||||||
    height: 600
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Listen for messages and store data in chrome.storage.local
 | 
					 | 
				
			||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
 | 
					 | 
				
			||||||
  const { type, data } = message;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  switch (type) {
 | 
					 | 
				
			||||||
    case "DRM_TYPE":
 | 
					 | 
				
			||||||
      console.log("DRM Type:", data);
 | 
					 | 
				
			||||||
      chrome.storage.local.set({ drmType: data });
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case "PSSH_DATA":
 | 
					 | 
				
			||||||
      console.log("Storing PSSH:", data);
 | 
					 | 
				
			||||||
      chrome.storage.local.set({ latestPSSH: data });
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    case "KEYS_DATA":
 | 
					 | 
				
			||||||
      console.log("Storing Decryption Keys:", data);
 | 
					 | 
				
			||||||
      chrome.storage.local.set({ latestKeys: data });
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    case "LICENSE_URL":
 | 
					 | 
				
			||||||
      console.log("Storling License URL " + data);
 | 
					 | 
				
			||||||
      chrome.storage.local.set({licenseURL: data});
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      console.warn("Unknown message type received:", type);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Set initial config and injection type on install
 | 
					 | 
				
			||||||
chrome.runtime.onInstalled.addListener((details) => {
 | 
					 | 
				
			||||||
  if (details.reason === "install") {
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ valid_config: false }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error setting valid_config:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log("valid_config set to false on first install.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ injection_type: "LICENSE" }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error setting Injection Type:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log("Injection type set to LICENSE on first install.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ drm_override: "DISABLED" }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error setting DRM Override type:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log("DRM Override type set to DISABLED on first install.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ cdrm_instance: null }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error setting CDRM instance:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log("CDRM instance set to null.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ cdrm_api_key: null }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error setting CDRM API Key:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log("CDRM API Key set.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										119
									
								
								buildext.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								buildext.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					import { execSync } from "child_process";
 | 
				
			||||||
 | 
					import fs from "fs";
 | 
				
			||||||
 | 
					import path from "path";
 | 
				
			||||||
 | 
					import { minify } from "terser";
 | 
				
			||||||
 | 
					import url from "url";
 | 
				
			||||||
 | 
					import syncVersion from "./syncVersion.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
 | 
				
			||||||
 | 
					const frontendDir = path.join(__dirname, "frontend");
 | 
				
			||||||
 | 
					const distDir = path.join(frontendDir, "dist");
 | 
				
			||||||
 | 
					const srcDir = path.join(__dirname, "src");
 | 
				
			||||||
 | 
					const iconDir = path.join(__dirname, "icons");
 | 
				
			||||||
 | 
					const releaseDir = path.join(__dirname, "extension-release");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const run = (cmd, cwd) => {
 | 
				
			||||||
 | 
					    console.log(`🛠️ Running: ${cmd}`);
 | 
				
			||||||
 | 
					    execSync(cmd, { cwd, stdio: "inherit" });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const copyDir = async (src, dest) => {
 | 
				
			||||||
 | 
					    await fs.promises.mkdir(dest, { recursive: true });
 | 
				
			||||||
 | 
					    await fs.promises.cp(src, dest, {
 | 
				
			||||||
 | 
					        recursive: true,
 | 
				
			||||||
 | 
					        force: true,
 | 
				
			||||||
 | 
					        filter: (src) => !src.endsWith(".map"),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const minifyJS = async (jsContent) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const result = await minify(jsContent, {
 | 
				
			||||||
 | 
					            compress: {
 | 
				
			||||||
 | 
					                drop_console: false, // Keep console logs for debugging
 | 
				
			||||||
 | 
					                drop_debugger: true,
 | 
				
			||||||
 | 
					                pure_funcs: ["console.debug"],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            mangle: {
 | 
				
			||||||
 | 
					                reserved: ["chrome"], // Don't mangle chrome API
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return result.code;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        console.warn("⚠️ Minification failed, using original:", error.message);
 | 
				
			||||||
 | 
					        return jsContent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Copy and minify JavaScript files from src directory
 | 
				
			||||||
 | 
					const copyAndMinifySrcFiles = async (src, dest) => {
 | 
				
			||||||
 | 
					    await fs.promises.mkdir(dest, { recursive: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const entries = await fs.promises.readdir(src, { withFileTypes: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const entry of entries) {
 | 
				
			||||||
 | 
					        const srcPath = path.join(src, entry.name);
 | 
				
			||||||
 | 
					        const destPath = path.join(dest, entry.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entry.isDirectory()) {
 | 
				
			||||||
 | 
					            await copyAndMinifySrcFiles(srcPath, destPath);
 | 
				
			||||||
 | 
					        } else if (entry.name.endsWith(".js")) {
 | 
				
			||||||
 | 
					            // Minify JavaScript files
 | 
				
			||||||
 | 
					            console.log(`🗜️ Minifying ${entry.name}...`);
 | 
				
			||||||
 | 
					            const content = await fs.promises.readFile(srcPath, "utf8");
 | 
				
			||||||
 | 
					            const originalSize = Buffer.byteLength(content, "utf8");
 | 
				
			||||||
 | 
					            const minified = await minifyJS(content, entry.name);
 | 
				
			||||||
 | 
					            const minifiedSize = Buffer.byteLength(minified, "utf8");
 | 
				
			||||||
 | 
					            const savings = (((originalSize - minifiedSize) / originalSize) * 100).toFixed(1);
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					                `   📊 ${entry.name}: ${originalSize} → ${minifiedSize} bytes (${savings}% smaller)`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            await fs.promises.writeFile(destPath, minified, "utf8");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Copy other files as-is
 | 
				
			||||||
 | 
					            await fs.promises.copyFile(srcPath, destPath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const main = async () => {
 | 
				
			||||||
 | 
					    await syncVersion();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("🚀 Starting extension build...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1. Install frontend deps if needed
 | 
				
			||||||
 | 
					    if (!fs.existsSync(path.join(frontendDir, "node_modules"))) {
 | 
				
			||||||
 | 
					        console.log("📦 node_modules not found. Running npm install...");
 | 
				
			||||||
 | 
					        run("npm install", frontendDir);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Build frontend
 | 
				
			||||||
 | 
					    console.log("📦 Building frontend...");
 | 
				
			||||||
 | 
					    run("npm run build", frontendDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 3. Clean release folder
 | 
				
			||||||
 | 
					    if (fs.existsSync(releaseDir)) {
 | 
				
			||||||
 | 
					        console.log("🧹 Cleaning existing extension-release folder...");
 | 
				
			||||||
 | 
					        await fs.promises.rm(releaseDir, { recursive: true, force: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await fs.promises.mkdir(releaseDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. Copy and minify src files
 | 
				
			||||||
 | 
					    console.log("📦 Copying and minifying src files...");
 | 
				
			||||||
 | 
					    await copyAndMinifySrcFiles(srcDir, releaseDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 5. Copy frontend dist files to release (merged at root)
 | 
				
			||||||
 | 
					    console.log("📦 Copying frontend dist files to extension-release...");
 | 
				
			||||||
 | 
					    await copyDir(distDir, releaseDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 6. Copy icon directory to release (merged at root)
 | 
				
			||||||
 | 
					    console.log("📦 Copying icon directory to extension-release...");
 | 
				
			||||||
 | 
					    await copyDir(iconDir, path.join(releaseDir, "icons"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("✅ Build complete! extension-release ready.");
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main().catch((e) => {
 | 
				
			||||||
 | 
					    console.error("❌ Build failed:", e);
 | 
				
			||||||
 | 
					    process.exit(1);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										67
									
								
								content.js
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								content.js
									
									
									
									
									
								
							@ -1,67 +0,0 @@
 | 
				
			|||||||
// Inject `inject.js` into the page context
 | 
					 | 
				
			||||||
(function injectScript() {
 | 
					 | 
				
			||||||
  const script = document.createElement('script');
 | 
					 | 
				
			||||||
  script.src = chrome.runtime.getURL('inject.js');
 | 
					 | 
				
			||||||
  script.type = 'text/javascript';
 | 
					 | 
				
			||||||
  script.onload = () => script.remove(); // Clean up
 | 
					 | 
				
			||||||
  // Inject directly into <html> or <head>
 | 
					 | 
				
			||||||
  (document.documentElement || document.head || document.body).appendChild(script);
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Listen for messages from the injected script
 | 
					 | 
				
			||||||
window.addEventListener("message", function(event) {
 | 
					 | 
				
			||||||
    if (event.source !== window) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(event.data?.type)) {
 | 
					 | 
				
			||||||
        chrome.runtime.sendMessage({
 | 
					 | 
				
			||||||
            type: event.data.type.replace("__", "").replace("__", ""),
 | 
					 | 
				
			||||||
            data: event.data.data
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (event.data.type === "__GET_CDM_DEVICES__") {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      chrome.storage.local.get(["widevine_device", "playready_device"], (result) => {
 | 
					 | 
				
			||||||
        const widevine_device = result.widevine_device || null;
 | 
					 | 
				
			||||||
        const playready_device = result.playready_device || null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.postMessage(
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            type: "__CDM_DEVICES__",
 | 
					 | 
				
			||||||
            widevine_device,
 | 
					 | 
				
			||||||
            playready_device
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "*"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (event.data.type === "__GET_INJECTION_TYPE__") {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      chrome.storage.local.get("injection_type", (result) => {
 | 
					 | 
				
			||||||
        const injectionType = result.injection_type || "LICENSE";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.postMessage(
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            type: "__INJECTION_TYPE__",
 | 
					 | 
				
			||||||
            injectionType
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "*"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (event.data.type === "__GET_DRM_OVERRIDE__") {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      chrome.storage.local.get("drm_override", (result) => {
 | 
					 | 
				
			||||||
        const drmOverride = result.drm_override || "DISABLED";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.postMessage(
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            type: "__DRM_OVERRIDE__",
 | 
					 | 
				
			||||||
            drmOverride
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          "*"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								frontend/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "trailingComma": "es5",
 | 
				
			||||||
 | 
					    "tabWidth": 4,
 | 
				
			||||||
 | 
					    "semi": true,
 | 
				
			||||||
 | 
					    "singleQuote": false,
 | 
				
			||||||
 | 
					    "useTabs": false,
 | 
				
			||||||
 | 
					    "printWidth": 100,
 | 
				
			||||||
 | 
					    "plugins": ["prettier-plugin-tailwindcss"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								frontend/dist/assets/index-UaipKa9p.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/dist/assets/index-UaipKa9p.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										13
									
								
								frontend/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								frontend/dist/index.html
									
									
									
									
										vendored
									
									
								
							@ -1,13 +0,0 @@
 | 
				
			|||||||
<!doctype html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <meta charset="UTF-8" />
 | 
					 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					 | 
				
			||||||
    <title>CDRM Decryption Extension</title>
 | 
					 | 
				
			||||||
    <script type="module" crossorigin src="./assets/index-ydPQKJSy.js"></script>
 | 
					 | 
				
			||||||
    <link rel="stylesheet" crossorigin href="./assets/index-UaipKa9p.css">
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body class="min-w-full min-h-full w-full h-full">
 | 
					 | 
				
			||||||
    <div class="min-w-full min-h-full w-full h-full" id="root"></div>
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
@ -1,33 +1,30 @@
 | 
				
			|||||||
import js from '@eslint/js'
 | 
					import js from "@eslint/js";
 | 
				
			||||||
import globals from 'globals'
 | 
					import globals from "globals";
 | 
				
			||||||
import reactHooks from 'eslint-plugin-react-hooks'
 | 
					import reactHooks from "eslint-plugin-react-hooks";
 | 
				
			||||||
import reactRefresh from 'eslint-plugin-react-refresh'
 | 
					import reactRefresh from "eslint-plugin-react-refresh";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default [
 | 
					export default [
 | 
				
			||||||
  { ignores: ['dist'] },
 | 
					    { ignores: ["dist"] },
 | 
				
			||||||
  {
 | 
					    {
 | 
				
			||||||
    files: ['**/*.{js,jsx}'],
 | 
					        files: ["**/*.{js,jsx}"],
 | 
				
			||||||
    languageOptions: {
 | 
					        languageOptions: {
 | 
				
			||||||
      ecmaVersion: 2020,
 | 
					            ecmaVersion: 2020,
 | 
				
			||||||
      globals: globals.browser,
 | 
					            globals: globals.browser,
 | 
				
			||||||
      parserOptions: {
 | 
					            parserOptions: {
 | 
				
			||||||
        ecmaVersion: 'latest',
 | 
					                ecmaVersion: "latest",
 | 
				
			||||||
        ecmaFeatures: { jsx: true },
 | 
					                ecmaFeatures: { jsx: true },
 | 
				
			||||||
        sourceType: 'module',
 | 
					                sourceType: "module",
 | 
				
			||||||
      },
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        plugins: {
 | 
				
			||||||
 | 
					            "react-hooks": reactHooks,
 | 
				
			||||||
 | 
					            "react-refresh": reactRefresh,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        rules: {
 | 
				
			||||||
 | 
					            ...js.configs.recommended.rules,
 | 
				
			||||||
 | 
					            ...reactHooks.configs.recommended.rules,
 | 
				
			||||||
 | 
					            "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
 | 
				
			||||||
 | 
					            "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    plugins: {
 | 
					];
 | 
				
			||||||
      'react-hooks': reactHooks,
 | 
					 | 
				
			||||||
      'react-refresh': reactRefresh,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    rules: {
 | 
					 | 
				
			||||||
      ...js.configs.recommended.rules,
 | 
					 | 
				
			||||||
      ...reactHooks.configs.recommended.rules,
 | 
					 | 
				
			||||||
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
 | 
					 | 
				
			||||||
      'react-refresh/only-export-components': [
 | 
					 | 
				
			||||||
        'warn',
 | 
					 | 
				
			||||||
        { allowConstantExport: true },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,12 @@
 | 
				
			|||||||
<!doctype html>
 | 
					<!doctype html>
 | 
				
			||||||
<html lang="en">
 | 
					<html lang="en">
 | 
				
			||||||
  <head>
 | 
					    <head>
 | 
				
			||||||
    <meta charset="UTF-8" />
 | 
					        <meta charset="UTF-8" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					        <meta name="viewport" content="width=device-width" />
 | 
				
			||||||
    <title>CDRM Decryption Extension</title>
 | 
					        <title>CDRM Decryption Extension v%APPVERSION%</title>
 | 
				
			||||||
  </head>
 | 
					    </head>
 | 
				
			||||||
  <body class="min-w-full min-h-full w-full h-full">
 | 
					    <body class="min-w-full min-h-full w-full h-full">
 | 
				
			||||||
    <div class="min-w-full min-h-full w-full h-full" id="root"></div>
 | 
					        <div class="min-w-full min-h-full w-full h-full" id="root"></div>
 | 
				
			||||||
    <script type="module" src="/src/main.jsx"></script>
 | 
					        <script type="module" src="/src/main.jsx"></script>
 | 
				
			||||||
  </body>
 | 
					    </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7183
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7183
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,30 +1,35 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "frontend",
 | 
					    "name": "frontend",
 | 
				
			||||||
  "private": true,
 | 
					    "private": true,
 | 
				
			||||||
  "version": "0.0.0",
 | 
					    "version": "3.0",
 | 
				
			||||||
  "type": "module",
 | 
					    "type": "module",
 | 
				
			||||||
  "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
    "dev": "vite",
 | 
					        "dev": "vite",
 | 
				
			||||||
    "build": "vite build",
 | 
					        "build": "vite build",
 | 
				
			||||||
    "lint": "eslint .",
 | 
					        "lint": "eslint .",
 | 
				
			||||||
    "preview": "vite preview"
 | 
					        "preview": "vite preview"
 | 
				
			||||||
  },
 | 
					    },
 | 
				
			||||||
  "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
    "@tailwindcss/vite": "^4.1.7",
 | 
					        "@tailwindcss/vite": "^4.1.11",
 | 
				
			||||||
    "react": "^19.1.0",
 | 
					        "react": "^19.1.0",
 | 
				
			||||||
    "react-dom": "^19.1.0",
 | 
					        "react-dom": "^19.1.0",
 | 
				
			||||||
    "react-router-dom": "^7.6.1",
 | 
					        "react-icons": "^5.5.0",
 | 
				
			||||||
    "tailwindcss": "^4.1.7"
 | 
					        "react-router-dom": "^7.7.0",
 | 
				
			||||||
  },
 | 
					        "sonner": "^2.0.6",
 | 
				
			||||||
  "devDependencies": {
 | 
					        "tailwindcss": "^4.1.11"
 | 
				
			||||||
    "@eslint/js": "^9.25.0",
 | 
					    },
 | 
				
			||||||
    "@types/react": "^19.1.2",
 | 
					    "devDependencies": {
 | 
				
			||||||
    "@types/react-dom": "^19.1.2",
 | 
					        "@eslint/js": "^9.31.0",
 | 
				
			||||||
    "@vitejs/plugin-react": "^4.4.1",
 | 
					        "@types/react": "^19.1.8",
 | 
				
			||||||
    "eslint": "^9.25.0",
 | 
					        "@types/react-dom": "^19.1.6",
 | 
				
			||||||
    "eslint-plugin-react-hooks": "^5.2.0",
 | 
					        "@vitejs/plugin-react": "^4.7.0",
 | 
				
			||||||
    "eslint-plugin-react-refresh": "^0.4.19",
 | 
					        "@vitejs/plugin-react-swc": "^3.11.0",
 | 
				
			||||||
    "globals": "^16.0.0",
 | 
					        "daisyui": "^5.0.46",
 | 
				
			||||||
    "vite": "^6.3.5"
 | 
					        "eslint": "^9.31.0",
 | 
				
			||||||
  }
 | 
					        "eslint-plugin-react-hooks": "^5.2.0",
 | 
				
			||||||
 | 
					        "eslint-plugin-react-refresh": "^0.4.20",
 | 
				
			||||||
 | 
					        "globals": "^16.3.0",
 | 
				
			||||||
 | 
					        "prettier-plugin-tailwindcss": "^0.6.14",
 | 
				
			||||||
 | 
					        "vite": "^7.0.5"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,77 +1,74 @@
 | 
				
			|||||||
import { useState, useEffect } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import {
 | 
					import { Navigate, Route, HashRouter as Router, Routes } from "react-router-dom";
 | 
				
			||||||
  HashRouter as Router,
 | 
					import About from "./components/about";
 | 
				
			||||||
  Routes,
 | 
					import Container from "./components/container";
 | 
				
			||||||
  Route,
 | 
					 | 
				
			||||||
  Navigate,
 | 
					 | 
				
			||||||
} from "react-router-dom";
 | 
					 | 
				
			||||||
import TopNav from "./components/topnav";
 | 
					 | 
				
			||||||
import SideNav from "./components/sidenav";
 | 
					 | 
				
			||||||
import Results from "./components/results";
 | 
					import Results from "./components/results";
 | 
				
			||||||
import Settings from "./components/settings";
 | 
					import Settings from "./components/settings";
 | 
				
			||||||
 | 
					import TabNavigation from "./components/tabnavigation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() {
 | 
					const App = () => {
 | 
				
			||||||
  const [isSideNavOpen, setIsSideNavOpen] = useState(false);
 | 
					    const [validConfig, setValidConfig] = useState(null);
 | 
				
			||||||
  const [validConfig, setValidConfig] = useState(null); // null = loading
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
    chrome.storage.local.get("valid_config", (result) => {
 | 
					        // Fix: Access chrome API properly for browser extensions
 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					        if (typeof chrome !== "undefined" && chrome.storage) {
 | 
				
			||||||
        console.error("Error reading valid_config:", chrome.runtime.lastError);
 | 
					            chrome.storage.local.get("valid_config", (result) => {
 | 
				
			||||||
        setValidConfig(false); // fallback
 | 
					                if (chrome.runtime.lastError) {
 | 
				
			||||||
      } else {
 | 
					                    console.error("Error reading valid_config:", chrome.runtime.lastError);
 | 
				
			||||||
        setValidConfig(result.valid_config === true);
 | 
					                    setValidConfig(false);
 | 
				
			||||||
      }
 | 
					                } else {
 | 
				
			||||||
    });
 | 
					                    setValidConfig(result.valid_config === true);
 | 
				
			||||||
  }, []);
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Fallback for development/testing
 | 
				
			||||||
 | 
					            setValidConfig(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleConfigSaved = () => {
 | 
				
			||||||
 | 
					        setValidConfig(true);
 | 
				
			||||||
 | 
					        // Navigate to main tab after config is saved
 | 
				
			||||||
 | 
					        window.location.hash = "#/results";
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (validConfig === null) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div className="flex h-screen items-center justify-center">
 | 
				
			||||||
 | 
					                <span className="loading loading-spinner loading-md ms-2"></span>
 | 
				
			||||||
 | 
					                Loading...
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (validConfig === null) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="flex items-center justify-center h-screen bg-black text-white">
 | 
					        <Router>
 | 
				
			||||||
        Loading...
 | 
					            <div className="flex h-screen flex-col py-4">
 | 
				
			||||||
      </div>
 | 
					                <Container>
 | 
				
			||||||
 | 
					                    <TabNavigation validConfig={validConfig} />
 | 
				
			||||||
 | 
					                    <div className="divider"></div>
 | 
				
			||||||
 | 
					                    <Routes>
 | 
				
			||||||
 | 
					                        {!validConfig ? (
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <Route
 | 
				
			||||||
 | 
					                                    path="/settings"
 | 
				
			||||||
 | 
					                                    element={<Settings onConfigSaved={handleConfigSaved} />}
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <Route path="*" element={<Navigate to="/settings" replace />} />
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        ) : (
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <Route path="/" element={<Navigate to="/results" replace />} />
 | 
				
			||||||
 | 
					                                <Route path="/results" element={<Results />} />
 | 
				
			||||||
 | 
					                                <Route path="/settings" element={<Settings />} />
 | 
				
			||||||
 | 
					                                <Route path="/about" element={<About />} />
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
 | 
					                    </Routes>
 | 
				
			||||||
 | 
					                </Container>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </Router>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Router>
 | 
					 | 
				
			||||||
      <div className="min-w-full min-h-full w-full h-full flex flex-grow bg-black/95 flex-col relative">
 | 
					 | 
				
			||||||
        <div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black overflow-x-hidden">
 | 
					 | 
				
			||||||
          <TopNav onMenuClick={() => setIsSideNavOpen(true)} />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div id="currentpagecontainer" className="w-full grow overflow-y-auto">
 | 
					 | 
				
			||||||
          <Routes>
 | 
					 | 
				
			||||||
            {!validConfig ? (
 | 
					 | 
				
			||||||
              <>
 | 
					 | 
				
			||||||
                <Route
 | 
					 | 
				
			||||||
                  path="/settings"
 | 
					 | 
				
			||||||
                  element={
 | 
					 | 
				
			||||||
                    <Settings onConfigSaved={() => setValidConfig(true)} />
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <Route path="*" element={<Navigate to="/settings" replace />} />
 | 
					 | 
				
			||||||
              </>
 | 
					 | 
				
			||||||
            ) : (
 | 
					 | 
				
			||||||
              <>
 | 
					 | 
				
			||||||
                <Route path="/" element={<Navigate to="/results" replace />} />
 | 
					 | 
				
			||||||
                <Route path="/results" element={<Results />} />
 | 
					 | 
				
			||||||
                <Route path="/settings" element={<Settings />} />
 | 
					 | 
				
			||||||
              </>
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          </Routes>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          className={`fixed top-0 left-0 w-full h-full z-50 bg-black transform transition-transform duration-300 ease-in-out ${
 | 
					 | 
				
			||||||
            isSideNavOpen ? "translate-x-0" : "-translate-x-full"
 | 
					 | 
				
			||||||
          }`}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <SideNav onClose={() => setIsSideNavOpen(false)} />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </Router>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default App;
 | 
					export default App;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/fonts/InterVariable-Italic.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/fonts/InterVariable-Italic.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/fonts/InterVariable.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/fonts/InterVariable.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										15
									
								
								frontend/src/assets/fonts/font-face.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/src/assets/fonts/font-face.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					    font-family: Inter;
 | 
				
			||||||
 | 
					    src: url("./InterVariable.woff2");
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    font-weight: 300 900;
 | 
				
			||||||
 | 
					    font-display: swap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					    font-family: Inter;
 | 
				
			||||||
 | 
					    src: url("./InterVariable-Italic.woff2");
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    font-weight: 300 900;
 | 
				
			||||||
 | 
					    font-display: swap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								frontend/src/components/about.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/src/components/about.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					import { FaDiscord, FaTelegram } from "react-icons/fa";
 | 
				
			||||||
 | 
					import { SiGitea } from "react-icons/si";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AboutPage = () => {
 | 
				
			||||||
 | 
					    const socialLinks = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            name: "Discord",
 | 
				
			||||||
 | 
					            icon: <FaDiscord className="text-4xl" />,
 | 
				
			||||||
 | 
					            url: "https://discord.cdrm-project.com/",
 | 
				
			||||||
 | 
					            description: "Join our Discord community",
 | 
				
			||||||
 | 
					            color: "hover:text-indigo-400",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            name: "Telegram",
 | 
				
			||||||
 | 
					            icon: <FaTelegram className="text-4xl" />,
 | 
				
			||||||
 | 
					            url: "https://telegram.cdrm-project.com/",
 | 
				
			||||||
 | 
					            description: "Follow us on Telegram",
 | 
				
			||||||
 | 
					            color: "hover:text-sky-400",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            name: "Gitea",
 | 
				
			||||||
 | 
					            icon: <SiGitea className="text-4xl" />,
 | 
				
			||||||
 | 
					            url: "https://cdm-project.com/tpd94/cdrm-project",
 | 
				
			||||||
 | 
					            description: "Check out our code",
 | 
				
			||||||
 | 
					            color: "hover:text-lime-400",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="flex min-h-full flex-col items-center justify-center p-6">
 | 
				
			||||||
 | 
					            <div className="mb-8 text-center">
 | 
				
			||||||
 | 
					                <h2 className="mb-2 text-3xl font-bold">Connect with us</h2>
 | 
				
			||||||
 | 
					                <p className="text-base-content/70 text-lg">Join our community and stay updated</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div className="grid w-full max-w-4xl grid-cols-1 gap-6 md:grid-cols-3">
 | 
				
			||||||
 | 
					                {socialLinks.map((link) => (
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                        key={link.name}
 | 
				
			||||||
 | 
					                        href={link.url}
 | 
				
			||||||
 | 
					                        target="_blank"
 | 
				
			||||||
 | 
					                        rel="noopener noreferrer"
 | 
				
			||||||
 | 
					                        className={`card bg-base-200 shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl ${link.color}`}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <div className="card-body items-center text-center">
 | 
				
			||||||
 | 
					                            <div className="mb-2">{link.icon}</div>
 | 
				
			||||||
 | 
					                            <h3 className="card-title text-xl font-semibold">{link.name}</h3>
 | 
				
			||||||
 | 
					                            <p className="text-base-content/70">{link.description}</p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default AboutPage;
 | 
				
			||||||
							
								
								
									
										9
									
								
								frontend/src/components/container.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/src/components/container.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					const Container = ({ children, className = "", ...props }) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <main className={`container mx-auto p-4 mb-5 ${className}`} {...props}>
 | 
				
			||||||
 | 
					            {children}
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Container;
 | 
				
			||||||
							
								
								
									
										72
									
								
								frontend/src/components/injectionmenu.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								frontend/src/components/injectionmenu.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InjectionMenu = () => {
 | 
				
			||||||
 | 
					    const [injectionType, setInjectionType] = useState("LICENSE");
 | 
				
			||||||
 | 
					    const [drmOverride, setDrmOverride] = useState("DISABLED");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        chrome.storage.local.get(["injection_type", "drm_override"], (result) => {
 | 
				
			||||||
 | 
					            if (result.injection_type !== undefined) {
 | 
				
			||||||
 | 
					                setInjectionType(result.injection_type);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (result.drm_override !== undefined) {
 | 
				
			||||||
 | 
					                setDrmOverride(result.drm_override);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleInjectionTypeChange = (type) => {
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ injection_type: type }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error updating injection_type:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setInjectionType(type);
 | 
				
			||||||
 | 
					                console.log(`Injection type updated to ${type}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleDrmOverrideChange = (type) => {
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ drm_override: type }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error updating drm_override:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setDrmOverride(type);
 | 
				
			||||||
 | 
					                console.log(`DRM Override updated to ${type}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="flex flex-row">
 | 
				
			||||||
 | 
					            <div className="mr-2 ml-auto flex h-full flex-row items-center justify-center">
 | 
				
			||||||
 | 
					                <p className="mr-2 p-2 text-lg text-nowrap">Injection type:</p>
 | 
				
			||||||
 | 
					                <div role="tablist" className="tabs tabs-border">
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                        role="tab"
 | 
				
			||||||
 | 
					                        className={`tab ${injectionType === "LICENSE" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                        onClick={() => handleInjectionTypeChange("LICENSE")}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        License
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                        role="tab"
 | 
				
			||||||
 | 
					                        className={`tab ${injectionType === "EME" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                        onClick={() => handleInjectionTypeChange("EME")}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        EME
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                        role="tab"
 | 
				
			||||||
 | 
					                        className={`tab ${injectionType === "DISABLED" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                        onClick={() => handleInjectionTypeChange("DISABLED")}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Disabled
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default InjectionMenu;
 | 
				
			||||||
@ -1,149 +1,321 @@
 | 
				
			|||||||
import React, { useEffect, useState } from "react";
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { IoCameraOutline, IoCopyOutline, IoSaveOutline } from "react-icons/io5";
 | 
				
			||||||
 | 
					import { toast } from "sonner";
 | 
				
			||||||
 | 
					import InjectionMenu from "./injectionmenu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Results() {
 | 
					const Results = () => {
 | 
				
			||||||
  const [drmType, setDrmType] = useState("");
 | 
					    const [drmType, setDrmType] = useState("");
 | 
				
			||||||
  const [pssh, setPssh] = useState("");
 | 
					    const [pssh, setPssh] = useState("");
 | 
				
			||||||
  const [licenseUrl, setLicenseUrl] = useState("");
 | 
					    const [licenseUrl, setLicenseUrl] = useState("");
 | 
				
			||||||
  const [keys, setKeys] = useState([]);
 | 
					    const [keys, setKeys] = useState([]);
 | 
				
			||||||
 | 
					    const [manifestUrl, setManifestUrl] = useState("");
 | 
				
			||||||
 | 
					    const [currentTabUrl, setCurrentTabUrl] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
    chrome.storage.local.get(
 | 
					        chrome.storage.local.get(
 | 
				
			||||||
      [
 | 
					            [
 | 
				
			||||||
        "drmType",
 | 
					                "drmType",
 | 
				
			||||||
        "latestPSSH",
 | 
					                "latestPSSH",
 | 
				
			||||||
        "latestLicenseRequest",
 | 
					                "latestLicenseRequest",
 | 
				
			||||||
        "latestKeys",
 | 
					                "latestKeys",
 | 
				
			||||||
        "licenseURL",
 | 
					                "licenseURL",
 | 
				
			||||||
      ],
 | 
					                "manifestURL",
 | 
				
			||||||
      (result) => {
 | 
					            ],
 | 
				
			||||||
        if (result.drmType) setDrmType(result.drmType);
 | 
					            (result) => {
 | 
				
			||||||
        if (result.latestPSSH) setPssh(result.latestPSSH);
 | 
					                if (result.drmType) setDrmType(result.drmType || "");
 | 
				
			||||||
        if (result.licenseURL) setLicenseUrl(result.licenseURL);
 | 
					                if (result.latestPSSH) setPssh(result.latestPSSH || "");
 | 
				
			||||||
        if (result.latestKeys) {
 | 
					                if (result.licenseURL) setLicenseUrl(result.licenseURL || "");
 | 
				
			||||||
          try {
 | 
					                if (result.manifestURL) setManifestUrl(result.manifestURL || "");
 | 
				
			||||||
            const parsed = Array.isArray(result.latestKeys)
 | 
					                if (result.latestKeys) {
 | 
				
			||||||
              ? result.latestKeys
 | 
					                    try {
 | 
				
			||||||
              : JSON.parse(result.latestKeys);
 | 
					                        const parsed = Array.isArray(result.latestKeys)
 | 
				
			||||||
            setKeys(parsed);
 | 
					                            ? result.latestKeys
 | 
				
			||||||
          } catch (e) {
 | 
					                            : JSON.parse(result.latestKeys);
 | 
				
			||||||
            console.error("Failed to parse keys:", e);
 | 
					                        setKeys(parsed);
 | 
				
			||||||
            setKeys([]);
 | 
					                    } catch (e) {
 | 
				
			||||||
          }
 | 
					                        console.error("Failed to parse keys:", e);
 | 
				
			||||||
        }
 | 
					                        setKeys([]);
 | 
				
			||||||
      }
 | 
					                    }
 | 
				
			||||||
    );
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
    const handleChange = (changes, area) => {
 | 
					 | 
				
			||||||
      if (area === "local") {
 | 
					 | 
				
			||||||
        if (changes.drmType) {
 | 
					 | 
				
			||||||
          setDrmType(changes.drmType.newValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (changes.latestPSSH) {
 | 
					 | 
				
			||||||
          setPssh(changes.latestPSSH.newValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (changes.licenseURL) {
 | 
					 | 
				
			||||||
          setLicenseUrl(changes.licenseURL.newValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (changes.latestKeys) {
 | 
					 | 
				
			||||||
          setKeys(changes.latestKeys.newValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chrome.storage.onChanged.addListener(handleChange);
 | 
					 | 
				
			||||||
    return () => chrome.storage.onChanged.removeListener(handleChange);
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleCapture = () => {
 | 
					 | 
				
			||||||
    // Reset stored values
 | 
					 | 
				
			||||||
    chrome.storage.local.set({
 | 
					 | 
				
			||||||
      drmType: "None",
 | 
					 | 
				
			||||||
      latestPSSH: "None",
 | 
					 | 
				
			||||||
      licenseURL: "None",
 | 
					 | 
				
			||||||
      latestKeys: [],
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get all normal windows to exclude your popup
 | 
					 | 
				
			||||||
    chrome.windows.getAll(
 | 
					 | 
				
			||||||
      { populate: true, windowTypes: ["normal"] },
 | 
					 | 
				
			||||||
      (windows) => {
 | 
					 | 
				
			||||||
        if (!windows || windows.length === 0) {
 | 
					 | 
				
			||||||
          console.warn("No normal Chrome windows found");
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Find the last focused normal window
 | 
					 | 
				
			||||||
        const lastFocusedWindow = windows.find((w) => w.focused) || windows[0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!lastFocusedWindow) {
 | 
					 | 
				
			||||||
          console.warn("No focused normal window found");
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Find the active tab in that window (that is a regular webpage)
 | 
					 | 
				
			||||||
        const activeTab = lastFocusedWindow.tabs.find(
 | 
					 | 
				
			||||||
          (tab) => tab.active && tab.url && /^https?:\/\//.test(tab.url)
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (activeTab?.id) {
 | 
					        // Get current tab URL when component mounts
 | 
				
			||||||
          chrome.tabs.reload(activeTab.id, () => {
 | 
					        chrome.windows.getAll({ populate: true, windowTypes: ["normal"] }, (windows) => {
 | 
				
			||||||
            if (chrome.runtime.lastError) {
 | 
					            if (windows && windows.length > 0) {
 | 
				
			||||||
              console.error("Failed to reload tab:", chrome.runtime.lastError);
 | 
					                const lastFocusedWindow = windows.find((w) => w.focused) || windows[0];
 | 
				
			||||||
 | 
					                if (lastFocusedWindow) {
 | 
				
			||||||
 | 
					                    const activeTab = lastFocusedWindow.tabs.find(
 | 
				
			||||||
 | 
					                        (tab) => tab.active && tab.url && /^https?:\/\//.test(tab.url)
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    if (activeTab?.url) {
 | 
				
			||||||
 | 
					                        setCurrentTabUrl(activeTab.url);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					        });
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          console.warn("No active tab found in the last focused normal window");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					        const handleChange = (changes, area) => {
 | 
				
			||||||
    <div className="w-full grow flex h-full overflow-y-auto overflow-x-auto flex-col text-white p-4">
 | 
					            if (area === "local") {
 | 
				
			||||||
      <button
 | 
					                if (changes.drmType) {
 | 
				
			||||||
        onClick={handleCapture}
 | 
					                    setDrmType(changes.drmType.newValue || "");
 | 
				
			||||||
        className="w-full h-10 bg-sky-500 rounded-md p-2 mt-2 text-white cursor-pointer hover:bg-sky-600"
 | 
					                }
 | 
				
			||||||
      >
 | 
					                if (changes.latestPSSH) {
 | 
				
			||||||
        Capture current tab
 | 
					                    setPssh(changes.latestPSSH.newValue || "");
 | 
				
			||||||
      </button>
 | 
					                }
 | 
				
			||||||
      <p className="text-2xl mt-5">DRM Type</p>
 | 
					                if (changes.licenseURL) {
 | 
				
			||||||
      <input
 | 
					                    setLicenseUrl(changes.licenseURL.newValue || "");
 | 
				
			||||||
        type="text"
 | 
					                }
 | 
				
			||||||
        value={drmType}
 | 
					                if (changes.manifestURL) {
 | 
				
			||||||
        className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
 | 
					                    setManifestUrl(changes.manifestURL.newValue || "");
 | 
				
			||||||
        placeholder="None"
 | 
					                }
 | 
				
			||||||
        disabled
 | 
					                if (changes.latestKeys) {
 | 
				
			||||||
      />
 | 
					                    setKeys(changes.latestKeys.newValue || []);
 | 
				
			||||||
      <p className="text-2xl mt-5">PSSH</p>
 | 
					                }
 | 
				
			||||||
      <input
 | 
					            }
 | 
				
			||||||
        type="text"
 | 
					        };
 | 
				
			||||||
        value={pssh}
 | 
					
 | 
				
			||||||
        className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
 | 
					        chrome.storage.onChanged.addListener(handleChange);
 | 
				
			||||||
        placeholder="None"
 | 
					        return () => chrome.storage.onChanged.removeListener(handleChange);
 | 
				
			||||||
        disabled
 | 
					    }, []);
 | 
				
			||||||
      />
 | 
					
 | 
				
			||||||
      <p className="text-2xl mt-5">License URL</p>
 | 
					    const handleCapture = () => {
 | 
				
			||||||
      <input
 | 
					        // Reset stored values
 | 
				
			||||||
        type="text"
 | 
					        chrome.storage.local.set({
 | 
				
			||||||
        value={licenseUrl}
 | 
					            drmType: "",
 | 
				
			||||||
        className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
 | 
					            latestPSSH: "",
 | 
				
			||||||
        placeholder="None"
 | 
					            licenseURL: "",
 | 
				
			||||||
        disabled
 | 
					            manifestURL: "",
 | 
				
			||||||
      />
 | 
					            latestKeys: [],
 | 
				
			||||||
      <p className="text-2xl mt-5">Keys</p>
 | 
					        });
 | 
				
			||||||
      <div className="w-full min-h-64 h-64 flex items-center justify-center text-center overflow-y-auto bg-slate-800/50 rounded-md p-2 mt-2 text-white whitespace-pre-line">
 | 
					
 | 
				
			||||||
        {Array.isArray(keys) &&
 | 
					        // Get all normal windows to exclude your popup
 | 
				
			||||||
        keys.filter((k) => k.type !== "SIGNING").length > 0 ? (
 | 
					        chrome.windows.getAll({ populate: true, windowTypes: ["normal"] }, (windows) => {
 | 
				
			||||||
          keys
 | 
					            if (!windows || windows.length === 0) {
 | 
				
			||||||
            .filter((k) => k.type !== "SIGNING")
 | 
					                console.warn("No normal Chrome windows found");
 | 
				
			||||||
            .map((k) => `${k.key_id || k.keyId}:${k.key}`)
 | 
					                return;
 | 
				
			||||||
            .join("\n")
 | 
					            }
 | 
				
			||||||
        ) : (
 | 
					
 | 
				
			||||||
          <span className="text-gray-400">None</span>
 | 
					            // Find the last focused normal window
 | 
				
			||||||
        )}
 | 
					            const lastFocusedWindow = windows.find((w) => w.focused) || windows[0];
 | 
				
			||||||
      </div>
 | 
					
 | 
				
			||||||
    </div>
 | 
					            if (!lastFocusedWindow) {
 | 
				
			||||||
  );
 | 
					                console.warn("No focused normal window found");
 | 
				
			||||||
}
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Find the active tab in that window (that is a regular webpage)
 | 
				
			||||||
 | 
					            const activeTab = lastFocusedWindow.tabs.find(
 | 
				
			||||||
 | 
					                (tab) => tab.active && tab.url && /^https?:\/\//.test(tab.url)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (activeTab?.id) {
 | 
				
			||||||
 | 
					                chrome.tabs.reload(activeTab.id, () => {
 | 
				
			||||||
 | 
					                    if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                        console.error("Failed to reload tab:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.warn("No active tab found in the last focused normal window");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleCopyToClipboard = (text, label) => {
 | 
				
			||||||
 | 
					        navigator.clipboard.writeText(text);
 | 
				
			||||||
 | 
					        toast.success(`Copied ${label} to clipboard`);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if current tab is YouTube
 | 
				
			||||||
 | 
					    const isYouTube = () => {
 | 
				
			||||||
 | 
					        return currentTabUrl.includes("youtube.com") || currentTabUrl.includes("youtu.be");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Get manifest URL display value
 | 
				
			||||||
 | 
					    const getManifestDisplayValue = () => {
 | 
				
			||||||
 | 
					        if (manifestUrl) {
 | 
				
			||||||
 | 
					            return manifestUrl;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (isYouTube()) {
 | 
				
			||||||
 | 
					            return "[Use yt-dlp to download video]";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return "";
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Get manifest URL placeholder
 | 
				
			||||||
 | 
					    const getManifestPlaceholder = () => {
 | 
				
			||||||
 | 
					        if (isYouTube() && !manifestUrl) {
 | 
				
			||||||
 | 
					            return "[Use yt-dlp to download video]";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return "[Not available]";
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Export to JSON file
 | 
				
			||||||
 | 
					    const hasData = () => {
 | 
				
			||||||
 | 
					        return Array.isArray(keys) && keys.filter((k) => k.type !== "SIGNING").length > 0;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleExportJSON = () => {
 | 
				
			||||||
 | 
					        const exportData = {
 | 
				
			||||||
 | 
					            drmType: drmType || null,
 | 
				
			||||||
 | 
					            manifestUrl: manifestUrl || null,
 | 
				
			||||||
 | 
					            pssh: pssh || null,
 | 
				
			||||||
 | 
					            licenseUrl: licenseUrl || null,
 | 
				
			||||||
 | 
					            keys:
 | 
				
			||||||
 | 
					                Array.isArray(keys) && keys.length > 0
 | 
				
			||||||
 | 
					                    ? keys
 | 
				
			||||||
 | 
					                          .filter((k) => k.type !== "SIGNING")
 | 
				
			||||||
 | 
					                          .map((k) => `${k.key_id || k.keyId}:${k.key}`)
 | 
				
			||||||
 | 
					                    : null,
 | 
				
			||||||
 | 
					            exportedAt: new Date().toISOString(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const blob = new Blob([JSON.stringify(exportData, null, 2)], {
 | 
				
			||||||
 | 
					            type: "application/json",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const url = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					        const a = document.createElement("a");
 | 
				
			||||||
 | 
					        a.href = url;
 | 
				
			||||||
 | 
					        a.download = `drm-data-${new Date().toISOString().slice(0, 19).replace(/:/g, "-")}.json`;
 | 
				
			||||||
 | 
					        document.body.appendChild(a);
 | 
				
			||||||
 | 
					        a.click();
 | 
				
			||||||
 | 
					        document.body.removeChild(a);
 | 
				
			||||||
 | 
					        URL.revokeObjectURL(url);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="flex h-full w-full flex-col gap-4">
 | 
				
			||||||
 | 
					            <InjectionMenu />
 | 
				
			||||||
 | 
					            <button onClick={handleCapture} className="btn btn-primary">
 | 
				
			||||||
 | 
					                <IoCameraOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                Capture current tab
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">DRM Type</legend>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    value={drmType}
 | 
				
			||||||
 | 
					                    className="input w-full font-mono"
 | 
				
			||||||
 | 
					                    placeholder="[Not available]"
 | 
				
			||||||
 | 
					                    disabled
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">Manifest URL</legend>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    value={getManifestDisplayValue()}
 | 
				
			||||||
 | 
					                    className={`input w-full font-mono ${
 | 
				
			||||||
 | 
					                        isYouTube() && !manifestUrl ? "text-yellow-400" : ""
 | 
				
			||||||
 | 
					                    }`}
 | 
				
			||||||
 | 
					                    placeholder={getManifestPlaceholder()}
 | 
				
			||||||
 | 
					                    disabled
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {!isYouTube() && manifestUrl && (
 | 
				
			||||||
 | 
					                    <p className="label flex justify-end">
 | 
				
			||||||
 | 
					                        <button
 | 
				
			||||||
 | 
					                            className="btn btn-link btn-sm text-info"
 | 
				
			||||||
 | 
					                            onClick={() => handleCopyToClipboard(manifestUrl, "manifest URL")}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <IoCopyOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                            Copy manifest URL
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">PSSH</legend>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    value={pssh}
 | 
				
			||||||
 | 
					                    className="input w-full font-mono"
 | 
				
			||||||
 | 
					                    placeholder="[Not available]"
 | 
				
			||||||
 | 
					                    disabled
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {pssh && (
 | 
				
			||||||
 | 
					                    <p className="label flex justify-end">
 | 
				
			||||||
 | 
					                        <button
 | 
				
			||||||
 | 
					                            className="btn btn-link btn-sm text-info"
 | 
				
			||||||
 | 
					                            onClick={() => handleCopyToClipboard(pssh, "PSSH")}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <IoCopyOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                            Copy PSSH
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">License URL</legend>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    value={licenseUrl}
 | 
				
			||||||
 | 
					                    className="input w-full font-mono"
 | 
				
			||||||
 | 
					                    placeholder="[Not available]"
 | 
				
			||||||
 | 
					                    disabled
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {licenseUrl && (
 | 
				
			||||||
 | 
					                    <p className="label flex justify-end">
 | 
				
			||||||
 | 
					                        <button
 | 
				
			||||||
 | 
					                            className="btn btn-link btn-sm text-info"
 | 
				
			||||||
 | 
					                            onClick={() => handleCopyToClipboard(licenseUrl, "license URL")}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <IoCopyOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                            Copy license URL
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">Keys</legend>
 | 
				
			||||||
 | 
					                <textarea
 | 
				
			||||||
 | 
					                    value={
 | 
				
			||||||
 | 
					                        Array.isArray(keys) && keys.filter((k) => k.type !== "SIGNING").length > 0
 | 
				
			||||||
 | 
					                            ? keys
 | 
				
			||||||
 | 
					                                  .filter((k) => k.type !== "SIGNING")
 | 
				
			||||||
 | 
					                                  .map((k) => `${k.key_id || k.keyId}:${k.key}`)
 | 
				
			||||||
 | 
					                                  .join("\n")
 | 
				
			||||||
 | 
					                            : "[Not available]"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    className="textarea w-full font-mono"
 | 
				
			||||||
 | 
					                    disabled
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                {hasData() &&
 | 
				
			||||||
 | 
					                    Array.isArray(keys) &&
 | 
				
			||||||
 | 
					                    keys.filter((k) => k.type !== "SIGNING").length > 0 && (
 | 
				
			||||||
 | 
					                        <p className="label flex justify-end">
 | 
				
			||||||
 | 
					                            <button
 | 
				
			||||||
 | 
					                                className="btn btn-link btn-sm text-info"
 | 
				
			||||||
 | 
					                                onClick={() =>
 | 
				
			||||||
 | 
					                                    handleCopyToClipboard(
 | 
				
			||||||
 | 
					                                        keys
 | 
				
			||||||
 | 
					                                            .filter((k) => k.type !== "SIGNING")
 | 
				
			||||||
 | 
					                                            .map((k) => `${k.key_id || k.keyId}:${k.key}`)
 | 
				
			||||||
 | 
					                                            .join("\n"),
 | 
				
			||||||
 | 
					                                        "keys"
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <IoCopyOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                                Copy keys
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {hasData() && (
 | 
				
			||||||
 | 
					                <button onClick={handleExportJSON} className="btn btn-success">
 | 
				
			||||||
 | 
					                    <IoSaveOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                    Export as JSON
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Results;
 | 
					export default Results;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,150 +1,155 @@
 | 
				
			|||||||
import { useState, useEffect } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { IoSaveOutline } from "react-icons/io5";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { toast } from "sonner";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Settings({ onConfigSaved }) {
 | 
					const Settings = ({ onConfigSaved }) => {
 | 
				
			||||||
  const [instanceUrl, setInstanceUrl] = useState("");
 | 
					    const [instanceUrl, setInstanceUrl] = useState("");
 | 
				
			||||||
  const [storedUrl, setStoredUrl] = useState(null);
 | 
					    const [storedUrl, setStoredUrl] = useState(null);
 | 
				
			||||||
  const [message, setMessage] = useState(null);
 | 
					    const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const [messageType, setMessageType] = useState(null);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
    chrome.storage.local.get("cdrm_instance", (result) => {
 | 
					        chrome.storage.local.get("cdrm_instance", (result) => {
 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error(
 | 
					 | 
				
			||||||
          "Error fetching CDRM instance:",
 | 
					 | 
				
			||||||
          chrome.runtime.lastError
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else if (result.cdrm_instance) {
 | 
					 | 
				
			||||||
        setStoredUrl(result.cdrm_instance);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSave = async () => {
 | 
					 | 
				
			||||||
    const trimmedUrl = instanceUrl.trim().replace(/\/+$/, "");
 | 
					 | 
				
			||||||
    if (!trimmedUrl) {
 | 
					 | 
				
			||||||
      setMessage("Please enter a valid URL.");
 | 
					 | 
				
			||||||
      setMessageType("error");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const endpoint = trimmedUrl + "/api/extension";
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    setMessage(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const response = await fetch(endpoint, {
 | 
					 | 
				
			||||||
        method: "POST",
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          "Content-Type": "application/json",
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const data = await response.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (data.status === true) {
 | 
					 | 
				
			||||||
        setMessage("Successfully connected to CDRM Instance.");
 | 
					 | 
				
			||||||
        setMessageType("success");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const widevineRes = await fetch(
 | 
					 | 
				
			||||||
          `${trimmedUrl}/remotecdm/widevine/deviceinfo`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        if (!widevineRes.ok)
 | 
					 | 
				
			||||||
          throw new Error("Failed to fetch Widevine device info");
 | 
					 | 
				
			||||||
        const widevineData = await widevineRes.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const playreadyRes = await fetch(
 | 
					 | 
				
			||||||
          `${trimmedUrl}/remotecdm/playready/deviceinfo`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        if (!playreadyRes.ok)
 | 
					 | 
				
			||||||
          throw new Error("Failed to fetch PlayReady device info");
 | 
					 | 
				
			||||||
        const playreadyData = await playreadyRes.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        chrome.storage.local.set(
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            valid_config: true,
 | 
					 | 
				
			||||||
            cdrm_instance: trimmedUrl,
 | 
					 | 
				
			||||||
            widevine_device: {
 | 
					 | 
				
			||||||
              device_type: widevineData.device_type,
 | 
					 | 
				
			||||||
              system_id: widevineData.system_id,
 | 
					 | 
				
			||||||
              security_level: widevineData.security_level,
 | 
					 | 
				
			||||||
              secret: widevineData.secret,
 | 
					 | 
				
			||||||
              device_name: widevineData.device_name,
 | 
					 | 
				
			||||||
              host: trimmedUrl,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            playready_device: {
 | 
					 | 
				
			||||||
              security_level: playreadyData.security_level,
 | 
					 | 
				
			||||||
              secret: playreadyData.secret,
 | 
					 | 
				
			||||||
              device_name: playreadyData.device_name,
 | 
					 | 
				
			||||||
              host: trimmedUrl,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          () => {
 | 
					 | 
				
			||||||
            if (chrome.runtime.lastError) {
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
              console.error(
 | 
					                toast.error("Error fetching CDRM instance:", chrome.runtime.lastError);
 | 
				
			||||||
                "Error saving to chrome.storage:",
 | 
					                console.error("Error fetching CDRM instance:", chrome.runtime.lastError);
 | 
				
			||||||
                chrome.runtime.lastError
 | 
					            } else if (result.cdrm_instance) {
 | 
				
			||||||
              );
 | 
					                setStoredUrl(result.cdrm_instance);
 | 
				
			||||||
              setMessage("Error saving configuration.");
 | 
					 | 
				
			||||||
              setMessageType("error");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              console.log("Configuration saved.");
 | 
					 | 
				
			||||||
              setStoredUrl(trimmedUrl);
 | 
					 | 
				
			||||||
              setInstanceUrl("");
 | 
					 | 
				
			||||||
              if (onConfigSaved) onConfigSaved();
 | 
					 | 
				
			||||||
              navigate("/results"); // Automatically redirect after success
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					        });
 | 
				
			||||||
        );
 | 
					    }, []);
 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new Error("Invalid response from endpoint.");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.error("Connection error:", err);
 | 
					 | 
				
			||||||
      setMessage("Invalid endpoint or device info could not be retrieved.");
 | 
					 | 
				
			||||||
      setMessageType("error");
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					    const handleSave = async () => {
 | 
				
			||||||
    <div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col p-4">
 | 
					        const trimmedUrl = instanceUrl.trim().replace(/\/+$/, "");
 | 
				
			||||||
      <input
 | 
					        if (!trimmedUrl) {
 | 
				
			||||||
        type="text"
 | 
					            toast.error("Please enter a valid URL.");
 | 
				
			||||||
        value={instanceUrl}
 | 
					            return;
 | 
				
			||||||
        onChange={(e) => setInstanceUrl(e.target.value)}
 | 
					 | 
				
			||||||
        placeholder={
 | 
					 | 
				
			||||||
          storedUrl
 | 
					 | 
				
			||||||
            ? `Current CDRM Instance: ${storedUrl}`
 | 
					 | 
				
			||||||
            : "CDRM Instance URL (e.g., https://cdrm-project.com/, http://127.0.0.1:5000/)"
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        className="w-full p-4 text-lg bg-gray-800 text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mt-4"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        onClick={handleSave}
 | 
					 | 
				
			||||||
        disabled={loading}
 | 
					 | 
				
			||||||
        className={`mt-4 p-2 ${
 | 
					 | 
				
			||||||
          loading ? "bg-blue-400" : "bg-blue-600 hover:bg-blue-700"
 | 
					 | 
				
			||||||
        } text-white rounded-md transition duration-300`}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        {loading ? "Connecting..." : "Save Settings"}
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {message && (
 | 
					        const endpoint = trimmedUrl + "/api/extension";
 | 
				
			||||||
        <p
 | 
					        setLoading(true);
 | 
				
			||||||
          className={`mt-2 text-sm text-center ${
 | 
					
 | 
				
			||||||
            messageType === "success" ? "text-green-400" : "text-red-400"
 | 
					        try {
 | 
				
			||||||
          }`}
 | 
					            const response = await fetch(endpoint, {
 | 
				
			||||||
        >
 | 
					                method: "POST",
 | 
				
			||||||
          {message}
 | 
					                headers: {
 | 
				
			||||||
        </p>
 | 
					                    "Content-Type": "application/json",
 | 
				
			||||||
      )}
 | 
					                },
 | 
				
			||||||
    </div>
 | 
					            });
 | 
				
			||||||
  );
 | 
					
 | 
				
			||||||
}
 | 
					            const data = await response.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.status === true) {
 | 
				
			||||||
 | 
					                toast.success("Successfully connected to a CDRM instance");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const widevineRes = await fetch(`${trimmedUrl}/remotecdm/widevine/deviceinfo`);
 | 
				
			||||||
 | 
					                if (!widevineRes.ok) {
 | 
				
			||||||
 | 
					                    toast.error(
 | 
				
			||||||
 | 
					                        `Failed to fetch Widevine device info. Reason: ${widevineRes.statusText}`
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const widevineData = await widevineRes.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const playreadyRes = await fetch(`${trimmedUrl}/remotecdm/playready/deviceinfo`);
 | 
				
			||||||
 | 
					                if (!playreadyRes.ok) {
 | 
				
			||||||
 | 
					                    toast.error(
 | 
				
			||||||
 | 
					                        `Failed to fetch PlayReady device info. Reason: ${playreadyRes.statusText}`
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const playreadyData = await playreadyRes.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                chrome.storage.local.set(
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        valid_config: true,
 | 
				
			||||||
 | 
					                        cdrm_instance: trimmedUrl,
 | 
				
			||||||
 | 
					                        widevine_device: {
 | 
				
			||||||
 | 
					                            device_type: widevineData.device_type,
 | 
				
			||||||
 | 
					                            system_id: widevineData.system_id,
 | 
				
			||||||
 | 
					                            security_level: widevineData.security_level,
 | 
				
			||||||
 | 
					                            secret: widevineData.secret,
 | 
				
			||||||
 | 
					                            device_name: widevineData.device_name,
 | 
				
			||||||
 | 
					                            host: trimmedUrl,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        playready_device: {
 | 
				
			||||||
 | 
					                            security_level: playreadyData.security_level,
 | 
				
			||||||
 | 
					                            secret: playreadyData.secret,
 | 
				
			||||||
 | 
					                            device_name: playreadyData.device_name,
 | 
				
			||||||
 | 
					                            host: trimmedUrl,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    () => {
 | 
				
			||||||
 | 
					                        if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                            console.error(
 | 
				
			||||||
 | 
					                                "Error saving to chrome.storage:",
 | 
				
			||||||
 | 
					                                chrome.runtime.lastError
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                            toast.error(
 | 
				
			||||||
 | 
					                                `Error saving configuration. Reason: ${chrome.runtime.lastError}`
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            console.log("Configuration saved");
 | 
				
			||||||
 | 
					                            setStoredUrl(trimmedUrl);
 | 
				
			||||||
 | 
					                            setInstanceUrl("");
 | 
				
			||||||
 | 
					                            if (onConfigSaved) onConfigSaved();
 | 
				
			||||||
 | 
					                            navigate("/results"); // Automatically redirect after success
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                toast.error("Invalid response from endpoint.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            console.error("Connection error:", err);
 | 
				
			||||||
 | 
					            toast.error(
 | 
				
			||||||
 | 
					                `Invalid endpoint or device info could not be retrieved. Reason: ${err.message}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            setLoading(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="flex h-full w-full flex-col gap-4">
 | 
				
			||||||
 | 
					            {storedUrl && (
 | 
				
			||||||
 | 
					                <p className="mb-2 text-base">
 | 
				
			||||||
 | 
					                    Current instance: <span className="font-mono font-semibold">{storedUrl}</span>
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <fieldset className="fieldset">
 | 
				
			||||||
 | 
					                <legend className="fieldset-legend text-base">New instance URL</legend>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    value={instanceUrl}
 | 
				
			||||||
 | 
					                    onChange={(e) => setInstanceUrl(e.target.value)}
 | 
				
			||||||
 | 
					                    placeholder="https://cdrm-project.com/, http://127.0.0.1:5000/"
 | 
				
			||||||
 | 
					                    className="input w-full font-mono"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					                type="button"
 | 
				
			||||||
 | 
					                onClick={handleSave}
 | 
				
			||||||
 | 
					                disabled={loading}
 | 
				
			||||||
 | 
					                className="btn btn-primary btn-block"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                {loading ? (
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                        <span className="loading loading-spinner loading-sm"></span> Connecting...
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
 | 
					                ) : (
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                        <IoSaveOutline className="h-5 w-5" />
 | 
				
			||||||
 | 
					                        Save settings
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Settings;
 | 
					export default Settings;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,51 +0,0 @@
 | 
				
			|||||||
import { NavLink } from "react-router-dom";
 | 
					 | 
				
			||||||
import homeIcon from "../assets/home.svg";
 | 
					 | 
				
			||||||
import settingsIcon from "../assets/settings.svg";
 | 
					 | 
				
			||||||
import closeIcon from "../assets/close.svg";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function SideNav({ onClose }) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col bg-black">
 | 
					 | 
				
			||||||
      <div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black">
 | 
					 | 
				
			||||||
        <button
 | 
					 | 
				
			||||||
          onClick={onClose}
 | 
					 | 
				
			||||||
          className="h-full ml-auto p-3 hover:cursor-pointer"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <img src={closeIcon} alt="Close" className="h-full" />
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className="w-full h-16 flex items-center justify-center mt-2">
 | 
					 | 
				
			||||||
        <NavLink
 | 
					 | 
				
			||||||
          to="/results"
 | 
					 | 
				
			||||||
          onClick={onClose}
 | 
					 | 
				
			||||||
          className="text-white text-2xl font-bold flex flex-row items-center border-l-white hover:border-l-1 w-full hover:bg-black/50 transition duration-300 ease-in-out p-2"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <img
 | 
					 | 
				
			||||||
            src={homeIcon}
 | 
					 | 
				
			||||||
            alt="Home"
 | 
					 | 
				
			||||||
            className="h-full w-16 p-2 flex items-center cursor-pointer"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          Home
 | 
					 | 
				
			||||||
        </NavLink>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className="w-full h-16 flex items-center justify-center mt-2">
 | 
					 | 
				
			||||||
        <NavLink
 | 
					 | 
				
			||||||
          to="/settings"
 | 
					 | 
				
			||||||
          onClick={onClose}
 | 
					 | 
				
			||||||
          className="text-white text-2xl font-bold flex flex-row items-center hover:border-l-1 border-l-white w-full hover:bg-black/50 transition duration-300 ease-in-out p-2"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <img
 | 
					 | 
				
			||||||
            src={settingsIcon}
 | 
					 | 
				
			||||||
            alt="Settings"
 | 
					 | 
				
			||||||
            className="h-full w-16 p-2 flex items-center cursor-pointer"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          Settings
 | 
					 | 
				
			||||||
        </NavLink>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default SideNav;
 | 
					 | 
				
			||||||
							
								
								
									
										51
									
								
								frontend/src/components/tabnavigation.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/src/components/tabnavigation.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import { IoIosInformationCircleOutline } from "react-icons/io";
 | 
				
			||||||
 | 
					import { IoHomeOutline, IoSettingsOutline } from "react-icons/io5";
 | 
				
			||||||
 | 
					import { NavLink, useLocation } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TabNavigation = ({ validConfig }) => {
 | 
				
			||||||
 | 
					    const location = useLocation();
 | 
				
			||||||
 | 
					    const activeTab =
 | 
				
			||||||
 | 
					        location.pathname === "/settings"
 | 
				
			||||||
 | 
					            ? "settings"
 | 
				
			||||||
 | 
					            : location.pathname === "/about"
 | 
				
			||||||
 | 
					              ? "about"
 | 
				
			||||||
 | 
					              : "main";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="mb-4 flex items-center justify-center">
 | 
				
			||||||
 | 
					            <div role="tablist" className="tabs tabs-box">
 | 
				
			||||||
 | 
					                <NavLink
 | 
				
			||||||
 | 
					                    role="tab"
 | 
				
			||||||
 | 
					                    to="/results"
 | 
				
			||||||
 | 
					                    className={`tab ${!validConfig ? "cursor-not-allowed" : activeTab === "main" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                    onClick={(e) => {
 | 
				
			||||||
 | 
					                        if (!validConfig) {
 | 
				
			||||||
 | 
					                            e.preventDefault();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <IoHomeOutline className="mr-1 h-5 w-5" />
 | 
				
			||||||
 | 
					                    Main
 | 
				
			||||||
 | 
					                </NavLink>
 | 
				
			||||||
 | 
					                <NavLink
 | 
				
			||||||
 | 
					                    role="tab"
 | 
				
			||||||
 | 
					                    to="/settings"
 | 
				
			||||||
 | 
					                    className={`tab ${activeTab === "settings" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <IoSettingsOutline className="mr-1 h-5 w-5" />
 | 
				
			||||||
 | 
					                    Settings
 | 
				
			||||||
 | 
					                </NavLink>
 | 
				
			||||||
 | 
					                <NavLink
 | 
				
			||||||
 | 
					                    role="tab"
 | 
				
			||||||
 | 
					                    to="/about"
 | 
				
			||||||
 | 
					                    className={`tab ${activeTab === "about" ? "tab-active font-semibold" : ""}`}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <IoIosInformationCircleOutline className="mr-1 h-5 w-5" />
 | 
				
			||||||
 | 
					                    About
 | 
				
			||||||
 | 
					                </NavLink>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TabNavigation;
 | 
				
			||||||
@ -1,85 +0,0 @@
 | 
				
			|||||||
import { useEffect, useState } from "react";
 | 
					 | 
				
			||||||
import hamburgerIcon from "../assets/hamburger.svg";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function TopNav({ onMenuClick }) {
 | 
					 | 
				
			||||||
  const [injectionType, setInjectionType] = useState("LICENSE");
 | 
					 | 
				
			||||||
  const [drmOverride, setDrmOverride] = useState("DISABLED");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    chrome.storage.local.get(["injection_type", "drm_override"], (result) => {
 | 
					 | 
				
			||||||
      if (result.injection_type !== undefined) {
 | 
					 | 
				
			||||||
        setInjectionType(result.injection_type);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (result.drm_override !== undefined) {
 | 
					 | 
				
			||||||
        setDrmOverride(result.drm_override);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleInjectionTypeChange = (type) => {
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ injection_type: type }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error(
 | 
					 | 
				
			||||||
          "Error updating injection_type:",
 | 
					 | 
				
			||||||
          chrome.runtime.lastError
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        setInjectionType(type);
 | 
					 | 
				
			||||||
        console.log(`Injection type updated to ${type}`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleDrmOverrideChange = (type) => {
 | 
					 | 
				
			||||||
    chrome.storage.local.set({ drm_override: type }, () => {
 | 
					 | 
				
			||||||
      if (chrome.runtime.lastError) {
 | 
					 | 
				
			||||||
        console.error("Error updating drm_override:", chrome.runtime.lastError);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        setDrmOverride(type);
 | 
					 | 
				
			||||||
        console.log(`DRM Override updated to ${type}`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="w-full h-full flex flex-row overflow-x-hidden">
 | 
					 | 
				
			||||||
      <img
 | 
					 | 
				
			||||||
        src={hamburgerIcon}
 | 
					 | 
				
			||||||
        alt="Menu"
 | 
					 | 
				
			||||||
        className="h-full w-16 p-2 flex items-center cursor-pointer"
 | 
					 | 
				
			||||||
        onClick={onMenuClick}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <div className="flex flex-row h-full justify-center items-center ml-auto mr-2">
 | 
					 | 
				
			||||||
        <p className="text-white text-lg p-2 mr-2 border-r-2 border-r-white text-nowrap">
 | 
					 | 
				
			||||||
          Injection Type:
 | 
					 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
        <button
 | 
					 | 
				
			||||||
          onClick={() => handleInjectionTypeChange("LICENSE")}
 | 
					 | 
				
			||||||
          className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
 | 
					 | 
				
			||||||
            injectionType === "LICENSE" ? "bg-sky-500/70" : "bg-black"
 | 
					 | 
				
			||||||
          }`}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          License
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
        <button
 | 
					 | 
				
			||||||
          onClick={() => handleInjectionTypeChange("EME")}
 | 
					 | 
				
			||||||
          className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
 | 
					 | 
				
			||||||
            injectionType === "EME" ? "bg-green-500/70" : "bg-black"
 | 
					 | 
				
			||||||
          }`}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          EME
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
        <button
 | 
					 | 
				
			||||||
          onClick={() => handleInjectionTypeChange("DISABLED")}
 | 
					 | 
				
			||||||
          className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
 | 
					 | 
				
			||||||
            injectionType === "DISABLED" ? "bg-red-500/70" : "bg-black"
 | 
					 | 
				
			||||||
          }`}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Disabled
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default TopNav;
 | 
					 | 
				
			||||||
@ -1,8 +1,35 @@
 | 
				
			|||||||
@import "tailwindcss";
 | 
					@import "tailwindcss";
 | 
				
			||||||
 | 
					@plugin "daisyui";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html, body, #root {
 | 
					@plugin "daisyui/theme" {
 | 
				
			||||||
  height: 100%;
 | 
					    name: "dim";
 | 
				
			||||||
  width: 100%;
 | 
					    default: true;
 | 
				
			||||||
  margin: 0;
 | 
					    prefersdark: true;
 | 
				
			||||||
  padding: 0;
 | 
					    color-scheme: "dark";
 | 
				
			||||||
}
 | 
					    --radius-selector: 0.5rem;
 | 
				
			||||||
 | 
					    --radius-field: 0.5rem;
 | 
				
			||||||
 | 
					    --radius-box: 0.5rem;
 | 
				
			||||||
 | 
					    --size-selector: 0.25rem;
 | 
				
			||||||
 | 
					    --size-field: 0.25rem;
 | 
				
			||||||
 | 
					    --depth: 0;
 | 
				
			||||||
 | 
					    --noise: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --font-default-sans:
 | 
				
			||||||
 | 
					        "Inter", system-ui, -apple-system, Roboto, "Segoe UI", "Helvetica Neue", "Noto Sans",
 | 
				
			||||||
 | 
					        Oxygen, Ubuntu, Cantarell, "Open Sans", Arial, sans-serif;
 | 
				
			||||||
 | 
					    font-family: var(--font-default-sans);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Force Sonner toast to use Inter first */
 | 
				
			||||||
 | 
					[data-sonner-toast],
 | 
				
			||||||
 | 
					.sonner-toast,
 | 
				
			||||||
 | 
					:where([data-sonner-toast]) :where([data-title]) :where([data-description]) {
 | 
				
			||||||
 | 
					    font-family: var(--font-default-sans) !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html,
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: var(--font-default-sans);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,19 @@
 | 
				
			|||||||
import { StrictMode } from 'react'
 | 
					import { StrictMode } from "react";
 | 
				
			||||||
import { createRoot } from 'react-dom/client'
 | 
					import { createRoot } from "react-dom/client";
 | 
				
			||||||
import './index.css'
 | 
					import { Toaster } from "sonner";
 | 
				
			||||||
import App from './App.jsx'
 | 
					import App from "./App.jsx";
 | 
				
			||||||
 | 
					import "./assets/fonts/font-face.css";
 | 
				
			||||||
 | 
					import "./index.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createRoot(document.getElementById('root')).render(
 | 
					createRoot(document.getElementById("root")).render(
 | 
				
			||||||
  <StrictMode>
 | 
					    <StrictMode>
 | 
				
			||||||
    <App />
 | 
					        <App />
 | 
				
			||||||
  </StrictMode>,
 | 
					        <Toaster
 | 
				
			||||||
)
 | 
					            richColors
 | 
				
			||||||
 | 
					            className="flex justify-center"
 | 
				
			||||||
 | 
					            position="bottom-center"
 | 
				
			||||||
 | 
					            duration="7000"
 | 
				
			||||||
 | 
					            theme="dark"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </StrictMode>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,21 @@
 | 
				
			|||||||
import { defineConfig } from 'vite'
 | 
					import tailwindcss from "@tailwindcss/vite";
 | 
				
			||||||
import react from '@vitejs/plugin-react'
 | 
					import react from "@vitejs/plugin-react-swc";
 | 
				
			||||||
import tailwindcss from '@tailwindcss/vite'
 | 
					import { readFileSync } from "fs";
 | 
				
			||||||
 | 
					import { defineConfig } from "vite";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const packageJson = JSON.parse(readFileSync("./package.json", "utf8"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const replaceVersionPlugin = () => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        name: "replace-version",
 | 
				
			||||||
 | 
					        transformIndexHtml(html) {
 | 
				
			||||||
 | 
					            return html.replace("%APPVERSION%", packageJson.version);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://vite.dev/config/
 | 
					// https://vite.dev/config/
 | 
				
			||||||
export default defineConfig({
 | 
					export default defineConfig({
 | 
				
			||||||
  base: './',
 | 
					    base: "./",
 | 
				
			||||||
  plugins: [react(), tailwindcss()],
 | 
					    plugins: [react(), tailwindcss(), replaceVersionPlugin()],
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										813
									
								
								inject.js
									
									
									
									
									
								
							
							
						
						
									
										813
									
								
								inject.js
									
									
									
									
									
								
							@ -1,813 +0,0 @@
 | 
				
			|||||||
let widevineDeviceInfo = null;
 | 
					 | 
				
			||||||
let playreadyDeviceInfo = null;
 | 
					 | 
				
			||||||
let originalChallenge = null
 | 
					 | 
				
			||||||
let serviceCertFound = false;
 | 
					 | 
				
			||||||
let drmType = "NONE";
 | 
					 | 
				
			||||||
let psshFound = false;
 | 
					 | 
				
			||||||
let foundWidevinePssh = null;
 | 
					 | 
				
			||||||
let foundPlayreadyPssh = null;
 | 
					 | 
				
			||||||
let drmDecided = null;
 | 
					 | 
				
			||||||
let drmOverride = "DISABLED";
 | 
					 | 
				
			||||||
let interceptType = "DISABLED";
 | 
					 | 
				
			||||||
let remoteCDM = null;
 | 
					 | 
				
			||||||
let generateRequestCalled = false;
 | 
					 | 
				
			||||||
let remoteListenerMounted = false;
 | 
					 | 
				
			||||||
let injectionSuccess = false;
 | 
					 | 
				
			||||||
let foundChallengeInBody = false;
 | 
					 | 
				
			||||||
let licenseResponseCounter = 0;
 | 
					 | 
				
			||||||
let keysRetrieved = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Post message to content.js to get DRM override
 | 
					 | 
				
			||||||
window.postMessage({ type: "__GET_DRM_OVERRIDE__" }, "*");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add listener for DRM override messages
 | 
					 | 
				
			||||||
window.addEventListener("message", function(event) {
 | 
					 | 
				
			||||||
  if (event.source !== window) return;
 | 
					 | 
				
			||||||
    if (event.data.type === "__DRM_OVERRIDE__") {
 | 
					 | 
				
			||||||
    drmOverride = event.data.drmOverride || "DISABLED";
 | 
					 | 
				
			||||||
    console.log("DRM Override set to:", drmOverride);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Post message to content.js to get injection type
 | 
					 | 
				
			||||||
window.postMessage({ type: "__GET_INJECTION_TYPE__" }, "*");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add listener for injection type messages
 | 
					 | 
				
			||||||
window.addEventListener("message", function(event) {
 | 
					 | 
				
			||||||
  if (event.source !== window) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (event.data.type === "__INJECTION_TYPE__") {
 | 
					 | 
				
			||||||
    interceptType = event.data.injectionType || "DISABLED";
 | 
					 | 
				
			||||||
    console.log("Injection type set to:", interceptType);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Post message to get CDM devices
 | 
					 | 
				
			||||||
window.postMessage({ type: "__GET_CDM_DEVICES__" }, "*");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add listener for CDM device messages
 | 
					 | 
				
			||||||
window.addEventListener("message", function(event) {
 | 
					 | 
				
			||||||
  if (event.source !== window) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (event.data.type === "__CDM_DEVICES__") {
 | 
					 | 
				
			||||||
    const { widevine_device, playready_device } = event.data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log("Received device info:", widevine_device, playready_device);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    widevineDeviceInfo = widevine_device;
 | 
					 | 
				
			||||||
    playreadyDeviceInfo = playready_device;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// PlayReady Remote CDM Class
 | 
					 | 
				
			||||||
class remotePlayReadyCDM {
 | 
					 | 
				
			||||||
    constructor(security_level, host, secret, device_name) {
 | 
					 | 
				
			||||||
        this.security_level = security_level;
 | 
					 | 
				
			||||||
        this.host = host;
 | 
					 | 
				
			||||||
        this.secret = secret;
 | 
					 | 
				
			||||||
        this.device_name = device_name;
 | 
					 | 
				
			||||||
        this.session_id = null;
 | 
					 | 
				
			||||||
        this.challenge = null;
 | 
					 | 
				
			||||||
        this.keys = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Open PlayReady session
 | 
					 | 
				
			||||||
    openSession() {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/playready/${this.device_name}/open`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('GET', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        xhr.send();
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.session_id) {
 | 
					 | 
				
			||||||
            this.session_id = jsonData.data.session_id;
 | 
					 | 
				
			||||||
            console.log("PlayReady session opened:", this.session_id);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to open PlayReady session:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to open PlayReady session");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get PlayReady challenge
 | 
					 | 
				
			||||||
    getChallenge(init_data) {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/playready/${this.device_name}/get_license_challenge`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id,
 | 
					 | 
				
			||||||
            init_data: init_data
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.challenge) {
 | 
					 | 
				
			||||||
            this.challenge = btoa(jsonData.data.challenge);
 | 
					 | 
				
			||||||
            console.log("PlayReady challenge received:", this.challenge);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to get PlayReady challenge:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to get PlayReady challenge");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Parse PlayReady license response
 | 
					 | 
				
			||||||
    parseLicense(license_message) {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/playready/${this.device_name}/parse_license`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id,
 | 
					 | 
				
			||||||
            license_message: license_message
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.message === "Successfully parsed and loaded the Keys from the License message")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            console.log("PlayReady license response parsed successfully");
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to parse PlayReady license response:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to parse PlayReady license response");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get PlayReady keys
 | 
					 | 
				
			||||||
    getKeys() {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/playready/${this.device_name}/get_keys`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.keys) {
 | 
					 | 
				
			||||||
            this.keys = jsonData.data.keys;
 | 
					 | 
				
			||||||
            console.log("PlayReady keys received:", this.keys);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to get PlayReady keys:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to get PlayReady keys");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Close PlayReady session
 | 
					 | 
				
			||||||
    closeSession () {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/playready/${this.device_name}/close/${this.session_id}`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('GET', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        xhr.send();
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData) {
 | 
					 | 
				
			||||||
            console.log("PlayReady session closed successfully");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to close PlayReady session:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to close PlayReady session");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Widevine Remote CDM Class
 | 
					 | 
				
			||||||
class remoteWidevineCDM {
 | 
					 | 
				
			||||||
        constructor(device_type, system_id, security_level, host, secret, device_name) {
 | 
					 | 
				
			||||||
            this.device_type = device_type;
 | 
					 | 
				
			||||||
            this.system_id = system_id;
 | 
					 | 
				
			||||||
            this.security_level = security_level;
 | 
					 | 
				
			||||||
            this.host = host;
 | 
					 | 
				
			||||||
            this.secret = secret;
 | 
					 | 
				
			||||||
            this.device_name = device_name;
 | 
					 | 
				
			||||||
            this.session_id = null;
 | 
					 | 
				
			||||||
            this.challenge = null;
 | 
					 | 
				
			||||||
            this.keys = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Open Widevine session
 | 
					 | 
				
			||||||
    openSession () {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/widevine/${this.device_name}/open`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('GET', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        xhr.send();
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.session_id) {
 | 
					 | 
				
			||||||
            this.session_id = jsonData.data.session_id;
 | 
					 | 
				
			||||||
            console.log("Widevine session opened:", this.session_id);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to open Widevine session:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to open Widevine session");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set Widevine service certificate
 | 
					 | 
				
			||||||
    setServiceCertificate(certificate) {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id,
 | 
					 | 
				
			||||||
            certificate: certificate ?? null
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.status === 200) {
 | 
					 | 
				
			||||||
            console.log("Service certificate set successfully");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to set service certificate:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to set service certificate");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get Widevine challenge
 | 
					 | 
				
			||||||
    getChallenge(init_data, license_type = 'STREAMING') {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id,
 | 
					 | 
				
			||||||
            init_data: init_data,
 | 
					 | 
				
			||||||
            privacy_mode: serviceCertFound
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.challenge_b64) {
 | 
					 | 
				
			||||||
            this.challenge = jsonData.data.challenge_b64;
 | 
					 | 
				
			||||||
            console.log("Widevine challenge received:", this.challenge);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to get Widevine challenge:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to get Widevine challenge");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Parse Widevine license response
 | 
					 | 
				
			||||||
    parseLicense(license_message) {
 | 
					 | 
				
			||||||
        const url =  `${this.host}/remotecdm/widevine/${this.device_name}/parse_license`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id,
 | 
					 | 
				
			||||||
            license_message: license_message
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.status === 200) {
 | 
					 | 
				
			||||||
            console.log("Widevine license response parsed successfully");
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to parse Widevine license response:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to parse Widevine license response");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get Widevine keys
 | 
					 | 
				
			||||||
    getKeys() {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_keys/ALL`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('POST', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        const body = {
 | 
					 | 
				
			||||||
            session_id: this.session_id
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        xhr.send(JSON.stringify(body));
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData.data?.keys) {
 | 
					 | 
				
			||||||
            this.keys = jsonData.data.keys;
 | 
					 | 
				
			||||||
            console.log("Widevine keys received:", this.keys);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to get Widevine keys:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to get Widevine keys");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Close Widevine session
 | 
					 | 
				
			||||||
    closeSession() {
 | 
					 | 
				
			||||||
        const url = `${this.host}/remotecdm/widevine/${this.device_name}/close/${this.session_id}`;
 | 
					 | 
				
			||||||
        const xhr = new XMLHttpRequest();
 | 
					 | 
				
			||||||
        xhr.open('GET', url, false);
 | 
					 | 
				
			||||||
        xhr.setRequestHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
        xhr.send();
 | 
					 | 
				
			||||||
        const jsonData = JSON.parse(xhr.responseText);
 | 
					 | 
				
			||||||
        if (jsonData) {
 | 
					 | 
				
			||||||
            console.log("Widevine session closed successfully");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error("Failed to close Widevine session:", jsonData.message);
 | 
					 | 
				
			||||||
            throw new Error("Failed to close Widevine session");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Utility functions
 | 
					 | 
				
			||||||
function hexStrToU8(hexString) {
 | 
					 | 
				
			||||||
    return Uint8Array.from(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function u8ToHexStr(bytes) {
 | 
					 | 
				
			||||||
    return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function b64ToHexStr(b64) {
 | 
					 | 
				
			||||||
    return [...atob(b64)].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join``;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function jsonContainsValue(obj, prefix = "CAES") {
 | 
					 | 
				
			||||||
    if (typeof obj === "string") return obj.startsWith(prefix);
 | 
					 | 
				
			||||||
    if (Array.isArray(obj)) return obj.some(val => jsonContainsValue(val, prefix));
 | 
					 | 
				
			||||||
    if (typeof obj === "object" && obj !== null) {
 | 
					 | 
				
			||||||
        return Object.values(obj).some(val => jsonContainsValue(val, prefix));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function jsonReplaceValue(obj, newValue) {
 | 
					 | 
				
			||||||
    if (typeof obj === "string") {
 | 
					 | 
				
			||||||
        return obj.startsWith("CAES") || obj.startsWith("PD94") ? newValue : obj;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (Array.isArray(obj)) {
 | 
					 | 
				
			||||||
        return obj.map(item => jsonReplaceValue(item, newValue));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (typeof obj === "object" && obj !== null) {
 | 
					 | 
				
			||||||
        const newObj = {};
 | 
					 | 
				
			||||||
        for (const key in obj) {
 | 
					 | 
				
			||||||
            if (Object.hasOwn(obj, key)) {
 | 
					 | 
				
			||||||
                newObj[key] = jsonReplaceValue(obj[key], newValue);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return newObj;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return obj;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function isJson(str) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        JSON.parse(str);
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getWidevinePssh(buffer) {
 | 
					 | 
				
			||||||
    const hex = u8ToHexStr(new Uint8Array(buffer));
 | 
					 | 
				
			||||||
    const match = hex.match(/000000(..)?70737368.*/);
 | 
					 | 
				
			||||||
    if (!match) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const boxHex = match[0];
 | 
					 | 
				
			||||||
    const bytes = hexStrToU8(boxHex);
 | 
					 | 
				
			||||||
    return window.btoa(String.fromCharCode(...bytes));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getPlayReadyPssh(buffer) {
 | 
					 | 
				
			||||||
    const u8 = new Uint8Array(buffer);
 | 
					 | 
				
			||||||
    const systemId = "9a04f07998404286ab92e65be0885f95";
 | 
					 | 
				
			||||||
    const hex = u8ToHexStr(u8);
 | 
					 | 
				
			||||||
    const index = hex.indexOf(systemId);
 | 
					 | 
				
			||||||
    if (index === -1) return null;
 | 
					 | 
				
			||||||
    const psshBoxStart = hex.lastIndexOf("70737368", index);
 | 
					 | 
				
			||||||
    if (psshBoxStart === -1) return null;
 | 
					 | 
				
			||||||
    const lenStart = psshBoxStart - 8;
 | 
					 | 
				
			||||||
    const boxLen = parseInt(hex.substr(lenStart, 8), 16) * 2;
 | 
					 | 
				
			||||||
    const psshHex = hex.substr(lenStart, boxLen);
 | 
					 | 
				
			||||||
    const psshBytes = hexStrToU8(psshHex);
 | 
					 | 
				
			||||||
    return window.btoa(String.fromCharCode(...psshBytes));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getClearkey(response) {
 | 
					 | 
				
			||||||
    let obj = JSON.parse((new TextDecoder("utf-8")).decode(response));
 | 
					 | 
				
			||||||
    return obj["keys"].map(o => ({
 | 
					 | 
				
			||||||
        key_id: b64ToHexStr(o["kid"].replace(/-/g, '+').replace(/_/g, '/')),
 | 
					 | 
				
			||||||
        key: b64ToHexStr(o["k"].replace(/-/g, '+').replace(/_/g, '/')),
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function base64ToUint8Array(base64) {
 | 
					 | 
				
			||||||
    const binaryStr = atob(base64);
 | 
					 | 
				
			||||||
    const len = binaryStr.length;
 | 
					 | 
				
			||||||
    const bytes = new Uint8Array(len);
 | 
					 | 
				
			||||||
    for (let i = 0; i < len; i++) {
 | 
					 | 
				
			||||||
        bytes[i] = binaryStr.charCodeAt(i);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return bytes;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function arrayBufferToBase64(uint8array) {
 | 
					 | 
				
			||||||
    let binary = '';
 | 
					 | 
				
			||||||
    const len = uint8array.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let i = 0; i < len; i++) {
 | 
					 | 
				
			||||||
        binary += String.fromCharCode(uint8array[i]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return window.btoa(binary);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Challenge generator interceptor
 | 
					 | 
				
			||||||
const originalGenerateRequest = MediaKeySession.prototype.generateRequest;
 | 
					 | 
				
			||||||
MediaKeySession.prototype.generateRequest = function(initDataType, initData) {
 | 
					 | 
				
			||||||
    const session = this;
 | 
					 | 
				
			||||||
    let playReadyPssh = getPlayReadyPssh(initData);
 | 
					 | 
				
			||||||
    if (playReadyPssh) {
 | 
					 | 
				
			||||||
        console.log("[DRM Detected] PlayReady");
 | 
					 | 
				
			||||||
        foundPlayreadyPssh = playReadyPssh;
 | 
					 | 
				
			||||||
        console.log("[PlayReady PSSH found] " + playReadyPssh)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let wideVinePssh = getWidevinePssh(initData)
 | 
					 | 
				
			||||||
    if (wideVinePssh) {
 | 
					 | 
				
			||||||
        // Widevine code
 | 
					 | 
				
			||||||
        console.log("[DRM Detected] Widevine");
 | 
					 | 
				
			||||||
        foundWidevinePssh = wideVinePssh;
 | 
					 | 
				
			||||||
        console.log("[Widevine PSSH found] " + wideVinePssh)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Challenge message interceptor
 | 
					 | 
				
			||||||
    if (!remoteListenerMounted) {
 | 
					 | 
				
			||||||
        remoteListenerMounted = true;
 | 
					 | 
				
			||||||
        session.addEventListener("message", function messageInterceptor(event) {
 | 
					 | 
				
			||||||
            event.stopImmediatePropagation();
 | 
					 | 
				
			||||||
            const uint8Array = new Uint8Array(event.message);
 | 
					 | 
				
			||||||
            const base64challenge = arrayBufferToBase64(uint8Array);
 | 
					 | 
				
			||||||
            if (base64challenge === "CAQ=" && interceptType !== "DISABLED" && !serviceCertFound) {
 | 
					 | 
				
			||||||
                const {
 | 
					 | 
				
			||||||
                    device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                remoteCDM.openSession();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (!injectionSuccess && base64challenge !== "CAQ=" && interceptType !== "DISABLED") {
 | 
					 | 
				
			||||||
                if (interceptType === "EME") {
 | 
					 | 
				
			||||||
                    injectionSuccess = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (!originalChallenge) {
 | 
					 | 
				
			||||||
                    originalChallenge = base64challenge;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (originalChallenge.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__DRM_TYPE__", data: "Widevine" }, "*");
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
 | 
					 | 
				
			||||||
                    if (interceptType === "EME" && !remoteCDM) {
 | 
					 | 
				
			||||||
                        const {
 | 
					 | 
				
			||||||
                            device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                        } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                        remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                        remoteCDM.openSession();
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                if (!originalChallenge.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                    const buffer = event.message;
 | 
					 | 
				
			||||||
                    const decoder = new TextDecoder('utf-16');
 | 
					 | 
				
			||||||
                    const decodedText = decoder.decode(buffer);
 | 
					 | 
				
			||||||
                    const match = decodedText.match(/<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/);
 | 
					 | 
				
			||||||
                    if (match) {
 | 
					 | 
				
			||||||
                        window.postMessage({ type: "__DRM_TYPE__", data: "PlayReady" }, "*");
 | 
					 | 
				
			||||||
                        window.postMessage({ type: "__PSSH_DATA__", data: foundPlayreadyPssh }, "*");
 | 
					 | 
				
			||||||
                        originalChallenge = match[1];
 | 
					 | 
				
			||||||
                        if (interceptType === "EME" && !remoteCDM) {    
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name)
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                if (interceptType === "EME" && remoteCDM) {
 | 
					 | 
				
			||||||
                    const uint8challenge = base64ToUint8Array(remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    const challengeBuffer = uint8challenge.buffer;
 | 
					 | 
				
			||||||
                    const syntheticEvent = new MessageEvent("message", {
 | 
					 | 
				
			||||||
                        data: event.data,
 | 
					 | 
				
			||||||
                        origin: event.origin,
 | 
					 | 
				
			||||||
                        lastEventId: event.lastEventId,
 | 
					 | 
				
			||||||
                        source: event.source,
 | 
					 | 
				
			||||||
                        ports: event.ports
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    Object.defineProperty(syntheticEvent, "message", {
 | 
					 | 
				
			||||||
                        get: () => challengeBuffer
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    console.log("Intercepted EME Challenge and injected custom one.")
 | 
					 | 
				
			||||||
                    session.dispatchEvent(syntheticEvent);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        console.log("Message interceptor mounted.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
return originalGenerateRequest.call(session, initDataType, initData);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Message update interceptors
 | 
					 | 
				
			||||||
const originalUpdate = MediaKeySession.prototype.update;
 | 
					 | 
				
			||||||
MediaKeySession.prototype.update = function(response) {
 | 
					 | 
				
			||||||
    const uint8 = response instanceof Uint8Array ? response : new Uint8Array(response);
 | 
					 | 
				
			||||||
    const base64Response = window.btoa(String.fromCharCode(...uint8));
 | 
					 | 
				
			||||||
    if (base64Response.startsWith("CAUS") && foundWidevinePssh && remoteCDM && !serviceCertFound) {
 | 
					 | 
				
			||||||
        remoteCDM.setServiceCertificate(base64Response);
 | 
					 | 
				
			||||||
        if (interceptType === "EME" && !remoteCDM.challenge) {
 | 
					 | 
				
			||||||
            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        window.postMessage({ type: "__DRM_TYPE__", data: "Widevine" }, "*");
 | 
					 | 
				
			||||||
        window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
 | 
					 | 
				
			||||||
        serviceCertFound = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!base64Response.startsWith("CAUS") && (foundWidevinePssh || foundPlayreadyPssh) && !keysRetrieved) {
 | 
					 | 
				
			||||||
        if (licenseResponseCounter === 1 || foundChallengeInBody) {
 | 
					 | 
				
			||||||
            remoteCDM.parseLicense(base64Response);
 | 
					 | 
				
			||||||
            remoteCDM.getKeys();
 | 
					 | 
				
			||||||
            remoteCDM.closeSession();
 | 
					 | 
				
			||||||
            keysRetrieved = true;
 | 
					 | 
				
			||||||
            window.postMessage({ type: "__KEYS_DATA__", data: remoteCDM.keys }, "*");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        licenseResponseCounter++;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const updatePromise = originalUpdate.call(this, response);
 | 
					 | 
				
			||||||
    if (!foundPlayreadyPssh && !foundWidevinePssh) {
 | 
					 | 
				
			||||||
        updatePromise
 | 
					 | 
				
			||||||
            .then(() => {
 | 
					 | 
				
			||||||
                let clearKeys = getClearkey(response);
 | 
					 | 
				
			||||||
                if (clearKeys && clearKeys.length > 0) {
 | 
					 | 
				
			||||||
                  console.log("[CLEARKEY] ", clearKeys);
 | 
					 | 
				
			||||||
                  const drmType = {
 | 
					 | 
				
			||||||
                      type: "__DRM_TYPE__",
 | 
					 | 
				
			||||||
                      data: 'ClearKey'
 | 
					 | 
				
			||||||
                  };
 | 
					 | 
				
			||||||
                  window.postMessage(drmType, "*");
 | 
					 | 
				
			||||||
                  const keysData = {
 | 
					 | 
				
			||||||
                      type: "__KEYS_DATA__",
 | 
					 | 
				
			||||||
                      data: clearKeys
 | 
					 | 
				
			||||||
                  };
 | 
					 | 
				
			||||||
                  window.postMessage(keysData, "*");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(e => {
 | 
					 | 
				
			||||||
                console.log("[CLEARKEY] Not found");
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return updatePromise;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// fetch POST interceptor
 | 
					 | 
				
			||||||
(function() {
 | 
					 | 
				
			||||||
  const originalFetch = window.fetch;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  window.fetch = async function(resource, config = {}) {
 | 
					 | 
				
			||||||
    const method = (config.method || 'GET').toUpperCase();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (method === 'POST') {
 | 
					 | 
				
			||||||
        let body = config.body;
 | 
					 | 
				
			||||||
        if (body) {
 | 
					 | 
				
			||||||
            if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
 | 
					 | 
				
			||||||
                const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
 | 
					 | 
				
			||||||
                const base64Body = window.btoa(String.fromCharCode(...buffer));
 | 
					 | 
				
			||||||
                if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64Body !== remoteCDM.challenge) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && interceptType == "LICENSE" &&!foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (base64Body.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (base64Body.startsWith("PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = base64ToUint8Array(remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    config.body = injectedBody;
 | 
					 | 
				
			||||||
                    return originalFetch(resource, config);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (typeof body === 'string' && !isJson(body)) {
 | 
					 | 
				
			||||||
                const base64EncodedBody = btoa(body);
 | 
					 | 
				
			||||||
                if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64EncodedBody !== remoteCDM.challenge) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && interceptType == "LICENSE" && !foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (base64EncodedBody.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (base64EncodedBody.startsWith("PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = atob(remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    config.body = injectedBody;
 | 
					 | 
				
			||||||
                    return originalFetch(resource, config);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (typeof body === 'string' && isJson(body)) {
 | 
					 | 
				
			||||||
                const jsonBody = JSON.parse(body);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && (!remoteCDM || remoteCDM.challenge === null) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && interceptType === "LICENSE" && !foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (jsonContainsValue(jsonBody, "CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (jsonContainsValue(jsonBody, "PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    config.body = JSON.stringify(injectedBody);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return originalFetch(resource, config);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// XHR POST interceptor
 | 
					 | 
				
			||||||
(function() {
 | 
					 | 
				
			||||||
  const originalOpen = XMLHttpRequest.prototype.open;
 | 
					 | 
				
			||||||
  const originalSend = XMLHttpRequest.prototype.send;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
 | 
					 | 
				
			||||||
    this._method = method;
 | 
					 | 
				
			||||||
    this._url = url;
 | 
					 | 
				
			||||||
    return originalOpen.apply(this, arguments);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  XMLHttpRequest.prototype.send = function(body) {
 | 
					 | 
				
			||||||
    if (this._method && this._method.toUpperCase() === 'POST') {
 | 
					 | 
				
			||||||
        if (body) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
 | 
					 | 
				
			||||||
                const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
 | 
					 | 
				
			||||||
                const base64Body = window.btoa(String.fromCharCode(...buffer));
 | 
					 | 
				
			||||||
                if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64Body !== remoteCDM.challenge) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if ((base64Body.startsWith("CAES") || base64Body.startsWith("PD94")) && interceptType == "LICENSE" &&!foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (base64Body.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (base64Body.startsWith("PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = base64ToUint8Array(remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    return originalSend.call(this, injectedBody);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (typeof body === 'string' && !isJson(body)) {
 | 
					 | 
				
			||||||
                const base64EncodedBody = btoa(body);
 | 
					 | 
				
			||||||
                if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && (!remoteCDM || remoteCDM.challenge === null || base64EncodedBody !== remoteCDM.challenge) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if ((base64EncodedBody.startsWith("CAES") || base64EncodedBody.startsWith("PD94")) && interceptType == "LICENSE" && !foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (base64EncodedBody.startsWith("CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (base64EncodedBody.startsWith("PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = atob(remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    return originalSend.call(this, injectedBody);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (typeof body === 'string' && isJson(body)) {
 | 
					 | 
				
			||||||
                const jsonBody = JSON.parse(body);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && (!remoteCDM || remoteCDM.challenge === null) && interceptType === "EME") {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    // Block the request
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if ((jsonContainsValue(jsonBody, "CAES") || jsonContainsValue(jsonBody, "PD94")) && interceptType === "LICENSE" && !foundChallengeInBody) {
 | 
					 | 
				
			||||||
                    foundChallengeInBody = true;
 | 
					 | 
				
			||||||
                    window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
 | 
					 | 
				
			||||||
                    if (!remoteCDM) {
 | 
					 | 
				
			||||||
                        if (jsonContainsValue(jsonBody, "CAES")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                device_type, system_id, security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = widevineDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remoteWidevineCDM(device_type, system_id, security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (jsonContainsValue(jsonBody, "PD94")) {
 | 
					 | 
				
			||||||
                            const {
 | 
					 | 
				
			||||||
                                security_level, host, secret, device_name
 | 
					 | 
				
			||||||
                            } = playreadyDeviceInfo;
 | 
					 | 
				
			||||||
                            remoteCDM = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
					 | 
				
			||||||
                            remoteCDM.openSession();
 | 
					 | 
				
			||||||
                            remoteCDM.getChallenge(foundPlayreadyPssh);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (remoteCDM && remoteCDM.challenge === null) {
 | 
					 | 
				
			||||||
                        remoteCDM.getChallenge(foundWidevinePssh);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
 | 
					 | 
				
			||||||
                    return originalSend.call(this, JSON.stringify(injectedBody));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return originalSend.apply(this, arguments);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
@ -1,41 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "manifest_version": 2,
 | 
					 | 
				
			||||||
  "name": "CDRM Extension 2.0",
 | 
					 | 
				
			||||||
  "version": "2.0",
 | 
					 | 
				
			||||||
  "description": "Decrypt DRM Protected content",
 | 
					 | 
				
			||||||
  "permissions": [
 | 
					 | 
				
			||||||
    "webRequest",
 | 
					 | 
				
			||||||
    "webRequestBlocking",
 | 
					 | 
				
			||||||
    "<all_urls>",
 | 
					 | 
				
			||||||
    "activeTab",
 | 
					 | 
				
			||||||
    "storage",
 | 
					 | 
				
			||||||
    "tabs",
 | 
					 | 
				
			||||||
    "contextMenus"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "background": {
 | 
					 | 
				
			||||||
    "scripts": ["background.js"],
 | 
					 | 
				
			||||||
    "persistent": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "content_scripts": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "matches": ["<all_urls>"],
 | 
					 | 
				
			||||||
      "js": ["content.js"],
 | 
					 | 
				
			||||||
      "run_at": "document_start",
 | 
					 | 
				
			||||||
      "all_frames": true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "web_accessible_resources": ["inject.js"],
 | 
					 | 
				
			||||||
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
 | 
					 | 
				
			||||||
  "browser_action": {
 | 
					 | 
				
			||||||
    "default_icon": {
 | 
					 | 
				
			||||||
      "16": "icons/icon16.png",
 | 
					 | 
				
			||||||
      "32": "icons/icon32.png",
 | 
					 | 
				
			||||||
      "128": "icons/icon128.png"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "icons": {
 | 
					 | 
				
			||||||
    "16": "icons/icon16.png",
 | 
					 | 
				
			||||||
    "32": "icons/icon32.png",
 | 
					 | 
				
			||||||
    "128": "icons/icon128.png"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										89
									
								
								mv2/background.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								mv2/background.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					// Open popout window when the extension icon is clicked
 | 
				
			||||||
 | 
					chrome.browserAction.onClicked.addListener(() => {
 | 
				
			||||||
 | 
					    chrome.windows.create({
 | 
				
			||||||
 | 
					        url: chrome.runtime.getURL("index.html"),
 | 
				
			||||||
 | 
					        type: "popup", // opens as a floating window
 | 
				
			||||||
 | 
					        width: 800,
 | 
				
			||||||
 | 
					        height: 600,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Listen for messages and store data in chrome.storage.local
 | 
				
			||||||
 | 
					chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
 | 
				
			||||||
 | 
					    const { type, data } = message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (type) {
 | 
				
			||||||
 | 
					        case "DRM_TYPE":
 | 
				
			||||||
 | 
					            console.log("DRM Type:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ drmType: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "PSSH_DATA":
 | 
				
			||||||
 | 
					            console.log("Storing PSSH:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ latestPSSH: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "KEYS_DATA":
 | 
				
			||||||
 | 
					            console.log("Storing Decryption Keys:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ latestKeys: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "LICENSE_URL":
 | 
				
			||||||
 | 
					            console.log("Storling License URL " + data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ licenseURL: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "MANIFEST_URL_FOUND":
 | 
				
			||||||
 | 
					            console.log("Storing Manifest URL:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ manifestURL: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            console.warn("Unknown message type received:", type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set initial config and injection type on install
 | 
				
			||||||
 | 
					chrome.runtime.onInstalled.addListener((details) => {
 | 
				
			||||||
 | 
					    if (details.reason === "install") {
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ valid_config: false }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error setting valid_config:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("valid_config set to false on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ injection_type: "LICENSE" }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error setting Injection Type:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("Injection type set to LICENSE on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ drm_override: "DISABLED" }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error setting DRM Override type:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("DRM Override type set to DISABLED on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ cdrm_instance: null }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error setting CDRM instance:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("CDRM instance set to null.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ cdrm_api_key: null }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("Error setting CDRM API Key:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("CDRM API Key set.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										96
									
								
								mv2/content.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								mv2/content.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					// Inject `inject.js` into the page context
 | 
				
			||||||
 | 
					(function injectScript() {
 | 
				
			||||||
 | 
					    const script = document.createElement("script");
 | 
				
			||||||
 | 
					    script.src = chrome.runtime.getURL("inject.js");
 | 
				
			||||||
 | 
					    script.type = "text/javascript";
 | 
				
			||||||
 | 
					    script.onload = () => script.remove(); // Clean up
 | 
				
			||||||
 | 
					    // Inject directly into <html> or <head>
 | 
				
			||||||
 | 
					    (document.documentElement || document.head || document.body).appendChild(script);
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Listen for messages from the injected script
 | 
				
			||||||
 | 
					window.addEventListener("message", function (event) {
 | 
				
			||||||
 | 
					    if (event.source !== window) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        ["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(
 | 
				
			||||||
 | 
					            event.data?.type
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: event.data.type.replace("__", "").replace("__", ""),
 | 
				
			||||||
 | 
					            data: event.data.data,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_CDM_DEVICES__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get(["widevine_device", "playready_device"], (result) => {
 | 
				
			||||||
 | 
					            const widevine_device = result.widevine_device || null;
 | 
				
			||||||
 | 
					            const playready_device = result.playready_device || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__CDM_DEVICES__",
 | 
				
			||||||
 | 
					                    widevine_device,
 | 
				
			||||||
 | 
					                    playready_device,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_INJECTION_TYPE__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get("injection_type", (result) => {
 | 
				
			||||||
 | 
					            const injectionType = result.injection_type || "LICENSE";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__INJECTION_TYPE__",
 | 
				
			||||||
 | 
					                    injectionType,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_DRM_OVERRIDE__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get("drm_override", (result) => {
 | 
				
			||||||
 | 
					            const drmOverride = result.drm_override || "DISABLED";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__DRM_OVERRIDE__",
 | 
				
			||||||
 | 
					                    drmOverride,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Manifest header and URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const seenManifestUrls = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data?.type === "__MANIFEST_URL__") {
 | 
				
			||||||
 | 
					        const url = event.data.data;
 | 
				
			||||||
 | 
					        if (seenManifestUrls.has(url)) return;
 | 
				
			||||||
 | 
					        seenManifestUrls.add(url);
 | 
				
			||||||
 | 
					        console.log("✅ [Content] Unique manifest URL:", url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: "MANIFEST_URL_FOUND",
 | 
				
			||||||
 | 
					            data: url,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data?.type === "__MANIFEST_HEADERS__") {
 | 
				
			||||||
 | 
					        const { url, headers } = event.data;
 | 
				
			||||||
 | 
					        console.log("[Content.js] Manifest Headers:", url, headers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: "MANIFEST_HEADERS",
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            headers,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										1143
									
								
								mv2/inject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1143
									
								
								mv2/inject.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										41
									
								
								mv2/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								mv2/manifest.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "manifest_version": 2,
 | 
				
			||||||
 | 
					    "name": "CDRM Extension",
 | 
				
			||||||
 | 
					    "version": "2.1.0",
 | 
				
			||||||
 | 
					    "description": "Decrypt DRM protected content",
 | 
				
			||||||
 | 
					    "permissions": [
 | 
				
			||||||
 | 
					        "webRequest",
 | 
				
			||||||
 | 
					        "webRequestBlocking",
 | 
				
			||||||
 | 
					        "<all_urls>",
 | 
				
			||||||
 | 
					        "activeTab",
 | 
				
			||||||
 | 
					        "storage",
 | 
				
			||||||
 | 
					        "tabs",
 | 
				
			||||||
 | 
					        "contextMenus"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "background": {
 | 
				
			||||||
 | 
					        "scripts": ["background.js"],
 | 
				
			||||||
 | 
					        "persistent": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "content_scripts": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "matches": ["<all_urls>"],
 | 
				
			||||||
 | 
					            "js": ["content.js"],
 | 
				
			||||||
 | 
					            "run_at": "document_start",
 | 
				
			||||||
 | 
					            "all_frames": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "web_accessible_resources": ["inject.js"],
 | 
				
			||||||
 | 
					    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
 | 
				
			||||||
 | 
					    "browser_action": {
 | 
				
			||||||
 | 
					        "default_icon": {
 | 
				
			||||||
 | 
					            "16": "icons/icon16.png",
 | 
				
			||||||
 | 
					            "32": "icons/icon32.png",
 | 
				
			||||||
 | 
					            "128": "icons/icon128.png"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "icons": {
 | 
				
			||||||
 | 
					        "16": "icons/icon16.png",
 | 
				
			||||||
 | 
					        "32": "icons/icon32.png",
 | 
				
			||||||
 | 
					        "128": "icons/icon128.png"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										136
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "name": "cdrm-extension",
 | 
				
			||||||
 | 
					    "version": "2.1.0",
 | 
				
			||||||
 | 
					    "lockfileVersion": 3,
 | 
				
			||||||
 | 
					    "requires": true,
 | 
				
			||||||
 | 
					    "packages": {
 | 
				
			||||||
 | 
					        "": {
 | 
				
			||||||
 | 
					            "name": "cdrm-extension",
 | 
				
			||||||
 | 
					            "version": "2.1.0",
 | 
				
			||||||
 | 
					            "license": "ISC",
 | 
				
			||||||
 | 
					            "devDependencies": {
 | 
				
			||||||
 | 
					                "terser": "^5.43.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">=21.0.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@jridgewell/gen-mapping": {
 | 
				
			||||||
 | 
					            "version": "0.3.12",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@jridgewell/sourcemap-codec": "^1.5.0",
 | 
				
			||||||
 | 
					                "@jridgewell/trace-mapping": "^0.3.24"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@jridgewell/resolve-uri": {
 | 
				
			||||||
 | 
					            "version": "3.1.2",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">=6.0.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@jridgewell/source-map": {
 | 
				
			||||||
 | 
					            "version": "0.3.10",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@jridgewell/gen-mapping": "^0.3.5",
 | 
				
			||||||
 | 
					                "@jridgewell/trace-mapping": "^0.3.25"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@jridgewell/sourcemap-codec": {
 | 
				
			||||||
 | 
					            "version": "1.5.4",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@jridgewell/trace-mapping": {
 | 
				
			||||||
 | 
					            "version": "0.3.29",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@jridgewell/resolve-uri": "^3.1.0",
 | 
				
			||||||
 | 
					                "@jridgewell/sourcemap-codec": "^1.4.14"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/acorn": {
 | 
				
			||||||
 | 
					            "version": "8.15.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "acorn": "bin/acorn"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">=0.4.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/buffer-from": {
 | 
				
			||||||
 | 
					            "version": "1.1.2",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/commander": {
 | 
				
			||||||
 | 
					            "version": "2.20.3",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/source-map": {
 | 
				
			||||||
 | 
					            "version": "0.6.1",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "BSD-3-Clause",
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">=0.10.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/source-map-support": {
 | 
				
			||||||
 | 
					            "version": "0.5.21",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "buffer-from": "^1.0.0",
 | 
				
			||||||
 | 
					                "source-map": "^0.6.0"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/terser": {
 | 
				
			||||||
 | 
					            "version": "5.43.1",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
 | 
				
			||||||
 | 
					            "dev": true,
 | 
				
			||||||
 | 
					            "license": "BSD-2-Clause",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@jridgewell/source-map": "^0.3.3",
 | 
				
			||||||
 | 
					                "acorn": "^8.14.0",
 | 
				
			||||||
 | 
					                "commander": "^2.20.0",
 | 
				
			||||||
 | 
					                "source-map-support": "~0.5.20"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "terser": "bin/terser"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">=10"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "name": "cdrm-extension",
 | 
				
			||||||
 | 
					    "version": "3.0",
 | 
				
			||||||
 | 
					    "description": "",
 | 
				
			||||||
 | 
					    "main": "background.js",
 | 
				
			||||||
 | 
					    "scripts": {
 | 
				
			||||||
 | 
					        "buildext": "node buildext.js",
 | 
				
			||||||
 | 
					        "test": "echo \"Error: no test specified\" && exit 1"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "repository": {
 | 
				
			||||||
 | 
					        "type": "git",
 | 
				
			||||||
 | 
					        "url": "https://cdm-project.com/tpd94/CDRM-Extension.git"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "keywords": [],
 | 
				
			||||||
 | 
					    "author": "",
 | 
				
			||||||
 | 
					    "license": "ISC",
 | 
				
			||||||
 | 
					    "type": "module",
 | 
				
			||||||
 | 
					    "engines": {
 | 
				
			||||||
 | 
					        "node": ">=21.0.0"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "devDependencies": {
 | 
				
			||||||
 | 
					        "terser": "^5.43.1"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,13 +0,0 @@
 | 
				
			|||||||
<!doctype html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <meta charset="UTF-8" />
 | 
					 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					 | 
				
			||||||
    <title>CDRM Decryption Extension</title>
 | 
					 | 
				
			||||||
    <script type="module" crossorigin src="./assets/index-ydPQKJSy.js"></script>
 | 
					 | 
				
			||||||
    <link rel="stylesheet" crossorigin href="./assets/index-UaipKa9p.css">
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body class="min-w-full min-h-full w-full h-full">
 | 
					 | 
				
			||||||
    <div class="min-w-full min-h-full w-full h-full" id="root"></div>
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										88
									
								
								src/background.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/background.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					chrome.action.onClicked.addListener(() => {
 | 
				
			||||||
 | 
					    chrome.windows.create({
 | 
				
			||||||
 | 
					        url: chrome.runtime.getURL("index.html"),
 | 
				
			||||||
 | 
					        type: "popup",
 | 
				
			||||||
 | 
					        width: 800,
 | 
				
			||||||
 | 
					        height: 600,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Listen for messages and store data in chrome.storage.local
 | 
				
			||||||
 | 
					chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
 | 
				
			||||||
 | 
					    const { type, data } = message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (type) {
 | 
				
			||||||
 | 
					        case "DRM_TYPE":
 | 
				
			||||||
 | 
					            console.log("[CDRM-Extension] DRM Type:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ drmType: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "PSSH_DATA":
 | 
				
			||||||
 | 
					            console.log("[CDRM-Extension] Storing PSSH:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ latestPSSH: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "KEYS_DATA":
 | 
				
			||||||
 | 
					            console.log("[CDRM-Extension] Storing Decryption Keys:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ latestKeys: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "LICENSE_URL":
 | 
				
			||||||
 | 
					            console.log("[CDRM-Extension] Storing License URL " + data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ licenseURL: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "MANIFEST_URL":
 | 
				
			||||||
 | 
					            console.log("[CDRM-Extension] Storing Manifest URL:", data);
 | 
				
			||||||
 | 
					            chrome.storage.local.set({ manifestURL: data });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            console.warn("[CDRM-Extension] Unknown message type received:", type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set initial config and injection type on install
 | 
				
			||||||
 | 
					chrome.runtime.onInstalled.addListener((details) => {
 | 
				
			||||||
 | 
					    if (details.reason === "install") {
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ valid_config: false }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("[CDRM-Extension] Error setting valid_config:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("[CDRM-Extension] valid_config set to false on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ injection_type: "LICENSE" }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("[CDRM-Extension] Error setting Injection Type:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("[CDRM-Extension] Injection type set to LICENSE on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ drm_override: "DISABLED" }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("[CDRM-Extension] Error setting DRM Override type:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("[CDRM-Extension] DRM Override type set to DISABLED on first install.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ cdrm_instance: null }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("[CDRM-Extension] Error setting CDRM instance:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("[CDRM-Extension] CDRM instance set to null.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.storage.local.set({ cdrm_api_key: null }, () => {
 | 
				
			||||||
 | 
					            if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					                console.error("[CDRM-Extension] Error setting CDRM API Key:", chrome.runtime.lastError);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("[CDRM-Extension] CDRM API Key set.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/content.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/content.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					// Inject `inject.js` into the page context
 | 
				
			||||||
 | 
					(function injectScript() {
 | 
				
			||||||
 | 
					    const script = document.createElement("script");
 | 
				
			||||||
 | 
					    script.src = chrome.runtime.getURL("inject.js");
 | 
				
			||||||
 | 
					    script.type = "text/javascript";
 | 
				
			||||||
 | 
					    script.onload = () => script.remove(); // Clean up
 | 
				
			||||||
 | 
					    // Inject directly into <html> or <head>
 | 
				
			||||||
 | 
					    (document.documentElement || document.head || document.body).appendChild(script);
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Listen for messages from the injected script
 | 
				
			||||||
 | 
					window.addEventListener("message", function (event) {
 | 
				
			||||||
 | 
					    if (event.source !== window) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        ["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(
 | 
				
			||||||
 | 
					            event.data?.type
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: event.data.type.replace("__", "").replace("__", ""),
 | 
				
			||||||
 | 
					            data: event.data.data,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_CDM_DEVICES__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get(["widevine_device", "playready_device"], (result) => {
 | 
				
			||||||
 | 
					            const widevine_device = result.widevine_device || null;
 | 
				
			||||||
 | 
					            const playready_device = result.playready_device || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__CDM_DEVICES__",
 | 
				
			||||||
 | 
					                    widevine_device,
 | 
				
			||||||
 | 
					                    playready_device,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_INJECTION_TYPE__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get("injection_type", (result) => {
 | 
				
			||||||
 | 
					            const injectionType = result.injection_type || "LICENSE";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__INJECTION_TYPE__",
 | 
				
			||||||
 | 
					                    injectionType,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data.type === "__GET_DRM_OVERRIDE__") {
 | 
				
			||||||
 | 
					        chrome.storage.local.get("drm_override", (result) => {
 | 
				
			||||||
 | 
					            const drmOverride = result.drm_override || "DISABLED";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.postMessage(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: "__DRM_OVERRIDE__",
 | 
				
			||||||
 | 
					                    drmOverride,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "*"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Manifest header and URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const seenManifestUrls = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data?.type === "__MANIFEST_URL__") {
 | 
				
			||||||
 | 
					        const url = event.data.data;
 | 
				
			||||||
 | 
					        if (seenManifestUrls.has(url)) return;
 | 
				
			||||||
 | 
					        seenManifestUrls.add(url);
 | 
				
			||||||
 | 
					        console.log("[CDRM-Extension] ✅ [content.js] Unique manifest URL:", url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: "MANIFEST_URL",
 | 
				
			||||||
 | 
					            data: url,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (event.data?.type === "__MANIFEST_HEADERS__") {
 | 
				
			||||||
 | 
					        const { url, headers } = event.data;
 | 
				
			||||||
 | 
					        console.log("[CDRM-Extension] [content.js] Manifest headers:", url, headers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chrome.runtime.sendMessage({
 | 
				
			||||||
 | 
					            type: "MANIFEST_HEADERS",
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            headers,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										930
									
								
								src/inject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										930
									
								
								src/inject.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,930 @@
 | 
				
			|||||||
 | 
					let widevineDeviceInfo = null;
 | 
				
			||||||
 | 
					let playreadyDeviceInfo = null;
 | 
				
			||||||
 | 
					let originalChallenge = null;
 | 
				
			||||||
 | 
					let serviceCertFound = false;
 | 
				
			||||||
 | 
					let drmType = "NONE";
 | 
				
			||||||
 | 
					let psshFound = false;
 | 
				
			||||||
 | 
					let foundWidevinePssh = null;
 | 
				
			||||||
 | 
					let foundPlayreadyPssh = null;
 | 
				
			||||||
 | 
					let drmDecided = null;
 | 
				
			||||||
 | 
					let drmOverride = "DISABLED";
 | 
				
			||||||
 | 
					let interceptType = "DISABLED";
 | 
				
			||||||
 | 
					let remoteCDM = null;
 | 
				
			||||||
 | 
					let generateRequestCalled = false;
 | 
				
			||||||
 | 
					let remoteListenerMounted = false;
 | 
				
			||||||
 | 
					let injectionSuccess = false;
 | 
				
			||||||
 | 
					let foundChallengeInBody = false;
 | 
				
			||||||
 | 
					let licenseResponseCounter = 0;
 | 
				
			||||||
 | 
					let keysRetrieved = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DRM_SIGNATURES = {
 | 
				
			||||||
 | 
					    WIDEVINE: "CAES",
 | 
				
			||||||
 | 
					    PLAYREADY: "PD94",
 | 
				
			||||||
 | 
					    SERVICE_CERT: "CAUS",
 | 
				
			||||||
 | 
					    WIDEVINE_INIT: "CAQ=",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EXTENSION_PREFIX = "[CDRM EXTENSION]";
 | 
				
			||||||
 | 
					const PREFIX_COLOR = "black";
 | 
				
			||||||
 | 
					const PREFIX_BACKGROUND_COLOR = "yellow";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const logWithPrefix = (...args) => {
 | 
				
			||||||
 | 
					    const style = `color: ${PREFIX_COLOR}; background: ${PREFIX_BACKGROUND_COLOR}; font-weight: bold; padding: 2px 4px; border-radius: 2px;`;
 | 
				
			||||||
 | 
					    if (typeof args[0] === "string") {
 | 
				
			||||||
 | 
					        // If the first arg is a string, prepend the prefix
 | 
				
			||||||
 | 
					        console.log(`%c${EXTENSION_PREFIX}%c ${args[0]}`, style, "", ...args.slice(1));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // If not, just log the prefix and the rest
 | 
				
			||||||
 | 
					        console.log(`%c${EXTENSION_PREFIX}`, style, ...args);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resetDRMState() {
 | 
				
			||||||
 | 
					    logWithPrefix("Resetting DRM state for new manifest...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Reset DRM detection state
 | 
				
			||||||
 | 
					    originalChallenge = null;
 | 
				
			||||||
 | 
					    serviceCertFound = false;
 | 
				
			||||||
 | 
					    drmType = "NONE";
 | 
				
			||||||
 | 
					    psshFound = false;
 | 
				
			||||||
 | 
					    foundWidevinePssh = null;
 | 
				
			||||||
 | 
					    foundPlayreadyPssh = null;
 | 
				
			||||||
 | 
					    drmDecided = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Reset CDM and session state
 | 
				
			||||||
 | 
					    if (remoteCDM) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Try to close the existing session if it exists
 | 
				
			||||||
 | 
					            if (remoteCDM.session_id) {
 | 
				
			||||||
 | 
					                remoteCDM.closeSession();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            // Ignore errors when closing session
 | 
				
			||||||
 | 
					            logWithPrefix("Error closing previous CDM session:", e.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        remoteCDM = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Reset interceptor state
 | 
				
			||||||
 | 
					    generateRequestCalled = false;
 | 
				
			||||||
 | 
					    remoteListenerMounted = false;
 | 
				
			||||||
 | 
					    injectionSuccess = false;
 | 
				
			||||||
 | 
					    foundChallengeInBody = false;
 | 
				
			||||||
 | 
					    licenseResponseCounter = 0;
 | 
				
			||||||
 | 
					    keysRetrieved = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Post reset messages to clear UI state
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__DRM_TYPE__", data: "" }, "*");
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__PSSH_DATA__", data: "" }, "*");
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__KEYS_DATA__", data: "" }, "*");
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__LICENSE_URL__", data: "" }, "*");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.postMessage({ type: "__GET_DRM_OVERRIDE__" }, "*");
 | 
				
			||||||
 | 
					window.postMessage({ type: "__GET_INJECTION_TYPE__" }, "*");
 | 
				
			||||||
 | 
					window.postMessage({ type: "__GET_CDM_DEVICES__" }, "*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createMessageHandler(handlers) {
 | 
				
			||||||
 | 
					    window.addEventListener("message", function (event) {
 | 
				
			||||||
 | 
					        if (event.source !== window) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const handler = handlers[event.data.type];
 | 
				
			||||||
 | 
					        if (handler) {
 | 
				
			||||||
 | 
					            handler(event.data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					createMessageHandler({
 | 
				
			||||||
 | 
					    __DRM_OVERRIDE__: (data) => {
 | 
				
			||||||
 | 
					        drmOverride = data.drmOverride || "DISABLED";
 | 
				
			||||||
 | 
					        logWithPrefix("DRM Override set to:", drmOverride);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    __INJECTION_TYPE__: (data) => {
 | 
				
			||||||
 | 
					        interceptType = data.injectionType || "DISABLED";
 | 
				
			||||||
 | 
					        logWithPrefix("Injection type set to:", interceptType);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    __CDM_DEVICES__: (data) => {
 | 
				
			||||||
 | 
					        const { widevine_device, playready_device } = data;
 | 
				
			||||||
 | 
					        logWithPrefix("Received device info:", widevine_device, playready_device);
 | 
				
			||||||
 | 
					        widevineDeviceInfo = widevine_device;
 | 
				
			||||||
 | 
					        playreadyDeviceInfo = playready_device;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function safeHeaderShellEscape(str) {
 | 
				
			||||||
 | 
					    return str
 | 
				
			||||||
 | 
					        .replace(/\\/g, "\\\\")
 | 
				
			||||||
 | 
					        .replace(/"/g, '\\"')
 | 
				
			||||||
 | 
					        .replace(/\$/g, "\\$") // escape shell expansion
 | 
				
			||||||
 | 
					        .replace(/`/g, "\\`")
 | 
				
			||||||
 | 
					        .replace(/\n/g, ""); // strip newlines
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function headersToFlags(headersObj) {
 | 
				
			||||||
 | 
					    return Object.entries(headersObj)
 | 
				
			||||||
 | 
					        .map(
 | 
				
			||||||
 | 
					            ([key, val]) =>
 | 
				
			||||||
 | 
					                '--add-headers "' +
 | 
				
			||||||
 | 
					                safeHeaderShellEscape(key) +
 | 
				
			||||||
 | 
					                ": " +
 | 
				
			||||||
 | 
					                safeHeaderShellEscape(val) +
 | 
				
			||||||
 | 
					                '"'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .join(" ");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleManifestDetection(url, headersObj, contentType, source) {
 | 
				
			||||||
 | 
					    // Reset DRM state when new manifest is detected
 | 
				
			||||||
 | 
					    resetDRMState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__MANIFEST_URL__", data: url }, "*");
 | 
				
			||||||
 | 
					    logWithPrefix(`[Manifest][${source}]`, url, contentType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const headerFlags = headersToFlags(headersObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.postMessage(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            type: "__MANIFEST_HEADERS__",
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            headers: headerFlags,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "*"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Intercep network to find manifest
 | 
				
			||||||
 | 
					function injectManifestInterceptor() {
 | 
				
			||||||
 | 
					    // Execute the interceptor code directly instead of injecting a script
 | 
				
			||||||
 | 
					    (function () {
 | 
				
			||||||
 | 
					        function isProbablyManifest(text = "", contentType = "") {
 | 
				
			||||||
 | 
					            const lowerCT = contentType?.toLowerCase() ?? "";
 | 
				
			||||||
 | 
					            const sample = text.slice(0, 2000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const isHLSMime = lowerCT.includes("mpegurl");
 | 
				
			||||||
 | 
					            const isDASHMime = lowerCT.includes("dash+xml");
 | 
				
			||||||
 | 
					            const isSmoothMime = lowerCT.includes("sstr+xml");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const isHLSKeyword = sample.includes("#EXTM3U") || sample.includes("#EXT-X-STREAM-INF");
 | 
				
			||||||
 | 
					            const isDASHKeyword = sample.includes("<MPD") || sample.includes("<AdaptationSet");
 | 
				
			||||||
 | 
					            const isSmoothKeyword = sample.includes("<SmoothStreamingMedia");
 | 
				
			||||||
 | 
					            const isJsonManifest = sample.includes('"playlist"') && sample.includes('"segments"');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					                isHLSMime ||
 | 
				
			||||||
 | 
					                isDASHMime ||
 | 
				
			||||||
 | 
					                isSmoothMime ||
 | 
				
			||||||
 | 
					                isHLSKeyword ||
 | 
				
			||||||
 | 
					                isDASHKeyword ||
 | 
				
			||||||
 | 
					                isSmoothKeyword ||
 | 
				
			||||||
 | 
					                isJsonManifest
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const originalFetch = window.fetch;
 | 
				
			||||||
 | 
					        window.fetch = async function (input, init) {
 | 
				
			||||||
 | 
					            const response = await originalFetch.apply(this, arguments);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const clone = response.clone();
 | 
				
			||||||
 | 
					                const contentType = clone.headers.get("content-type") || "";
 | 
				
			||||||
 | 
					                const text = await clone.text();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const url = typeof input === "string" ? input : input.url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (isProbablyManifest(text, contentType)) {
 | 
				
			||||||
 | 
					                    const headersObj = {};
 | 
				
			||||||
 | 
					                    clone.headers.forEach((value, key) => {
 | 
				
			||||||
 | 
					                        headersObj[key] = value;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    handleManifestDetection(url, headersObj, contentType, "fetch");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return response;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const originalXHROpen = XMLHttpRequest.prototype.open;
 | 
				
			||||||
 | 
					        const originalXHRSend = XMLHttpRequest.prototype.send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        XMLHttpRequest.prototype.open = function (method, url) {
 | 
				
			||||||
 | 
					            this.__url = url;
 | 
				
			||||||
 | 
					            return originalXHROpen.apply(this, arguments);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        XMLHttpRequest.prototype.send = function (body) {
 | 
				
			||||||
 | 
					            this.addEventListener("load", function () {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const contentType = this.getResponseHeader("content-type") || "";
 | 
				
			||||||
 | 
					                    const text = this.responseText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (isProbablyManifest(text, contentType)) {
 | 
				
			||||||
 | 
					                        const xhrHeaders = {};
 | 
				
			||||||
 | 
					                        const rawHeaders = this.getAllResponseHeaders().trim().split(/\r?\n/);
 | 
				
			||||||
 | 
					                        rawHeaders.forEach((line) => {
 | 
				
			||||||
 | 
					                            const parts = line.split(": ");
 | 
				
			||||||
 | 
					                            if (parts.length === 2) {
 | 
				
			||||||
 | 
					                                xhrHeaders[parts[0]] = parts[1];
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        handleManifestDetection(this.__url, xhrHeaders, contentType, "xhr");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch (e) {}
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            return originalXHRSend.apply(this, arguments);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    })();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					injectManifestInterceptor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RemoteCDMBase {
 | 
				
			||||||
 | 
					    constructor({ host, secret, device_name, security_level }) {
 | 
				
			||||||
 | 
					        this.host = host;
 | 
				
			||||||
 | 
					        this.secret = secret;
 | 
				
			||||||
 | 
					        this.device_name = device_name;
 | 
				
			||||||
 | 
					        this.security_level = security_level;
 | 
				
			||||||
 | 
					        this.session_id = null;
 | 
				
			||||||
 | 
					        this.challenge = null;
 | 
				
			||||||
 | 
					        this.keys = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    openSession(path) {
 | 
				
			||||||
 | 
					        const url = `${this.host}${path}/open`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("GET", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        xhr.send();
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (jsonData.data?.session_id) {
 | 
				
			||||||
 | 
					            this.session_id = jsonData.data.session_id;
 | 
				
			||||||
 | 
					            logWithPrefix("Session opened:", this.session_id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to open session:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to open session");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getChallenge(path, body) {
 | 
				
			||||||
 | 
					        const url = `${this.host}${path}/get_license_challenge`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("POST", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        xhr.send(JSON.stringify(body));
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (jsonData.data?.challenge) {
 | 
				
			||||||
 | 
					            this.challenge = btoa(jsonData.data.challenge);
 | 
				
			||||||
 | 
					            logWithPrefix("Challenge received:", this.challenge);
 | 
				
			||||||
 | 
					        } else if (jsonData.data?.challenge_b64) {
 | 
				
			||||||
 | 
					            this.challenge = jsonData.data.challenge_b64;
 | 
				
			||||||
 | 
					            logWithPrefix("Challenge received:", this.challenge);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to get challenge:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to get challenge");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parseLicense(path, body) {
 | 
				
			||||||
 | 
					        const url = `${this.host}${path}/parse_license`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("POST", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        xhr.send(JSON.stringify(body));
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            xhr.status === 200 ||
 | 
				
			||||||
 | 
					            jsonData.status === "Success" ||
 | 
				
			||||||
 | 
					            jsonData.status === 200 ||
 | 
				
			||||||
 | 
					            jsonData.message?.includes("parsed and loaded")
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            logWithPrefix("License response parsed successfully");
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to parse license response:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to parse license response");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getKeys(path, body, extraPath = "") {
 | 
				
			||||||
 | 
					        const url = `${this.host}${path}/get_keys${extraPath}`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("POST", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        xhr.send(JSON.stringify(body));
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (jsonData.data?.keys) {
 | 
				
			||||||
 | 
					            this.keys = jsonData.data.keys;
 | 
				
			||||||
 | 
					            logWithPrefix("Keys received:", this.keys);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to get keys:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to get keys");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    closeSession(path) {
 | 
				
			||||||
 | 
					        const url = `${this.host}${path}/close/${this.session_id}`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("GET", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        xhr.send();
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (jsonData) {
 | 
				
			||||||
 | 
					            logWithPrefix("Session closed successfully");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to close session:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to close session");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PlayReady Remote CDM Class
 | 
				
			||||||
 | 
					class remotePlayReadyCDM extends RemoteCDMBase {
 | 
				
			||||||
 | 
					    constructor(security_level, host, secret, device_name) {
 | 
				
			||||||
 | 
					        super({ host, secret, device_name, security_level });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    openSession() {
 | 
				
			||||||
 | 
					        super.openSession(`/remotecdm/playready/${this.device_name}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getChallenge(init_data) {
 | 
				
			||||||
 | 
					        super.getChallenge(`/remotecdm/playready/${this.device_name}`, {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					            init_data: init_data,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parseLicense(license_message) {
 | 
				
			||||||
 | 
					        return super.parseLicense(`/remotecdm/playready/${this.device_name}`, {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					            license_message: license_message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getKeys() {
 | 
				
			||||||
 | 
					        super.getKeys(`/remotecdm/playready/${this.device_name}`, {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    closeSession() {
 | 
				
			||||||
 | 
					        super.closeSession(`/remotecdm/playready/${this.device_name}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Widevine Remote CDM Class
 | 
				
			||||||
 | 
					class remoteWidevineCDM extends RemoteCDMBase {
 | 
				
			||||||
 | 
					    constructor(device_type, system_id, security_level, host, secret, device_name) {
 | 
				
			||||||
 | 
					        super({ host, secret, device_name, security_level });
 | 
				
			||||||
 | 
					        this.device_type = device_type;
 | 
				
			||||||
 | 
					        this.system_id = system_id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    openSession() {
 | 
				
			||||||
 | 
					        super.openSession(`/remotecdm/widevine/${this.device_name}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setServiceCertificate(certificate) {
 | 
				
			||||||
 | 
					        const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("POST", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        const body = {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					            certificate: certificate ?? null,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        xhr.send(JSON.stringify(body));
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (xhr.status === 200 || jsonData.status === "Success" || jsonData.status === 200) {
 | 
				
			||||||
 | 
					            logWithPrefix("Service certificate set successfully");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to set service certificate:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to set service certificate");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getChallenge(init_data, license_type = "STREAMING") {
 | 
				
			||||||
 | 
					        const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
 | 
				
			||||||
 | 
					        const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.open("POST", url, false);
 | 
				
			||||||
 | 
					        xhr.setRequestHeader("Content-Type", "application/json");
 | 
				
			||||||
 | 
					        const body = {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					            init_data: init_data,
 | 
				
			||||||
 | 
					            privacy_mode: serviceCertFound,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        xhr.send(JSON.stringify(body));
 | 
				
			||||||
 | 
					        const jsonData = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					        if (jsonData.data?.challenge_b64) {
 | 
				
			||||||
 | 
					            this.challenge = jsonData.data.challenge_b64;
 | 
				
			||||||
 | 
					            logWithPrefix("Widevine challenge received:", this.challenge);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.error("Failed to get Widevine challenge:", jsonData.message);
 | 
				
			||||||
 | 
					            throw new Error("Failed to get Widevine challenge");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parseLicense(license_message) {
 | 
				
			||||||
 | 
					        return super.parseLicense(`/remotecdm/widevine/${this.device_name}`, {
 | 
				
			||||||
 | 
					            session_id: this.session_id,
 | 
				
			||||||
 | 
					            license_message: license_message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getKeys() {
 | 
				
			||||||
 | 
					        super.getKeys(
 | 
				
			||||||
 | 
					            `/remotecdm/widevine/${this.device_name}`,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                session_id: this.session_id,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/ALL"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    closeSession() {
 | 
				
			||||||
 | 
					        super.closeSession(`/remotecdm/widevine/${this.device_name}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Utility functions
 | 
				
			||||||
 | 
					function hexStrToU8(hexString) {
 | 
				
			||||||
 | 
					    return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function u8ToHexStr(bytes) {
 | 
				
			||||||
 | 
					    return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function b64ToHexStr(b64) {
 | 
				
			||||||
 | 
					    return [...atob(b64)].map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join``;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function jsonContainsValue(obj, prefix = DRM_SIGNATURES.WIDEVINE) {
 | 
				
			||||||
 | 
					    if (typeof obj === "string") return obj.startsWith(prefix);
 | 
				
			||||||
 | 
					    if (Array.isArray(obj)) return obj.some((val) => jsonContainsValue(val, prefix));
 | 
				
			||||||
 | 
					    if (typeof obj === "object" && obj !== null) {
 | 
				
			||||||
 | 
					        return Object.values(obj).some((val) => jsonContainsValue(val, prefix));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function jsonReplaceValue(obj, newValue) {
 | 
				
			||||||
 | 
					    if (typeof obj === "string") {
 | 
				
			||||||
 | 
					        return obj.startsWith(DRM_SIGNATURES.WIDEVINE) || obj.startsWith(DRM_SIGNATURES.PLAYREADY)
 | 
				
			||||||
 | 
					            ? newValue
 | 
				
			||||||
 | 
					            : obj;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (Array.isArray(obj)) {
 | 
				
			||||||
 | 
					        return obj.map((item) => jsonReplaceValue(item, newValue));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof obj === "object" && obj !== null) {
 | 
				
			||||||
 | 
					        const newObj = {};
 | 
				
			||||||
 | 
					        for (const key in obj) {
 | 
				
			||||||
 | 
					            if (Object.hasOwn(obj, key)) {
 | 
				
			||||||
 | 
					                newObj[key] = jsonReplaceValue(obj[key], newValue);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return newObj;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return obj;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isJson(str) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        JSON.parse(str);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getWidevinePssh(buffer) {
 | 
				
			||||||
 | 
					    const hex = u8ToHexStr(new Uint8Array(buffer));
 | 
				
			||||||
 | 
					    const match = hex.match(/000000(..)?70737368.*/);
 | 
				
			||||||
 | 
					    if (!match) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const boxHex = match[0];
 | 
				
			||||||
 | 
					    const bytes = hexStrToU8(boxHex);
 | 
				
			||||||
 | 
					    return window.btoa(String.fromCharCode(...bytes));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getPlayReadyPssh(buffer) {
 | 
				
			||||||
 | 
					    const u8 = new Uint8Array(buffer);
 | 
				
			||||||
 | 
					    const systemId = "9a04f07998404286ab92e65be0885f95";
 | 
				
			||||||
 | 
					    const hex = u8ToHexStr(u8);
 | 
				
			||||||
 | 
					    const index = hex.indexOf(systemId);
 | 
				
			||||||
 | 
					    if (index === -1) return null;
 | 
				
			||||||
 | 
					    const psshBoxStart = hex.lastIndexOf("70737368", index);
 | 
				
			||||||
 | 
					    if (psshBoxStart === -1) return null;
 | 
				
			||||||
 | 
					    const lenStart = psshBoxStart - 8;
 | 
				
			||||||
 | 
					    const boxLen = parseInt(hex.substr(lenStart, 8), 16) * 2;
 | 
				
			||||||
 | 
					    const psshHex = hex.substr(lenStart, boxLen);
 | 
				
			||||||
 | 
					    const psshBytes = hexStrToU8(psshHex);
 | 
				
			||||||
 | 
					    return window.btoa(String.fromCharCode(...psshBytes));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getClearkey(response) {
 | 
				
			||||||
 | 
					    let obj = JSON.parse(new TextDecoder("utf-8").decode(response));
 | 
				
			||||||
 | 
					    return obj["keys"].map((o) => ({
 | 
				
			||||||
 | 
					        key_id: b64ToHexStr(o["kid"].replace(/-/g, "+").replace(/_/g, "/")),
 | 
				
			||||||
 | 
					        key: b64ToHexStr(o["k"].replace(/-/g, "+").replace(/_/g, "/")),
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function base64ToUint8Array(base64) {
 | 
				
			||||||
 | 
					    const binaryStr = atob(base64);
 | 
				
			||||||
 | 
					    const len = binaryStr.length;
 | 
				
			||||||
 | 
					    const bytes = new Uint8Array(len);
 | 
				
			||||||
 | 
					    for (let i = 0; i < len; i++) {
 | 
				
			||||||
 | 
					        bytes[i] = binaryStr.charCodeAt(i);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return bytes;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function arrayBufferToBase64(uint8array) {
 | 
				
			||||||
 | 
					    let binary = "";
 | 
				
			||||||
 | 
					    const len = uint8array.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < len; i++) {
 | 
				
			||||||
 | 
					        binary += String.fromCharCode(uint8array[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return window.btoa(binary);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function bufferToBase64(buffer) {
 | 
				
			||||||
 | 
					    const uint8 = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
 | 
				
			||||||
 | 
					    return window.btoa(String.fromCharCode(...uint8));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DRM type detection
 | 
				
			||||||
 | 
					function isWidevine(base64str) {
 | 
				
			||||||
 | 
					    return base64str.startsWith(DRM_SIGNATURES.WIDEVINE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function isPlayReady(base64str) {
 | 
				
			||||||
 | 
					    return base64str.startsWith(DRM_SIGNATURES.PLAYREADY);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function isServiceCertificate(base64str) {
 | 
				
			||||||
 | 
					    return base64str.startsWith(DRM_SIGNATURES.SERVICE_CERT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function postDRMTypeAndPssh(type, pssh) {
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__DRM_TYPE__", data: type }, "*");
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__PSSH_DATA__", data: pssh }, "*");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createAndOpenRemoteCDM(type, deviceInfo, pssh) {
 | 
				
			||||||
 | 
					    let cdm;
 | 
				
			||||||
 | 
					    if (type === "Widevine") {
 | 
				
			||||||
 | 
					        const { device_type, system_id, security_level, host, secret, device_name } = deviceInfo;
 | 
				
			||||||
 | 
					        cdm = new remoteWidevineCDM(
 | 
				
			||||||
 | 
					            device_type,
 | 
				
			||||||
 | 
					            system_id,
 | 
				
			||||||
 | 
					            security_level,
 | 
				
			||||||
 | 
					            host,
 | 
				
			||||||
 | 
					            secret,
 | 
				
			||||||
 | 
					            device_name
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        cdm.openSession();
 | 
				
			||||||
 | 
					        cdm.getChallenge(pssh);
 | 
				
			||||||
 | 
					    } else if (type === "PlayReady") {
 | 
				
			||||||
 | 
					        const { security_level, host, secret, device_name } = deviceInfo;
 | 
				
			||||||
 | 
					        cdm = new remotePlayReadyCDM(security_level, host, secret, device_name);
 | 
				
			||||||
 | 
					        cdm.openSession();
 | 
				
			||||||
 | 
					        cdm.getChallenge(pssh);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return cdm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ensureRemoteCDM(type, deviceInfo, pssh) {
 | 
				
			||||||
 | 
					    if (!remoteCDM) {
 | 
				
			||||||
 | 
					        remoteCDM = createAndOpenRemoteCDM(type, deviceInfo, pssh);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function detectAndStorePssh(initData) {
 | 
				
			||||||
 | 
					    const detections = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            type: "PlayReady",
 | 
				
			||||||
 | 
					            getter: getPlayReadyPssh,
 | 
				
			||||||
 | 
					            store: (pssh) => (foundPlayreadyPssh = pssh),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        { type: "Widevine", getter: getWidevinePssh, store: (pssh) => (foundWidevinePssh = pssh) },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    detections.forEach(({ type, getter, store }) => {
 | 
				
			||||||
 | 
					        const pssh = getter(initData);
 | 
				
			||||||
 | 
					        if (pssh) {
 | 
				
			||||||
 | 
					            logWithPrefix(`[DRM Detected] ${type}`);
 | 
				
			||||||
 | 
					            store(pssh);
 | 
				
			||||||
 | 
					            logWithPrefix(`[${type} PSSH found] ${pssh}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Challenge generator interceptor
 | 
				
			||||||
 | 
					const originalGenerateRequest = MediaKeySession.prototype.generateRequest;
 | 
				
			||||||
 | 
					MediaKeySession.prototype.generateRequest = function (initDataType, initData) {
 | 
				
			||||||
 | 
					    const session = this;
 | 
				
			||||||
 | 
					    detectAndStorePssh(initData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Challenge message interceptor
 | 
				
			||||||
 | 
					    if (!remoteListenerMounted) {
 | 
				
			||||||
 | 
					        remoteListenerMounted = true;
 | 
				
			||||||
 | 
					        session.addEventListener("message", function messageInterceptor(event) {
 | 
				
			||||||
 | 
					            event.stopImmediatePropagation();
 | 
				
			||||||
 | 
					            const base64challenge = bufferToBase64(event.message);
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                base64challenge === DRM_SIGNATURES.WIDEVINE_INIT &&
 | 
				
			||||||
 | 
					                interceptType !== "DISABLED" &&
 | 
				
			||||||
 | 
					                !serviceCertFound
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                remoteCDM = createAndOpenRemoteCDM(
 | 
				
			||||||
 | 
					                    "Widevine",
 | 
				
			||||||
 | 
					                    widevineDeviceInfo,
 | 
				
			||||||
 | 
					                    foundWidevinePssh
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !injectionSuccess &&
 | 
				
			||||||
 | 
					                base64challenge !== DRM_SIGNATURES.WIDEVINE_INIT &&
 | 
				
			||||||
 | 
					                interceptType !== "DISABLED"
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                if (interceptType === "EME") {
 | 
				
			||||||
 | 
					                    injectionSuccess = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!originalChallenge) {
 | 
				
			||||||
 | 
					                    originalChallenge = base64challenge;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (originalChallenge.startsWith(DRM_SIGNATURES.WIDEVINE)) {
 | 
				
			||||||
 | 
					                    postDRMTypeAndPssh("Widevine", foundWidevinePssh);
 | 
				
			||||||
 | 
					                    if (interceptType === "EME") {
 | 
				
			||||||
 | 
					                        ensureRemoteCDM("Widevine", widevineDeviceInfo, foundWidevinePssh);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!originalChallenge.startsWith(DRM_SIGNATURES.WIDEVINE)) {
 | 
				
			||||||
 | 
					                    const buffer = event.message;
 | 
				
			||||||
 | 
					                    const decoder = new TextDecoder("utf-16");
 | 
				
			||||||
 | 
					                    const decodedText = decoder.decode(buffer);
 | 
				
			||||||
 | 
					                    const match = decodedText.match(
 | 
				
			||||||
 | 
					                        /<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    if (match) {
 | 
				
			||||||
 | 
					                        postDRMTypeAndPssh("PlayReady", foundPlayreadyPssh);
 | 
				
			||||||
 | 
					                        originalChallenge = match[1];
 | 
				
			||||||
 | 
					                        if (interceptType === "EME") {
 | 
				
			||||||
 | 
					                            ensureRemoteCDM("PlayReady", playreadyDeviceInfo, foundPlayreadyPssh);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (interceptType === "EME" && remoteCDM) {
 | 
				
			||||||
 | 
					                    const uint8challenge = base64ToUint8Array(remoteCDM.challenge);
 | 
				
			||||||
 | 
					                    const challengeBuffer = uint8challenge.buffer;
 | 
				
			||||||
 | 
					                    const syntheticEvent = new MessageEvent("message", {
 | 
				
			||||||
 | 
					                        data: event.data,
 | 
				
			||||||
 | 
					                        origin: event.origin,
 | 
				
			||||||
 | 
					                        lastEventId: event.lastEventId,
 | 
				
			||||||
 | 
					                        source: event.source,
 | 
				
			||||||
 | 
					                        ports: event.ports,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    Object.defineProperty(syntheticEvent, "message", {
 | 
				
			||||||
 | 
					                        get: () => challengeBuffer,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    logWithPrefix("Intercepted EME Challenge and injected custom one.");
 | 
				
			||||||
 | 
					                    session.dispatchEvent(syntheticEvent);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        logWithPrefix("Message interceptor mounted.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return originalGenerateRequest.call(session, initDataType, initData);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Message update interceptors
 | 
				
			||||||
 | 
					const originalUpdate = MediaKeySession.prototype.update;
 | 
				
			||||||
 | 
					MediaKeySession.prototype.update = function (response) {
 | 
				
			||||||
 | 
					    const base64Response = bufferToBase64(response);
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        base64Response.startsWith(DRM_SIGNATURES.SERVICE_CERT) &&
 | 
				
			||||||
 | 
					        foundWidevinePssh &&
 | 
				
			||||||
 | 
					        remoteCDM &&
 | 
				
			||||||
 | 
					        !serviceCertFound
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        remoteCDM.setServiceCertificate(base64Response);
 | 
				
			||||||
 | 
					        if (interceptType === "EME" && !remoteCDM.challenge) {
 | 
				
			||||||
 | 
					            remoteCDM.getChallenge(foundWidevinePssh);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        window.postMessage({ type: "__DRM_TYPE__", data: "Widevine" }, "*");
 | 
				
			||||||
 | 
					        window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
 | 
				
			||||||
 | 
					        serviceCertFound = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !base64Response.startsWith(DRM_SIGNATURES.SERVICE_CERT) &&
 | 
				
			||||||
 | 
					        (foundWidevinePssh || foundPlayreadyPssh) &&
 | 
				
			||||||
 | 
					        !keysRetrieved
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        if (licenseResponseCounter === 1 || foundChallengeInBody) {
 | 
				
			||||||
 | 
					            remoteCDM.parseLicense(base64Response);
 | 
				
			||||||
 | 
					            remoteCDM.getKeys();
 | 
				
			||||||
 | 
					            remoteCDM.closeSession();
 | 
				
			||||||
 | 
					            keysRetrieved = true;
 | 
				
			||||||
 | 
					            window.postMessage({ type: "__KEYS_DATA__", data: remoteCDM.keys }, "*");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        licenseResponseCounter++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const updatePromise = originalUpdate.call(this, response);
 | 
				
			||||||
 | 
					    if (!foundPlayreadyPssh && !foundWidevinePssh) {
 | 
				
			||||||
 | 
					        updatePromise
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
 | 
					                let clearKeys = getClearkey(response);
 | 
				
			||||||
 | 
					                if (clearKeys && clearKeys.length > 0) {
 | 
				
			||||||
 | 
					                    logWithPrefix("[CLEARKEY] ", clearKeys);
 | 
				
			||||||
 | 
					                    const drmType = {
 | 
				
			||||||
 | 
					                        type: "__DRM_TYPE__",
 | 
				
			||||||
 | 
					                        data: "ClearKey",
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    window.postMessage(drmType, "*");
 | 
				
			||||||
 | 
					                    const keysData = {
 | 
				
			||||||
 | 
					                        type: "__KEYS_DATA__",
 | 
				
			||||||
 | 
					                        data: clearKeys,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    window.postMessage(keysData, "*");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch((e) => {
 | 
				
			||||||
 | 
					                logWithPrefix("[CLEARKEY] Not found");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return updatePromise;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helpers
 | 
				
			||||||
 | 
					function detectDRMChallenge(body) {
 | 
				
			||||||
 | 
					    // Handles ArrayBuffer, Uint8Array, string, and JSON string
 | 
				
			||||||
 | 
					    // Returns: { type: "Widevine"|"PlayReady"|null, base64: string|null, bodyType: "buffer"|"string"|"json"|null }
 | 
				
			||||||
 | 
					    if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
 | 
				
			||||||
 | 
					        const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
 | 
				
			||||||
 | 
					        const base64Body = window.btoa(String.fromCharCode(...buffer));
 | 
				
			||||||
 | 
					        if (base64Body.startsWith(DRM_SIGNATURES.WIDEVINE)) {
 | 
				
			||||||
 | 
					            return { type: "Widevine", base64: base64Body, bodyType: "buffer" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
 | 
				
			||||||
 | 
					            return { type: "PlayReady", base64: base64Body, bodyType: "buffer" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if (typeof body === "string" && !isJson(body)) {
 | 
				
			||||||
 | 
					        const base64EncodedBody = btoa(body);
 | 
				
			||||||
 | 
					        if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
 | 
				
			||||||
 | 
					            return { type: "Widevine", base64: base64EncodedBody, bodyType: "string" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
 | 
				
			||||||
 | 
					            return { type: "PlayReady", base64: base64EncodedBody, bodyType: "string" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if (typeof body === "string" && isJson(body)) {
 | 
				
			||||||
 | 
					        const jsonBody = JSON.parse(body);
 | 
				
			||||||
 | 
					        if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) {
 | 
				
			||||||
 | 
					            return { type: "Widevine", base64: null, bodyType: "json" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
 | 
				
			||||||
 | 
					            return { type: "PlayReady", base64: null, bodyType: "json" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { type: null, base64: null, bodyType: null };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleLicenseMode({
 | 
				
			||||||
 | 
					    drmInfo,
 | 
				
			||||||
 | 
					    body,
 | 
				
			||||||
 | 
					    setBody, // function to set the new body (for fetch: (b) => config.body = b, for XHR: (b) => originalSend.call(this, b))
 | 
				
			||||||
 | 
					    urlOrResource,
 | 
				
			||||||
 | 
					    getWidevinePssh,
 | 
				
			||||||
 | 
					    getPlayreadyPssh,
 | 
				
			||||||
 | 
					    widevineDeviceInfo,
 | 
				
			||||||
 | 
					    playreadyDeviceInfo,
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    foundChallengeInBody = true;
 | 
				
			||||||
 | 
					    window.postMessage({ type: "__LICENSE_URL__", data: urlOrResource }, "*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create remoteCDM if needed
 | 
				
			||||||
 | 
					    if (!remoteCDM) {
 | 
				
			||||||
 | 
					        if (drmInfo.type === "Widevine") {
 | 
				
			||||||
 | 
					            remoteCDM = createAndOpenRemoteCDM("Widevine", widevineDeviceInfo, getWidevinePssh());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (drmInfo.type === "PlayReady") {
 | 
				
			||||||
 | 
					            remoteCDM = createAndOpenRemoteCDM(
 | 
				
			||||||
 | 
					                "PlayReady",
 | 
				
			||||||
 | 
					                playreadyDeviceInfo,
 | 
				
			||||||
 | 
					                getPlayreadyPssh()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (remoteCDM && remoteCDM.challenge === null) {
 | 
				
			||||||
 | 
					        remoteCDM.getChallenge(getWidevinePssh());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Inject the new challenge into the request body
 | 
				
			||||||
 | 
					    if (drmInfo.bodyType === "json") {
 | 
				
			||||||
 | 
					        const jsonBody = JSON.parse(body);
 | 
				
			||||||
 | 
					        const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
 | 
				
			||||||
 | 
					        setBody(JSON.stringify(injectedBody));
 | 
				
			||||||
 | 
					    } else if (drmInfo.bodyType === "buffer") {
 | 
				
			||||||
 | 
					        setBody(base64ToUint8Array(remoteCDM.challenge));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        setBody(atob(remoteCDM.challenge));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleDRMInterception(drmInfo, body, url, setBodyCallback, continueRequestCallback) {
 | 
				
			||||||
 | 
					    // EME mode: block the request if a DRM challenge is detected
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        drmInfo.type &&
 | 
				
			||||||
 | 
					        (!remoteCDM || remoteCDM.challenge === null || drmInfo.base64 !== remoteCDM.challenge) &&
 | 
				
			||||||
 | 
					        interceptType === "EME"
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        foundChallengeInBody = true;
 | 
				
			||||||
 | 
					        window.postMessage({ type: "__LICENSE_URL__", data: url }, "*");
 | 
				
			||||||
 | 
					        // Block the request
 | 
				
			||||||
 | 
					        return { shouldBlock: true };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // LICENSE mode: replace the challenge in the request
 | 
				
			||||||
 | 
					    if (drmInfo.type && interceptType === "LICENSE" && !foundChallengeInBody) {
 | 
				
			||||||
 | 
					        handleLicenseMode({
 | 
				
			||||||
 | 
					            drmInfo,
 | 
				
			||||||
 | 
					            body,
 | 
				
			||||||
 | 
					            setBody: setBodyCallback,
 | 
				
			||||||
 | 
					            urlOrResource: url,
 | 
				
			||||||
 | 
					            getWidevinePssh: () => foundWidevinePssh,
 | 
				
			||||||
 | 
					            getPlayreadyPssh: () => foundPlayreadyPssh,
 | 
				
			||||||
 | 
					            widevineDeviceInfo,
 | 
				
			||||||
 | 
					            playreadyDeviceInfo,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return { shouldIntercept: true, result: continueRequestCallback() };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { shouldContinue: true };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fetch POST interceptor
 | 
				
			||||||
 | 
					(function () {
 | 
				
			||||||
 | 
					    const originalFetch = window.fetch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.fetch = async function (resource, config = {}) {
 | 
				
			||||||
 | 
					        const method = (config.method || "GET").toUpperCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (method === "POST" && config.body) {
 | 
				
			||||||
 | 
					            logWithPrefix("[FETCH] Intercepting POST request to:", resource);
 | 
				
			||||||
 | 
					            const drmInfo = detectDRMChallenge(config.body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const result = handleDRMInterception(
 | 
				
			||||||
 | 
					                drmInfo,
 | 
				
			||||||
 | 
					                config.body,
 | 
				
			||||||
 | 
					                resource,
 | 
				
			||||||
 | 
					                (b) => {
 | 
				
			||||||
 | 
					                    config.body = b;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                () => originalFetch(resource, config)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (result.shouldBlock) return;
 | 
				
			||||||
 | 
					            if (result.shouldIntercept) return result.result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return originalFetch(resource, config);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// XHR POST interceptor
 | 
				
			||||||
 | 
					(function () {
 | 
				
			||||||
 | 
					    const originalOpen = XMLHttpRequest.prototype.open;
 | 
				
			||||||
 | 
					    const originalSend = XMLHttpRequest.prototype.send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
 | 
				
			||||||
 | 
					        this._method = method;
 | 
				
			||||||
 | 
					        this._url = url;
 | 
				
			||||||
 | 
					        return originalOpen.apply(this, arguments);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    XMLHttpRequest.prototype.send = function (body) {
 | 
				
			||||||
 | 
					        if (this._method && this._method.toUpperCase() === "POST" && body) {
 | 
				
			||||||
 | 
					            logWithPrefix("[XHR] Intercepting POST request to:", this._url);
 | 
				
			||||||
 | 
					            const drmInfo = detectDRMChallenge(body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const result = handleDRMInterception(
 | 
				
			||||||
 | 
					                drmInfo,
 | 
				
			||||||
 | 
					                body,
 | 
				
			||||||
 | 
					                this._url,
 | 
				
			||||||
 | 
					                (b) => originalSend.call(this, b),
 | 
				
			||||||
 | 
					                () => {} // XHR doesn't need continuation callback
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (result.shouldBlock) return;
 | 
				
			||||||
 | 
					            if (result.shouldIntercept) return result.result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return originalSend.apply(this, arguments);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/manifest.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "manifest_version": 3,
 | 
				
			||||||
 | 
					    "name": "CDRM Extension",
 | 
				
			||||||
 | 
					    "version": "3.0",
 | 
				
			||||||
 | 
					    "description": "Decrypt DRM protected content",
 | 
				
			||||||
 | 
					    "permissions": ["storage", "activeTab", "contextMenus"],
 | 
				
			||||||
 | 
					    "host_permissions": ["<all_urls>"],
 | 
				
			||||||
 | 
					    "background": {
 | 
				
			||||||
 | 
					        "service_worker": "background.js"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "content_scripts": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "matches": ["<all_urls>"],
 | 
				
			||||||
 | 
					            "js": ["content.js"],
 | 
				
			||||||
 | 
					            "run_at": "document_start",
 | 
				
			||||||
 | 
					            "all_frames": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "web_accessible_resources": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "resources": ["inject.js"],
 | 
				
			||||||
 | 
					            "matches": ["<all_urls>"]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "action": {
 | 
				
			||||||
 | 
					        "default_icon": {
 | 
				
			||||||
 | 
					            "16": "icons/icon16.png",
 | 
				
			||||||
 | 
					            "32": "icons/icon32.png",
 | 
				
			||||||
 | 
					            "128": "icons/icon128.png"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "icons": {
 | 
				
			||||||
 | 
					        "16": "icons/icon16.png",
 | 
				
			||||||
 | 
					        "32": "icons/icon32.png",
 | 
				
			||||||
 | 
					        "128": "icons/icon128.png"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								syncVersion.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								syncVersion.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					import fs from "fs/promises";
 | 
				
			||||||
 | 
					import path from "path";
 | 
				
			||||||
 | 
					import { fileURLToPath } from "url";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const __filename = fileURLToPath(import.meta.url);
 | 
				
			||||||
 | 
					const __dirname = path.dirname(__filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateVersionWithRegex = async (filePath, newVersion) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const content = await fs.readFile(filePath, "utf-8");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Regex to match "version": "any.version.number"
 | 
				
			||||||
 | 
					        const versionRegex = /("version"\s*:\s*")([^"]+)(")/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!versionRegex.test(content)) {
 | 
				
			||||||
 | 
					            console.warn(`⚠️ No version field found in ${filePath}`);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const updatedContent = content.replace(versionRegex, `$1${newVersion}$3`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (content !== updatedContent) {
 | 
				
			||||||
 | 
					            await fs.writeFile(filePath, updatedContent);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					        console.error(`❌ Failed to update ${filePath}: ${err.message}`);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const syncVersion = async () => {
 | 
				
			||||||
 | 
					    const rootPkgPath = path.join(__dirname, "package.json");
 | 
				
			||||||
 | 
					    const frontendPkgPath = path.join(__dirname, "frontend", "package.json");
 | 
				
			||||||
 | 
					    const manifestPath = path.join(__dirname, "src", "manifest.json");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Read root package.json version
 | 
				
			||||||
 | 
					    const rootPkgRaw = await fs.readFile(rootPkgPath, "utf-8");
 | 
				
			||||||
 | 
					    const rootPkg = JSON.parse(rootPkgRaw);
 | 
				
			||||||
 | 
					    const version = rootPkg.version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!version) {
 | 
				
			||||||
 | 
					        console.warn("⚠️ No version field found in root package.json, skipping sync.");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Update frontend/package.json using regex
 | 
				
			||||||
 | 
					    const frontendUpdated = await updateVersionWithRegex(frontendPkgPath, version);
 | 
				
			||||||
 | 
					    if (frontendUpdated) {
 | 
				
			||||||
 | 
					        console.log(`🔄 Updated frontend/package.json version to ${version}`);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        console.log("ℹ️ frontend/package.json not found or no changes needed.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Update src/manifest.json using regex
 | 
				
			||||||
 | 
					    const manifestUpdated = await updateVersionWithRegex(manifestPath, version);
 | 
				
			||||||
 | 
					    if (manifestUpdated) {
 | 
				
			||||||
 | 
					        console.log(`🔄 Updated src/manifest.json version to ${version}`);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        console.log("ℹ️ src/manifest.json not found or no changes needed.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default syncVersion;
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user