Skip to content

Commit

Permalink
Bundle updates further tweaks (stream-labs#2644)
Browse files Browse the repository at this point in the history
* rename webpack files

* WIP

* working bundle updater v3

* remove commented out code

* revert version change

* again
  • Loading branch information
avacreeth authored May 15, 2020
1 parent c194aa4 commit baffb30
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 62 deletions.
1 change: 1 addition & 0 deletions electron-builder/base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const base = {
'media/images/game-capture',
'updater/build/bootstrap.js',
'updater/build/bundle-updater.js',
'updater/index.html',
'index.html',
'main.js',
'obs-api'
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
"version": "0.22.0-preview.7",
"main": "main.js",
"scripts": {
"compile": "yarn clear && yarn compile:updater && yarn webpack-cli --progress --config dev.config.js",
"compile:production": "yarn clear && yarn compile:updater && yarn webpack-cli --progress --config prod.config.js",
"compile": "yarn clear && yarn compile:updater && yarn webpack-cli --progress --config webpack.dev.config.js",
"compile:production": "yarn clear && yarn compile:updater && yarn webpack-cli --progress --config webpack.prod.config.js",
"compile:updater": "rimraf updater/build && tsc -p updater",
"compile:strictnulls": "yarn clear && yarn compile:updater && yarn webpack-cli --config strict-null-check.config.js",
"compile:strictnulls": "yarn clear && yarn compile:updater && yarn webpack-cli --config webpack.strict-null-check.config.js",
"compile:tests": "tsc -p test",
"webpack-cli": "node --max-old-space-size=4096 node_modules/webpack-cli/bin/cli.js",
"watch": "yarn clear && yarn compile:updater && yarn webpack-cli --watch --progress --config dev.config.js",
"watch:strictnulls": "yarn clear && yarn compile:updater && yarn webpack-cli --watch --progress --config strict-null-check.config.js",
"watch:app": "yarn clear && yarn webpack-cli --watch --progress --config dev-app.config.js",
"watch": "yarn clear && yarn compile:updater && yarn webpack-cli --watch --progress --config webpack.dev.config.js",
"watch:strictnulls": "yarn clear && yarn compile:updater && yarn webpack-cli --watch --progress --config webpack.strict-null-check.config.js",
"watch:app": "yarn clear && yarn webpack-cli --watch --progress --config webpack.dev-app.config.js",
"start": "electron .",
"clear-plugins": "rimraf plugins",
"package": "yarn generate-agreement && rimraf dist && build -w --x64 --config electron-builder/base.config.js",
Expand All @@ -28,7 +28,7 @@
"test:debug-brk": "tsc -p test && node --inspect --inspect-brk ./node_modules/ava/profile.js",
"clear": "rimraf bundles/media",
"typedoc": "typedoc --out docs/dist ./app/services/api/external-api/ --mode modules --theme ./docs/theme --readme ./docs/README.md --ignoreCompilerErrors --excludePrivate --excludeProtected --excludeExternals --hideGenerator",
"ci:compile": "yarn clear && yarn compile:updater && yarn webpack-cli --config dev.config.js",
"ci:compile": "yarn clear && yarn compile:updater && yarn webpack-cli --config webpack.dev.config.js",
"ci:tests": "node ./test/helpers/runner.js",
"ci:screentests": "node test/screentest/runner.js",
"ci:performance": "node test/performance/performance-test-runner.js",
Expand Down Expand Up @@ -74,6 +74,7 @@
"extract-zip": "^1.6.7",
"facemask-plugin": "https://s3-us-west-2.amazonaws.com/faces-internal.streamlabs.com/facemask-plugin-deployment/facemask-plugin-0.9.1.tar.gz",
"font-manager": "https://github.com/stream-labs/font-manager/releases/download/v1.0.8electron6/iojs-v6.0.3-font-manager.tar.gz",
"fs-extra": "^9.0.0",
"node-fetch": "^2.6.0",
"node-fontinfo": "https://github.com/stream-labs/node-fontinfo/releases/download/v0.0.12electron6/iojs-v6.0.3-node-fontinfo.tar.gz",
"node-libuiohook": "https://github.com/stream-labs/node-libuiohook/releases/download/v0.0.14/iojs-v6.0.3-node-libuiohook.tar.gz",
Expand Down Expand Up @@ -124,8 +125,8 @@
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-decorators-legacy": "1.3.5",
"classnames": "2.2.6",
"cli-table": "0.3.1",
"clean-webpack-plugin": "3.0.0",
"cli-table": "0.3.1",
"css-element-queries": "1.2.3",
"css-loader": "2.1.1",
"devtron": "1.4.0",
Expand Down Expand Up @@ -201,4 +202,4 @@
"webpack-manifest-plugin": "2.2.0",
"webpack-merge": "4.2.1"
}
}
}
14 changes: 12 additions & 2 deletions updater/UpdaterWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<i class="UpdaterWindow-icon fas fa-sync-alt fa-spin"/>
{{ message }}
</div>
<div class="UpdaterWindow-subMessage">
{{ subMessage }}
</div>
</div>
</template>

