diff --git a/extension/lib/README.md b/extension/lib/README.md index c1bf4864fdf..de4f7bda569 100644 --- a/extension/lib/README.md +++ b/extension/lib/README.md @@ -10,6 +10,28 @@ Only add a copy of a library here if it's not published as a npm module. Both `forge.js` and `forge.mjs` need to be rebuilt when updating node-forge to a new version. +### Automated Method (Recommended) + +Use the automated update script: + +```bash +npm run update-forge -- v1.3.3 +``` + +This script will automatically: +1. Clone the forge repository at the specified version +2. Modify webpack.config.js to add source maps +3. Build forge.js with source maps +4. Convert forge.js to forge.mjs +5. Copy both files to `extension/lib/` +6. Clean up temporary files + +The version can be specified with or without the 'v' prefix (e.g., `v1.3.3` or `1.3.3`). + +### Manual Method + +If you prefer to update manually, follow these steps: + ### 1) Clone the forge repository at the desired version ```bash diff --git a/extension/lib/forge.js b/extension/lib/forge.js index c790c70b347..836eac3d09c 100644 --- a/extension/lib/forge.js +++ b/extension/lib/forge.js @@ -10749,6 +10749,7 @@ var pfxValidator = { capture: 'macAlgorithm' }, { name: 'PFX.macData.mac.digestAlgorithm.parameters', + optional: true, tagClass: asn1.Class.UNIVERSAL, captureAsn1: 'macAlgorithmParameters' }] diff --git a/extension/lib/forge.mjs b/extension/lib/forge.mjs index 432ae82f760..0347fcf72ee 100644 --- a/extension/lib/forge.mjs +++ b/extension/lib/forge.mjs @@ -10743,6 +10743,7 @@ var pfxValidator = { capture: 'macAlgorithm' }, { name: 'PFX.macData.mac.digestAlgorithm.parameters', + optional: true, tagClass: asn1.Class.UNIVERSAL, captureAsn1: 'macAlgorithmParameters' }] diff --git a/package.json b/package.json index 07920b58d1c..e63e1d328fe 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "run_firefox": "npm run build-incremental && npx web-ext run --source-dir ./build/firefox-consumer/ --firefox-profile ~/.mozilla/firefox/flowcrypt-dev --keep-profile-changes", "run_firefox_windows": "npm run build-incremental && npx web-ext run --source-dir ./build/firefox-consumer/ --firefox-profile %userprofile%/AppData/Local/Mozilla/Firefox/Profiles/flowcrypt-dev --keep-profile-changes", "find_unused": "ts-prune -p tsconfig.json | grep -v extension/types | grep -v extension/js/common/core", + "update-forge": "node ./scripts/update-forge.js", "release-step-1": "npm run build && ./tooling/release-step-1-pack-and-sign", "release-step-2": "./tooling/release-step-2-upload", "prepare": "husky" diff --git a/scripts/update-forge.js b/scripts/update-forge.js new file mode 100644 index 00000000000..e0efd743f9b --- /dev/null +++ b/scripts/update-forge.js @@ -0,0 +1,191 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +const fs = require('fs-extra'); +const path = require('path'); +const { execSync, execFileSync } = require('child_process'); + +const FORGE_REPO_URL = 'https://github.com/digitalbazaar/forge'; +const TEMP_DIR = path.join(__dirname, '../.tmp-forge-build'); +const EXTENSION_LIB_DIR = path.join(__dirname, '../extension/lib'); + +const updateForge = async (version) => { + if (!version) { + console.error('Error: Version parameter is required'); + console.error('Usage: npm run update-forge -- v1.3.3'); + process.exit(1); + } + + // Ensure version starts with 'v' + if (!version.startsWith('v')) { + version = 'v' + version; + } + + console.log(`Starting Forge update process for version ${version}...`); + + try { + // Clean up any existing temp directory + await fs.remove(TEMP_DIR); + + // Step 1: Clone the forge repository at the desired version + console.log('\n1. Cloning forge repository...'); + execFileSync('git', ['clone', '-b', version, FORGE_REPO_URL, TEMP_DIR], { stdio: 'inherit' }); + + // Step 2: Install dependencies + console.log('\n2. Installing dependencies...'); + execSync('npm install', { cwd: TEMP_DIR, stdio: 'inherit' }); + + // Step 3: Modify webpack.config.js to add source maps + console.log('\n3. Modifying webpack.config.js to add source maps...'); + const webpackConfigPath = path.join(TEMP_DIR, 'webpack.config.js'); + let webpackConfig = fs.readFileSync(webpackConfigPath, 'utf8'); + + // According to the readme, we need to add devtool to the plain unoptimized bundle + // This is the bundle object created around line 78 + // Look for "// plain unoptimized unminified bundle" comment and the bundle object after it + + const bundlePattern = /(\/\/ plain unoptimized unminified bundle\s*\n\s*const bundle = Object\.assign\(\{}, common, \{[\s\S]*?\n\s*}\);)/; + const match = webpackConfig.match(bundlePattern); + + if (match) { + const bundleBlock = match[1]; + + // Check if devtool already exists in this bundle configuration + if (!bundleBlock.includes('devtool:')) { + // Find the right place to insert devtool (after mode: 'development') + const modifiedBundle = bundleBlock.replace( + /(mode:\s*'development',)/, + "$1\n devtool: 'cheap-module-source-map'," + ); + + webpackConfig = webpackConfig.replace(bundleBlock, modifiedBundle); + await fs.writeFile(webpackConfigPath, webpackConfig, 'utf8'); + console.log(' Added source map configuration to webpack.config.js'); + } else { + console.log(' Source map configuration already present'); + } + } else { + console.warn(' Warning: Could not find the bundle configuration in webpack.config.js'); + console.warn(' The webpack.config.js structure may have changed'); + console.warn(' You may need to manually add devtool: \'cheap-module-source-map\' to the plain unoptimized bundle configuration'); + } + + // Step 4: Build forge.js + console.log('\n4. Building forge.js...'); + execSync('npm run build', { cwd: TEMP_DIR, stdio: 'inherit' }); + + // Step 5: Create conversion script for forge.mjs + console.log('\n5. Creating conversion script for forge.mjs...'); + const conversionScriptPath = path.join(TEMP_DIR, 'convert-forge-to-mjs.js'); + const conversionScript = `const fs = require('fs'); +const path = require('path'); + +const sourceFile = path.join(__dirname, 'dist', 'forge.js'); +const targetFile = path.join(__dirname, 'dist', 'forge.mjs'); + +console.log('Reading forge.js...'); +let content = fs.readFileSync(sourceFile, 'utf8'); + +// Split content into lines +const lines = content.split('\\n'); + +// Find where the webpack bundle starts +// It starts with the webpack bootstrap function +let startLine = lines.findIndex(line => line.includes('/******/ (function(modules) { // webpackBootstrap')); + +if (startLine === -1) { + throw new Error('Could not find webpack bundle start'); +} + +// Find the end - the webpack bundle ends with "/******/ });" +// This closes the modules object passed to the bootstrap function +let endLine = -1; +for (let i = lines.length - 1; i >= 0; i--) { + if (lines[i].trim() === '/******/ });') { + endLine = i + 1; // Include this line + break; + } +} + +if (endLine === -1) { + throw new Error('Could not find bundle end'); +} + +console.log(\`Extracting lines \${startLine} to \${endLine}\`); + +// Extract just the webpack bundle +const bundleLines = lines.slice(startLine, endLine); + +// Remove 'return ' from the first line if present (from UMD wrapper) +// The line looks like: return /******/ (function(modules) { // webpackBootstrap +bundleLines[0] = bundleLines[0].replace(/^\\s*return\\s+/, ''); + +// Prepend 'var forge = ' to capture the return value +bundleLines[0] = 'var forge = ' + bundleLines[0]; + +const bundle = bundleLines.join('\\n'); + +// Create ES module wrapper +const esModule = \`const globalThis = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +\${bundle} + +var lib = forge; + +export { lib as default }; +\`; + +console.log('Writing forge.mjs...'); +fs.writeFileSync(targetFile, esModule, 'utf8'); +console.log('forge.mjs generated successfully!'); +`; + + await fs.writeFile(conversionScriptPath, conversionScript, 'utf8'); + + // Step 6: Run the conversion script + console.log('\n6. Converting forge.js to forge.mjs...'); + execSync('node convert-forge-to-mjs.js', { cwd: TEMP_DIR, stdio: 'inherit' }); + + // Step 7: Copy files to extension/lib + console.log('\n7. Copying files to extension/lib...'); + + // Ensure the destination directory exists + await fs.ensureDir(EXTENSION_LIB_DIR); + + // Copy forge.js + const sourceForgeJs = path.join(TEMP_DIR, 'dist', 'forge.js'); + const destForgeJs = path.join(EXTENSION_LIB_DIR, 'forge.js'); + await fs.copy(sourceForgeJs, destForgeJs); + console.log(` Copied forge.js to ${destForgeJs}`); + + // Copy forge.mjs + const sourceForgeMjs = path.join(TEMP_DIR, 'dist', 'forge.mjs'); + const destForgeMjs = path.join(EXTENSION_LIB_DIR, 'forge.mjs'); + await fs.copy(sourceForgeMjs, destForgeMjs); + console.log(` Copied forge.mjs to ${destForgeMjs}`); + + // Step 8: Clean up + console.log('\n8. Cleaning up temporary files...'); + await fs.remove(TEMP_DIR); + + console.log(`\n✅ Successfully updated Forge to version ${version}!`); + console.log('Files have been copied to extension/lib/'); + + } catch (error) { + console.error('\n❌ Error during Forge update:', error.message); + + // Clean up on error + try { + await fs.remove(TEMP_DIR); + } catch { + // Ignore cleanup errors + } + + process.exit(1); + } +} + +// Get version from command line arguments +const version = process.argv[2]; +void updateForge(version);