mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-18 02:41:15 +02:00
[PM-6426] Implementing clear clipboard call on generatePasswordToClipboard with the TaskSchedulerService
This commit is contained in:
parent
d989dc3804
commit
2f517336db
@ -36,6 +36,7 @@ import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
|||||||
import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory";
|
import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory";
|
||||||
import { eventCollectionServiceFactory } from "../../background/service-factories/event-collection-service.factory";
|
import { eventCollectionServiceFactory } from "../../background/service-factories/event-collection-service.factory";
|
||||||
import { Account } from "../../models/account";
|
import { Account } from "../../models/account";
|
||||||
|
import { browserTaskSchedulerServiceFactory } from "../../platform/background/service-factories/browser-task-scheduler-service.factory";
|
||||||
import { CachedServices } from "../../platform/background/service-factories/factory-options";
|
import { CachedServices } from "../../platform/background/service-factories/factory-options";
|
||||||
import { stateServiceFactory } from "../../platform/background/service-factories/state-service.factory";
|
import { stateServiceFactory } from "../../platform/background/service-factories/state-service.factory";
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
@ -116,6 +117,7 @@ export class ContextMenuClickedHandler {
|
|||||||
const generatePasswordToClipboardCommand = new GeneratePasswordToClipboardCommand(
|
const generatePasswordToClipboardCommand = new GeneratePasswordToClipboardCommand(
|
||||||
await passwordGenerationServiceFactory(cachedServices, serviceOptions),
|
await passwordGenerationServiceFactory(cachedServices, serviceOptions),
|
||||||
await autofillSettingsServiceFactory(cachedServices, serviceOptions),
|
await autofillSettingsServiceFactory(cachedServices, serviceOptions),
|
||||||
|
await browserTaskSchedulerServiceFactory(cachedServices, serviceOptions),
|
||||||
);
|
);
|
||||||
|
|
||||||
const autofillCommand = new AutofillTabCommand(
|
const autofillCommand = new AutofillTabCommand(
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
|
||||||
export const clearClipboardAlarmName = "clearClipboard";
|
|
||||||
|
|
||||||
export class ClearClipboard {
|
export class ClearClipboard {
|
||||||
/**
|
/**
|
||||||
We currently rely on an active tab with an injected content script (`../content/misc-utils.ts`) to clear the clipboard via `window.navigator.clipboard.writeText(text)`
|
We currently rely on an active tab with an injected content script (`../content/misc-utils.ts`) to clear the clipboard via `window.navigator.clipboard.writeText(text)`
|
||||||
|
@ -1,30 +1,24 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
|
import { ScheduledTaskNames } from "@bitwarden/common/platform/enums/scheduled-task-name.enum";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
|
|
||||||
import { setAlarmTime } from "../../platform/alarms/alarm-state";
|
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
import { BrowserTaskSchedulerService } from "../../platform/services/browser-task-scheduler.service";
|
||||||
|
|
||||||
import { clearClipboardAlarmName } from "./clear-clipboard";
|
|
||||||
import { GeneratePasswordToClipboardCommand } from "./generate-password-to-clipboard-command";
|
import { GeneratePasswordToClipboardCommand } from "./generate-password-to-clipboard-command";
|
||||||
|
|
||||||
jest.mock("../../platform/alarms/alarm-state", () => {
|
|
||||||
return {
|
|
||||||
setAlarmTime: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const setAlarmTimeMock = setAlarmTime as jest.Mock;
|
|
||||||
|
|
||||||
describe("GeneratePasswordToClipboardCommand", () => {
|
describe("GeneratePasswordToClipboardCommand", () => {
|
||||||
let passwordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
let passwordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
||||||
let autofillSettingsService: MockProxy<AutofillSettingsService>;
|
let autofillSettingsService: MockProxy<AutofillSettingsService>;
|
||||||
|
let browserTaskSchedulerService: MockProxy<BrowserTaskSchedulerService>;
|
||||||
|
|
||||||
let sut: GeneratePasswordToClipboardCommand;
|
let sut: GeneratePasswordToClipboardCommand;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
passwordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
passwordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
||||||
|
browserTaskSchedulerService = mock<BrowserTaskSchedulerService>();
|
||||||
|
|
||||||
passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]);
|
passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]);
|
||||||
|
|
||||||
@ -35,6 +29,7 @@ describe("GeneratePasswordToClipboardCommand", () => {
|
|||||||
sut = new GeneratePasswordToClipboardCommand(
|
sut = new GeneratePasswordToClipboardCommand(
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
autofillSettingsService,
|
autofillSettingsService,
|
||||||
|
browserTaskSchedulerService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,9 +50,12 @@ describe("GeneratePasswordToClipboardCommand", () => {
|
|||||||
text: "PASSWORD",
|
text: "PASSWORD",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(setAlarmTimeMock).toHaveBeenCalledTimes(1);
|
expect(browserTaskSchedulerService.setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
expect(browserTaskSchedulerService.setTimeout).toHaveBeenCalledWith(
|
||||||
expect(setAlarmTimeMock).toHaveBeenCalledWith(clearClipboardAlarmName, expect.any(Number));
|
expect.any(Function),
|
||||||
|
expect.any(Number),
|
||||||
|
ScheduledTaskNames.clearClipboardTimeout,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not have clear clipboard value", async () => {
|
it("does not have clear clipboard value", async () => {
|
||||||
@ -71,8 +69,7 @@ describe("GeneratePasswordToClipboardCommand", () => {
|
|||||||
command: "copyText",
|
command: "copyText",
|
||||||
text: "PASSWORD",
|
text: "PASSWORD",
|
||||||
});
|
});
|
||||||
|
expect(browserTaskSchedulerService.setTimeout).not.toHaveBeenCalled();
|
||||||
expect(setAlarmTimeMock).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
|
import { ScheduledTaskNames } from "@bitwarden/common/platform/enums/scheduled-task-name.enum";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
|
|
||||||
import { setAlarmTime } from "../../platform/alarms/alarm-state";
|
import { BrowserTaskSchedulerService } from "../../platform/services/abstractions/browser-task-scheduler.service";
|
||||||
|
|
||||||
import { clearClipboardAlarmName } from "./clear-clipboard";
|
import { ClearClipboard } from "./clear-clipboard";
|
||||||
import { copyToClipboard } from "./copy-to-clipboard-command";
|
import { copyToClipboard } from "./copy-to-clipboard-command";
|
||||||
|
|
||||||
export class GeneratePasswordToClipboardCommand {
|
export class GeneratePasswordToClipboardCommand {
|
||||||
|
private clearClipboardTimeout: number | NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
|
private taskSchedulerService: BrowserTaskSchedulerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getClearClipboard() {
|
async getClearClipboard() {
|
||||||
@ -22,14 +26,22 @@ export class GeneratePasswordToClipboardCommand {
|
|||||||
const [options] = await this.passwordGenerationService.getOptions();
|
const [options] = await this.passwordGenerationService.getOptions();
|
||||||
const password = await this.passwordGenerationService.generatePassword(options);
|
const password = await this.passwordGenerationService.generatePassword(options);
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await copyToClipboard(tab, password);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
copyToClipboard(tab, password);
|
|
||||||
|
|
||||||
const clearClipboard = await this.getClearClipboard();
|
const clearClipboardDelayInSeconds = await this.getClearClipboard();
|
||||||
|
if (!clearClipboardDelayInSeconds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (clearClipboard != null) {
|
const timeoutInMs = clearClipboardDelayInSeconds * 1000;
|
||||||
await setAlarmTime(clearClipboardAlarmName, clearClipboard * 1000);
|
await this.taskSchedulerService.clearScheduledTask({
|
||||||
}
|
taskName: ScheduledTaskNames.clearClipboardTimeout,
|
||||||
|
timeoutId: this.clearClipboardTimeout,
|
||||||
|
});
|
||||||
|
await this.taskSchedulerService.setTimeout(
|
||||||
|
() => ClearClipboard.run(),
|
||||||
|
timeoutInMs,
|
||||||
|
ScheduledTaskNames.clearClipboardTimeout,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import { clearClipboardAlarmName } from "../../autofill/clipboard";
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
|
||||||
|
|
||||||
export const alarmKeys = [clearClipboardAlarmName] as const;
|
|
||||||
export type AlarmKeys = (typeof alarmKeys)[number];
|
|
||||||
|
|
||||||
type AlarmState = { [T in AlarmKeys]: number | undefined };
|
|
||||||
|
|
||||||
const alarmState: AlarmState = {
|
|
||||||
clearClipboard: null,
|
|
||||||
//TODO once implemented vaultTimeout: null;
|
|
||||||
//TODO once implemented checkNotifications: null;
|
|
||||||
//TODO once implemented (if necessary) processReload: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the set alarm time (planned execution) for a give an commandName {@link AlarmState}
|
|
||||||
* @param commandName A command that has been previously registered with {@link AlarmState}
|
|
||||||
* @returns {Promise<number>} null or Unix epoch timestamp when the alarm action is supposed to execute
|
|
||||||
* @example
|
|
||||||
* // getAlarmTime(clearClipboard)
|
|
||||||
*/
|
|
||||||
export async function getAlarmTime(commandName: AlarmKeys): Promise<number> {
|
|
||||||
let alarmTime: number;
|
|
||||||
if (BrowserApi.isManifestVersion(3)) {
|
|
||||||
const fromSessionStore = await chrome.storage.session.get(commandName);
|
|
||||||
alarmTime = fromSessionStore[commandName];
|
|
||||||
} else {
|
|
||||||
alarmTime = alarmState[commandName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return alarmTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an action that should execute after the given time has passed
|
|
||||||
* @param commandName A command that has been previously registered with {@link AlarmState}
|
|
||||||
* @param delay_ms The number of ms from now in which the command should execute from
|
|
||||||
* @example
|
|
||||||
* // setAlarmTime(clearClipboard, 5000) register the clearClipboard action which will execute when at least 5 seconds from now have passed
|
|
||||||
*/
|
|
||||||
export async function setAlarmTime(commandName: AlarmKeys, delay_ms: number): Promise<void> {
|
|
||||||
if (!delay_ms || delay_ms === 0) {
|
|
||||||
await this.clearAlarmTime(commandName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = Date.now() + delay_ms;
|
|
||||||
await setAlarmTimeInternal(commandName, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the time currently set for a given command
|
|
||||||
* @param commandName A command that has been previously registered with {@link AlarmState}
|
|
||||||
*/
|
|
||||||
export async function clearAlarmTime(commandName: AlarmKeys): Promise<void> {
|
|
||||||
await setAlarmTimeInternal(commandName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setAlarmTimeInternal(commandName: AlarmKeys, time: number): Promise<void> {
|
|
||||||
if (BrowserApi.isManifestVersion(3)) {
|
|
||||||
await chrome.storage.session.set({ [commandName]: time });
|
|
||||||
} else {
|
|
||||||
alarmState[commandName] = time;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import { ClearClipboard, clearClipboardAlarmName } from "../../autofill/clipboard";
|
|
||||||
|
|
||||||
import { alarmKeys, clearAlarmTime, getAlarmTime } from "./alarm-state";
|
|
||||||
|
|
||||||
export const onAlarmListener = async (alarm: chrome.alarms.Alarm) => {
|
|
||||||
alarmKeys.forEach(async (key) => {
|
|
||||||
const executionTime = await getAlarmTime(key);
|
|
||||||
if (!executionTime) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDate = Date.now();
|
|
||||||
if (executionTime > currentDate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await clearAlarmTime(key);
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case clearClipboardAlarmName:
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
ClearClipboard.run();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
const NUMBER_OF_ALARMS = 6;
|
|
||||||
|
|
||||||
export function registerAlarms() {
|
|
||||||
alarmsToBeCreated(NUMBER_OF_ALARMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates staggered alarms that periodically (1min) raise OnAlarm events. The staggering is calculated based on the number of alarms passed in.
|
|
||||||
* @param numberOfAlarms Number of named alarms, that shall be registered
|
|
||||||
* @example
|
|
||||||
* // alarmsToBeCreated(2) results in 2 alarms separated by 30 seconds
|
|
||||||
* @example
|
|
||||||
* // alarmsToBeCreated(4) results in 4 alarms separated by 15 seconds
|
|
||||||
* @example
|
|
||||||
* // alarmsToBeCreated(6) results in 6 alarms separated by 10 seconds
|
|
||||||
* @example
|
|
||||||
* // alarmsToBeCreated(60) results in 60 alarms separated by 1 second
|
|
||||||
*/
|
|
||||||
function alarmsToBeCreated(numberOfAlarms: number): void {
|
|
||||||
const oneMinuteInMs = 60 * 1000;
|
|
||||||
const offset = oneMinuteInMs / numberOfAlarms;
|
|
||||||
|
|
||||||
let calculatedWhen: number = Date.now() + offset;
|
|
||||||
|
|
||||||
for (let index = 0; index < numberOfAlarms; index++) {
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
chrome.alarms.create(`bw_alarm${index}`, { periodInMinutes: 1, when: calculatedWhen });
|
|
||||||
calculatedWhen += offset;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
import MainBackground from "../background/main.background";
|
import MainBackground from "../background/main.background";
|
||||||
|
|
||||||
import { onAlarmListener } from "./alarms/on-alarm-listener";
|
|
||||||
import { registerAlarms } from "./alarms/register-alarms";
|
|
||||||
import { BrowserApi } from "./browser/browser-api";
|
import { BrowserApi } from "./browser/browser-api";
|
||||||
import {
|
import {
|
||||||
contextMenusClickedListener,
|
contextMenusClickedListener,
|
||||||
@ -17,8 +15,6 @@ import {
|
|||||||
if (BrowserApi.isManifestVersion(3)) {
|
if (BrowserApi.isManifestVersion(3)) {
|
||||||
chrome.commands.onCommand.addListener(onCommandListener);
|
chrome.commands.onCommand.addListener(onCommandListener);
|
||||||
chrome.runtime.onInstalled.addListener(onInstallListener);
|
chrome.runtime.onInstalled.addListener(onInstallListener);
|
||||||
chrome.alarms.onAlarm.addListener(onAlarmListener);
|
|
||||||
registerAlarms();
|
|
||||||
chrome.windows.onFocusChanged.addListener(windowsOnFocusChangedListener);
|
chrome.windows.onFocusChanged.addListener(windowsOnFocusChangedListener);
|
||||||
chrome.tabs.onActivated.addListener(tabsOnActivatedListener);
|
chrome.tabs.onActivated.addListener(tabsOnActivatedListener);
|
||||||
chrome.tabs.onReplaced.addListener(tabsOnReplacedListener);
|
chrome.tabs.onReplaced.addListener(tabsOnReplacedListener);
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { BrowserTaskSchedulerService } from "../../services/browser-task-scheduler.service";
|
||||||
|
|
||||||
|
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
||||||
|
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
||||||
|
import { stateProviderFactory, StateProviderInitOptions } from "./state-provider.factory";
|
||||||
|
|
||||||
|
type BrowserTaskSchedulerServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
|
export type BrowserTaskSchedulerServiceInitOptions = BrowserTaskSchedulerServiceFactoryOptions &
|
||||||
|
LogServiceInitOptions &
|
||||||
|
StateProviderInitOptions;
|
||||||
|
|
||||||
|
export function browserTaskSchedulerServiceFactory(
|
||||||
|
cache: { browserTaskSchedulerService?: BrowserTaskSchedulerService } & CachedServices,
|
||||||
|
opts: BrowserTaskSchedulerServiceInitOptions,
|
||||||
|
): Promise<BrowserTaskSchedulerService> {
|
||||||
|
return factory(
|
||||||
|
cache,
|
||||||
|
"browserTaskSchedulerService",
|
||||||
|
opts,
|
||||||
|
async () =>
|
||||||
|
new BrowserTaskSchedulerService(
|
||||||
|
await logServiceFactory(cache, opts),
|
||||||
|
await stateProviderFactory(cache, opts),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -12,6 +12,7 @@ import {
|
|||||||
passwordGenerationServiceFactory,
|
passwordGenerationServiceFactory,
|
||||||
PasswordGenerationServiceInitOptions,
|
PasswordGenerationServiceInitOptions,
|
||||||
} from "../../tools/background/service_factories/password-generation-service.factory";
|
} from "../../tools/background/service_factories/password-generation-service.factory";
|
||||||
|
import { browserTaskSchedulerServiceFactory } from "../background/service-factories/browser-task-scheduler-service.factory";
|
||||||
import { CachedServices } from "../background/service-factories/factory-options";
|
import { CachedServices } from "../background/service-factories/factory-options";
|
||||||
import { logServiceFactory } from "../background/service-factories/log-service.factory";
|
import { logServiceFactory } from "../background/service-factories/log-service.factory";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
@ -102,6 +103,7 @@ const doGeneratePasswordToClipboard = async (tab: chrome.tabs.Tab): Promise<void
|
|||||||
const command = new GeneratePasswordToClipboardCommand(
|
const command = new GeneratePasswordToClipboardCommand(
|
||||||
await passwordGenerationServiceFactory(cache, options),
|
await passwordGenerationServiceFactory(cache, options),
|
||||||
await autofillSettingsServiceFactory(cache, options),
|
await autofillSettingsServiceFactory(cache, options),
|
||||||
|
await browserTaskSchedulerServiceFactory(cache, options),
|
||||||
);
|
);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
Loading…
Reference in New Issue
Block a user