/* globals require, __dirname, process */ /*eslint no-console: ["error", { allow: ["log"] }]*/ const { app, BrowserWindow, BrowserView, ipcMain, dialog, shell } = require('electron') const electronLocalshortcut = require('electron-localshortcut') //const electron = require('electron') const os = require('os') const fs = require('fs') const path = require('path') const { URL } = require('url') const Store = require('./store.js') const { prettyPrint } = require('html') // Use this constant to start the application in kiosk-mode or in development mode const DEVELOPMENT = true // true: Dev-Tools are open // false (KIOSK-Mode): No application switcher, no menu, no taskbar (or dock on a mac), shortcuts are working global.multiUserMode = true global.errorCount = 0 global.stopTestsOnError = false global.jsonData = { value: null } // UO: Experimental feature using Native Windows global.useBrowserView = false global.useMinimalPad = true global.menu = null global.observeTraffic = false // 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 win let browsers = new Map() // url: BrowserWindow const store = new Store({ // We'll call our data file 'user-preferences' configName: 'user-preferences', defaults: { url: `file://${__dirname}/index.html`, devTools: DEVELOPMENT, multiUserBrowser: true } }) function createWindow() { if (global.observeTraffic) { const {session} = require('electron') session.defaultSession.webRequest.onCompleted((details) => { console.log("onCompleted", details.url) }) } let { screen } = require('electron') let bounds = store.get('storedBounds') ? store.get('storedBounds') : screen.getPrimaryDisplay().bounds // let displays = screen.getAllDisplays() // let externalDisplay = null // externalDisplay = displays[displays.length-1] // const {width, height} =displays[displays.length-1].workAreaSize // externalDisplay = displays[0] // const {width, height} =displays[0].workAreaSize win = new BrowserWindow({ x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height, fullscreenable: true, fullscreen: !DEVELOPMENT, title: 'IWM Browser', show: false, kiosk: !DEVELOPMENT, acceptFirstMouse: true, webPreferences: { webSecurity: false, allowRunningInsecureContent: true, nodeIntegration: true, webviewTag: true, nativeWindowOpen: true, devTools: true, preload: path.join(__dirname, './preload.js') }, icon: path.join(__dirname, 'assets/icons/png/64x64.png') }) module.exports.win = win let url = store.get('url') if (process.argv.length > 2) { let path = process.argv[2] url = `file://${__dirname}/../${path}` console.log('Using process.argv[2]', url) } console.log('Using', url) win.maximize() // BAD: All other methods don't work (like ensureFileSync, fileExists...) try { let settings = require('./settings.json') console.log('Using settings', `file://${__dirname}/${settings.url}`) win.loadURL(`file://${__dirname}/${settings.url}`) } catch (ex) { win.loadURL(url) } const { webContents } = win // // if (process.platform === 'win32' && win.isKiosk()) { // webContents.on('did-finish-load', function() { // webContents.executeJavaScript('document.body.style.cursor = "none";') // }) // } // Add the app menu let menu = require('./menu.js') global.menu = menu // Add global shortcuts // Esc quits the app electronLocalshortcut.register('Esc', () => { app.quit() }) // Command (Mac) or Control (Win) + K toggles the Kiosk mode electronLocalshortcut.register('CommandOrControl+K', () => { if (win) { win.setKiosk(!win.isKiosk()) } }) // Show if its ready. win.once('ready-to-show', () => { webContents.send('preparePads') win.show() }) // Clear cache webContents.session.clearCache(() => console.log('Cache cleared')) // Open dev tools when in development mode if (store.get('devTools')) { webContents.openDevTools({ mode: 'right' }) } else { webContents.closeDevTools() } webContents.on('devtools-opened', () => { store.set('devTools', true) }) webContents.on('devtools-closed', () => { store.set('devTools', false) }) webContents.on('did-navigate', (event, url) => { menu.setHistoryStatus() }) /* UO: At this point we have no access to the event or link position*/ webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => { console.log('new-window', global.multiUserMode) if (global.multiUserMode) { event.preventDefault() webContents.send('newPad', url, options.x, options.y) } }) // WORKAROUND: On windows, if the app was set to fullscreen, the menubar is not hidden if (win.isKiosk()) { win.setMenuBarVisibility(false) } win.on('focus', event => { menu.focus() }) win.on('blur', event => { menu.blur() }) win.on('enter-full-screen', () => { win.setMenuBarVisibility(false) }) win.on('leave-full-screen', () => { win.setMenuBarVisibility(true) }) win.on('enter-html-full-screen', () => { win.setMenuBarVisibility(false) }) win.on('leave-html-full-screen', () => { win.setMenuBarVisibility(true) }) win.on('close', () => { store.set('storedBounds', win.getBounds()) }) // Emitted when the window is closed. win.on('closed', () => { app.quit() }) } // When work makes progress, show the progress bar function onProgress(progress) { // Use values 0 to 1, or -1 to hide the progress bar try { win.setProgressBar(progress || -1) // Progress bar works on all platforms } catch (e) { if (DEVELOPMENT) console.log(e.message) } } function trySend(target, ...args) { try { target.send(...args) } catch (e) { if (DEVELOPMENT) console.log(e.message) } } function openBrowserView(url, x, y) { const useMinBrowser = false // Change this to switch between Min and a custom browser const minURL = 'file:///Users/uo/devel/min/index.html' const browserURL = `file://${__dirname}/browser.html` let width = 640 let height = 1200 let [winWidth, winHeight] = win.getSize() if (x + width > winWidth) { x = winWidth - width } if (y + height > winHeight) { y = winHeight - height } console.log('open browser view') let browser = new BrowserWindow({ x, y, width, height, minWidth: 320, minHeight: 350, titleBarStyle: useMinBrowser ? 'hidden-inset' : 'hidden', frame: process.platform !== 'win32' }) let browserContents = browser.webContents browser.setAlwaysOnTop(true) if (useMinBrowser) { browserContents.on('did-finish-load', event => { console.log('did-finish-load', browserContents.getURL()) browserContents.executeJavaScript( 'Object.values(window.webviews.elementMap).map(obj => obj.src)', result => { console.log( 'window.webviews', result, url, result.indexOf(url) ) if (result.indexOf(url) == -1) { console.log('Adding tab') browserContents.send('addTab', { url }) } } ) }) browser.loadURL(minURL) } else { console.log('Loading', browserURL) browser.loadURL(browserURL) let view = new BrowserView({ webPreferences: { nodeIntegration: false, devTools: true } }) //browserContents.openDevTools({mode: 'right'}) browser.setBrowserView(view) view.setBounds({ x: 0, y: 24, width: width, height: height - 24 }) view.setAutoResize({ width: true, height: true }) let viewContents = view.webContents let progress = 0 viewContents.on('page-title-set', event => { console.log('page-title-set', event) }) viewContents.on('page-favicon-updated', (event, favicons) => { //console.log("page-favicon-updated", event, favicons) trySend(browserContents, 'favicons', favicons) }) viewContents.on('did-start-loading', event => { onProgress(0) trySend(browserContents, 'progress', 0) trySend(browserContents, 'did-start-loading') //let senderURL = event.sender.getURL() || url //console.log('did-start-loading', senderURL) }) viewContents.on( 'did-get-response-details', ( event, status, newURL, originalURL, httpResponseCode, requestMethod, referrer, headers, resourceType ) => { trySend(browserContents, 'did-get-response-details', { status, newURL, originalURL, httpResponseCode, requestMethod, referrer, headers, resourceType }) progress += 0.01 onProgress(progress) trySend(browserContents, 'progress', progress) //console.log('did-get-response-details', newURL) } ) viewContents.on( 'did-get-redirect-request', ( event, oldURL, newURL, isMainFrame, httpResponseCode, requestMethod, referrer, headers ) => { trySend(browserContents, 'did-get-redirect-request', { oldURL, newURL, isMainFrame, httpResponseCode, requestMethod, referrer, headers }) //console.log('did-get-redirect-request', newURL) } ) viewContents.on('did-stop-loading', event => { //console.log('did-stop-loading', event.sender.getURL()) trySend(browserContents, 'did-stop-loading') }) viewContents.on('did-finish-load', event => { //console.log('did-finish-load', event.sender.getURL()) progress = 1 onProgress(progress) trySend(browserContents, 'progress', progress) }) viewContents.on('dom-ready', event => { if (progress < 0.5) { progress = 0.5 onProgress(progress) trySend(browserContents, 'progress', progress) } viewContents.executeJavaScript('document.title', result => { trySend(browserContents, 'title', result) }) //console.log('dom-ready', event.sender.getURL()) }) viewContents.on('new-window', function (event, url) { event.preventDefault() console.log('new-window') openBrowserView(url, x, y) }) viewContents.loadURL(url) } browsers.set(url, browser) browser.on('closed', e => { for (let [url, browser] of browsers.entries()) { if (browser == e.sender) { browsers.delete(url) console.log('removed browser view', url) } } }) } // UO: Experimental call. Opens a Min Browser window or a limited window with a browser view ipcMain.on('loadBrowserView', (e, opts = {}) => { let { url, x, y } = opts openBrowserView(url, x, y) }) ipcMain.on('multiUserMode', (e, opts = {}) => { global.multiUserMode = opts }) ipcMain.on('padContainerLoaded', e => { win.webContents.send('padContainerAvailable') }) ipcMain.on('createScreenshot', (e, opts = {}) => { opts = Object.assign( {}, { name: `iwmbrowser-${new Date() .toISOString() .replace(/:/g, '-')}.png`, path: os.tmpdir() }, opts ) win.webContents.capturePage(image => { if (image) { let file = path.join(opts.path, opts.name) fs.writeFile(file, image.toPNG(), err => { if (err) { //throw err } else { console.log(`Screenshot saved: ${file}`) } }) } }) }) ipcMain.on('directoryListing', (e, opts = {}) => { let { directory, files, folders } = opts console.log("directoryListing", opts) try { let listing = fs.readdirSync(directory) let result = { directory, files: [], folders: [] } for (let name of listing) { if (name.startsWith('.')) continue let fullPath = path.join(directory, name) let stat = fs.lstatSync(fullPath) if (files && stat.isFile()) { if (typeof files == 'string' && !files.endsWith(files)) continue result.files.push(name) } if (folders && stat.isDirectory()) result.folders.push(name) } e.sender.send('directoryListing', result) } catch (err) { let args = { directory, errorMessage: err.message} e.sender.send('directoryListingError', args) } }) ipcMain.on('createTextfile', (e, opts = {}) => { opts = Object.assign( {}, { name: `iwmbrowser-${new Date() .toISOString() .replace(/:/g, '-')}.txt`, path: os.tmpdir(), text: '' }, opts ) let file = path.join(opts.path, opts.name) fs.writeFile(file, opts.text, err => { if (err) { //throw err } else { console.log(`Textfile saved: ${file}`) } }) }) ipcMain.on('error', e => { console.log('Received error notification') global.errorCount += 1 }) ipcMain.on('openExternal', (e, url=null) => { console.log('Received openExternal', url) if (url) { shell.openExternal(url) } }) ipcMain.on('save', (e, opts = {}) => { let { url, html, saveAs, action } = opts // url must absolute URL let urlObj = new URL(url) let pathname = urlObj.pathname if (saveAs) { pathname = dialog.showSaveDialog(win, { title: 'Save as:', defaultPath: pathname }) if (typeof pathname == 'undefined') return } try { console.log("Saving", pathname, action) html = prettyPrint(html, { indent_size: 4 }); fs.writeFileSync(pathname, html, 'utf-8') if (saveAs) { let normalized = pathname.replace(/\\/g, '/') e.sender.send('savedAs', {url: `file://${normalized}`, action}) } } catch (e) { console.warn('Failed to save the file', pathname) e.sender.send('saveFailed', pathname) } }) // 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('ready', createWindow) // Quit when all windows are closed. app.on('window-all-closed', () => { // On macOS 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( 'select-client-certificate', (event, webContents, url, list, callback) => { console.log('select-client-certificate', url, list) event.preventDefault() ipc.once('client-certificate-selected', (event, item) => { console.log('selected:', item) callback(item) }) mainWindow.webContents.send('select-client-certificate', list) } ) app.on( 'certificate-error', (event, webContents, url, error, certificate, callback) => { console.log('certificate-error', url) event.preventDefault() const result = true // TODO: do real validation here callback(result) } ) app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (win === null) { createWindow() } }) module.exports = { store: store }