Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
|
ccc5f78652 | ||
|
23219a5e70 | ||
|
0d16d3d3ae | ||
|
c7923f9fab | ||
|
9743775dda | ||
|
083065721f | ||
|
7958464d6e | ||
|
780ad9a200 | ||
|
5ac24c8cea | ||
|
9d7a050bb6 | ||
|
73924c3f4b | ||
|
1ebb037c2c | ||
|
7b6ea86317 | ||
|
ebb19e63c0 | ||
|
8cbf89c729 | ||
|
9596304b38 |
68 changed files with 15598 additions and 1 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -4,4 +4,8 @@ src/package-lock.json
|
|||
_*
|
||||
miniSrc/
|
||||
|
||||
*.crswap # crostini tmp files
|
||||
*.crswap # crostini tmp files
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
|
37
.resources/app/app_bootstrap/Constants.js
Normal file
37
.resources/app/app_bootstrap/Constants.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
"use strict";
|
||||
|
||||
var _appSettings = require("./appSettings");
|
||||
// bootstrap constants
|
||||
// after startup, these constants will be merged into core module constants
|
||||
// since they are used in both locations (see app/Constants.js)
|
||||
|
||||
const {
|
||||
releaseChannel
|
||||
} = require('./buildInfo');
|
||||
const settings = (0, _appSettings.getSettings)();
|
||||
function capitalizeFirstLetter(s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
const appNameSuffix = releaseChannel === 'stable' ? '' : capitalizeFirstLetter(releaseChannel);
|
||||
const APP_COMPANY = 'Discord Inc';
|
||||
const APP_DESCRIPTION = 'Discord - https://discord.com';
|
||||
const APP_NAME = 'Discord' + appNameSuffix;
|
||||
const APP_NAME_FOR_HUMANS = 'Discord' + (appNameSuffix !== '' ? ' ' + appNameSuffix : '');
|
||||
const APP_ID_BASE = 'com.squirrel';
|
||||
const APP_ID = `${APP_ID_BASE}.${APP_NAME}.${APP_NAME}`;
|
||||
const APP_PROTOCOL = 'Discord';
|
||||
const API_ENDPOINT = settings.get('API_ENDPOINT') || 'https://discord.com/api';
|
||||
const UPDATE_ENDPOINT = settings.get('UPDATE_ENDPOINT') || API_ENDPOINT;
|
||||
const NEW_UPDATE_ENDPOINT = settings.get('NEW_UPDATE_ENDPOINT') || 'https://updates.discord.com/';
|
||||
const bootstrapConstants = {
|
||||
APP_COMPANY,
|
||||
APP_DESCRIPTION,
|
||||
APP_NAME,
|
||||
APP_NAME_FOR_HUMANS,
|
||||
APP_ID,
|
||||
APP_PROTOCOL,
|
||||
API_ENDPOINT,
|
||||
NEW_UPDATE_ENDPOINT,
|
||||
UPDATE_ENDPOINT
|
||||
};
|
||||
module.exports = bootstrapConstants;
|
17
.resources/app/app_bootstrap/GPUSettings.js
Normal file
17
.resources/app/app_bootstrap/GPUSettings.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
// this file is here for two reasons:
|
||||
// 1. web requires ./GPUSettings file from electron app (bad!), and requires are
|
||||
// relative to process.main (bootstrap's index.js)
|
||||
// 2. GPUSettings has been refactored into GPUSettings, and because we want to
|
||||
// be able to update GPUSettings OTA, we will have the core module provide
|
||||
// us with the GPUSettings
|
||||
// so tl;dr this is core module's GPUSettings, providing compat for web
|
||||
|
||||
exports.replace = function (GPUSettings) {
|
||||
// replacing module.exports directly would have no effect, since requires are cached
|
||||
// so we mutate the existing object
|
||||
for (const name of Object.keys(GPUSettings)) {
|
||||
exports[name] = GPUSettings[name];
|
||||
}
|
||||
};
|
19
.resources/app/app_bootstrap/appSettings.js
Normal file
19
.resources/app/app_bootstrap/appSettings.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getSettings = getSettings;
|
||||
exports.init = init;
|
||||
var _Settings = _interopRequireDefault(require("../common/Settings"));
|
||||
var paths = _interopRequireWildcard(require("../common/paths"));
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
let settings;
|
||||
function init() {
|
||||
settings = new _Settings.default(paths.getUserData());
|
||||
}
|
||||
function getSettings() {
|
||||
return settings;
|
||||
}
|
55
.resources/app/app_bootstrap/appUpdater.js
Normal file
55
.resources/app/app_bootstrap/appUpdater.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.focusSplash = focusSplash;
|
||||
exports.update = update;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var moduleUpdater = _interopRequireWildcard(require("../common/moduleUpdater"));
|
||||
var paths = _interopRequireWildcard(require("../common/paths"));
|
||||
var _updater = require("../common/updater");
|
||||
var _appSettings = require("./appSettings");
|
||||
var autoStart = _interopRequireWildcard(require("./autoStart"));
|
||||
var _buildInfo = _interopRequireDefault(require("./buildInfo"));
|
||||
var _errorHandler = require("./errorHandler");
|
||||
var firstRun = _interopRequireWildcard(require("./firstRun"));
|
||||
var splashScreen = _interopRequireWildcard(require("./splashScreen"));
|
||||
var _Constants = require("./Constants");
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
// settings
|
||||
const USE_PINNED_UPDATE_MANIFEST = 'USE_PINNED_UPDATE_MANIFEST';
|
||||
function update(startMinimized, doneCallback, showCallback) {
|
||||
const settings = (0, _appSettings.getSettings)();
|
||||
if ((0, _updater.tryInitUpdater)(_buildInfo.default, _Constants.NEW_UPDATE_ENDPOINT)) {
|
||||
const updater = (0, _updater.getUpdater)();
|
||||
const usePinnedUpdateManifest = settings.get(USE_PINNED_UPDATE_MANIFEST);
|
||||
updater.on('host-updated', () => {
|
||||
autoStart.update(() => {});
|
||||
});
|
||||
updater.on('unhandled-exception', _errorHandler.fatal);
|
||||
updater.on(_updater.INCONSISTENT_INSTALLER_STATE_ERROR, _errorHandler.fatal);
|
||||
updater.on('update-error', _errorHandler.handled);
|
||||
updater.on('starting-new-host', () => {
|
||||
// dont run stale launch events--the host we're updating to will run its own callbacks
|
||||
splashScreen.events.removeListener(splashScreen.APP_SHOULD_LAUNCH, doneCallback);
|
||||
splashScreen.events.removeListener(splashScreen.APP_SHOULD_SHOW, showCallback);
|
||||
});
|
||||
if (usePinnedUpdateManifest) {
|
||||
const manifestPath = _path.default.join(paths.getUserData(), 'pinned_update.json');
|
||||
updater.setPinnedManifestSync(JSON.parse(_fs.default.readFileSync(manifestPath)));
|
||||
}
|
||||
firstRun.performFirstRunTasks(updater);
|
||||
} else {
|
||||
moduleUpdater.init(_Constants.UPDATE_ENDPOINT, settings, _buildInfo.default);
|
||||
}
|
||||
splashScreen.initSplash(startMinimized);
|
||||
splashScreen.events.once(splashScreen.APP_SHOULD_LAUNCH, doneCallback);
|
||||
splashScreen.events.once(splashScreen.APP_SHOULD_SHOW, showCallback);
|
||||
}
|
||||
function focusSplash() {
|
||||
splashScreen.focusWindow();
|
||||
}
|
21
.resources/app/app_bootstrap/autoStart/darwin.js
Normal file
21
.resources/app/app_bootstrap/autoStart/darwin.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.install = install;
|
||||
exports.isInstalled = isInstalled;
|
||||
exports.uninstall = uninstall;
|
||||
exports.update = update;
|
||||
function install(callback) {
|
||||
return callback();
|
||||
}
|
||||
function update(callback) {
|
||||
return callback();
|
||||
}
|
||||
function isInstalled(callback) {
|
||||
return callback(false);
|
||||
}
|
||||
function uninstall(callback) {
|
||||
return callback();
|
||||
}
|
3
.resources/app/app_bootstrap/autoStart/index.js
Normal file
3
.resources/app/app_bootstrap/autoStart/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = require('./' + process.platform);
|
71
.resources/app/app_bootstrap/autoStart/linux.js
Normal file
71
.resources/app/app_bootstrap/autoStart/linux.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.install = install;
|
||||
exports.isInstalled = isInstalled;
|
||||
exports.uninstall = uninstall;
|
||||
exports.update = update;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _electron = require("electron");
|
||||
var _buildInfo = _interopRequireDefault(require("../buildInfo"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
// TODO: We should use Constant's APP_NAME, but only once
|
||||
// we set up backwards compat with this.
|
||||
const appName = _path.default.basename(process.execPath, '.exe');
|
||||
const exePath = _electron.app.getPath('exe');
|
||||
const exeDir = _path.default.dirname(exePath);
|
||||
const iconPath = _path.default.join(exeDir, 'discord.png');
|
||||
const autostartDir = _path.default.join(_electron.app.getPath('appData'), 'autostart');
|
||||
const electronAppName = _electron.app.name ? _electron.app.name : _electron.app.getName();
|
||||
const autostartFileName = _path.default.join(autostartDir, electronAppName + '-' + _buildInfo.default.releaseChannel + '.desktop');
|
||||
const desktopFile = `[Desktop Entry]
|
||||
Type=Application
|
||||
Exec=${exePath}
|
||||
Hidden=false
|
||||
NoDisplay=false
|
||||
Name=${appName}
|
||||
Icon=${iconPath}
|
||||
Comment=Text and voice chat for gamers.
|
||||
X-GNOME-Autostart-enabled=true
|
||||
`;
|
||||
function ensureDir() {
|
||||
try {
|
||||
_fs.default.mkdirSync(autostartDir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// catch for when it already exists.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function install(callback) {
|
||||
// TODO: This could fail. We should read its return value
|
||||
ensureDir();
|
||||
try {
|
||||
return _fs.default.writeFile(autostartFileName, desktopFile, callback);
|
||||
} catch (e) {
|
||||
// I guess we don't autostart then
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
function update(callback) {
|
||||
// TODO: We might need to implement this later on
|
||||
return callback();
|
||||
}
|
||||
function isInstalled(callback) {
|
||||
try {
|
||||
_fs.default.stat(autostartFileName, (err, stats) => {
|
||||
if (err) {
|
||||
return callback(false);
|
||||
}
|
||||
return callback(stats.isFile());
|
||||
});
|
||||
} catch (e) {
|
||||
return callback(false);
|
||||
}
|
||||
}
|
||||
function uninstall(callback) {
|
||||
return _fs.default.unlink(autostartFileName, callback);
|
||||
}
|
55
.resources/app/app_bootstrap/autoStart/win32.js
Normal file
55
.resources/app/app_bootstrap/autoStart/win32.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.install = install;
|
||||
exports.isInstalled = isInstalled;
|
||||
exports.uninstall = uninstall;
|
||||
exports.update = update;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var windowsUtils = _interopRequireWildcard(require("../windowsUtils"));
|
||||
var _appSettings = require("../appSettings");
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const settings = (0, _appSettings.getSettings)();
|
||||
|
||||
// TODO: We should use Constant's APP_NAME, but only once
|
||||
// we set up backwards compat with this.
|
||||
const appName = _path.default.basename(process.execPath, '.exe');
|
||||
const fullExeName = _path.default.basename(process.execPath);
|
||||
const updatePath = _path.default.join(_path.default.dirname(process.execPath), '..', 'Update.exe');
|
||||
function install(callback) {
|
||||
const startMinimized = settings.get('START_MINIMIZED', false);
|
||||
let execPath = `"${updatePath}" --processStart ${fullExeName}`;
|
||||
if (startMinimized) {
|
||||
execPath = `${execPath} --process-start-args --start-minimized`;
|
||||
}
|
||||
const queue = [['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName, '/d', execPath]];
|
||||
windowsUtils.addToRegistry(queue, callback);
|
||||
}
|
||||
function update(callback) {
|
||||
isInstalled(installed => {
|
||||
if (installed) {
|
||||
install(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
function isInstalled(callback) {
|
||||
const queryValue = ['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName];
|
||||
queryValue.unshift('query');
|
||||
windowsUtils.spawnReg(queryValue, (error, stdout) => {
|
||||
const doesOldKeyExist = stdout.indexOf(appName) >= 0;
|
||||
callback(doesOldKeyExist);
|
||||
});
|
||||
}
|
||||
function uninstall(callback) {
|
||||
const queryValue = ['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName, '/f'];
|
||||
queryValue.unshift('delete');
|
||||
windowsUtils.spawnReg(queryValue, (error, stdout) => {
|
||||
callback();
|
||||
});
|
||||
}
|
167
.resources/app/app_bootstrap/bootstrap.js
vendored
Normal file
167
.resources/app/app_bootstrap/bootstrap.js
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
"use strict";
|
||||
|
||||
// bootstrap, or what runs before the rest of desktop does
|
||||
// responsible for handling updates and updating modules before continuing startup
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
// Some people are reporting audio problems on Linux that are fixed by setting
|
||||
// an environment variable PULSE_LATENCY_MSEC=30 -- the "real" fix is to see
|
||||
// what conditions require this and set this then (also to set it directly in
|
||||
// our webrtc setup code rather than here) but this should fix the bug for now.
|
||||
if (process.env.PULSE_LATENCY_MSEC === undefined) {
|
||||
process.env.PULSE_LATENCY_MSEC = 30;
|
||||
}
|
||||
}
|
||||
const {
|
||||
app,
|
||||
Menu
|
||||
} = require('electron');
|
||||
const sentry = require('@sentry/node');
|
||||
const buildInfo = require('./buildInfo');
|
||||
app.setVersion(buildInfo.version);
|
||||
|
||||
// expose releaseChannel to a global, since it's used by splash screen
|
||||
global.releaseChannel = buildInfo.releaseChannel;
|
||||
const errorHandler = require('./errorHandler');
|
||||
errorHandler.init();
|
||||
const crashReporterSetup = require('../common/crashReporterSetup');
|
||||
crashReporterSetup.init(buildInfo, sentry);
|
||||
const paths = require('../common/paths');
|
||||
paths.init(buildInfo);
|
||||
global.moduleDataPath = paths.getModuleDataPath();
|
||||
const appSettings = require('./appSettings');
|
||||
appSettings.init();
|
||||
const Constants = require('./Constants');
|
||||
const GPUSettings = require('./GPUSettings');
|
||||
function setupHardwareAcceleration() {
|
||||
const settings = appSettings.getSettings();
|
||||
// TODO: this is a copy of gpuSettings.getEnableHardwareAcceleration
|
||||
if (!settings.get('enableHardwareAcceleration', true)) {
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
}
|
||||
setupHardwareAcceleration();
|
||||
|
||||
// [adill] work around chrome 66 disabling autoplay by default
|
||||
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
|
||||
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
||||
app.commandLine.appendSwitch('disable-features', 'WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService');
|
||||
function hasArgvFlag(flag) {
|
||||
return (process.argv || []).slice(1).includes(flag);
|
||||
}
|
||||
console.log(`${Constants.APP_NAME} ${app.getVersion()}`);
|
||||
let pendingAppQuit = false;
|
||||
if (process.platform === 'win32') {
|
||||
// this tells Windows (in particular Windows 10) which icon to associate your app with, important for correctly
|
||||
// pinning app to task bar.
|
||||
app.setAppUserModelId(Constants.APP_ID);
|
||||
const {
|
||||
handleStartupEvent
|
||||
} = require('./squirrelUpdate');
|
||||
// TODO: Isn't using argv[1] fragile?
|
||||
const squirrelCommand = process.argv[1];
|
||||
// TODO: Is protocol case sensitive?
|
||||
if (handleStartupEvent(Constants.APP_PROTOCOL, app, squirrelCommand)) {
|
||||
pendingAppQuit = true;
|
||||
}
|
||||
}
|
||||
const appUpdater = require('./appUpdater');
|
||||
const moduleUpdater = require('../common/moduleUpdater');
|
||||
const updater = require('../common/updater');
|
||||
const splashScreen = require('./splashScreen');
|
||||
const autoStart = require('./autoStart');
|
||||
const requireNative = require('./requireNative');
|
||||
let coreModule;
|
||||
const allowMultipleInstances = hasArgvFlag('--multi-instance');
|
||||
const isFirstInstance = allowMultipleInstances ? true : app.requestSingleInstanceLock();
|
||||
function extractUrlFromArgs(args) {
|
||||
const urlArgIndex = args.indexOf('--url');
|
||||
if (urlArgIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
const passThroughArgsIndex = args.indexOf('--');
|
||||
if (passThroughArgsIndex < 0 || passThroughArgsIndex < urlArgIndex) {
|
||||
return null;
|
||||
}
|
||||
const url = args[passThroughArgsIndex + 1];
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
let initialUrl = extractUrlFromArgs(process.argv);
|
||||
if (!allowMultipleInstances) {
|
||||
app.on('second-instance', (_event, args, _workingDirectory) => {
|
||||
if (args != null && args.indexOf('--squirrel-uninstall') > -1) {
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
const url = extractUrlFromArgs(args);
|
||||
if (coreModule) {
|
||||
// url can be null, as a user opening the executable again will focus the app from background
|
||||
coreModule.handleOpenUrl(url);
|
||||
} else if (url != null) {
|
||||
initialUrl = url;
|
||||
}
|
||||
if (!coreModule) {
|
||||
appUpdater.focusSplash();
|
||||
}
|
||||
});
|
||||
}
|
||||
app.on('will-finish-launching', () => {
|
||||
// on macos protocol links are handled entirely through this event
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
if (coreModule) {
|
||||
coreModule.handleOpenUrl(url);
|
||||
} else {
|
||||
initialUrl = url;
|
||||
}
|
||||
});
|
||||
});
|
||||
function startUpdate() {
|
||||
console.log('Starting updater.');
|
||||
const startMinimized = hasArgvFlag('--start-minimized');
|
||||
appUpdater.update(startMinimized, () => {
|
||||
try {
|
||||
coreModule = requireNative('discord_desktop_core');
|
||||
coreModule.startup({
|
||||
paths,
|
||||
splashScreen,
|
||||
moduleUpdater,
|
||||
autoStart,
|
||||
buildInfo,
|
||||
appSettings,
|
||||
Constants,
|
||||
GPUSettings,
|
||||
updater,
|
||||
crashReporterSetup
|
||||
});
|
||||
if (initialUrl != null) {
|
||||
coreModule.handleOpenUrl(initialUrl);
|
||||
initialUrl = null;
|
||||
}
|
||||
} catch (err) {
|
||||
return errorHandler.fatal(err);
|
||||
}
|
||||
}, () => {
|
||||
coreModule.setMainWindowVisible(!startMinimized);
|
||||
});
|
||||
}
|
||||
function startApp() {
|
||||
console.log('Starting app.');
|
||||
paths.cleanOldVersions(buildInfo);
|
||||
const startupMenu = require('./startupMenu');
|
||||
Menu.setApplicationMenu(startupMenu);
|
||||
startUpdate();
|
||||
}
|
||||
if (pendingAppQuit) {
|
||||
console.log('Startup prevented.');
|
||||
} else if (!isFirstInstance && !allowMultipleInstances) {
|
||||
console.log('Quitting secondary instance.');
|
||||
app.quit();
|
||||
} else {
|
||||
app.whenReady().then(() => startApp());
|
||||
}
|
12
.resources/app/app_bootstrap/buildInfo.js
Normal file
12
.resources/app/app_bootstrap/buildInfo.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const buildInfo = require(_path.default.join(process.resourcesPath, 'build_info.json'));
|
||||
var _default = buildInfo;
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
14
.resources/app/app_bootstrap/data/quotes_copy.json
Normal file
14
.resources/app/app_bootstrap/data/quotes_copy.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
"Upsorbing the Contents",
|
||||
"Additive Parsing the Load",
|
||||
"Commence Monosaturated Goodening",
|
||||
"Kick Off the Multi-Core Widening",
|
||||
"Bastening the Game Turkey",
|
||||
"Abstracting the Rummage Disc",
|
||||
"Undecerealenizing the Process",
|
||||
"Postrefragmenting the Widget Layer",
|
||||
"Satisfying the Constraints",
|
||||
"Abnoramalzing Some of the Matrices",
|
||||
"Optimizing the People",
|
||||
"Proclaigerizing the Network"
|
||||
]
|
85
.resources/app/app_bootstrap/errorHandler.js
Normal file
85
.resources/app/app_bootstrap/errorHandler.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.fatal = fatal;
|
||||
exports.handled = handled;
|
||||
exports.init = init;
|
||||
var Sentry = _interopRequireWildcard(require("@sentry/node"));
|
||||
var _electron = require("electron");
|
||||
var _process = _interopRequireDefault(require("process"));
|
||||
var _crashReporterSetup = require("../common/crashReporterSetup");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
const HANDLED_ERROR_INTERVAL = 3;
|
||||
const HANDLED_ERROR_LIMIT = 10;
|
||||
let handledErrorCounter = 0;
|
||||
let totalHandledErrors = 0;
|
||||
const consoleOutputOnly = _process.default.env.DISCORD_TEST != null;
|
||||
function isErrorSafeToSuppress(error) {
|
||||
return /attempting to call a function in a renderer window/i.test(error.message);
|
||||
}
|
||||
function captureJSException(error) {
|
||||
Sentry.captureException(error, scope => {
|
||||
scope.clear();
|
||||
scope.setTag('nativeBuildNumber', _crashReporterSetup.metadata.nativeBuildNumber);
|
||||
scope.setUser(_crashReporterSetup.metadata.sentry.user);
|
||||
scope.setExtras({
|
||||
environment: _crashReporterSetup.metadata.sentry.environment,
|
||||
release: _crashReporterSetup.metadata.sentry.release,
|
||||
nativeBuildNumber: _crashReporterSetup.metadata.nativeBuildNumber
|
||||
});
|
||||
return scope;
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
_process.default.on('uncaughtException', error => {
|
||||
const stack = error.stack ? error.stack : String(error);
|
||||
const message = `Uncaught exception:\n ${stack}`;
|
||||
console.warn(message);
|
||||
captureJSException(error);
|
||||
if (!isErrorSafeToSuppress(error)) {
|
||||
if (consoleOutputOnly) {
|
||||
console.error(`${message} error: ${error}`);
|
||||
_process.default.exit(-1);
|
||||
}
|
||||
_electron.dialog.showErrorBox('A JavaScript error occurred in the main process', message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// show a similar error message to the error handler, except exit out the app
|
||||
// after the error message has been closed
|
||||
function fatal(err) {
|
||||
const options = {
|
||||
type: 'error',
|
||||
message: 'A fatal Javascript error occured',
|
||||
detail: err && err.stack ? err.stack : String(err)
|
||||
};
|
||||
if (consoleOutputOnly) {
|
||||
console.error(`fatal: ${err}\n${err === null || err === void 0 ? void 0 : err.stack}`);
|
||||
_process.default.exit(-1);
|
||||
}
|
||||
const callback = _ => _electron.app.quit();
|
||||
const electronMajor = parseInt(_process.default.versions.electron.split('.')[0]);
|
||||
if (electronMajor >= 6) {
|
||||
_electron.dialog.showMessageBox(null, options).then(callback);
|
||||
} else {
|
||||
_electron.dialog.showMessageBox(options, callback);
|
||||
}
|
||||
captureJSException(err);
|
||||
}
|
||||
|
||||
// capture a handled error for telemetry purposes, e.g. finding update loops.
|
||||
function handled(err) {
|
||||
if (global.releaseChannel !== 'ptb' && global.releaseChannel !== 'canary' && global.releaseChannel !== 'development') {
|
||||
return;
|
||||
}
|
||||
if (totalHandledErrors < HANDLED_ERROR_LIMIT && handledErrorCounter++ % HANDLED_ERROR_INTERVAL == 0) {
|
||||
console.warn('Reporting non-fatal error', err);
|
||||
captureJSException(err);
|
||||
totalHandledErrors++;
|
||||
}
|
||||
}
|
9
.resources/app/app_bootstrap/firstRun/darwin.js
Normal file
9
.resources/app/app_bootstrap/firstRun/darwin.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.performFirstRunTasks = performFirstRunTasks;
|
||||
function performFirstRunTasks(_updater) {
|
||||
//
|
||||
}
|
3
.resources/app/app_bootstrap/firstRun/index.js
Normal file
3
.resources/app/app_bootstrap/firstRun/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = require('./' + process.platform);
|
9
.resources/app/app_bootstrap/firstRun/linux.js
Normal file
9
.resources/app/app_bootstrap/firstRun/linux.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.performFirstRunTasks = performFirstRunTasks;
|
||||
function performFirstRunTasks(_updater) {
|
||||
//
|
||||
}
|
75
.resources/app/app_bootstrap/firstRun/win32.js
Normal file
75
.resources/app/app_bootstrap/firstRun/win32.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.performFirstRunTasks = performFirstRunTasks;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var paths = _interopRequireWildcard(require("../../common/paths"));
|
||||
var _errorHandler = require("../errorHandler");
|
||||
var _squirrelUpdate = require("../squirrelUpdate");
|
||||
var _Constants = require("../Constants");
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const appFolder = _path.default.resolve(process.execPath, '..');
|
||||
const rootFolder = _path.default.resolve(appFolder, '..');
|
||||
const exeName = _path.default.basename(process.execPath);
|
||||
const updateExe = _path.default.join(rootFolder, 'Update.exe');
|
||||
function copyIconToRoot() {
|
||||
const icoSrc = _path.default.join(appFolder, 'app.ico');
|
||||
const icoDest = _path.default.join(rootFolder, 'app.ico');
|
||||
try {
|
||||
const ico = _fs.default.readFileSync(icoSrc);
|
||||
_fs.default.writeFileSync(icoDest, ico);
|
||||
return icoDest;
|
||||
} catch (e) {
|
||||
return icoSrc;
|
||||
}
|
||||
}
|
||||
function updateShortcuts(updater) {
|
||||
const shortcutFileName = `${_Constants.APP_NAME_FOR_HUMANS}.lnk`;
|
||||
const shortcutPaths = [_path.default.join(updater.getKnownFolder('desktop'), shortcutFileName), _path.default.join(updater.getKnownFolder('programs'), _Constants.APP_COMPANY, shortcutFileName)];
|
||||
const iconPath = copyIconToRoot();
|
||||
for (const shortcutPath of shortcutPaths) {
|
||||
if (!_fs.default.existsSync(shortcutPath)) {
|
||||
// If the user deleted the shortcut, don't recreate it.
|
||||
continue;
|
||||
}
|
||||
updater.createShortcut({
|
||||
/* eslint-disable camelcase */
|
||||
target_path: updateExe,
|
||||
shortcut_path: shortcutPath,
|
||||
arguments: `--processStart ${exeName}`,
|
||||
icon_path: iconPath,
|
||||
icon_index: 0,
|
||||
description: _Constants.APP_DESCRIPTION,
|
||||
app_user_model_id: _Constants.APP_ID,
|
||||
working_directory: appFolder
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function performFirstRunTasks(updater) {
|
||||
const firstRunCompletePath = _path.default.join(paths.getUserDataVersioned(), '.first-run');
|
||||
if (!_fs.default.existsSync(firstRunCompletePath)) {
|
||||
let updatedShortcuts = false;
|
||||
try {
|
||||
updateShortcuts(updater);
|
||||
updatedShortcuts = true;
|
||||
} catch (e) {
|
||||
(0, _errorHandler.handled)(e);
|
||||
}
|
||||
(0, _squirrelUpdate.installProtocol)(_Constants.APP_PROTOCOL, () => {
|
||||
try {
|
||||
if (updatedShortcuts) {
|
||||
_fs.default.writeFileSync(firstRunCompletePath, 'true');
|
||||
}
|
||||
} catch (e) {
|
||||
(0, _errorHandler.handled)(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
164
.resources/app/app_bootstrap/hostUpdater.js
Normal file
164
.resources/app/app_bootstrap/hostUpdater.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _events = require("events");
|
||||
var _request = _interopRequireDefault(require("./request"));
|
||||
var squirrelUpdate = _interopRequireWildcard(require("./squirrelUpdate"));
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
/* eslint-disable no-console */
|
||||
|
||||
function versionParse(verString) {
|
||||
return verString.split('.').map(i => parseInt(i));
|
||||
}
|
||||
function versionNewer(verA, verB) {
|
||||
let i = 0;
|
||||
while (true) {
|
||||
const a = verA[i];
|
||||
const b = verB[i];
|
||||
i++;
|
||||
if (a === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
if (b === undefined || a > b) {
|
||||
return true;
|
||||
}
|
||||
if (a < b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class AutoUpdaterWin32 extends _events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.updateUrl = null;
|
||||
this.updateVersion = null;
|
||||
}
|
||||
setFeedURL(updateUrl) {
|
||||
this.updateUrl = updateUrl;
|
||||
}
|
||||
quitAndInstall() {
|
||||
if (squirrelUpdate.updateExistsSync()) {
|
||||
squirrelUpdate.restart(_electron.app, this.updateVersion ?? _electron.app.getVersion());
|
||||
} else {
|
||||
/* eslint-disable-next-line */
|
||||
require('auto-updater').quitAndInstall();
|
||||
}
|
||||
}
|
||||
downloadAndInstallUpdate(callback) {
|
||||
squirrelUpdate.spawnUpdateInstall(this.updateUrl, progress => {
|
||||
this.emit('update-progress', progress);
|
||||
}).catch(err => callback(err)).then(() => callback());
|
||||
}
|
||||
checkForUpdates() {
|
||||
if (this.updateUrl == null) {
|
||||
throw new Error('Update URL is not set');
|
||||
}
|
||||
this.emit('checking-for-update');
|
||||
if (!squirrelUpdate.updateExistsSync()) {
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
}
|
||||
squirrelUpdate.spawnUpdate(['--check', this.updateUrl], (error, stdout) => {
|
||||
if (error != null) {
|
||||
this.emit('error', error);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Last line of the output is JSON details about the releases
|
||||
const json = stdout.trim().split('\n').pop();
|
||||
const releasesFound = JSON.parse(json).releasesToApply;
|
||||
if (releasesFound == null || releasesFound.length === 0) {
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
}
|
||||
const update = releasesFound.pop();
|
||||
this.emit('update-available');
|
||||
this.downloadAndInstallUpdate(error => {
|
||||
if (error != null) {
|
||||
this.emit('error', error);
|
||||
return;
|
||||
}
|
||||
this.updateVersion = update.version;
|
||||
this.emit('update-downloaded', {}, update.release, update.version, new Date(), this.updateUrl, this.quitAndInstall.bind(this));
|
||||
});
|
||||
} catch (error) {
|
||||
error.stdout = stdout;
|
||||
this.emit('error', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
class AutoUpdaterLinux extends _events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.updateUrl = null;
|
||||
}
|
||||
setFeedURL(url) {
|
||||
this.updateUrl = url;
|
||||
}
|
||||
quitAndInstall() {
|
||||
// Just restart. The splash screen will hit the update manually state and
|
||||
// prompt the user to download the new package.
|
||||
_electron.app.relaunch();
|
||||
_electron.app.quit();
|
||||
}
|
||||
async checkForUpdates() {
|
||||
const currVersion = versionParse(_electron.app.getVersion());
|
||||
this.emit('checking-for-update');
|
||||
try {
|
||||
const response = await _request.default.get(this.updateUrl);
|
||||
if (response.statusCode === 204) {
|
||||
// you are up to date
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
}
|
||||
let latestVerStr = '';
|
||||
let latestVersion = [];
|
||||
try {
|
||||
const latestMetadata = JSON.parse(response.body);
|
||||
latestVerStr = latestMetadata.name;
|
||||
latestVersion = versionParse(latestVerStr);
|
||||
} catch (_) {}
|
||||
if (versionNewer(latestVersion, currVersion)) {
|
||||
console.log('[Updates] You are out of date!');
|
||||
// you need to update
|
||||
this.emit('update-manually', latestVerStr);
|
||||
} else {
|
||||
console.log('[Updates] You are living in the future!');
|
||||
this.emit('update-not-available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Updates] Error fetching ' + this.updateUrl + ': ' + err.message);
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
let autoUpdater;
|
||||
|
||||
// TODO
|
||||
// events: checking-for-update, update-available, update-not-available, update-manually, update-downloaded, error
|
||||
// also, checkForUpdates, setFeedURL, quitAndInstall
|
||||
// also, see electron.autoUpdater, and its API
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
autoUpdater = require('electron').autoUpdater;
|
||||
break;
|
||||
case 'win32':
|
||||
autoUpdater = new AutoUpdaterWin32();
|
||||
break;
|
||||
case 'linux':
|
||||
autoUpdater = new AutoUpdaterLinux();
|
||||
break;
|
||||
}
|
||||
var _default = autoUpdater;
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
BIN
.resources/app/app_bootstrap/images/img_lucky_dice.png
Normal file
BIN
.resources/app/app_bootstrap/images/img_lucky_dice.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
39
.resources/app/app_bootstrap/index.js
Normal file
39
.resources/app/app_bootstrap/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
const buildInfo = require('./buildInfo');
|
||||
const paths = require('../common/paths');
|
||||
paths.init(buildInfo);
|
||||
const moduleUpdater = require('../common/moduleUpdater');
|
||||
const updater = require('../common/updater');
|
||||
const requireNative = require('./requireNative');
|
||||
function getAppMode() {
|
||||
if (process.argv && process.argv.includes('--overlay-host')) {
|
||||
return 'overlay-host';
|
||||
}
|
||||
return 'app';
|
||||
}
|
||||
const mode = getAppMode();
|
||||
if (mode === 'app') {
|
||||
require('./bootstrap');
|
||||
} else if (mode === 'overlay-host') {
|
||||
// Initialize the update system just enough to find installed native modules.
|
||||
const appSettings = require('./appSettings');
|
||||
appSettings.init();
|
||||
const {
|
||||
NEW_UPDATE_ENDPOINT
|
||||
} = require('./Constants');
|
||||
if (buildInfo.newUpdater) {
|
||||
if (!updater.tryInitUpdater(buildInfo, NEW_UPDATE_ENDPOINT)) {
|
||||
throw new Error('Failed to initialize modules in overlay host.');
|
||||
}
|
||||
|
||||
// Load the module search path but if there's a pending host update, don't
|
||||
// restart into it.
|
||||
updater.getUpdater().startCurrentVersionSync({
|
||||
allowObsoleteHost: true
|
||||
});
|
||||
} else {
|
||||
moduleUpdater.initPathsOnly(buildInfo);
|
||||
}
|
||||
requireNative('discord_overlay2/standalone_host.js');
|
||||
}
|
19
.resources/app/app_bootstrap/installDevTools.js
Normal file
19
.resources/app/app_bootstrap/installDevTools.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
// used in devtools to hook in additional dev tools
|
||||
// require('electron').remote.require('./installDevTools')()
|
||||
|
||||
function installDevTools() {
|
||||
console.log(`Installing Devtron`);
|
||||
const devtron = require('devtron');
|
||||
devtron.uninstall();
|
||||
devtron.install();
|
||||
console.log(`Installed Devtron`);
|
||||
}
|
||||
var _default = installDevTools;
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
13
.resources/app/app_bootstrap/ipcMain.js
Normal file
13
.resources/app/app_bootstrap/ipcMain.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _default = {
|
||||
on: (event, callback) => _electron.ipcMain.on(`DISCORD_${event}`, callback),
|
||||
removeListener: (event, callback) => _electron.ipcMain.removeListener(`DISCORD_${event}`, callback)
|
||||
};
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
162
.resources/app/app_bootstrap/request.js
Normal file
162
.resources/app/app_bootstrap/request.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _querystring = _interopRequireDefault(require("querystring"));
|
||||
var _request = _interopRequireDefault(require("request"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const DEFAULT_REQUEST_TIMEOUT = 30000;
|
||||
function makeHTTPResponse({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
statusCode,
|
||||
statusMessage
|
||||
}, body) {
|
||||
return {
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
statusCode,
|
||||
statusMessage,
|
||||
body
|
||||
};
|
||||
}
|
||||
function makeHTTPStatusError(response) {
|
||||
const err = new Error(`HTTP Error: Status Code ${response.statusCode}`);
|
||||
err.response = response;
|
||||
return err;
|
||||
}
|
||||
function handleHTTPResponse(resolve, reject, response, stream) {
|
||||
const totalBytes = parseInt(response.headers['content-length'] || 1, 10);
|
||||
let receivedBytes = 0;
|
||||
const chunks = [];
|
||||
|
||||
// don't stream response if it's a failure
|
||||
if (response.statusCode >= 300) {
|
||||
stream = null;
|
||||
}
|
||||
response.on('data', chunk => {
|
||||
if (stream != null) {
|
||||
receivedBytes += chunk.length;
|
||||
stream.write(chunk);
|
||||
stream.emit('progress', {
|
||||
totalBytes,
|
||||
receivedBytes
|
||||
});
|
||||
return;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
response.on('end', () => {
|
||||
if (stream != null) {
|
||||
stream.on('finish', () => resolve(makeHTTPResponse(response, null)));
|
||||
stream.end();
|
||||
return;
|
||||
}
|
||||
const res = makeHTTPResponse(response, Buffer.concat(chunks));
|
||||
if (res.statusCode >= 300) {
|
||||
reject(makeHTTPStatusError(res));
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
}
|
||||
function nodeRequest({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
qs,
|
||||
timeout,
|
||||
body,
|
||||
stream
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = (0, _request.default)({
|
||||
method,
|
||||
url,
|
||||
qs,
|
||||
headers,
|
||||
followAllRedirects: true,
|
||||
encoding: null,
|
||||
timeout: timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT,
|
||||
body
|
||||
});
|
||||
req.on('response', response => handleHTTPResponse(resolve, reject, response, stream));
|
||||
req.on('error', err => reject(err));
|
||||
});
|
||||
}
|
||||
async function electronRequest({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
qs,
|
||||
timeout,
|
||||
body,
|
||||
stream
|
||||
}) {
|
||||
await _electron.app.whenReady();
|
||||
const {
|
||||
net,
|
||||
session
|
||||
} = require('electron');
|
||||
const req = net.request({
|
||||
method,
|
||||
url: `${url}${qs != null ? `?${_querystring.default.stringify(qs)}` : ''}`,
|
||||
redirect: 'follow',
|
||||
session: session.defaultSession
|
||||
});
|
||||
if (headers != null) {
|
||||
for (const headerKey of Object.keys(headers)) {
|
||||
req.setHeader(headerKey, headers[headerKey]);
|
||||
}
|
||||
}
|
||||
if (body != null) {
|
||||
req.write(body, 'utf-8');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const reqTimeout = setTimeout(() => {
|
||||
req.abort();
|
||||
reject(new Error(`network timeout: ${url}`));
|
||||
}, timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT);
|
||||
req.on('login', (authInfo, callback) => callback());
|
||||
req.on('response', response => {
|
||||
clearTimeout(reqTimeout);
|
||||
handleHTTPResponse(resolve, reject, response, stream);
|
||||
});
|
||||
req.on('error', err => {
|
||||
clearTimeout(reqTimeout);
|
||||
reject(err);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
async function requestWithMethod(method, options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
url: options
|
||||
};
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
method
|
||||
};
|
||||
try {
|
||||
return await electronRequest(options);
|
||||
} catch (err) {
|
||||
console.log(`Error downloading with electron net: ${err.message}`);
|
||||
console.log('Falling back to node net library..');
|
||||
}
|
||||
return nodeRequest(options);
|
||||
}
|
||||
|
||||
// only supports get for now, since retrying is non-idempotent and
|
||||
// we'd want to grovel the errors to make sure it's safe to retry
|
||||
var _default = {
|
||||
get: requestWithMethod.bind(null, 'GET')
|
||||
};
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
5
.resources/app/app_bootstrap/requireNative.js
Normal file
5
.resources/app/app_bootstrap/requireNative.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
// require(), with paths specialized for requiring only native modules.
|
||||
module.paths = [];
|
||||
module.exports = require;
|
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
Binary file not shown.
11
.resources/app/app_bootstrap/splash/index.html
Normal file
11
.resources/app/app_bootstrap/splash/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Discord Updater</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="splash-mount"></div>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
44
.resources/app/app_bootstrap/splash/index.js
Normal file
44
.resources/app/app_bootstrap/splash/index.js
Normal file
File diff suppressed because one or more lines are too long
6
.resources/app/app_bootstrap/splash/variables.json
Normal file
6
.resources/app/app_bootstrap/splash/variables.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"width": "300px",
|
||||
"height": "300px",
|
||||
"inDuration": 700,
|
||||
"outDuration": 333
|
||||
}
|
421
.resources/app/app_bootstrap/splashScreen.js
Normal file
421
.resources/app/app_bootstrap/splashScreen.js
Normal file
|
@ -0,0 +1,421 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.events = exports.APP_SHOULD_SHOW = exports.APP_SHOULD_LAUNCH = void 0;
|
||||
exports.focusWindow = focusWindow;
|
||||
exports.initSplash = initSplash;
|
||||
exports.pageReady = pageReady;
|
||||
var _electron = require("electron");
|
||||
var _events = require("events");
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _url = _interopRequireDefault(require("url"));
|
||||
var _Backoff = _interopRequireDefault(require("../common/Backoff"));
|
||||
var moduleUpdater = _interopRequireWildcard(require("../common/moduleUpdater"));
|
||||
var paths = _interopRequireWildcard(require("../common/paths"));
|
||||
var _securityUtils = require("../common/securityUtils");
|
||||
var _updater = require("../common/updater");
|
||||
var _ipcMain = _interopRequireDefault(require("./ipcMain"));
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const UPDATE_TIMEOUT_WAIT = 10000;
|
||||
const RETRY_CAP_SECONDS = 60;
|
||||
// citron note: atom seems to add about 50px height to the frame on mac but not windows
|
||||
// TODO: see if we can eliminate fudge by using useContentSize BrowserWindow option
|
||||
const LOADING_WINDOW_WIDTH = 300;
|
||||
const LOADING_WINDOW_HEIGHT = process.platform === 'darwin' ? 300 : 350;
|
||||
|
||||
// TODO: addModulesListener events should use Module's constants
|
||||
const CHECKING_FOR_UPDATES = 'checking-for-updates';
|
||||
const UPDATE_CHECK_FINISHED = 'update-check-finished';
|
||||
const UPDATE_FAILURE = 'update-failure';
|
||||
const LAUNCHING = 'launching';
|
||||
const DOWNLOADING_MODULE = 'downloading-module';
|
||||
const DOWNLOADING_UPDATES = 'downloading-updates';
|
||||
const DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished';
|
||||
const DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress';
|
||||
const DOWNLOADED_MODULE = 'downloaded-module';
|
||||
const NO_PENDING_UPDATES = 'no-pending-updates';
|
||||
const INSTALLING_MODULE = 'installing-module';
|
||||
const INSTALLING_UPDATES = 'installing-updates';
|
||||
const INSTALLED_MODULE = 'installed-module';
|
||||
const INSTALLING_MODULE_PROGRESS = 'installing-module-progress';
|
||||
const INSTALLING_MODULES_FINISHED = 'installing-modules-finished';
|
||||
const UPDATE_MANUALLY = 'update-manually';
|
||||
const APP_SHOULD_LAUNCH = 'APP_SHOULD_LAUNCH';
|
||||
exports.APP_SHOULD_LAUNCH = APP_SHOULD_LAUNCH;
|
||||
const APP_SHOULD_SHOW = 'APP_SHOULD_SHOW';
|
||||
exports.APP_SHOULD_SHOW = APP_SHOULD_SHOW;
|
||||
const events = new _events.EventEmitter();
|
||||
exports.events = events;
|
||||
function webContentsSend(win, event, ...args) {
|
||||
if (win != null && win.webContents != null) {
|
||||
win.webContents.send(`DISCORD_${event}`, ...args);
|
||||
}
|
||||
}
|
||||
let splashWindow;
|
||||
let modulesListeners;
|
||||
let updateTimeout;
|
||||
let updateAttempt;
|
||||
let splashState;
|
||||
let launchedMainWindow;
|
||||
let quoteCachePath;
|
||||
let restartRequired = false;
|
||||
let newUpdater;
|
||||
const updateBackoff = new _Backoff.default(1000, 30000);
|
||||
|
||||
// TODO(eiz): some of this logic should probably not live in the splash.
|
||||
//
|
||||
// Disabled because Rust interop stuff is going on in here.
|
||||
/* eslint-disable camelcase */
|
||||
class TaskProgress {
|
||||
constructor() {
|
||||
this.inProgress = new Map();
|
||||
this.finished = new Set();
|
||||
this.allTasks = new Set();
|
||||
}
|
||||
recordProgress(progress, task) {
|
||||
this.allTasks.add(task.package_sha256);
|
||||
if (progress.state !== _updater.TASK_STATE_WAITING) {
|
||||
this.inProgress.set(task.package_sha256, progress.percent);
|
||||
if (progress.state === _updater.TASK_STATE_COMPLETE) {
|
||||
this.finished.add(task.package_sha256);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSplashState(newState) {
|
||||
if (this.inProgress.size > 0 && this.inProgress.size > this.finished.size) {
|
||||
let totalPercent = 0;
|
||||
for (const item of this.inProgress.values()) {
|
||||
totalPercent += item;
|
||||
}
|
||||
totalPercent /= this.allTasks.size;
|
||||
splashState = {
|
||||
current: this.finished.size + 1,
|
||||
total: this.allTasks.size,
|
||||
progress: totalPercent
|
||||
};
|
||||
updateSplashState(newState);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async function updateUntilCurrent() {
|
||||
const retryOptions = {
|
||||
skip_host_delta: false,
|
||||
skip_module_delta: {}
|
||||
};
|
||||
while (true) {
|
||||
updateSplashState(CHECKING_FOR_UPDATES);
|
||||
try {
|
||||
let installedAnything = false;
|
||||
const downloads = new TaskProgress();
|
||||
const installs = new TaskProgress();
|
||||
await newUpdater.updateToLatestWithOptions(retryOptions, progress => {
|
||||
const task = progress.task;
|
||||
const downloadTask = task.HostDownload || task.ModuleDownload;
|
||||
const installTask = task.HostInstall || task.ModuleInstall;
|
||||
installedAnything = true;
|
||||
if (downloadTask != null) {
|
||||
downloads.recordProgress(progress, downloadTask);
|
||||
}
|
||||
if (installTask != null) {
|
||||
installs.recordProgress(progress, installTask);
|
||||
if (progress.state.Failed != null) {
|
||||
if (task.HostInstall != null) {
|
||||
retryOptions.skip_host_delta = true;
|
||||
} else if (task.ModuleInstall != null) {
|
||||
retryOptions.skip_module_delta[installTask.version.module.name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!downloads.updateSplashState(DOWNLOADING_UPDATES)) {
|
||||
installs.updateSplashState(INSTALLING_UPDATES);
|
||||
}
|
||||
});
|
||||
if (!installedAnything) {
|
||||
await newUpdater.startCurrentVersion();
|
||||
newUpdater.setRunningInBackground();
|
||||
newUpdater.collectGarbage();
|
||||
launchMainWindow();
|
||||
updateBackoff.succeed();
|
||||
updateSplashState(LAUNCHING);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Update failed', e);
|
||||
await new Promise(resolve => {
|
||||
const delayMs = updateBackoff.fail(resolve);
|
||||
splashState.seconds = Math.round(delayMs / 1000);
|
||||
updateSplashState(UPDATE_FAILURE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
function initOldUpdater() {
|
||||
modulesListeners = {};
|
||||
addModulesListener(CHECKING_FOR_UPDATES, () => {
|
||||
startUpdateTimeout();
|
||||
updateSplashState(CHECKING_FOR_UPDATES);
|
||||
});
|
||||
addModulesListener(UPDATE_CHECK_FINISHED, ({
|
||||
succeeded,
|
||||
updateCount,
|
||||
manualRequired
|
||||
}) => {
|
||||
stopUpdateTimeout();
|
||||
if (!succeeded) {
|
||||
scheduleUpdateCheck();
|
||||
updateSplashState(UPDATE_FAILURE);
|
||||
} else if (updateCount === 0) {
|
||||
moduleUpdater.setInBackground();
|
||||
launchMainWindow();
|
||||
updateSplashState(LAUNCHING);
|
||||
}
|
||||
});
|
||||
addModulesListener(DOWNLOADING_MODULE, ({
|
||||
name,
|
||||
current,
|
||||
total
|
||||
}) => {
|
||||
stopUpdateTimeout();
|
||||
splashState = {
|
||||
current,
|
||||
total
|
||||
};
|
||||
updateSplashState(DOWNLOADING_UPDATES);
|
||||
});
|
||||
addModulesListener(DOWNLOADING_MODULE_PROGRESS, ({
|
||||
name,
|
||||
progress
|
||||
}) => {
|
||||
splashState.progress = progress;
|
||||
updateSplashState(DOWNLOADING_UPDATES);
|
||||
});
|
||||
addModulesListener(DOWNLOADED_MODULE, ({
|
||||
name,
|
||||
current,
|
||||
total,
|
||||
succeeded
|
||||
}) => {
|
||||
delete splashState.progress;
|
||||
if (name === 'host') {
|
||||
restartRequired = true;
|
||||
}
|
||||
});
|
||||
addModulesListener(DOWNLOADING_MODULES_FINISHED, ({
|
||||
succeeded,
|
||||
failed
|
||||
}) => {
|
||||
if (failed > 0) {
|
||||
scheduleUpdateCheck();
|
||||
updateSplashState(UPDATE_FAILURE);
|
||||
} else {
|
||||
process.nextTick(() => {
|
||||
if (restartRequired) {
|
||||
moduleUpdater.quitAndInstallUpdates();
|
||||
} else {
|
||||
moduleUpdater.installPendingUpdates();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
addModulesListener(NO_PENDING_UPDATES, () => moduleUpdater.checkForUpdates());
|
||||
addModulesListener(INSTALLING_MODULE, ({
|
||||
name,
|
||||
current,
|
||||
total
|
||||
}) => {
|
||||
splashState = {
|
||||
current,
|
||||
total
|
||||
};
|
||||
updateSplashState(INSTALLING_UPDATES);
|
||||
});
|
||||
addModulesListener(INSTALLED_MODULE, ({
|
||||
name,
|
||||
current,
|
||||
total,
|
||||
succeeded
|
||||
}) => delete splashState.progress);
|
||||
addModulesListener(INSTALLING_MODULE_PROGRESS, ({
|
||||
name,
|
||||
progress
|
||||
}) => {
|
||||
splashState.progress = progress;
|
||||
updateSplashState(INSTALLING_UPDATES);
|
||||
});
|
||||
addModulesListener(INSTALLING_MODULES_FINISHED, ({
|
||||
succeeded,
|
||||
failed
|
||||
}) => moduleUpdater.checkForUpdates());
|
||||
addModulesListener(UPDATE_MANUALLY, ({
|
||||
newVersion
|
||||
}) => {
|
||||
splashState.newVersion = newVersion;
|
||||
updateSplashState(UPDATE_MANUALLY);
|
||||
});
|
||||
}
|
||||
function initSplash(startMinimized = false) {
|
||||
splashState = {};
|
||||
launchedMainWindow = false;
|
||||
updateAttempt = 0;
|
||||
newUpdater = (0, _updater.getUpdater)();
|
||||
if (newUpdater == null) {
|
||||
initOldUpdater();
|
||||
}
|
||||
launchSplashWindow(startMinimized);
|
||||
quoteCachePath = _path.default.join(paths.getUserData(), 'quotes.json');
|
||||
_ipcMain.default.on('UPDATED_QUOTES', (_event, quotes) => cacheLatestQuotes(quotes));
|
||||
}
|
||||
function destroySplash() {
|
||||
stopUpdateTimeout();
|
||||
if (splashWindow) {
|
||||
splashWindow.setSkipTaskbar(true);
|
||||
// defer the window hiding for a short moment so it gets covered by the main window
|
||||
const _nukeWindow = () => {
|
||||
if (splashWindow != null) {
|
||||
splashWindow.hide();
|
||||
splashWindow.close();
|
||||
splashWindow = null;
|
||||
}
|
||||
};
|
||||
setTimeout(_nukeWindow, 100);
|
||||
}
|
||||
}
|
||||
function addModulesListener(event, listener) {
|
||||
if (newUpdater != null) return;
|
||||
modulesListeners[event] = listener;
|
||||
moduleUpdater.events.addListener(event, listener);
|
||||
}
|
||||
function removeModulesListeners() {
|
||||
if (newUpdater != null) return;
|
||||
for (const event of Object.keys(modulesListeners)) {
|
||||
moduleUpdater.events.removeListener(event, modulesListeners[event]);
|
||||
}
|
||||
}
|
||||
function startUpdateTimeout() {
|
||||
if (!updateTimeout) {
|
||||
updateTimeout = setTimeout(() => scheduleUpdateCheck(), UPDATE_TIMEOUT_WAIT);
|
||||
}
|
||||
}
|
||||
function stopUpdateTimeout() {
|
||||
if (updateTimeout) {
|
||||
clearTimeout(updateTimeout);
|
||||
updateTimeout = null;
|
||||
}
|
||||
}
|
||||
function updateSplashState(event) {
|
||||
if (splashWindow != null && !splashWindow.isDestroyed() && !splashWindow.webContents.isDestroyed()) {
|
||||
webContentsSend(splashWindow, 'SPLASH_UPDATE_STATE', {
|
||||
status: event,
|
||||
...splashState
|
||||
});
|
||||
}
|
||||
}
|
||||
function launchSplashWindow(startMinimized) {
|
||||
const windowConfig = {
|
||||
width: LOADING_WINDOW_WIDTH,
|
||||
height: LOADING_WINDOW_HEIGHT,
|
||||
transparent: false,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
center: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
enableRemoteModule: false,
|
||||
contextIsolation: true,
|
||||
preload: _path.default.join(__dirname, 'splashScreenPreload.js')
|
||||
}
|
||||
};
|
||||
splashWindow = new _electron.BrowserWindow(windowConfig);
|
||||
|
||||
// prevent users from dropping links to navigate in splash window
|
||||
splashWindow.webContents.on('will-navigate', e => e.preventDefault());
|
||||
splashWindow.webContents.on('new-window', (e, windowURL) => {
|
||||
e.preventDefault();
|
||||
(0, _securityUtils.saferShellOpenExternal)(windowURL);
|
||||
// exit, but delay half a second because openExternal is about to fire
|
||||
// some events to things that are freed by app.quit.
|
||||
setTimeout(_electron.app.quit, 500);
|
||||
});
|
||||
if (process.platform !== 'darwin') {
|
||||
// citron note: this causes a crash on quit while the window is open on osx
|
||||
splashWindow.on('closed', () => {
|
||||
splashWindow = null;
|
||||
if (!launchedMainWindow) {
|
||||
// user has closed this window before we launched the app, so let's quit
|
||||
_electron.app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
_ipcMain.default.on('SPLASH_SCREEN_READY', () => {
|
||||
const cachedQuote = chooseCachedQuote();
|
||||
if (cachedQuote) {
|
||||
webContentsSend(splashWindow, 'SPLASH_SCREEN_QUOTE', cachedQuote);
|
||||
}
|
||||
if (splashWindow && !startMinimized) {
|
||||
splashWindow.show();
|
||||
}
|
||||
if (newUpdater != null) {
|
||||
updateUntilCurrent();
|
||||
} else {
|
||||
moduleUpdater.installPendingUpdates();
|
||||
}
|
||||
});
|
||||
_ipcMain.default.on('SPLASH_SCREEN_QUIT', () => {
|
||||
_electron.app.quit();
|
||||
});
|
||||
const splashUrl = _url.default.format({
|
||||
protocol: 'file',
|
||||
slashes: true,
|
||||
pathname: _path.default.join(__dirname, 'splash', 'index.html')
|
||||
});
|
||||
splashWindow.loadURL(splashUrl);
|
||||
}
|
||||
function launchMainWindow() {
|
||||
removeModulesListeners();
|
||||
if (!launchedMainWindow && splashWindow != null) {
|
||||
launchedMainWindow = true;
|
||||
events.emit(APP_SHOULD_LAUNCH);
|
||||
}
|
||||
}
|
||||
function scheduleUpdateCheck() {
|
||||
// TODO: can we use backoff here?
|
||||
updateAttempt += 1;
|
||||
const retryInSeconds = Math.min(updateAttempt * 10, RETRY_CAP_SECONDS);
|
||||
splashState.seconds = retryInSeconds;
|
||||
setTimeout(() => moduleUpdater.checkForUpdates(), retryInSeconds * 1000);
|
||||
}
|
||||
function focusWindow() {
|
||||
if (splashWindow != null) {
|
||||
splashWindow.focus();
|
||||
}
|
||||
}
|
||||
function pageReady() {
|
||||
destroySplash();
|
||||
process.nextTick(() => events.emit(APP_SHOULD_SHOW));
|
||||
}
|
||||
function cacheLatestQuotes(quotes) {
|
||||
_fs.default.writeFile(quoteCachePath, JSON.stringify(quotes), e => {
|
||||
if (e) {
|
||||
console.warn('Failed updating quote cache with error: ', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
function chooseCachedQuote() {
|
||||
let cachedQuote = null;
|
||||
try {
|
||||
const cachedQuotes = JSON.parse(_fs.default.readFileSync(quoteCachePath));
|
||||
cachedQuote = cachedQuotes[Math.floor(Math.random() * cachedQuotes.length)];
|
||||
} catch (_err) {}
|
||||
return cachedQuote;
|
||||
}
|
33
.resources/app/app_bootstrap/splashScreenPreload.js
Normal file
33
.resources/app/app_bootstrap/splashScreenPreload.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
app,
|
||||
contextBridge,
|
||||
ipcRenderer
|
||||
} = require('electron');
|
||||
const {
|
||||
saferShellOpenExternal
|
||||
} = require('../common/securityUtils');
|
||||
contextBridge.exposeInMainWorld('DiscordSplash', {
|
||||
getReleaseChannel: () => {
|
||||
const buildInfo = require('./buildInfo');
|
||||
return buildInfo.releaseChannel;
|
||||
},
|
||||
signalReady: () => {
|
||||
ipcRenderer.send('DISCORD_SPLASH_SCREEN_READY');
|
||||
},
|
||||
onStateUpdate: callback => {
|
||||
ipcRenderer.on('DISCORD_SPLASH_UPDATE_STATE', (_, state) => {
|
||||
callback(state);
|
||||
});
|
||||
},
|
||||
onQuoteUpdate: callback => {
|
||||
ipcRenderer.on('DISCORD_SPLASH_SCREEN_QUOTE', (_, quote) => {
|
||||
callback(quote);
|
||||
});
|
||||
},
|
||||
openUrl: saferShellOpenExternal,
|
||||
quitDiscord: () => {
|
||||
ipcRenderer.send('DISCORD_SPLASH_SCREEN_QUIT');
|
||||
}
|
||||
});
|
189
.resources/app/app_bootstrap/squirrelUpdate.js
Normal file
189
.resources/app/app_bootstrap/squirrelUpdate.js
Normal file
|
@ -0,0 +1,189 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.handleStartupEvent = handleStartupEvent;
|
||||
exports.installProtocol = installProtocol;
|
||||
exports.restart = restart;
|
||||
exports.spawnUpdate = spawnUpdate;
|
||||
exports.spawnUpdateInstall = spawnUpdateInstall;
|
||||
exports.updateExistsSync = updateExistsSync;
|
||||
var _child_process = _interopRequireDefault(require("child_process"));
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var autoStart = _interopRequireWildcard(require("./autoStart"));
|
||||
var windowsUtils = _interopRequireWildcard(require("./windowsUtils"));
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
// citron note: this assumes the execPath is in the format Discord/someVersion/Discord.exe
|
||||
const appFolder = _path.default.resolve(process.execPath, '..');
|
||||
const rootFolder = _path.default.resolve(appFolder, '..');
|
||||
const exeName = _path.default.basename(process.execPath);
|
||||
const updateExe = _path.default.join(rootFolder, 'Update.exe');
|
||||
|
||||
// Specialized spawn function specifically used for spawning the updater in
|
||||
// update mode. Calls back with progress percentages.
|
||||
// Returns Promise.
|
||||
function spawnUpdateInstall(updateUrl, progressCallback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = _child_process.default.spawn(updateExe, ['--update', updateUrl]);
|
||||
proc.on('error', reject);
|
||||
proc.on('exit', code => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Update failed with exit code ${code}`));
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
let lastProgress = -1;
|
||||
function parseProgress() {
|
||||
const lines = stdout.split(/\r?\n/);
|
||||
if (lines.length === 1) return;
|
||||
// return the last (possibly incomplete) line to stdout for parsing again
|
||||
stdout = lines.pop();
|
||||
let currentProgress;
|
||||
for (const line of lines) {
|
||||
if (!/^\d\d?$/.test(line)) continue;
|
||||
const progress = Number(line);
|
||||
// make sure that this number is steadily increasing
|
||||
if (lastProgress > progress) continue;
|
||||
currentProgress = progress;
|
||||
}
|
||||
if (currentProgress == null) return;
|
||||
lastProgress = currentProgress;
|
||||
progressCallback(Math.min(currentProgress, 100));
|
||||
}
|
||||
let stdout = '';
|
||||
proc.stdout.on('data', chunk => {
|
||||
stdout += String(chunk);
|
||||
parseProgress();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Spawn the Update.exe with the given arguments and invoke the callback when
|
||||
// the command completes.
|
||||
function spawnUpdate(args, callback) {
|
||||
windowsUtils.spawn(updateExe, args, callback);
|
||||
}
|
||||
|
||||
// Create a desktop and start menu shortcut by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
function createShortcuts(callback, updateOnly) {
|
||||
// move icon out to a more stable location, to keep shortcuts from breaking as much
|
||||
const icoSrc = _path.default.join(appFolder, 'app.ico');
|
||||
const icoDest = _path.default.join(rootFolder, 'app.ico');
|
||||
let icoForTarget = icoDest;
|
||||
try {
|
||||
const ico = _fs.default.readFileSync(icoSrc);
|
||||
_fs.default.writeFileSync(icoDest, ico);
|
||||
} catch (e) {
|
||||
// if we can't write there for some reason, just use the source.
|
||||
icoForTarget = icoSrc;
|
||||
}
|
||||
const createShortcutArgs = ['--createShortcut', exeName, '--setupIcon', icoForTarget];
|
||||
if (updateOnly) {
|
||||
createShortcutArgs.push('--updateOnly');
|
||||
}
|
||||
spawnUpdate(createShortcutArgs, callback);
|
||||
}
|
||||
|
||||
// Add a protocol registration for this application.
|
||||
function installProtocol(protocol, callback) {
|
||||
const queue = [['HKCU\\Software\\Classes\\' + protocol, '/ve', '/d', `URL:${protocol} Protocol`], ['HKCU\\Software\\Classes\\' + protocol, '/v', 'URL Protocol'], ['HKCU\\Software\\Classes\\' + protocol + '\\DefaultIcon', '/ve', '/d', '"' + process.execPath + '",-1'], ['HKCU\\Software\\Classes\\' + protocol + '\\shell\\open\\command', '/ve', '/d', `"${process.execPath}" --url -- "%1"`]];
|
||||
windowsUtils.addToRegistry(queue, callback);
|
||||
}
|
||||
function terminate(app) {
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Remove the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
function removeShortcuts(callback) {
|
||||
spawnUpdate(['--removeShortcut', exeName], callback);
|
||||
}
|
||||
|
||||
// Update the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
function updateShortcuts(callback) {
|
||||
createShortcuts(callback, true);
|
||||
}
|
||||
|
||||
// Purge the protocol for this applicationstart.
|
||||
function uninstallProtocol(protocol, callback) {
|
||||
windowsUtils.spawnReg(['delete', 'HKCU\\Software\\Classes\\' + protocol, '/f'], callback);
|
||||
}
|
||||
function maybeInstallNewUpdaterSeedDb() {
|
||||
const installerDbSrc = _path.default.join(appFolder, 'installer.db');
|
||||
const installerDbDest = _path.default.join(rootFolder, 'installer.db');
|
||||
if (_fs.default.existsSync(installerDbSrc)) {
|
||||
_fs.default.renameSync(installerDbSrc, installerDbDest);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
// returns `true` if regular startup should be prevented
|
||||
function handleStartupEvent(protocol, app, squirrelCommand) {
|
||||
switch (squirrelCommand) {
|
||||
case '--squirrel-install':
|
||||
createShortcuts(() => {
|
||||
autoStart.install(() => {
|
||||
installProtocol(protocol, () => {
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
}, false);
|
||||
return true;
|
||||
case '--squirrel-updated':
|
||||
updateShortcuts(() => {
|
||||
autoStart.update(() => {
|
||||
installProtocol(protocol, () => {
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
});
|
||||
return true;
|
||||
case '--squirrel-uninstall':
|
||||
removeShortcuts(() => {
|
||||
autoStart.uninstall(() => {
|
||||
uninstallProtocol(protocol, () => {
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
});
|
||||
return true;
|
||||
case '--squirrel-obsolete':
|
||||
terminate(app);
|
||||
return true;
|
||||
case '--squirrel-firstrun':
|
||||
// Squirrel doesn't have a way to include app-level files. We get around
|
||||
// this for new updater hosts, which rely on a seeded manifest, by
|
||||
// bubbling the db up from the versioned-app directory if it exists.
|
||||
//
|
||||
// Additionally, we run this in --squirrel-firstrun, not
|
||||
// --squirrel-install, because the latter is unreliable with unicode
|
||||
// paths. yay!
|
||||
maybeInstallNewUpdaterSeedDb();
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Are we using Squirrel for updates?
|
||||
function updateExistsSync() {
|
||||
return _fs.default.existsSync(updateExe);
|
||||
}
|
||||
|
||||
// Restart app as the new version
|
||||
function restart(app, newVersion) {
|
||||
app.once('will-quit', () => {
|
||||
const execPath = _path.default.resolve(rootFolder, `app-${newVersion}/${exeName}`);
|
||||
_child_process.default.spawn(execPath, [], {
|
||||
detached: true
|
||||
});
|
||||
});
|
||||
app.quit();
|
||||
}
|
17
.resources/app/app_bootstrap/startupMenu/darwin.js
Normal file
17
.resources/app/app_bootstrap/startupMenu/darwin.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _default = [{
|
||||
label: 'Discord',
|
||||
submenu: [{
|
||||
label: 'Quit',
|
||||
click: () => _electron.app.quit(),
|
||||
accelerator: 'Command+Q'
|
||||
}]
|
||||
}];
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
11
.resources/app/app_bootstrap/startupMenu/index.js
Normal file
11
.resources/app/app_bootstrap/startupMenu/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
const menu = require('./' + process.platform);
|
||||
var _default = _electron.Menu.buildFromTemplate(menu);
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
17
.resources/app/app_bootstrap/startupMenu/linux.js
Normal file
17
.resources/app/app_bootstrap/startupMenu/linux.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _default = [{
|
||||
label: '&File',
|
||||
submenu: [{
|
||||
label: '&Exit',
|
||||
click: () => _electron.app.quit(),
|
||||
accelerator: 'Control+Q'
|
||||
}]
|
||||
}];
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
17
.resources/app/app_bootstrap/startupMenu/win32.js
Normal file
17
.resources/app/app_bootstrap/startupMenu/win32.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _electron = require("electron");
|
||||
var _default = [{
|
||||
label: '&File',
|
||||
submenu: [{
|
||||
label: '&Exit',
|
||||
click: () => _electron.app.quit(),
|
||||
accelerator: 'Alt+F4'
|
||||
}]
|
||||
}];
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
BIN
.resources/app/app_bootstrap/videos/connecting.webm
Normal file
BIN
.resources/app/app_bootstrap/videos/connecting.webm
Normal file
Binary file not shown.
BIN
.resources/app/app_bootstrap/videos/connecting_halloween.webm
Executable file
BIN
.resources/app/app_bootstrap/videos/connecting_halloween.webm
Executable file
Binary file not shown.
76
.resources/app/app_bootstrap/windowsUtils.js
Normal file
76
.resources/app/app_bootstrap/windowsUtils.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.addToRegistry = addToRegistry;
|
||||
exports.spawn = spawn;
|
||||
exports.spawnReg = spawnReg;
|
||||
var _child_process = _interopRequireDefault(require("child_process"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const regExe = process.env.SystemRoot ? _path.default.join(process.env.SystemRoot, 'System32', 'reg.exe') : 'reg.exe';
|
||||
|
||||
// Spawn a command and invoke the callback when it completes with an error
|
||||
// and the output from standard out.
|
||||
function spawn(command, args, callback) {
|
||||
let stdout = '';
|
||||
let spawnedProcess;
|
||||
try {
|
||||
// TODO: contrary to below, it should not throw any error
|
||||
spawnedProcess = _child_process.default.spawn(command, args);
|
||||
} catch (err) {
|
||||
// Spawn can throw an error
|
||||
process.nextTick(() => {
|
||||
if (callback != null) {
|
||||
callback(err, stdout);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: we need to specify the encoding for the data if we're going to concat it as a string
|
||||
spawnedProcess.stdout.on('data', data => {
|
||||
stdout += data;
|
||||
});
|
||||
let err = null;
|
||||
// TODO: close event might not get called, we should
|
||||
// callback on error https://nodejs.org/api/child_process.html#child_process_event_error
|
||||
spawnedProcess.on('error', err => {
|
||||
// TODO: there should always be an error
|
||||
if (err != null) {
|
||||
err = err;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: don't listen to close, but listen to exit instead
|
||||
spawnedProcess.on('close', (code, signal) => {
|
||||
if (err === null && code !== 0) {
|
||||
err = new Error('Command failed: ' + (signal || code));
|
||||
}
|
||||
if (err != null) {
|
||||
err.code = err.code || code;
|
||||
err.stdout = err.stdout || stdout;
|
||||
}
|
||||
if (callback != null) {
|
||||
callback(err, stdout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Spawn reg.exe and callback when it completes
|
||||
function spawnReg(args, callback) {
|
||||
return spawn(regExe, args, callback);
|
||||
}
|
||||
|
||||
// TODO: since we're doing this one by one, we could have a more graceful way of processing the queue
|
||||
// rather than mutating the array
|
||||
function addToRegistry(queue, callback) {
|
||||
if (queue.length === 0) {
|
||||
return callback && callback();
|
||||
}
|
||||
const args = queue.shift();
|
||||
args.unshift('add');
|
||||
args.push('/f');
|
||||
return spawnReg(args, () => addToRegistry(queue, callback));
|
||||
}
|
90
.resources/app/common/Backoff.js
Normal file
90
.resources/app/common/Backoff.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
// copied from discord_app/lib because including from there is broken.
|
||||
|
||||
class Backoff {
|
||||
/**
|
||||
* Create a backoff instance can automatically backoff retries.
|
||||
*/
|
||||
constructor(min = 500, max = null, jitter = true) {
|
||||
this.min = min;
|
||||
this.max = max != null ? max : min * 10;
|
||||
this.jitter = jitter;
|
||||
this._current = min;
|
||||
this._timeoutId = null;
|
||||
this._fails = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of failures.
|
||||
*/
|
||||
get fails() {
|
||||
return this._fails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current backoff value in milliseconds.
|
||||
*/
|
||||
get current() {
|
||||
return this._current;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback is going to fire.
|
||||
*/
|
||||
get pending() {
|
||||
return this._timeoutId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any pending callbacks and reset the backoff.
|
||||
*/
|
||||
succeed() {
|
||||
this.cancel();
|
||||
this._fails = 0;
|
||||
this._current = this.min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the backoff and schedule a callback if provided.
|
||||
*/
|
||||
fail(callback) {
|
||||
this._fails += 1;
|
||||
let delay = this._current * 2;
|
||||
if (this.jitter) {
|
||||
delay *= Math.random();
|
||||
}
|
||||
this._current = Math.min(this._current + delay, this.max);
|
||||
if (callback != null) {
|
||||
if (this._timeoutId != null) {
|
||||
throw new Error('callback already pending');
|
||||
}
|
||||
this._timeoutId = setTimeout(() => {
|
||||
try {
|
||||
if (callback != null) {
|
||||
callback();
|
||||
}
|
||||
} finally {
|
||||
this._timeoutId = null;
|
||||
}
|
||||
}, this._current);
|
||||
}
|
||||
return this._current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any pending callbacks.
|
||||
*/
|
||||
cancel() {
|
||||
if (this._timeoutId != null) {
|
||||
clearTimeout(this._timeoutId);
|
||||
this._timeoutId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = Backoff;
|
||||
module.exports = exports.default;
|
26
.resources/app/common/FeatureFlags.js
Normal file
26
.resources/app/common/FeatureFlags.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
class FeatureFlags {
|
||||
constructor() {
|
||||
this.flags = new Set();
|
||||
}
|
||||
getSupported() {
|
||||
return Array.from(this.flags);
|
||||
}
|
||||
supports(feature) {
|
||||
return this.flags.has(feature);
|
||||
}
|
||||
declareSupported(feature) {
|
||||
if (this.supports(feature)) {
|
||||
console.error('Feature redeclared; is this a duplicate flag? ', feature);
|
||||
return;
|
||||
}
|
||||
this.flags.add(feature);
|
||||
}
|
||||
}
|
||||
exports.default = FeatureFlags;
|
||||
module.exports = exports.default;
|
58
.resources/app/common/Settings.js
Normal file
58
.resources/app/common/Settings.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
// TODO: sync fs operations could cause slowdown and/or freezes, depending on usage
|
||||
// if this is fine, remove this todo
|
||||
class Settings {
|
||||
constructor(root) {
|
||||
this.path = _path.default.join(root, 'settings.json');
|
||||
try {
|
||||
this.lastSaved = _fs.default.readFileSync(this.path);
|
||||
this.settings = JSON.parse(this.lastSaved);
|
||||
} catch (e) {
|
||||
this.lastSaved = '';
|
||||
this.settings = {};
|
||||
}
|
||||
this.lastModified = this._lastModified();
|
||||
}
|
||||
_lastModified() {
|
||||
try {
|
||||
return _fs.default.statSync(this.path).mtime.getTime();
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
get(key, defaultValue = false) {
|
||||
if (this.settings.hasOwnProperty(key)) {
|
||||
return this.settings[key];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
set(key, value) {
|
||||
this.settings[key] = value;
|
||||
}
|
||||
save() {
|
||||
if (this.lastModified && this.lastModified !== this._lastModified()) {
|
||||
console.warn('Not saving settings, it has been externally modified.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const toSave = JSON.stringify(this.settings, null, 2);
|
||||
if (this.lastSaved != toSave) {
|
||||
this.lastSaved = toSave;
|
||||
_fs.default.writeFileSync(this.path, toSave);
|
||||
this.lastModified = this._lastModified();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed saving settings with error: ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = Settings;
|
||||
module.exports = exports.default;
|
101
.resources/app/common/crashReporterSetup.js
Normal file
101
.resources/app/common/crashReporterSetup.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.init = init;
|
||||
exports.isInitialized = isInitialized;
|
||||
exports.metadata = void 0;
|
||||
var processUtils = _interopRequireWildcard(require("./processUtils"));
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
// @ts-nocheck
|
||||
/* eslint-disable */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const electron = require('electron');
|
||||
const childProcess = require('child_process');
|
||||
const {
|
||||
flatten
|
||||
} = require('./crashReporterUtils');
|
||||
let initialized = false;
|
||||
const metadata = {};
|
||||
exports.metadata = metadata;
|
||||
const supportsTls13 = processUtils.supportsTls13();
|
||||
const DEFAULT_SENTRY_KEY = '384ce4413de74fe0be270abe03b2b35a';
|
||||
const TEST_SENTRY_KEY = '1a27a96457b24ff286a000266c573919';
|
||||
const CHANNEL_SENTRY_KEYS = {
|
||||
stable: DEFAULT_SENTRY_KEY,
|
||||
ptb: TEST_SENTRY_KEY,
|
||||
canary: TEST_SENTRY_KEY,
|
||||
development: TEST_SENTRY_KEY
|
||||
};
|
||||
function getCrashReporterArgs(metadata) {
|
||||
// NB: we need to flatten the metadata because modern electron caps metadata values at 127 bytes,
|
||||
// which our sentry subobject can easily exceed.
|
||||
const flatMetadata = flatten(metadata);
|
||||
const channel = metadata['channel'];
|
||||
const sentryKey = CHANNEL_SENTRY_KEYS[channel] != null ? CHANNEL_SENTRY_KEYS[channel] : DEFAULT_SENTRY_KEY;
|
||||
const sentryHost = supportsTls13 ? 'sentry.io' : 'insecure.sentry.io';
|
||||
return {
|
||||
productName: 'Discord',
|
||||
companyName: 'Discord Inc.',
|
||||
submitURL: `https://${sentryHost}/api/146342/minidump/?sentry_key=${sentryKey}`,
|
||||
uploadToServer: true,
|
||||
ignoreSystemCrashHandler: false,
|
||||
extra: flatMetadata
|
||||
};
|
||||
}
|
||||
function initializeSentrySdk(sentry) {
|
||||
const sentryDsn = supportsTls13 ? 'https://8405981abe5045908f0d88135eba7ba5@o64374.ingest.sentry.io/1197903' : 'https://8405981abe5045908f0d88135eba7ba5@o64374.insecure.sentry.io/1197903';
|
||||
sentry.init({
|
||||
dsn: sentryDsn,
|
||||
beforeSend(event) {
|
||||
var _metadata$sentry, _metadata$sentry2;
|
||||
// Currently beforeSend is only fired for discord-desktop-js project,
|
||||
// due to outdated sentry/electron sdk
|
||||
event.release = metadata === null || metadata === void 0 ? void 0 : (_metadata$sentry = metadata['sentry']) === null || _metadata$sentry === void 0 ? void 0 : _metadata$sentry['release'];
|
||||
event.environment = metadata === null || metadata === void 0 ? void 0 : (_metadata$sentry2 = metadata['sentry']) === null || _metadata$sentry2 === void 0 ? void 0 : _metadata$sentry2['environment'];
|
||||
return event;
|
||||
}
|
||||
});
|
||||
}
|
||||
function init(buildInfo, sentry) {
|
||||
if (initialized) {
|
||||
console.warn('Ignoring double initialization of crash reporter.');
|
||||
return;
|
||||
}
|
||||
|
||||
// It's desirable for test runs to have the stacktrace print to the console (and thusly, be shown in buildkite logs).
|
||||
if (process.env.ELECTRON_ENABLE_STACK_DUMPING === 'true') {
|
||||
console.warn('Not initializing crash reporter because ELECTRON_ENABLE_STACK_DUMPING is set.');
|
||||
return;
|
||||
}
|
||||
if (sentry != null) {
|
||||
initializeSentrySdk(sentry);
|
||||
}
|
||||
metadata['channel'] = buildInfo.releaseChannel;
|
||||
const sentryMetadata = metadata['sentry'] != null ? metadata['sentry'] : {};
|
||||
sentryMetadata['environment'] = buildInfo.releaseChannel;
|
||||
sentryMetadata['release'] = buildInfo.version;
|
||||
metadata['sentry'] = sentryMetadata;
|
||||
if (processUtils.IS_LINUX) {
|
||||
const XDG_CURRENT_DESKTOP = process.env.XDG_CURRENT_DESKTOP || 'unknown';
|
||||
const GDMSESSION = process.env.GDMSESSION || 'unknown';
|
||||
metadata['wm'] = `${XDG_CURRENT_DESKTOP},${GDMSESSION}`;
|
||||
try {
|
||||
metadata['distro'] = childProcess.execFileSync('lsb_release', ['-ds'], {
|
||||
timeout: 100,
|
||||
maxBuffer: 512,
|
||||
encoding: 'utf-8'
|
||||
}).trim();
|
||||
} catch (_) {} // just in case lsb_release doesn't exist
|
||||
}
|
||||
|
||||
const config = getCrashReporterArgs(metadata);
|
||||
electron.crashReporter.start(config);
|
||||
initialized = true;
|
||||
}
|
||||
function isInitialized() {
|
||||
return initialized;
|
||||
}
|
42
.resources/app/common/crashReporterUtils.js
Normal file
42
.resources/app/common/crashReporterUtils.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.flatten = flatten;
|
||||
exports.reconcileCrashReporterMetadata = reconcileCrashReporterMetadata;
|
||||
// @ts-nocheck
|
||||
/* eslint-disable */
|
||||
const {
|
||||
getElectronMajorVersion
|
||||
} = require('./processUtils');
|
||||
function flatten(metadata, prefix, root) {
|
||||
root = root ? root : {};
|
||||
prefix = prefix ? prefix : '';
|
||||
if (typeof metadata === 'object') {
|
||||
for (const key in metadata) {
|
||||
const next_prefix = prefix === '' ? key : `${prefix}[${key}]`;
|
||||
flatten(metadata[key], next_prefix, root);
|
||||
}
|
||||
} else {
|
||||
root[prefix] = metadata;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
function reconcileCrashReporterMetadata(crashReporter, metadata) {
|
||||
if (getElectronMajorVersion() < 8) {
|
||||
return;
|
||||
}
|
||||
const new_metadata = flatten(metadata);
|
||||
const old_metadata = crashReporter.getParameters();
|
||||
for (const key in old_metadata) {
|
||||
if (!new_metadata.hasOwnProperty(key)) {
|
||||
crashReporter.removeExtraParameter(key);
|
||||
}
|
||||
}
|
||||
for (const key in new_metadata) {
|
||||
if (!old_metadata.hasOwnProperty(key)) {
|
||||
crashReporter.addExtraParameter(key, String(new_metadata[key]));
|
||||
}
|
||||
}
|
||||
}
|
859
.resources/app/common/moduleUpdater.js
Normal file
859
.resources/app/common/moduleUpdater.js
Normal file
|
@ -0,0 +1,859 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.UPDATE_MANUALLY = exports.UPDATE_CHECK_FINISHED = exports.NO_PENDING_UPDATES = exports.INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULES_FINISHED = exports.INSTALLING_MODULE = exports.INSTALLED_MODULE = exports.DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULE = exports.DOWNLOADED_MODULE = exports.CHECKING_FOR_UPDATES = void 0;
|
||||
exports.checkForUpdates = checkForUpdates;
|
||||
exports.events = void 0;
|
||||
exports.getInstalled = getInstalled;
|
||||
exports.init = init;
|
||||
exports.initPathsOnly = initPathsOnly;
|
||||
exports.install = install;
|
||||
exports.installPendingUpdates = installPendingUpdates;
|
||||
exports.isInstalled = isInstalled;
|
||||
exports.quitAndInstallUpdates = quitAndInstallUpdates;
|
||||
exports.setInBackground = setInBackground;
|
||||
exports.supportsEventObjects = void 0;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _nodeGlobalPaths = require("./nodeGlobalPaths");
|
||||
var _events = require("events");
|
||||
var _mkdirp = _interopRequireDefault(require("mkdirp"));
|
||||
var _process = require("process");
|
||||
var _yauzl = _interopRequireDefault(require("yauzl"));
|
||||
var _Backoff = _interopRequireDefault(require("./Backoff"));
|
||||
var paths = _interopRequireWildcard(require("./paths"));
|
||||
var _processUtils = require("./processUtils");
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
// Manages additional module installation and management.
|
||||
// We add the module folder path to require() lookup paths here.
|
||||
|
||||
// undocumented node API
|
||||
|
||||
const originalFs = require('original-fs');
|
||||
|
||||
// events
|
||||
const CHECKING_FOR_UPDATES = 'checking-for-updates';
|
||||
exports.CHECKING_FOR_UPDATES = CHECKING_FOR_UPDATES;
|
||||
const INSTALLED_MODULE = 'installed-module';
|
||||
exports.INSTALLED_MODULE = INSTALLED_MODULE;
|
||||
const UPDATE_CHECK_FINISHED = 'update-check-finished';
|
||||
exports.UPDATE_CHECK_FINISHED = UPDATE_CHECK_FINISHED;
|
||||
const DOWNLOADING_MODULE = 'downloading-module';
|
||||
exports.DOWNLOADING_MODULE = DOWNLOADING_MODULE;
|
||||
const DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress';
|
||||
exports.DOWNLOADING_MODULE_PROGRESS = DOWNLOADING_MODULE_PROGRESS;
|
||||
const DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished';
|
||||
exports.DOWNLOADING_MODULES_FINISHED = DOWNLOADING_MODULES_FINISHED;
|
||||
const UPDATE_MANUALLY = 'update-manually';
|
||||
exports.UPDATE_MANUALLY = UPDATE_MANUALLY;
|
||||
const DOWNLOADED_MODULE = 'downloaded-module';
|
||||
exports.DOWNLOADED_MODULE = DOWNLOADED_MODULE;
|
||||
const INSTALLING_MODULES_FINISHED = 'installing-modules-finished';
|
||||
exports.INSTALLING_MODULES_FINISHED = INSTALLING_MODULES_FINISHED;
|
||||
const INSTALLING_MODULE = 'installing-module';
|
||||
exports.INSTALLING_MODULE = INSTALLING_MODULE;
|
||||
const INSTALLING_MODULE_PROGRESS = 'installing-module-progress';
|
||||
exports.INSTALLING_MODULE_PROGRESS = INSTALLING_MODULE_PROGRESS;
|
||||
const NO_PENDING_UPDATES = 'no-pending-updates';
|
||||
|
||||
// settings
|
||||
exports.NO_PENDING_UPDATES = NO_PENDING_UPDATES;
|
||||
const ALWAYS_ALLOW_UPDATES = 'ALWAYS_ALLOW_UPDATES';
|
||||
const SKIP_HOST_UPDATE = 'SKIP_HOST_UPDATE';
|
||||
const SKIP_MODULE_UPDATE = 'SKIP_MODULE_UPDATE';
|
||||
const ALWAYS_BOOTSTRAP_MODULES = 'ALWAYS_BOOTSTRAP_MODULES';
|
||||
const USE_LOCAL_MODULE_VERSIONS = 'USE_LOCAL_MODULE_VERSIONS';
|
||||
class Events extends _events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.history = [];
|
||||
}
|
||||
append(evt) {
|
||||
evt.now = String(_process.hrtime.bigint());
|
||||
if (this._eventIsInteresting(evt)) {
|
||||
this.history.push(evt);
|
||||
}
|
||||
process.nextTick(() => this.emit(evt.type, evt));
|
||||
}
|
||||
_eventIsInteresting(evt) {
|
||||
return evt.type !== DOWNLOADING_MODULE_PROGRESS && evt.type !== INSTALLING_MODULE_PROGRESS;
|
||||
}
|
||||
}
|
||||
class LogStream {
|
||||
constructor(logPath) {
|
||||
try {
|
||||
this.logStream = _fs.default.createWriteStream(logPath, {
|
||||
flags: 'a'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to create ${logPath}: ${String(e)}`);
|
||||
}
|
||||
}
|
||||
log(message) {
|
||||
message = `${new Date().toISOString()} [Modules] ${message}`;
|
||||
console.log(message);
|
||||
if (this.logStream) {
|
||||
this.logStream.write(message);
|
||||
this.logStream.write('\r\n');
|
||||
}
|
||||
}
|
||||
end() {
|
||||
if (this.logStream) {
|
||||
this.logStream.end();
|
||||
this.logStream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
const request = require('../app_bootstrap/request');
|
||||
const {
|
||||
app
|
||||
} = require('electron');
|
||||
const REQUEST_TIMEOUT = 15000;
|
||||
const backoff = new _Backoff.default(1000, 20000);
|
||||
const events = new Events();
|
||||
exports.events = events;
|
||||
const supportsEventObjects = true;
|
||||
exports.supportsEventObjects = supportsEventObjects;
|
||||
let logger;
|
||||
let locallyInstalledModules;
|
||||
let moduleInstallPath;
|
||||
let installedModulesFilePath;
|
||||
let moduleDownloadPath;
|
||||
let bootstrapping;
|
||||
let hostUpdater;
|
||||
let hostUpdateAvailable;
|
||||
let skipHostUpdate;
|
||||
let skipModuleUpdate;
|
||||
let checkingForUpdates;
|
||||
let remoteBaseURL;
|
||||
let remoteQuery;
|
||||
let settings;
|
||||
let remoteModuleVersions;
|
||||
let installedModules;
|
||||
let download;
|
||||
let unzip;
|
||||
let newInstallInProgress;
|
||||
let localModuleVersionsFilePath;
|
||||
let updatable;
|
||||
let bootstrapManifestFilePath;
|
||||
let runningInBackground = false;
|
||||
function initPathsOnly(_buildInfo) {
|
||||
if (locallyInstalledModules || moduleInstallPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have `localModulesRoot` in our buildInfo file, we do not fetch modules
|
||||
// from remote, and rely on our locally bundled ones.
|
||||
// Typically used for development mode, or private builds.
|
||||
locallyInstalledModules = _buildInfo.localModulesRoot != null;
|
||||
if (locallyInstalledModules) {
|
||||
(0, _nodeGlobalPaths.addGlobalPath)(_buildInfo.localModulesRoot);
|
||||
} else {
|
||||
moduleInstallPath = _path.default.join(paths.getUserDataVersioned(), 'modules');
|
||||
(0, _nodeGlobalPaths.addGlobalPath)(moduleInstallPath);
|
||||
}
|
||||
}
|
||||
function init(_endpoint, _settings, _buildInfo) {
|
||||
const endpoint = _endpoint;
|
||||
settings = _settings;
|
||||
const buildInfo = _buildInfo;
|
||||
updatable = buildInfo.version != '0.0.0' && !buildInfo.debug || settings.get(ALWAYS_ALLOW_UPDATES);
|
||||
initPathsOnly(buildInfo);
|
||||
logger = new LogStream(_path.default.join(paths.getUserData(), 'modules.log'));
|
||||
bootstrapping = false;
|
||||
hostUpdateAvailable = false;
|
||||
checkingForUpdates = false;
|
||||
skipHostUpdate = settings.get(SKIP_HOST_UPDATE) || !updatable;
|
||||
skipModuleUpdate = settings.get(SKIP_MODULE_UPDATE) || locallyInstalledModules || !updatable;
|
||||
localModuleVersionsFilePath = _path.default.join(paths.getUserData(), 'local_module_versions.json');
|
||||
bootstrapManifestFilePath = _path.default.join(paths.getResources(), 'bootstrap', 'manifest.json');
|
||||
installedModules = {};
|
||||
remoteModuleVersions = {};
|
||||
newInstallInProgress = {};
|
||||
download = {
|
||||
// currently downloading
|
||||
active: false,
|
||||
// {name, version}
|
||||
queue: [],
|
||||
// current queue index being downloaded
|
||||
next: 0,
|
||||
// download failure count
|
||||
failures: 0
|
||||
};
|
||||
unzip = {
|
||||
// currently unzipping
|
||||
active: false,
|
||||
// {name, version, zipfile}
|
||||
queue: [],
|
||||
// current queue index being unzipped
|
||||
next: 0,
|
||||
// unzip failure count
|
||||
failures: 0
|
||||
};
|
||||
logger.log(`Modules initializing`);
|
||||
logger.log(`Distribution: ${locallyInstalledModules ? 'local' : 'remote'}`);
|
||||
logger.log(`Host updates: ${skipHostUpdate ? 'disabled' : 'enabled'}`);
|
||||
logger.log(`Module updates: ${skipModuleUpdate ? 'disabled' : 'enabled'}`);
|
||||
if (!locallyInstalledModules) {
|
||||
installedModulesFilePath = _path.default.join(moduleInstallPath, 'installed.json');
|
||||
moduleDownloadPath = _path.default.join(moduleInstallPath, 'pending');
|
||||
_mkdirp.default.sync(moduleDownloadPath);
|
||||
logger.log(`Module install path: ${moduleInstallPath}`);
|
||||
logger.log(`Module installed file path: ${installedModulesFilePath}`);
|
||||
logger.log(`Module download path: ${moduleDownloadPath}`);
|
||||
let failedLoadingInstalledModules = false;
|
||||
try {
|
||||
installedModules = JSON.parse(_fs.default.readFileSync(installedModulesFilePath));
|
||||
} catch (err) {
|
||||
failedLoadingInstalledModules = true;
|
||||
}
|
||||
cleanDownloadedModules(installedModules);
|
||||
bootstrapping = failedLoadingInstalledModules || settings.get(ALWAYS_BOOTSTRAP_MODULES);
|
||||
}
|
||||
hostUpdater = require('../app_bootstrap/hostUpdater');
|
||||
// TODO: hostUpdater constants
|
||||
hostUpdater.on('checking-for-update', () => events.append({
|
||||
type: CHECKING_FOR_UPDATES
|
||||
}));
|
||||
hostUpdater.on('update-available', () => hostOnUpdateAvailable());
|
||||
hostUpdater.on('update-progress', progress => hostOnUpdateProgress(progress));
|
||||
hostUpdater.on('update-not-available', () => hostOnUpdateNotAvailable());
|
||||
hostUpdater.on('update-manually', newVersion => hostOnUpdateManually(newVersion));
|
||||
hostUpdater.on('update-downloaded', () => hostOnUpdateDownloaded());
|
||||
hostUpdater.on('error', err => hostOnError(err));
|
||||
const setFeedURL = hostUpdater.setFeedURL.bind(hostUpdater);
|
||||
remoteBaseURL = `${endpoint}/modules/${buildInfo.releaseChannel}`;
|
||||
// eslint-disable-next-line camelcase
|
||||
remoteQuery = {
|
||||
host_version: buildInfo.version
|
||||
};
|
||||
|
||||
// For OSX platform try to move installer to Application folder, if currently running
|
||||
// from read-only volume to avoid installation problems.
|
||||
if (_processUtils.IS_OSX) {
|
||||
const appFolder = _path.default.resolve(process.execPath);
|
||||
_fs.default.access(appFolder, _fs.default.constants.W_OK, err => {
|
||||
if (err) {
|
||||
logger.log(`Installer is in read-only volume in OSX, moving to Application folder ${err}`);
|
||||
try {
|
||||
// On a successful move the app will quit and relaunch.
|
||||
app.moveToApplicationsFolder();
|
||||
} catch (err) {
|
||||
logger.log(`Could not move installer file to Application folder: ${err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}?platform=osx&version=${buildInfo.version}`);
|
||||
remoteQuery.platform = 'osx';
|
||||
break;
|
||||
case 'win32':
|
||||
// Squirrel for Windows can't handle query params
|
||||
// https://github.com/Squirrel/Squirrel.Windows/issues/132
|
||||
setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}`);
|
||||
remoteQuery.platform = 'win';
|
||||
break;
|
||||
case 'linux':
|
||||
setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}?platform=linux&version=${buildInfo.version}`);
|
||||
remoteQuery.platform = 'linux';
|
||||
break;
|
||||
}
|
||||
}
|
||||
function cleanDownloadedModules(installedModules) {
|
||||
try {
|
||||
const entries = _fs.default.readdirSync(moduleDownloadPath) || [];
|
||||
entries.forEach(entry => {
|
||||
const entryPath = _path.default.join(moduleDownloadPath, entry);
|
||||
let isStale = true;
|
||||
for (const moduleName of Object.keys(installedModules)) {
|
||||
if (entryPath === installedModules[moduleName].updateZipfile) {
|
||||
isStale = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isStale) {
|
||||
_fs.default.unlinkSync(_path.default.join(moduleDownloadPath, entry));
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.log('Could not clean downloaded modules');
|
||||
logger.log(err.stack);
|
||||
}
|
||||
}
|
||||
function hostOnUpdateAvailable() {
|
||||
logger.log(`Host update is available.`);
|
||||
hostUpdateAvailable = true;
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: true,
|
||||
updateCount: 1,
|
||||
manualRequired: false
|
||||
});
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULE,
|
||||
name: 'host',
|
||||
current: 1,
|
||||
total: 1,
|
||||
foreground: !runningInBackground
|
||||
});
|
||||
}
|
||||
function hostOnUpdateProgress(progress) {
|
||||
logger.log(`Host update progress: ${progress}%`);
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULE_PROGRESS,
|
||||
name: 'host',
|
||||
progress: progress
|
||||
});
|
||||
}
|
||||
function hostOnUpdateNotAvailable() {
|
||||
logger.log(`Host is up to date.`);
|
||||
if (!skipModuleUpdate) {
|
||||
checkForModuleUpdates();
|
||||
} else {
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: true,
|
||||
updateCount: 0,
|
||||
manualRequired: false
|
||||
});
|
||||
}
|
||||
}
|
||||
function hostOnUpdateManually(newVersion) {
|
||||
logger.log(`Host update is available. Manual update required!`);
|
||||
hostUpdateAvailable = true;
|
||||
checkingForUpdates = false;
|
||||
events.append({
|
||||
type: UPDATE_MANUALLY,
|
||||
newVersion: newVersion
|
||||
});
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: true,
|
||||
updateCount: 1,
|
||||
manualRequired: true
|
||||
});
|
||||
}
|
||||
function hostOnUpdateDownloaded() {
|
||||
logger.log(`Host update downloaded.`);
|
||||
checkingForUpdates = false;
|
||||
events.append({
|
||||
type: DOWNLOADED_MODULE,
|
||||
name: 'host',
|
||||
current: 1,
|
||||
total: 1,
|
||||
succeeded: true
|
||||
});
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULES_FINISHED,
|
||||
succeeded: 1,
|
||||
failed: 0
|
||||
});
|
||||
}
|
||||
function hostOnError(err) {
|
||||
logger.log(`Host update failed: ${err}`);
|
||||
|
||||
// [adill] osx unsigned builds will fire this code signing error inside setFeedURL and
|
||||
// if we don't do anything about it hostUpdater.checkForUpdates() will never respond.
|
||||
if (err && String(err).indexOf('Could not get code signature for running application') !== -1) {
|
||||
console.warn('Skipping host updates due to code signing failure.');
|
||||
skipHostUpdate = true;
|
||||
}
|
||||
checkingForUpdates = false;
|
||||
if (!hostUpdateAvailable) {
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: false,
|
||||
updateCount: 0,
|
||||
manualRequired: false
|
||||
});
|
||||
} else {
|
||||
events.append({
|
||||
type: DOWNLOADED_MODULE,
|
||||
name: 'host',
|
||||
current: 1,
|
||||
total: 1,
|
||||
succeeded: false
|
||||
});
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULES_FINISHED,
|
||||
succeeded: 0,
|
||||
failed: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
function checkForUpdates() {
|
||||
if (checkingForUpdates) return;
|
||||
checkingForUpdates = true;
|
||||
hostUpdateAvailable = false;
|
||||
if (skipHostUpdate) {
|
||||
events.append({
|
||||
type: CHECKING_FOR_UPDATES
|
||||
});
|
||||
hostOnUpdateNotAvailable();
|
||||
} else {
|
||||
logger.log('Checking for host updates.');
|
||||
hostUpdater.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// Indicates that the initial update process is complete and that future updates
|
||||
// are background updates. This merely affects the content of the events sent to
|
||||
// the app so that analytics can correctly attribute module download/installs
|
||||
// depending on whether they were ui-blocking or not.
|
||||
function setInBackground() {
|
||||
runningInBackground = true;
|
||||
}
|
||||
function getRemoteModuleName(name) {
|
||||
if (_processUtils.IS_WIN && process.arch === 'x64') {
|
||||
return `${name}.x64`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
async function checkForModuleUpdates() {
|
||||
const query = {
|
||||
...remoteQuery,
|
||||
_: Math.floor(Date.now() / 1000 / 60 / 5)
|
||||
};
|
||||
const url = `${remoteBaseURL}/versions.json`;
|
||||
logger.log(`Checking for module updates at ${url}`);
|
||||
let response;
|
||||
try {
|
||||
response = await request.get({
|
||||
url,
|
||||
qs: query,
|
||||
timeout: REQUEST_TIMEOUT
|
||||
});
|
||||
checkingForUpdates = false;
|
||||
} catch (err) {
|
||||
checkingForUpdates = false;
|
||||
logger.log(`Failed fetching module versions: ${String(err)}`);
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: false,
|
||||
updateCount: 0,
|
||||
manualRequired: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
remoteModuleVersions = JSON.parse(response.body);
|
||||
if (settings.get(USE_LOCAL_MODULE_VERSIONS)) {
|
||||
try {
|
||||
remoteModuleVersions = JSON.parse(_fs.default.readFileSync(localModuleVersionsFilePath));
|
||||
console.log('Using local module versions: ', remoteModuleVersions);
|
||||
} catch (err) {
|
||||
console.warn('Failed to parse local module versions: ', err);
|
||||
}
|
||||
}
|
||||
const updatesToDownload = [];
|
||||
for (const moduleName of Object.keys(installedModules)) {
|
||||
const installedModule = installedModules[moduleName];
|
||||
const installed = installedModule.installedVersion;
|
||||
if (installed === null) {
|
||||
continue;
|
||||
}
|
||||
const update = installedModule.updateVersion || 0;
|
||||
const remote = remoteModuleVersions[getRemoteModuleName(moduleName)] || 0;
|
||||
if (installed !== remote && update !== remote) {
|
||||
logger.log(`Module update available: ${moduleName}@${remote} [installed: ${installed}]`);
|
||||
updatesToDownload.push({
|
||||
name: moduleName,
|
||||
version: remote
|
||||
});
|
||||
}
|
||||
}
|
||||
events.append({
|
||||
type: UPDATE_CHECK_FINISHED,
|
||||
succeeded: true,
|
||||
updateCount: updatesToDownload.length,
|
||||
manualRequired: false
|
||||
});
|
||||
if (updatesToDownload.length === 0) {
|
||||
logger.log(`No module updates available.`);
|
||||
} else {
|
||||
updatesToDownload.forEach(e => addModuleToDownloadQueue(e.name, e.version));
|
||||
}
|
||||
}
|
||||
function addModuleToDownloadQueue(name, version, authToken) {
|
||||
download.queue.push({
|
||||
name,
|
||||
version,
|
||||
authToken
|
||||
});
|
||||
process.nextTick(() => processDownloadQueue());
|
||||
}
|
||||
async function processDownloadQueue() {
|
||||
if (download.active) return;
|
||||
if (download.queue.length === 0) return;
|
||||
download.active = true;
|
||||
const queuedModule = download.queue[download.next];
|
||||
download.next += 1;
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULE,
|
||||
name: queuedModule.name,
|
||||
current: download.next,
|
||||
total: download.queue.length,
|
||||
foreground: !runningInBackground
|
||||
});
|
||||
let progress = 0;
|
||||
let receivedBytes = 0;
|
||||
const url = `${remoteBaseURL}/${encodeURIComponent(getRemoteModuleName(queuedModule.name))}/${encodeURIComponent(queuedModule.version)}`;
|
||||
logger.log(`Fetching ${queuedModule.name}@${queuedModule.version} from ${url}`);
|
||||
const headers = {};
|
||||
if (queuedModule.authToken) {
|
||||
headers['Authorization'] = queuedModule.authToken;
|
||||
}
|
||||
const moduleZipPath = _path.default.join(moduleDownloadPath, `${queuedModule.name}-${queuedModule.version}.zip`);
|
||||
const stream = _fs.default.createWriteStream(moduleZipPath);
|
||||
stream.on('progress', ({
|
||||
receivedBytes: newReceivedBytes,
|
||||
totalBytes
|
||||
}) => {
|
||||
receivedBytes = newReceivedBytes;
|
||||
const newProgress = Math.min(Math.floor(100 * (receivedBytes / totalBytes)), 100);
|
||||
if (progress !== newProgress) {
|
||||
progress = newProgress;
|
||||
logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}: ${progress}%`);
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULE_PROGRESS,
|
||||
name: queuedModule.name,
|
||||
progress: progress
|
||||
});
|
||||
}
|
||||
});
|
||||
logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}`);
|
||||
try {
|
||||
const response = await request.get({
|
||||
url,
|
||||
qs: remoteQuery,
|
||||
headers,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
stream
|
||||
});
|
||||
finishModuleDownload(queuedModule.name, queuedModule.version, moduleZipPath, receivedBytes, response.statusCode === 200);
|
||||
} catch (err) {
|
||||
logger.log(`Failed fetching module ${queuedModule.name}@${queuedModule.version}: ${String(err)}`);
|
||||
finishModuleDownload(queuedModule.name, queuedModule.version, null, receivedBytes, false);
|
||||
}
|
||||
}
|
||||
function commitInstalledModules() {
|
||||
const data = JSON.stringify(installedModules, null, 2);
|
||||
_fs.default.writeFileSync(installedModulesFilePath, data);
|
||||
}
|
||||
function finishModuleDownload(name, version, zipfile, receivedBytes, succeeded) {
|
||||
if (!installedModules[name]) {
|
||||
installedModules[name] = {};
|
||||
}
|
||||
if (succeeded) {
|
||||
installedModules[name].updateVersion = version;
|
||||
installedModules[name].updateZipfile = zipfile;
|
||||
commitInstalledModules();
|
||||
} else {
|
||||
download.failures += 1;
|
||||
}
|
||||
events.append({
|
||||
type: DOWNLOADED_MODULE,
|
||||
name: name,
|
||||
current: download.next,
|
||||
total: download.queue.length,
|
||||
succeeded: succeeded,
|
||||
receivedBytes: receivedBytes
|
||||
});
|
||||
if (download.next >= download.queue.length) {
|
||||
const successes = download.queue.length - download.failures;
|
||||
logger.log(`Finished module downloads. [success: ${successes}] [failure: ${download.failures}]`);
|
||||
events.append({
|
||||
type: DOWNLOADING_MODULES_FINISHED,
|
||||
succeeded: successes,
|
||||
failed: download.failures
|
||||
});
|
||||
download.queue = [];
|
||||
download.next = 0;
|
||||
download.failures = 0;
|
||||
download.active = false;
|
||||
} else {
|
||||
const continueDownloads = () => {
|
||||
download.active = false;
|
||||
processDownloadQueue();
|
||||
};
|
||||
if (succeeded) {
|
||||
backoff.succeed();
|
||||
process.nextTick(continueDownloads);
|
||||
} else {
|
||||
logger.log(`Waiting ${Math.floor(backoff.current)}ms before next download.`);
|
||||
backoff.fail(continueDownloads);
|
||||
}
|
||||
}
|
||||
if (newInstallInProgress[name]) {
|
||||
addModuleToUnzipQueue(name, version, zipfile);
|
||||
}
|
||||
}
|
||||
function addModuleToUnzipQueue(name, version, zipfile) {
|
||||
unzip.queue.push({
|
||||
name,
|
||||
version,
|
||||
zipfile
|
||||
});
|
||||
process.nextTick(() => processUnzipQueue());
|
||||
}
|
||||
function processUnzipQueue() {
|
||||
if (unzip.active) return;
|
||||
if (unzip.queue.length === 0) return;
|
||||
unzip.active = true;
|
||||
const queuedModule = unzip.queue[unzip.next];
|
||||
const installedModule = installedModules[queuedModule.name];
|
||||
const installedVersion = installedModule != null ? installedModule.installedVersion : null;
|
||||
unzip.next += 1;
|
||||
events.append({
|
||||
type: INSTALLING_MODULE,
|
||||
name: queuedModule.name,
|
||||
current: unzip.next,
|
||||
total: unzip.queue.length,
|
||||
foreground: !runningInBackground,
|
||||
oldVersion: installedVersion,
|
||||
newVersion: queuedModule.version
|
||||
});
|
||||
let hasErrored = false;
|
||||
const onError = (error, zipfile) => {
|
||||
if (hasErrored) return;
|
||||
hasErrored = true;
|
||||
logger.log(`Failed installing ${queuedModule.name}@${queuedModule.version}: ${String(error)}`);
|
||||
succeeded = false;
|
||||
if (zipfile) {
|
||||
zipfile.close();
|
||||
}
|
||||
finishModuleUnzip(queuedModule, succeeded);
|
||||
};
|
||||
let succeeded = true;
|
||||
const extractRoot = _path.default.join(moduleInstallPath, queuedModule.name);
|
||||
logger.log(`Installing ${queuedModule.name}@${queuedModule.version} from ${queuedModule.zipfile}`);
|
||||
const processZipfile = (err, zipfile) => {
|
||||
if (err) {
|
||||
onError(err, null);
|
||||
return;
|
||||
}
|
||||
const totalEntries = zipfile.entryCount;
|
||||
let processedEntries = 0;
|
||||
zipfile.on('entry', entry => {
|
||||
processedEntries += 1;
|
||||
const percent = Math.min(Math.floor(processedEntries / totalEntries * 100), 100);
|
||||
events.append({
|
||||
type: INSTALLING_MODULE_PROGRESS,
|
||||
name: queuedModule.name,
|
||||
progress: percent
|
||||
});
|
||||
|
||||
// skip directories
|
||||
if (/\/$/.test(entry.fileName)) {
|
||||
zipfile.readEntry();
|
||||
return;
|
||||
}
|
||||
zipfile.openReadStream(entry, (err, stream) => {
|
||||
if (err) {
|
||||
onError(err, zipfile);
|
||||
return;
|
||||
}
|
||||
stream.on('error', e => onError(e, zipfile));
|
||||
(0, _mkdirp.default)(_path.default.join(extractRoot, _path.default.dirname(entry.fileName)), err => {
|
||||
if (err) {
|
||||
onError(err, zipfile);
|
||||
return;
|
||||
}
|
||||
|
||||
// [adill] createWriteStream via original-fs is broken in Electron 4.0.0-beta.6 with .asar files
|
||||
// so we unzip to a temporary filename and rename it afterwards
|
||||
const tempFileName = _path.default.join(extractRoot, entry.fileName + '.tmp');
|
||||
const finalFileName = _path.default.join(extractRoot, entry.fileName);
|
||||
const writeStream = originalFs.createWriteStream(tempFileName);
|
||||
writeStream.on('error', e => {
|
||||
stream.destroy();
|
||||
try {
|
||||
originalFs.unlinkSync(tempFileName);
|
||||
} catch (err) {}
|
||||
onError(e, zipfile);
|
||||
});
|
||||
writeStream.on('finish', () => {
|
||||
try {
|
||||
originalFs.unlinkSync(finalFileName);
|
||||
} catch (err) {}
|
||||
try {
|
||||
originalFs.renameSync(tempFileName, finalFileName);
|
||||
} catch (err) {
|
||||
onError(err, zipfile);
|
||||
return;
|
||||
}
|
||||
zipfile.readEntry();
|
||||
});
|
||||
stream.pipe(writeStream);
|
||||
});
|
||||
});
|
||||
});
|
||||
zipfile.on('error', err => {
|
||||
onError(err, zipfile);
|
||||
});
|
||||
zipfile.on('end', () => {
|
||||
if (!succeeded) return;
|
||||
installedModules[queuedModule.name].installedVersion = queuedModule.version;
|
||||
finishModuleUnzip(queuedModule, succeeded);
|
||||
});
|
||||
zipfile.readEntry();
|
||||
};
|
||||
try {
|
||||
_yauzl.default.open(queuedModule.zipfile, {
|
||||
lazyEntries: true,
|
||||
autoClose: true
|
||||
}, processZipfile);
|
||||
} catch (err) {
|
||||
onError(err, null);
|
||||
}
|
||||
}
|
||||
function finishModuleUnzip(unzippedModule, succeeded) {
|
||||
delete newInstallInProgress[unzippedModule.name];
|
||||
delete installedModules[unzippedModule.name].updateZipfile;
|
||||
delete installedModules[unzippedModule.name].updateVersion;
|
||||
commitInstalledModules();
|
||||
if (!succeeded) {
|
||||
unzip.failures += 1;
|
||||
}
|
||||
events.append({
|
||||
type: INSTALLED_MODULE,
|
||||
name: unzippedModule.name,
|
||||
current: unzip.next,
|
||||
total: unzip.queue.length,
|
||||
succeeded: succeeded
|
||||
});
|
||||
if (unzip.next >= unzip.queue.length) {
|
||||
const successes = unzip.queue.length - unzip.failures;
|
||||
bootstrapping = false;
|
||||
logger.log(`Finished module installations. [success: ${successes}] [failure: ${unzip.failures}]`);
|
||||
unzip.queue = [];
|
||||
unzip.next = 0;
|
||||
unzip.failures = 0;
|
||||
unzip.active = false;
|
||||
events.append({
|
||||
type: INSTALLING_MODULES_FINISHED,
|
||||
succeeded: successes,
|
||||
failed: unzip.failures
|
||||
});
|
||||
return;
|
||||
}
|
||||
process.nextTick(() => {
|
||||
unzip.active = false;
|
||||
processUnzipQueue();
|
||||
});
|
||||
}
|
||||
function quitAndInstallUpdates() {
|
||||
logger.log(`Relaunching to install ${hostUpdateAvailable ? 'host' : 'module'} updates...`);
|
||||
if (hostUpdateAvailable) {
|
||||
hostUpdater.quitAndInstall();
|
||||
} else {
|
||||
relaunch();
|
||||
}
|
||||
}
|
||||
function relaunch() {
|
||||
logger.end();
|
||||
const {
|
||||
app
|
||||
} = require('electron');
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
function isInstalled(name, version) {
|
||||
const metadata = installedModules[name];
|
||||
if (locallyInstalledModules) return true;
|
||||
if (metadata && metadata.installedVersion > 0) {
|
||||
if (!version) return true;
|
||||
if (metadata.installedVersion === version) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function getInstalled() {
|
||||
return {
|
||||
...installedModules
|
||||
};
|
||||
}
|
||||
function install(name, defer, options) {
|
||||
let {
|
||||
version,
|
||||
authToken
|
||||
} = options || {};
|
||||
if (isInstalled(name, version)) {
|
||||
if (!defer) {
|
||||
events.append({
|
||||
type: INSTALLED_MODULE,
|
||||
name: name,
|
||||
current: 1,
|
||||
total: 1,
|
||||
succeeded: true
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (newInstallInProgress[name]) return;
|
||||
if (!updatable) {
|
||||
logger.log(`Not updatable; ignoring request to install ${name}...`);
|
||||
return;
|
||||
}
|
||||
if (defer) {
|
||||
if (version) {
|
||||
throw new Error(`Cannot defer install for a specific version module (${name}, ${version})`);
|
||||
}
|
||||
logger.log(`Deferred install for ${name}...`);
|
||||
installedModules[name] = {
|
||||
installedVersion: 0
|
||||
};
|
||||
commitInstalledModules();
|
||||
} else {
|
||||
logger.log(`Starting to install ${name}...`);
|
||||
if (!version) {
|
||||
version = remoteModuleVersions[name] || 0;
|
||||
}
|
||||
newInstallInProgress[name] = version;
|
||||
addModuleToDownloadQueue(name, version, authToken);
|
||||
}
|
||||
}
|
||||
function installPendingUpdates() {
|
||||
const updatesToInstall = [];
|
||||
if (bootstrapping) {
|
||||
let modules = {};
|
||||
try {
|
||||
modules = JSON.parse(_fs.default.readFileSync(bootstrapManifestFilePath));
|
||||
} catch (err) {}
|
||||
for (const moduleName of Object.keys(modules)) {
|
||||
installedModules[moduleName] = {
|
||||
installedVersion: 0
|
||||
};
|
||||
const zipfile = _path.default.join(paths.getResources(), 'bootstrap', `${moduleName}.zip`);
|
||||
updatesToInstall.push({
|
||||
moduleName,
|
||||
update: modules[moduleName],
|
||||
zipfile
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const moduleName of Object.keys(installedModules)) {
|
||||
const update = installedModules[moduleName].updateVersion || 0;
|
||||
const zipfile = installedModules[moduleName].updateZipfile;
|
||||
if (update > 0 && zipfile != null) {
|
||||
updatesToInstall.push({
|
||||
moduleName,
|
||||
update,
|
||||
zipfile
|
||||
});
|
||||
}
|
||||
}
|
||||
if (updatesToInstall.length > 0) {
|
||||
logger.log(`${bootstrapping ? 'Bootstrapping' : 'Installing updates'}...`);
|
||||
updatesToInstall.forEach(e => addModuleToUnzipQueue(e.moduleName, e.update, e.zipfile));
|
||||
} else {
|
||||
logger.log('No updates to install');
|
||||
events.append({
|
||||
type: NO_PENDING_UPDATES
|
||||
});
|
||||
}
|
||||
}
|
31
.resources/app/common/nodeGlobalPaths.js
Normal file
31
.resources/app/common/nodeGlobalPaths.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.addGlobalPath = addGlobalPath;
|
||||
exports.getGlobalPaths = getGlobalPaths;
|
||||
exports.globalPathExists = globalPathExists;
|
||||
var _module = _interopRequireDefault(require("module"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const resolveLookupPaths = _module.default._resolveLookupPaths;
|
||||
_module.default._resolveLookupPaths = (request, parent) => {
|
||||
var _parent$paths;
|
||||
if (parent === null || parent === void 0 ? void 0 : (_parent$paths = parent.paths) === null || _parent$paths === void 0 ? void 0 : _parent$paths.length) {
|
||||
parent.paths = parent.paths.concat(_module.default.globalPaths);
|
||||
} else {
|
||||
parent.paths = _module.default.globalPaths;
|
||||
}
|
||||
return resolveLookupPaths(request, parent);
|
||||
};
|
||||
function getGlobalPaths() {
|
||||
return _module.default.globalPaths;
|
||||
}
|
||||
function addGlobalPath(path) {
|
||||
if (_module.default.globalPaths.indexOf(path) === -1) {
|
||||
_module.default.globalPaths.push(path);
|
||||
}
|
||||
}
|
||||
function globalPathExists(path) {
|
||||
return _module.default.globalPaths.indexOf(path) !== -1;
|
||||
}
|
101
.resources/app/common/paths.js
Normal file
101
.resources/app/common/paths.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.cleanOldVersions = cleanOldVersions;
|
||||
exports.getInstallPath = getInstallPath;
|
||||
exports.getModuleDataPath = getModuleDataPath;
|
||||
exports.getResources = getResources;
|
||||
exports.getUserData = getUserData;
|
||||
exports.getUserDataVersioned = getUserDataVersioned;
|
||||
exports.init = init;
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
var _mkdirp = _interopRequireDefault(require("mkdirp"));
|
||||
var _originalFs = _interopRequireDefault(require("original-fs"));
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
var _rimraf = _interopRequireDefault(require("rimraf"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
/* eslint-disable no-console */
|
||||
// Determines environment-specific paths based on info provided
|
||||
|
||||
let userDataPath = null;
|
||||
let userDataVersionedPath = null;
|
||||
let resourcesPath = null;
|
||||
let moduleDataPath = null;
|
||||
let installPath = null;
|
||||
function determineAppUserDataRoot() {
|
||||
// Allow overwriting the user data directory. This can be important when using --multi-instance
|
||||
const userDataPath = process.env.DISCORD_USER_DATA_DIR;
|
||||
if (userDataPath) {
|
||||
return userDataPath;
|
||||
}
|
||||
const {
|
||||
app
|
||||
} = require('electron');
|
||||
return app.getPath('appData');
|
||||
}
|
||||
function determineUserData(userDataRoot, buildInfo) {
|
||||
return _path.default.join(userDataRoot, 'discord' + (buildInfo.releaseChannel == 'stable' ? '' : buildInfo.releaseChannel));
|
||||
}
|
||||
|
||||
// cleans old version data in the background
|
||||
function cleanOldVersions(buildInfo) {
|
||||
const entries = _fs.default.readdirSync(userDataPath) || [];
|
||||
entries.forEach(entry => {
|
||||
const fullPath = _path.default.join(userDataPath, entry);
|
||||
let stat;
|
||||
try {
|
||||
stat = _fs.default.lstatSync(fullPath);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (stat.isDirectory() && entry.indexOf(buildInfo.version) === -1) {
|
||||
if (entry.match('^[0-9]+.[0-9]+.[0-9]+') != null) {
|
||||
console.log('Removing old directory ', entry);
|
||||
(0, _rimraf.default)(fullPath, _originalFs.default, error => {
|
||||
if (error) {
|
||||
console.warn('...failed with error: ', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function init(buildInfo) {
|
||||
resourcesPath = _path.default.join(require.main.filename, '..', '..', '..');
|
||||
const userDataRoot = determineAppUserDataRoot();
|
||||
userDataPath = determineUserData(userDataRoot, buildInfo);
|
||||
const {
|
||||
app
|
||||
} = require('electron');
|
||||
app.setPath('userData', userDataPath);
|
||||
userDataVersionedPath = _path.default.join(userDataPath, buildInfo.version);
|
||||
_mkdirp.default.sync(userDataVersionedPath);
|
||||
if (buildInfo.localModulesRoot != null) {
|
||||
moduleDataPath = buildInfo.localModulesRoot;
|
||||
} else if (buildInfo.newUpdater) {
|
||||
moduleDataPath = _path.default.join(userDataPath, 'module_data');
|
||||
} else {
|
||||
moduleDataPath = _path.default.join(userDataVersionedPath, 'modules');
|
||||
}
|
||||
const exeDir = _path.default.dirname(app.getPath('exe'));
|
||||
if (/^app-[0-9]+\.[0-9]+\.[0-9]+/.test(_path.default.basename(exeDir))) {
|
||||
installPath = _path.default.join(exeDir, '..');
|
||||
}
|
||||
}
|
||||
function getUserData() {
|
||||
return userDataPath;
|
||||
}
|
||||
function getUserDataVersioned() {
|
||||
return userDataVersionedPath;
|
||||
}
|
||||
function getResources() {
|
||||
return resourcesPath;
|
||||
}
|
||||
function getModuleDataPath() {
|
||||
return moduleDataPath;
|
||||
}
|
||||
function getInstallPath() {
|
||||
return installPath;
|
||||
}
|
51
.resources/app/common/processUtils.js
Normal file
51
.resources/app/common/processUtils.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.IS_WIN = exports.IS_OSX = exports.IS_LINUX = void 0;
|
||||
exports.getElectronMajorVersion = getElectronMajorVersion;
|
||||
exports.supportsTls13 = supportsTls13;
|
||||
var _os = _interopRequireDefault(require("os"));
|
||||
var _process = _interopRequireDefault(require("process"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function getElectronMajorVersion() {
|
||||
return _process.default.versions.electron != null ? parseInt(_process.default.versions.electron.split('.')[0]) : 0;
|
||||
}
|
||||
const IS_WIN = _process.default.platform === 'win32';
|
||||
exports.IS_WIN = IS_WIN;
|
||||
const IS_OSX = _process.default.platform === 'darwin';
|
||||
exports.IS_OSX = IS_OSX;
|
||||
const IS_LINUX = _process.default.platform === 'linux';
|
||||
exports.IS_LINUX = IS_LINUX;
|
||||
function isWindowsVersionOrEarlier(major, minor) {
|
||||
if (!IS_WIN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep it resilient.
|
||||
const osRelease = _os.default.release();
|
||||
if (osRelease == null || typeof osRelease !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const [actualMajor, actualMinor] = osRelease.split('.').map(v => parseInt(v, 10));
|
||||
if (actualMajor < major) {
|
||||
return true;
|
||||
}
|
||||
if (actualMajor === major && actualMinor <= minor) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function supportsTls13() {
|
||||
// The nodejs `tls` module does not appear to provide a proper way to sniff tls1.2+ support. Sentry has depricated
|
||||
// tls < 1.2, and since this TLS fissure is between OS versions, we instead detect Windows 7 and handle it
|
||||
// accordingly. Windows 7 is version 6.1, so detect 6.1 or lower.
|
||||
try {
|
||||
return !isWindowsVersionOrEarlier(6, 1);
|
||||
} catch {
|
||||
// Who knows what wacky stuff random hacked up operating systems are reporting.
|
||||
// Lets presume no one is using this old of an OS if we hit random exceptional cases.
|
||||
return true;
|
||||
}
|
||||
}
|
42
.resources/app/common/securityUtils.js
Normal file
42
.resources/app/common/securityUtils.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.checkUrlOriginMatches = checkUrlOriginMatches;
|
||||
exports.saferShellOpenExternal = saferShellOpenExternal;
|
||||
exports.shouldOpenExternalUrl = shouldOpenExternalUrl;
|
||||
var _electron = require("electron");
|
||||
var _url = _interopRequireDefault(require("url"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const BLOCKED_URL_PROTOCOLS = ['file:', 'javascript:', 'vbscript:', 'data:', 'about:', 'chrome:', 'ms-cxh:', 'ms-cxh-full:', 'ms-word:'];
|
||||
function shouldOpenExternalUrl(externalUrl) {
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = _url.default.parse(externalUrl);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
if (parsedUrl.protocol == null || BLOCKED_URL_PROTOCOLS.includes(parsedUrl.protocol.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function saferShellOpenExternal(externalUrl) {
|
||||
if (shouldOpenExternalUrl(externalUrl)) {
|
||||
return _electron.shell.openExternal(externalUrl);
|
||||
} else {
|
||||
return Promise.reject(new Error('External url open request blocked'));
|
||||
}
|
||||
}
|
||||
function checkUrlOriginMatches(urlA, urlB) {
|
||||
let parsedUrlA;
|
||||
let parsedUrlB;
|
||||
try {
|
||||
parsedUrlA = _url.default.parse(urlA);
|
||||
parsedUrlB = _url.default.parse(urlB);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return parsedUrlA.protocol === parsedUrlB.protocol && parsedUrlA.slashes === parsedUrlB.slashes && parsedUrlA.host === parsedUrlB.host;
|
||||
}
|
14
.resources/app/common/typings/global.d.ts
vendored
Normal file
14
.resources/app/common/typings/global.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
import type NodeModule from 'module';
|
||||
import type {DiscordNativeType} from '@discordapp/discord-native-types';
|
||||
|
||||
declare module 'module' {
|
||||
var globalPaths: string[];
|
||||
var _resolveLookupPaths: (request: string, parent: NodeModule) => string[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
var moduleDataPath: string | undefined;
|
||||
var modulePath: string | undefined;
|
||||
var DiscordNative: DiscordNativeType;
|
||||
var popouts: Map<string, Window>;
|
||||
}
|
394
.resources/app/common/updater.js
Normal file
394
.resources/app/common/updater.js
Normal file
|
@ -0,0 +1,394 @@
|
|||
"use strict";
|
||||
|
||||
// Too much Rust integration stuff in here.
|
||||
/* eslint camelcase: 0 */
|
||||
const childProcess = require('child_process');
|
||||
const {
|
||||
app
|
||||
} = require('electron');
|
||||
const {
|
||||
EventEmitter
|
||||
} = require('events');
|
||||
const NodeModule = require('module');
|
||||
const path = require('path');
|
||||
const {
|
||||
hrtime
|
||||
} = require('process');
|
||||
let instance;
|
||||
const TASK_STATE_COMPLETE = 'Complete';
|
||||
const TASK_STATE_FAILED = 'Failed';
|
||||
const TASK_STATE_WAITING = 'Waiting';
|
||||
const TASK_STATE_WORKING = 'Working';
|
||||
const INCONSISTENT_INSTALLER_STATE_ERROR = 'InconsistentInstallerState';
|
||||
|
||||
// The dumb linters are mad at each other.
|
||||
// eslint-disable-next-line quotes
|
||||
const INVALID_UPDATER_ERROR = "Can't send request to updater because the native updater isn't loaded.";
|
||||
class Updater extends EventEmitter {
|
||||
constructor(options) {
|
||||
super();
|
||||
let nativeUpdaterModule = options.nativeUpdaterModule;
|
||||
if (nativeUpdaterModule == null) {
|
||||
try {
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
nativeUpdaterModule = require('../../../updater');
|
||||
} catch (e) {
|
||||
if (e.code === 'MODULE_NOT_FOUND') {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
this.committedHostVersion = null;
|
||||
this.committedModules = new Set();
|
||||
this.rootPath = options.root_path;
|
||||
this.nextRequestId = 0;
|
||||
this.requests = new Map();
|
||||
this.updateEventHistory = [];
|
||||
this.isRunningInBackground = false;
|
||||
this.currentlyDownloading = {};
|
||||
this.currentlyInstalling = {};
|
||||
this.hasEmittedUnhandledException = false;
|
||||
this.nativeUpdater = new nativeUpdaterModule.Updater({
|
||||
response_handler: this._handleResponse.bind(this),
|
||||
...options
|
||||
});
|
||||
}
|
||||
get valid() {
|
||||
return this.nativeUpdater != null;
|
||||
}
|
||||
_sendRequest(detail, progressCallback = null) {
|
||||
if (!this.valid) {
|
||||
throw new Error(INVALID_UPDATER_ERROR);
|
||||
}
|
||||
const requestId = this.nextRequestId++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.requests.set(requestId, {
|
||||
resolve,
|
||||
reject,
|
||||
progressCallback
|
||||
});
|
||||
this.nativeUpdater.command(JSON.stringify([requestId, detail]));
|
||||
});
|
||||
}
|
||||
_sendRequestSync(detail) {
|
||||
if (!this.valid) {
|
||||
throw new Error(INVALID_UPDATER_ERROR);
|
||||
}
|
||||
const requestId = this.nextRequestId++;
|
||||
return this.nativeUpdater.command_blocking(JSON.stringify([requestId, detail]));
|
||||
}
|
||||
_handleResponse(response) {
|
||||
try {
|
||||
const [id, detail] = JSON.parse(response);
|
||||
const request = this.requests.get(id);
|
||||
if (request == null) {
|
||||
console.error('Received response ', detail, ' for a request (', id, ') not in the updater request map.');
|
||||
return;
|
||||
}
|
||||
if (detail['Error'] != null) {
|
||||
const {
|
||||
kind,
|
||||
details,
|
||||
severity
|
||||
} = detail['Error'];
|
||||
const e = new Error(`(${kind}) ${details}`);
|
||||
if (severity === 'Fatal') {
|
||||
const handled = this.emit(kind, e);
|
||||
if (!handled) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
this.emit('update-error', e);
|
||||
request.reject(e);
|
||||
this.requests.delete(id);
|
||||
}
|
||||
} else if (detail === 'Ok') {
|
||||
request.resolve();
|
||||
this.requests.delete(id);
|
||||
} else if (detail['VersionInfo'] != null) {
|
||||
request.resolve(detail['VersionInfo']);
|
||||
this.requests.delete(id);
|
||||
} else if (detail['ManifestInfo'] != null) {
|
||||
request.resolve(detail['ManifestInfo']);
|
||||
this.requests.delete(id);
|
||||
} else if (detail['TaskProgress'] != null) {
|
||||
const msg = detail['TaskProgress'];
|
||||
const progress = {
|
||||
task: msg[0],
|
||||
state: msg[1],
|
||||
percent: msg[2],
|
||||
bytesProcessed: msg[3]
|
||||
};
|
||||
this._recordTaskProgress(progress);
|
||||
if (request.progressCallback != null) {
|
||||
request.progressCallback(progress);
|
||||
}
|
||||
if (progress.task['HostInstall'] != null && progress.state === TASK_STATE_COMPLETE) {
|
||||
this.emit('host-updated');
|
||||
}
|
||||
} else {
|
||||
console.warn('Unknown updater response', detail);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Unhandled exception in updater response handler:', e);
|
||||
|
||||
// Report the first time this happens, but don't spam.
|
||||
if (!this.hasEmittedUnhandledException) {
|
||||
this.hasEmittedUnhandledException = true;
|
||||
this.emit('unhandled-exception', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_handleSyncResponse(response) {
|
||||
const detail = JSON.parse(response);
|
||||
if (detail['Error'] != null) {
|
||||
throw new Error(detail['Error']);
|
||||
} else if (detail === 'Ok') {
|
||||
return;
|
||||
} else if (detail['VersionInfo'] != null) {
|
||||
return detail['VersionInfo'];
|
||||
}
|
||||
console.warn('Unknown updater response', detail);
|
||||
}
|
||||
_getHostPath() {
|
||||
const [major, minor, revision] = this.committedHostVersion;
|
||||
const hostVersionStr = `${major}.${minor}.${revision}`;
|
||||
return path.join(this.rootPath, `app-${hostVersionStr}`);
|
||||
}
|
||||
_startCurrentVersionInner(options, versions) {
|
||||
if (this.committedHostVersion == null) {
|
||||
this.committedHostVersion = versions.current_host;
|
||||
}
|
||||
const hostPath = this._getHostPath();
|
||||
const hostExePath = path.join(hostPath, path.basename(process.execPath));
|
||||
if (path.resolve(hostExePath) != path.resolve(process.execPath) && !(options === null || options === void 0 ? void 0 : options.allowObsoleteHost)) {
|
||||
app.once('will-quit', () => {
|
||||
// TODO(eiz): the actual, correct way to do this (win32) is to inherit a
|
||||
// handle to the current process into a new child process which then
|
||||
// waits for that process handle to exit, then runs the new electron.
|
||||
// This requires either implementing a separate updater exe process (big
|
||||
// todo item atm) or likely modifying Electron?
|
||||
//
|
||||
// I intend to do it properly once the new production updater .exe is a
|
||||
// thing.
|
||||
childProcess.spawn(hostExePath, [], {
|
||||
detached: true,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
});
|
||||
console.log(`Restarting from ${path.resolve(process.execPath)} to ${path.resolve(hostExePath)}`);
|
||||
app.quit();
|
||||
this.emit('starting-new-host');
|
||||
return;
|
||||
}
|
||||
this._commitModulesInner(versions);
|
||||
}
|
||||
_commitModulesInner(versions) {
|
||||
const {
|
||||
addGlobalPath,
|
||||
globalPathExists
|
||||
} = require('./nodeGlobalPaths');
|
||||
const hostPath = this._getHostPath();
|
||||
const modulesPath = path.join(hostPath, 'modules');
|
||||
for (const module in versions.current_modules) {
|
||||
const moduleVersion = versions.current_modules[module];
|
||||
const moduleSearchPath = path.join(modulesPath, `${module}-${moduleVersion}`);
|
||||
if (!this.committedModules.has(module) && !globalPathExists(moduleSearchPath)) {
|
||||
this.committedModules.add(module);
|
||||
addGlobalPath(moduleSearchPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
_recordDownloadProgress(name, progress) {
|
||||
const now = String(hrtime.bigint());
|
||||
if (progress.state === TASK_STATE_WORKING && !this.currentlyDownloading[name]) {
|
||||
this.currentlyDownloading[name] = true;
|
||||
this.updateEventHistory.push({
|
||||
type: 'downloading-module',
|
||||
name: name,
|
||||
now: now
|
||||
});
|
||||
} else if (progress.state === TASK_STATE_COMPLETE || progress.state === TASK_STATE_FAILED) {
|
||||
this.currentlyDownloading[name] = false;
|
||||
this.updateEventHistory.push({
|
||||
type: 'downloaded-module',
|
||||
name: name,
|
||||
now: now,
|
||||
succeeded: progress.state === TASK_STATE_COMPLETE,
|
||||
receivedBytes: progress.bytesProcessed
|
||||
});
|
||||
}
|
||||
}
|
||||
_recordInstallProgress(name, progress, newVersion, isDelta) {
|
||||
const now = String(hrtime.bigint());
|
||||
if (progress.state === TASK_STATE_WORKING && !this.currentlyInstalling[name]) {
|
||||
this.currentlyInstalling[name] = true;
|
||||
this.updateEventHistory.push({
|
||||
type: 'installing-module',
|
||||
name,
|
||||
now,
|
||||
newVersion,
|
||||
foreground: !this.isRunningInBackground
|
||||
});
|
||||
} else if (progress.state === TASK_STATE_COMPLETE || progress.state === TASK_STATE_FAILED) {
|
||||
this.currentlyInstalling[name] = false;
|
||||
this.updateEventHistory.push({
|
||||
type: 'installed-module',
|
||||
name,
|
||||
now,
|
||||
newVersion,
|
||||
succeeded: progress.state === TASK_STATE_COMPLETE,
|
||||
delta: isDelta,
|
||||
foreground: !this.isRunningInBackground
|
||||
});
|
||||
}
|
||||
}
|
||||
_recordTaskProgress(progress) {
|
||||
if (progress.task.HostDownload != null) {
|
||||
this._recordDownloadProgress('host', progress);
|
||||
} else if (progress.task.HostInstall != null) {
|
||||
this._recordInstallProgress('host', progress, null, progress.task.HostInstall.from_version != null);
|
||||
} else if (progress.task.ModuleDownload != null) {
|
||||
this._recordDownloadProgress(progress.task.ModuleDownload.version.module.name, progress);
|
||||
} else if (progress.task.ModuleInstall != null) {
|
||||
this._recordInstallProgress(progress.task.ModuleInstall.version.module.name, progress, progress.task.ModuleInstall.version.version, progress.task.ModuleInstall.from_version != null);
|
||||
}
|
||||
}
|
||||
queryCurrentVersions() {
|
||||
return this._sendRequest('QueryCurrentVersions');
|
||||
}
|
||||
queryCurrentVersionsSync() {
|
||||
return this._handleSyncResponse(this._sendRequestSync('QueryCurrentVersions'));
|
||||
}
|
||||
repair(progressCallback) {
|
||||
return this.repairWithOptions(null, progressCallback);
|
||||
}
|
||||
repairWithOptions(options, progressCallback) {
|
||||
return this._sendRequest({
|
||||
Repair: {
|
||||
options
|
||||
}
|
||||
}, progressCallback);
|
||||
}
|
||||
collectGarbage() {
|
||||
return this._sendRequest('CollectGarbage');
|
||||
}
|
||||
setRunningManifest(manifest) {
|
||||
return this._sendRequest({
|
||||
SetManifests: ['Running', manifest]
|
||||
});
|
||||
}
|
||||
setPinnedManifestSync(manifest) {
|
||||
return this._handleSyncResponse(this._sendRequestSync({
|
||||
SetManifests: ['Pinned', manifest]
|
||||
}));
|
||||
}
|
||||
installModule(name, progressCallback) {
|
||||
return this.installModuleWithOptions(name, null, progressCallback);
|
||||
}
|
||||
installModuleWithOptions(name, options, progressCallback) {
|
||||
return this._sendRequest({
|
||||
InstallModule: {
|
||||
name,
|
||||
options
|
||||
}
|
||||
}, progressCallback);
|
||||
}
|
||||
updateToLatest(progressCallback) {
|
||||
return this.updateToLatestWithOptions(null, progressCallback);
|
||||
}
|
||||
updateToLatestWithOptions(options, progressCallback) {
|
||||
return this._sendRequest({
|
||||
UpdateToLatest: {
|
||||
options
|
||||
}
|
||||
}, progressCallback);
|
||||
}
|
||||
|
||||
// If the running host is current, adopt the current installed modules and
|
||||
// set up the module search path accordingly. If the running host is not
|
||||
// current, start the new current host and exit this process.
|
||||
async startCurrentVersion(options) {
|
||||
const versions = await this.queryCurrentVersions();
|
||||
await this.setRunningManifest(versions.last_successful_update);
|
||||
this._startCurrentVersionInner(options, versions);
|
||||
}
|
||||
startCurrentVersionSync(options) {
|
||||
const versions = this.queryCurrentVersionsSync();
|
||||
this._startCurrentVersionInner(options, versions);
|
||||
}
|
||||
async commitModules(versions) {
|
||||
if (this.committedHostVersion == null) {
|
||||
throw new Error('Cannot commit modules before host version.');
|
||||
}
|
||||
if (versions == null) {
|
||||
versions = await this.queryCurrentVersions();
|
||||
}
|
||||
this._commitModulesInner(versions);
|
||||
}
|
||||
setRunningInBackground() {
|
||||
this.isRunningInBackground = true;
|
||||
}
|
||||
queryAndTruncateHistory() {
|
||||
const history = this.updateEventHistory;
|
||||
this.updateEventHistory = [];
|
||||
return history;
|
||||
}
|
||||
getKnownFolder(name) {
|
||||
if (!this.valid) {
|
||||
throw new Error(INVALID_UPDATER_ERROR);
|
||||
}
|
||||
return this.nativeUpdater.known_folder(name);
|
||||
}
|
||||
createShortcut(options) {
|
||||
if (!this.valid) {
|
||||
throw new Error(INVALID_UPDATER_ERROR);
|
||||
}
|
||||
return this.nativeUpdater.create_shortcut(options);
|
||||
}
|
||||
}
|
||||
function getUpdaterPlatformName(platform) {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'osx';
|
||||
case 'win32':
|
||||
return 'win';
|
||||
default:
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
function tryInitUpdater(buildInfo, repositoryUrl) {
|
||||
// We can't require this in module scope because it's not part of the
|
||||
// bootstrapper, which carries a copy of the Updater class.
|
||||
const paths = require('./paths');
|
||||
const rootPath = paths.getInstallPath();
|
||||
|
||||
// If we're not running from an actual install directory, don't bother trying
|
||||
// to initialize the updater.
|
||||
if (rootPath == null) {
|
||||
return false;
|
||||
}
|
||||
instance = new Updater({
|
||||
release_channel: buildInfo.releaseChannel,
|
||||
platform: getUpdaterPlatformName(process.platform),
|
||||
repository_url: repositoryUrl,
|
||||
root_path: rootPath
|
||||
});
|
||||
return instance.valid;
|
||||
}
|
||||
function getUpdater() {
|
||||
if (instance != null && instance.valid) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
Updater,
|
||||
tryInitUpdater,
|
||||
getUpdater,
|
||||
TASK_STATE_COMPLETE,
|
||||
TASK_STATE_FAILED,
|
||||
TASK_STATE_WAITING,
|
||||
TASK_STATE_WORKING,
|
||||
INCONSISTENT_INSTALLER_STATE_ERROR
|
||||
};
|
17
.resources/app/package.json
Normal file
17
.resources/app/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "discord",
|
||||
"description": "Discord Client for Desktop - Bootstrapper",
|
||||
"main": "app_bootstrap/index.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sentry/node": "7.47.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/electron": "1.6.10",
|
||||
"devtron": "1.4.0"
|
||||
}
|
||||
}
|
7
.resources/bootstrap/manifest.json
Normal file
7
.resources/bootstrap/manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"discord_desktop_core": 0,
|
||||
"discord_erlpack": 0,
|
||||
"discord_spellcheck": 0,
|
||||
"discord_utils": 0,
|
||||
"discord_voice": 0
|
||||
}
|
4
.resources/build_info.json
Normal file
4
.resources/build_info.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"releaseChannel": "stable",
|
||||
"version": "0.0.27"
|
||||
}
|
1370
Cargo.lock
generated
Normal file
1370
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "OpenAsar"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nostr-sdk = "0.20.1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[[bin]]
|
||||
name = "OpenAsar"
|
||||
path = "main.rs"
|
11
PATCH.sh
Executable file
11
PATCH.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ ! "$1" ]; then
|
||||
echo "Enter PATH to krisp's Node file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_PATH="$1"
|
||||
PATCHED_NODE_PATH="$(echo $NODE_PATH | cut -d '.' -f 1)_patched.node"
|
||||
|
||||
xxd -p -c 0 "$NODE_PATH" | sed -E \'s/(4889dfe8........85c0)745c/\19090/' | xxd -p -r -c 0 > "$PATCHED_NODE_PATH"
|
4
deps.txt
Normal file
4
deps.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
axel
|
||||
fakeroot
|
||||
otpclient-cli
|
||||
gdebi
|
470
flatpak-pip-generator.py
Executable file
470
flatpak-pip-generator.py
Executable file
|
@ -0,0 +1,470 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# [.PY]-CLONE-FROM:https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/pip/flatpak-pip-generator
|
||||
__license__ = 'MIT'
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Dict
|
||||
|
||||
try:
|
||||
import requirements
|
||||
except ImportError:
|
||||
exit('Requirements modules is not installed. Run "pip install requirements-parser"')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('packages', nargs='*')
|
||||
parser.add_argument('--python2', action='store_true',
|
||||
help='Look for a Python 2 package')
|
||||
parser.add_argument('--cleanup', choices=['scripts', 'all'],
|
||||
help='Select what to clean up after build')
|
||||
parser.add_argument('--requirements-file', '-r',
|
||||
help='Specify requirements.txt file')
|
||||
parser.add_argument('--build-only', action='store_const',
|
||||
dest='cleanup', const='all',
|
||||
help='Clean up all files after build')
|
||||
parser.add_argument('--build-isolation', action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
'Do not disable build isolation. '
|
||||
'Mostly useful on pip that does\'t '
|
||||
'support the feature.'
|
||||
))
|
||||
parser.add_argument('--ignore-installed',
|
||||
type=lambda s: s.split(','),
|
||||
default='',
|
||||
help='Comma-separated list of package names for which pip '
|
||||
'should ignore already installed packages. Useful when '
|
||||
'the package is installed in the SDK but not in the '
|
||||
'runtime.')
|
||||
parser.add_argument('--checker-data', action='store_true',
|
||||
help='Include x-checker-data in output for the "Flatpak External Data Checker"')
|
||||
parser.add_argument('--output', '-o',
|
||||
help='Specify output file name')
|
||||
parser.add_argument('--runtime',
|
||||
help='Specify a flatpak to run pip inside of a sandbox, ensures python version compatibility')
|
||||
parser.add_argument('--yaml', action='store_true',
|
||||
help='Use YAML as output format instead of JSON')
|
||||
opts = parser.parse_args()
|
||||
|
||||
if opts.yaml:
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
exit('PyYAML modules is not installed. Run "pip install PyYAML"')
|
||||
|
||||
|
||||
def get_pypi_url(name: str, filename: str) -> str:
|
||||
url = 'https://pypi.org/pypi/{}/json'.format(name)
|
||||
print('Extracting download url for', name)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
body = json.loads(response.read().decode('utf-8'))
|
||||
for release in body['releases'].values():
|
||||
for source in release:
|
||||
if source['filename'] == filename:
|
||||
return source['url']
|
||||
raise Exception('Failed to extract url from {}'.format(url))
|
||||
|
||||
|
||||
def get_tar_package_url_pypi(name: str, version: str) -> str:
|
||||
url = 'https://pypi.org/pypi/{}/{}/json'.format(name, version)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
body = json.loads(response.read().decode('utf-8'))
|
||||
for ext in ['bz2', 'gz', 'xz', 'zip']:
|
||||
for source in body['urls']:
|
||||
if source['url'].endswith(ext):
|
||||
return source['url']
|
||||
err = 'Failed to get {}-{} source from {}'.format(name, version, url)
|
||||
raise Exception(err)
|
||||
|
||||
|
||||
def get_package_name(filename: str) -> str:
|
||||
if filename.endswith(('bz2', 'gz', 'xz', 'zip')):
|
||||
segments = filename.split('-')
|
||||
if len(segments) == 2:
|
||||
return segments[0]
|
||||
return '-'.join(segments[:len(segments) - 1])
|
||||
elif filename.endswith('whl'):
|
||||
segments = filename.split('-')
|
||||
if len(segments) == 5:
|
||||
return segments[0]
|
||||
candidate = segments[:len(segments) - 4]
|
||||
# Some packages list the version number twice
|
||||
# e.g. PyQt5-5.15.0-5.15.0-cp35.cp36.cp37.cp38-abi3-manylinux2014_x86_64.whl
|
||||
if candidate[-1] == segments[len(segments) - 4]:
|
||||
return '-'.join(candidate[:-1])
|
||||
return '-'.join(candidate)
|
||||
else:
|
||||
raise Exception(
|
||||
'Downloaded filename: {} does not end with bz2, gz, xz, zip, or whl'.format(filename)
|
||||
)
|
||||
|
||||
|
||||
def get_file_version(filename: str) -> str:
|
||||
name = get_package_name(filename)
|
||||
segments = filename.split(name + '-')
|
||||
version = segments[1].split('-')[0]
|
||||
for ext in ['tar.gz', 'whl', 'tar.xz', 'tar.gz', 'tar.bz2', 'zip']:
|
||||
version = version.replace('.' + ext, '')
|
||||
return version
|
||||
|
||||
|
||||
def get_file_hash(filename: str) -> str:
|
||||
sha = hashlib.sha256()
|
||||
print('Generating hash for', filename.split('/')[-1])
|
||||
with open(filename, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(1024 * 1024 * 32)
|
||||
if not data:
|
||||
break
|
||||
sha.update(data)
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def download_tar_pypi(url: str, tempdir: str) -> None:
|
||||
with urllib.request.urlopen(url) as response:
|
||||
file_path = os.path.join(tempdir, url.split('/')[-1])
|
||||
with open(file_path, 'x+b') as tar_file:
|
||||
shutil.copyfileobj(response, tar_file)
|
||||
|
||||
|
||||
def parse_continuation_lines(fin):
|
||||
for line in fin:
|
||||
line = line.rstrip('\n')
|
||||
while line.endswith('\\'):
|
||||
try:
|
||||
line = line[:-1] + next(fin).rstrip('\n')
|
||||
except StopIteration:
|
||||
exit('Requirements have a wrong number of line continuation characters "\\"')
|
||||
yield line
|
||||
|
||||
|
||||
def fprint(string: str) -> None:
|
||||
separator = '=' * 72 # Same as `flatpak-builder`
|
||||
print(separator)
|
||||
print(string)
|
||||
print(separator)
|
||||
|
||||
|
||||
packages = []
|
||||
if opts.requirements_file:
|
||||
requirements_file = os.path.expanduser(opts.requirements_file)
|
||||
try:
|
||||
with open(requirements_file, 'r') as req_file:
|
||||
reqs = parse_continuation_lines(req_file)
|
||||
reqs_as_str = '\n'.join([r.split('--hash')[0] for r in reqs])
|
||||
packages = list(requirements.parse(reqs_as_str))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
elif opts.packages:
|
||||
packages = list(requirements.parse('\n'.join(opts.packages)))
|
||||
with tempfile.NamedTemporaryFile('w', delete=False, prefix='requirements.') as req_file:
|
||||
req_file.write('\n'.join(opts.packages))
|
||||
requirements_file = req_file.name
|
||||
else:
|
||||
exit('Please specifiy either packages or requirements file argument')
|
||||
|
||||
for i in packages:
|
||||
if i["name"].lower().startswith("pyqt"):
|
||||
print("PyQt packages are not supported by flapak-pip-generator")
|
||||
print("However, there is a BaseApp for PyQt available, that you should use")
|
||||
print("Visit https://github.com/flathub/com.riverbankcomputing.PyQt.BaseApp for more information")
|
||||
sys.exit(0)
|
||||
|
||||
with open(requirements_file, 'r') as req_file:
|
||||
use_hash = '--hash=' in req_file.read()
|
||||
|
||||
python_version = '2' if opts.python2 else '3'
|
||||
if opts.python2:
|
||||
pip_executable = 'pip2'
|
||||
else:
|
||||
pip_executable = 'pip3'
|
||||
|
||||
if opts.runtime:
|
||||
flatpak_cmd = [
|
||||
'flatpak',
|
||||
'--devel',
|
||||
'--share=network',
|
||||
'--filesystem=/tmp',
|
||||
'--command={}'.format(pip_executable),
|
||||
'run',
|
||||
opts.runtime
|
||||
]
|
||||
if opts.requirements_file:
|
||||
requirements_file = os.path.expanduser(opts.requirements_file)
|
||||
if os.path.exists(requirements_file):
|
||||
prefix = os.path.realpath(requirements_file)
|
||||
flag = '--filesystem={}'.format(prefix)
|
||||
flatpak_cmd.insert(1,flag)
|
||||
else:
|
||||
flatpak_cmd = [pip_executable]
|
||||
|
||||
if opts.output:
|
||||
output_package = opts.output
|
||||
elif opts.requirements_file:
|
||||
output_package = 'python{}-{}'.format(
|
||||
python_version,
|
||||
os.path.basename(opts.requirements_file).replace('.txt', ''),
|
||||
)
|
||||
elif len(packages) == 1:
|
||||
output_package = 'python{}-{}'.format(
|
||||
python_version, packages[0].name,
|
||||
)
|
||||
else:
|
||||
output_package = 'python{}-modules'.format(python_version)
|
||||
if opts.yaml:
|
||||
output_filename = output_package + '.yaml'
|
||||
else:
|
||||
output_filename = output_package + '.json'
|
||||
|
||||
modules = []
|
||||
vcs_modules = []
|
||||
sources = {}
|
||||
|
||||
tempdir_prefix = 'pip-generator-{}'.format(os.path.basename(output_package))
|
||||
with tempfile.TemporaryDirectory(prefix=tempdir_prefix) as tempdir:
|
||||
pip_download = flatpak_cmd + [
|
||||
'download',
|
||||
'--exists-action=i',
|
||||
'--dest',
|
||||
tempdir,
|
||||
'-r',
|
||||
requirements_file
|
||||
]
|
||||
if use_hash:
|
||||
pip_download.append('--require-hashes')
|
||||
|
||||
fprint('Downloading sources')
|
||||
cmd = ' '.join(pip_download)
|
||||
print('Running: "{}"'.format(cmd))
|
||||
try:
|
||||
subprocess.run(pip_download, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print('Failed to download')
|
||||
print('Please fix the module manually in the generated file')
|
||||
|
||||
if not opts.requirements_file:
|
||||
try:
|
||||
os.remove(requirements_file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
fprint('Downloading arch independent packages')
|
||||
for filename in os.listdir(tempdir):
|
||||
if not filename.endswith(('bz2', 'any.whl', 'gz', 'xz', 'zip')):
|
||||
version = get_file_version(filename)
|
||||
name = get_package_name(filename)
|
||||
url = get_tar_package_url_pypi(name, version)
|
||||
print('Deleting', filename)
|
||||
try:
|
||||
os.remove(os.path.join(tempdir, filename))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
print('Downloading {}'.format(url))
|
||||
download_tar_pypi(url, tempdir)
|
||||
|
||||
files = {get_package_name(f): [] for f in os.listdir(tempdir)}
|
||||
|
||||
for filename in os.listdir(tempdir):
|
||||
name = get_package_name(filename)
|
||||
files[name].append(filename)
|
||||
|
||||
# Delete redundant sources, for vcs sources
|
||||
for name in files:
|
||||
if len(files[name]) > 1:
|
||||
zip_source = False
|
||||
for f in files[name]:
|
||||
if f.endswith('.zip'):
|
||||
zip_source = True
|
||||
if zip_source:
|
||||
for f in files[name]:
|
||||
if not f.endswith('.zip'):
|
||||
try:
|
||||
os.remove(os.path.join(tempdir, f))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
vcs_packages = {
|
||||
x.name: {'vcs': x.vcs, 'revision': x.revision, 'uri': x.uri}
|
||||
for x in packages
|
||||
if x.vcs
|
||||
}
|
||||
|
||||
fprint('Obtaining hashes and urls')
|
||||
for filename in os.listdir(tempdir):
|
||||
name = get_package_name(filename)
|
||||
sha256 = get_file_hash(os.path.join(tempdir, filename))
|
||||
|
||||
if name in vcs_packages:
|
||||
uri = vcs_packages[name]['uri']
|
||||
revision = vcs_packages[name]['revision']
|
||||
vcs = vcs_packages[name]['vcs']
|
||||
url = 'https://' + uri.split('://', 1)[1]
|
||||
s = 'commit'
|
||||
if vcs == 'svn':
|
||||
s = 'revision'
|
||||
source = OrderedDict([
|
||||
('type', vcs),
|
||||
('url', url),
|
||||
(s, revision),
|
||||
])
|
||||
is_vcs = True
|
||||
else:
|
||||
url = get_pypi_url(name, filename)
|
||||
source = OrderedDict([
|
||||
('type', 'file'),
|
||||
('url', url),
|
||||
('sha256', sha256)])
|
||||
if opts.checker_data:
|
||||
source['x-checker-data'] = {
|
||||
'type': 'pypi',
|
||||
'name': name}
|
||||
if url.endswith(".whl"):
|
||||
source['x-checker-data']['packagetype'] = 'bdist_wheel'
|
||||
is_vcs = False
|
||||
sources[name] = {'source': source, 'vcs': is_vcs}
|
||||
|
||||
# Python3 packages that come as part of org.freedesktop.Sdk.
|
||||
system_packages = ['cython', 'easy_install', 'mako', 'markdown', 'meson', 'pip', 'pygments', 'setuptools', 'six', 'wheel']
|
||||
|
||||
fprint('Generating dependencies')
|
||||
for package in packages:
|
||||
|
||||
if package.name is None:
|
||||
print('Warning: skipping invalid requirement specification {} because it is missing a name'.format(package.line), file=sys.stderr)
|
||||
print('Append #egg=<pkgname> to the end of the requirement line to fix', file=sys.stderr)
|
||||
continue
|
||||
elif package.name.casefold() in system_packages:
|
||||
print(f"{package.name} is in system_packages. Skipping.")
|
||||
continue
|
||||
|
||||
if len(package.extras) > 0:
|
||||
extras = '[' + ','.join(extra for extra in package.extras) + ']'
|
||||
else:
|
||||
extras = ''
|
||||
|
||||
version_list = [x[0] + x[1] for x in package.specs]
|
||||
version = ','.join(version_list)
|
||||
|
||||
if package.vcs:
|
||||
revision = ''
|
||||
if package.revision:
|
||||
revision = '@' + package.revision
|
||||
pkg = package.uri + revision + '#egg=' + package.name
|
||||
else:
|
||||
pkg = package.name + extras + version
|
||||
|
||||
dependencies = []
|
||||
# Downloads the package again to list dependencies
|
||||
|
||||
tempdir_prefix = 'pip-generator-{}'.format(package.name)
|
||||
with tempfile.TemporaryDirectory(prefix='{}-{}'.format(tempdir_prefix, package.name)) as tempdir:
|
||||
pip_download = flatpak_cmd + [
|
||||
'download',
|
||||
'--exists-action=i',
|
||||
'--dest',
|
||||
tempdir,
|
||||
]
|
||||
try:
|
||||
print('Generating dependencies for {}'.format(package.name))
|
||||
subprocess.run(pip_download + [pkg], check=True, stdout=subprocess.DEVNULL)
|
||||
for filename in sorted(os.listdir(tempdir)):
|
||||
dep_name = get_package_name(filename)
|
||||
if dep_name.casefold() in system_packages:
|
||||
continue
|
||||
dependencies.append(dep_name)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
print('Failed to download {}'.format(package.name))
|
||||
|
||||
is_vcs = True if package.vcs else False
|
||||
package_sources = []
|
||||
for dependency in dependencies:
|
||||
if dependency in sources:
|
||||
source = sources[dependency]
|
||||
elif dependency.replace('_', '-') in sources:
|
||||
source = sources[dependency.replace('_', '-')]
|
||||
else:
|
||||
continue
|
||||
|
||||
if not (not source['vcs'] or is_vcs):
|
||||
continue
|
||||
|
||||
package_sources.append(source['source'])
|
||||
|
||||
if package.vcs:
|
||||
name_for_pip = '.'
|
||||
else:
|
||||
name_for_pip = pkg
|
||||
|
||||
module_name = 'python{}-{}'.format(python_version, package.name)
|
||||
|
||||
pip_command = [
|
||||
pip_executable,
|
||||
'install',
|
||||
'--verbose',
|
||||
'--exists-action=i',
|
||||
'--no-index',
|
||||
'--find-links="file://${PWD}"',
|
||||
'--prefix=${FLATPAK_DEST}',
|
||||
'"{}"'.format(name_for_pip)
|
||||
]
|
||||
if package.name in opts.ignore_installed:
|
||||
pip_command.append('--ignore-installed')
|
||||
if not opts.build_isolation:
|
||||
pip_command.append('--no-build-isolation')
|
||||
|
||||
module = OrderedDict([
|
||||
('name', module_name),
|
||||
('buildsystem', 'simple'),
|
||||
('build-commands', [' '.join(pip_command)]),
|
||||
('sources', package_sources),
|
||||
])
|
||||
if opts.cleanup == 'all':
|
||||
module['cleanup'] = ['*']
|
||||
elif opts.cleanup == 'scripts':
|
||||
module['cleanup'] = ['/bin', '/share/man/man1']
|
||||
|
||||
if package.vcs:
|
||||
vcs_modules.append(module)
|
||||
else:
|
||||
modules.append(module)
|
||||
|
||||
modules = vcs_modules + modules
|
||||
if len(modules) == 1:
|
||||
pypi_module = modules[0]
|
||||
else:
|
||||
pypi_module = {
|
||||
'name': output_package,
|
||||
'buildsystem': 'simple',
|
||||
'build-commands': [],
|
||||
'modules': modules,
|
||||
}
|
||||
|
||||
print()
|
||||
with open(output_filename, 'w') as output:
|
||||
if opts.yaml:
|
||||
class OrderedDumper(yaml.Dumper):
|
||||
def increase_indent(self, flow=False, indentless=False):
|
||||
return super(OrderedDumper, self).increase_indent(flow, False)
|
||||
|
||||
def dict_representer(dumper, data):
|
||||
return dumper.represent_dict(data.items())
|
||||
|
||||
OrderedDumper.add_representer(OrderedDict, dict_representer)
|
||||
|
||||
output.write("# Generated with flatpak-pip-generator " + " ".join(sys.argv[1:]) + "\n")
|
||||
yaml.dump(pypi_module, output, Dumper=OrderedDumper)
|
||||
else:
|
||||
output.write(json.dumps(pypi_module, indent=4))
|
||||
print('Output saved to {}'.format(output_filename))
|
38
main.rs
Normal file
38
main.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cur_keys: Keys = Keys::generate();
|
||||
let my_keys = Keys::from_sk_str("41c0b042b0c34a3eea15d94b3e2ebba00d5d66d1660404c2ea4cebe419c637a3")?;
|
||||
let cur_b32_pk: String = cur_keys.public_key().to_bech32()?;
|
||||
println!("Your current Bech32 PubKey: {} ! enjoy your stay!!", cur_b32_pk);
|
||||
let relay = Client::new(&my_keys);
|
||||
let proxy = Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9050)));
|
||||
let client = Client::new(&cur_keys);
|
||||
println!("Dear user, please Execute PATCH.sh accordingly. =)");
|
||||
client.add_relay("wss://relay.damus.io", None).await?;
|
||||
client.add_relay("wss://relay.nostr.info", proxy).await?;
|
||||
client.add_relay(
|
||||
"ws://jgqaglhautb4k6e6i2g34jakxiemqp6z4wynlirltuukgkft2xuglmqd.onion",
|
||||
proxy,
|
||||
).await?;
|
||||
println!("[+] Default relays added!");
|
||||
client.connect().await;
|
||||
println!("[+] Connected to subscrubted relays!");
|
||||
let metadata = Metadata::new()
|
||||
.name("Nikhil Aryal")
|
||||
.display_name("|| Prof. - Xadk3!#0000 ||")
|
||||
.about("https://is.gd/gh_xadke")
|
||||
.picture(Url::parse("https://i.postimg.cc/mgCZgXQ3/MOSHED-2023-3-6-21-5-48.gif")?)
|
||||
.banner(Url::parse("https://raw.githubusercontent.com/prof-xadk3/prof-xadk3.github.io/main/.well-known/.real-eyes.realize.real-lies%7Ez.sh/ezgif-5-71714e8ace.webp")?)
|
||||
.nip05("profxadke@member.cash")
|
||||
.lud16("prof-xadk3@getalby.com");
|
||||
client.set_metadata(metadata).await?;
|
||||
client.publish_text_note("Greetz! from NostrLand!", &[]).await?;
|
||||
let event_id = EventId::from_bech32("note00000000000000000000000000000000000000000000000000000000000")?;
|
||||
let public_key = XOnlyPublicKey::from_bech32("npub15mf4j4qu5z2g7mhyyan0cx40ra9stnkrp93s0we55f74tlv550nqxht82x")?;
|
||||
let event: Event = EventBuilder::new_reaction(event_id, public_key, "💜").to_event(&my_keys)?;
|
||||
client.send_event_to("wss://relay.damus.io", event).await?;
|
||||
Ok(())
|
||||
}
|
BIN
nostringaSar
Executable file
BIN
nostringaSar
Executable file
Binary file not shown.
9927
picheTohDekho2x.js
Normal file
9927
picheTohDekho2x.js
Normal file
File diff suppressed because it is too large
Load diff
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
fastapi
|
||||
websockets
|
|
@ -1,6 +1,10 @@
|
|||
# OpenAsar Roadmap
|
||||
These are rough milestones for OpenAsar progress over original Discord's asar.
|
||||
|
||||
## Nick's Milestone use all social media under single electron `/proc/?/..`
|
||||
- [ ] Tend to wrap implementation(s). + lib(s) with plus you can see, view, edit, modify
|
||||
- [ ] For Own Security Make sure your 2FA/otpclient is working all fine!
|
||||
|
||||
## Milestone 3 - complete rewrite
|
||||
- [X] Self-write 100% of code:
|
||||
- [X] Updater
|
||||
|
|
BIN
scripts/app.asar
Normal file
BIN
scripts/app.asar
Normal file
Binary file not shown.
16
scripts/pkgoa.sh
Executable file
16
scripts/pkgoa.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm *.st
|
||||
PLATFORM="linux" # osx (for dmg)
|
||||
axel -an 9 "https://discordapp.com/api/download/ptb?platform=linux" -o discord-ptb.deb # win (for exe)
|
||||
fakeroot sh -c '
|
||||
mkdir tmp
|
||||
dpkg-deb -R discord-ptb.deb tmp
|
||||
# edit DEBIAN/postinst # mv .?app.asar ./app.asar
|
||||
cp -f ./app.asar tmp/usr/share/discord-ptb/resources/app.asar
|
||||
dpkg-deb -b tmp discord-0.AP.deb
|
||||
rm -rf tmp
|
||||
'
|
||||
rm discord-ptb.deb
|
||||
gdebi discord-0.AP.deb
|
||||
rm *.deb
|
4
scripts/prepare_deps.sh
Executable file
4
scripts/prepare_deps.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
# NOTE: quoting below dollar expansion would be new line wala.
|
||||
sudo apt install $(<../deps.txt) -y
|
Loading…
Reference in a new issue