src/index.js
import { app, BrowserWindow } from 'electron';
import path from 'path';
import ua from 'universal-analytics';
import uuid from 'uuid';
import winston from 'winston';
import configureApp from './main/configureApp';
import generateBrowserConfig from './main/configureBrowser';
import { positionOnScreen } from './_util';
import EmitterClass from './main/utils/Emitter';
import SettingsClass from './main/utils/Settings';
import WindowManagerClass from './main/utils/WindowManager';
import PlaybackAPIClass from './main/utils/PlaybackAPI';
import I3IpcHelperClass from './main/utils/I3IpcHelper';
import handleStartupEvent from './squirrel';
import { updateShortcuts } from './main/utils/_shortcutManager';
app.setAppUserModelId('com.marshallofsound.gpmdp.core');
(() => {
if (handleStartupEvent()) {
return;
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow = null;
// DEV: Make the app single instance
const gotLock = app.requestSingleInstanceLock();
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
mainWindow.show();
mainWindow.setSkipTaskbar(false);
if (app.dock && app.dock.show) app.dock.show();
}
});
if (!gotLock) {
app.quit();
return;
}
if (process.env['TEST_SPEC']) { // eslint-disable-line
global.Settings = new SettingsClass('.test', true);
} else {
global.Settings = new SettingsClass();
}
global.DEV_MODE = process.env['TEST_SPEC'] || process.argv.some(arg => arg === '--development') || process.argv.some(arg => arg === '--dev') || Settings.get('_alwaysDevMode'); // eslint-disable-line
updateShortcuts();
// Initialize the logger with some default logging levels.
const defaultFileLogLevel = 'info';
const defaultConsoleLogLevel = global.DEV_MODE ? 'debug' : 'error';
global.Logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({
filename: path.resolve(app.getPath('userData'), 'gpmdp.log'),
level: defaultFileLogLevel,
maxsize: 5000000,
maxfiles: 2,
}),
new (winston.transports.Console)({
level: defaultConsoleLogLevel,
}),
],
});
Logger.info('Application started.');
configureApp(app);
global.Emitter = new EmitterClass();
global.WindowManager = new WindowManagerClass();
global.PlaybackAPI = new PlaybackAPIClass();
// UA for GA
// This is for user reporting
Settings.set('uuid', Settings.get('uuid', uuid.v4()));
const user = ua('UA-44220619-5', Settings.get('uuid'));
user.pageview(`/${app.getVersion()}`).send();
// Replace the logger's levels with those from settings.
Logger.transports.console.level = Settings.get('consoleLogLevel', defaultConsoleLogLevel);
Logger.transports.file.level = Settings.get('fileLogLevel', defaultFileLogLevel);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', () => {
// Attempt to install React Developer Tools
if (global.DEV_MODE) {
try {
const devtoolsInstaller = require('electron-devtools-installer');
devtoolsInstaller.default(devtoolsInstaller.REACT_DEVELOPER_TOOLS).catch(() => null);
} catch (err) {
// Whoe cares
}
}
mainWindow = new BrowserWindow(generateBrowserConfig());
const signInUserAgent = Settings.get('overrideSignInUserAgent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0');
// Spoof the user agent, this allows for future work arounds
if (Settings.get('overrideUserAgent')) {
mainWindow.webContents.session.setUserAgent('overrideUserAgent');
}
// Intercept all requests to accounts.google.com and hijack the UA
mainWindow.webContents.session.webRequest.onBeforeSendHeaders({
urls: ['https://accounts.google.com/*'],
}, (details, callback) => {
const newRequestHeaders = Object.assign({}, (details.requestHeaders || {}), {
'User-Agent': signInUserAgent,
});
callback({ requestHeaders: newRequestHeaders });
});
global.mainWindowID = WindowManager.add(mainWindow, 'main');
const position = Settings.get('position');
const inBounds = positionOnScreen(position);
let size = Settings.get('size');
size = size || [1200, 800];
mainWindow.setSize(...size);
if (position && inBounds) {
mainWindow.setPosition(...position);
} else {
mainWindow.center();
}
if (Settings.get('maximized', false)) {
mainWindow.maximize();
}
// and load the index.html of the app.
mainWindow.loadURL(`file://${__dirname}/public_html/index.html`);
require('./renderer/generic/translations');
require('./main/features');
require('./old_win32');
// Proxy window events through IPC to solve 'webContents destroyed' errors
const proxyWindowEvent = (name) => {
mainWindow.on(name, (...args) => Emitter.sendToGooglePlayMusic(`BrowserWindow:${name}`, ...args));
};
proxyWindowEvent('app-command');
proxyWindowEvent('swipe');
proxyWindowEvent('scroll-touch-begin');
proxyWindowEvent('scroll-touch-end');
// Emitted when the window is closed.
mainWindow.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
PlaybackAPI.reset();
});
// setup i3 listener
const I3IpcHelper = new I3IpcHelperClass();
I3IpcHelper.setupEventListener();
});
app.on('before-quit', () => {
Logger.info('Application exiting...');
global.quitting = true;
});
})();