sneedium/crx-extractor.js

112 lines
No EOL
3.5 KiB
JavaScript

/**
* CRX Extractor - Utility for handling Chrome extension files
* This module provides functions to extract and install Chrome extensions from CRX files
*/
const fs = require('fs');
const path = require('path');
const AdmZip = require('adm-zip');
/**
* Extracts a CRX file to the specified directory
*
* @param {string} crxFilePath - Path to the CRX file
* @param {string} outputDir - Directory to extract the CRX file to
* @returns {Promise<Object>} Result of the extraction
*/
async function extractCrxFile(crxFilePath, outputDir) {
return new Promise((resolve, reject) => {
try {
// Read the CRX file
const crxData = fs.readFileSync(crxFilePath);
// Check CRX header (Chrome extensions start with "Cr24")
if (crxData.slice(0, 4).toString() !== 'Cr24') {
return reject(new Error('Invalid CRX file format'));
}
// CRX format:
// [0-3] "Cr24" (Chrome extension signature)
// [4-7] Version number (uint32)
// [8-11] Public key length (uint32)
// [12-15] Signature length (uint32)
// Extract header information
const publicKeyLength = crxData.readUInt32LE(8);
const signatureLength = crxData.readUInt32LE(12);
// ZIP data starts after the header, public key, and signature
const zipStartOffset = 16 + publicKeyLength + signatureLength;
// Extract the ZIP portion of the CRX file
const zipData = crxData.slice(zipStartOffset);
// Write the ZIP data to a temporary file
const tempZipPath = path.join(require('os').tmpdir(), `${path.basename(crxFilePath, '.crx')}.zip`);
fs.writeFileSync(tempZipPath, zipData);
// Extract the ZIP file using AdmZip
const zip = new AdmZip(tempZipPath);
zip.extractAllTo(outputDir, true);
// Clean up the temporary ZIP file
fs.unlinkSync(tempZipPath);
// Check if manifest.json exists in the extracted files
const manifestPath = path.join(outputDir, 'manifest.json');
if (!fs.existsSync(manifestPath)) {
return reject(new Error('No manifest.json found in the extension'));
}
// Read the manifest.json file to get extension information
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
resolve({
success: true,
manifest,
extensionId: manifest.name ? manifest.name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase() : null
});
} catch (error) {
reject(error);
}
});
}
/**
* Installs a Chrome extension from a CRX file
*
* @param {string} crxFilePath - Path to the CRX file
* @param {string} extensionsDir - Directory where extensions are stored
* @returns {Promise<Object>} Result of the installation
*/
async function installCrxExtension(crxFilePath, extensionsDir) {
try {
const extensionId = path.basename(crxFilePath, '.crx');
const extractPath = path.join(extensionsDir, extensionId);
// Create directory if it doesn't exist
if (!fs.existsSync(extractPath)) {
fs.mkdirSync(extractPath, { recursive: true });
}
// Extract the CRX file
const extractResult = await extractCrxFile(crxFilePath, extractPath);
return {
success: true,
message: 'Extension installed successfully',
extensionId,
manifest: extractResult.manifest
};
} catch (error) {
return {
success: false,
message: `Failed to install extension: ${error.message}`
};
}
}
module.exports = {
extractCrxFile,
installCrxExtension
};