import { app, BrowserWindow, dialog, Menu, ipcMain, powerSaveBlocker, screen, session, shell } from 'electron' import Datastore from 'nedb' import path from 'path' import cp from 'child_process' if (process.argv.includes('--version')) { console.log(`v${app.getVersion()}`) app.exit() } else { runApp() } function runApp() { require('electron-context-menu')({ showSearchWithGoogle: false, showSaveImageAs: true, showCopyImageAddress: true, prepend: (params, browserWindow) => [] }) const localDataStorage = app.getPath('userData') // Grabs the userdata directory based on the user's OS const settingsDb = new Datastore({ filename: localDataStorage + '/settings.db', autoload: true }) // disable electron warning process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' const isDev = process.env.NODE_ENV === 'development' const isDebug = process.argv.includes('--debug') let mainWindow let openedWindows = [] let startupUrl // CORS somehow gets re-enabled in Electron v9.0.4 // This line disables it. // This line can possible be removed if the issue is fixed upstream app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') app.commandLine.appendSwitch('enable-accelerated-video-decode') app.commandLine.appendSwitch('enable-file-cookies') app.commandLine.appendSwitch('ignore-gpu-blacklist') // See: https://stackoverflow.com/questions/45570589/electron-protocol-handler-not-working-on-windows // remove so we can register each time as we run the app. app.removeAsDefaultProtocolClient('freetube') // If we are running a non-packaged version of the app && on windows if (isDev && process.platform === 'win32') { // Set the path of electron.exe and your app. // These two additional parameters are only available on windows. app.setAsDefaultProtocolClient('freetube', process.execPath, [path.resolve(process.argv[1])]) } else { app.setAsDefaultProtocolClient('freetube') } if (!isDev) { // Only allow single instance of the application const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { app.quit() } app.on('second-instance', (_, commandLine, __) => { // Someone tried to run a second instance, we should focus our window if (mainWindow && typeof commandLine !== 'undefined') { if (mainWindow.isMinimized()) mainWindow.restore() mainWindow.focus() const url = getLinkUrl(commandLine) if (url) { mainWindow.webContents.send('openUrl', url) } } }) } else { require('electron-debug')({ showDevTools: !(process.env.RENDERER_REMOTE_DEBUGGING === 'true') }) } app.on('ready', (_, __) => { settingsDb.find({ $or: [ { _id: 'disableSmoothScrolling' }, { _id: 'useProxy' }, { _id: 'proxyProtocol' }, { _id: 'proxyHostname' }, { _id: 'proxyPort' } ] }, function (err, doc) { if (err) { app.exit() return } let disableSmoothScrolling = false let useProxy = false let proxyProtocol = 'socks5' let proxyHostname = '127.0.0.1' let proxyPort = '9050' if (typeof doc === 'object' && doc.length > 0) { doc.forEach((dbItem) => { switch (dbItem._id) { case 'disableSmoothScrolling': disableSmoothScrolling = dbItem.value break case 'useProxy': useProxy = dbItem.value break case 'proxyProtocol': proxyProtocol = dbItem.value break case 'proxyHostname': proxyHostname = dbItem.value break case 'proxyPort': proxyPort = dbItem.value break } }) } if (disableSmoothScrolling) { app.commandLine.appendSwitch('disable-smooth-scrolling') } else { app.commandLine.appendSwitch('enable-smooth-scrolling') } const proxyUrl = `${proxyProtocol}://${proxyHostname}:${proxyPort}` createWindow(useProxy, proxyUrl) if (isDev) { installDevTools() } if (isDebug) { mainWindow.webContents.openDevTools() } }) }) async function installDevTools() { try { /* eslint-disable */ require('vue-devtools').install() /* eslint-enable */ } catch (err) { console.log(err) } } function createWindow(useProxy = false, proxyUrl = '', replaceMainWindow = true) { /** * Initial window options */ const newWindow = new BrowserWindow({ backgroundColor: '#fff', icon: isDev ? path.join(__dirname, '../../_icons/iconColor.png') /* eslint-disable-next-line */ : `${__dirname}/_icons/iconColor.png`, autoHideMenuBar: true, // useContentSize: true, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: false, webSecurity: false, backgroundThrottling: false, contextIsolation: false }, show: false }) openedWindows.push(newWindow) if (replaceMainWindow) { mainWindow = newWindow } newWindow.setBounds({ width: 1200, height: 800 }) if (useProxy) { session.defaultSession.setProxy({ proxyRules: proxyUrl }) } // Set CONSENT cookie on reasonable domains const consentCookieDomains = [ 'http://www.youtube.com', 'https://www.youtube.com', 'http://youtube.com', 'https://youtube.com' ] consentCookieDomains.forEach(url => { session.defaultSession.cookies.set({ url: url, name: 'CONSENT', value: 'YES+' }) }) settingsDb.findOne({ _id: 'bounds' }, function (err, doc) { if (doc === null || err) { return } if (typeof doc !== 'object' || typeof doc.value !== 'object') { return } const { maximized, ...bounds } = doc.value const allDisplaysSummaryWidth = screen .getAllDisplays() .reduce((accumulator, { size: { width } }) => accumulator + width, 0) if (allDisplaysSummaryWidth >= bounds.x) { newWindow.setBounds({ x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height }) } if (maximized) { newWindow.maximize() } }) // If called multiple times // Duplicate menu items will be added if (replaceMainWindow) { // eslint-disable-next-line setMenu() } // load root file/url if (isDev) { newWindow.loadURL('http://localhost:9080') } else { /* eslint-disable-next-line */ newWindow.loadFile(`${__dirname}/index.html`) global.__static = path .join(__dirname, '/static') .replace(/\\/g, '\\\\') } // Show when loaded newWindow.on('ready-to-show', () => { newWindow.show() newWindow.focus() }) newWindow.on('close', () => { // Clear cache and storage if it's the last window if (openedWindows.length === 1) { session.defaultSession.clearCache() session.defaultSession.clearStorageData({ storages: [ 'appcache', 'cookies', 'filesystem', 'indexdb', 'shadercache', 'websql', 'serviceworkers', 'cachestorage' ] }) } }) newWindow.on('closed', () => { // Remove closed window openedWindows = openedWindows.filter((window) => window !== newWindow) if (newWindow === mainWindow) { // Replace mainWindow to avoid accessing `mainWindow.webContents` // Which raises "Object has been destroyed" error mainWindow = openedWindows[0] } console.log('closed') }) } // Save closing window bounds & maximized status ipcMain.on('setBounds', (event) => { const value = { ...mainWindow.getNormalBounds(), maximized: mainWindow.isMaximized() } settingsDb.update( { _id: 'bounds' }, { _id: 'bounds', value }, { upsert: true }, () => { event.returnValue = 0 } ) }) ipcMain.on('appReady', () => { if (startupUrl) { mainWindow.webContents.send('openUrl', startupUrl) } }) ipcMain.on('disableSmoothScrolling', () => { app.commandLine.appendSwitch('disable-smooth-scrolling') mainWindow.close() createWindow() }) ipcMain.on('enableSmoothScrolling', () => { app.commandLine.appendSwitch('enable-smooth-scrolling') mainWindow.close() createWindow() }) ipcMain.on('enableProxy', (_, url) => { console.log(url) session.defaultSession.setProxy({ proxyRules: url }) }) ipcMain.on('disableProxy', () => { session.defaultSession.setProxy({}) }) ipcMain.on('openExternalLink', (_, url) => { if (typeof url === 'string') shell.openExternal(url) }) ipcMain.handle('getLocale', () => { return app.getLocale() }) ipcMain.handle('getUserDataPath', () => { return app.getPath('userData') }) ipcMain.on('getUserDataPathSync', (event) => { event.returnValue = app.getPath('userData') }) ipcMain.handle('showOpenDialog', async (_, options) => { return await dialog.showOpenDialog(options) }) ipcMain.handle('showSaveDialog', async (_, options) => { return await dialog.showSaveDialog(options) }) ipcMain.on('stopPowerSaveBlocker', (_, id) => { powerSaveBlocker.stop(id) }) ipcMain.handle('startPowerSaveBlocker', (_, type) => { return powerSaveBlocker.start(type) }) ipcMain.on('createNewWindow', () => { createWindow(false, '', false) }) ipcMain.on('syncSetting', (event, setting) => { const otherWindows = openedWindows.filter((window) => { return window.webContents.id !== event.sender.id }) for (const window of otherWindows) { window.webContents.send('syncSetting', setting) } }) ipcMain.on('openInExternalPlayer', (_, payload) => { const child = cp.spawn(payload.executable, payload.args, { detached: true, stdio: 'ignore' }) child.unref() }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (mainWindow === null || mainWindow === undefined) { createWindow() } }) /* * Callback when processing a freetube:// link (macOS) */ app.on('open-url', (event, url) => { event.preventDefault() if (mainWindow && mainWindow.webContents) { mainWindow.webContents.send('openUrl', baseUrl(url)) } else { startupUrl = baseUrl(url) } }) /* * Check if an argument was passed and send it over to the GUI (Linux / Windows). * Remove freetube:// protocol if present */ const url = getLinkUrl(process.argv) if (url) { startupUrl = url } function baseUrl(arg) { return arg.replace('freetube://', '') } function getLinkUrl(argv) { if (argv.length > 1) { return baseUrl(argv[argv.length - 1]) } else { return null } } /** * Auto Updater * * Uncomment the following code below and install `electron-updater` to * support auto updating. Code Signing with a valid certificate is required. * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating */ /* import { autoUpdater } from 'electron-updater' autoUpdater.on('update-downloaded', () => { autoUpdater.quitAndInstall() }) app.on('ready', () => { if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() }) */ /* eslint-disable-next-line */ const sendMenuEvent = async data => { mainWindow.webContents.send('change-view', data) } function setMenu() { const template = [ { label: 'File', submenu: [{ role: 'quit' }] }, { label: 'Edit', submenu: [ { role: 'cut' }, { role: 'copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, { role: 'paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, { role: 'pasteandmatchstyle' }, { role: 'delete' }, { role: 'selectall' } ] }, { label: 'View', submenu: [ { role: 'reload' }, { role: 'forcereload', accelerator: 'CmdOrCtrl+Shift+R' }, { role: 'toggledevtools' }, { type: 'separator' }, { role: 'resetzoom' }, { role: 'zoomin' }, { role: 'zoomout' }, { type: 'separator' }, { role: 'togglefullscreen' } ] }, { role: 'window', submenu: [ { role: 'minimize' }, { role: 'close' } ] } ] if (process.platform === 'darwin') { template.unshift({ label: app.getName(), submenu: [ { role: 'about' }, { type: 'separator' }, { role: 'services' }, { type: 'separator' }, { role: 'hide' }, { role: 'hideothers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' } ] }) template.push( { role: 'window' }, { role: 'help' }, { role: 'services' } ) } const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } }