forked from tpd94/CDRM-Project
		
	Responsive Design
This commit is contained in:
		
							parent
							
								
									73723741b5
								
							
						
					
					
						commit
						8172c2122e
					
				
							
								
								
									
										89
									
								
								cdrm-frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										89
									
								
								cdrm-frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -11,7 +11,9 @@
 | 
			
		||||
        "@tailwindcss/vite": "^4.1.4",
 | 
			
		||||
        "react": "^19.0.0",
 | 
			
		||||
        "react-dom": "^19.0.0",
 | 
			
		||||
        "react-helmet": "^6.1.0",
 | 
			
		||||
        "react-router-dom": "^7.5.2",
 | 
			
		||||
        "shaka-player": "^4.14.9",
 | 
			
		||||
        "tailwindcss": "^4.1.4"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
@ -1872,6 +1874,12 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "ISC"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/eme-encryption-scheme-polyfill": {
 | 
			
		||||
      "version": "2.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-GzgrLuZPYGijd8oaKuBqYv3Tc2oruDZM3V2982KOuy/PA1N3zwMe+/oIXJYfZ3BH3PwW5nONdBBE+VY6jlwbrw==",
 | 
			
		||||
      "license": "Apache-2.0"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/enhanced-resolve": {
 | 
			
		||||
      "version": "5.18.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
 | 
			
		||||
@ -2358,7 +2366,6 @@
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/js-yaml": {
 | 
			
		||||
@ -2696,6 +2703,18 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/loose-envify": {
 | 
			
		||||
      "version": "1.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "js-tokens": "^3.0.0 || ^4.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "loose-envify": "cli.js"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/lru-cache": {
 | 
			
		||||
      "version": "5.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
 | 
			
		||||
