responsively-app/desktop-app-legacy/app/main.dev.js

714 lines
19 KiB
JavaScript
Raw Normal View History

2019-08-10 02:47:13 +00:00
/* eslint global-require: off */
2020-07-05 07:41:33 +00:00
require('dotenv').config();
2019-08-10 02:47:13 +00:00
/**
* This module executes inside of electron's main process. You can start
* electron renderer process from here and communicate with the other processes
* through IPC.
*
* When running `yarn build` or `yarn build-main`, this file is compiled to
* `./app/main.prod.js` using webpack. This gives us some performance wins.
*
* @flow
*/
2020-06-04 15:49:03 +00:00
import electron, {
app,
BrowserWindow,
BrowserView,
globalShortcut,
ipcMain,
2020-06-11 02:52:42 +00:00
nativeTheme,
2020-06-13 06:55:57 +00:00
webContents,
2020-07-05 08:23:42 +00:00
shell,
dialog,
session,
2020-06-04 15:49:03 +00:00
} from 'electron';
2019-09-02 16:23:13 +00:00
import settings from 'electron-settings';
2019-08-10 02:47:13 +00:00
import log from 'electron-log';
2019-09-05 17:29:33 +00:00
import * as Sentry from '@sentry/electron';
import installExtension, {
REACT_DEVELOPER_TOOLS,
REDUX_DEVTOOLS,
} from 'electron-devtools-installer';
2020-05-25 14:07:11 +00:00
import fs from 'fs';
2020-07-04 08:26:23 +00:00
import MenuBuilder from './menu';
import {USER_PREFERENCES, NETWORK_CONFIGURATION} from './constants/settingKeys';
import {STARTUP_PAGE} from './constants/values';
import {migrateDeviceSchema} from './settings/migration';
2020-06-09 13:25:05 +00:00
import {DEVTOOLS_MODES} from './constants/previewerLayouts';
import {initMainShortcutManager} from './shortcut-manager/main-shortcut-manager';
2020-06-10 20:05:31 +00:00
import {appUpdater} from './app-updater';
2020-07-05 07:41:33 +00:00
import trimStart from 'lodash/trimStart';
import isURL from 'validator/lib/isURL';
2021-05-19 14:06:12 +00:00
import {
confirmMove,
conflictHandler,
movingFailed,
} from './move-to-applications';
2020-07-20 15:22:54 +00:00
import {
initBrowserSync,
getBrowserSyncHost,
getBrowserSyncEmbedScriptURL,
2020-07-25 04:47:11 +00:00
closeBrowserSync,
2020-08-03 14:37:28 +00:00
stopWatchFiles,
watchFiles,
2020-07-20 15:22:54 +00:00
} from './utils/browserSync';
import {getHostFromURL, normalize} from './utils/urlUtils';
2020-08-27 13:10:59 +00:00
import {getPermissionSettingPreference} from './utils/permissionUtils';
2020-07-20 15:22:54 +00:00
import browserSync from 'browser-sync';
2020-08-11 14:20:55 +00:00
import {captureOnSentry} from './utils/logUtils';
2020-08-12 16:58:08 +00:00
import appMetadata from './services/db/appMetadata';
import {convertToProxyConfig} from './utils/proxyUtils';
2020-08-27 13:10:59 +00:00
import {PERMISSION_MANAGEMENT_OPTIONS} from './constants/permissionsManagement';
2020-08-29 10:49:34 +00:00
import {endSession, startSession} from './utils/analytics';
import {getStartupPage, getLastOpenedAddress} from './utils/navigatorUtils';
2019-09-05 17:29:33 +00:00
2020-03-14 08:01:12 +00:00
const path = require('path');
2020-07-05 09:20:36 +00:00
const URL = require('url').URL;
2019-10-22 02:52:14 +00:00
const HOME_PAGE = 'HOME_PAGE';
const LAST_OPENED_ADDRESS = 'LAST_OPENED_ADDRESS';
migrateDeviceSchema();
if (process.env.NODE_ENV !== 'development') {
2020-03-14 08:05:02 +00:00
Sentry.init({
dsn: 'https://f2cdbc6a88aa4a068a738d4e4cfd3e12@sentry.io/1553155',
environment: process.env.NODE_ENV,
beforeSend: (event, hint) => {
// Suppress address already in use error
if (
(event?.exception?.values?.[0]?.value || '').indexOf(
'listen EADDRINUSE: address already in use'
) > -1
) {
return null;
}
event.tags = {appVersion: app.getVersion()};
return event;
},
2020-03-14 08:05:02 +00:00
});
}
2019-08-10 02:47:13 +00:00
const protocol = 'responsively';
let hasActiveWindow = false;
2019-08-10 02:47:13 +00:00
let mainWindow = null;
2020-05-25 14:07:11 +00:00
let urlToOpen = null;
2020-06-07 10:03:44 +00:00
let devToolsView = null;
let fileToOpen = null;
2019-08-10 02:47:13 +00:00
2020-07-04 08:26:23 +00:00
const httpAuthCallbacks = {};
const permissionCallbacks = {};
2019-08-24 06:07:07 +00:00
2019-08-10 02:47:13 +00:00
if (process.env.NODE_ENV === 'production') {
2020-03-14 08:05:02 +00:00
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
2019-08-10 02:47:13 +00:00
}
if (
2020-03-14 08:05:02 +00:00
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
2019-08-10 02:47:13 +00:00
) {
require('electron-debug')({isEnabled: true});
2019-08-10 02:47:13 +00:00
}
const openWithHandler = filePath => {
fileToOpen = null;
if (
filePath != null &&
2021-06-16 15:11:47 +00:00
!filePath.startsWith('http://') &&
!filePath.startsWith('https://') &&
(filePath.endsWith('.html') || filePath.endsWith('.htm'))
) {
2021-06-16 15:11:47 +00:00
if (filePath.startsWith('file://')) fileToOpen = filePath;
else fileToOpen = `file://${filePath}`;
return true;
}
return false;
};
const setProxyOnStart = () => {
const proxyConfig = (settings.get(NETWORK_CONFIGURATION) || {}).proxy;
if (proxyConfig != null && proxyConfig.active) {
session.defaultSession.setProxy(convertToProxyConfig(proxyConfig));
}
};
2019-08-10 02:47:13 +00:00
/**
* Add event listeners...
*/
2020-05-25 14:07:11 +00:00
app.on('will-finish-launching', () => {
2020-06-09 09:52:48 +00:00
if (process.platform === 'win32') {
urlToOpen = process.argv.filter(i => /^responsively/.test(i))[0];
}
2020-05-25 14:07:11 +00:00
if (['win32', 'darwin'].includes(process.platform)) {
if (process.argv.length >= 2) {
if (!openWithHandler(process.argv[1])) {
app.setAsDefaultProtocolClient(protocol, process.execPath, [
path.resolve(process.argv[1]),
]);
}
2020-05-25 14:07:11 +00:00
} else {
app.setAsDefaultProtocolClient(protocol);
2020-05-25 14:07:11 +00:00
}
}
if (
!fileToOpen &&
!urlToOpen &&
process.argv.length >= 2 &&
!openWithHandler(process.argv[1]) &&
2021-06-16 15:11:47 +00:00
isURL(process.argv[1], {
protocols: ['http', 'https', 'file'],
require_tld: false,
})
) {
urlToOpen = process.argv[1];
}
2020-05-25 14:07:11 +00:00
});
app.on('open-file', async (event, filePath) => {
event.preventDefault();
let htmlFile = filePath;
if (process.platform === 'win32' && process.argv.length >= 2) {
htmlFile = process.argv[1];
}
if (openWithHandler(htmlFile)) {
if (mainWindow) {
openFile(fileToOpen);
} else if (!hasActiveWindow) {
2020-08-13 14:59:13 +00:00
await createWindow();
}
}
});
2020-05-25 14:07:11 +00:00
app.on('open-url', async (event, url) => {
if (mainWindow) {
openUrl(url);
} else {
urlToOpen = url;
if (!hasActiveWindow) {
2020-08-13 14:59:13 +00:00
await createWindow();
}
2020-05-25 14:07:11 +00:00
}
});
2019-08-10 02:47:13 +00:00
app.on('window-all-closed', () => {
2020-08-29 10:49:34 +00:00
endSession();
2020-07-22 04:22:48 +00:00
hasActiveWindow = false;
ipcMain.removeAllListeners();
ipcMain.removeHandler('install-extension');
ipcMain.removeHandler('get-local-extension-path');
ipcMain.removeHandler('get-screen-shot-save-path');
ipcMain.removeHandler('request-browser-sync');
2020-07-25 05:06:56 +00:00
closeBrowserSync();
2020-03-14 08:05:02 +00:00
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
}
2019-08-10 02:47:13 +00:00
});
app.on(
'certificate-error',
(event, webContents, url, error, certificate, callback) => {
2020-07-12 15:48:14 +00:00
if (
2020-07-20 15:22:54 +00:00
getHostFromURL(url) === getBrowserSyncHost() ||
2020-07-12 15:48:14 +00:00
(settings.get(USER_PREFERENCES) || {}).disableSSLValidation === true
) {
event.preventDefault();
callback(true);
}
}
);
2019-08-24 06:07:07 +00:00
app.on('login', (event, webContents, request, authInfo, callback) => {
2020-03-14 08:05:02 +00:00
event.preventDefault();
const {url} = request;
if (authInfo.isProxy) {
const proxyConfig = (settings.get(NETWORK_CONFIGURATION) || {}).proxy;
if (proxyConfig != null && proxyConfig.active) {
const schConfig =
proxyConfig[url.substr(0, url.indexOf(':')).toLowerCase()];
if (schConfig != null && !schConfig.useDefault) {
callback(schConfig.user, schConfig.password);
} else {
callback(proxyConfig.default.user, proxyConfig.default.password);
}
}
} else {
if (httpAuthCallbacks[url]) {
return httpAuthCallbacks[url].push(callback);
}
httpAuthCallbacks[url] = [callback];
mainWindow.webContents.send('http-auth-prompt', {url});
2020-03-14 08:05:02 +00:00
}
});
ipcMain.on('set-proxy-profile', async (_, proxyProfile) => {
if (proxyProfile == null || proxyProfile.length === 0) return;
await session.defaultSession.clearAuthCache();
await session.defaultSession.setProxy(proxyProfile);
2019-08-24 06:07:07 +00:00
});
2020-08-13 14:59:13 +00:00
app.on('activate', async (event, hasVisibleWindows) => {
2020-07-11 16:44:00 +00:00
if (hasVisibleWindows || hasActiveWindow) {
return;
}
2020-08-13 14:59:13 +00:00
await createWindow();
2020-07-11 16:44:00 +00:00
});
2020-08-13 14:59:13 +00:00
app.on('ready', async () => {
2020-07-11 16:44:00 +00:00
if (hasActiveWindow) {
return;
}
if (
process.platform === 'darwin' &&
!app.isInApplicationsFolder() &&
(await confirmMove(dialog))
) {
2021-05-19 14:06:12 +00:00
try {
app.moveToApplicationsFolder({
conflictHandler: conflictHandler.bind(this, dialog),
});
} catch (e) {
movingFailed(dialog);
}
}
2020-08-31 01:41:01 +00:00
// Set theme based on user preference
const themeSource = (settings.get(USER_PREFERENCES) || {}).theme;
if (themeSource) {
nativeTheme.themeSource = themeSource;
}
2020-08-13 14:59:13 +00:00
await createWindow();
2020-07-11 16:44:00 +00:00
});
const chooseOpenWindowHandler = url => {
if (url == null || url.trim() === '' || url === 'about:blank#blocked')
return 'none';
if (url === 'about:blank') return 'useWindow';
2021-06-16 15:11:47 +00:00
if (isURL(url, {protocols: ['http', 'https'], require_tld: false}))
return 'useWindow';
2020-07-11 16:44:00 +00:00
let urlObj = null;
try {
urlObj = new URL(url);
} catch {}
if (
urlObj != null &&
urlObj.protocol === 'file:' &&
(urlObj.pathname.endsWith('.html') || urlObj.pathname.endsWith('.htm'))
)
return 'useWindow';
return 'useShell';
};
const installExtensions = async () => {
const extensions = [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS];
try {
await installExtension(extensions);
} catch (err) {
console.log('Error installing extensions', err);
}
};
const openUrl = url => {
mainWindow.webContents.send(
'address-change',
normalize(url.replace(`${protocol}://`, ''))
2020-07-11 16:44:00 +00:00
);
mainWindow.show();
};
const openFile = filePath => {
mainWindow.webContents.send('address-change', normalize(filePath));
mainWindow.show();
};
function getUserPreferences(): UserPreferenceType {
return settings.get(USER_PREFERENCES) || {};
}
const createWindow = async () => {
2020-08-12 16:58:08 +00:00
appMetadata.incrementOpenCount();
hasActiveWindow = true;
setProxyOnStart();
2020-07-12 05:24:47 +00:00
2020-07-01 07:31:02 +00:00
if (process.env.NODE_ENV === 'development') {
2020-03-14 08:05:02 +00:00
await installExtensions();
}
const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
2020-07-04 08:26:23 +00:00
const iconPath = path.resolve(__dirname, '../resources/icons/64x64.png');
2020-03-14 08:05:02 +00:00
mainWindow = new BrowserWindow({
show: false,
width,
height,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
webviewTag: true,
enableRemoteModule: true,
2021-05-19 14:06:12 +00:00
contextIsolation: false,
2020-03-14 08:05:02 +00:00
},
titleBarStyle: 'hidden',
icon: iconPath,
});
2020-08-05 16:52:39 +00:00
await initBrowserSync();
ipcMain.handle('request-browser-sync', (event, data) => {
const browserSyncOptions = {
url: getBrowserSyncEmbedScriptURL(),
};
return browserSyncOptions;
});
2020-03-14 08:05:02 +00:00
mainWindow.loadURL(`file://${__dirname}/app.html`);
2020-07-04 08:26:23 +00:00
mainWindow.webContents.on('did-finish-load', () => {
2020-08-29 10:49:34 +00:00
startSession();
2020-03-14 08:05:02 +00:00
if (process.platform === 'darwin') {
// Trick to make the transparent title bar draggable
mainWindow.webContents
.executeJavaScript(
`
var div = document.createElement("div");
div.style.position = "absolute";
div.style.top = 0;
div.style.height = "23px";
div.style.width = "100%";
div.style["-webkit-app-region"] = "drag";
div.style['-webkit-user-select'] = 'none';
document.body.appendChild(div);
true;
`
)
.catch(captureOnSentry);
2020-03-14 08:05:02 +00:00
}
});
initMainShortcutManager();
2020-06-15 17:53:30 +00:00
const onResize = () => {
const [width, height] = mainWindow.getContentSize();
mainWindow.webContents.send('window-resize', {height, width});
2020-06-15 17:53:30 +00:00
};
mainWindow.on('resize', onResize);
2020-03-14 08:05:02 +00:00
mainWindow.once('ready-to-show', () => {
2020-05-25 14:07:11 +00:00
if (urlToOpen) {
openUrl(urlToOpen);
urlToOpen = null;
} else if (fileToOpen) {
openFile(fileToOpen);
fileToOpen = null;
2020-06-09 09:52:48 +00:00
} else {
openUrl(
getUserPreferences().reopenLastAddress
? getLastOpenedAddress()
: getStartupPage()
);
2020-06-09 09:52:48 +00:00
mainWindow.show();
2020-05-25 14:07:11 +00:00
}
mainWindow.maximize();
2020-06-15 17:53:30 +00:00
onResize();
2020-03-14 08:05:02 +00:00
});
session.defaultSession.setPermissionRequestHandler(
(webContents, permission, callback, details) => {
const preferences = getPermissionSettingPreference();
const reqUrl = webContents.getURL();
if (permissionCallbacks[reqUrl] == null) permissionCallbacks[reqUrl] = {};
if (permissionCallbacks[reqUrl][permission] == null) {
permissionCallbacks[reqUrl][permission] = {
called: false,
allowed: null,
callbacks: [],
};
}
const entry = permissionCallbacks[reqUrl][permission];
2020-08-27 13:10:59 +00:00
if (preferences === PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS) {
entry.callbacks.forEach(callback => callback(true));
entry.callbacks = [];
entry.allowed = true;
entry.called = true;
return callback(true);
}
2020-08-27 13:10:59 +00:00
if (preferences === PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS) {
entry.callbacks.forEach(callback => callback(false));
entry.callbacks = [];
entry.allowed = false;
entry.called = true;
return callback(false);
}
if (entry.called) {
if (entry.allowed == null) return;
return callback(entry.allowed);
}
if (entry.callbacks.length === 0) {
entry.callbacks.push(callback);
mainWindow.webContents.send('permission-prompt', {
url: reqUrl,
permission,
details,
});
} else {
entry.callbacks.push(callback);
}
}
);
session.defaultSession.setPermissionCheckHandler(
(webContents, permission) => {
const reqUrl = webContents.getURL();
let entry = permissionCallbacks[reqUrl];
if (entry != null) entry = entry[permission];
if (entry == null || !entry.called) {
return null;
}
return entry.allowed;
}
);
ipcMain.on('permission-response', (evnt, ...args) => {
if (args[0] == null) return;
const {url, permission, allowed} = args[0];
let entry = permissionCallbacks[url];
if (entry != null) entry = entry[permission];
if (entry != null && !entry.called) {
entry.called = true;
entry.allowed = allowed;
if (allowed != null)
entry.callbacks.forEach(callback => callback(allowed));
entry.callbacks = [];
}
});
ipcMain.on('reset-ignored-permissions', evnt => {
Object.entries(permissionCallbacks).forEach(([_, permissions]) => {
Object.entries(permissions).forEach(([_, entry]) => {
if (entry.called && entry.allowed == null) entry.called = false;
entry.callbacks = [];
});
});
});
2020-08-04 16:04:10 +00:00
ipcMain.on('start-watching-file', async (event, fileInfo) => {
2020-07-05 07:41:33 +00:00
let path = fileInfo.path.replace('file://', '');
if (process.platform === 'win32') {
path = trimStart(path, '/');
}
app.addRecentDocument(path);
2020-08-04 16:04:10 +00:00
await stopWatchFiles();
2020-08-04 15:53:12 +00:00
watchFiles(path);
});
2020-08-04 16:04:10 +00:00
ipcMain.on('stop-watcher', async () => {
await stopWatchFiles();
});
ipcMain.on('open-new-window', (event, data) => {
const handler = chooseOpenWindowHandler(data.url);
if (handler === 'useWindow') {
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
devTools: false,
},
2020-07-05 09:20:36 +00:00
});
win.setMenu(null);
win.loadURL(data.url);
win.once('ready-to-show', () => {
win.show();
});
win.on('closed', () => {
win = null;
});
} else if (handler === 'useShell') {
shell.openExternal(data.url);
}
});
2020-03-14 08:05:02 +00:00
ipcMain.on('http-auth-promt-response', (event, ...args) => {
if (!args[0].url) {
return;
}
const {url, username, password} = args[0];
if (!httpAuthCallbacks[url]) {
return;
}
httpAuthCallbacks[url].forEach(cb => cb(username, password));
httpAuthCallbacks[url] = null;
});
2020-06-09 08:43:41 +00:00
ipcMain.on('prefers-color-scheme-select', (event, scheme) => {
2020-09-22 07:24:05 +00:00
if (!scheme) {
return;
}
nativeTheme.themeSource = scheme;
2020-06-09 08:43:41 +00:00
});
ipcMain.handle('install-extension', (event, extensionId) => {
2020-07-05 08:23:42 +00:00
let isLocalExtension;
try {
isLocalExtension = fs.statSync(extensionId).isDirectory();
} catch {
isLocalExtension = false;
}
if (isLocalExtension) {
return electron.BrowserWindow.addDevToolsExtension(extensionId);
}
2020-07-05 08:23:42 +00:00
const id = extensionId
.replace(/\/$/, '')
.split('/')
.pop();
2020-06-23 12:02:29 +00:00
return installExtension(id, true);
});
2020-07-04 08:26:23 +00:00
ipcMain.on('uninstall-extension', (event, name) =>
BrowserWindow.removeDevToolsExtension(name)
);
2020-06-23 12:02:29 +00:00
2020-07-05 08:23:42 +00:00
ipcMain.handle('get-local-extension-path', async event => {
try {
const {filePaths = []} = await dialog.showOpenDialog({
properties: ['openDirectory'],
});
const [localExtensionPath = ''] = filePaths;
return localExtensionPath;
} catch {
return '';
}
});
ipcMain.handle('get-screen-shot-save-path', async event => {
try {
const {filePaths = []} = await dialog.showOpenDialog({
properties: ['openDirectory'],
});
return filePaths[0];
} catch {
return '';
}
});
ipcMain.on('open-devtools', (event, ...args) => {
2020-06-09 13:25:05 +00:00
const {webViewId, bounds, mode} = args[0];
2020-06-04 15:49:03 +00:00
if (!webViewId) {
return;
}
2020-06-09 13:25:05 +00:00
const webView = webContents.fromId(webViewId);
if (mode === DEVTOOLS_MODES.UNDOCKED) {
return webView.openDevTools();
}
2020-06-07 10:03:44 +00:00
devToolsView = new BrowserView();
mainWindow.setBrowserView(devToolsView);
devToolsView.setBounds(bounds);
webView.setDevToolsWebContents(devToolsView.webContents);
2020-06-04 15:49:03 +00:00
webView.openDevTools();
devToolsView.webContents
.executeJavaScript(
`
(async function () {
const sleep = ms => (new Promise(resolve => setTimeout(resolve, ms)));
var retryCount = 0;
var done = false;
while(retryCount < 10 && !done) {
try {
retryCount++;
document.querySelectorAll('div[slot="insertion-point-main"]')[0].shadowRoot.querySelectorAll('.tabbed-pane-left-toolbar.toolbar')[0].style.display = 'none'
done = true
} catch(err){
await sleep(100);
}
}
})()
`
)
.catch(captureOnSentry);
2020-06-04 15:49:03 +00:00
});
ipcMain.on('close-devtools', (event, ...args) => {
const {webViewId} = args[0];
if (!webViewId) {
2020-06-04 15:49:03 +00:00
return;
}
2020-07-06 11:31:05 +00:00
const _webContents = webContents.fromId(webViewId);
if (_webContents) {
_webContents.closeDevTools();
}
if (!devToolsView) {
return;
}
2020-06-07 10:03:44 +00:00
mainWindow.removeBrowserView(devToolsView);
devToolsView.destroy();
devToolsView = null;
2020-06-04 15:49:03 +00:00
});
ipcMain.on('resize-devtools', (event, ...args) => {
2020-06-07 07:04:35 +00:00
const {bounds} = args[0];
2020-06-07 10:03:44 +00:00
if (!bounds || !devToolsView) {
2020-06-04 15:49:03 +00:00
return;
}
2020-06-07 10:03:44 +00:00
devToolsView.setBounds(bounds);
2020-06-04 15:49:03 +00:00
});
2022-01-11 19:24:22 +00:00
ipcMain.on('download-preferences', (event, ...args) => {
session.defaultSession.downloadURL(args[0].url);
});
2020-03-14 08:05:02 +00:00
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.webContents.on(
'new-window',
(event, url, frameName, disposition, options) => {
if (url?.indexOf('headwayapp.co') !== -1) {
event.preventDefault();
shell.openExternal(url);
}
}
);
2020-03-14 08:05:02 +00:00
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
2020-06-13 06:55:57 +00:00
appUpdater.on('status-changed', nextStatus => {
2020-06-10 20:05:31 +00:00
menuBuilder.buildMenu(true);
2020-06-22 16:48:06 +00:00
mainWindow.webContents.send('updater-status-changed', {nextStatus});
2020-06-10 20:05:31 +00:00
});
2020-03-14 08:05:02 +00:00
// Remove this if your app does not use auto updates
2020-08-05 05:38:05 +00:00
appUpdater
.checkForUpdatesAndNotify()
.catch(err => console.log('Error while updating app', err));
};