Compare commits

...

16 Commits
main ... kali

Author SHA1 Message Date
|| Prof. - Xadk3!#0000 || @naryal2580 ccc5f78652 fix dl bs. 2023-05-29 22:07:06 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 23219a5e70 deps+sh 2023-05-23 10:29:52 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 0d16d3d3ae -silent+PATCH:+ADD++ 2023-05-23 10:25:02 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 c7923f9fab silent+PATCH:+ADD+ 2023-05-22 21:46:02 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 9743775dda silent+note:ADD 2023-05-22 21:42:26 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 083065721f pkgInst 2023-05-22 21:41:51 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 7958464d6e background.js 2023-05-22 20:40:39 +05:30
|| Prof. - Xadk3!#0000 || @naryal2580 780ad9a200 add.[ALL] 2023-05-21 16:28:12 +05:30
|| Prof. - Xadk3! 5ac24c8cea Merge branch 'main' into kali 2023-04-14 07:07:12 +00:00
|| Prof. - Xadk3! 9d7a050bb6 misc. updates 2023-04-14 07:00:03 +00:00
|| Prof. - Xadk3! 73924c3f4b Merge branch 'kali' of https://gitdab.com/naryal2580/OpenAsar into kali 2023-04-13 20:44:09 +00:00
|| Prof. - Xadk3! 1ebb037c2c update roadmap 2023-04-13 20:43:34 +00:00
|| Prof. - Xadk3! 7b6ea86317 gen url source link :| 2023-04-13 20:43:11 +00:00
Prof. - Xadk3! ebb19e63c0 gen url source link :| 2023-04-13 14:17:17 +00:00
Prof. - Xadk3! 8cbf89c729 bit intro to roadmap. =) 2023-04-13 13:36:45 +00:00
Prof. - Xadk3! 9596304b38 add asar init files 2023-04-13 13:26:10 +00:00
68 changed files with 15598 additions and 1 deletions

6
.gitignore vendored
View File

@ -4,4 +4,8 @@ src/package-lock.json
_*
miniSrc/
*.crswap # crostini tmp files
*.crswap # crostini tmp files
# Added by cargo
/target

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

View 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];
}
};

View 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;
}

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

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

View File

@ -0,0 +1,3 @@
"use strict";
module.exports = require('./' + process.platform);

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

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

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

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

View 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"
]

View 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++;
}
}

View File

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.performFirstRunTasks = performFirstRunTasks;
function performFirstRunTasks(_updater) {
//
}

View File

@ -0,0 +1,3 @@
"use strict";
module.exports = require('./' + process.platform);

View File

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.performFirstRunTasks = performFirstRunTasks;
function performFirstRunTasks(_updater) {
//
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View 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');
}

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

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

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

View 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

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
{
"width": "300px",
"height": "300px",
"inDuration": 700,
"outDuration": 333
}

View 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;
}

View 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');
}
});

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

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

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

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

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

Binary file not shown.

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

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

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

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

View 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;
}

View 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]));
}
}
}

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

View 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;
}

View 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;
}

View 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;
}
}

View 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;
}

View 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>;
}

View 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
};

View 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"
}
}

View File

@ -0,0 +1,7 @@
{
"discord_desktop_core": 0,
"discord_erlpack": 0,
"discord_spellcheck": 0,
"discord_utils": 0,
"discord_voice": 0
}

View File

@ -0,0 +1,4 @@
{
"releaseChannel": "stable",
"version": "0.0.27"
}

1370
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
axel
fakeroot
otpclient-cli
gdebi

470
flatpak-pip-generator.py Executable file
View 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
View 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

Binary file not shown.

9927
picheTohDekho2x.js Normal file

File diff suppressed because it is too large Load Diff

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
fastapi
websockets

View File

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

Binary file not shown.

16
scripts/pkgoa.sh Executable file
View 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
View File

@ -0,0 +1,4 @@
#!/bin/bash
# NOTE: quoting below dollar expansion would be new line wala.
sudo apt install $(<../deps.txt) -y