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
This commit is contained in:
CanadaHonk 2022-12-02 22:23:46 +00:00 committed by GitHub
parent f2da613f2b
commit ec5082860e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 61 additions and 175 deletions

View file

@ -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

2
faq.md
View file

@ -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.

View file

@ -1,2 +0,0 @@
// Stub
exports.lookup = (file) => 'text/plain';

View file

@ -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;

View file

@ -1,3 +0,0 @@
rm -rf src/node_modules
mkdir src/node_modules
cp -rf poly/* src/node_modules

View file

@ -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

View file

@ -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');

5
src/bootstrap.js vendored
View file

@ -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>', hash || 'custom')
.replaceAll('<hash>', hash)
.replaceAll('<notrack>', oaConfig.noTrack)
.replace('<css>', (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);
}

View file

@ -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 => {

View file

@ -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();
}
};

View file

@ -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);
}