Expand All @@ -13,9 +16,10 @@ const { remote, ipcRenderer } = window.require('electron');
export default {
data() {
return {
message: 'Checking for updates',
message: 'Downloading Updates',
subMessage: 'Streamlabs OBS is downloading updates. This can take several minutes.',
};
}
},
};
</script>

Expand All @@ -30,6 +34,12 @@ export default {
-webkit-app-region: drag;
}
.UpdaterWindow-subMessage {
font-size: 13px;
margin-top: 16px;
color: #bdc2c4;
}
.updater-window__img {
width: 48px;
}
Expand Down
195 changes: 148 additions & 47 deletions updater/bundle-updater.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,126 @@
import fetch from 'node-fetch';
import * as electron from 'electron';
import * as path from 'path';
import * as fs from 'fs-extra';

module.exports = async (basePath: string) => {
const cdnBase = `https://slobs-cdn.streamlabs.com/${process.env.SLOBS_VERSION}/bundles/`;
const localBase = `file://${basePath}/bundles/`;
const bundlesBaseDirectory = path.join(electron.app.getPath('userData'), 'bundles');
const bundleDirectory = path.join(bundlesBaseDirectory, process.env.SLOBS_VERSION!);

let updaterWindow: electron.BrowserWindow;
let updaterWindowSuccessfulClose = false;

function spawnUpdaterWindow() {
updaterWindow = new electron.BrowserWindow({
width: 400,
height: 180,
frame: false,
resizable: false,
show: false,
alwaysOnTop: true,
webPreferences: { nodeIntegration: true },
});

updaterWindow.on('ready-to-show', () => {
updaterWindow.show();
});

updaterWindow.on('close', () => {
if (!updaterWindowSuccessfulClose) electron.app.quit();
});

updaterWindow.loadURL(`file://${basePath}/updater/index.html`);

updaterWindow.webContents.openDevTools();
}

function closeUpdaterWindow() {
updaterWindowSuccessfulClose = true;
if (updaterWindow) {
// Closing the only window would normally quit the app, so ensure it doesn't.
electron.app.once('will-quit', e => e.preventDefault());
updaterWindow.close();
}
}

function downloadFile(srcUrl: string, dstPath: string): Promise<void> {
const tmpPath = `${dstPath}.tmp`;

return new Promise<void>((resolve, reject) => {
fetch(srcUrl)
.then(response => {
if (response.ok) return response;

console.log(`Got ${response.status} response from ${srcUrl}`);
return Promise.reject(response);
})
.then(({ body }) => {
const fileStream = fs.createWriteStream(tmpPath);
body.pipe(fileStream);

fileStream.on('finish', () => {
fs.rename(tmpPath, dstPath, e => {
if (e) {
reject(e);
return;
}

console.log(`Successfully downloaded ${srcUrl}`);
resolve();
});
});

fileStream.on('error', e => {
console.log(`Error downloading ${srcUrl}`, e);
reject(e);
});
})
.catch(e => reject(e));
});
}

/**
* This ensures that if there isn't a directory for this specific container version,
* we empty the bundles directory (to preserve HD space over time) and create a new
* directory for this specific version.
*/
async function ensureBundlesDirectory() {
if (!fs.existsSync(bundleDirectory)) {
fs.emptyDirSync(bundlesBaseDirectory);
fs.mkdirSync(bundleDirectory);
}
}

async function getBundleFilePath(bundle: string) {
console.log(`Looking for bundle: ${bundle}`);

// Check for bundle in this app package
const localPath = path.join(basePath, 'bundles', bundle);
if (fs.existsSync(localPath)) {
console.log(`Found local bundle ${bundle}`);
return localPath;
}

// Fall back to checking the download directory
const downloadPath = path.join(bundleDirectory, bundle);
if (fs.existsSync(downloadPath)) {
console.log(`Found existing downloaded bundle ${bundle}`);
return downloadPath;
}

// Finally check the server
const serverPath = `${cdnBase}${bundle}`;
console.log(`Attempting to download bundle ${bundle}`);
ensureBundlesDirectory();
await downloadFile(serverPath, downloadPath);
return downloadPath;
}

let useLocalBundles = false;

if (process.argv.includes('--localBundles')) {
if (process.argv.includes('--local-bundles')) {
useLocalBundles = true;
}

Expand All @@ -21,7 +133,7 @@ module.exports = async (basePath: string) => {
console.log('Local bundle info:', localManifest);

// Check if bundle updates are available
// TODO: Cache the latest bundle name for offline use?
// TODO: Cache the latest manifest for offline use?
let serverManifest: { [bundle: string]: string } | undefined;

if (!useLocalBundles) {
Expand All @@ -46,60 +158,49 @@ module.exports = async (basePath: string) => {
}
}

const bundlePathsMap: { [bundle: string]: string } = {};

if (!useLocalBundles && serverManifest) {
const promises = ['renderer.js', 'vendors~renderer.js'].map(bundleName => {
return getBundleFilePath(serverManifest![bundleName]).then(bundlePath => {
bundlePathsMap[bundleName] = bundlePath;
});
});

let timeout: NodeJS.Timeout | null = null;

try {
// Either all bundles need to successfully download, or we have to revert to local.
// If this takes more than 10 seconds, we will spawn a window to let the user know
// we are working on updates.
timeout = setTimeout(() => {
spawnUpdaterWindow();
}, 10 * 1000);

await Promise.all(promises);

clearTimeout(timeout);
closeUpdaterWindow();
} catch (e) {
if (timeout) clearTimeout(timeout);
closeUpdaterWindow();
console.log('Failed to download 1 or more bundles', e);
useLocalBundles = true;
}
}

electron.session.defaultSession?.webRequest.onBeforeRequest(
{ urls: ['https://slobs-cdn.streamlabs.com/bundles/*.js'] },
(request, cb) => {
const bundleName = request.url.split('/')[4];

if (!useLocalBundles && serverManifest && serverManifest[bundleName]) {
if (serverManifest[bundleName] !== localManifest[bundleName]) {
console.log(`Newer version of ${bundleName} is available`);
cb({ redirectURL: `${cdnBase}${serverManifest[bundleName]}` });
return;
}
if (!useLocalBundles && bundlePathsMap[bundleName]) {
cb({ redirectURL: `file://${bundlePathsMap[bundleName]}` });
return;
}

console.log(`Using local bundle for ${bundleName}`);
cb({ redirectURL: `${localBase}${localManifest[bundleName]}` });
},
);

// The following handlers should rarely be used and are a failsafe.
// If something goes wrong while fetching bundles even when the pre-fetch
// succeeded, then we restart the app and force it to use local bundles.

let appRelaunching = false;

function revertToLocalBundles() {
if (appRelaunching) return;
appRelaunching = true;
console.log('Reverting to local bundles and restarting app');
electron.app.relaunch({ args: ['--localBundles'] });
electron.app.quit();
}

if (!useLocalBundles) {
electron.session.defaultSession?.webRequest.onHeadersReceived(
{ urls: [`${cdnBase}*.js`] },
(info, cb) => {
if (info.statusCode / 100 < 4) {
cb({});
return;
}

console.log(`Caught error fetching bundle with status ${info.statusCode}`);

revertToLocalBundles();
},
);

electron.session.defaultSession?.webRequest.onErrorOccurred(
{ urls: [`${cdnBase}*.js`] },
info => {
console.log('Caught error fetching bundle', info.error);

revertToLocalBundles();
},
);
}
};
1 change: 1 addition & 0 deletions base.config.js → webpack.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module.exports = {
archiver: 'require("archiver")',
'@streamlabs/game-overlay': 'require("@streamlabs/game-overlay")',
'extract-zip': 'require("extract-zip")',
'fs-extra': 'require("fs-extra")',
},

module: {
Expand Down
2 changes: 1 addition & 1 deletion dev-app.config.js → webpack.dev-app.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const merge = require('webpack-merge');
const devConfig = require('./dev.config.js');
const devConfig = require('./webpack.dev.config.js');

module.exports = merge.strategy({ entry: 'replace' })(devConfig, {
entry: { renderer: './app/app.ts' },
Expand Down
2 changes: 1 addition & 1 deletion dev.config.js → webpack.dev.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const merge = require('webpack-merge');
const baseConfig = require('./base.config.js');
const baseConfig = require('./webpack.base.config.js');
const path = require('path');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const { CheckerPlugin } = require('awesome-typescript-loader');
Expand Down
2 changes: 1 addition & 1 deletion prod.config.js → webpack.prod.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const merge = require('webpack-merge');
const baseConfig = require('./base.config.js');
const baseConfig = require('./webpack.base.config.js');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge.smart(baseConfig, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const merge = require('webpack-merge');
const baseConfig = require('./base.config.js');
const baseConfig = require('./webpack.base.config.js');
const path = require('path');
const fs = require('fs');

Expand Down
Loading

0 comments on commit baffb30

Please sign in to comment.