fix: clear desktop web cache on first version launch (#1184)
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"rules": {
|
||||
"no-console": "warn",
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@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 { action, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../test/TestIpcMessage'
|
||||
import { AppState } from './AppState'
|
||||
import { createExtensionsServer } from './javascripts/Main/ExtensionsServer'
|
||||
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 { Paths, Urls } from './javascripts/Main/Types/Paths'
|
||||
import { isLinux, isMac, isWindows } from './javascripts/Main/Types/Platforms'
|
||||
import { UpdateState } from './javascripts/Main/UpdateManager'
|
||||
import { handleTestMessage } from './javascripts/Main/Utils/Testing'
|
||||
import { isDev, isTesting } from './javascripts/Main/Utils/Utils'
|
||||
import { createWindowState, WindowState } from './javascripts/Main/Window'
|
||||
import { isDev } from './javascripts/Main/Utils/Utils'
|
||||
import { createWindowState } from './javascripts/Main/Window'
|
||||
|
||||
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 {
|
||||
const { app } = args
|
||||
|
||||
@@ -146,7 +109,6 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
|
||||
const keychainWindow = await Keychain.ensureKeychainAccess(state.store)
|
||||
|
||||
initializeStrings(app.getLocale())
|
||||
initializeExtensionsServer(state.store)
|
||||
|
||||
const windowState = await createWindowState({
|
||||
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
|
||||
* app will quit automatically
|
||||
@@ -171,9 +141,3 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
|
||||
|
||||
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 { initializeApplication } from './application'
|
||||
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 { Paths } from './javascripts/Main/Types/Paths'
|
||||
import { setupTesting } from './javascripts/Main/Utils/Testing'
|
||||
|
||||
@@ -2,9 +2,9 @@ import { dialog, shell, WebContents } from 'electron'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { AppMessageType, MessageType } from '../../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../../application'
|
||||
import { AppState } from '../../../AppState'
|
||||
import { MessageToWebApp } from '../../Shared/IpcMessages'
|
||||
import { StoreKeys } from '../Store'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
import { backups as str } from '../Strings'
|
||||
import { Paths } from '../Types/Paths'
|
||||
import {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { URL } from 'url'
|
||||
import { extensions as str } from './Strings'
|
||||
import { Paths } from './Types/Paths'
|
||||
import { FileDoesNotExist } from './Utils/FileUtils'
|
||||
import { app } from 'electron'
|
||||
|
||||
const Protocol = 'http'
|
||||
|
||||
@@ -61,7 +62,8 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
||||
const mimeType = mime.lookup(path.parse(filePath).ext)
|
||||
|
||||
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`)
|
||||
|
||||
const data = fs.readFileSync(filePath)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { AppState } from 'app/application'
|
||||
import { AppState } from 'app/AppState'
|
||||
import { shell } from 'electron'
|
||||
import { StoreKeys } from '../Store'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
import path from 'path'
|
||||
import {
|
||||
deleteFile,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||
import keytar from 'keytar'
|
||||
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 { keychainAccessIsUserConfigurable } from '../Types/Constants'
|
||||
import { Paths, Urls } from '../Types/Paths'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { Store } from '../Store'
|
||||
import { Store } from '../Store/Store'
|
||||
|
||||
export interface KeychainInterface {
|
||||
ensureKeychainAccess(store: Store): Promise<BrowserWindow | undefined>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppState } from 'app/application'
|
||||
import { AppState } from 'app/AppState'
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
WebContents,
|
||||
} from 'electron'
|
||||
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 { TrayManager } from '../TrayManager'
|
||||
import { autoUpdatingAvailable } from '../Types/Constants'
|
||||
|
||||
@@ -19,11 +19,11 @@ import { downloadFile, getJSON } from './Networking'
|
||||
import { Component, MappingFile, PackageInfo, PackageManagerInterface, SyncTask } from './PackageManagerInterface'
|
||||
|
||||
function logMessage(...message: any) {
|
||||
log.info('PackageManager:', ...message)
|
||||
log.info('PackageManager[Info]:', ...message)
|
||||
}
|
||||
|
||||
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'>) {
|
||||
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 {
|
||||
relativePath,
|
||||
absolutePath: path.join(Paths.userDataDir, relativePath),
|
||||
downloadPath: path.join(Paths.tempDir, AppName, 'downloads', component.content!.name + '.zip'),
|
||||
absolutePath,
|
||||
downloadPath,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* 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 { 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 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 { isLinux, isWindows } from './Types/Platforms'
|
||||
import { isDev } from './Utils/Utils'
|
||||
|
||||
@@ -4,9 +4,9 @@ import electronLog from 'electron-log'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../application'
|
||||
import { AppState } from '../../AppState'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { StoreKeys } from './Store'
|
||||
import { StoreKeys } from './Store/StoreKeys'
|
||||
import { updates as str } from './Strings'
|
||||
import { autoUpdatingAvailable } from './Types/Constants'
|
||||
import { handleTestMessage } from './Utils/Testing'
|
||||
|
||||
@@ -4,7 +4,7 @@ import fs from 'fs'
|
||||
import { debounce } from 'lodash'
|
||||
import path from 'path'
|
||||
import { AppMessageType, MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../application'
|
||||
import { AppState } from '../../AppState'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { createBackupsManager } from './Backups/BackupsManager'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
@@ -16,7 +16,8 @@ import { initializePackageManager } from './Packages/PackageManager'
|
||||
import { RemoteBridge } from './Remote/RemoteBridge'
|
||||
import { initializeSearchManager } from './Search/SearchManager'
|
||||
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 { Paths } from './Types/Paths'
|
||||
import { isMac, isWindows } from './Types/Platforms'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 {
|
||||
window.webContents.on('dom-ready', () => {
|
||||
|
||||
Reference in New Issue
Block a user