fix: clear desktop web cache on first version launch (#1184)
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"extends": ["../../.eslintrc.json"],
|
"extends": ["../../.eslintrc.json"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "warn",
|
"no-console": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"@typescript-eslint/no-var-requires": "off"
|
"@typescript-eslint/no-var-requires": "off"
|
||||||
},
|
},
|
||||||
|
|||||||
52
packages/desktop/app/AppState.ts
Normal file
52
packages/desktop/app/AppState.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { action, makeObservable, observable } from 'mobx'
|
||||||
|
import { MessageType } from '../test/TestIpcMessage'
|
||||||
|
import { Store } from './javascripts/Main/Store/Store'
|
||||||
|
import { StoreKeys } from './javascripts/Main/Store/StoreKeys'
|
||||||
|
import { Paths, Urls } from './javascripts/Main/Types/Paths'
|
||||||
|
import { UpdateState } from './javascripts/Main/UpdateManager'
|
||||||
|
import { handleTestMessage } from './javascripts/Main/Utils/Testing'
|
||||||
|
import { isTesting } from './javascripts/Main/Utils/Utils'
|
||||||
|
import { WindowState } from './javascripts/Main/Window'
|
||||||
|
|
||||||
|
export class AppState {
|
||||||
|
readonly version: string
|
||||||
|
readonly store: Store
|
||||||
|
readonly startUrl = Urls.indexHtml
|
||||||
|
readonly isPrimaryInstance: boolean
|
||||||
|
public willQuitApp = false
|
||||||
|
public lastBackupDate: number | null = null
|
||||||
|
public windowState?: WindowState
|
||||||
|
public deepLinkUrl?: string
|
||||||
|
public readonly updates: UpdateState
|
||||||
|
public lastRunVersion: string
|
||||||
|
|
||||||
|
constructor(app: Electron.App) {
|
||||||
|
this.version = app.getVersion()
|
||||||
|
this.store = new Store(Paths.userDataDir)
|
||||||
|
this.isPrimaryInstance = app.requestSingleInstanceLock()
|
||||||
|
|
||||||
|
this.lastRunVersion = this.store.get(StoreKeys.LastRunVersion) || 'unknown'
|
||||||
|
this.store.set(StoreKeys.LastRunVersion, this.version)
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
lastBackupDate: observable,
|
||||||
|
setBackupCreationDate: action,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.updates = new UpdateState(this)
|
||||||
|
|
||||||
|
if (isTesting()) {
|
||||||
|
handleTestMessage(MessageType.AppStateCall, (method, ...args) => {
|
||||||
|
;(this as any)[method](...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRunningVersionForFirstTime(): boolean {
|
||||||
|
return this.lastRunVersion !== this.version
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackupCreationDate(date: number | null): void {
|
||||||
|
this.lastBackupDate = date
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +1,15 @@
|
|||||||
import { App, Shell } from 'electron'
|
import { App, Shell } from 'electron'
|
||||||
import { action, makeObservable, observable } from 'mobx'
|
import { AppState } from './AppState'
|
||||||
import { MessageType } from '../test/TestIpcMessage'
|
|
||||||
import { createExtensionsServer } from './javascripts/Main/ExtensionsServer'
|
import { createExtensionsServer } from './javascripts/Main/ExtensionsServer'
|
||||||
import { Keychain } from './javascripts/Main/Keychain/Keychain'
|
import { Keychain } from './javascripts/Main/Keychain/Keychain'
|
||||||
import { Store, StoreKeys } from './javascripts/Main/Store'
|
import { StoreKeys } from './javascripts/Main/Store/StoreKeys'
|
||||||
import { AppName, initializeStrings } from './javascripts/Main/Strings'
|
import { AppName, initializeStrings } from './javascripts/Main/Strings'
|
||||||
import { Paths, Urls } from './javascripts/Main/Types/Paths'
|
|
||||||
import { isLinux, isMac, isWindows } from './javascripts/Main/Types/Platforms'
|
import { isLinux, isMac, isWindows } from './javascripts/Main/Types/Platforms'
|
||||||
import { UpdateState } from './javascripts/Main/UpdateManager'
|
import { isDev } from './javascripts/Main/Utils/Utils'
|
||||||
import { handleTestMessage } from './javascripts/Main/Utils/Testing'
|
import { createWindowState } from './javascripts/Main/Window'
|
||||||
import { isDev, isTesting } from './javascripts/Main/Utils/Utils'
|
|
||||||
import { createWindowState, WindowState } from './javascripts/Main/Window'
|
|
||||||
|
|
||||||
const deepLinkScheme = 'standardnotes'
|
const deepLinkScheme = 'standardnotes'
|
||||||
|
|
||||||
export class AppState {
|
|
||||||
readonly version: string
|
|
||||||
readonly store: Store
|
|
||||||
readonly startUrl = Urls.indexHtml
|
|
||||||
readonly isPrimaryInstance: boolean
|
|
||||||
public willQuitApp = false
|
|
||||||
public lastBackupDate: number | null = null
|
|
||||||
public windowState?: WindowState
|
|
||||||
public deepLinkUrl?: string
|
|
||||||
public readonly updates: UpdateState
|
|
||||||
|
|
||||||
constructor(app: Electron.App) {
|
|
||||||
this.version = app.getVersion()
|
|
||||||
this.store = new Store(Paths.userDataDir)
|
|
||||||
this.isPrimaryInstance = app.requestSingleInstanceLock()
|
|
||||||
makeObservable(this, {
|
|
||||||
lastBackupDate: observable,
|
|
||||||
setBackupCreationDate: action,
|
|
||||||
})
|
|
||||||
this.updates = new UpdateState(this)
|
|
||||||
|
|
||||||
if (isTesting()) {
|
|
||||||
handleTestMessage(MessageType.AppStateCall, (method, ...args) => {
|
|
||||||
;(this as any)[method](...args)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackupCreationDate(date: number | null): void {
|
|
||||||
this.lastBackupDate = date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeApplication(args: { app: Electron.App; ipcMain: Electron.IpcMain; shell: Shell }): void {
|
export function initializeApplication(args: { app: Electron.App; ipcMain: Electron.IpcMain; shell: Shell }): void {
|
||||||
const { app } = args
|
const { app } = args
|
||||||
|
|
||||||
@@ -146,7 +109,6 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
|
|||||||
const keychainWindow = await Keychain.ensureKeychainAccess(state.store)
|
const keychainWindow = await Keychain.ensureKeychainAccess(state.store)
|
||||||
|
|
||||||
initializeStrings(app.getLocale())
|
initializeStrings(app.getLocale())
|
||||||
initializeExtensionsServer(state.store)
|
|
||||||
|
|
||||||
const windowState = await createWindowState({
|
const windowState = await createWindowState({
|
||||||
shell,
|
shell,
|
||||||
@@ -157,6 +119,14 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (state.isRunningVersionForFirstTime()) {
|
||||||
|
console.log('Clearing window cache')
|
||||||
|
await windowState.window.webContents.session.clearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = createExtensionsServer()
|
||||||
|
state.store.set(StoreKeys.ExtServerHost, host)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the keychain window after the main window is created, otherwise the
|
* Close the keychain window after the main window is created, otherwise the
|
||||||
* app will quit automatically
|
* app will quit automatically
|
||||||
@@ -171,9 +141,3 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
|
|||||||
|
|
||||||
void windowState.window.loadURL(state.startUrl)
|
void windowState.window.loadURL(state.startUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeExtensionsServer(store: Store) {
|
|
||||||
const host = createExtensionsServer()
|
|
||||||
|
|
||||||
store.set(StoreKeys.ExtServerHost, host)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import path from 'path'
|
|||||||
import './@types/modules'
|
import './@types/modules'
|
||||||
import { initializeApplication } from './application'
|
import { initializeApplication } from './application'
|
||||||
import { enableExperimentalFeaturesForFileAccessFix } from './enableExperimentalWebFeatures'
|
import { enableExperimentalFeaturesForFileAccessFix } from './enableExperimentalWebFeatures'
|
||||||
import { Store, StoreKeys } from './javascripts/Main/Store'
|
import { Store } from './javascripts/Main/Store/Store'
|
||||||
|
import { StoreKeys } from './javascripts/Main/Store/StoreKeys'
|
||||||
import { isSnap } from './javascripts/Main/Types/Constants'
|
import { isSnap } from './javascripts/Main/Types/Constants'
|
||||||
import { Paths } from './javascripts/Main/Types/Paths'
|
import { Paths } from './javascripts/Main/Types/Paths'
|
||||||
import { setupTesting } from './javascripts/Main/Utils/Testing'
|
import { setupTesting } from './javascripts/Main/Utils/Testing'
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { dialog, shell, WebContents } from 'electron'
|
|||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { AppMessageType, MessageType } from '../../../../test/TestIpcMessage'
|
import { AppMessageType, MessageType } from '../../../../test/TestIpcMessage'
|
||||||
import { AppState } from '../../../application'
|
import { AppState } from '../../../AppState'
|
||||||
import { MessageToWebApp } from '../../Shared/IpcMessages'
|
import { MessageToWebApp } from '../../Shared/IpcMessages'
|
||||||
import { StoreKeys } from '../Store'
|
import { StoreKeys } from '../Store/StoreKeys'
|
||||||
import { backups as str } from '../Strings'
|
import { backups as str } from '../Strings'
|
||||||
import { Paths } from '../Types/Paths'
|
import { Paths } from '../Types/Paths'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { URL } from 'url'
|
|||||||
import { extensions as str } from './Strings'
|
import { extensions as str } from './Strings'
|
||||||
import { Paths } from './Types/Paths'
|
import { Paths } from './Types/Paths'
|
||||||
import { FileDoesNotExist } from './Utils/FileUtils'
|
import { FileDoesNotExist } from './Utils/FileUtils'
|
||||||
|
import { app } from 'electron'
|
||||||
|
|
||||||
const Protocol = 'http'
|
const Protocol = 'http'
|
||||||
|
|
||||||
@@ -61,7 +62,8 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
|||||||
const mimeType = mime.lookup(path.parse(filePath).ext)
|
const mimeType = mime.lookup(path.parse(filePath).ext)
|
||||||
|
|
||||||
response.setHeader('Access-Control-Allow-Origin', '*')
|
response.setHeader('Access-Control-Allow-Origin', '*')
|
||||||
response.setHeader('Cache-Control', 'max-age=604800')
|
response.setHeader('Cache-Control', 'no-cache')
|
||||||
|
response.setHeader('ETag', app.getVersion())
|
||||||
response.setHeader('Content-Type', `${mimeType}; charset=utf-8`)
|
response.setHeader('Content-Type', `${mimeType}; charset=utf-8`)
|
||||||
|
|
||||||
const data = fs.readFileSync(filePath)
|
const data = fs.readFileSync(filePath)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
||||||
import { AppState } from 'app/application'
|
import { AppState } from 'app/AppState'
|
||||||
import { shell } from 'electron'
|
import { shell } from 'electron'
|
||||||
import { StoreKeys } from '../Store'
|
import { StoreKeys } from '../Store/StoreKeys'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
deleteFile,
|
deleteFile,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||||
import keytar from 'keytar'
|
import keytar from 'keytar'
|
||||||
import { MessageToMainProcess } from '../../Shared/IpcMessages'
|
import { MessageToMainProcess } from '../../Shared/IpcMessages'
|
||||||
import { Store, StoreKeys } from '../Store'
|
import { Store } from '../Store/Store'
|
||||||
|
import { StoreKeys } from '../Store/StoreKeys'
|
||||||
import { AppName } from '../Strings'
|
import { AppName } from '../Strings'
|
||||||
import { keychainAccessIsUserConfigurable } from '../Types/Constants'
|
import { keychainAccessIsUserConfigurable } from '../Types/Constants'
|
||||||
import { Paths, Urls } from '../Types/Paths'
|
import { Paths, Urls } from '../Types/Paths'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BrowserWindow } from 'electron'
|
import { BrowserWindow } from 'electron'
|
||||||
import { Store } from '../Store'
|
import { Store } from '../Store/Store'
|
||||||
|
|
||||||
export interface KeychainInterface {
|
export interface KeychainInterface {
|
||||||
ensureKeychainAccess(store: Store): Promise<BrowserWindow | undefined>
|
ensureKeychainAccess(store: Store): Promise<BrowserWindow | undefined>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppState } from 'app/application'
|
import { AppState } from 'app/AppState'
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
WebContents,
|
WebContents,
|
||||||
} from 'electron'
|
} from 'electron'
|
||||||
import { autorun } from 'mobx'
|
import { autorun } from 'mobx'
|
||||||
import { Store, StoreKeys } from '../Store'
|
import { Store } from '../Store/Store'
|
||||||
|
import { StoreKeys } from '../Store/StoreKeys'
|
||||||
import { appMenu as str, contextMenu } from '../Strings'
|
import { appMenu as str, contextMenu } from '../Strings'
|
||||||
import { TrayManager } from '../TrayManager'
|
import { TrayManager } from '../TrayManager'
|
||||||
import { autoUpdatingAvailable } from '../Types/Constants'
|
import { autoUpdatingAvailable } from '../Types/Constants'
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ import { downloadFile, getJSON } from './Networking'
|
|||||||
import { Component, MappingFile, PackageInfo, PackageManagerInterface, SyncTask } from './PackageManagerInterface'
|
import { Component, MappingFile, PackageInfo, PackageManagerInterface, SyncTask } from './PackageManagerInterface'
|
||||||
|
|
||||||
function logMessage(...message: any) {
|
function logMessage(...message: any) {
|
||||||
log.info('PackageManager:', ...message)
|
log.info('PackageManager[Info]:', ...message)
|
||||||
}
|
}
|
||||||
|
|
||||||
function logError(...message: any) {
|
function logError(...message: any) {
|
||||||
console.error('PackageManager:', ...message)
|
console.error('PackageManager[Error]:', ...message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -357,10 +357,13 @@ async function installComponent(
|
|||||||
|
|
||||||
function pathsForComponent(component: Pick<Component, 'content'>) {
|
function pathsForComponent(component: Pick<Component, 'content'>) {
|
||||||
const relativePath = path.join(Paths.extensionsDirRelative, component.content!.package_info.identifier)
|
const relativePath = path.join(Paths.extensionsDirRelative, component.content!.package_info.identifier)
|
||||||
|
const absolutePath = path.join(Paths.userDataDir, relativePath)
|
||||||
|
const downloadPath = path.join(Paths.tempDir, AppName, 'downloads', component.content!.name + '.zip')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
relativePath,
|
relativePath,
|
||||||
absolutePath: path.join(Paths.userDataDir, relativePath),
|
absolutePath,
|
||||||
downloadPath: path.join(Paths.tempDir, AppName, 'downloads', component.content!.name + '.zip'),
|
downloadPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CrossProcessBridge } from '../../Renderer/CrossProcessBridge'
|
import { CrossProcessBridge } from '../../Renderer/CrossProcessBridge'
|
||||||
import { Store, StoreKeys } from '../Store'
|
import { Store } from '../Store/Store'
|
||||||
|
import { StoreKeys } from '../Store/StoreKeys'
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable no-inline-comments */
|
/* eslint-disable no-inline-comments */
|
||||||
import { Store, StoreKeys } from './Store'
|
import { Store } from './Store/Store'
|
||||||
|
import { StoreKeys } from './Store/StoreKeys'
|
||||||
import { isMac } from './Types/Platforms'
|
import { isMac } from './Types/Platforms'
|
||||||
import { isDev } from './Utils/Utils'
|
import { isDev } from './Utils/Utils'
|
||||||
|
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { MessageType } from '../../../test/TestIpcMessage'
|
|
||||||
import { BackupsDirectoryName } from './Backups/BackupsManager'
|
|
||||||
import { Language } from './SpellcheckerManager'
|
|
||||||
import { FileDoesNotExist } from './Utils/FileUtils'
|
|
||||||
import { handleTestMessage } from './Utils/Testing'
|
|
||||||
import { ensureIsBoolean, isBoolean, isDev, isTesting } from './Utils/Utils'
|
|
||||||
|
|
||||||
const app = process.type === 'browser' ? require('electron').app : require('@electron/remote').app
|
|
||||||
|
|
||||||
function logError(...message: any) {
|
|
||||||
console.error('store:', ...message)
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum StoreKeys {
|
|
||||||
ExtServerHost = 'extServerHost',
|
|
||||||
UseSystemMenuBar = 'useSystemMenuBar',
|
|
||||||
MenuBarVisible = 'isMenuBarVisible',
|
|
||||||
BackupsLocation = 'backupsLocation',
|
|
||||||
BackupsDisabled = 'backupsDisabled',
|
|
||||||
MinimizeToTray = 'minimizeToTray',
|
|
||||||
EnableAutoUpdate = 'enableAutoUpdates',
|
|
||||||
ZoomFactor = 'zoomFactor',
|
|
||||||
SelectedSpellCheckerLanguageCodes = 'selectedSpellCheckerLanguageCodes',
|
|
||||||
UseNativeKeychain = 'useNativeKeychain',
|
|
||||||
|
|
||||||
FileBackupsEnabled = 'fileBackupsEnabled',
|
|
||||||
FileBackupsLocation = 'fileBackupsLocation',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StoreData {
|
|
||||||
[StoreKeys.ExtServerHost]: string
|
|
||||||
[StoreKeys.UseSystemMenuBar]: boolean
|
|
||||||
[StoreKeys.MenuBarVisible]: boolean
|
|
||||||
[StoreKeys.BackupsLocation]: string
|
|
||||||
[StoreKeys.BackupsDisabled]: boolean
|
|
||||||
[StoreKeys.MinimizeToTray]: boolean
|
|
||||||
[StoreKeys.EnableAutoUpdate]: boolean
|
|
||||||
[StoreKeys.UseNativeKeychain]: boolean | null
|
|
||||||
[StoreKeys.ZoomFactor]: number
|
|
||||||
[StoreKeys.SelectedSpellCheckerLanguageCodes]: Set<Language> | null
|
|
||||||
[StoreKeys.FileBackupsEnabled]: boolean
|
|
||||||
[StoreKeys.FileBackupsLocation]: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSanitizedStoreData(data: any = {}): StoreData {
|
|
||||||
return {
|
|
||||||
[StoreKeys.MenuBarVisible]: ensureIsBoolean(data[StoreKeys.MenuBarVisible], true),
|
|
||||||
[StoreKeys.UseSystemMenuBar]: ensureIsBoolean(data[StoreKeys.UseSystemMenuBar], false),
|
|
||||||
[StoreKeys.BackupsDisabled]: ensureIsBoolean(data[StoreKeys.BackupsDisabled], false),
|
|
||||||
[StoreKeys.MinimizeToTray]: ensureIsBoolean(data[StoreKeys.MinimizeToTray], false),
|
|
||||||
[StoreKeys.EnableAutoUpdate]: ensureIsBoolean(data[StoreKeys.EnableAutoUpdate], true),
|
|
||||||
[StoreKeys.UseNativeKeychain]: isBoolean(data[StoreKeys.UseNativeKeychain])
|
|
||||||
? data[StoreKeys.UseNativeKeychain]
|
|
||||||
: null,
|
|
||||||
[StoreKeys.ExtServerHost]: data[StoreKeys.ExtServerHost],
|
|
||||||
[StoreKeys.BackupsLocation]: sanitizeBackupsLocation(data[StoreKeys.BackupsLocation]),
|
|
||||||
[StoreKeys.ZoomFactor]: sanitizeZoomFactor(data[StoreKeys.ZoomFactor]),
|
|
||||||
[StoreKeys.SelectedSpellCheckerLanguageCodes]: sanitizeSpellCheckerLanguageCodes(
|
|
||||||
data[StoreKeys.SelectedSpellCheckerLanguageCodes],
|
|
||||||
),
|
|
||||||
[StoreKeys.FileBackupsEnabled]: ensureIsBoolean(data[StoreKeys.FileBackupsEnabled], false),
|
|
||||||
[StoreKeys.FileBackupsLocation]: data[StoreKeys.FileBackupsLocation],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeZoomFactor(factor?: any): number {
|
|
||||||
if (typeof factor === 'number' && factor > 0) {
|
|
||||||
return factor
|
|
||||||
} else {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeBackupsLocation(location?: unknown): string {
|
|
||||||
const defaultPath = path.join(
|
|
||||||
isTesting() ? app.getPath('userData') : isDev() ? app.getPath('documents') : app.getPath('home'),
|
|
||||||
BackupsDirectoryName,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (typeof location !== 'string') {
|
|
||||||
return defaultPath
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stat = fs.lstatSync(location)
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
return location
|
|
||||||
}
|
|
||||||
/** Path points to something other than a directory */
|
|
||||||
return defaultPath
|
|
||||||
} catch (e) {
|
|
||||||
/** Path does not point to a valid directory */
|
|
||||||
logError(e)
|
|
||||||
return defaultPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeSpellCheckerLanguageCodes(languages?: unknown): Set<Language> | null {
|
|
||||||
if (!languages) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (!Array.isArray(languages)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const set = new Set<Language>()
|
|
||||||
const validLanguages = Object.values(Language)
|
|
||||||
for (const language of languages) {
|
|
||||||
if (validLanguages.includes(language)) {
|
|
||||||
set.add(language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeStoreData(data: StoreData): string {
|
|
||||||
return JSON.stringify(data, (_key, value) => {
|
|
||||||
if (value instanceof Set) {
|
|
||||||
return Array.from(value)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDataFile(filePath: string) {
|
|
||||||
try {
|
|
||||||
const fileData = fs.readFileSync(filePath)
|
|
||||||
const userData = JSON.parse(fileData.toString())
|
|
||||||
return createSanitizedStoreData(userData)
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log('Error reading store file', error)
|
|
||||||
if (error.code !== FileDoesNotExist) {
|
|
||||||
logError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return createSanitizedStoreData({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Store {
|
|
||||||
static instance: Store
|
|
||||||
readonly path: string
|
|
||||||
readonly data: StoreData
|
|
||||||
|
|
||||||
static getInstance(): Store {
|
|
||||||
if (!this.instance) {
|
|
||||||
/**
|
|
||||||
* Renderer process has to get `app` module via `remote`, whereas the main process
|
|
||||||
* can get it directly app.getPath('userData') will return a string of the user's
|
|
||||||
* app data directory path.
|
|
||||||
* TODO(baptiste): stop using Store in the renderer process.
|
|
||||||
*/
|
|
||||||
const userDataPath = app.getPath('userData')
|
|
||||||
this.instance = new Store(userDataPath)
|
|
||||||
}
|
|
||||||
return this.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
static get<T extends keyof StoreData>(key: T): StoreData[T] {
|
|
||||||
return this.getInstance().get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(userDataPath: string) {
|
|
||||||
this.path = path.join(userDataPath, 'user-preferences.json')
|
|
||||||
this.data = parseDataFile(this.path)
|
|
||||||
|
|
||||||
if (isTesting()) {
|
|
||||||
handleTestMessage(MessageType.StoreSettingsLocation, () => this.path)
|
|
||||||
handleTestMessage(MessageType.StoreSet, (key, value) => {
|
|
||||||
this.set(key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get<T extends keyof StoreData>(key: T): StoreData[T] {
|
|
||||||
return this.data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
set<T extends keyof StoreData>(key: T, val: StoreData[T]): void {
|
|
||||||
this.data[key] = val
|
|
||||||
fs.writeFileSync(this.path, serializeStoreData(this.data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
packages/desktop/app/javascripts/Main/Store/Store.ts
Normal file
52
packages/desktop/app/javascripts/Main/Store/Store.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { MessageType } from '../../../../test/TestIpcMessage'
|
||||||
|
import { handleTestMessage } from '../Utils/Testing'
|
||||||
|
import { isTesting } from '../Utils/Utils'
|
||||||
|
import { parseDataFile, serializeStoreData } from './createSanitizedStoreData'
|
||||||
|
import { StoreData } from './StoreKeys'
|
||||||
|
|
||||||
|
export const app = process.type === 'browser' ? require('electron').app : require('@electron/remote').app
|
||||||
|
|
||||||
|
export function logError(...message: any) {
|
||||||
|
console.error('store:', ...message)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Store {
|
||||||
|
static instance: Store
|
||||||
|
readonly path: string
|
||||||
|
readonly data: StoreData
|
||||||
|
|
||||||
|
static getInstance(): Store {
|
||||||
|
if (!this.instance) {
|
||||||
|
const userDataPath = app.getPath('userData')
|
||||||
|
this.instance = new Store(userDataPath)
|
||||||
|
}
|
||||||
|
return this.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
static get<T extends keyof StoreData>(key: T): StoreData[T] {
|
||||||
|
return this.getInstance().get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(userDataPath: string) {
|
||||||
|
this.path = path.join(userDataPath, 'user-preferences.json')
|
||||||
|
this.data = parseDataFile(this.path)
|
||||||
|
|
||||||
|
if (isTesting()) {
|
||||||
|
handleTestMessage(MessageType.StoreSettingsLocation, () => this.path)
|
||||||
|
handleTestMessage(MessageType.StoreSet, (key, value) => {
|
||||||
|
this.set(key, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get<T extends keyof StoreData>(key: T): StoreData[T] {
|
||||||
|
return this.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
set<T extends keyof StoreData>(key: T, val: StoreData[T]): void {
|
||||||
|
this.data[key] = val
|
||||||
|
fs.writeFileSync(this.path, serializeStoreData(this.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/desktop/app/javascripts/Main/Store/StoreKeys.ts
Normal file
33
packages/desktop/app/javascripts/Main/Store/StoreKeys.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Language } from '../SpellcheckerManager'
|
||||||
|
|
||||||
|
export enum StoreKeys {
|
||||||
|
ExtServerHost = 'extServerHost',
|
||||||
|
UseSystemMenuBar = 'useSystemMenuBar',
|
||||||
|
MenuBarVisible = 'isMenuBarVisible',
|
||||||
|
BackupsLocation = 'backupsLocation',
|
||||||
|
BackupsDisabled = 'backupsDisabled',
|
||||||
|
MinimizeToTray = 'minimizeToTray',
|
||||||
|
EnableAutoUpdate = 'enableAutoUpdates',
|
||||||
|
ZoomFactor = 'zoomFactor',
|
||||||
|
SelectedSpellCheckerLanguageCodes = 'selectedSpellCheckerLanguageCodes',
|
||||||
|
UseNativeKeychain = 'useNativeKeychain',
|
||||||
|
FileBackupsEnabled = 'fileBackupsEnabled',
|
||||||
|
FileBackupsLocation = 'fileBackupsLocation',
|
||||||
|
LastRunVersion = 'LastRunVersion',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoreData {
|
||||||
|
[StoreKeys.ExtServerHost]: string
|
||||||
|
[StoreKeys.UseSystemMenuBar]: boolean
|
||||||
|
[StoreKeys.MenuBarVisible]: boolean
|
||||||
|
[StoreKeys.BackupsLocation]: string
|
||||||
|
[StoreKeys.BackupsDisabled]: boolean
|
||||||
|
[StoreKeys.MinimizeToTray]: boolean
|
||||||
|
[StoreKeys.EnableAutoUpdate]: boolean
|
||||||
|
[StoreKeys.UseNativeKeychain]: boolean | null
|
||||||
|
[StoreKeys.ZoomFactor]: number
|
||||||
|
[StoreKeys.SelectedSpellCheckerLanguageCodes]: Set<Language> | null
|
||||||
|
[StoreKeys.FileBackupsEnabled]: boolean
|
||||||
|
[StoreKeys.FileBackupsLocation]: string
|
||||||
|
[StoreKeys.LastRunVersion]: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { BackupsDirectoryName } from '../Backups/BackupsManager'
|
||||||
|
import { Language } from '../SpellcheckerManager'
|
||||||
|
import { FileDoesNotExist } from '../Utils/FileUtils'
|
||||||
|
import { ensureIsBoolean, isBoolean, isDev, isTesting } from '../Utils/Utils'
|
||||||
|
import { StoreData, StoreKeys } from './StoreKeys'
|
||||||
|
import { app, logError } from './Store'
|
||||||
|
|
||||||
|
export function createSanitizedStoreData(data: any = {}): StoreData {
|
||||||
|
return {
|
||||||
|
[StoreKeys.MenuBarVisible]: ensureIsBoolean(data[StoreKeys.MenuBarVisible], true),
|
||||||
|
[StoreKeys.UseSystemMenuBar]: ensureIsBoolean(data[StoreKeys.UseSystemMenuBar], false),
|
||||||
|
[StoreKeys.BackupsDisabled]: ensureIsBoolean(data[StoreKeys.BackupsDisabled], false),
|
||||||
|
[StoreKeys.MinimizeToTray]: ensureIsBoolean(data[StoreKeys.MinimizeToTray], false),
|
||||||
|
[StoreKeys.EnableAutoUpdate]: ensureIsBoolean(data[StoreKeys.EnableAutoUpdate], true),
|
||||||
|
[StoreKeys.UseNativeKeychain]: isBoolean(data[StoreKeys.UseNativeKeychain])
|
||||||
|
? data[StoreKeys.UseNativeKeychain]
|
||||||
|
: null,
|
||||||
|
[StoreKeys.ExtServerHost]: data[StoreKeys.ExtServerHost],
|
||||||
|
[StoreKeys.BackupsLocation]: sanitizeBackupsLocation(data[StoreKeys.BackupsLocation]),
|
||||||
|
[StoreKeys.ZoomFactor]: sanitizeZoomFactor(data[StoreKeys.ZoomFactor]),
|
||||||
|
[StoreKeys.SelectedSpellCheckerLanguageCodes]: sanitizeSpellCheckerLanguageCodes(
|
||||||
|
data[StoreKeys.SelectedSpellCheckerLanguageCodes],
|
||||||
|
),
|
||||||
|
[StoreKeys.FileBackupsEnabled]: ensureIsBoolean(data[StoreKeys.FileBackupsEnabled], false),
|
||||||
|
[StoreKeys.FileBackupsLocation]: data[StoreKeys.FileBackupsLocation],
|
||||||
|
[StoreKeys.LastRunVersion]: data[StoreKeys.LastRunVersion],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sanitizeZoomFactor(factor?: any): number {
|
||||||
|
if (typeof factor === 'number' && factor > 0) {
|
||||||
|
return factor
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sanitizeBackupsLocation(location?: unknown): string {
|
||||||
|
const defaultPath = path.join(
|
||||||
|
isTesting() ? app.getPath('userData') : isDev() ? app.getPath('documents') : app.getPath('home'),
|
||||||
|
BackupsDirectoryName,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (typeof location !== 'string') {
|
||||||
|
return defaultPath
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stat = fs.lstatSync(location)
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
/** Path points to something other than a directory */
|
||||||
|
return defaultPath
|
||||||
|
} catch (e) {
|
||||||
|
/** Path does not point to a valid directory */
|
||||||
|
logError(e)
|
||||||
|
return defaultPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sanitizeSpellCheckerLanguageCodes(languages?: unknown): Set<Language> | null {
|
||||||
|
if (!languages) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!Array.isArray(languages)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = new Set<Language>()
|
||||||
|
const validLanguages = Object.values(Language)
|
||||||
|
for (const language of languages) {
|
||||||
|
if (validLanguages.includes(language)) {
|
||||||
|
set.add(language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeStoreData(data: StoreData): string {
|
||||||
|
return JSON.stringify(data, (_key, value) => {
|
||||||
|
if (value instanceof Set) {
|
||||||
|
return Array.from(value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseDataFile(filePath: string) {
|
||||||
|
try {
|
||||||
|
const fileData = fs.readFileSync(filePath)
|
||||||
|
const userData = JSON.parse(fileData.toString())
|
||||||
|
return createSanitizedStoreData(userData)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log('Error reading store file', error)
|
||||||
|
if (error.code !== FileDoesNotExist) {
|
||||||
|
logError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createSanitizedStoreData({})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Menu, Tray } from 'electron'
|
import { Menu, Tray } from 'electron'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Store, StoreKeys } from './Store'
|
import { Store } from './Store/Store'
|
||||||
|
import { StoreKeys } from './Store/StoreKeys'
|
||||||
import { AppName, tray as str } from './Strings'
|
import { AppName, tray as str } from './Strings'
|
||||||
import { isLinux, isWindows } from './Types/Platforms'
|
import { isLinux, isWindows } from './Types/Platforms'
|
||||||
import { isDev } from './Utils/Utils'
|
import { isDev } from './Utils/Utils'
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import electronLog from 'electron-log'
|
|||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
||||||
import { MessageType } from '../../../test/TestIpcMessage'
|
import { MessageType } from '../../../test/TestIpcMessage'
|
||||||
import { AppState } from '../../application'
|
import { AppState } from '../../AppState'
|
||||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||||
import { StoreKeys } from './Store'
|
import { StoreKeys } from './Store/StoreKeys'
|
||||||
import { updates as str } from './Strings'
|
import { updates as str } from './Strings'
|
||||||
import { autoUpdatingAvailable } from './Types/Constants'
|
import { autoUpdatingAvailable } from './Types/Constants'
|
||||||
import { handleTestMessage } from './Utils/Testing'
|
import { handleTestMessage } from './Utils/Testing'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fs from 'fs'
|
|||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { AppMessageType, MessageType } from '../../../test/TestIpcMessage'
|
import { AppMessageType, MessageType } from '../../../test/TestIpcMessage'
|
||||||
import { AppState } from '../../application'
|
import { AppState } from '../../AppState'
|
||||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||||
import { createBackupsManager } from './Backups/BackupsManager'
|
import { createBackupsManager } from './Backups/BackupsManager'
|
||||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||||
@@ -16,7 +16,8 @@ import { initializePackageManager } from './Packages/PackageManager'
|
|||||||
import { RemoteBridge } from './Remote/RemoteBridge'
|
import { RemoteBridge } from './Remote/RemoteBridge'
|
||||||
import { initializeSearchManager } from './Search/SearchManager'
|
import { initializeSearchManager } from './Search/SearchManager'
|
||||||
import { createSpellcheckerManager } from './SpellcheckerManager'
|
import { createSpellcheckerManager } from './SpellcheckerManager'
|
||||||
import { Store, StoreKeys } from './Store'
|
import { Store } from './Store/Store'
|
||||||
|
import { StoreKeys } from './Store/StoreKeys'
|
||||||
import { createTrayManager, TrayManager } from './TrayManager'
|
import { createTrayManager, TrayManager } from './TrayManager'
|
||||||
import { Paths } from './Types/Paths'
|
import { Paths } from './Types/Paths'
|
||||||
import { isMac, isWindows } from './Types/Platforms'
|
import { isMac, isWindows } from './Types/Platforms'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BrowserWindow } from 'electron'
|
import { BrowserWindow } from 'electron'
|
||||||
import { Store, StoreKeys } from './Store'
|
import { Store } from './Store/Store'
|
||||||
|
import { StoreKeys } from './Store/StoreKeys'
|
||||||
|
|
||||||
export function initializeZoomManager(window: BrowserWindow, store: Store): void {
|
export function initializeZoomManager(window: BrowserWindow, store: Store): void {
|
||||||
window.webContents.on('dom-ready', () => {
|
window.webContents.on('dom-ready', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user