@ -2758,6 +2777,15 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/object-assign": {
 | 
			
		||||
      "version": "4.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/optionator": {
 | 
			
		||||
      "version": "0.9.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
 | 
			
		||||
@ -2897,6 +2925,17 @@
 | 
			
		||||
        "node": ">= 0.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prop-types": {
 | 
			
		||||
      "version": "15.8.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
 | 
			
		||||
      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "loose-envify": "^1.4.0",
 | 
			
		||||
        "object-assign": "^4.1.1",
 | 
			
		||||
        "react-is": "^16.13.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/punycode": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 | 
			
		||||
@ -2928,6 +2967,42 @@
 | 
			
		||||
        "react": "^19.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-fast-compare": {
 | 
			
		||||
      "version": "3.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-helmet": {
 | 
			
		||||
      "version": "6.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "object-assign": "^4.1.1",
 | 
			
		||||
        "prop-types": "^15.7.2",
 | 
			
		||||
        "react-fast-compare": "^3.1.1",
 | 
			
		||||
        "react-side-effect": "^2.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=16.3.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-helmet/node_modules/react-side-effect": {
 | 
			
		||||
      "version": "2.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.3.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-is": {
 | 
			
		||||
      "version": "16.13.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 | 
			
		||||
      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-refresh": {
 | 
			
		||||
      "version": "0.17.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
 | 
			
		||||
@ -3048,6 +3123,18 @@
 | 
			
		||||
      "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/shaka-player": {
 | 
			
		||||
      "version": "4.14.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.14.9.tgz",
 | 
			
		||||
      "integrity": "sha512-0YZJ+UUHBz3meAzN/eOvjNoLH7eCG1yHP3BFH8ZnFbGf3K50DgLWNMoV6bm6pH4cndvXem+SdcQRXabItD4RBA==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "eme-encryption-scheme-polyfill": "^2.2.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/shebang-command": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,9 @@
 | 
			
		||||
    "@tailwindcss/vite": "^4.1.4",
 | 
			
		||||
    "react": "^19.0.0",
 | 
			
		||||
    "react-dom": "^19.0.0",
 | 
			
		||||
    "react-helmet": "^6.1.0",
 | 
			
		||||
    "react-router-dom": "^7.5.2",
 | 
			
		||||
    "shaka-player": "^4.14.9",
 | 
			
		||||
    "tailwindcss": "^4.1.4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import Home from "./components/Pages/HomePage";
 | 
			
		||||
import Cache from "./components/Pages/Cache";
 | 
			
		||||
import API from "./components/Pages/API";
 | 
			
		||||
import TestPlayer from "./components/Pages/TestPlayer";
 | 
			
		||||
import NavBar from "./components/NavBar";
 | 
			
		||||
import NavBarMain from "./components/NavBarMain";
 | 
			
		||||
import SideMenu from "./components/SideMenu"; // Add this import
 | 
			
		||||
@ -13,18 +16,21 @@ function App() {
 | 
			
		||||
      {/* The SideMenu should be visible when isMenuOpen is true */}
 | 
			
		||||
      <SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
 | 
			
		||||
 | 
			
		||||
      <div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shadow-lg shadow-blue-900/50">
 | 
			
		||||
      <div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 0">
 | 
			
		||||
        <NavBar />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col">
 | 
			
		||||
        <div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 shadow-md shadow-blue-900/35 sticky top-0 z-10">
 | 
			
		||||
      <div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col grow">
 | 
			
		||||
        <div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5  sticky top-0 z-10">
 | 
			
		||||
          <NavBarMain setIsMenuOpen={setIsMenuOpen} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="maincontentcontainer" className="w-full grow">
 | 
			
		||||
        <div id="maincontentcontainer" className="w-full grow overflow-y-auto">
 | 
			
		||||
          <Routes>
 | 
			
		||||
            <Route path="/" element={<Home />} />
 | 
			
		||||
            <Route path="/cache" element={<Cache />} />
 | 
			
		||||
            <Route path="/api" element={<API />} />
 | 
			
		||||
            <Route path="/testplayer" element={<TestPlayer />} />
 | 
			
		||||
          </Routes>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										173
									
								
								cdrm-frontend/src/components/Functions/ParseChallenge.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								cdrm-frontend/src/components/Functions/ParseChallenge.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
			
		||||
import "./protobuf.min.js";
 | 
			
		||||
import "./license_protocol.min.js";
 | 
			
		||||
 | 
			
		||||
const { SignedMessage, LicenseRequest } = protobuf.roots.default.license_protocol;
 | 
			
		||||
 | 
			
		||||
function uint8ArrayToBase64(uint8Array) {
 | 
			
		||||
  const binaryString = Array.from(uint8Array)
 | 
			
		||||
    .map(b => String.fromCharCode(b))
 | 
			
		||||
    .join('');
 | 
			
		||||
 | 
			
		||||
  return btoa(binaryString);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseFetch(fetchString) {
 | 
			
		||||
    // Remove `await` if it exists in the string
 | 
			
		||||
    fetchString = fetchString.replace(/^await\s+/, "");
 | 
			
		||||
 | 
			
		||||
    // Use a more lenient regex to capture the fetch pattern (including complex bodies)
 | 
			
		||||
    const fetchRegex = /fetch\(['"](.+?)['"],\s*(\{.+?\})\)/s; // Non-greedy match for JSON
 | 
			
		||||
    const lines = fetchString.split('\n').map(line => line.trim()).filter(Boolean);
 | 
			
		||||
    const result = {
 | 
			
		||||
        method: 'UNDEFINED',
 | 
			
		||||
        url: '',
 | 
			
		||||
        headers: {},
 | 
			
		||||
        body: null,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Try matching the regex
 | 
			
		||||
    const fetchMatch = fetchString.match(fetchRegex);
 | 
			
		||||
    if (!fetchMatch) {
 | 
			
		||||
        console.log(fetchString);
 | 
			
		||||
        throw new Error("Invalid 'Copy as fetch' string.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Extract URL from the match
 | 
			
		||||
    result.url = fetchMatch[1];
 | 
			
		||||
 | 
			
		||||
    // Parse the options JSON from the match (this will include headers, body, etc.)
 | 
			
		||||
    const optionsString = fetchMatch[2];
 | 
			
		||||
    const options = JSON.parse(optionsString);
 | 
			
		||||
 | 
			
		||||
    // Assign method, headers, and body if available
 | 
			
		||||
    if (options.method) result.method = options.method;
 | 
			
		||||
    if (options.headers) result.headers = options.headers;
 | 
			
		||||
    if (options.body) result.body = options.body;
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const WIDEVINE_SYSTEM_ID = new Uint8Array([0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed]);
 | 
			
		||||
const PLAYREADY_SYSTEM_ID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
 | 
			
		||||
const PSSH_MAGIC = new Uint8Array([0x70, 0x73, 0x73, 0x68]);
 | 
			
		||||
 | 
			
		||||
function intToUint8Array(num, endian) {
 | 
			
		||||
    const buffer = new ArrayBuffer(4);
 | 
			
		||||
    const view = new DataView(buffer);
 | 
			
		||||
    view.setUint32(0, num, endian);
 | 
			
		||||
    return new Uint8Array(buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function shortToUint8Array(num, endian) {
 | 
			
		||||
    const buffer = new ArrayBuffer(2);
 | 
			
		||||
    const view = new DataView(buffer);
 | 
			
		||||
    view.setUint16(0, num, endian);
 | 
			
		||||
    return new Uint8Array(buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function psshDataToPsshBoxB64(pssh_data, system_id) {
 | 
			
		||||
    const dataLength = pssh_data.length;
 | 
			
		||||
    const totalLength = dataLength + 32;
 | 
			
		||||
    const pssh = new Uint8Array([
 | 
			
		||||
        ...intToUint8Array(totalLength, false),
 | 
			
		||||
        ...PSSH_MAGIC,
 | 
			
		||||
        ...new Uint8Array(4),
 | 
			
		||||
        ...system_id,
 | 
			
		||||
        ...intToUint8Array(dataLength, false),
 | 
			
		||||
        ...pssh_data
 | 
			
		||||
    ]);
 | 
			
		||||
    return uint8ArrayToBase64(pssh);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function wrmHeaderToPlayReadyHeader(wrm_header){
 | 
			
		||||
    const playready_object = new Uint8Array([
 | 
			
		||||
        ...shortToUint8Array(1, true),
 | 
			
		||||
        ...shortToUint8Array(wrm_header.length, true),
 | 
			
		||||
        ...wrm_header
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    return new Uint8Array([
 | 
			
		||||
        ...intToUint8Array(playready_object.length + 2 + 4, true),
 | 
			
		||||
        ...shortToUint8Array(1, true),
 | 
			
		||||
        ...playready_object
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function encodeUtf16LE(str) {
 | 
			
		||||
  const buffer = new Uint8Array(str.length * 2);
 | 
			
		||||
  for (let i = 0; i < str.length; i++) {
 | 
			
		||||
    const code = str.charCodeAt(i);
 | 
			
		||||
    buffer[i * 2] = code & 0xff;
 | 
			
		||||
    buffer[i * 2 + 1] = code >> 8;
 | 
			
		||||
  }
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stringToUint8Array(string) {
 | 
			
		||||
    return Uint8Array.from(string.split("").map(x => x.charCodeAt()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function readTextFromClipboard() {
 | 
			
		||||
    try {
 | 
			
		||||
        // Request text from the clipboard
 | 
			
		||||
        const clipboardText = await navigator.clipboard.readText();
 | 
			
		||||
        
 | 
			
		||||
        const result = parseFetch(clipboardText);
 | 
			
		||||
 | 
			
		||||
        let pssh_data_string;
 | 
			
		||||
        let payload_string;
 | 
			
		||||
 | 
			
		||||
        if (result.body.startsWith("<")) {
 | 
			
		||||
            // If body starts with "<", process it as PlayReady content
 | 
			
		||||
            payload_string = result.body;
 | 
			
		||||
            const wrmHeaderMatch = payload_string.match(/.*(<WRMHEADER.*<\/WRMHEADER>).*/);
 | 
			
		||||
            const wrmHeader = wrmHeaderMatch ? wrmHeaderMatch[1] : null;
 | 
			
		||||
            const encodedWrmHeader = encodeUtf16LE(wrmHeader);
 | 
			
		||||
            const playreadyHeader = wrmHeaderToPlayReadyHeader(encodedWrmHeader);
 | 
			
		||||
            pssh_data_string = psshDataToPsshBoxB64(playreadyHeader, PLAYREADY_SYSTEM_ID);
 | 
			
		||||
        } else {
 | 
			
		||||
            // If body is in a different format, process as Widevine content
 | 
			
		||||
            const uint8Array = stringToUint8Array(result.body);
 | 
			
		||||
            let signed_message;
 | 
			
		||||
            let license_request;
 | 
			
		||||
            try {
 | 
			
		||||
                signed_message = SignedMessage.decode(uint8Array);
 | 
			
		||||
                license_request = LicenseRequest.decode(signed_message.msg);
 | 
			
		||||
            } catch (decodeError) {
 | 
			
		||||
                // If error occurs during decoding, return an empty pssh
 | 
			
		||||
                console.error('Decoding failed, returning empty pssh', decodeError);
 | 
			
		||||
                pssh_data_string = '';  // Empty pssh if decoding fails
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (license_request && license_request.contentId && license_request.contentId.widevinePsshData) {
 | 
			
		||||
                const pssh_data = license_request.contentId.widevinePsshData.psshData[0];
 | 
			
		||||
                pssh_data_string = psshDataToPsshBoxB64(pssh_data, WIDEVINE_SYSTEM_ID);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check if the body contains binary data (non-UTF-8 characters)
 | 
			
		||||
            if (isBinary(uint8Array)) {
 | 
			
		||||
                payload_string = uint8ArrayToBase64(uint8Array);
 | 
			
		||||
            } else {
 | 
			
		||||
                // If it's text, return it as is
 | 
			
		||||
                payload_string = result.body;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Output the result
 | 
			
		||||
        document.getElementById("licurl").value = result.url;
 | 
			
		||||
        document.getElementById("headers").value = JSON.stringify(result.headers);
 | 
			
		||||
        document.getElementById("pssh").value = pssh_data_string;
 | 
			
		||||
        document.getElementById("data").value = payload_string;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('Failed to read clipboard contents:', error);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper function to check if the data is binary
 | 
			
		||||
function isBinary(uint8Array) {
 | 
			
		||||
    // Check for non-text (non-ASCII) bytes (basic heuristic)
 | 
			
		||||
    return uint8Array.some(byte => byte > 127); // Non-ASCII byte indicates binary
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								cdrm-frontend/src/components/Functions/license_protocol.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								cdrm-frontend/src/components/Functions/license_protocol.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								cdrm-frontend/src/components/Functions/protobuf.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								cdrm-frontend/src/components/Functions/protobuf.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										134
									
								
								cdrm-frontend/src/components/Pages/API.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								cdrm-frontend/src/components/Pages/API.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
			
		||||
import React, { useState, useEffect } from 'react';
 | 
			
		||||
import { Helmet } from 'react-helmet'; // Import Helmet
 | 
			
		||||
 | 
			
		||||
const { protocol, hostname, port } = window.location;
 | 
			
		||||
 | 
			
		||||
let fullHost = `${protocol}//${hostname}`;
 | 
			
		||||
if (
 | 
			
		||||
  (protocol === 'http:' && port !== '80') ||
 | 
			
		||||
  (protocol === 'https:' && port !== '443' && port !== '')
 | 
			
		||||
) {
 | 
			
		||||
  fullHost += `:${port}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function API() {
 | 
			
		||||
  const [deviceInfo, setDeviceInfo] = useState({
 | 
			
		||||
    device_type: '',
 | 
			
		||||
    system_id: '',
 | 
			
		||||
    security_level: '',
 | 
			
		||||
    host: '',
 | 
			
		||||
    secret: '',
 | 
			
		||||
    device_name: ''
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [prDeviceInfo, setPrDeviceInfo] = useState({
 | 
			
		||||
    security_level: '',
 | 
			
		||||
    host: '',
 | 
			
		||||
    secret: '',
 | 
			
		||||
    device_name: ''
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // Fetch Widevine info
 | 
			
		||||
    fetch('/remotecdm/widevine/deviceinfo')
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .then(data => {
 | 
			
		||||
        setDeviceInfo({
 | 
			
		||||
          device_type: data.device_type,
 | 
			
		||||
          system_id: data.system_id,
 | 
			
		||||
          security_level: data.security_level,
 | 
			
		||||
          host: data.host,
 | 
			
		||||
          secret: data.secret,
 | 
			
		||||
          device_name: data.device_name
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .catch(error => console.error('Error fetching Widevine info:', error));
 | 
			
		||||
 | 
			
		||||
    // Fetch PlayReady info
 | 
			
		||||
    fetch('/remotecdm/playready/deviceinfo')
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .then(data => {
 | 
			
		||||
        setPrDeviceInfo({
 | 
			
		||||
          security_level: data.security_level,
 | 
			
		||||
          host: data.host,
 | 
			
		||||
          secret: data.secret,
 | 
			
		||||
          device_name: data.device_name
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .catch(error => console.error('Error fetching PlayReady info:', error));
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col w-full overflow-y-auto p-4 text-white">
 | 
			
		||||
      <Helmet>
 | 
			
		||||
        <title>API</title>
 | 
			
		||||
      </Helmet>
 | 
			
		||||
        <details open className='w-full list-none'>
 | 
			
		||||
            <summary className='text-2xl'>Sending a decryption request</summary>
 | 
			
		||||
            <div className='mt-5 p-5 rounded-lg border-2 border-indigo-500/50'>  
 | 
			
		||||
              <pre className='rounded-lg font-mono whitespace-pre-wrap text-white overflow-auto'>
 | 
			
		||||
              {`import requests
 | 
			
		||||
 | 
			
		||||
print(requests.post(
 | 
			
		||||
    url='${fullHost}/api/decrypt',
 | 
			
		||||
    headers={
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
    },
 | 
			
		||||
    json={
 | 
			
		||||
        'pssh': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==',
 | 
			
		||||
        'licurl': 'https://cwip-shaka-proxy.appspot.com/no_auth',
 | 
			
		||||
        'headers': str({
 | 
			
		||||
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0',
 | 
			
		||||
            'Accept': '*/*',
 | 
			
		||||
            'Accept-Language': 'en-US,en;q=0.5',
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
).json()['message'])`}
 | 
			
		||||
              </pre>
 | 
			
		||||
            </div>
 | 
			
		||||
        </details>
 | 
			
		||||
        <details open className='w-full list-none mt-5'>
 | 
			
		||||
            <summary className='text-2xl'>Sending a search request</summary>
 | 
			
		||||
            <div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg'>
 | 
			
		||||
            <pre className="rounded-lg font-mono whitespace-pre text-white overflow-x-auto max-w-full p-5">
 | 
			
		||||
{`import requests
 | 
			
		||||
 | 
			
		||||
print(requests.post(
 | 
			
		||||
    url='${fullHost}/api/cache/search',
 | 
			
		||||
    json={
 | 
			
		||||
        'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
 | 
			
		||||
    }
 | 
			
		||||
).json())`}
 | 
			
		||||
</pre>
 | 
			
		||||
            </div>
 | 
			
		||||
        </details>
 | 
			
		||||
        <details open className='w-full list-none mt-5'>
 | 
			
		||||
            <summary className='text-2xl'>PyWidevine RemoteCDM info</summary>
 | 
			
		||||
            <div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
 | 
			
		||||
                <p>
 | 
			
		||||
                    <strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
 | 
			
		||||
                    <strong>System ID:</strong> {deviceInfo.system_id}<br />
 | 
			
		||||
                    <strong>Security Level:</strong> {deviceInfo.security_level}<br />
 | 
			
		||||
                    <strong>Host:</strong> {fullHost}/remotecdm/widevine<br />
 | 
			
		||||
                    <strong>Secret:</strong> {deviceInfo.secret}<br />
 | 
			
		||||
                    <strong>Device Name:</strong> {deviceInfo.device_name}
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </details>
 | 
			
		||||
        <details open className='w-full list-none mt-5'>
 | 
			
		||||
            <summary className='text-2xl'>PyPlayready RemoteCDM info</summary>
 | 
			
		||||
            <div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
 | 
			
		||||
                <p>
 | 
			
		||||
                    <strong>Security Level:</strong> {prDeviceInfo.security_level}<br />
 | 
			
		||||
                    <strong>Host:</strong> {fullHost}/remotecdm/playready<br />
 | 
			
		||||
                    <strong>Secret:</strong> {prDeviceInfo.secret}<br />
 | 
			
		||||
                    <strong>Device Name:</strong> {prDeviceInfo.device_name}
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </details>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default API;
 | 
			
		||||
							
								
								
									
										107
									
								
								cdrm-frontend/src/components/Pages/Cache.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								cdrm-frontend/src/components/Pages/Cache.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
			
		||||
import { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import { Helmet } from 'react-helmet'; // Import Helmet
 | 
			
		||||
 | 
			
		||||
function Cache() {
 | 
			
		||||
    const [searchQuery, setSearchQuery] = useState('');
 | 
			
		||||
    const [cacheData, setCacheData] = useState([]);
 | 
			
		||||
    const [keyCount, setKeyCount] = useState(0); // New state to store the key count
 | 
			
		||||
    const debounceTimeout = useRef(null);
 | 
			
		||||
 | 
			
		||||
    // Fetch the key count when the component mounts
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const fetchKeyCount = async () => {
 | 
			
		||||
            try {
 | 
			
		||||
                const response = await fetch('/api/cache/keycount');
 | 
			
		||||
                const data = await response.json();
 | 
			
		||||
                setKeyCount(data.count); // Update key count
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error('Error fetching key count:', error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        fetchKeyCount();
 | 
			
		||||
    }, []); // Run only once when the component mounts
 | 
			
		||||
 | 
			
		||||
    const handleInputChange = (event) => {
 | 
			
		||||
        const query = event.target.value;
 | 
			
		||||
        setSearchQuery(query); // Update the search query
 | 
			
		||||
    
 | 
			
		||||
        // Clear the previous timeout
 | 
			
		||||
        if (debounceTimeout.current) {
 | 
			
		||||
            clearTimeout(debounceTimeout.current);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set a new timeout to send the API call after 1 second of no typing
 | 
			
		||||
        debounceTimeout.current = setTimeout(() => {
 | 
			
		||||
            if (query.trim() !== '') {
 | 
			
		||||
                sendApiCall(query); // Only call the API if the query is not empty
 | 
			
		||||
            } else {
 | 
			
		||||
                setCacheData([]); // Clear results if query is empty
 | 
			
		||||
            }
 | 
			
		||||
        }, 1000); // 1 second delay
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const sendApiCall = (text) => {
 | 
			
		||||
        fetch('/api/cache/search', {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: { 'Content-Type': 'application/json' },
 | 
			
		||||
            body: JSON.stringify({ input: text }),
 | 
			
		||||
        })
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((data) => setCacheData(data)) // Update cache data with the results
 | 
			
		||||
            .catch((error) => console.error('Error:', error));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="flex flex-col w-full h-full overflow-y-auto p-4">
 | 
			
		||||
            <Helmet>
 | 
			
		||||
                <title>Cache</title>
 | 
			
		||||
            </Helmet>
 | 
			
		||||
            <div className="flex flex-col lg:flex-row w-full lg:h-12 items-center">
 | 
			
		||||
                <input
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    value={searchQuery}
 | 
			
		||||
                    onChange={handleInputChange}
 | 
			
		||||
                    placeholder={`Search ${keyCount} keys...`} // Dynamic placeholder
 | 
			
		||||
                    className="lg:grow w-full border-2 border-emerald-500/25 rounded-xl h-10 self-center m-2 text-white p-1 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all duration-200 ease-in-out"
 | 
			
		||||
                />
 | 
			
		||||
                <a
 | 
			
		||||
                    href="/api/cache/download"
 | 
			
		||||
                    className="bg-emerald-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-10 truncate w-full text-center flex items-center justify-center m-2"
 | 
			
		||||
                >
 | 
			
		||||
                    Download Cache
 | 
			
		||||
                </a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="w-full grow p-4 border-2 border-emerald-500/50 rounded-2xl mt-5 overflow-y-auto">
 | 
			
		||||
                <table className="min-w-full text-white">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th className="p-2 border border-black">PSSH</th>
 | 
			
		||||
                            <th className="p-2 border border-black">KID</th>
 | 
			
		||||
                            <th className="p-2 border border-black">Key</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        {cacheData.length > 0 ? (
 | 
			
		||||
                            cacheData.map((item, index) => (
 | 
			
		||||
                                <tr key={index}>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.PSSH}</td>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.KID}</td>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.Key}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            ))
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td colSpan="3" className="p-2 border border-black text-center">
 | 
			
		||||
                                    No data found
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        )}
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Cache;
 | 
			
		||||
@ -1,10 +1,192 @@
 | 
			
		||||
function HomePage () {
 | 
			
		||||
import React, { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import { readTextFromClipboard } from '../Functions/ParseChallenge'
 | 
			
		||||
import { Helmet } from 'react-helmet'; // Import Helmet
 | 
			
		||||
 | 
			
		||||
function HomePage() {
 | 
			
		||||
  const [pssh, setPssh] = useState('');
 | 
			
		||||
  const [licurl, setLicurl] = useState('');
 | 
			
		||||
  const [proxy, setProxy] = useState('');
 | 
			
		||||
  const [headers, setHeaders] = useState('');
 | 
			
		||||
  const [cookies, setCookies] = useState('');
 | 
			
		||||
  const [data, setData] = useState('');
 | 
			
		||||
  const [message, setMessage] = useState('');
 | 
			
		||||
  const [isVisible, setIsVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="flex flex-col w-full h-full overflow-y-auto p-4">
 | 
			
		||||
  const bottomRef = useRef(null);
 | 
			
		||||
  const messageRef = useRef(null); // Reference to result container
 | 
			
		||||
 | 
			
		||||
  const handleReset = () => {
 | 
			
		||||
    if (isVisible) {
 | 
			
		||||
      setIsVisible(false);
 | 
			
		||||
    }
 | 
			
		||||
    setPssh('');
 | 
			
		||||
    setLicurl('');
 | 
			
		||||
    setProxy('');
 | 
			
		||||
    setHeaders('');
 | 
			
		||||
    setCookies('');
 | 
			
		||||
    setData('');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSubmitButton = (event) => {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
    fetch('/api/decrypt', {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      },
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        pssh: pssh,
 | 
			
		||||
        licurl: licurl,
 | 
			
		||||
        proxy: proxy,
 | 
			
		||||
        headers: headers,
 | 
			
		||||
        cookies: cookies,
 | 
			
		||||
        data: data
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .then(data => {
 | 
			
		||||
        const resultMessage = data['message'].replace(/\n/g, '<br />');
 | 
			
		||||
        setMessage(resultMessage);
 | 
			
		||||
        setIsVisible(true);
 | 
			
		||||
      })
 | 
			
		||||
      .catch((error) => {
 | 
			
		||||
        console.error('Error during decryption request:', error);
 | 
			
		||||
        setMessage('Error: Unable to process request.');
 | 
			
		||||
        setIsVisible(true);
 | 
			
		||||
      });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCopy = (event) => {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    if (messageRef.current) {
 | 
			
		||||
      const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
 | 
			
		||||
      navigator.clipboard.writeText(textToCopy).catch(err => {
 | 
			
		||||
        alert('Failed to copy!');
 | 
			
		||||
        console.error(err);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  const handleFetchPaste = () => {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    readTextFromClipboard().then(() => {
 | 
			
		||||
      setPssh(document.getElementById("pssh").value);
 | 
			
		||||
      setLicurl(document.getElementById("licurl").value);
 | 
			
		||||
      setHeaders(document.getElementById("headers").value);
 | 
			
		||||
      setData(document.getElementById("data").value);
 | 
			
		||||
    }).catch(err => {
 | 
			
		||||
      alert('Failed to paste from fetch!');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isVisible && bottomRef.current) {
 | 
			
		||||
      bottomRef.current.scrollIntoView({ behavior: 'smooth' });
 | 
			
		||||
    }
 | 
			
		||||
  }, [message, isVisible]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
 | 
			
		||||
        <Helmet>
 | 
			
		||||
          <title>CDRM-Project</title>
 | 
			
		||||
        </Helmet>
 | 
			
		||||
        <form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
 | 
			
		||||
          <label htmlFor="pssh" className="text-white w-8/10 self-center">PSSH: </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            id="pssh"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
            value={pssh}
 | 
			
		||||
            onChange={(e) => setPssh(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
          <label htmlFor="licurl" className="text-white w-8/10 self-center">License URL: </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            id="licurl"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
            value={licurl}
 | 
			
		||||
            onChange={(e) => setLicurl(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
          <label htmlFor="proxy" className="text-white w-8/10 self-center">Proxy: </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            id="proxy"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
            value={proxy}
 | 
			
		||||
            onChange={(e) => setProxy(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
          <label htmlFor="headers" className="text-white w-8/10 self-center">Headers: </label>
 | 
			
		||||
          <textarea
 | 
			
		||||
            id="headers"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
            value={headers}
 | 
			
		||||
            onChange={(e) => setHeaders(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
          <label htmlFor="cookies" className="text-white w-8/10 self-center">Cookies: </label>
 | 
			
		||||
          <textarea
 | 
			
		||||
            id="cookies"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
            value={cookies}
 | 
			
		||||
            onChange={(e) => setCookies(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
          <label htmlFor="data" className="text-white w-8/10 self-center">Data: </label>
 | 
			
		||||
          <textarea
 | 
			
		||||
            id="data"
 | 
			
		||||
            className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
            value={data}
 | 
			
		||||
            onChange={(e) => setData(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              className="bg-sky-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
 | 
			
		||||
              onClick={handleSubmitButton}
 | 
			
		||||
            >
 | 
			
		||||
              Submit
 | 
			
		||||
            </button>
 | 
			
		||||
            <button onClick={handleFetchPaste} className="bg-yellow-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0">
 | 
			
		||||
              Paste from fetch
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
              className="bg-red-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0"
 | 
			
		||||
              onClick={handleReset}
 | 
			
		||||
            >
 | 
			
		||||
              Reset
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {isVisible && (
 | 
			
		||||
        <div id="main_content" className="flex-col w-full h-full p-10 items-center justify-center self-center">
 | 
			
		||||
          <div className="flex flex-col w-full h-full overflow-y-auto items-center">
 | 
			
		||||
            <div className='w-8/10 grow p-4 text-white text-bold text-center text-xl md:text-3xl border-2 border-sky-500/25 rounded-xl bg-black/5'>
 | 
			
		||||
              <p className="w-full border-b-2 border-white/75 pb-2">Results:</p>
 | 
			
		||||
              <p
 | 
			
		||||
                className="w-full grow pt-10 break-words overflow-y-auto"
 | 
			
		||||
                ref={messageRef}
 | 
			
		||||
                dangerouslySetInnerHTML={{ __html: message }}
 | 
			
		||||
              />
 | 
			
		||||
              <div ref={bottomRef} />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
 | 
			
		||||
            <button
 | 
			
		||||
              className="bg-green-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
 | 
			
		||||
              onClick={handleCopy}
 | 
			
		||||
            >
 | 
			
		||||
              Copy Results
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    )
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default HomePage
 | 
			
		||||
export default HomePage;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										158
									
								
								cdrm-frontend/src/components/Pages/TestPlayer.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								cdrm-frontend/src/components/Pages/TestPlayer.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
			
		||||
import React, { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import shaka from 'shaka-player';
 | 
			
		||||
import { Helmet } from 'react-helmet'; // Import Helmet
 | 
			
		||||
 | 
			
		||||
function TestPlayer() {
 | 
			
		||||
  const [mpdUrl, setMpdUrl] = useState(''); // State to hold the MPD URL
 | 
			
		||||
  const [kids, setKids] = useState(''); // State to hold KIDs (separated by line breaks)
 | 
			
		||||
  const [keys, setKeys] = useState(''); // State to hold Keys (separated by line breaks)
 | 
			
		||||
  const [headers, setHeaders] = useState(''); // State to hold request headers
 | 
			
		||||
 | 
			
		||||
  const videoRef = useRef(null); // Ref for the video element
 | 
			
		||||
  const playerRef = useRef(null); // Ref for Shaka Player instance
 | 
			
		||||
 | 
			
		||||
  // Function to update the MPD URL state
 | 
			
		||||
  const handleInputChange = (event) => {
 | 
			
		||||
    setMpdUrl(event.target.value);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Function to update KIDs and Keys
 | 
			
		||||
  const handleKidsChange = (event) => {
 | 
			
		||||
    setKids(event.target.value);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleKeysChange = (event) => {
 | 
			
		||||
    setKeys(event.target.value);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleHeadersChange = (event) => {
 | 
			
		||||
    setHeaders(event.target.value);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Function to initialize Shaka Player
 | 
			
		||||
  const initializePlayer = () => {
 | 
			
		||||
    if (videoRef.current) {
 | 
			
		||||
      // Initialize Shaka Player only if it's not already initialized
 | 
			
		||||
      if (!playerRef.current) {
 | 
			
		||||
        const player = new shaka.Player(videoRef.current);
 | 
			
		||||
        playerRef.current = player;
 | 
			
		||||
 | 
			
		||||
        // Add error listener
 | 
			
		||||
        player.addEventListener('error', (event) => {
 | 
			
		||||
          console.error('Error code', event.detail.code, 'object', event.detail);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Function to handle submit and configure player with DRM keys and headers
 | 
			
		||||
  const handleSubmit = () => {
 | 
			
		||||
    if (mpdUrl && kids && keys) {
 | 
			
		||||
      // Split the KIDs and Keys by new lines
 | 
			
		||||
      const kidsArray = kids.split("\n").map((k) => k.trim());
 | 
			
		||||
      const keysArray = keys.split("\n").map((k) => k.trim());
 | 
			
		||||
 | 
			
		||||
      if (kidsArray.length !== keysArray.length) {
 | 
			
		||||
        console.error("The number of KIDs and Keys must be the same.");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Initialize Shaka Player only when the submit button is pressed
 | 
			
		||||
      const player = new shaka.Player(videoRef.current);
 | 
			
		||||
 | 
			
		||||
      // Widevine DRM configuration with the provided KIDs and Keys
 | 
			
		||||
      const config = {
 | 
			
		||||
        drm: {
 | 
			
		||||
          clearKeys: {},
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      // Map KIDs to Keys
 | 
			
		||||
      kidsArray.forEach((kid, index) => {
 | 
			
		||||
        config.drm.clearKeys[kid] = keysArray[index];
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      console.log("Configuring player with the following DRM config and headers:", config);
 | 
			
		||||
 | 
			
		||||
      // Configure the player with ClearKey DRM and custom headers
 | 
			
		||||
      player.configure(config);
 | 
			
		||||
 | 
			
		||||
      // Load the video stream with MPD URL
 | 
			
		||||
      player.load(mpdUrl).then(() => {
 | 
			
		||||
        console.log('Video loaded');
 | 
			
		||||
      }).catch((error) => {
 | 
			
		||||
        console.error('Error loading the video', error);
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      console.error('MPD URL, KIDs, and Keys are required.');
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Load the video stream whenever the MPD URL changes
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    initializePlayer(); // Initialize the player if it's not initialized already
 | 
			
		||||
  }, []); // This effect runs only once on mount
 | 
			
		||||
 | 
			
		||||
  // Helper function to parse headers from the textarea input
 | 
			
		||||
  const parseHeaders = (headersText) => {
 | 
			
		||||
    const headersArr = headersText.split('\n');
 | 
			
		||||
    const headersObj = {};
 | 
			
		||||
    headersArr.forEach((line) => {
 | 
			
		||||
      const [key, value] = line.split(':');
 | 
			
		||||
      if (key && value) {
 | 
			
		||||
        headersObj[key.trim()] = value.trim();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return headersObj;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col items-center w-full p-4">
 | 
			
		||||
      <Helmet>
 | 
			
		||||
        <title>Test Player</title>
 | 
			
		||||
      </Helmet>
 | 
			
		||||
      <div className="w-full flex flex-col">
 | 
			
		||||
        <video
 | 
			
		||||
          ref={videoRef}
 | 
			
		||||
          width="100%"
 | 
			
		||||
          height="auto"
 | 
			
		||||
          controls
 | 
			
		||||
          className="h-96"
 | 
			
		||||
        />
 | 
			
		||||
        <input
 | 
			
		||||
          type="text"
 | 
			
		||||
          value={mpdUrl}
 | 
			
		||||
          onChange={handleInputChange}
 | 
			
		||||
          placeholder="MPD URL"
 | 
			
		||||
          className="border-2 border-rose-700/50 mt-2 text-white p-1 rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
 | 
			
		||||
        />
 | 
			
		||||
        <textarea
 | 
			
		||||
          placeholder="KIDs (one per line)"
 | 
			
		||||
          value={kids}
 | 
			
		||||
          onChange={handleKidsChange}
 | 
			
		||||
          className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
 | 
			
		||||
        />
 | 
			
		||||
        <textarea
 | 
			
		||||
          placeholder="Keys (one per line)"
 | 
			
		||||
          value={keys}
 | 
			
		||||
          onChange={handleKeysChange}
 | 
			
		||||
          className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
 | 
			
		||||
        />
 | 
			
		||||
        <textarea
 | 
			
		||||
          placeholder="Headers (one per line)"
 | 
			
		||||
          value={headers}
 | 
			
		||||
          onChange={handleHeadersChange}
 | 
			
		||||
          className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
 | 
			
		||||
        />
 | 
			
		||||
        <button
 | 
			
		||||
          onClick={handleSubmit}
 | 
			
		||||
          className="mt-4 p-2 bg-blue-500 text-white rounded"
 | 
			
		||||
        >
 | 
			
		||||
          Submit
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default TestPlayer;
 | 
			
		||||
@ -1 +1,10 @@
 | 
			
		||||
@import "tailwindcss";
 | 
			
		||||
@import "tailwindcss";
 | 
			
		||||
 | 
			
		||||
details summary::-webkit-details-marker {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  details summary {
 | 
			
		||||
    list-style: none; 
 | 
			
		||||
    cursor: pointer; 
 | 
			
		||||
  }
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user