mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
This commit is contained in:
parent
86b00d0a2b
commit
694ca6480e
@ -415,6 +415,8 @@ packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
|
||||
packages/app-desktop/utils/markupLanguageUtils.js
|
||||
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
|
||||
packages/app-desktop/utils/restartInSafeModeFromMain.js
|
||||
packages/app-mobile/PluginAssetsLoader.js
|
||||
packages/app-mobile/components/ActionButton.js
|
||||
packages/app-mobile/components/BackButtonDialogBox.js
|
||||
@ -902,6 +904,7 @@ packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
packages/lib/utils/userFetcher.js
|
||||
packages/lib/utils/webDAVUtils.test.js
|
||||
packages/lib/utils/webDAVUtils.js
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -397,6 +397,8 @@ packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtilsTestData.js
|
||||
packages/app-desktop/utils/markupLanguageUtils.js
|
||||
packages/app-desktop/utils/restartInSafeModeFromMain.test.js
|
||||
packages/app-desktop/utils/restartInSafeModeFromMain.js
|
||||
packages/app-mobile/PluginAssetsLoader.js
|
||||
packages/app-mobile/components/ActionButton.js
|
||||
packages/app-mobile/components/BackButtonDialogBox.js
|
||||
@ -884,6 +886,7 @@ packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/processStartFlags.js
|
||||
packages/lib/utils/userFetcher.js
|
||||
packages/lib/utils/webDAVUtils.test.js
|
||||
packages/lib/utils/webDAVUtils.js
|
||||
|
@ -9,7 +9,10 @@ const url = require('url');
|
||||
const path = require('path');
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
const fs = require('fs-extra');
|
||||
const { ipcMain } = require('electron');
|
||||
|
||||
import { dialog, ipcMain } from 'electron';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import restartInSafeModeFromMain from './utils/restartInSafeModeFromMain';
|
||||
|
||||
interface RendererProcessQuitReply {
|
||||
canClose: boolean;
|
||||
@ -34,7 +37,7 @@ export default class ElectronAppWrapper {
|
||||
private pluginWindows_: PluginWindows = {};
|
||||
private initialCallbackUrl_: string = null;
|
||||
|
||||
public constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||
public constructor(electronApp: any, env: string, profilePath: string|null, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||
this.electronApp_ = electronApp;
|
||||
this.env_ = env;
|
||||
this.isDebugMode_ = isDebugMode;
|
||||
@ -66,6 +69,42 @@ export default class ElectronAppWrapper {
|
||||
return this.initialCallbackUrl_;
|
||||
}
|
||||
|
||||
// Call when the app fails in a significant way.
|
||||
//
|
||||
// Assumes that the renderer process may be in an invalid state and so cannot
|
||||
// be accessed.
|
||||
public async handleAppFailure(errorMessage: string, canIgnore: boolean, isTesting?: boolean) {
|
||||
const buttons = [];
|
||||
buttons.push(_('Quit'));
|
||||
const exitIndex = 0;
|
||||
|
||||
if (canIgnore) {
|
||||
buttons.push(_('Ignore'));
|
||||
}
|
||||
const restartIndex = buttons.length;
|
||||
buttons.push(_('Restart in safe mode'));
|
||||
|
||||
const { response } = await dialog.showMessageBox({
|
||||
message: _('An error occurred: %s', errorMessage),
|
||||
buttons,
|
||||
});
|
||||
|
||||
if (response === restartIndex) {
|
||||
await restartInSafeModeFromMain();
|
||||
|
||||
// A hung renderer seems to prevent the process from exiting completely.
|
||||
// In this case, crashing the renderer allows the window to close.
|
||||
//
|
||||
// Also only run this if not testing (crashing the renderer breaks automated
|
||||
// tests).
|
||||
if (this.win_ && !this.win_.webContents.isCrashed() && !isTesting) {
|
||||
this.win_.webContents.forcefullyCrashRenderer();
|
||||
}
|
||||
} else if (response === exitIndex) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public createWindow() {
|
||||
// Set to true to view errors if the application does not start
|
||||
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
|
||||
@ -121,6 +160,20 @@ export default class ElectronAppWrapper {
|
||||
this.win_.setPosition(primaryDisplayWidth / 2 - windowWidth, primaryDisplayHeight / 2 - windowHeight);
|
||||
}
|
||||
|
||||
this.win_.webContents.on('unresponsive', async () => {
|
||||
await this.handleAppFailure(_('Window unresponsive.'), true);
|
||||
});
|
||||
|
||||
this.win_.webContents.on('render-process-gone', async _event => {
|
||||
await this.handleAppFailure('Renderer process gone.', false);
|
||||
});
|
||||
|
||||
this.win_.webContents.on('did-fail-load', async event => {
|
||||
if ((event as any).isMainFrame) {
|
||||
await this.handleAppFailure('Renderer process failed to load', false);
|
||||
}
|
||||
});
|
||||
|
||||
void this.win_.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file:',
|
||||
|
@ -2,6 +2,9 @@ import { test, expect } from './util/test';
|
||||
import MainScreen from './models/MainScreen';
|
||||
import activateMainMenuItem from './util/activateMainMenuItem';
|
||||
import SettingsScreen from './models/SettingsScreen';
|
||||
import { _electron as electron } from '@playwright/test';
|
||||
import { writeFile } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
|
||||
|
||||
test.describe('main', () => {
|
||||
@ -121,4 +124,23 @@ test.describe('main', () => {
|
||||
|
||||
expect(await nextExternalUrlPromise).toBe(linkHref);
|
||||
});
|
||||
|
||||
test('should start in safe mode if profile-dir/force-safe-mode-on-next-start exists', async ({ profileDirectory }) => {
|
||||
await writeFile(join(profileDirectory, 'force-safe-mode-on-next-start'), 'true', 'utf8');
|
||||
|
||||
// We need to write to the force-safe-mode file before opening the Electron app.
|
||||
// Open the app ourselves:
|
||||
const startupArgs = [
|
||||
'main.js', '--env', 'dev', '--profile', profileDirectory,
|
||||
];
|
||||
const electronApp = await electron.launch({ args: startupArgs });
|
||||
const mainWindow = await electronApp.firstWindow();
|
||||
|
||||
const safeModeDisableLink = mainWindow.getByText('Disable safe mode and restart');
|
||||
await safeModeDisableLink.waitFor();
|
||||
await expect(safeModeDisableLink).toBeInViewport();
|
||||
|
||||
await electronApp.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ import uuid from '@joplin/lib/uuid';
|
||||
|
||||
|
||||
type JoplinFixtures = {
|
||||
profileDirectory: string;
|
||||
electronApp: ElectronApplication;
|
||||
mainWindow: Page;
|
||||
};
|
||||
@ -20,19 +21,26 @@ export const test = base.extend<JoplinFixtures>({
|
||||
// See https://github.com/microsoft/playwright/issues/8798
|
||||
//
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
electronApp: async ({ }, use) => {
|
||||
profileDirectory: async ({ }, use) => {
|
||||
const profilePath = resolve(join(dirname(__dirname), 'test-profile'));
|
||||
const profileSubdir = join(profilePath, uuid.createNano());
|
||||
await mkdirp(profileSubdir);
|
||||
|
||||
const startupArgs = ['main.js', '--env', 'dev', '--profile', profileSubdir];
|
||||
await use(profileSubdir);
|
||||
|
||||
await remove(profileSubdir);
|
||||
},
|
||||
|
||||
electronApp: async ({ profileDirectory }, use) => {
|
||||
const startupArgs = [
|
||||
'main.js', '--env', 'dev', '--profile', profileDirectory,
|
||||
];
|
||||
const electronApp = await electron.launch({ args: startupArgs });
|
||||
|
||||
await use(electronApp);
|
||||
|
||||
await electronApp.firstWindow();
|
||||
await electronApp.close();
|
||||
await remove(profileSubdir);
|
||||
},
|
||||
|
||||
mainWindow: async ({ electronApp }, use) => {
|
||||
|
@ -31,117 +31,122 @@ const React = require('react');
|
||||
const nodeSqlite = require('sqlite3');
|
||||
const initLib = require('@joplin/lib/initLib').default;
|
||||
|
||||
|
||||
if (bridge().env() === 'dev') {
|
||||
const newConsole = function(oldConsole) {
|
||||
const output = {};
|
||||
const fnNames = ['assert', 'clear', 'context', 'count', 'countReset', 'debug', 'dir', 'dirxml', 'error', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'memory', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 'timeLog', 'timeStamp', 'trace', 'warn'];
|
||||
for (const fnName of fnNames) {
|
||||
if (fnName === 'warn') {
|
||||
output.warn = function(...text) {
|
||||
const s = [...text].join('');
|
||||
// React spams the console with walls of warnings even outside of strict mode, and even after having renamed
|
||||
// unsafe methods to UNSAFE_xxxx, so we need to hack the console to remove them...
|
||||
if (s.indexOf('Warning: componentWillReceiveProps has been renamed, and is not recommended for use') === 0) return;
|
||||
if (s.indexOf('Warning: componentWillUpdate has been renamed, and is not recommended for use.') === 0) return;
|
||||
oldConsole.warn(...text);
|
||||
};
|
||||
} else {
|
||||
output[fnName] = function(...text) {
|
||||
return oldConsole[fnName](...text);
|
||||
};
|
||||
const main = async () => {
|
||||
if (bridge().env() === 'dev') {
|
||||
const newConsole = function(oldConsole) {
|
||||
const output = {};
|
||||
const fnNames = ['assert', 'clear', 'context', 'count', 'countReset', 'debug', 'dir', 'dirxml', 'error', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'memory', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 'timeLog', 'timeStamp', 'trace', 'warn'];
|
||||
for (const fnName of fnNames) {
|
||||
if (fnName === 'warn') {
|
||||
output.warn = function(...text) {
|
||||
const s = [...text].join('');
|
||||
// React spams the console with walls of warnings even outside of strict mode, and even after having renamed
|
||||
// unsafe methods to UNSAFE_xxxx, so we need to hack the console to remove them...
|
||||
if (s.indexOf('Warning: componentWillReceiveProps has been renamed, and is not recommended for use') === 0) return;
|
||||
if (s.indexOf('Warning: componentWillUpdate has been renamed, and is not recommended for use.') === 0) return;
|
||||
oldConsole.warn(...text);
|
||||
};
|
||||
} else {
|
||||
output[fnName] = function(...text) {
|
||||
return oldConsole[fnName](...text);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}(window.console);
|
||||
return output;
|
||||
}(window.console);
|
||||
|
||||
window.console = newConsole;
|
||||
}
|
||||
window.console = newConsole;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`Environment: ${bridge().env()}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`Environment: ${bridge().env()}`);
|
||||
|
||||
const fsDriver = new FsDriverNode();
|
||||
Logger.fsDriver_ = fsDriver;
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
const fsDriver = new FsDriverNode();
|
||||
Logger.fsDriver_ = fsDriver;
|
||||
Resource.fsDriver_ = fsDriver;
|
||||
EncryptionService.fsDriver_ = fsDriver;
|
||||
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||
|
||||
// That's not good, but it's to avoid circular dependency issues
|
||||
// in the BaseItem class.
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
// That's not good, but it's to avoid circular dependency issues
|
||||
// in the BaseItem class.
|
||||
BaseItem.loadClass('Note', Note);
|
||||
BaseItem.loadClass('Folder', Folder);
|
||||
BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
|
||||
Setting.setConstant('appId', `net.cozic.joplin${bridge().env() === 'dev' ? 'dev' : ''}-desktop`);
|
||||
Setting.setConstant('appType', 'desktop');
|
||||
Setting.setConstant('appId', `net.cozic.joplin${bridge().env() === 'dev' ? 'dev' : ''}-desktop`);
|
||||
Setting.setConstant('appType', 'desktop');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`appId: ${Setting.value('appId')}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`appType: ${Setting.value('appType')}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`appId: ${Setting.value('appId')}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`appType: ${Setting.value('appType')}`);
|
||||
|
||||
let keytar;
|
||||
try {
|
||||
keytar = shim.platformSupportsKeyChain() ? require('keytar') : null;
|
||||
} catch (error) {
|
||||
console.error('Cannot load keytar - keychain support will be disabled', error);
|
||||
keytar = null;
|
||||
}
|
||||
let keytar;
|
||||
try {
|
||||
keytar = shim.platformSupportsKeyChain() ? require('keytar') : null;
|
||||
} catch (error) {
|
||||
console.error('Cannot load keytar - keychain support will be disabled', error);
|
||||
keytar = null;
|
||||
}
|
||||
|
||||
function appVersion() {
|
||||
const p = require('./packageInfo.js');
|
||||
return p.version;
|
||||
}
|
||||
function appVersion() {
|
||||
const p = require('./packageInfo.js');
|
||||
return p.version;
|
||||
}
|
||||
|
||||
shimInit({
|
||||
keytar,
|
||||
React,
|
||||
appVersion,
|
||||
electronBridge: bridge(),
|
||||
nodeSqlite,
|
||||
});
|
||||
shimInit({
|
||||
keytar,
|
||||
React,
|
||||
appVersion,
|
||||
electronBridge: bridge(),
|
||||
nodeSqlite,
|
||||
});
|
||||
|
||||
// Disable drag and drop of links inside application (which would
|
||||
// open it as if the whole app was a browser)
|
||||
document.addEventListener('dragover', event => event.preventDefault());
|
||||
document.addEventListener('drop', event => event.preventDefault());
|
||||
// Disable drag and drop of links inside application (which would
|
||||
// open it as if the whole app was a browser)
|
||||
document.addEventListener('dragover', event => event.preventDefault());
|
||||
document.addEventListener('drop', event => event.preventDefault());
|
||||
|
||||
// Disable middle-click (which would open a new browser window, but we don't want this)
|
||||
document.addEventListener('auxclick', event => event.preventDefault());
|
||||
// Disable middle-click (which would open a new browser window, but we don't want this)
|
||||
document.addEventListener('auxclick', event => event.preventDefault());
|
||||
|
||||
// Each link (rendered as a button or list item) has its own custom click event
|
||||
// so disable the default. In particular this will disable Ctrl+Clicking a link
|
||||
// which would open a new browser window.
|
||||
document.addEventListener('click', (event) => {
|
||||
// We don't apply this to labels and inputs because it would break
|
||||
// checkboxes. Such a global event handler is probably not a good idea
|
||||
// anyway but keeping it for now, as it doesn't seem to break anything else.
|
||||
// https://github.com/facebook/react/issues/13477#issuecomment-489274045
|
||||
if (['LABEL', 'INPUT'].includes(event.target.nodeName)) return;
|
||||
// Each link (rendered as a button or list item) has its own custom click event
|
||||
// so disable the default. In particular this will disable Ctrl+Clicking a link
|
||||
// which would open a new browser window.
|
||||
document.addEventListener('click', (event) => {
|
||||
// We don't apply this to labels and inputs because it would break
|
||||
// checkboxes. Such a global event handler is probably not a good idea
|
||||
// anyway but keeping it for now, as it doesn't seem to break anything else.
|
||||
// https://github.com/facebook/react/issues/13477#issuecomment-489274045
|
||||
if (['LABEL', 'INPUT'].includes(event.target.nodeName)) return;
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
const logger = new Logger();
|
||||
Logger.initializeGlobalLogger(logger);
|
||||
initLib(logger);
|
||||
const logger = new Logger();
|
||||
Logger.initializeGlobalLogger(logger);
|
||||
initLib(logger);
|
||||
|
||||
app().start(bridge().processArgv()).then((result) => {
|
||||
if (!result || !result.action) {
|
||||
const startResult = await app().start(bridge().processArgv());
|
||||
|
||||
if (!startResult || !startResult.action) {
|
||||
require('./gui/Root');
|
||||
} else if (result.action === 'upgradeSyncTarget') {
|
||||
} else if (startResult.action === 'upgradeSyncTarget') {
|
||||
require('./gui/Root_UpgradeSyncTarget');
|
||||
}
|
||||
}).catch((error) => {
|
||||
const env = bridge().env();
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
const env = bridge().env();
|
||||
console.error(error);
|
||||
|
||||
let errorMessage;
|
||||
if (error.code === 'flagError') {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
errorMessage = error.message;
|
||||
} else {
|
||||
// If something goes wrong at this stage we don't have a console or a log file
|
||||
// so display the error in a message box.
|
||||
@ -150,13 +155,12 @@ app().start(bridge().processArgv()).then((result) => {
|
||||
if (error.lineNumber) msg.push(error.lineNumber);
|
||||
if (error.stack) msg.push(error.stack);
|
||||
|
||||
if (env === 'dev') {
|
||||
console.error(error);
|
||||
} else {
|
||||
bridge().showErrorMessageBox(msg.join('\n\n'));
|
||||
}
|
||||
errorMessage = msg.join('\n\n');
|
||||
}
|
||||
|
||||
// In dev, we leave the app open as debug statements in the console can be useful
|
||||
if (env !== 'dev') bridge().electronApp().exit(1);
|
||||
// In dev, we give the option to leave the app open as debug statements in the
|
||||
// console can be useful
|
||||
const canIgnore = env === 'dev';
|
||||
bridge().electronApp().handleAppFailure(errorMessage, canIgnore);
|
||||
});
|
||||
|
||||
|
45
packages/app-desktop/utils/restartInSafeModeFromMain.test.ts
Normal file
45
packages/app-desktop/utils/restartInSafeModeFromMain.test.ts
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
let currentProfileDirectory: string;
|
||||
|
||||
jest.doMock('../bridge', () => ({
|
||||
// Mock the bridge functions used by restartInSafeModeFromMain
|
||||
// to remove the dependency on Electron.
|
||||
default: () => ({
|
||||
restart: jest.fn(),
|
||||
processArgv: () => [
|
||||
// The argument parser expects the first two arguments to
|
||||
// be the path to NodeJS and the second to be the main filename.
|
||||
process.argv[0], __filename,
|
||||
|
||||
// Only the following arguments are used.
|
||||
'--profile', currentProfileDirectory,
|
||||
],
|
||||
env: () => 'dev',
|
||||
}),
|
||||
}));
|
||||
|
||||
import { mkdtemp, readFile, remove } from 'fs-extra';
|
||||
import restartInSafeModeFromMain from './restartInSafeModeFromMain';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { safeModeFlagFilename } from '@joplin/lib/BaseApplication';
|
||||
|
||||
|
||||
describe('restartInSafeModeFromMain', () => {
|
||||
beforeEach(async () => {
|
||||
currentProfileDirectory = await mkdtemp(join(tmpdir(), 'safemode-restart-test'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await remove(currentProfileDirectory);
|
||||
});
|
||||
|
||||
test('should create a safe mode flag file', async () => {
|
||||
await restartInSafeModeFromMain();
|
||||
const safeModeFlagFilepath = join(
|
||||
currentProfileDirectory, safeModeFlagFilename,
|
||||
);
|
||||
expect(await readFile(safeModeFlagFilepath, 'utf8')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
33
packages/app-desktop/utils/restartInSafeModeFromMain.ts
Normal file
33
packages/app-desktop/utils/restartInSafeModeFromMain.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import bridge from '../bridge';
|
||||
import processStartFlags from '@joplin/lib/utils/processStartFlags';
|
||||
import BaseApplication, { safeModeFlagFilename } from '@joplin/lib/BaseApplication';
|
||||
import initProfile from '@joplin/lib/services/profileConfig/initProfile';
|
||||
import { writeFile } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
|
||||
|
||||
const restartInSafeModeFromMain = async () => {
|
||||
// Only set constants here -- the main process doesn't have easy access (without loading
|
||||
// a large amount of other code) to the database.
|
||||
const appName = `joplin${bridge().env() === 'dev' ? 'dev' : ''}-desktop`;
|
||||
Setting.setConstant('appId', `net.cozic.${appName}`);
|
||||
Setting.setConstant('appType', 'desktop');
|
||||
Setting.setConstant('appName', appName);
|
||||
|
||||
// Load just enough for us to write a file in the profile directory
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
shimInit({});
|
||||
|
||||
const startFlags = await processStartFlags(bridge().processArgv());
|
||||
const rootProfileDir = BaseApplication.determineProfileDir(startFlags.matched);
|
||||
const { profileDir } = await initProfile(rootProfileDir);
|
||||
|
||||
// We can't access the database, so write to a file instead.
|
||||
const safeModeFlagFile = join(profileDir, safeModeFlagFilename);
|
||||
await writeFile(safeModeFlagFile, 'true', 'utf8');
|
||||
|
||||
bridge().restart();
|
||||
};
|
||||
|
||||
export default restartInSafeModeFromMain;
|
@ -6,7 +6,7 @@ import BaseService from './services/BaseService';
|
||||
import reducer, { getNotesParent, serializeNotesParent, setStore, State } from './reducer';
|
||||
import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.node';
|
||||
import KeychainServiceDriverDummy from './services/keychain/KeychainServiceDriver.dummy';
|
||||
import { _, setLocale } from './locale';
|
||||
import { setLocale } from './locale';
|
||||
import KvStore from './services/KvStore';
|
||||
import SyncTargetJoplinServer from './SyncTargetJoplinServer';
|
||||
import SyncTargetOneDrive from './SyncTargetOneDrive';
|
||||
@ -26,8 +26,7 @@ import time from './time';
|
||||
import BaseSyncTarget from './BaseSyncTarget';
|
||||
const reduxSharedMiddleware = require('./components/shared/reduxSharedMiddleware');
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
import JoplinError from './JoplinError';
|
||||
import fs = require('fs-extra');
|
||||
const EventEmitter = require('events');
|
||||
const syswidecas = require('./vendor/syswide-cas');
|
||||
import SyncTargetRegistry from './SyncTargetRegistry';
|
||||
@ -60,6 +59,8 @@ import { ProfileConfig } from './services/profileConfig/types';
|
||||
import initProfile from './services/profileConfig/initProfile';
|
||||
import { parseShareCache } from './services/share/reducer';
|
||||
import RotatingLogs from './RotatingLogs';
|
||||
import { join } from 'path';
|
||||
import processStartFlags, { MatchedStartFlags } from './utils/processStartFlags';
|
||||
|
||||
const appLogger: LoggerWrapper = Logger.create('App');
|
||||
|
||||
@ -70,6 +71,7 @@ interface StartOptions {
|
||||
keychainEnabled?: boolean;
|
||||
setupGlobalLogger?: boolean;
|
||||
}
|
||||
export const safeModeFlagFilename = 'force-safe-mode-on-next-start';
|
||||
|
||||
export default class BaseApplication {
|
||||
|
||||
@ -163,154 +165,15 @@ export default class BaseApplication {
|
||||
// Handles the initial flags passed to main script and
|
||||
// returns the remaining args.
|
||||
private async handleStartFlags_(argv: string[], setDefaults = true) {
|
||||
const matched: any = {};
|
||||
argv = argv.slice(0);
|
||||
argv.splice(0, 2); // First arguments are the node executable, and the node JS file
|
||||
const flags = await processStartFlags(argv, setDefaults);
|
||||
|
||||
while (argv.length) {
|
||||
const arg = argv[0];
|
||||
const nextArg = argv.length >= 2 ? argv[1] : null;
|
||||
|
||||
if (arg === '--profile') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile <dir-path>'), 'flagError');
|
||||
matched.profileDir = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--no-welcome') {
|
||||
matched.welcomeDisabled = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--env') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--env <dev|prod>'), 'flagError');
|
||||
matched.env = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--is-demo') {
|
||||
Setting.setConstant('isDemo', true);
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--safe-mode') {
|
||||
matched.isSafeMode = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--open-dev-tools') {
|
||||
Setting.setConstant('flagOpenDevTools', true);
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--debug') {
|
||||
// Currently only handled by ElectronAppWrapper (isDebugMode property)
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--update-geolocation-disabled') {
|
||||
Note.updateGeolocationEnabled_ = false;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--stack-trace-enabled') {
|
||||
this.showStackTraces_ = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--log-level') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level <none|error|warn|info|debug>'), 'flagError');
|
||||
matched.logLevel = Logger.levelStringToId(nextArg);
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('-psn') === 0) {
|
||||
// Some weird flag passed by macOS - can be ignored.
|
||||
// https://github.com/laurent22/joplin/issues/480
|
||||
// https://stackoverflow.com/questions/10242115
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--enable-logging') {
|
||||
// Electron-specific flag used for debugging - ignore it
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--dev-plugins') {
|
||||
Setting.setConstant('startupDevPlugins', nextArg.split(',').map(p => p.trim()));
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--remote-debugging-port=') === 0) {
|
||||
// Electron-specific flag used for debugging - ignore it. Electron expects this flag in '--x=y' form, a single string.
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--no-sandbox') {
|
||||
// Electron-specific flag for running the app without chrome-sandbox
|
||||
// Allows users to use it as a workaround for the electron+AppImage issue
|
||||
// https://github.com/laurent22/joplin/issues/2246
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--user-data-dir=') === 0) {
|
||||
// Electron-specific flag. Allows users to run the app with chromedriver.
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--enable-features=') === 0) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to run the app on native wayland
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--ozone-platform=') === 0) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to run the app on native wayland
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--disable-smooth-scrolling') {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to disable smooth scrolling
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.length && arg[0] === '-') {
|
||||
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (setDefaults) {
|
||||
if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO;
|
||||
if (!matched.env) matched.env = 'prod';
|
||||
if (!matched.devPlugins) matched.devPlugins = [];
|
||||
if (flags.matched.showStackTraces) {
|
||||
this.showStackTraces_ = true;
|
||||
}
|
||||
|
||||
return {
|
||||
matched: matched,
|
||||
argv: argv,
|
||||
matched: flags.matched,
|
||||
argv: flags.argv,
|
||||
};
|
||||
}
|
||||
|
||||
@ -725,7 +588,7 @@ export default class BaseApplication {
|
||||
return flags.matched;
|
||||
}
|
||||
|
||||
public determineProfileDir(initArgs: any) {
|
||||
public static determineProfileDir(initArgs: MatchedStartFlags) {
|
||||
let output = '';
|
||||
|
||||
if (initArgs.profileDir) {
|
||||
@ -773,7 +636,7 @@ export default class BaseApplication {
|
||||
// https://immerjs.github.io/immer/docs/freezing
|
||||
setAutoFreeze(initArgs.env === 'dev');
|
||||
|
||||
const rootProfileDir = this.determineProfileDir(initArgs);
|
||||
const rootProfileDir = BaseApplication.determineProfileDir(initArgs);
|
||||
const { profileDir, profileConfig, isSubProfile } = await initProfile(rootProfileDir);
|
||||
this.profileConfig_ = profileConfig;
|
||||
|
||||
@ -863,6 +726,13 @@ export default class BaseApplication {
|
||||
Setting.setValue('isSafeMode', true);
|
||||
}
|
||||
|
||||
const safeModeFlagFile = join(profileDir, safeModeFlagFilename);
|
||||
if (await fs.pathExists(safeModeFlagFile) && fs.readFileSync(safeModeFlagFile, 'utf8') === 'true') {
|
||||
appLogger.info(`Safe mode enabled because of file: ${safeModeFlagFile}`);
|
||||
Setting.setValue('isSafeMode', true);
|
||||
fs.removeSync(safeModeFlagFile);
|
||||
}
|
||||
|
||||
if (Setting.value('firstStart')) {
|
||||
const locale = shim.detectAndSetLocale(Setting);
|
||||
reg.logger().info(`First start: detected locale as ${locale}`);
|
||||
|
174
packages/lib/utils/processStartFlags.ts
Normal file
174
packages/lib/utils/processStartFlags.ts
Normal file
@ -0,0 +1,174 @@
|
||||
|
||||
|
||||
import Logger, { LogLevel } from '@joplin/utils/Logger';
|
||||
import JoplinError from '../JoplinError';
|
||||
import { _ } from '../locale';
|
||||
import Setting from '../models/Setting';
|
||||
import Note from '../models/Note';
|
||||
|
||||
export interface MatchedStartFlags {
|
||||
profileDir?: string;
|
||||
welcomeDisabled?: boolean;
|
||||
env?: string;
|
||||
isSafeMode?: boolean;
|
||||
showStackTraces?: boolean;
|
||||
logLevel?: LogLevel;
|
||||
devPlugins?: string[];
|
||||
}
|
||||
|
||||
// Handles the initial flags passed to main script and
|
||||
// returns the remaining args.
|
||||
const processStartFlags = async (argv: string[], setDefaults = true) => {
|
||||
const matched: MatchedStartFlags = {};
|
||||
argv = argv.slice(0);
|
||||
argv.splice(0, 2); // First arguments are the node executable, and the node JS file
|
||||
|
||||
while (argv.length) {
|
||||
const arg = argv[0];
|
||||
const nextArg = argv.length >= 2 ? argv[1] : null;
|
||||
|
||||
if (arg === '--profile') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile <dir-path>'), 'flagError');
|
||||
matched.profileDir = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--no-welcome') {
|
||||
matched.welcomeDisabled = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--env') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--env <dev|prod>'), 'flagError');
|
||||
matched.env = nextArg;
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--is-demo') {
|
||||
Setting.setConstant('isDemo', true);
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--safe-mode') {
|
||||
matched.isSafeMode = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--open-dev-tools') {
|
||||
Setting.setConstant('flagOpenDevTools', true);
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--debug') {
|
||||
// Currently only handled by ElectronAppWrapper (isDebugMode property)
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--update-geolocation-disabled') {
|
||||
Note.updateGeolocationEnabled_ = false;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--stack-trace-enabled') {
|
||||
matched.showStackTraces = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--log-level') {
|
||||
if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level <none|error|warn|info|debug>'), 'flagError');
|
||||
matched.logLevel = Logger.levelStringToId(nextArg);
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('-psn') === 0) {
|
||||
// Some weird flag passed by macOS - can be ignored.
|
||||
// https://github.com/laurent22/joplin/issues/480
|
||||
// https://stackoverflow.com/questions/10242115
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--enable-logging') {
|
||||
// Electron-specific flag used for debugging - ignore it
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--dev-plugins') {
|
||||
matched.devPlugins = nextArg.split(',').map(p => p.trim());
|
||||
Setting.setConstant('startupDevPlugins', matched.devPlugins);
|
||||
argv.splice(0, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--remote-debugging-port=') === 0) {
|
||||
// Electron-specific flag used for debugging - ignore it. Electron expects this flag in '--x=y' form, a single string.
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--no-sandbox') {
|
||||
// Electron-specific flag for running the app without chrome-sandbox
|
||||
// Allows users to use it as a workaround for the electron+AppImage issue
|
||||
// https://github.com/laurent22/joplin/issues/2246
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--user-data-dir=') === 0) {
|
||||
// Electron-specific flag. Allows users to run the app with chromedriver.
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--enable-features=') === 0) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to run the app on native wayland
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--ozone-platform=') === 0) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to run the app on native wayland
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--disable-smooth-scrolling') {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to disable smooth scrolling
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.length && arg[0] === '-') {
|
||||
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (setDefaults) {
|
||||
if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO;
|
||||
if (!matched.env) matched.env = 'prod';
|
||||
if (!matched.devPlugins) matched.devPlugins = [];
|
||||
}
|
||||
|
||||
return {
|
||||
matched: matched,
|
||||
argv: argv,
|
||||
};
|
||||
};
|
||||
|
||||
export default processStartFlags;
|
Loading…
Reference in New Issue
Block a user