[PS-816] Add Autofill Shortcut to MV3 Extension (#3131)
* Work on background service worker. * Work on shortcuts * Work on supporting service worker * Put new background behind version check * Fix build * Use new storage service * create commands from crypto service (#2995) * Work on service worker autofill * Got basic autofill working * Final touches * Work on tests * Revert some changes * Add modifications * Remove unused ciphers for now * Cleanup * Address PR feedback * Update lock file * Update noop service * Add chrome type * Handle "/" in branch names Updates web workflow to handle the `/` in branch names when it's a PR. * Remove any Co-authored-by: Jake Fink <jfink@bitwarden.com> Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
This commit is contained in:
parent
cfc8858ef9
commit
43d428b3df
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig",
|
"extends": "../tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest", "chrome"],
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
|
"exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import MainBackground from "./background/main.background";
|
import MainBackground from "./background/main.background";
|
||||||
|
import { onCommandListener } from "./listeners/onCommandListener";
|
||||||
|
|
||||||
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
|
||||||
|
if (manifest.manifest_version === 3) {
|
||||||
|
chrome.commands.onCommand.addListener(onCommandListener);
|
||||||
|
} else {
|
||||||
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
||||||
bitwardenMain.bootstrap().then(() => {
|
bitwardenMain.bootstrap().then(() => {
|
||||||
// Finished bootstrapping
|
// Finished bootstrapping
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -81,6 +81,10 @@ export default class ContextMenusBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
||||||
|
if (typeof info.menuItemId !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const id = info.menuItemId.split("_")[1];
|
const id = info.menuItemId.split("_")[1];
|
||||||
|
|
||||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||||
|
|
|
@ -14,7 +14,10 @@ export default class WebRequestBackground {
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private authService: AuthService
|
private authService: AuthService
|
||||||
) {
|
) {
|
||||||
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
if (manifest.manifest_version === 2) {
|
||||||
this.webRequest = (window as any).chrome.webRequest;
|
this.webRequest = (window as any).chrome.webRequest;
|
||||||
|
}
|
||||||
this.isFirefox = platformUtilsService.isFirefox();
|
this.isFirefox = platformUtilsService.isFirefox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import AutofillPageDetails from "../models/autofillPageDetails";
|
||||||
|
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||||
|
|
||||||
|
export class AutoFillActiveTabCommand {
|
||||||
|
constructor(private autofillService: AutofillService) {}
|
||||||
|
|
||||||
|
async doAutoFillActiveTabCommand(tab: chrome.tabs.Tab) {
|
||||||
|
if (!tab.id) {
|
||||||
|
throw new Error("Tab does not have an id, cannot complete autofill.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = await this.collectPageDetails(tab.id);
|
||||||
|
await this.autofillService.doAutoFillOnTab(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
frameId: 0,
|
||||||
|
tab: tab,
|
||||||
|
details: details,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tab,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async collectPageDetails(tabId: number): Promise<AutofillPageDetails> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.tabs.sendMessage(
|
||||||
|
tabId,
|
||||||
|
{
|
||||||
|
command: "collectPageDetailsImmediately",
|
||||||
|
},
|
||||||
|
(response: AutofillPageDetails) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
reject(chrome.runtime.lastError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,7 @@
|
||||||
6. Rename com.agilebits.* stuff to com.bitwarden.*
|
6. Rename com.agilebits.* stuff to com.bitwarden.*
|
||||||
7. Remove "some useful globals" on window
|
7. Remove "some useful globals" on window
|
||||||
8. Add ability to autofill span[data-bwautofill] elements
|
8. Add ability to autofill span[data-bwautofill] elements
|
||||||
|
9. Add new handler, for new command that responds with page details in response callback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function collect(document, undefined) {
|
function collect(document, undefined) {
|
||||||
|
@ -1037,6 +1038,11 @@
|
||||||
fill(document, msg.fillScript);
|
fill(document, msg.fillScript);
|
||||||
sendResponse();
|
sendResponse();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (msg.command === 'collectPageDetailsImmediately') {
|
||||||
|
var pageDetails = collect(document);
|
||||||
|
var pageDetailsObj = JSON.parse(pageDetails);
|
||||||
|
sendResponse(pageDetailsObj);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
|
||||||
|
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||||
|
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||||
|
import { AuthService } from "@bitwarden/common/services/auth.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/services/cipher.service";
|
||||||
|
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||||
|
import NoOpEventService from "@bitwarden/common/services/noOpEvent.service";
|
||||||
|
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||||
|
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
||||||
|
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
|
||||||
|
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
|
||||||
|
|
||||||
|
import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand";
|
||||||
|
import { Account } from "../models/account";
|
||||||
|
import { StateService as AbstractStateService } from "../services/abstractions/state.service";
|
||||||
|
import AutofillService from "../services/autofill.service";
|
||||||
|
import { BrowserCryptoService } from "../services/browserCrypto.service";
|
||||||
|
import BrowserLocalStorageService from "../services/browserLocalStorage.service";
|
||||||
|
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||||
|
import I18nService from "../services/i18n.service";
|
||||||
|
import { KeyGenerationService } from "../services/keyGeneration.service";
|
||||||
|
import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service";
|
||||||
|
import { StateService } from "../services/state.service";
|
||||||
|
|
||||||
|
export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => {
|
||||||
|
switch (command) {
|
||||||
|
case "autofill_login":
|
||||||
|
await doAutoFillLogin(tab);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise<void> => {
|
||||||
|
const logService = new ConsoleLogService(false);
|
||||||
|
|
||||||
|
const cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||||
|
|
||||||
|
const storageService = new BrowserLocalStorageService();
|
||||||
|
|
||||||
|
const secureStorageService = new BrowserLocalStorageService();
|
||||||
|
|
||||||
|
const memoryStorageService = new LocalBackedSessionStorageService(
|
||||||
|
new EncryptService(cryptoFunctionService, logService, false),
|
||||||
|
new KeyGenerationService(cryptoFunctionService)
|
||||||
|
);
|
||||||
|
|
||||||
|
const stateFactory = new StateFactory(GlobalState, Account);
|
||||||
|
|
||||||
|
const stateMigrationService = new StateMigrationService(
|
||||||
|
storageService,
|
||||||
|
secureStorageService,
|
||||||
|
stateFactory
|
||||||
|
);
|
||||||
|
|
||||||
|
const stateService: AbstractStateService = new StateService(
|
||||||
|
storageService,
|
||||||
|
secureStorageService,
|
||||||
|
memoryStorageService, // AbstractStorageService
|
||||||
|
logService,
|
||||||
|
stateMigrationService,
|
||||||
|
stateFactory
|
||||||
|
);
|
||||||
|
|
||||||
|
await stateService.init();
|
||||||
|
|
||||||
|
const platformUtils = new BrowserPlatformUtilsService(
|
||||||
|
null, // MessagingService
|
||||||
|
stateService,
|
||||||
|
null, // clipboardWriteCallback
|
||||||
|
null // biometricCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
const cryptoService = new BrowserCryptoService(
|
||||||
|
cryptoFunctionService,
|
||||||
|
null, // AbstractEncryptService
|
||||||
|
platformUtils,
|
||||||
|
logService,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsService = new SettingsService(stateService);
|
||||||
|
|
||||||
|
const i18nService = new I18nService(chrome.i18n.getUILanguage());
|
||||||
|
|
||||||
|
await i18nService.init();
|
||||||
|
|
||||||
|
// Don't love this pt.1
|
||||||
|
let searchService: SearchService = null;
|
||||||
|
|
||||||
|
const cipherService = new CipherService(
|
||||||
|
cryptoService,
|
||||||
|
settingsService,
|
||||||
|
null, // ApiService
|
||||||
|
null, // FileUploadService,
|
||||||
|
i18nService,
|
||||||
|
() => searchService, // Don't love this pt.2
|
||||||
|
logService,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't love this pt.3
|
||||||
|
searchService = new SearchService(cipherService, logService, i18nService);
|
||||||
|
|
||||||
|
// TODO: Remove this before we encourage anyone to start using this
|
||||||
|
const eventService = new NoOpEventService();
|
||||||
|
|
||||||
|
const autofillService = new AutofillService(
|
||||||
|
cipherService,
|
||||||
|
stateService,
|
||||||
|
null, // TotpService
|
||||||
|
eventService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
|
||||||
|
const authService = new AuthService(
|
||||||
|
cryptoService, // CryptoService
|
||||||
|
null, // ApiService
|
||||||
|
null, // TokenService
|
||||||
|
null, // AppIdService
|
||||||
|
platformUtils,
|
||||||
|
null, // MessagingService
|
||||||
|
logService,
|
||||||
|
null, // KeyConnectorService
|
||||||
|
null, // EnvironmentService
|
||||||
|
stateService,
|
||||||
|
null, // TwoFactorService
|
||||||
|
i18nService
|
||||||
|
);
|
||||||
|
|
||||||
|
const authStatus = await authService.getAuthStatus();
|
||||||
|
if (authStatus < AuthenticationStatus.Unlocked) {
|
||||||
|
// TODO: Add back in unlock on autofill
|
||||||
|
logService.info("Currently not unlocked, MV3 does not support unlock on autofill currently.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = new AutoFillActiveTabCommand(autofillService);
|
||||||
|
await command.doAutoFillActiveTabCommand(tab);
|
||||||
|
};
|
|
@ -47,7 +47,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js",
|
||||||
|
"type": "module"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
|
|
|
@ -22,4 +22,5 @@ export default class AutofillField {
|
||||||
selectInfo: any;
|
selectInfo: any;
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
tagName: string;
|
tagName: string;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
|
|
||||||
export default abstract class AbstractChromeStorageService implements AbstractStorageService {
|
export default abstract class AbstractChromeStorageService implements AbstractStorageService {
|
||||||
protected abstract chromeStorageApi: any;
|
protected abstract chromeStorageApi: chrome.storage.StorageArea;
|
||||||
|
|
||||||
async get<T>(key: string): Promise<T> {
|
async get<T>(key: string): Promise<T> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|
|
@ -1,7 +1,41 @@
|
||||||
|
import { CipherView } from "@bitwarden/common/models/view/cipherView";
|
||||||
|
|
||||||
|
import AutofillField from "../../models/autofillField";
|
||||||
|
import AutofillForm from "../../models/autofillForm";
|
||||||
import AutofillPageDetails from "../../models/autofillPageDetails";
|
import AutofillPageDetails from "../../models/autofillPageDetails";
|
||||||
|
|
||||||
export abstract class AutofillService {
|
export interface PageDetail {
|
||||||
getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[];
|
frameId: number;
|
||||||
doAutoFill: (options: any) => Promise<string>;
|
tab: chrome.tabs.Tab;
|
||||||
doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise<string>;
|
details: AutofillPageDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoFillOptions {
|
||||||
|
cipher: CipherView;
|
||||||
|
pageDetails: PageDetail[];
|
||||||
|
doc?: typeof window.document;
|
||||||
|
tab: chrome.tabs.Tab;
|
||||||
|
skipUsernameOnlyFill?: boolean;
|
||||||
|
onlyEmptyFields?: boolean;
|
||||||
|
onlyVisibleFields?: boolean;
|
||||||
|
fillNewPassword?: boolean;
|
||||||
|
skipLastUsed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormData {
|
||||||
|
form: AutofillForm;
|
||||||
|
password: AutofillField;
|
||||||
|
username: AutofillField;
|
||||||
|
passwords: AutofillField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AutofillService {
|
||||||
|
getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => FormData[];
|
||||||
|
doAutoFill: (options: AutoFillOptions) => Promise<string>;
|
||||||
|
doAutoFillOnTab: (
|
||||||
|
pageDetails: PageDetail[],
|
||||||
|
tab: chrome.tabs.Tab,
|
||||||
|
fromCommand: boolean
|
||||||
|
) => Promise<string>;
|
||||||
|
doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,26 @@ import AutofillPageDetails from "../models/autofillPageDetails";
|
||||||
import AutofillScript from "../models/autofillScript";
|
import AutofillScript from "../models/autofillScript";
|
||||||
import { StateService } from "../services/abstractions/state.service";
|
import { StateService } from "../services/abstractions/state.service";
|
||||||
|
|
||||||
import { AutofillService as AutofillServiceInterface } from "./abstractions/autofill.service";
|
import {
|
||||||
|
AutoFillOptions,
|
||||||
|
AutofillService as AutofillServiceInterface,
|
||||||
|
PageDetail,
|
||||||
|
FormData,
|
||||||
|
} from "./abstractions/autofill.service";
|
||||||
import {
|
import {
|
||||||
AutoFillConstants,
|
AutoFillConstants,
|
||||||
CreditCardAutoFillConstants,
|
CreditCardAutoFillConstants,
|
||||||
IdentityAutoFillConstants,
|
IdentityAutoFillConstants,
|
||||||
} from "./autofillConstants";
|
} from "./autofillConstants";
|
||||||
|
|
||||||
|
export interface GenerateFillScriptOptions {
|
||||||
|
skipUsernameOnlyFill: boolean;
|
||||||
|
onlyEmptyFields: boolean;
|
||||||
|
onlyVisibleFields: boolean;
|
||||||
|
fillNewPassword: boolean;
|
||||||
|
cipher: CipherView;
|
||||||
|
}
|
||||||
|
|
||||||
export default class AutofillService implements AutofillServiceInterface {
|
export default class AutofillService implements AutofillServiceInterface {
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
|
@ -31,10 +44,16 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
private logService: LogService
|
private logService: LogService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): any[] {
|
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
|
||||||
const formData: any[] = [];
|
const formData: FormData[] = [];
|
||||||
|
|
||||||
const passwordFields = this.loadPasswordFields(pageDetails, true, true, false, false);
|
const passwordFields = AutofillService.loadPasswordFields(
|
||||||
|
pageDetails,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
if (passwordFields.length === 0) {
|
if (passwordFields.length === 0) {
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
@ -64,16 +83,17 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAutoFill(options: any) {
|
async doAutoFill(options: AutoFillOptions) {
|
||||||
let totpPromise: Promise<string> = null;
|
|
||||||
const tab = options.tab;
|
const tab = options.tab;
|
||||||
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
||||||
throw new Error("Nothing to auto-fill.");
|
throw new Error("Nothing to auto-fill.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totpPromise: Promise<string> = null;
|
||||||
|
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||||
let didAutofill = false;
|
let didAutofill = false;
|
||||||
options.pageDetails.forEach((pd: any) => {
|
options.pageDetails.forEach((pd) => {
|
||||||
// make sure we're still on correct tab
|
// make sure we're still on correct tab
|
||||||
if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) {
|
if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) {
|
||||||
return;
|
return;
|
||||||
|
@ -138,12 +158,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAutoFillActiveTab(pageDetails: any, fromCommand: boolean) {
|
async doAutoFillOnTab(pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean) {
|
||||||
const tab = await this.getActiveTab();
|
|
||||||
if (!tab || !tab.url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cipher: CipherView;
|
let cipher: CipherView;
|
||||||
if (fromCommand) {
|
if (fromCommand) {
|
||||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
||||||
|
@ -186,9 +201,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return totpCode;
|
return totpCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async doAutoFillActiveTab(pageDetails: PageDetail[], fromCommand: boolean) {
|
||||||
|
const tab = await this.getActiveTab();
|
||||||
|
if (!tab || !tab.url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private async getActiveTab(): Promise<any> {
|
private async getActiveTab(): Promise<chrome.tabs.Tab> {
|
||||||
const tab = await BrowserApi.getTabFromCurrentWindow();
|
const tab = await BrowserApi.getTabFromCurrentWindow();
|
||||||
if (!tab) {
|
if (!tab) {
|
||||||
throw new Error("No tab found.");
|
throw new Error("No tab found.");
|
||||||
|
@ -197,7 +221,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateFillScript(pageDetails: AutofillPageDetails, options: any): AutofillScript {
|
private generateFillScript(
|
||||||
|
pageDetails: AutofillPageDetails,
|
||||||
|
options: GenerateFillScriptOptions
|
||||||
|
): AutofillScript {
|
||||||
if (!pageDetails || !options.cipher) {
|
if (!pageDetails || !options.cipher) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -209,13 +236,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
if (fields && fields.length) {
|
if (fields && fields.length) {
|
||||||
const fieldNames: string[] = [];
|
const fieldNames: string[] = [];
|
||||||
|
|
||||||
fields.forEach((f: any) => {
|
fields.forEach((f) => {
|
||||||
if (this.hasValue(f.name)) {
|
if (AutofillService.hasValue(f.name)) {
|
||||||
fieldNames.push(f.name.toLowerCase());
|
fieldNames.push(f.name.toLowerCase());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pageDetails.fields.forEach((field: any) => {
|
pageDetails.fields.forEach((field) => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
if (filledFields.hasOwnProperty(field.opid)) {
|
if (filledFields.hasOwnProperty(field.opid)) {
|
||||||
return;
|
return;
|
||||||
|
@ -228,10 +255,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
const matchingIndex = this.findMatchingFieldIndex(field, fieldNames);
|
const matchingIndex = this.findMatchingFieldIndex(field, fieldNames);
|
||||||
if (matchingIndex > -1) {
|
if (matchingIndex > -1) {
|
||||||
const matchingField: FieldView = fields[matchingIndex];
|
const matchingField: FieldView = fields[matchingIndex];
|
||||||
let val;
|
let val: string;
|
||||||
if (matchingField.type === FieldType.Linked) {
|
if (matchingField.type === FieldType.Linked) {
|
||||||
// Assumption: Linked Field is not being used to autofill a boolean value
|
// Assumption: Linked Field is not being used to autofill a boolean value
|
||||||
val = options.cipher.linkedFieldValue(matchingField.linkedId);
|
val = options.cipher.linkedFieldValue(matchingField.linkedId) as string;
|
||||||
} else {
|
} else {
|
||||||
val = matchingField.value;
|
val = matchingField.value;
|
||||||
if (val == null && matchingField.type === FieldType.Boolean) {
|
if (val == null && matchingField.type === FieldType.Boolean) {
|
||||||
|
@ -240,7 +267,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledFields[field.opid] = field;
|
filledFields[field.opid] = field;
|
||||||
this.fillByOpid(fillScript, field, val);
|
AutofillService.fillByOpid(fillScript, field, val);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -269,9 +296,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
private generateLoginFillScript(
|
private generateLoginFillScript(
|
||||||
fillScript: AutofillScript,
|
fillScript: AutofillScript,
|
||||||
pageDetails: any,
|
pageDetails: AutofillPageDetails,
|
||||||
filledFields: { [id: string]: AutofillField },
|
filledFields: { [id: string]: AutofillField },
|
||||||
options: any
|
options: GenerateFillScriptOptions
|
||||||
): AutofillScript {
|
): AutofillScript {
|
||||||
if (!options.cipher.login) {
|
if (!options.cipher.login) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -285,11 +312,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
if (!login.password || login.password === "") {
|
if (!login.password || login.password === "") {
|
||||||
// No password for this login. Maybe they just wanted to auto-fill some custom fields?
|
// No password for this login. Maybe they just wanted to auto-fill some custom fields?
|
||||||
fillScript = this.setFillScriptForFocus(filledFields, fillScript);
|
fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript);
|
||||||
return fillScript;
|
return fillScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordFields = this.loadPasswordFields(
|
let passwordFields = AutofillService.loadPasswordFields(
|
||||||
pageDetails,
|
pageDetails,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
@ -298,7 +325,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
);
|
);
|
||||||
if (!passwordFields.length && !options.onlyVisibleFields) {
|
if (!passwordFields.length && !options.onlyVisibleFields) {
|
||||||
// not able to find any viewable password fields. maybe there are some "hidden" ones?
|
// not able to find any viewable password fields. maybe there are some "hidden" ones?
|
||||||
passwordFields = this.loadPasswordFields(
|
passwordFields = AutofillService.loadPasswordFields(
|
||||||
pageDetails,
|
pageDetails,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
@ -362,11 +389,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
if (!passwordFields.length && !options.skipUsernameOnlyFill) {
|
if (!passwordFields.length && !options.skipUsernameOnlyFill) {
|
||||||
// No password fields on this page. Let's try to just fuzzy fill the username.
|
// No password fields on this page. Let's try to just fuzzy fill the username.
|
||||||
pageDetails.fields.forEach((f: any) => {
|
pageDetails.fields.forEach((f) => {
|
||||||
if (
|
if (
|
||||||
f.viewable &&
|
f.viewable &&
|
||||||
(f.type === "text" || f.type === "email" || f.type === "tel") &&
|
(f.type === "text" || f.type === "email" || f.type === "tel") &&
|
||||||
this.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
|
AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
|
||||||
) {
|
) {
|
||||||
usernames.push(f);
|
usernames.push(f);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +407,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledFields[u.opid] = u;
|
filledFields[u.opid] = u;
|
||||||
this.fillByOpid(fillScript, u, login.username);
|
AutofillService.fillByOpid(fillScript, u, login.username);
|
||||||
});
|
});
|
||||||
|
|
||||||
passwords.forEach((p) => {
|
passwords.forEach((p) => {
|
||||||
|
@ -390,18 +417,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledFields[p.opid] = p;
|
filledFields[p.opid] = p;
|
||||||
this.fillByOpid(fillScript, p, login.password);
|
AutofillService.fillByOpid(fillScript, p, login.password);
|
||||||
});
|
});
|
||||||
|
|
||||||
fillScript = this.setFillScriptForFocus(filledFields, fillScript);
|
fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript);
|
||||||
return fillScript;
|
return fillScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateCardFillScript(
|
private generateCardFillScript(
|
||||||
fillScript: AutofillScript,
|
fillScript: AutofillScript,
|
||||||
pageDetails: any,
|
pageDetails: AutofillPageDetails,
|
||||||
filledFields: { [id: string]: AutofillField },
|
filledFields: { [id: string]: AutofillField },
|
||||||
options: any
|
options: GenerateFillScriptOptions
|
||||||
): AutofillScript {
|
): AutofillScript {
|
||||||
if (!options.cipher.card) {
|
if (!options.cipher.card) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -409,8 +436,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
const fillFields: { [id: string]: AutofillField } = {};
|
const fillFields: { [id: string]: AutofillField } = {};
|
||||||
|
|
||||||
pageDetails.fields.forEach((f: any) => {
|
pageDetails.fields.forEach((f) => {
|
||||||
if (this.forCustomFieldsOnly(f)) {
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +456,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
||||||
if (
|
if (
|
||||||
!fillFields.cardholderName &&
|
!fillFields.cardholderName &&
|
||||||
this.isFieldMatch(
|
AutofillService.isFieldMatch(
|
||||||
f[attr],
|
f[attr],
|
||||||
CreditCardAutoFillConstants.CardHolderFieldNames,
|
CreditCardAutoFillConstants.CardHolderFieldNames,
|
||||||
CreditCardAutoFillConstants.CardHolderFieldNameValues
|
CreditCardAutoFillConstants.CardHolderFieldNameValues
|
||||||
|
@ -439,7 +466,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.number &&
|
!fillFields.number &&
|
||||||
this.isFieldMatch(
|
AutofillService.isFieldMatch(
|
||||||
f[attr],
|
f[attr],
|
||||||
CreditCardAutoFillConstants.CardNumberFieldNames,
|
CreditCardAutoFillConstants.CardNumberFieldNames,
|
||||||
CreditCardAutoFillConstants.CardNumberFieldNameValues
|
CreditCardAutoFillConstants.CardNumberFieldNameValues
|
||||||
|
@ -449,7 +476,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.exp &&
|
!fillFields.exp &&
|
||||||
this.isFieldMatch(
|
AutofillService.isFieldMatch(
|
||||||
f[attr],
|
f[attr],
|
||||||
CreditCardAutoFillConstants.CardExpiryFieldNames,
|
CreditCardAutoFillConstants.CardExpiryFieldNames,
|
||||||
CreditCardAutoFillConstants.CardExpiryFieldNameValues
|
CreditCardAutoFillConstants.CardExpiryFieldNameValues
|
||||||
|
@ -459,25 +486,25 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.expMonth &&
|
!fillFields.expMonth &&
|
||||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.expMonth = f;
|
fillFields.expMonth = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.expYear &&
|
!fillFields.expYear &&
|
||||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.expYear = f;
|
fillFields.expYear = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.code &&
|
!fillFields.code &&
|
||||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.code = f;
|
fillFields.code = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.brand &&
|
!fillFields.brand &&
|
||||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.brand = f;
|
fillFields.brand = f;
|
||||||
break;
|
break;
|
||||||
|
@ -491,7 +518,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
this.makeScriptAction(fillScript, card, fillFields, filledFields, "code");
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "code");
|
||||||
this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand");
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand");
|
||||||
|
|
||||||
if (fillFields.expMonth && this.hasValue(card.expMonth)) {
|
if (fillFields.expMonth && AutofillService.hasValue(card.expMonth)) {
|
||||||
let expMonth: string = card.expMonth;
|
let expMonth: string = card.expMonth;
|
||||||
|
|
||||||
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
|
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
|
||||||
|
@ -526,10 +553,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledFields[fillFields.expMonth.opid] = fillFields.expMonth;
|
filledFields[fillFields.expMonth.opid] = fillFields.expMonth;
|
||||||
this.fillByOpid(fillScript, fillFields.expMonth, expMonth);
|
AutofillService.fillByOpid(fillScript, fillFields.expMonth, expMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillFields.expYear && this.hasValue(card.expYear)) {
|
if (fillFields.expYear && AutofillService.hasValue(card.expYear)) {
|
||||||
let expYear: string = card.expYear;
|
let expYear: string = card.expYear;
|
||||||
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
|
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
|
||||||
for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) {
|
for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) {
|
||||||
|
@ -572,10 +599,14 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledFields[fillFields.expYear.opid] = fillFields.expYear;
|
filledFields[fillFields.expYear.opid] = fillFields.expYear;
|
||||||
this.fillByOpid(fillScript, fillFields.expYear, expYear);
|
AutofillService.fillByOpid(fillScript, fillFields.expYear, expYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillFields.exp && this.hasValue(card.expMonth) && this.hasValue(card.expYear)) {
|
if (
|
||||||
|
fillFields.exp &&
|
||||||
|
AutofillService.hasValue(card.expMonth) &&
|
||||||
|
AutofillService.hasValue(card.expYear)
|
||||||
|
) {
|
||||||
const fullMonth = ("0" + card.expMonth).slice(-2);
|
const fullMonth = ("0" + card.expMonth).slice(-2);
|
||||||
|
|
||||||
let fullYear: string = card.expYear;
|
let fullYear: string = card.expYear;
|
||||||
|
@ -712,7 +743,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return fillScript;
|
return fillScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
private fieldAttrsContain(field: any, containsVal: string) {
|
private fieldAttrsContain(field: AutofillField, containsVal: string) {
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -734,9 +765,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
private generateIdentityFillScript(
|
private generateIdentityFillScript(
|
||||||
fillScript: AutofillScript,
|
fillScript: AutofillScript,
|
||||||
pageDetails: any,
|
pageDetails: AutofillPageDetails,
|
||||||
filledFields: { [id: string]: AutofillField },
|
filledFields: { [id: string]: AutofillField },
|
||||||
options: any
|
options: GenerateFillScriptOptions
|
||||||
): AutofillScript {
|
): AutofillScript {
|
||||||
if (!options.cipher.identity) {
|
if (!options.cipher.identity) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -744,8 +775,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
const fillFields: { [id: string]: AutofillField } = {};
|
const fillFields: { [id: string]: AutofillField } = {};
|
||||||
|
|
||||||
pageDetails.fields.forEach((f: any) => {
|
pageDetails.fields.forEach((f) => {
|
||||||
if (this.forCustomFieldsOnly(f)) {
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,7 +795,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
||||||
if (
|
if (
|
||||||
!fillFields.name &&
|
!fillFields.name &&
|
||||||
this.isFieldMatch(
|
AutofillService.isFieldMatch(
|
||||||
f[attr],
|
f[attr],
|
||||||
IdentityAutoFillConstants.FullNameFieldNames,
|
IdentityAutoFillConstants.FullNameFieldNames,
|
||||||
IdentityAutoFillConstants.FullNameFieldNameValues
|
IdentityAutoFillConstants.FullNameFieldNameValues
|
||||||
|
@ -774,37 +805,37 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.firstName &&
|
!fillFields.firstName &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.firstName = f;
|
fillFields.firstName = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.middleName &&
|
!fillFields.middleName &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.middleName = f;
|
fillFields.middleName = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.lastName &&
|
!fillFields.lastName &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.lastName = f;
|
fillFields.lastName = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.title &&
|
!fillFields.title &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.title = f;
|
fillFields.title = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.email &&
|
!fillFields.email &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.email = f;
|
fillFields.email = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.address &&
|
!fillFields.address &&
|
||||||
this.isFieldMatch(
|
AutofillService.isFieldMatch(
|
||||||
f[attr],
|
f[attr],
|
||||||
IdentityAutoFillConstants.AddressFieldNames,
|
IdentityAutoFillConstants.AddressFieldNames,
|
||||||
IdentityAutoFillConstants.AddressFieldNameValues
|
IdentityAutoFillConstants.AddressFieldNameValues
|
||||||
|
@ -814,61 +845,61 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.address1 &&
|
!fillFields.address1 &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.address1 = f;
|
fillFields.address1 = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.address2 &&
|
!fillFields.address2 &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.address2 = f;
|
fillFields.address2 = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.address3 &&
|
!fillFields.address3 &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.address3 = f;
|
fillFields.address3 = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.postalCode &&
|
!fillFields.postalCode &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.postalCode = f;
|
fillFields.postalCode = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.city &&
|
!fillFields.city &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.city = f;
|
fillFields.city = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.state &&
|
!fillFields.state &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.state = f;
|
fillFields.state = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.country &&
|
!fillFields.country &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.country = f;
|
fillFields.country = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.phone &&
|
!fillFields.phone &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.phone = f;
|
fillFields.phone = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.username &&
|
!fillFields.username &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.username = f;
|
fillFields.username = f;
|
||||||
break;
|
break;
|
||||||
} else if (
|
} else if (
|
||||||
!fillFields.company &&
|
!fillFields.company &&
|
||||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
||||||
) {
|
) {
|
||||||
fillFields.company = f;
|
fillFields.company = f;
|
||||||
break;
|
break;
|
||||||
|
@ -923,16 +954,16 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
if (fillFields.name && (identity.firstName || identity.lastName)) {
|
if (fillFields.name && (identity.firstName || identity.lastName)) {
|
||||||
let fullName = "";
|
let fullName = "";
|
||||||
if (this.hasValue(identity.firstName)) {
|
if (AutofillService.hasValue(identity.firstName)) {
|
||||||
fullName = identity.firstName;
|
fullName = identity.firstName;
|
||||||
}
|
}
|
||||||
if (this.hasValue(identity.middleName)) {
|
if (AutofillService.hasValue(identity.middleName)) {
|
||||||
if (fullName !== "") {
|
if (fullName !== "") {
|
||||||
fullName += " ";
|
fullName += " ";
|
||||||
}
|
}
|
||||||
fullName += identity.middleName;
|
fullName += identity.middleName;
|
||||||
}
|
}
|
||||||
if (this.hasValue(identity.lastName)) {
|
if (AutofillService.hasValue(identity.lastName)) {
|
||||||
if (fullName !== "") {
|
if (fullName !== "") {
|
||||||
fullName += " ";
|
fullName += " ";
|
||||||
}
|
}
|
||||||
|
@ -942,18 +973,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
|
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillFields.address && this.hasValue(identity.address1)) {
|
if (fillFields.address && AutofillService.hasValue(identity.address1)) {
|
||||||
let address = "";
|
let address = "";
|
||||||
if (this.hasValue(identity.address1)) {
|
if (AutofillService.hasValue(identity.address1)) {
|
||||||
address = identity.address1;
|
address = identity.address1;
|
||||||
}
|
}
|
||||||
if (this.hasValue(identity.address2)) {
|
if (AutofillService.hasValue(identity.address2)) {
|
||||||
if (address !== "") {
|
if (address !== "") {
|
||||||
address += ", ";
|
address += ", ";
|
||||||
}
|
}
|
||||||
address += identity.address2;
|
address += identity.address2;
|
||||||
}
|
}
|
||||||
if (this.hasValue(identity.address3)) {
|
if (AutofillService.hasValue(identity.address3)) {
|
||||||
if (address !== "") {
|
if (address !== "") {
|
||||||
address += ", ";
|
address += ", ";
|
||||||
}
|
}
|
||||||
|
@ -970,7 +1001,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return excludedTypes.indexOf(type) > -1;
|
return excludedTypes.indexOf(type) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isFieldMatch(value: string, options: string[], containsOptions?: string[]): boolean {
|
private static isFieldMatch(
|
||||||
|
value: string,
|
||||||
|
options: string[],
|
||||||
|
containsOptions?: string[]
|
||||||
|
): boolean {
|
||||||
value = value
|
value = value
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
@ -1011,12 +1046,15 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
filledFields: { [id: string]: AutofillField }
|
filledFields: { [id: string]: AutofillField }
|
||||||
) {
|
) {
|
||||||
let doFill = false;
|
let doFill = false;
|
||||||
if (this.hasValue(dataValue) && field) {
|
if (AutofillService.hasValue(dataValue) && field) {
|
||||||
if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) {
|
if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) {
|
||||||
for (let i = 0; i < field.selectInfo.options.length; i++) {
|
for (let i = 0; i < field.selectInfo.options.length; i++) {
|
||||||
const option = field.selectInfo.options[i];
|
const option = field.selectInfo.options[i];
|
||||||
for (let j = 0; j < option.length; j++) {
|
for (let j = 0; j < option.length; j++) {
|
||||||
if (this.hasValue(option[j]) && option[j].toLowerCase() === dataValue.toLowerCase()) {
|
if (
|
||||||
|
AutofillService.hasValue(option[j]) &&
|
||||||
|
option[j].toLowerCase() === dataValue.toLowerCase()
|
||||||
|
) {
|
||||||
doFill = true;
|
doFill = true;
|
||||||
if (option.length > 1) {
|
if (option.length > 1) {
|
||||||
dataValue = option[1];
|
dataValue = option[1];
|
||||||
|
@ -1036,11 +1074,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
if (doFill) {
|
if (doFill) {
|
||||||
filledFields[field.opid] = field;
|
filledFields[field.opid] = field;
|
||||||
this.fillByOpid(fillScript, field, dataValue);
|
AutofillService.fillByOpid(fillScript, field, dataValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadPasswordFields(
|
static loadPasswordFields(
|
||||||
pageDetails: AutofillPageDetails,
|
pageDetails: AutofillPageDetails,
|
||||||
canBeHidden: boolean,
|
canBeHidden: boolean,
|
||||||
canBeReadOnly: boolean,
|
canBeReadOnly: boolean,
|
||||||
|
@ -1049,7 +1087,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
) {
|
) {
|
||||||
const arr: AutofillField[] = [];
|
const arr: AutofillField[] = [];
|
||||||
pageDetails.fields.forEach((f) => {
|
pageDetails.fields.forEach((f) => {
|
||||||
if (this.forCustomFieldsOnly(f)) {
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,7 +1149,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
let usernameField: AutofillField = null;
|
let usernameField: AutofillField = null;
|
||||||
for (let i = 0; i < pageDetails.fields.length; i++) {
|
for (let i = 0; i < pageDetails.fields.length; i++) {
|
||||||
const f = pageDetails.fields[i];
|
const f = pageDetails.fields[i];
|
||||||
if (this.forCustomFieldsOnly(f)) {
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1195,7 +1233,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
|
|
||||||
private fieldPropertyIsMatch(field: any, property: string, name: string): boolean {
|
private fieldPropertyIsMatch(field: any, property: string, name: string): boolean {
|
||||||
let fieldVal = field[property] as string;
|
let fieldVal = field[property] as string;
|
||||||
if (!this.hasValue(fieldVal)) {
|
if (!AutofillService.hasValue(fieldVal)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1227,33 +1265,45 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return fieldVal.toLowerCase() === name;
|
return fieldVal.toLowerCase() === name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
|
static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
|
||||||
if (this.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
|
if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
|
if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field["label-tag"]) && this.fuzzyMatch(names, field["label-tag"])) {
|
if (
|
||||||
|
AutofillService.hasValue(field["label-tag"]) &&
|
||||||
|
this.fuzzyMatch(names, field["label-tag"])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
|
if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field["label-left"]) && this.fuzzyMatch(names, field["label-left"])) {
|
if (
|
||||||
|
AutofillService.hasValue(field["label-left"]) &&
|
||||||
|
this.fuzzyMatch(names, field["label-left"])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field["label-top"]) && this.fuzzyMatch(names, field["label-top"])) {
|
if (
|
||||||
|
AutofillService.hasValue(field["label-top"]) &&
|
||||||
|
this.fuzzyMatch(names, field["label-top"])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.hasValue(field["label-aria"]) && this.fuzzyMatch(names, field["label-aria"])) {
|
if (
|
||||||
|
AutofillService.hasValue(field["label-aria"]) &&
|
||||||
|
this.fuzzyMatch(names, field["label-aria"])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private fuzzyMatch(options: string[], value: string): boolean {
|
private static fuzzyMatch(options: string[], value: string): boolean {
|
||||||
if (options == null || options.length === 0 || value == null || value === "") {
|
if (options == null || options.length === 0 || value == null || value === "") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1272,11 +1322,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasValue(str: string): boolean {
|
static hasValue(str: string): boolean {
|
||||||
return str && str !== "";
|
return str && str !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private setFillScriptForFocus(
|
static setFillScriptForFocus(
|
||||||
filledFields: { [id: string]: AutofillField },
|
filledFields: { [id: string]: AutofillField },
|
||||||
fillScript: AutofillScript
|
fillScript: AutofillScript
|
||||||
): AutofillScript {
|
): AutofillScript {
|
||||||
|
@ -1304,7 +1354,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return fillScript;
|
return fillScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
private fillByOpid(fillScript: AutofillScript, field: AutofillField, value: string): void {
|
static fillByOpid(fillScript: AutofillScript, field: AutofillField, value: string): void {
|
||||||
if (field.maxLength && value && value.length > field.maxLength) {
|
if (field.maxLength && value && value.length > field.maxLength) {
|
||||||
value = value.substr(0, value.length);
|
value = value.substr(0, value.length);
|
||||||
}
|
}
|
||||||
|
@ -1315,7 +1365,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
fillScript.script.push(["fill_by_opid", field.opid, value]);
|
fillScript.script.push(["fill_by_opid", field.opid, value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private forCustomFieldsOnly(field: AutofillField): boolean {
|
static forCustomFieldsOnly(field: AutofillField): boolean {
|
||||||
return field.tagName === "span";
|
return field.tagName === "span";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
||||||
|
|
||||||
export default class BrowserLocalStorageService extends AbstractChromeStorageService {
|
export default class BrowserLocalStorageService extends AbstractChromeStorageService {
|
||||||
protected chromeStorageApi: any = chrome.storage.local;
|
protected chromeStorageApi = chrome.storage.local;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
||||||
|
|
||||||
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
||||||
protected chromeStorageApi: any = (chrome.storage as any).session;
|
protected chromeStorageApi = chrome.storage.session;
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,13 +176,6 @@ const config = {
|
||||||
return chunk.name === "popup/main";
|
return chunk.name === "popup/main";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
commons2: {
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
name: "vendor",
|
|
||||||
chunks: (chunk) => {
|
|
||||||
return chunk.name === "background";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -209,4 +202,16 @@ const config = {
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (manifestVersion == 2) {
|
||||||
|
// We can't use this in manifest v3
|
||||||
|
// Ideally we understand why this breaks it and we don't have to do this
|
||||||
|
config.optimization.splitChunks.cacheGroups.commons2 = {
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
name: "vendor",
|
||||||
|
chunks: (chunk) => {
|
||||||
|
return chunk.name === "background";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||||
templateUrl: "verify-email.component.html",
|
templateUrl: "verify-email.component.html",
|
||||||
})
|
})
|
||||||
export class VerifyEmailComponent {
|
export class VerifyEmailComponent {
|
||||||
actionPromise: Promise<any>;
|
actionPromise: Promise<unknown>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
|
|
|
@ -78,25 +78,4 @@ describe("ConsoleLogService", () => {
|
||||||
error: { 0: "this is an error message" },
|
error: { 0: "this is an error message" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("times with output to info", async () => {
|
|
||||||
logService.time();
|
|
||||||
await new Promise((r) => setTimeout(r, 250));
|
|
||||||
const duration = logService.timeEnd();
|
|
||||||
expect(duration[0]).toBe(0);
|
|
||||||
expect(duration[1]).toBeGreaterThan(0);
|
|
||||||
expect(duration[1]).toBeLessThan(500 * 10e6);
|
|
||||||
|
|
||||||
expect(caughtMessage).toEqual(expect.arrayContaining([]));
|
|
||||||
expect(caughtMessage.log.length).toBe(1);
|
|
||||||
expect(caughtMessage.log[0]).toEqual(expect.stringMatching(/^default: \d+\.?\d*ms$/));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("filters time output", async () => {
|
|
||||||
logService = new ConsoleLogService(true, () => true);
|
|
||||||
logService.time();
|
|
||||||
logService.timeEnd();
|
|
||||||
|
|
||||||
expect(caughtMessage).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,4 @@ export abstract class LogService {
|
||||||
warning: (message: string) => void;
|
warning: (message: string) => void;
|
||||||
error: (message: string) => void;
|
error: (message: string) => void;
|
||||||
write: (level: LogLevelType, message: string) => void;
|
write: (level: LogLevelType, message: string) => void;
|
||||||
time: (label: string) => void;
|
|
||||||
timeEnd: (label: string) => [number, number];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
import * as tldjs from "tldjs";
|
import * as tldjs from "tldjs";
|
||||||
|
|
||||||
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
|
|
||||||
import { I18nService } from "../abstractions/i18n.service";
|
import { I18nService } from "../abstractions/i18n.service";
|
||||||
|
|
||||||
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/* eslint-disable-next-line no-var */
|
||||||
|
var bitwardenContainerService: BitwardenContainerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BitwardenContainerService {
|
||||||
|
getCryptoService: () => CryptoService;
|
||||||
|
}
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
static inited = false;
|
static inited = false;
|
||||||
static isNode = false;
|
static isNode = false;
|
||||||
static isBrowser = true;
|
static isBrowser = true;
|
||||||
static isMobileBrowser = false;
|
static isMobileBrowser = false;
|
||||||
static isAppleMobileBrowser = false;
|
static isAppleMobileBrowser = false;
|
||||||
static global: any = null;
|
static global: typeof global = null;
|
||||||
static tldEndingRegex =
|
static tldEndingRegex =
|
||||||
/.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/;
|
/.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/;
|
||||||
// Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers.
|
// Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers.
|
||||||
|
@ -29,16 +40,25 @@ export class Utils {
|
||||||
(process as any).release != null &&
|
(process as any).release != null &&
|
||||||
(process as any).release.name === "node";
|
(process as any).release.name === "node";
|
||||||
Utils.isBrowser = typeof window !== "undefined";
|
Utils.isBrowser = typeof window !== "undefined";
|
||||||
|
|
||||||
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window);
|
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window);
|
||||||
Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window);
|
Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window);
|
||||||
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
|
|
||||||
|
if (Utils.isNode) {
|
||||||
|
Utils.global = global;
|
||||||
|
} else if (Utils.isBrowser) {
|
||||||
|
Utils.global = window;
|
||||||
|
} else {
|
||||||
|
// If it's not browser or node then it must be a service worker
|
||||||
|
Utils.global = self;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromB64ToArray(str: string): Uint8Array {
|
static fromB64ToArray(str: string): Uint8Array {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return new Uint8Array(Buffer.from(str, "base64"));
|
return new Uint8Array(Buffer.from(str, "base64"));
|
||||||
} else {
|
} else {
|
||||||
const binaryString = window.atob(str);
|
const binaryString = Utils.global.atob(str);
|
||||||
const bytes = new Uint8Array(binaryString.length);
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
for (let i = 0; i < binaryString.length; i++) {
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
bytes[i] = binaryString.charCodeAt(i);
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
@ -93,7 +113,7 @@ export class Utils {
|
||||||
for (let i = 0; i < bytes.byteLength; i++) {
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
binary += String.fromCharCode(bytes[i]);
|
binary += String.fromCharCode(bytes[i]);
|
||||||
}
|
}
|
||||||
return window.btoa(binary);
|
return Utils.global.btoa(binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +177,7 @@ export class Utils {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return Buffer.from(utfStr, "utf8").toString("base64");
|
return Buffer.from(utfStr, "utf8").toString("base64");
|
||||||
} else {
|
} else {
|
||||||
return decodeURIComponent(escape(window.btoa(utfStr)));
|
return decodeURIComponent(escape(Utils.global.btoa(utfStr)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +189,7 @@ export class Utils {
|
||||||
if (Utils.isNode) {
|
if (Utils.isNode) {
|
||||||
return Buffer.from(b64Str, "base64").toString("utf8");
|
return Buffer.from(b64Str, "base64").toString("utf8");
|
||||||
} else {
|
} else {
|
||||||
return decodeURIComponent(escape(window.atob(b64Str)));
|
return decodeURIComponent(escape(Utils.global.atob(b64Str)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +404,7 @@ export class Utils {
|
||||||
return new nodeURL.URL(uriString);
|
return new nodeURL.URL(uriString);
|
||||||
} else if (typeof URL === "function") {
|
} else if (typeof URL === "function") {
|
||||||
return new URL(uriString);
|
return new URL(uriString);
|
||||||
} else if (window != null) {
|
} else if (typeof window !== "undefined") {
|
||||||
const hasProtocol = uriString.indexOf("://") > -1;
|
const hasProtocol = uriString.indexOf("://") > -1;
|
||||||
if (!hasProtocol && uriString.indexOf(".") > -1) {
|
if (!hasProtocol && uriString.indexOf(".") > -1) {
|
||||||
uriString = "http://" + uriString;
|
uriString = "http://" + uriString;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class Attachment extends Domain {
|
||||||
|
|
||||||
if (this.key != null) {
|
if (this.key != null) {
|
||||||
let cryptoService: CryptoService;
|
let cryptoService: CryptoService;
|
||||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
const containerService = Utils.global.bitwardenContainerService;
|
||||||
if (containerService) {
|
if (containerService) {
|
||||||
cryptoService = containerService.getCryptoService();
|
cryptoService = containerService.getCryptoService();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -104,7 +104,7 @@ export class EncString implements IEncrypted {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cryptoService: CryptoService;
|
let cryptoService: CryptoService;
|
||||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
const containerService = Utils.global.bitwardenContainerService;
|
||||||
if (containerService) {
|
if (containerService) {
|
||||||
cryptoService = containerService.getCryptoService();
|
cryptoService = containerService.getCryptoService();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -341,7 +341,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||||
throw new Error("No key.");
|
throw new Error("No key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: any[] = [];
|
const promises: Promise<number>[] = [];
|
||||||
const ciphers = await this.getAll();
|
const ciphers = await this.getAll();
|
||||||
ciphers.forEach(async (cipher) => {
|
ciphers.forEach(async (cipher) => {
|
||||||
promises.push(cipher.decrypt().then((c) => decCiphers.push(c)));
|
promises.push(cipher.decrypt().then((c) => decCiphers.push(c)));
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import * as hrtime from "browser-hrtime";
|
|
||||||
|
|
||||||
import { LogService as LogServiceAbstraction } from "../abstractions/log.service";
|
import { LogService as LogServiceAbstraction } from "../abstractions/log.service";
|
||||||
import { LogLevelType } from "../enums/logLevelType";
|
import { LogLevelType } from "../enums/logLevelType";
|
||||||
|
|
||||||
|
@ -56,17 +54,4 @@ export class ConsoleLogService implements LogServiceAbstraction {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time(label = "default") {
|
|
||||||
if (!this.timersMap.has(label)) {
|
|
||||||
this.timersMap.set(label, hrtime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeEnd(label = "default"): [number, number] {
|
|
||||||
const elapsed = hrtime(this.timersMap.get(label));
|
|
||||||
this.timersMap.delete(label);
|
|
||||||
this.write(LogLevelType.Info, `${label}: ${elapsed[0] * 1000 + elapsed[1] / 10e6}ms`);
|
|
||||||
return elapsed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||||
|
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you want to use this, don't.
|
||||||
|
* If you think you should use that after the warning, don't.
|
||||||
|
*/
|
||||||
|
export default class NoOpEventService implements EventService {
|
||||||
|
constructor() {
|
||||||
|
if (chrome.runtime.getManifest().manifest_version !== 3) {
|
||||||
|
throw new Error("You are not allowed to use this when not in manifest_version 3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collect(eventType: EventType, cipherId?: string, uploadImmediately?: boolean) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
uploadEvents(userId?: string) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
clearEvents(userId?: string) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ import { CipherView } from "../models/view/cipherView";
|
||||||
import { SendView } from "../models/view/sendView";
|
import { SendView } from "../models/view/sendView";
|
||||||
|
|
||||||
export class SearchService implements SearchServiceAbstraction {
|
export class SearchService implements SearchServiceAbstraction {
|
||||||
|
private static registeredPipeline = false;
|
||||||
|
|
||||||
indexedEntityId?: string = null;
|
indexedEntityId?: string = null;
|
||||||
private indexing = false;
|
private indexing = false;
|
||||||
private index: lunr.Index = null;
|
private index: lunr.Index = null;
|
||||||
|
@ -31,9 +33,14 @@ export class SearchService implements SearchServiceAbstraction {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Currently have to ensure this is only done a single time. Lunr allows you to register a function
|
||||||
|
// multiple times but they will add a warning message to the console. The way they do that breaks when ran on a service worker.
|
||||||
|
if (!SearchService.registeredPipeline) {
|
||||||
|
SearchService.registeredPipeline = true;
|
||||||
//register lunr pipeline function
|
//register lunr pipeline function
|
||||||
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
|
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clearIndex(): void {
|
clearIndex(): void {
|
||||||
this.indexedEntityId = null;
|
this.indexedEntityId = null;
|
||||||
|
@ -54,7 +61,6 @@ export class SearchService implements SearchServiceAbstraction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logService.time("search indexing");
|
|
||||||
this.indexing = true;
|
this.indexing = true;
|
||||||
this.indexedEntityId = indexedEntityId;
|
this.indexedEntityId = indexedEntityId;
|
||||||
this.index = null;
|
this.index = null;
|
||||||
|
@ -95,7 +101,7 @@ export class SearchService implements SearchServiceAbstraction {
|
||||||
|
|
||||||
this.indexing = false;
|
this.indexing = false;
|
||||||
|
|
||||||
this.logService.timeEnd("search indexing");
|
this.logService.info("Finished search indexing");
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchCiphers(
|
async searchCiphers(
|
||||||
|
|
|
@ -610,7 +610,7 @@ export class StateService<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@withPrototypeForArrayMembers(CipherView)
|
@withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
|
||||||
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
|
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||||
|
|
|
@ -9,7 +9,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||||
private crypto: Crypto;
|
private crypto: Crypto;
|
||||||
private subtle: SubtleCrypto;
|
private subtle: SubtleCrypto;
|
||||||
|
|
||||||
constructor(win: Window) {
|
constructor(win: Window | typeof global) {
|
||||||
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
|
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
|
||||||
this.subtle =
|
this.subtle =
|
||||||
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;
|
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
"big-integer": "^1.6.51",
|
"big-integer": "^1.6.51",
|
||||||
"bootstrap": "4.6.0",
|
"bootstrap": "4.6.0",
|
||||||
"braintree-web-drop-in": "^1.33.1",
|
"braintree-web-drop-in": "^1.33.1",
|
||||||
"browser-hrtime": "^1.1.8",
|
|
||||||
"bufferutil": "^4.0.6",
|
"bufferutil": "^4.0.6",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
|
@ -83,7 +82,7 @@
|
||||||
"@storybook/angular": "^6.5.7",
|
"@storybook/angular": "^6.5.7",
|
||||||
"@storybook/builder-webpack5": "^6.5.7",
|
"@storybook/builder-webpack5": "^6.5.7",
|
||||||
"@storybook/manager-webpack5": "^6.5.7",
|
"@storybook/manager-webpack5": "^6.5.7",
|
||||||
"@types/chrome": "^0.0.139",
|
"@types/chrome": "^0.0.190",
|
||||||
"@types/duo_web_sdk": "^2.7.1",
|
"@types/duo_web_sdk": "^2.7.1",
|
||||||
"@types/firefox-webext-browser": "^82.0.0",
|
"@types/firefox-webext-browser": "^82.0.0",
|
||||||
"@types/inquirer": "^8.2.1",
|
"@types/inquirer": "^8.2.1",
|
||||||
|
@ -12504,9 +12503,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/chrome": {
|
"node_modules/@types/chrome": {
|
||||||
"version": "0.0.139",
|
"version": "0.0.190",
|
||||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz",
|
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
|
||||||
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==",
|
"integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/filesystem": "*",
|
"@types/filesystem": "*",
|
||||||
|
@ -52084,9 +52083,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/chrome": {
|
"@types/chrome": {
|
||||||
"version": "0.0.139",
|
"version": "0.0.190",
|
||||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz",
|
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
|
||||||
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==",
|
"integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/filesystem": "*",
|
"@types/filesystem": "*",
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
"@storybook/angular": "^6.5.7",
|
"@storybook/angular": "^6.5.7",
|
||||||
"@storybook/builder-webpack5": "^6.5.7",
|
"@storybook/builder-webpack5": "^6.5.7",
|
||||||
"@storybook/manager-webpack5": "^6.5.7",
|
"@storybook/manager-webpack5": "^6.5.7",
|
||||||
"@types/chrome": "^0.0.139",
|
"@types/chrome": "^0.0.190",
|
||||||
"@types/duo_web_sdk": "^2.7.1",
|
"@types/duo_web_sdk": "^2.7.1",
|
||||||
"@types/firefox-webext-browser": "^82.0.0",
|
"@types/firefox-webext-browser": "^82.0.0",
|
||||||
"@types/inquirer": "^8.2.1",
|
"@types/inquirer": "^8.2.1",
|
||||||
|
@ -155,7 +155,6 @@
|
||||||
"big-integer": "^1.6.51",
|
"big-integer": "^1.6.51",
|
||||||
"bootstrap": "4.6.0",
|
"bootstrap": "4.6.0",
|
||||||
"braintree-web-drop-in": "^1.33.1",
|
"braintree-web-drop-in": "^1.33.1",
|
||||||
"browser-hrtime": "^1.1.8",
|
|
||||||
"bufferutil": "^4.0.6",
|
"bufferutil": "^4.0.6",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
|
|
Loading…
Reference in New Issue