Skip to content

Commit 37c7801

Browse files
authored
Enhance CRX viewer with theme toggle and file search
Refactor viewer.js to improve functionality and add theme toggle. Signed-off-by: canvrs <ngde@web.de>
1 parent e488cb0 commit 37c7801

File tree

1 file changed

+76
-57
lines changed

1 file changed

+76
-57
lines changed

viewer.js

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,84 @@
11
// viewer.js
2-
// Requires JSZip to handle CRX extraction
3-
// Include JSZip via CDN
4-
const script = document.createElement('script');
5-
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.11.0/jszip.min.js";
6-
document.head.appendChild(script);
2+
let zip, fileMap = {};
3+
const treeRoot = document.getElementById('treeRoot');
4+
const viewer = document.getElementById('viewer');
5+
const crxInput = document.getElementById('crxInput');
6+
const processBtn = document.getElementById('processBtn');
7+
const searchBox = document.getElementById('searchBox');
8+
const themeToggle = document.getElementById('themeToggle');
9+
const exportBtn = document.getElementById('exportBtn');
710

8-
script.onload = () => {
9-
const crxInput = document.getElementById('crxInput');
10-
const fileList = document.getElementById('fileList');
11-
const fileContent = document.getElementById('fileContent');
11+
let currentTheme = 'dark';
1212

13-
crxInput.addEventListener('change', async (e) => {
14-
const file = e.target.files[0];
15-
if (!file) return;
16-
17-
fileList.innerHTML = "Loading CRX...";
18-
19-
const buffer = await file.arrayBuffer();
20-
21-
// CRX Header parsing (CRXv2/v3)
22-
// Skip the header and extract zip portion
23-
let zipStart = 0;
24-
const magic = new Uint8Array(buffer.slice(0, 4));
25-
if (magic[0] !== 67 || magic[1] !== 82 || magic[2] !== 67 || magic[3] !== 0x21) {
26-
alert("Not a valid CRX file");
27-
return;
28-
}
13+
// THEME TOGGLE
14+
themeToggle.onclick = () => {
15+
if(currentTheme==='dark'){ document.body.classList.add('light'); currentTheme='light'; }
16+
else { document.body.classList.remove('light'); currentTheme='dark'; }
17+
};
2918

30-
// CRX v2 header size: 16 + public key length + signature length
31-
const dv = new DataView(buffer);
32-
const version = dv.getUint32(4, true);
33-
if (version === 2) {
34-
const publicKeyLength = dv.getUint32(8, true);
35-
const signatureLength = dv.getUint32(12, true);
36-
zipStart = 16 + publicKeyLength + signatureLength;
37-
} else if (version === 3) {
38-
// CRXv3 uses a header size field at 8
39-
const headerSize = dv.getUint32(8, true);
40-
zipStart = 12 + headerSize;
41-
} else {
42-
alert("Unsupported CRX version: " + version);
43-
return;
44-
}
19+
// PROCESS FILE
20+
processBtn.onclick = async () => {
21+
if (!crxInput.files[0]) return alert('Please select a CRX file!');
22+
const arrayBuffer = await crxInput.files[0].arrayBuffer();
23+
zip = await JSZip.loadAsync(arrayBuffer);
24+
fileMap = {}; // clear previous
25+
treeRoot.innerHTML = '';
26+
buildTree(zip, treeRoot, '');
27+
};
4528

46-
const zipData = buffer.slice(zipStart);
29+
// BUILD TREE
30+
async function buildTree(folder, parent, path) {
31+
for(const name in folder.files){
32+
const f = folder.files[name];
33+
const displayName = name.split('/').pop();
34+
if(f.dir){
35+
const li = document.createElement('li');
36+
li.textContent = displayName+'/';
37+
const ul = document.createElement('ul');
38+
li.appendChild(ul);
39+
parent.appendChild(li);
40+
buildTree(f, ul, name);
41+
} else {
42+
const li = document.createElement('li');
43+
li.textContent = displayName;
44+
li.onclick = async () => showFile(name, li);
45+
parent.appendChild(li);
46+
fileMap[name] = f;
47+
}
48+
}
49+
}
4750

48-
const zip = await JSZip.loadAsync(zipData);
49-
fileList.innerHTML = "";
51+
// SHOW FILE CONTENT
52+
async function showFile(name, li){
53+
const content = await zip.file(name).async('string');
54+
viewer.textContent = content;
55+
Prism.highlightElement(viewer);
56+
document.querySelectorAll('#fileTree li').forEach(i=>i.classList.remove('selected'));
57+
li.classList.add('selected');
58+
}
5059

51-
// Populate file list
52-
Object.keys(zip.files).forEach(filename => {
53-
const item = document.createElement('div');
54-
item.textContent = filename;
55-
item.className = 'file-item';
56-
item.addEventListener('click', async () => {
57-
const content = await zip.files[filename].async('string');
58-
fileContent.textContent = content;
59-
});
60-
fileList.appendChild(item);
61-
});
60+
// SEARCH FILES
61+
searchBox.oninput = ()=>{
62+
const q = searchBox.value.toLowerCase();
63+
document.querySelectorAll('#fileTree li').forEach(li=>{
64+
if(li.textContent.toLowerCase().includes(q)) li.style.display='block';
65+
else li.style.display='none';
66+
});
67+
}
6268

63-
fileContent.textContent = "Select a file to view its content...";
64-
});
65-
};
69+
// EXPORT SELECTED FILES
70+
exportBtn.onclick = async ()=>{
71+
if(!zip) return alert('No CRX loaded!');
72+
const exportZip = new JSZip();
73+
for(const path in fileMap){
74+
const content = await zip.file(path).async('arraybuffer');
75+
exportZip.file(path, content);
76+
}
77+
const blob = await exportZip.generateAsync({type:'blob'});
78+
const url = URL.createObjectURL(blob);
79+
const a = document.createElement('a');
80+
a.href = url;
81+
a.download = 'extracted_extension.zip';
82+
a.click();
83+
URL.revokeObjectURL(url);
84+
}

0 commit comments

Comments
 (0)