From 672b846364dadfab0e4c18d78b3db756192b4d2f Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 10 Dec 2019 15:54:17 +0000 Subject: [PATCH 1/4] Pack the webapp into an asar --- package.json | 8 ++++---- scripts/check-webapp.js | 4 ++-- scripts/fetch-package.js | 15 +++++++++++--- src/electron-main.js | 4 ++-- yarn.lock | 42 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e52f055..9880089 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "start": "yarn run check && yarn install:electron && electron .", "lint": "eslint src/", "build": "yarn run check && electron-builder", - "clean": "rimraf webapp dist packages deploys" + "clean": "rimraf webapp.asar dist packages deploys" }, "dependencies": { "auto-launch": "^5.0.1", @@ -28,6 +28,7 @@ "png-to-ico": "^1.0.2" }, "devDependencies": { + "asar": "^2.0.1", "electron-builder": "^21.2.0", "electron-builder-squirrel-windows": "^21.2.0", "electron-devtools-installer": "^2.2.4", @@ -53,7 +54,7 @@ "from": "res/img", "to": "img" }, - "webapp/**/*" + "webapp.asar" ], "linux": { "target": "deb", @@ -82,7 +83,6 @@ }, "directories": { "output": "dist" - }, - "afterSign": "scripts/electron_afterSign.js" + } } } diff --git a/scripts/check-webapp.js b/scripts/check-webapp.js index fbefd50..8cbabb9 100755 --- a/scripts/check-webapp.js +++ b/scripts/check-webapp.js @@ -4,10 +4,10 @@ const fs = require('fs').promises; async function main() { try { - const webappDir = await fs.opendir('webapp'); + const webappDir = await fs.stat('webapp.asar'); return 0; } catch (e) { - console.log("No 'webapp' directory found. Run 'yarn run fetch' or symlink manually"); + console.log("No 'webapp.asar' found. Run 'yarn run fetch'"); return 1; } } diff --git a/scripts/fetch-package.js b/scripts/fetch-package.js index 8403061..04efce1 100755 --- a/scripts/fetch-package.js +++ b/scripts/fetch-package.js @@ -7,11 +7,13 @@ const fsPromises = require('fs').promises; const { https } = require('follow-redirects'); const child_process = require('child_process'); const tar = require('tar'); +const asar = require('asar'); const riotDesktopPackageJson = require('../package.json'); const PUB_KEY_URL = "https://packages.riot.im/riot-release-key.asc"; const PACKAGE_URL_PREFIX = "https://github.com/vector-im/riot-web/releases/download/"; +const ASAR_PATH = 'webapp.asar'; async function downloadToFile(url, filename) { console.log("Downloading " + url + "..."); @@ -177,9 +179,16 @@ async function main() { }); } - console.log("Symlink " + expectedDeployDir + " -> webapp"); - // Does this do a sensible thing on Windows? - await fsPromises.symlink(expectedDeployDir, 'webapp'); + try { + await fsPromises.stat(ASAR_PATH); + console.log(ASAR_PATH + " already present: removing"); + await fsPromises.unlink(ASAR_PATH); + } catch (e) { + } + + console.log("Pack " + expectedDeployDir + " -> " + ASAR_PATH); + await asar.createPackage(expectedDeployDir, ASAR_PATH); + console.log("Done!"); } main().then((ret) => process.exit(ret)); diff --git a/src/electron-main.js b/src/electron-main.js index 1220f41..ca51233 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -71,7 +71,7 @@ if (argv['profile-dir']) { let vectorConfig = {}; try { - vectorConfig = require('../webapp/config.json'); + vectorConfig = require('../webapp.asar/config.json'); } catch (e) { // it would be nice to check the error code here and bail if the config // is unparseable, but we get MODULE_NOT_FOUND in the case of a missing @@ -466,7 +466,7 @@ app.on('ready', () => { let baseDir; if (target[1] === 'webapp') { - baseDir = path.join(__dirname, "../webapp"); + baseDir = path.join(__dirname, "../webapp.asar"); } else { callback({error: -6}); // FILE_NOT_FOUND return; diff --git a/yarn.lock b/yarn.lock index 966da90..9b304c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -196,6 +196,19 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +asar@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/asar/-/asar-2.0.1.tgz#8518a1c62c238109c15a5f742213e83a09b9fd38" + integrity sha512-Vo9yTuUtyFahkVMFaI6uMuX6N7k5DWa6a/8+7ov0/f8Lq9TVR0tUjzSzxQSxT1Y+RJIZgnP7BVb6Uhi+9cjxqA== + dependencies: + chromium-pickle-js "^0.2.0" + commander "^2.20.0" + cuint "^0.2.2" + glob "^7.1.3" + minimatch "^3.0.4" + mkdirp "^0.5.1" + tmp-promise "^1.0.5" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -307,7 +320,7 @@ bluebird@3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== -bluebird@^3.5.5: +bluebird@^3.5.0, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -510,6 +523,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + compress-commons@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610" @@ -608,6 +626,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +cuint@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" + integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2271,7 +2294,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.2: +rimraf@^2.5.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -2591,6 +2614,21 @@ tinycolor2@^1.1.2: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= +tmp-promise@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-1.1.0.tgz#bb924d239029157b9bc1d506a6aa341f8b13e64c" + integrity sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw== + dependencies: + bluebird "^3.5.0" + tmp "0.1.0" + +tmp@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" From 8322676df782b16cd7850238d9b13416cd5e6a20 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 10 Dec 2019 16:04:16 +0000 Subject: [PATCH 2/4] Oops, don't remove the aftersign script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9880089..494abdc 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ }, "directories": { "output": "dist" - } + }, + "afterSign": "scripts/electron_afterSign.js" } } From 8d3529787a9b68091956a12fc1649407e9c32ffa Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 10 Dec 2019 17:40:17 +0000 Subject: [PATCH 3/4] Search for the webapp resources on startup and error if we can't find them. --- package.json | 2 +- src/electron-main.js | 140 ++++++++++++++++++++++++++++++------------- 2 files changed, 100 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 494abdc..0dee142 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "mkdirs": "mkdirp packages deploys", "fetch": "yarn run mkdirs && node scripts/fetch-package.js", "check": "node scripts/check-webapp.js", - "start": "yarn run check && yarn install:electron && electron .", + "start": "electron .", "lint": "eslint src/", "build": "yarn run check && electron-builder", "clean": "rimraf webapp.asar dist packages deploys" diff --git a/src/electron-main.js b/src/electron-main.js index ca51233..8ecfcaa 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -50,6 +50,15 @@ try { console.warn("seshat unavailable", e); } +// Things we need throughout the file but need to be created +// async to are initialised in setupGlobals() +let asarPath; +let resPath; +let vectorConfig; +let iconPath; +let trayConfig; +let launcher; + if (argv["help"]) { console.log("Options:"); console.log(" --profile-dir {path}: Path to where to store the profile."); @@ -69,34 +78,87 @@ if (argv['profile-dir']) { app.setPath('userData', `${app.getPath('userData')}-${argv['profile']}`); } -let vectorConfig = {}; -try { - vectorConfig = require('../webapp.asar/config.json'); -} catch (e) { - // it would be nice to check the error code here and bail if the config - // is unparseable, but we get MODULE_NOT_FOUND in the case of a missing - // file or invalid json, so node is just very unhelpful. - // Continue with the defaults (ie. an empty config) +async function tryAsarPaths(rawPaths) { + // Make everything relative to the current file + const paths = rawPaths.map(p => path.join(__dirname, p)); + + for (const p of paths) { + try { + await afs.stat(p); + return p + '/'; + } catch (e) { + } + } + console.log("Couldn't find webapp files in any of: "); + for (const p of paths) { + console.log("\t"+path.resolve(p)); + } + throw new Error("Failed to find webapp files"); } -try { - // Load local config and use it to override values from the one baked with the build - const localConfig = require(path.join(app.getPath('userData'), 'config.json')); +// Find the webapp resources and set up things that require them +async function setupGlobals() { + // find the webapp asar. + asarPath = await tryAsarPaths([ + // If run from the source checkout, this will be in the directory above + '../webapp.asar', + // but if run from a packaged application, electron-main.js will be in + // a different asar file so it will be two levels above + '../../webapp.asar', + // also try without the 'asar' suffix to allow symlinking in a directory + '../webapp', + ]); + // we assume the resources path is in the same place as the asar + resPath = path.join(path.dirname(asarPath), 'res'); - // If the local config has a homeserver defined, don't use the homeserver from the build - // config. This is to avoid a problem where Riot thinks there are multiple homeservers - // defined, and panics as a result. - const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config']; - if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) { - // Rip out all the homeserver options from the vector config - vectorConfig = Object.keys(vectorConfig) - .filter(k => !homeserverProps.includes(k)) - .reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {}); + try { + vectorConfig = require(asarPath + 'config.json'); + } catch (e) { + // it would be nice to check the error code here and bail if the config + // is unparseable, but we get MODULE_NOT_FOUND in the case of a missing + // file or invalid json, so node is just very unhelpful. + // Continue with the defaults (ie. an empty config) + vectorConfig = {}; } - vectorConfig = Object.assign(vectorConfig, localConfig); -} catch (e) { - // Could not load local config, this is expected in most cases. + try { + // Load local config and use it to override values from the one baked with the build + const localConfig = require(path.join(app.getPath('userData'), 'config.json')); + + // If the local config has a homeserver defined, don't use the homeserver from the build + // config. This is to avoid a problem where Riot thinks there are multiple homeservers + // defined, and panics as a result. + const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config']; + if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) { + // Rip out all the homeserver options from the vector config + vectorConfig = Object.keys(vectorConfig) + .filter(k => !homeserverProps.includes(k)) + .reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {}); + } + + vectorConfig = Object.assign(vectorConfig, localConfig); + } catch (e) { + // Could not load local config, this is expected in most cases. + } + + // The tray icon + // It's important to call `path.join` so we don't end up with the packaged asar in the final path. + const iconFile = `riot.${process.platform === 'win32' ? 'ico' : 'png'}`; + iconPath = path.join(resPath, "img", iconFile); + console.log("loading tray icon from " + iconPath); + trayConfig = { + icon_path: iconPath, + brand: vectorConfig.brand || 'Riot', + }; + + // launcher + launcher = new AutoLaunch({ + name: vectorConfig.brand || 'Riot', + isHidden: true, + mac: { + useLaunchAgent: true, + }, + }); } const eventStorePath = path.join(app.getPath('userData'), 'EventStore'); @@ -107,14 +169,6 @@ let eventIndex = null; let mainWindow = null; global.appQuitting = false; -// It's important to call `path.join` so we don't end up with the packaged asar in the final path. -const iconFile = `riot.${process.platform === 'win32' ? 'ico' : 'png'}`; -const iconPath = path.join(__dirname, "..", "..", "img", iconFile); -const trayConfig = { - icon_path: iconPath, - brand: vectorConfig.brand || 'Riot', -}; - // handle uncaught errors otherwise it displays // stack traces in popup dialogs, which is terrible (which // it will do any time the auto update poke fails, and there's @@ -399,14 +453,6 @@ if (!gotLock) { app.exit(); } -const launcher = new AutoLaunch({ - name: vectorConfig.brand || 'Riot', - isHidden: true, - mac: { - useLaunchAgent: true, - }, -}); - // Register the scheme the app is served from as 'standard' // which allows things like relative URLs and IndexedDB to // work. @@ -421,7 +467,19 @@ protocol.registerSchemesAsPrivileged([{ }, }]); -app.on('ready', () => { +app.on('ready', async () => { + try { + await setupGlobals(); + } catch (e) { + console.log("App setup failed: exiting", e); + process.exit(1); + // process.exit doesn't cause node to stop running code immediately, + // so return (we could let the exception propagate but then we end up + // with node printing all sorts of stuff about unhandled exceptions + // when we want the actual error to be as obvious as possible). + return; + } + if (argv['devtools']) { try { const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer'); @@ -466,7 +524,7 @@ app.on('ready', () => { let baseDir; if (target[1] === 'webapp') { - baseDir = path.join(__dirname, "../webapp.asar"); + baseDir = asarPath; } else { callback({error: -6}); // FILE_NOT_FOUND return; From 680caed505d7523df1d92f972b1448e2928fa111 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 10 Dec 2019 17:56:50 +0000 Subject: [PATCH 4/4] Remove unnecessary logging --- src/electron-main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/electron-main.js b/src/electron-main.js index 8ecfcaa..517777b 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -145,7 +145,6 @@ async function setupGlobals() { // It's important to call `path.join` so we don't end up with the packaged asar in the final path. const iconFile = `riot.${process.platform === 'win32' ? 'ico' : 'png'}`; iconPath = path.join(resPath, "img", iconFile); - console.log("loading tray icon from " + iconPath); trayConfig = { icon_path: iconPath, brand: vectorConfig.brand || 'Riot',