579 lines
17 KiB
JavaScript
579 lines
17 KiB
JavaScript
|
/* 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
|
||
|
}
|