import { app, ipcMain, IpcMainEvent, remote, Tray, Menu, dialog } from 'electron'; import * as path from 'path'; import * as url from 'url'; import * as fse from 'fs-extra'; import * as fs from 'fs'; import { AppWindow } from './app/AppWindow'; import { now } from './util/now'; import { showUncaughtException } from './crash/show-uncaught-exception'; import { UpdaterChannel, FileChannel, IdleStateChannel, NotificationChannel, ChatChannel, MessengerChannel } from '@ucap-webmessenger/native-electron'; import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification'; import { root } from './util/root'; import { DefaultFolder } from './lib/default-folder'; import { FileUtil } from './lib/file-util'; import { IdleChecker } from './lib/idle-checker'; import { NotificationRequest } from '@ucap-webmessenger/native'; import { ElectronAppChannel } from '@ucap-webmessenger/electron-core'; const appIconPath = __LINUX__ ? path.join(__dirname, 'static', 'icon-logo.png') : path.join(__dirname, 'resources/image', 'ico_64_64.png'); let appWindow: AppWindow | null = null; let appTray: Tray | null = null; const launchTime = now(); let readyTime: number | null = null; type OnDidLoadFn = (window: AppWindow) => void; let onDidLoadFns: Array | null = []; let preventQuit = false; let notificationService: ElectronNotificationService | null; function handleUncaughtException(error: Error) { preventQuit = true; // If we haven't got a window we'll assume it's because // we've just launched and haven't created it yet. // It could also be because we're encountering an unhandled // exception on shutdown but that's less likely and since // this only affects the presentation of the crash dialog // it's a safe assumption to make. const isLaunchError = appWindow === null; if (appWindow) { appWindow.destroy(); appWindow = null; } showUncaughtException(isLaunchError, error); } function getUptimeInSeconds() { return (now() - launchTime) / 1000; } process.on('uncaughtException', (error: Error) => { // error = withSourceMappedStack(error); // reportError(error, getExtraErrorContext()); handleUncaughtException(error); }); let isDuplicateInstance = false; const gotSingleInstanceLock = app.requestSingleInstanceLock(); isDuplicateInstance = !gotSingleInstanceLock; let idle: IdleChecker | null; app.on(ElectronAppChannel.SecondInstance, (event, args, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (appWindow) { if (appWindow.isMinimized()) { appWindow.restore(); } if (!appWindow.isVisible()) { appWindow.show(); } appWindow.focus(); } }); if (isDuplicateInstance) { app.quit(); } function createWindow() { const window = new AppWindow(appIconPath); if (__DEV__) { // const { // default: installExtension, // REDUX_DEVTOOLS // } = require('electron-devtools-installer'); import('electron-debug').then(ed => { ed.default({ showDevTools: true }); }); import('electron-devtools-installer').then(edi => { const ChromeLens = { id: 'idikgljglpfilbhaboonnpnnincjhjkd', electron: '>=1.2.1' }; const extensions = [edi.REDUX_DEVTOOLS, ChromeLens]; for (const extension of extensions) { try { edi.default(extension); } catch (e) { console.log(e); } } }); } window.onClose(() => { appWindow = null; if (!__DARWIN__ && !preventQuit) { app.quit(); } }); window.onDidLoad(() => { window.show(); const fns = onDidLoadFns; onDidLoadFns = null; for (const fn of fns) { fn(window); } }); window.load(); appWindow = window; } function createTray() { appTray = new Tray(appIconPath); const contextMenu = Menu.buildFromTemplate([ { label: '로그아웃', // accelerator: 'Q', // selector: 'terminate:', click: () => { appWindow.browserWindow.webContents.send(MessengerChannel.Logout); } }, { label: '설정', // accelerator: 'Q', // selector: 'terminate:', click: () => { appWindow.show(); appWindow.browserWindow.webContents.send(MessengerChannel.ShowSetting); } }, { label: '버전', submenu: [{ label: 'Ver. ' + app.getVersion() }] }, { label: '종료', // accelerator: 'Q', // selector: 'terminate:', click: () => { // 메신저에 로그아웃 후 종료 appWindow = null; app.exit(); } } ]); appTray.setToolTip('UCapMessenger'); appTray.setContextMenu(contextMenu); appTray.on('click', () => { appWindow.isVisible() ? appWindow.hide() : appWindow.show(); }); } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on(ElectronAppChannel.Ready, () => { if (isDuplicateInstance) { return; } readyTime = now() - launchTime; createWindow(); createTray(); notificationService = new ElectronNotificationService({ width: 340, height: 100, padding: 0, borderRadius: 0, // appIcon: iconPath, displayTime: 5000, defaultStyleContainer: {}, defaultStyleAppIcon: { display: 'none' }, defaultStyleImage: {}, defaultStyleClose: {}, defaultStyleText: {} }); notificationService.options.defaultWindow.webPreferences.preload = path.join( __dirname, 'resources/notification/preload.js' ); notificationService.templatePath = path.join( __dirname, 'resources/notification/template.html' ); ipcMain.on('uncaught-exception', (event: IpcMainEvent, error: Error) => { handleUncaughtException(error); }); ipcMain.on( 'send-error-report', ( event: IpcMainEvent, { error, extra }: { error: Error; extra: { [key: string]: string } } ) => {} ); }); // Quit when all windows are closed. app.on(ElectronAppChannel.WindowAllClosed, () => { // 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(); } }); app.on(ElectronAppChannel.Activate, () => { onDidLoad(window => { window.show(); }); }); function onDidLoad(fn: OnDidLoadFn) { if (onDidLoadFns) { onDidLoadFns.push(fn); } else { if (appWindow) { fn(appWindow); } } } ipcMain.on(UpdaterChannel.Check, (event: IpcMainEvent, ...args: any[]) => { event.returnValue = false; }); ipcMain.on(FileChannel.ReadFile, (event: IpcMainEvent, ...args: any[]) => { try { fse.readFile(root(args[0]), (err, data) => { if (!!err) { event.returnValue = null; } else { event.returnValue = new Blob([data]); } }); } catch (error) { event.returnValue = null; } }); ipcMain.on( FileChannel.SaveFile, async (event: IpcMainEvent, ...args: any[]) => { try { const buffer: Buffer = args[0]; const fileName: string = args[1]; const mimeType: string = args[2]; let savePath: string = path.join( !!args[3] ? args[3] : DefaultFolder.downloads(), fileName ); savePath = await FileUtil.uniqueFileName(savePath); fse.writeFile(savePath, buffer, err => { if (!err) { event.returnValue = savePath; } else { event.returnValue = undefined; } }); } catch (error) { event.returnValue = undefined; } } ); ipcMain.on( IdleStateChannel.StartCheck, (event: IpcMainEvent, ...args: any[]) => { if (!!idle) { idle.destoryChecker(); idle = null; } idle = new IdleChecker(appWindow.browserWindow); // default 10min idle.startChecker(); } ); ipcMain.on( NotificationChannel.Notify, (event: IpcMainEvent, ...args: any[]) => { const noti: NotificationRequest = args[0]; notificationService.notify({ title: noti.title, text: noti.contents, image: noti.image || path.join(__dirname, 'resources/notification/image/img_nophoto_50.png'), sound: noti.useSound ? path.join( 'file://', __dirname, 'resources/notification/sound/messageAlarm.mp3' ) : '', onClick: e => { appWindow.browserWindow.webContents.send( ChatChannel.OpenRoom, noti.roomSeq ); appWindow.show(); e.close(); } }); } ); ipcMain.on( NotificationChannel.CloseAllNotify, (event: IpcMainEvent, ...args: any[]) => { console.log('Channel.closeAllNotify', args); } );