From ec5082860e5050cc5b810558b08197af7feef64a Mon Sep 17 00:00:00 2001 From: CanadaHonk <19228318+CanadaHonk@users.noreply.github.com> Date: Fri, 2 Dec 2022 22:23:46 +0000 Subject: [PATCH] Remove Polyfills (#101) * poly: remove completely * bootstrap: remove unused request require * asarUpdate: rewrite request -> https * moduleUpdater: rewrite request -> https * ui: fallback to channel instead of "custom" if no hash * index: set name to branch for obvious testing * config/backend: tweak source * moduleUpdater: fix rewrite * moduleUpdater: fix rewrite (x2) * moduleUpdater: follow redirects with rewrite * ci: start multi-branch rewrite * ci: upgrade node to 18.x * ci: rewrite version changing to use existing * ci: change release version getting to in file * ci: mess with yaml syntax * ci: revert to node 16.x, fix version wrapped in quotes * ci: fix invalid yaml * ci: yaml syntax fixing * ci: append to tag * ci: make pre-release unless nightly build * ci: add title nicening * asarUpdate: fix release suffix * asarUpdate: fix not following redirect * winFirst: don't remake shortcuts, tweaks * ui: add channel prefix if non-nightly * chore: prep for main branch --- .github/workflows/nightly.yml | 19 ++++---- faq.md | 2 +- poly/mime-types.js | 2 - poly/request.js | 84 ----------------------------------- scripts/injectPolyfills.sh | 3 -- scripts/run.sh | 1 - src/asarUpdate.js | 22 ++++----- src/bootstrap.js | 5 +-- src/config/index.js | 2 +- src/updater/moduleUpdater.js | 64 +++++++++++++------------- src/winFirst.js | 32 ++----------- 11 files changed, 61 insertions(+), 175 deletions(-) delete mode 100644 poly/mime-types.js delete mode 100644 poly/request.js delete mode 100755 scripts/injectPolyfills.sh diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6eef385..bfd3e1e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -6,7 +6,6 @@ on: paths: - 'src/**' - 'scripts/**' - - 'poly/**' - '.github/workflows/**' jobs: @@ -25,8 +24,7 @@ jobs: - name: Pack base asar run: | npm i -g asar - bash scripts/injectPolyfills.sh - sed -i -e "s/nightly/nightly-$(git rev-parse HEAD | cut -c 1-7)/" src/index.js + sed -i -e "s/oaVersion = '\(.*\)'/oaVersion = '\1-$(git rev-parse HEAD | cut -c 1-7)'/" src/index.js node scripts/strip.js npx asar pack src app.asar @@ -147,15 +145,16 @@ jobs: - name: GitHub Release run: | - git tag -d nightly || true - git push origin --delete nightly || true - git tag nightly - git push origin nightly + echo ${{ env.VERSION }} + git tag -d ${{ env.VERSION }} || true + git push origin --delete ${{ env.VERSION }} || true + git tag ${{ env.VERSION }} + git push origin ${{ env.VERSION }} gh release delete ${{ env.VERSION }} -y || true - gh release create ${{ env.VERSION }} -t "Nightly" -n "$(git rev-parse HEAD | cut -c 1-7) | $(git log -1 --pretty=%B)" ${{ env.FILES }} + gh release create ${{ env.VERSION }} -t "$(echo "${{ env.VERSION }}" | sed 's/.*/\u&/' | sed "s/nt/n't/")" -n "$(git rev-parse HEAD | cut -c 1-7) | $(git log -1 --pretty=%B)" "$([ "${{ env.VERSION }}" != "nightly" ] && echo "-p")" ${{ env.FILES }} env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - VERSION: 'nightly' + VERSION: $(strings app.asar | grep -oP "oaVersion='.*?-" | grep -oP "(?<=').*?(?=-)") FILES: app.asar # debug-linux: @@ -230,4 +229,4 @@ jobs: # cd discord/app-1.0.0 # ./DiscordCanary.exe --enable-logging # timeout-minutes: 5 - # shell: bash + # shell: bash \ No newline at end of file diff --git a/faq.md b/faq.md index 0f2122f..7c1d436 100644 --- a/faq.md +++ b/faq.md @@ -22,7 +22,7 @@ OpenAsar optimizes Chromium (the web engine / browser Discord uses) to help incr The main speed increase (default options) is mostly accidental / coincidental (not intended) as it is mostly a side effect of rewriting it. ### How is this so small? -Compared to Discord's original, OpenAsar is <2% of the size. This is because when rewriting we remove NPM dependencies with our own custom code for more performance and efficiency. These are replaced with custom polyfills (compatible replacements). +Compared to Discord's original, OpenAsar is <2% of the size. This is because when rewriting we remove NPM dependencies with our own custom code for more performance and efficiency. ### What is Quickstart? Quickstart "skips" a few Discord features like the splash screen and waiting for updates in favour of speed. It is currently experimental and not fully recommended for normal use. diff --git a/poly/mime-types.js b/poly/mime-types.js deleted file mode 100644 index 5a61a0e..0000000 --- a/poly/mime-types.js +++ /dev/null @@ -1,2 +0,0 @@ -// Stub -exports.lookup = (file) => 'text/plain'; \ No newline at end of file diff --git a/poly/request.js b/poly/request.js deleted file mode 100644 index daa4c99..0000000 --- a/poly/request.js +++ /dev/null @@ -1,84 +0,0 @@ -const https = require('https'); - -// Generic polyfill for "request" npm package, wrapper for https -const nodeReq = ({ method, url, headers, qs, timeout, body }) => new Promise((resolve) => { - let req; - try { - req = https.request(url + (qs != null ? `?${(new URLSearchParams(qs)).toString()}` : ''), { method, headers, timeout }, async (res) => { - const loc = res.headers.location; - if (loc) return resolve(await nodeReq({ url: loc, method, headers, timeout, body })); - - resolve(res); - }); - } catch (e) { - return resolve(e); - } - - req.on('error', resolve); - - if (body) req.write(body); // Write POST body if included - - req.end(); -}); - -const request = (...args) => { - let options, callback; - switch (args.length) { - case 3: // request(url, options, callback) - options = { - url: args[0], - ...args[1] - }; - - callback = args[2]; - break; - - default: // request(url, callback) / request(options, callback) - options = args[0]; - callback = args[1]; - } - - if (typeof options === 'string') { - options = { - url: options - }; - } - - const listeners = {}; - - nodeReq(options).then(async (res) => { - if (!res.statusCode) { - listeners['error']?.(res); - return callback?.(res, null, null); - } - - listeners['response']?.(res); - - let data = []; - res.on('data', (chunk) => { - data.push(chunk); - listeners['data']?.(chunk); - }); - - await new Promise((resolve) => res.on('end', resolve)); // Wait to read full body - - const buf = Buffer.concat(data); - callback?.(undefined, res, options.encoding !== null ? buf.toString() : buf); - }); - - const ret = { - on: (type, handler) => { - listeners[type] = handler; - return ret; // Return self - } - }; - - return ret; -}; - -for (const m of [ 'get', 'post', 'put', 'patch', 'delete', 'head', 'options' ]) { - request[m] = (url, callback) => request({ url, method: m }, callback); -} -request.del = request.delete; // Special case - -module.exports = request; \ No newline at end of file diff --git a/scripts/injectPolyfills.sh b/scripts/injectPolyfills.sh deleted file mode 100755 index 51fb485..0000000 --- a/scripts/injectPolyfills.sh +++ /dev/null @@ -1,3 +0,0 @@ -rm -rf src/node_modules -mkdir src/node_modules -cp -rf poly/* src/node_modules \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index 1cf5baf..4a5a700 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,7 +1,6 @@ #!/bin/sh echo "Packing asar..." -./scripts/injectPolyfills.sh asar pack src app.asar # Package asar # asar list app.asar # List asar for debugging / testing diff --git a/src/asarUpdate.js b/src/asarUpdate.js index 2cb95ff..382d82a 100644 --- a/src/asarUpdate.js +++ b/src/asarUpdate.js @@ -1,4 +1,4 @@ -const request = require('request'); +const { get } = require('https'); const fs = require('original-fs'); // Use original-fs, not Electron's modified fs const { join } = require('path'); @@ -7,21 +7,23 @@ const downloadPath = join(asarPath, '..', 'app.asar.download'); const asarUrl = `https://github.com/GooseMod/OpenAsar/releases/download/${oaVersion.split('-')[0]}/app.asar`; +// todo: have these https utils centralised? +const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper around https.get to follow redirects + const loc = r.headers.location; + if (loc) return redirs(loc).then(res); + + res(r); +})); + module.exports = async () => { // (Try) update asar log('AsarUpdate', 'Updating...'); if (!oaVersion.includes('-')) return; - await new Promise((res) => { - const file = fs.createWriteStream(downloadPath); + const file = fs.createWriteStream(downloadPath); + (await redirs(asarUrl)).pipe(file); - file.on('finish', () => { - file.close(); - res(); - }); - - request.get(asarUrl).on('response', r => r.pipe(file)); - }); + await new Promise(res => file.on('finish', res)); if (fs.readFileSync(downloadPath, 'utf8').startsWith('<')) return log('AsarUpdate', 'Download error'); diff --git a/src/bootstrap.js b/src/bootstrap.js index 8c2b192..259eaac 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -1,6 +1,5 @@ const { app, session } = require('electron'); const { readFileSync } = require('fs'); -const get = require('request'); const { join } = require('path'); if (!settings.get('enableHardwareAcceleration', true)) app.disableHardwareAcceleration(); @@ -41,7 +40,7 @@ const startCore = () => { const [ channel, hash ] = oaVersion.split('-'); // Split via - bw.webContents.executeJavaScript(readFileSync(join(__dirname, 'mainWindow.js'), 'utf8') - .replaceAll('', hash || 'custom') + .replaceAll('', hash) .replaceAll('', oaConfig.noTrack) .replace('', (oaConfig.css ?? '').replaceAll('\\', '\\\\').replaceAll('`', '\\`'))); @@ -92,7 +91,7 @@ const startUpdate = () => { inst.on('InconsistentInstallerState', fatal); inst.on('update-error', console.error); - require('./winFirst').do(inst); + require('./winFirst').do(); } else { moduleUpdater.init(Constants.UPDATE_ENDPOINT, buildInfo); } diff --git a/src/config/index.js b/src/config/index.js index 876ea42..55dfe44 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -18,7 +18,7 @@ exports.open = () => { ipcMain.on('cs', (e, c) => { config = c; settings.set('openasar', config); - settings.save(); // Ensure saving + settings.save(); }); ipcMain.on('cg', e => { diff --git a/src/updater/moduleUpdater.js b/src/updater/moduleUpdater.js index cf86256..ee382f4 100644 --- a/src/updater/moduleUpdater.js +++ b/src/updater/moduleUpdater.js @@ -3,7 +3,7 @@ const fs = require('fs'); const Module = require('module'); const { execFile } = require('child_process'); const { app, autoUpdater } = require('electron'); -const request = require('request'); +const { get } = require('https'); const paths = require('../paths'); @@ -32,6 +32,20 @@ const resetTracking = () => { installing = Object.assign({}, base); }; +const req = url => new Promise(res => get(url, r => { // Minimal wrapper around https.get to include body + let dat = ''; + r.on('data', b => dat += b.toString()); + + r.on('end', () => res([ r, dat ])); +})); + +const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper around https.get to follow redirects + const loc = r.headers.location; + if (loc) return redirs(loc).then(res); + + res(r); +})); + exports.init = (endpoint, { releaseChannel, version }) => { skipHost = settings.get('SKIP_HOST_UPDATE'); skipModule = settings.get('SKIP_MODULE_UPDATE'); @@ -61,13 +75,11 @@ exports.init = (endpoint, { releaseChannel, version }) => { setFeedURL(url) { this.url = url; } - + checkForUpdates() { - request(this.url, (e, r, b) => { - if (e) return this.emit('error'); - + req(this.url).then(([ r, b ]) => { if (r.statusCode === 204) return this.emit('update-not-available'); - + this.emit('update-manually', b); }); } @@ -95,17 +107,11 @@ exports.init = (endpoint, { releaseChannel, version }) => { host.setFeedURL(`${endpoint}/updates/${releaseChannel}?platform=${platform}&version=${version}`); baseUrl = `${endpoint}/modules/${releaseChannel}`; - qs = { - host_version: version, - platform - }; + qs = `?host_version=${version}&platform=${platform}`; }; const checkModules = async () => { - remote = await new Promise((res) => request({ - url: baseUrl + '/versions.json', - qs - }, (e, r, b) => res(JSON.parse(b)))); + remote = JSON.parse((await req(baseUrl + '/versions.json' + qs))[1]); for (const name in installed) { const inst = installed[name].installedVersion; @@ -113,7 +119,7 @@ const checkModules = async () => { if (inst !== rem) { log('Modules', 'Update:', name, inst, '->', rem); - + downloadModule(name, rem); } } @@ -129,26 +135,21 @@ const downloadModule = async (name, ver) => { // log('Modules', 'Downloading', `${name}@${ver}`); - let success, total, cur = 0; - request({ - url: baseUrl + '/' + name + '/' + ver, - qs - }).on('response', (res) => { - success = res.statusCode === 200; - total = parseInt(res.headers['content-length'] ?? 1, 10); + let success, total, cur = 0; + const res = await redirs(baseUrl + '/' + name + '/' + ver + qs); + success = res.statusCode === 200; + total = parseInt(res.headers['content-length'] ?? 1, 10); - res.pipe(file); + res.pipe(file); - res.on('data', c => { - cur += c.length; + res.on('data', c => { + cur += c.length; - events.emit('downloading-module', { name, cur, total }); - }); + events.emit('downloading-module', { name, cur, total }); }); await new Promise((res) => file.on('close', res)); - if (success) commitManifest(); else downloading.fail++; @@ -156,7 +157,6 @@ const downloadModule = async (name, ver) => { name }); - downloading.done++; if (downloading.done === downloading.total) { @@ -211,10 +211,10 @@ const installModule = async (name, ver, path) => { proc.on('close', () => { if (err) return; - + installed[name] = { installedVersion: ver }; commitManifest(); - + finishInstall(name, ver, true); }); }; @@ -236,7 +236,7 @@ const finishInstall = (name, ver, success) => { events.emit('installed', { failed: installing.fail }); - + resetTracking(); } }; diff --git a/src/winFirst.js b/src/winFirst.js index 6302459..1a2f9b8 100644 --- a/src/winFirst.js +++ b/src/winFirst.js @@ -1,15 +1,14 @@ const fs = require('fs'); -const { join, resolve, basename } = require('path'); +const { join, resolve } = require('path'); const Constants = require('./Constants'); const reg = (a, c) => require('child_process').execFile('reg.exe', a, c); const exec = process.execPath; const app = resolve(exec, '..'); -const root = resolve(app, '..'); -exports.do = (updater) => { +exports.do = () => { const flag = join(app, '.first-run'); if (fs.existsSync(flag)) return; // Already done, skip @@ -23,31 +22,8 @@ exports.do = (updater) => { [base + '\\shell\\open\\command', '/ve', '/d', `"${exec}" --url -- "%1"`] ]) reg([ 'add', ...x, '/f' ], e => {}); - try { // Make shortcuts - const file = Constants.APP_NAME_FOR_HUMANS + '.lnk'; - const icon_path = join(root, 'app.ico'); - - fs.copyFileSync(join(app, 'app.ico'), icon_path); // app-1.0.0/app.ico -> app.ico - - for (const shortcut_path of [ - join(updater.getKnownFolder('desktop'), file), - join(updater.getKnownFolder('programs'), Constants.APP_COMPANY, file) - ]) { - if (!fs.existsSync(shortcut_path)) continue; // Don't update already deleted paths - - updater.createShortcut({ - target_path: join(root, 'Update.exe'), - shortcut_path, - arguments: '--processStart ' + basename(exec), - icon_path, - icon_index: 0, - description: Constants.APP_DESCRIPTION, - app_user_model_id: Constants.APP_ID, - working_directory: app - }); - } - - fs.writeFileSync(flag, 'true'); + try { + fs.writeFileSync(flag, ''); } catch (e) { log('FirstRun', e); }