mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-22 13:17:30 +00:00
Desktop Autotype toggle on vault lock/unlock (#17062)
* Desktop Autotype toggle on vault lock/unlock * lint * add back disable on will-quit signal * improve IPC message args * claude: takeUntilDestroyed * claude: try/catch * claude: multiple listeners * claude: === * claude: concatMap * claude: IPC Handler Registration in Constructor * claude: helper function * claude: Type Safety for IPC Messages * fix claude suggestion? * bit by commit hook file write again * remove the type qualifier * add log svc dep * move the initialized ipcs back to constructor * frageele? * try disable premium check * replace takeUntilDestroy with takeUntil(destroy) * add import * create separate observable for premium check * clean up and remove distinctUntilChanged * re-add distinctUntilChanged * ipc handlers in init * check double initialization * Revert "check double initialization" This reverts commit8488b8a613. * Revert "ipc handlers in init" This reverts commita23999edcf. * ipc out of constructor * claude suggestion does not compile, awesome * add a dispose method for cleanup of ipc handlers * claude: remove of(false) on observable initializing * claude: remove the init/init'd * claude: remove takeUntil on isPremiumAccount * Revert "claude: remove takeUntil on isPremiumAccount" This reverts commit9fc32c5fcf. * align models file name with interface name * rename ipc listeners function * improve debug log message * improve debug log message * remove reference to not present observable in unit test * add function comment * make `autotypeKeyboardShortcut` private
This commit is contained in:
@@ -187,7 +187,6 @@ describe("SettingsComponent", () => {
|
||||
i18nService.userSetLocale$ = of("en");
|
||||
pinServiceAbstraction.isPinSet.mockResolvedValue(false);
|
||||
policyService.policiesByType$.mockReturnValue(of([null]));
|
||||
desktopAutotypeService.resolvedAutotypeEnabled$ = of(false);
|
||||
desktopAutotypeService.autotypeEnabledUserSetting$ = of(false);
|
||||
desktopAutotypeService.autotypeKeyboardShortcut$ = of(["Control", "Shift", "B"]);
|
||||
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
|
||||
@@ -489,6 +489,7 @@ const safeProviders: SafeProvider[] = [
|
||||
PlatformUtilsServiceAbstraction,
|
||||
BillingAccountProfileStateService,
|
||||
DesktopAutotypeDefaultSettingPolicy,
|
||||
LogService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -5,51 +5,46 @@ import { LogService } from "@bitwarden/logging";
|
||||
|
||||
import { WindowMain } from "../../main/window.main";
|
||||
import { stringIsNotUndefinedNullAndEmpty } from "../../utils";
|
||||
import { AutotypeConfig } from "../models/autotype-config";
|
||||
import { AutotypeMatchError } from "../models/autotype-errors";
|
||||
import { AutotypeVaultData } from "../models/autotype-vault-data";
|
||||
import { AUTOTYPE_IPC_CHANNELS } from "../models/ipc-channels";
|
||||
import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut";
|
||||
|
||||
export class MainDesktopAutotypeService {
|
||||
autotypeKeyboardShortcut: AutotypeKeyboardShortcut;
|
||||
private autotypeKeyboardShortcut: AutotypeKeyboardShortcut;
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private windowMain: WindowMain,
|
||||
) {
|
||||
this.autotypeKeyboardShortcut = new AutotypeKeyboardShortcut();
|
||||
|
||||
this.registerIpcListeners();
|
||||
}
|
||||
|
||||
init() {
|
||||
ipcMain.on("autofill.configureAutotype", (event, data) => {
|
||||
if (data.enabled) {
|
||||
const newKeyboardShortcut = new AutotypeKeyboardShortcut();
|
||||
const newKeyboardShortcutIsValid = newKeyboardShortcut.set(data.keyboardShortcut);
|
||||
|
||||
if (newKeyboardShortcutIsValid) {
|
||||
this.disableAutotype();
|
||||
this.autotypeKeyboardShortcut = newKeyboardShortcut;
|
||||
this.enableAutotype();
|
||||
} else {
|
||||
this.logService.error(
|
||||
"Attempting to configure autotype but the shortcut given is invalid.",
|
||||
);
|
||||
}
|
||||
registerIpcListeners() {
|
||||
ipcMain.on(AUTOTYPE_IPC_CHANNELS.TOGGLE, (_event, enable: boolean) => {
|
||||
if (enable) {
|
||||
this.enableAutotype();
|
||||
} else {
|
||||
this.disableAutotype();
|
||||
|
||||
// Deregister the incoming keyboard shortcut if needed
|
||||
const setCorrectly = this.autotypeKeyboardShortcut.set(data.keyboardShortcut);
|
||||
if (
|
||||
setCorrectly &&
|
||||
globalShortcut.isRegistered(this.autotypeKeyboardShortcut.getElectronFormat())
|
||||
) {
|
||||
globalShortcut.unregister(this.autotypeKeyboardShortcut.getElectronFormat());
|
||||
this.logService.info("Autotype disabled.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => {
|
||||
ipcMain.on(AUTOTYPE_IPC_CHANNELS.CONFIGURE, (_event, config: AutotypeConfig) => {
|
||||
const newKeyboardShortcut = new AutotypeKeyboardShortcut();
|
||||
const newKeyboardShortcutIsValid = newKeyboardShortcut.set(config.keyboardShortcut);
|
||||
|
||||
if (!newKeyboardShortcutIsValid) {
|
||||
this.logService.error("Configure autotype failed: the keyboard shortcut is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setKeyboardShortcut(newKeyboardShortcut);
|
||||
});
|
||||
|
||||
ipcMain.on(AUTOTYPE_IPC_CHANNELS.EXECUTE, (_event, vaultData: AutotypeVaultData) => {
|
||||
if (
|
||||
stringIsNotUndefinedNullAndEmpty(vaultData.username) &&
|
||||
stringIsNotUndefinedNullAndEmpty(vaultData.password)
|
||||
@@ -67,30 +62,74 @@ export class MainDesktopAutotypeService {
|
||||
});
|
||||
}
|
||||
|
||||
// Deregister the keyboard shortcut if registered.
|
||||
disableAutotype() {
|
||||
// Deregister the current keyboard shortcut if needed
|
||||
const formattedKeyboardShortcut = this.autotypeKeyboardShortcut.getElectronFormat();
|
||||
|
||||
if (globalShortcut.isRegistered(formattedKeyboardShortcut)) {
|
||||
globalShortcut.unregister(formattedKeyboardShortcut);
|
||||
this.logService.info("Autotype disabled.");
|
||||
this.logService.debug("Autotype disabled.");
|
||||
} else {
|
||||
this.logService.debug("Autotype is not registered, implicitly disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.TOGGLE);
|
||||
ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
|
||||
ipcMain.removeAllListeners(AUTOTYPE_IPC_CHANNELS.EXECUTE);
|
||||
|
||||
// Also unregister the global shortcut
|
||||
this.disableAutotype();
|
||||
}
|
||||
|
||||
// Register the current keyboard shortcut if not already registered.
|
||||
private enableAutotype() {
|
||||
const formattedKeyboardShortcut = this.autotypeKeyboardShortcut.getElectronFormat();
|
||||
if (globalShortcut.isRegistered(formattedKeyboardShortcut)) {
|
||||
this.logService.debug(
|
||||
"Autotype is already enabled with this keyboard shortcut: " + formattedKeyboardShortcut,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = globalShortcut.register(
|
||||
this.autotypeKeyboardShortcut.getElectronFormat(),
|
||||
() => {
|
||||
const windowTitle = autotype.getForegroundWindowTitle();
|
||||
|
||||
this.windowMain.win.webContents.send("autofill.listenAutotypeRequest", {
|
||||
this.windowMain.win.webContents.send(AUTOTYPE_IPC_CHANNELS.LISTEN, {
|
||||
windowTitle,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
result
|
||||
? this.logService.info("Autotype enabled.")
|
||||
: this.logService.info("Enabling autotype failed.");
|
||||
? this.logService.debug("Autotype enabled.")
|
||||
: this.logService.error("Failed to enable Autotype.");
|
||||
}
|
||||
|
||||
// Set the keyboard shortcut if it differs from the present one. If
|
||||
// the keyboard shortcut is set, de-register the old shortcut first.
|
||||
private setKeyboardShortcut(keyboardShortcut: AutotypeKeyboardShortcut) {
|
||||
if (
|
||||
keyboardShortcut.getElectronFormat() !== this.autotypeKeyboardShortcut.getElectronFormat()
|
||||
) {
|
||||
const registered = globalShortcut.isRegistered(
|
||||
this.autotypeKeyboardShortcut.getElectronFormat(),
|
||||
);
|
||||
if (registered) {
|
||||
this.disableAutotype();
|
||||
}
|
||||
this.autotypeKeyboardShortcut = keyboardShortcut;
|
||||
if (registered) {
|
||||
this.enableAutotype();
|
||||
}
|
||||
} else {
|
||||
this.logService.debug(
|
||||
"setKeyboardShortcut() called but shortcut is not different from current.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) {
|
||||
|
||||
3
apps/desktop/src/autofill/models/autotype-config.ts
Normal file
3
apps/desktop/src/autofill/models/autotype-config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface AutotypeConfig {
|
||||
keyboardShortcut: string[];
|
||||
}
|
||||
9
apps/desktop/src/autofill/models/ipc-channels.ts
Normal file
9
apps/desktop/src/autofill/models/ipc-channels.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const AUTOTYPE_IPC_CHANNELS = {
|
||||
INIT: "autofill.initAutotype",
|
||||
INITIALIZED: "autofill.autotypeIsInitialized",
|
||||
TOGGLE: "autofill.toggleAutotype",
|
||||
CONFIGURE: "autofill.configureAutotype",
|
||||
LISTEN: "autofill.listenAutotypeRequest",
|
||||
EXECUTION_ERROR: "autofill.autotypeExecutionError",
|
||||
EXECUTE: "autofill.executeAutotype",
|
||||
} as const;
|
||||
@@ -5,8 +5,10 @@ import type { autofill } from "@bitwarden/desktop-napi";
|
||||
import { Command } from "../platform/main/autofill/command";
|
||||
import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main";
|
||||
|
||||
import { AutotypeConfig } from "./models/autotype-config";
|
||||
import { AutotypeMatchError } from "./models/autotype-errors";
|
||||
import { AutotypeVaultData } from "./models/autotype-vault-data";
|
||||
import { AUTOTYPE_IPC_CHANNELS } from "./models/ipc-channels";
|
||||
|
||||
export default {
|
||||
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
|
||||
@@ -132,7 +134,6 @@ export default {
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
listenNativeStatus: (
|
||||
fn: (clientId: number, sequenceNumber: number, status: { key: string; value: string }) => void,
|
||||
) => {
|
||||
@@ -151,8 +152,11 @@ export default {
|
||||
},
|
||||
);
|
||||
},
|
||||
configureAutotype: (enabled: boolean, keyboardShortcut: string[]) => {
|
||||
ipcRenderer.send("autofill.configureAutotype", { enabled, keyboardShortcut });
|
||||
configureAutotype: (config: AutotypeConfig) => {
|
||||
ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.CONFIGURE, config);
|
||||
},
|
||||
toggleAutotype: (enable: boolean) => {
|
||||
ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.TOGGLE, enable);
|
||||
},
|
||||
listenAutotypeRequest: (
|
||||
fn: (
|
||||
@@ -161,7 +165,7 @@ export default {
|
||||
) => void,
|
||||
) => {
|
||||
ipcRenderer.on(
|
||||
"autofill.listenAutotypeRequest",
|
||||
AUTOTYPE_IPC_CHANNELS.LISTEN,
|
||||
(
|
||||
_event,
|
||||
data: {
|
||||
@@ -176,11 +180,12 @@ export default {
|
||||
windowTitle,
|
||||
errorMessage: error.message,
|
||||
};
|
||||
ipcRenderer.send("autofill.completeAutotypeError", matchError);
|
||||
ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.EXECUTION_ERROR, matchError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vaultData !== null) {
|
||||
ipcRenderer.send("autofill.completeAutotypeRequest", vaultData);
|
||||
ipcRenderer.send(AUTOTYPE_IPC_CHANNELS.EXECUTE, vaultData);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import { combineLatest, filter, firstValueFrom, map, Observable, of, switchMap } from "rxjs";
|
||||
import { Injectable, OnDestroy } from "@angular/core";
|
||||
import {
|
||||
combineLatest,
|
||||
concatMap,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
Subject,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
@@ -15,8 +28,10 @@ import {
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AutotypeConfig } from "../models/autotype-config";
|
||||
import { AutotypeVaultData } from "../models/autotype-vault-data";
|
||||
|
||||
import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service";
|
||||
@@ -44,16 +59,26 @@ export const AUTOTYPE_KEYBOARD_SHORTCUT = new KeyDefinition<string[]>(
|
||||
{ deserializer: (b) => b },
|
||||
);
|
||||
|
||||
export class DesktopAutotypeService {
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class DesktopAutotypeService implements OnDestroy {
|
||||
private readonly autotypeEnabledState = this.globalStateProvider.get(AUTOTYPE_ENABLED);
|
||||
private readonly autotypeKeyboardShortcut = this.globalStateProvider.get(
|
||||
AUTOTYPE_KEYBOARD_SHORTCUT,
|
||||
);
|
||||
|
||||
autotypeEnabledUserSetting$: Observable<boolean> = of(false);
|
||||
resolvedAutotypeEnabled$: Observable<boolean> = of(false);
|
||||
// if the user's account is Premium
|
||||
private readonly isPremiumAccount$: Observable<boolean>;
|
||||
|
||||
// The enabled/disabled state from the user settings menu
|
||||
autotypeEnabledUserSetting$: Observable<boolean>;
|
||||
|
||||
// The keyboard shortcut from the user settings menu
|
||||
autotypeKeyboardShortcut$: Observable<string[]> = of(defaultWindowsAutotypeKeyboardShortcut);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private authService: AuthService,
|
||||
@@ -63,76 +88,110 @@ export class DesktopAutotypeService {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private desktopAutotypePolicy: DesktopAutotypeDefaultSettingPolicy,
|
||||
private logService: LogService,
|
||||
) {
|
||||
this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$.pipe(
|
||||
map((enabled) => enabled ?? false),
|
||||
distinctUntilChanged(), // Only emit when the boolean result changes
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
this.isPremiumAccount$ = this.accountService.activeAccount$.pipe(
|
||||
filter((account): account is Account => !!account),
|
||||
switchMap((account) =>
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
||||
),
|
||||
distinctUntilChanged(), // Only emit when the boolean result changes
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$.pipe(
|
||||
map((shortcut) => shortcut ?? defaultWindowsAutotypeKeyboardShortcut),
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Currently Autotype is only supported for Windows
|
||||
if (this.platformUtilsService.getDevice() !== DeviceType.WindowsDesktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => {
|
||||
const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle);
|
||||
const firstCipher = possibleCiphers?.at(0);
|
||||
const [error, vaultData] = getAutotypeVaultData(firstCipher);
|
||||
callback(error, vaultData);
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$;
|
||||
this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$.pipe(
|
||||
map((shortcut) => shortcut ?? defaultWindowsAutotypeKeyboardShortcut),
|
||||
);
|
||||
|
||||
// Currently Autotype is only supported for Windows
|
||||
if (this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop) {
|
||||
// If `autotypeDefaultPolicy` is `true` for a user's organization, and the
|
||||
// user has never changed their local autotype setting (`autotypeEnabledState`),
|
||||
// we set their local setting to `true` (once the local user setting is changed
|
||||
// by this policy or the user themselves, the default policy should
|
||||
// never change the user setting again).
|
||||
combineLatest([
|
||||
this.autotypeEnabledState.state$,
|
||||
this.desktopAutotypePolicy.autotypeDefaultSetting$,
|
||||
])
|
||||
.pipe(
|
||||
map(async ([autotypeEnabledState, autotypeDefaultPolicy]) => {
|
||||
// If `autotypeDefaultPolicy` is `true` for a user's organization, and the
|
||||
// user has never changed their local autotype setting (`autotypeEnabledState`),
|
||||
// we set their local setting to `true` (once the local user setting is changed
|
||||
// by this policy or the user themselves, the default policy should
|
||||
// never change the user setting again).
|
||||
combineLatest([
|
||||
this.autotypeEnabledState.state$,
|
||||
this.desktopAutotypePolicy.autotypeDefaultSetting$,
|
||||
])
|
||||
.pipe(
|
||||
concatMap(async ([autotypeEnabledState, autotypeDefaultPolicy]) => {
|
||||
try {
|
||||
if (autotypeDefaultPolicy === true && autotypeEnabledState === null) {
|
||||
await this.setAutotypeEnabledState(true);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
} catch {
|
||||
this.logService.error("Failed to set Autotype enabled state.");
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// autotypeEnabledUserSetting$ publicly represents the value the
|
||||
// user has set for autotyeEnabled in their local settings.
|
||||
this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$;
|
||||
// listen for changes in keyboard shortcut settings
|
||||
this.autotypeKeyboardShortcut$
|
||||
.pipe(
|
||||
concatMap(async (keyboardShortcut) => {
|
||||
const config: AutotypeConfig = {
|
||||
keyboardShortcut,
|
||||
};
|
||||
ipc.autofill.configureAutotype(config);
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// resolvedAutotypeEnabled$ represents the final determination if the Autotype
|
||||
// feature should be on or off.
|
||||
this.resolvedAutotypeEnabled$ = combineLatest([
|
||||
this.autotypeEnabledState.state$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype),
|
||||
this.accountService.activeAccount$.pipe(
|
||||
map((activeAccount) => activeAccount?.id),
|
||||
switchMap((userId) => this.authService.authStatusFor$(userId)),
|
||||
),
|
||||
this.accountService.activeAccount$.pipe(
|
||||
filter((account): account is Account => !!account),
|
||||
switchMap((account) =>
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
||||
),
|
||||
),
|
||||
]).pipe(
|
||||
map(
|
||||
([autotypeEnabled, windowsDesktopAutotypeFeatureFlag, authStatus, hasPremium]) =>
|
||||
autotypeEnabled &&
|
||||
windowsDesktopAutotypeFeatureFlag &&
|
||||
authStatus == AuthenticationStatus.Unlocked &&
|
||||
hasPremium,
|
||||
),
|
||||
);
|
||||
this.autotypeFeatureEnabled$
|
||||
.pipe(
|
||||
concatMap(async (enabled) => {
|
||||
ipc.autofill.toggleAutotype(enabled);
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
combineLatest([this.resolvedAutotypeEnabled$, this.autotypeKeyboardShortcut$]).subscribe(
|
||||
([resolvedAutotypeEnabled, autotypeKeyboardShortcut]) => {
|
||||
ipc.autofill.configureAutotype(resolvedAutotypeEnabled, autotypeKeyboardShortcut);
|
||||
},
|
||||
);
|
||||
}
|
||||
// Returns an observable that represents whether autotype is enabled for the current user.
|
||||
private get autotypeFeatureEnabled$(): Observable<boolean> {
|
||||
return combineLatest([
|
||||
// if the user has enabled the setting
|
||||
this.autotypeEnabledUserSetting$,
|
||||
// if the feature flag is set
|
||||
this.configService.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype),
|
||||
// if there is an active account with an unlocked vault
|
||||
this.authService.activeAccountStatus$,
|
||||
// if the active user's account is Premium
|
||||
this.isPremiumAccount$,
|
||||
]).pipe(
|
||||
map(
|
||||
([settingsEnabled, ffEnabled, authStatus, isPremiumAcct]) =>
|
||||
settingsEnabled &&
|
||||
ffEnabled &&
|
||||
authStatus === AuthenticationStatus.Unlocked &&
|
||||
isPremiumAcct,
|
||||
),
|
||||
distinctUntilChanged(), // Only emit when the boolean result changes
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
}
|
||||
|
||||
async setAutotypeEnabledState(enabled: boolean): Promise<void> {
|
||||
@@ -176,6 +235,11 @@ export class DesktopAutotypeService {
|
||||
|
||||
return possibleCiphers;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -311,17 +311,8 @@ export class Main {
|
||||
this.windowMain,
|
||||
);
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(() => {
|
||||
this.mainDesktopAutotypeService.init();
|
||||
})
|
||||
.catch((reason) => {
|
||||
this.logService.error("Error initializing Autotype.", reason);
|
||||
});
|
||||
|
||||
app.on("will-quit", () => {
|
||||
this.mainDesktopAutotypeService.disableAutotype();
|
||||
this.mainDesktopAutotypeService.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user