From 9c4a886799173c254a487e95d0e5d885eed53b03 Mon Sep 17 00:00:00 2001 From: oxmc <67136658+oxmc@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:22:49 -0800 Subject: [PATCH] Add extension support and work on auto update --- .github/workflows/build-and-release.yml | 52 +++++++++--------- build/PKGBUILD | 2 +- package.json | 2 +- src/app/main.js | 37 ++++++++++++- src/app/utils/auto-update.js | 13 +++-- src/app/utils/loadCRX.js | 73 +++++++++++++++++++++++-- 6 files changed, 139 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 0d2fea98..8d55f71f 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -45,6 +45,10 @@ jobs: - name: Check the appimage(s) run: ./appimagelint-x86_64.AppImage dist/*.AppImage + + - name: Generate checksum + run: | + sha256sum dist/*.AppImage > sha256sum.txt - name: Upload Linux Artifacts uses: actions/upload-artifact@v4 @@ -54,16 +58,7 @@ jobs: path: | dist/*.AppImage dist/latest*.yml - - - name: Generate checksum - run: | - sha256sum dist/*.AppImage > sha256sum.txt - - - name: Upload checksums - uses: actions/upload-artifact@v4 - with: - name: checksums - path: sha256sum.txt + sha256sum.txt build-windows: name: Build bsky-desktop (Windows) @@ -89,6 +84,10 @@ jobs: - name: Build (arm64) run: npm run build -- --arch arm64 + + - name: Generate checksum + run: | + sha256sum dist/*.exe > sha256sum.txt - name: Upload Windows Artifacts uses: actions/upload-artifact@v4 @@ -98,6 +97,7 @@ jobs: path: | dist/*.exe dist/latest*.yml + sha256sum.txt build-macos: name: Build bsky-desktop (macOS) @@ -123,6 +123,11 @@ jobs: - name: Build (arm64) run: npm run build -- --arch arm64 + + - name: Generate checksum + run: | + sha256sum dist/*.dmg > sha256sum.txt + # sha256sum dist/*.pkg >> sha256sum.txt - name: Upload macOS Artifacts uses: actions/upload-artifact@v4 @@ -132,6 +137,7 @@ jobs: path: | dist/*.dmg dist/latest*.yml + sha256sum.txt release: name: Create Release @@ -170,11 +176,10 @@ jobs: - name: Display structure of downloaded files run: ls -R dist - - - name: Merge latest .ymls - uses: mikefarah/yq@v4.44.6 - with: - cmd: yq eval-all 'select(fileIndex == 0) * select(fileIndex > 0)' dist/*/*.yml > merged.yml + + - name: Combine checksums + run: | + cat dist/linux/sha256sum.txt dist/windows/sha256sum.txt dist/macos/sha256sum.txt > sha256sums.txt - name: Upload Release id: create_release @@ -187,7 +192,7 @@ jobs: dist/linux/*.AppImage dist/windows/*.exe dist/macos/*.dmg - merged.yml + sha256sums.txt aur: name: Publish to AUR @@ -200,20 +205,17 @@ jobs: - name: Checkout git repo uses: actions/checkout@v3 - - name: Install jq - run: sudo apt-get update && sudo apt-get install jq -y - - - name: Download checksums + - name: Download linux artifacts uses: actions/download-artifact@v4 with: - name: checksums - path: . + name: linux-artifacts + path: dist/linux - name: List downloaded files - run: ls -R + run: ls -R dist - name: Show content of sha256sum.txt - run: cat sha256sum.txt + run: cat dist/linux/sha256sum.txt - name: Get app version id: version @@ -221,7 +223,7 @@ jobs: - name: Extract checksum from sha256sum.txt and change build version run: | - new_checksum=$(awk 'NR==1 { print $1 }' ./sha256sum.txt) + new_checksum=$(awk 'NR==1 { print $1 }' ./dist/linux/sha256sum.txt) sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/PKGBUILD sed -i "s/^pkgver=.*$/pkgver=${{ steps.version.outputs.version }}/" ./build/PKGBUILD diff --git a/build/PKGBUILD b/build/PKGBUILD index 615aa2da..fd389758 100644 --- a/build/PKGBUILD +++ b/build/PKGBUILD @@ -70,7 +70,7 @@ package() { [Desktop Entry] Name=Bluesky Desktop Comment=Bluesky Desktop Client -Exec=/usr/bin/bsky-desktop +Exec=/usr/bin/bsky-desktop %u Icon=bsky-desktop Terminal=false Type=Application diff --git a/package.json b/package.json index 2b3b25d1..59737f74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky-desktop", - "version": "1.0.8", + "version": "1.0.9", "description": "A desktop app of bsky.app", "main": "src/app/main.js", "scripts": { diff --git a/src/app/main.js b/src/app/main.js index 2dbad0dc..46579891 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -7,7 +7,7 @@ const openAboutWindow = require("./about-window/src/index").default; const badge = require('./badge'); const contextMenu = require('./context-menu'); const autoUpdater = require('./utils/auto-update'); -//const loadCRX = require('./utils/loadCRX'); +const loadCRX = require('./utils/loadCRX'); const log4js = require("log4js"); const path = require("path"); const fs = require("fs"); @@ -36,6 +36,7 @@ global.paths = { temp: path.join(os.tmpdir(), global.appInfo.name), }; global.paths.updateDir = path.join(global.paths.data, 'update'); +global.paths.extensions = path.join(global.paths.data, 'extensions'); // URLs: global.urls = { @@ -119,6 +120,12 @@ if (!fs.existsSync(global.paths.updateDir)) { fs.mkdirSync(global.paths.updateDir, { recursive: true }); }; +// Create extensions directory if it does not exist: +if (!fs.existsSync(global.paths.extensions)) { + logger.info("Creating Extensions Directory"); + fs.mkdirSync(global.paths.extensions, { recursive: true }); +}; + // improve performance on linux? if (process.platform !== "win32" && process.platform !== "darwin") { logger.log("Disabling Hardware Acceleration and Transparent Visuals"); @@ -240,6 +247,34 @@ function createWindow() { // Badge count: (use mainWindow as that shows the badge on the taskbar) new badge(mainWindow, badgeOptions); + // Load extensions (.crx files): + logger.log("Checking for extensions"); + const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx')); + if (extensions.length > 0) { + logger.log(`Unpacking ${extensions.length} extensions and loading them`); + extensions.forEach((extension) => { + loadCRX(path.join(global.paths.extensions, extension)); + }); + } else { + // Check for unpacked extensions: + const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory()); + + // Check if the directory contains a manifest.json file + unpackedExtensions.forEach((extension) => { + const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json'); + if (fs.existsSync(manifestPath)) { + logger.log(`Loading unpacked extension: ${extension}`); + session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => { + logger.log(`Extension loaded with ID: ${id}`); + }).catch((error) => { + logger.error(`Failed to load extension: ${error}`); + }); + } else { + logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`); + }; + }); + }; + logger.log("Main Window Created, Showing splashscreen"); splash.show(); //mainWindow.show(); diff --git a/src/app/utils/auto-update.js b/src/app/utils/auto-update.js index 87f00bbf..3fe56ed2 100644 --- a/src/app/utils/auto-update.js +++ b/src/app/utils/auto-update.js @@ -107,13 +107,14 @@ function checkForUpdates() { // System is ARM64 (mac-arm64) macArch = 'arm64'; } else { - // System is Intel (mac-x64) - macArch = 'intel'; + // System is x64 (mac-x64) + macArch = 'x64'; } } else { - // macOS 10 is mostly Intel, but some versions are ARM64 - macArch = 'intel'; + // macOS 10 is mostly x64, but some versions are ARM64 + macArch = 'x64'; } + logger.log('System architecture:', macArch); // Check for updates, and if there are updates, download and install them logger.log('Checking for updates (mac)...'); @@ -122,7 +123,7 @@ function checkForUpdates() { const command = `sudo installer -pkg ${pkgPath} -target /`; // Spawn a new shell - const shellProcess = spawn('sh', ['-c', command], { + /*const shellProcess = spawn('sh', ['-c', command], { stdio: 'inherit', // Pipe input/output to/from the shell }); @@ -136,7 +137,7 @@ function checkForUpdates() { } else { console.error(`Shell process exited with code ${code}.`); } - }); + });*/ } else { // macOS versions before 10 are not supported logger.error('macOS versions before 10 are not supported, not updating...'); diff --git a/src/app/utils/loadCRX.js b/src/app/utils/loadCRX.js index c817bf5d..b17ec2ef 100644 --- a/src/app/utils/loadCRX.js +++ b/src/app/utils/loadCRX.js @@ -3,30 +3,91 @@ const path = require('path'); const { session } = require('electron'); const AdmZip = require('adm-zip'); +/** + * Converts a CRX file buffer to a ZIP buffer. + * @param {Buffer} buf - The CRX file buffer. + * @returns {Buffer} - The ZIP buffer extracted from the CRX file. + */ +function crxToZip(buf) { + function calcLength(a, b, c, d) { + let length = 0; + length += a << 0; + length += b << 8; + length += c << 16; + length += (d << 24) >>> 0; + return length; + } + + // Check if the file is already a ZIP file + if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) { + return buf; + } + + // Validate CRX magic number + if (buf[0] !== 67 || buf[1] !== 114 || buf[2] !== 50 || buf[3] !== 52) { + throw new Error('Invalid CRX file: Missing Cr24 magic number'); + } + + const version = buf[4]; + const isV2 = version === 2; + const isV3 = version === 3; + + if ((!isV2 && !isV3) || buf[5] || buf[6] || buf[7]) { + throw new Error('Unsupported CRX format version.'); + } + + if (isV2) { + const publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]); + const signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]); + const zipStartOffset = 16 + publicKeyLength + signatureLength; + return buf.slice(zipStartOffset); + } + + const headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]); + const zipStartOffset = 12 + headerSize; + return buf.slice(zipStartOffset); +} + /** * Unpacks a .crx file and loads it as an Electron extension. * @param {string} crxPath - Path to the .crx file. * @returns {Promise} - Resolves with the extension ID after loading. */ async function loadCRX(crxPath) { - const outputDir = path.join(__dirname, 'extensions', path.basename(crxPath, '.crx')); + const outputDir = path.join(global.paths.extensions, path.basename(crxPath, '.crx')); // Ensure the output directory exists if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); - // Extract the .crx file + // Read the CRX file const crxData = fs.readFileSync(crxPath); - const crxHeaderSize = crxData.readUInt32LE(8); // Extract header size from CRX - const zipData = crxData.slice(crxHeaderSize); - // Save the ZIP content + // Convert CRX to ZIP + const zipData = crxToZip(crxData); + + // Extract ZIP using AdmZip const zip = new AdmZip(zipData); - zip.extractAllTo(outputDir, true); + zip.getEntries().forEach((entry) => { + const fullPath = path.join(outputDir, entry.entryName); + + if (entry.isDirectory) { + fs.mkdirSync(fullPath, { recursive: true }); + } else { + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, entry.getData()); + } + }); } // Load the unpacked extension into Electron try { + // Check for manifest.json + const manifestPath = path.join(outputDir, 'manifest.json'); + if (!fs.existsSync(manifestPath)) { + throw new Error('Extension is missing manifest.json'); + }; + // Load the extension const { id } = await session.defaultSession.loadExtension(outputDir); console.log(`Extension loaded with ID: ${id}`); return id;