1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-21 02:33:37 +00:00

Merge commit '8b2dfc6cdcb8ff5b604364c2ea6d343473aee7cd' into feature/workspaces

# Conflicts:
#	common/spec/importers/fsecureFskImporter.spec.ts
#	common/spec/services/cipher.service.spec.ts
#	package-lock.json
#	package.json
This commit is contained in:
Hinton
2022-01-03 12:46:02 +01:00
108 changed files with 11516 additions and 8636 deletions

View File

@@ -11,20 +11,21 @@
"dependencies": {
"@bitwarden/jslib-common": "file:../common",
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"electron": "14.2.0",
"electron": "16.0.2",
"electron-log": "4.4.1",
"electron-store": "8.0.1",
"electron-updater": "4.3.9",
"electron-updater": "4.6.1",
"forcefocus": "^1.1.0",
"keytar": "7.7.0"
},
"devDependencies": {
"@types/node": "^14.17.1",
"@types/node": "^16.11.12",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
}
},
"../common": {
"name": "@bitwarden/jslib-common",
"version": "0.0.0",
"license": "GPL-3.0",
"dependencies": {
@@ -41,7 +42,7 @@
},
"devDependencies": {
"@types/lunr": "^2.3.3",
"@types/node": "^14.17.1",
"@types/node": "^16.11.12",
"@types/node-forge": "^0.9.7",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
@@ -104,9 +105,10 @@
}
},
"node_modules/@types/node": {
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz",
"integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ=="
"version": "16.11.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
"integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.3.9",
@@ -288,9 +290,9 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/builder-util-runtime": {
"version": "8.7.5",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz",
"integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==",
"version": "8.9.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz",
"integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==",
"dependencies": {
"debug": "^4.3.2",
"sax": "^1.2.4"
@@ -544,12 +546,12 @@
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"node_modules/electron": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz",
"integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==",
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-16.0.2.tgz",
"integrity": "sha512-kT746yVMztrP4BbT3nrFNcUcfgFu2yelUw6TWBVTy0pju+fBISaqcvoiMrq+8U0vRpoXSu2MJYygOf4T0Det7g==",
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^1.0.1",
"@electron/get": "^1.13.0",
"@types/node": "^14.6.2",
"extract-zip": "^1.0.3"
},
@@ -578,15 +580,15 @@
}
},
"node_modules/electron-updater": {
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz",
"integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==",
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz",
"integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==",
"dependencies": {
"@types/semver": "^7.3.5",
"builder-util-runtime": "8.7.5",
"@types/semver": "^7.3.6",
"builder-util-runtime": "8.9.1",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.4",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "^7.3.5"
@@ -638,6 +640,11 @@
"node": ">= 10.0.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz",
"integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -1887,7 +1894,7 @@
"@microsoft/signalr": "5.0.10",
"@microsoft/signalr-protocol-msgpack": "5.0.10",
"@types/lunr": "^2.3.3",
"@types/node": "^14.17.1",
"@types/node": "^16.11.12",
"@types/node-forge": "^0.9.7",
"@types/papaparse": "^5.2.5",
"@types/tldjs": "^2.3.0",
@@ -1942,9 +1949,10 @@
}
},
"@types/node": {
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz",
"integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ=="
"version": "16.11.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
"integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==",
"dev": true
},
"@types/semver": {
"version": "7.3.9",
@@ -2076,9 +2084,9 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"builder-util-runtime": {
"version": "8.7.5",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz",
"integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==",
"version": "8.9.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz",
"integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==",
"requires": {
"debug": "^4.3.2",
"sax": "^1.2.4"
@@ -2268,13 +2276,20 @@
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"electron": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz",
"integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==",
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-16.0.2.tgz",
"integrity": "sha512-kT746yVMztrP4BbT3nrFNcUcfgFu2yelUw6TWBVTy0pju+fBISaqcvoiMrq+8U0vRpoXSu2MJYygOf4T0Det7g==",
"requires": {
"@electron/get": "^1.0.1",
"@electron/get": "^1.13.0",
"@types/node": "^14.6.2",
"extract-zip": "^1.0.3"
},
"dependencies": {
"@types/node": {
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz",
"integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ=="
}
}
},
"electron-log": {
@@ -2292,15 +2307,15 @@
}
},
"electron-updater": {
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz",
"integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==",
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz",
"integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==",
"requires": {
"@types/semver": "^7.3.5",
"builder-util-runtime": "8.7.5",
"@types/semver": "^7.3.6",
"builder-util-runtime": "8.9.1",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.4",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "^7.3.5"

View File

@@ -22,17 +22,17 @@
"test:node": " "
},
"devDependencies": {
"@types/node": "^14.17.1",
"@types/node": "^16.11.12",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
},
"dependencies": {
"@bitwarden/jslib-common": "file:../common",
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"electron": "14.2.0",
"electron": "16.0.2",
"electron-log": "4.4.1",
"electron-store": "8.0.1",
"electron-updater": "4.3.9",
"electron-updater": "4.6.1",
"forcefocus": "^1.1.0",
"keytar": "7.7.0"
}

View File

@@ -1,21 +1,18 @@
import { ipcMain, systemPreferences } from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
import { ElectronConstants } from './electronConstants';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StateService } from 'jslib-common/abstractions/state.service';
export default class BiometricDarwinMain implements BiometricMain {
isError: boolean = false;
constructor(private storageService: StorageService, private i18nservice: I18nService) {}
constructor(private i18nservice: I18nService, private stateService: StateService) {}
async init() {
this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric());
this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId');
this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptTouchId');
await this.stateService.setEnableBiometric(await this.supportsBiometric());
await this.stateService.setBiometricText('unlockWithTouchId');
await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptTouchId');
ipcMain.on('biometric', async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric();

View File

@@ -1,22 +1,20 @@
import { ipcMain } from 'electron';
import forceFocus from 'forcefocus';
import { ElectronConstants } from './electronConstants';
import { WindowMain } from './window.main';
import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { StateService } from 'jslib-common/abstractions/state.service';
export default class BiometricWindowsMain implements BiometricMain {
isError: boolean = false;
private windowsSecurityCredentialsUiModule: any;
constructor(private storageService: StorageService, private i18nservice: I18nService, private windowMain: WindowMain,
private logService: LogService) { }
constructor(private i18nservice: I18nService, private windowMain: WindowMain,
private stateService: StateService, private logService: LogService) { }
async init() {
this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule();
@@ -27,9 +25,9 @@ export default class BiometricWindowsMain implements BiometricMain {
// store error state so we can let the user know on the settings page
this.isError = true;
}
this.storageService.save(ElectronConstants.enableBiometric, supportsBiometric);
this.storageService.save(ConstantsService.biometricText, 'unlockWithWindowsHello');
this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptWindowsHello');
await this.stateService.setEnableBiometric(supportsBiometric);
await this.stateService.setBiometricText('unlockWithWindowsHello');
await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptWindowsHello');
ipcMain.on('biometric', async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric();

View File

@@ -1,14 +0,0 @@
export class ElectronConstants {
static readonly enableMinimizeToTrayKey: string = 'enableMinimizeToTray';
static readonly enableCloseToTrayKey: string = 'enableCloseToTray';
static readonly enableTrayKey: string = 'enableTray';
static readonly enableStartToTrayKey: string = 'enableStartToTrayKey';
static readonly enableAlwaysOnTopKey: string = 'enableAlwaysOnTopKey';
static readonly minimizeOnCopyToClipboardKey: string = 'minimizeOnCopyToClipboardKey';
static readonly enableBiometric: string = 'enabledBiometric';
static readonly enableBrowserIntegration: string = 'enableBrowserIntegration';
static readonly enableBrowserIntegrationFingerprint: string = 'enableBrowserIntegrationFingerprint';
static readonly alwaysShowDock: string = 'alwaysShowDock';
static readonly openAtLogin: string = 'openAtLogin';
static readonly noAutoPromptBiometricsText: string = 'noAutoPromptBiometricsText';
}

View File

@@ -1,16 +1,19 @@
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { KeySuffixOptions, StorageService } from 'jslib-common/abstractions/storage.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from 'jslib-common/services/crypto.service';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
import { StorageLocation } from 'jslib-common/enums/storageLocation';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { CryptoService, Keys } from 'jslib-common/services/crypto.service';
export class ElectronCryptoService extends CryptoService {
constructor(storageService: StorageService, secureStorageService: StorageService,
cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService,
logService: LogService) {
super(storageService, secureStorageService, cryptoFunctionService, platformUtilService, logService);
constructor(cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService,
logService: LogService, stateService: StateService) {
super(cryptoFunctionService, platformUtilService, logService, stateService);
}
async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> {
@@ -18,17 +21,17 @@ export class ElectronCryptoService extends CryptoService {
return super.hasKeyStored(keySuffix);
}
protected async storeKey(key: SymmetricCryptoKey) {
if (await this.shouldStoreKey('auto')) {
await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'auto' });
protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
} else {
this.clearStoredKey('auto');
this.clearStoredKey(KeySuffixOptions.Auto);
}
if (await this.shouldStoreKey('biometric')) {
await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'biometric' });
if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId });
} else {
this.clearStoredKey('biometric');
this.clearStoredKey(KeySuffixOptions.Biometric);
}
}
@@ -43,24 +46,24 @@ export class ElectronCryptoService extends CryptoService {
*/
private async upgradeSecurelyStoredKey() {
// attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway.
const key = await this.secureStorageService.get<string>(Keys.key);
const key = await this.stateService.getCryptoMasterKeyB64();
if (key == null) {
return;
}
try {
if (await this.shouldStoreKey('auto')) {
await this.secureStorageService.save(Keys.key, key, { keySuffix: 'auto' });
if (await this.shouldStoreKey(KeySuffixOptions.Auto)) {
await this.stateService.setCryptoMasterKeyAuto(key);
}
if (await this.shouldStoreKey('biometric')) {
await this.secureStorageService.save(Keys.key, key, { keySuffix: 'biometric' });
if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) {
await this.stateService.setCryptoMasterKeyBiometric(key);
}
} catch (e) {
this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`);
this.logService.error(e);
}
await this.secureStorageService.remove(Keys.key);
await this.stateService.setCryptoMasterKeyB64(null);
}
}

View File

@@ -15,11 +15,7 @@ import { ThemeType } from 'jslib-common/enums/themeType';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ElectronConstants } from '../electronConstants';
import { StateService } from 'jslib-common/abstractions/state.service';
export class ElectronPlatformUtilsService implements PlatformUtilsService {
identityClientId: string;
@@ -27,7 +23,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
private deviceCache: DeviceType = null;
constructor(protected i18nService: I18nService, private messagingService: MessagingService,
private isDesktopApp: boolean, private storageService: StorageService) {
private isDesktopApp: boolean, private stateService: StateService) {
this.identityClientId = isDesktopApp ? 'desktop' : 'connector';
}
@@ -178,8 +174,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
return Promise.resolve(clipboard.readText(type));
}
supportsBiometric(): Promise<boolean> {
return this.storageService.get(ElectronConstants.enableBiometric);
async supportsBiometric(): Promise<boolean> {
return await this.stateService.getEnableBiometric();
}
authenticateBiometric(): Promise<boolean> {
@@ -200,7 +196,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
}
async getEffectiveTheme() {
const theme = await this.storageService.get<ThemeType>(ConstantsService.themeKey);
const theme = await this.stateService.getTheme();
if (theme == null || theme === ThemeType.System) {
return this.getDefaultSystemTheme();
} else {

View File

@@ -1,9 +1,11 @@
import { ipcRenderer } from 'electron';
import { StorageService, StorageServiceOptions } from 'jslib-common/abstractions/storage.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { StorageOptions } from 'jslib-common/models/domain/storageOptions';
export class ElectronRendererSecureStorageService implements StorageService {
async get<T>(key: string, options?: StorageServiceOptions): Promise<T> {
async get<T>(key: string, options?: StorageOptions): Promise<T> {
const val = ipcRenderer.sendSync('keytar', {
action: 'getPassword',
key: key,
@@ -12,7 +14,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve(val != null ? JSON.parse(val) as T : null);
}
async has(key: string, options?: StorageServiceOptions): Promise<boolean> {
async has(key: string, options?: StorageOptions): Promise<boolean> {
const val = ipcRenderer.sendSync('keytar', {
action: 'hasPassword',
key: key,
@@ -21,7 +23,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve(!!val);
}
async save(key: string, obj: any, options?: StorageServiceOptions): Promise<any> {
async save(key: string, obj: any, options?: StorageOptions): Promise<any> {
ipcRenderer.sendSync('keytar', {
action: 'setPassword',
key: key,
@@ -31,7 +33,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve();
}
async remove(key: string, options?: StorageServiceOptions): Promise<any> {
async remove(key: string, options?: StorageOptions): Promise<any> {
ipcRenderer.sendSync('keytar', {
action: 'deletePassword',
key: key,

View File

@@ -2,7 +2,6 @@ import {
app,
BrowserWindow,
Menu,
MenuItem,
MenuItemConstructorOptions,
nativeImage,
Tray,
@@ -10,9 +9,8 @@ import {
import * as path from 'path';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ElectronConstants } from './electronConstants';
import { WindowMain } from './window.main';
export class TrayMain {
@@ -24,7 +22,7 @@ export class TrayMain {
private pressedIcon: Electron.NativeImage;
constructor(private windowMain: WindowMain, private i18nService: I18nService,
private storageService: StorageService) {
private stateService: StateService) {
if (process.platform === 'win32') {
this.icon = path.join(__dirname, '/images/icon.ico');
} else if (process.platform === 'darwin') {
@@ -55,21 +53,21 @@ export class TrayMain {
}
this.contextMenu = Menu.buildFromTemplate(menuItemOptions);
if (await this.storageService.get<boolean>(ElectronConstants.enableTrayKey)) {
if (await this.stateService.getEnableTray()) {
this.showTray();
}
}
setupWindowListeners(win: BrowserWindow) {
win.on('minimize', async (e: Event) => {
if (await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey)) {
if (await this.stateService.getEnableMinimizeToTray()) {
e.preventDefault();
this.hideToTray();
}
});
win.on('close', async (e: Event) => {
if (await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey)) {
if (await this.stateService.getEnableCloseToTray()) {
if (!this.windowMain.isQuitting) {
e.preventDefault();
this.hideToTray();
@@ -78,7 +76,7 @@ export class TrayMain {
});
win.on('show', async (e: Event) => {
const enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
const enableTray = await this.stateService.getEnableTray();
if (!enableTray) {
setTimeout(() => this.removeTray(false), 100);
}
@@ -103,7 +101,7 @@ export class TrayMain {
if (this.windowMain.win != null) {
this.windowMain.win.hide();
}
if (this.isDarwin() && !await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock)) {
if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) {
this.hideDock();
}
}
@@ -167,7 +165,7 @@ export class TrayMain {
}
if (this.windowMain.win.isVisible()) {
this.windowMain.win.hide();
if (this.isDarwin() && !await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock)) {
if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) {
this.hideDock();
}
} else {

View File

@@ -25,8 +25,12 @@ export function isAppImage() {
return process.platform === 'linux' && 'APPIMAGE' in process.env;
}
export function isMac() {
return process.platform === 'darwin';
}
export function isMacAppStore() {
return process.platform === 'darwin' && process.mas && process.mas === true;
return isMac() && process.mas && process.mas === true;
}
export function isWindowsStore() {

View File

@@ -7,9 +7,8 @@ import * as path from 'path';
import * as url from 'url';
import { LogService } from 'jslib-common/abstractions/log.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ElectronConstants } from './electronConstants';
import {
cleanUserAgent,
isDev,
@@ -17,11 +16,8 @@ import {
isSnapStore,
} from './utils';
const mainWindowSizeKey = 'mainWindowSize';
const WindowEventHandlingDelay = 100;
const Keys = {
mainWindowSize: 'mainWindowSize',
};
export class WindowMain {
win: BrowserWindow;
isQuitting: boolean = false;
@@ -30,7 +26,7 @@ export class WindowMain {
private windowStates: { [key: string]: any; } = {};
private enableAlwaysOnTop: boolean = false;
constructor(private storageService: StorageService, private logService: LogService,
constructor(private stateService: StateService, private logService: LogService,
private hideTitleBar = false, private defaultWidth = 950, private defaultHeight = 600,
private argvCallback: (argv: string[]) => void = null,
private createWindowCallback: (win: BrowserWindow) => void) { }
@@ -107,18 +103,18 @@ export class WindowMain {
}
async createWindow(): Promise<void> {
this.windowStates[Keys.mainWindowSize] = await this.getWindowState(Keys.mainWindowSize, this.defaultWidth,
this.windowStates[mainWindowSizeKey] = await this.getWindowState(mainWindowSizeKey, this.defaultWidth,
this.defaultHeight);
this.enableAlwaysOnTop = await this.storageService.get<boolean>(ElectronConstants.enableAlwaysOnTopKey);
this.enableAlwaysOnTop = await this.stateService.getEnableAlwaysOnTop();
// Create the browser window.
this.win = new BrowserWindow({
width: this.windowStates[Keys.mainWindowSize].width,
height: this.windowStates[Keys.mainWindowSize].height,
width: this.windowStates[mainWindowSizeKey].width,
height: this.windowStates[mainWindowSizeKey].height,
minWidth: 680,
minHeight: 500,
x: this.windowStates[Keys.mainWindowSize].x,
y: this.windowStates[Keys.mainWindowSize].y,
x: this.windowStates[mainWindowSizeKey].x,
y: this.windowStates[mainWindowSizeKey].y,
title: app.name,
icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined,
titleBarStyle: this.hideTitleBar && process.platform === 'darwin' ? 'hiddenInset' : undefined,
@@ -132,7 +128,7 @@ export class WindowMain {
},
});
if (this.windowStates[Keys.mainWindowSize].isMaximized) {
if (this.windowStates[mainWindowSizeKey].isMaximized) {
this.win.maximize();
}
@@ -158,7 +154,7 @@ export class WindowMain {
// Emitted when the window is closed.
this.win.on('closed', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win);
await this.updateWindowState(mainWindowSizeKey, this.win);
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
@@ -167,23 +163,23 @@ export class WindowMain {
});
this.win.on('close', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win);
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on('maximize', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win);
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on('unmaximize', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win);
await this.updateWindowState(mainWindowSizeKey, this.win);
});
this.win.on('resize', () => {
this.windowStateChangeHandler(Keys.mainWindowSize, this.win);
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
});
this.win.on('move', () => {
this.windowStateChangeHandler(Keys.mainWindowSize, this.win);
this.windowStateChangeHandler(mainWindowSizeKey, this.win);
});
this.win.on('focus', () => {
this.win.webContents.send('messagingService', {
@@ -200,7 +196,7 @@ export class WindowMain {
async toggleAlwaysOnTop() {
this.enableAlwaysOnTop = !this.win.isAlwaysOnTop();
this.win.setAlwaysOnTop(this.enableAlwaysOnTop);
await this.storageService.save(ElectronConstants.enableAlwaysOnTopKey, this.enableAlwaysOnTop);
await this.stateService.setEnableAlwaysOnTop(this.enableAlwaysOnTop);
}
private windowStateChangeHandler(configKey: string, win: BrowserWindow) {
@@ -219,7 +215,7 @@ export class WindowMain {
const bounds = win.getBounds();
if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = await this.storageService.get<any>(configKey);
this.windowStates[configKey] = (await this.stateService.getWindow()).get(configKey);
if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = {};
}
@@ -235,14 +231,19 @@ export class WindowMain {
this.windowStates[configKey].height = bounds.height;
}
await this.storageService.save(configKey, this.windowStates[configKey]);
const cachedWindow = await this.stateService.getWindow() ?? new Map<string, any>();
cachedWindow.set(configKey, this.windowStates[configKey]);
await this.stateService.setWindow(cachedWindow);
} catch (e) {
this.logService.error(e);
}
}
private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) {
let state = await this.storageService.get<any>(configKey);
const windowState = await this.stateService.getWindow() ?? new Map<string, any>();
let state = windowState.has(configKey) ?
windowState.get(configKey) :
null;
const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized);
let displayBounds: Electron.Rectangle = null;