[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",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"],
|
||||
"types": ["node", "jest", "chrome"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import MainBackground from "./background/main.background";
|
||||
import { onCommandListener } from "./listeners/onCommandListener";
|
||||
|
||||
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
||||
bitwardenMain.bootstrap().then(() => {
|
||||
// Finished bootstrapping
|
||||
});
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
chrome.commands.onCommand.addListener(onCommandListener);
|
||||
} else {
|
||||
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
||||
bitwardenMain.bootstrap().then(() => {
|
||||
// Finished bootstrapping
|
||||
});
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ export default class ContextMenusBackground {
|
|||
}
|
||||
|
||||
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
||||
if (typeof info.menuItemId !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = info.menuItemId.split("_")[1];
|
||||
|
||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||
|
|
|
@ -14,7 +14,10 @@ export default class WebRequestBackground {
|
|||
private cipherService: CipherService,
|
||||
private authService: AuthService
|
||||
) {
|
||||
this.webRequest = (window as any).chrome.webRequest;
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
if (manifest.manifest_version === 2) {
|
||||
this.webRequest = (window as any).chrome.webRequest;
|
||||
}
|
||||
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.*
|
||||
7. Remove "some useful globals" on window
|
||||
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) {
|
||||
|
@ -1037,6 +1038,11 @@
|
|||
fill(document, msg.fillScript);
|
||||
sendResponse();
|
||||
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": {
|
||||
"service_worker": "background.js"
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"action": {
|
||||
"default_icon": {
|
||||
|
|
|
@ -22,4 +22,5 @@ export default class AutofillField {
|
|||
selectInfo: any;
|
||||
maxLength: number;
|
||||
tagName: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
|
||||
export default abstract class AbstractChromeStorageService implements AbstractStorageService {
|
||||
protected abstract chromeStorageApi: any;
|
||||
protected abstract chromeStorageApi: chrome.storage.StorageArea;
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
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";
|
||||
|
||||
export abstract class AutofillService {
|
||||
getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[];
|
||||
doAutoFill: (options: any) => Promise<string>;
|
||||
doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise<string>;
|
||||
export interface PageDetail {
|
||||
frameId: number;
|
||||
tab: chrome.tabs.Tab;
|
||||
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 { 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 {
|
||||
AutoFillConstants,
|
||||
CreditCardAutoFillConstants,
|
||||
IdentityAutoFillConstants,
|
||||
} from "./autofillConstants";
|
||||
|
||||
export interface GenerateFillScriptOptions {
|
||||
skipUsernameOnlyFill: boolean;
|
||||
onlyEmptyFields: boolean;
|
||||
onlyVisibleFields: boolean;
|
||||
fillNewPassword: boolean;
|
||||
cipher: CipherView;
|
||||
}
|
||||
|
||||
export default class AutofillService implements AutofillServiceInterface {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
|
@ -31,10 +44,16 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
private logService: LogService
|
||||
) {}
|
||||
|
||||
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): any[] {
|
||||
const formData: any[] = [];
|
||||
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
|
||||
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) {
|
||||
return formData;
|
||||
}
|
||||
|
@ -64,16 +83,17 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
return formData;
|
||||
}
|
||||
|
||||
async doAutoFill(options: any) {
|
||||
let totpPromise: Promise<string> = null;
|
||||
async doAutoFill(options: AutoFillOptions) {
|
||||
const tab = options.tab;
|
||||
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
||||
throw new Error("Nothing to auto-fill.");
|
||||
}
|
||||
|
||||
let totpPromise: Promise<string> = null;
|
||||
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
let didAutofill = false;
|
||||
options.pageDetails.forEach((pd: any) => {
|
||||
options.pageDetails.forEach((pd) => {
|
||||
// make sure we're still on correct tab
|
||||
if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) {
|
||||
return;
|
||||
|
@ -138,12 +158,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
}
|
||||
}
|
||||
|
||||
async doAutoFillActiveTab(pageDetails: any, fromCommand: boolean) {
|
||||
const tab = await this.getActiveTab();
|
||||
if (!tab || !tab.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
async doAutoFillOnTab(pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean) {
|
||||
let cipher: CipherView;
|
||||
if (fromCommand) {
|
||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
||||
|
@ -186,9 +201,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
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
|
||||
|
||||
private async getActiveTab(): Promise<any> {
|
||||
private async getActiveTab(): Promise<chrome.tabs.Tab> {
|
||||
const tab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (!tab) {
|
||||
throw new Error("No tab found.");
|
||||
|
@ -197,7 +221,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
return tab;
|
||||
}
|
||||
|
||||
private generateFillScript(pageDetails: AutofillPageDetails, options: any): AutofillScript {
|
||||
private generateFillScript(
|
||||
pageDetails: AutofillPageDetails,
|
||||
options: GenerateFillScriptOptions
|
||||
): AutofillScript {
|
||||
if (!pageDetails || !options.cipher) {
|
||||
return null;
|
||||
}
|
||||
|
@ -209,13 +236,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
if (fields && fields.length) {
|
||||
const fieldNames: string[] = [];
|
||||
|
||||
fields.forEach((f: any) => {
|
||||
if (this.hasValue(f.name)) {
|
||||
fields.forEach((f) => {
|
||||
if (AutofillService.hasValue(f.name)) {
|
||||
fieldNames.push(f.name.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
pageDetails.fields.forEach((field: any) => {
|
||||
pageDetails.fields.forEach((field) => {
|
||||
// eslint-disable-next-line
|
||||
if (filledFields.hasOwnProperty(field.opid)) {
|
||||
return;
|
||||
|
@ -228,10 +255,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
const matchingIndex = this.findMatchingFieldIndex(field, fieldNames);
|
||||
if (matchingIndex > -1) {
|
||||
const matchingField: FieldView = fields[matchingIndex];
|
||||
let val;
|
||||
let val: string;
|
||||
if (matchingField.type === FieldType.Linked) {
|
||||
// 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 {
|
||||
val = matchingField.value;
|
||||
if (val == null && matchingField.type === FieldType.Boolean) {
|
||||
|
@ -240,7 +267,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
}
|
||||
|
||||
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(
|
||||
fillScript: AutofillScript,
|
||||
pageDetails: any,
|
||||
pageDetails: AutofillPageDetails,
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
options: any
|
||||
options: GenerateFillScriptOptions
|
||||
): AutofillScript {
|
||||
if (!options.cipher.login) {
|
||||
return null;
|
||||
|
@ -285,11 +312,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
if (!login.password || login.password === "") {
|
||||
// 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;
|
||||
}
|
||||
|
||||
let passwordFields = this.loadPasswordFields(
|
||||
let passwordFields = AutofillService.loadPasswordFields(
|
||||
pageDetails,
|
||||
false,
|
||||
false,
|
||||
|
@ -298,7 +325,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
);
|
||||
if (!passwordFields.length && !options.onlyVisibleFields) {
|
||||
// not able to find any viewable password fields. maybe there are some "hidden" ones?
|
||||
passwordFields = this.loadPasswordFields(
|
||||
passwordFields = AutofillService.loadPasswordFields(
|
||||
pageDetails,
|
||||
true,
|
||||
true,
|
||||
|
@ -362,11 +389,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
if (!passwordFields.length && !options.skipUsernameOnlyFill) {
|
||||
// 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 (
|
||||
f.viewable &&
|
||||
(f.type === "text" || f.type === "email" || f.type === "tel") &&
|
||||
this.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
|
||||
AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
|
||||
) {
|
||||
usernames.push(f);
|
||||
}
|
||||
|
@ -380,7 +407,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
}
|
||||
|
||||
filledFields[u.opid] = u;
|
||||
this.fillByOpid(fillScript, u, login.username);
|
||||
AutofillService.fillByOpid(fillScript, u, login.username);
|
||||
});
|
||||
|
||||
passwords.forEach((p) => {
|
||||
|
@ -390,18 +417,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private generateCardFillScript(
|
||||
fillScript: AutofillScript,
|
||||
pageDetails: any,
|
||||
pageDetails: AutofillPageDetails,
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
options: any
|
||||
options: GenerateFillScriptOptions
|
||||
): AutofillScript {
|
||||
if (!options.cipher.card) {
|
||||
return null;
|
||||
|
@ -409,8 +436,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
const fillFields: { [id: string]: AutofillField } = {};
|
||||
|
||||
pageDetails.fields.forEach((f: any) => {
|
||||
if (this.forCustomFieldsOnly(f)) {
|
||||
pageDetails.fields.forEach((f) => {
|
||||
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -429,7 +456,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
||||
if (
|
||||
!fillFields.cardholderName &&
|
||||
this.isFieldMatch(
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
CreditCardAutoFillConstants.CardHolderFieldNames,
|
||||
CreditCardAutoFillConstants.CardHolderFieldNameValues
|
||||
|
@ -439,7 +466,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
break;
|
||||
} else if (
|
||||
!fillFields.number &&
|
||||
this.isFieldMatch(
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
CreditCardAutoFillConstants.CardNumberFieldNames,
|
||||
CreditCardAutoFillConstants.CardNumberFieldNameValues
|
||||
|
@ -449,7 +476,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
break;
|
||||
} else if (
|
||||
!fillFields.exp &&
|
||||
this.isFieldMatch(
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
CreditCardAutoFillConstants.CardExpiryFieldNames,
|
||||
CreditCardAutoFillConstants.CardExpiryFieldNameValues
|
||||
|
@ -459,25 +486,25 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
break;
|
||||
} else if (
|
||||
!fillFields.expMonth &&
|
||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
|
||||
) {
|
||||
fillFields.expMonth = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.expYear &&
|
||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
|
||||
) {
|
||||
fillFields.expYear = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.code &&
|
||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
|
||||
) {
|
||||
fillFields.code = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.brand &&
|
||||
this.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
|
||||
) {
|
||||
fillFields.brand = f;
|
||||
break;
|
||||
|
@ -491,7 +518,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
this.makeScriptAction(fillScript, card, fillFields, filledFields, "code");
|
||||
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;
|
||||
|
||||
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
|
||||
|
@ -526,10 +553,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
}
|
||||
|
||||
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;
|
||||
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
|
||||
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;
|
||||
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);
|
||||
|
||||
let fullYear: string = card.expYear;
|
||||
|
@ -712,7 +743,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
return fillScript;
|
||||
}
|
||||
|
||||
private fieldAttrsContain(field: any, containsVal: string) {
|
||||
private fieldAttrsContain(field: AutofillField, containsVal: string) {
|
||||
if (!field) {
|
||||
return false;
|
||||
}
|
||||
|
@ -734,9 +765,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
private generateIdentityFillScript(
|
||||
fillScript: AutofillScript,
|
||||
pageDetails: any,
|
||||
pageDetails: AutofillPageDetails,
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
options: any
|
||||
options: GenerateFillScriptOptions
|
||||
): AutofillScript {
|
||||
if (!options.cipher.identity) {
|
||||
return null;
|
||||
|
@ -744,8 +775,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
const fillFields: { [id: string]: AutofillField } = {};
|
||||
|
||||
pageDetails.fields.forEach((f: any) => {
|
||||
if (this.forCustomFieldsOnly(f)) {
|
||||
pageDetails.fields.forEach((f) => {
|
||||
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -764,7 +795,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
||||
if (
|
||||
!fillFields.name &&
|
||||
this.isFieldMatch(
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
IdentityAutoFillConstants.FullNameFieldNames,
|
||||
IdentityAutoFillConstants.FullNameFieldNameValues
|
||||
|
@ -774,37 +805,37 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
break;
|
||||
} else if (
|
||||
!fillFields.firstName &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
||||
) {
|
||||
fillFields.firstName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.middleName &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
||||
) {
|
||||
fillFields.middleName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.lastName &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
||||
) {
|
||||
fillFields.lastName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.title &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
||||
) {
|
||||
fillFields.title = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.email &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
||||
) {
|
||||
fillFields.email = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address &&
|
||||
this.isFieldMatch(
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
IdentityAutoFillConstants.AddressFieldNames,
|
||||
IdentityAutoFillConstants.AddressFieldNameValues
|
||||
|
@ -814,61 +845,61 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
break;
|
||||
} else if (
|
||||
!fillFields.address1 &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
||||
) {
|
||||
fillFields.address1 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address2 &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
||||
) {
|
||||
fillFields.address2 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address3 &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
||||
) {
|
||||
fillFields.address3 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.postalCode &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
||||
) {
|
||||
fillFields.postalCode = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.city &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
||||
) {
|
||||
fillFields.city = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.state &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
||||
) {
|
||||
fillFields.state = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.country &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
||||
) {
|
||||
fillFields.country = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.phone &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
||||
) {
|
||||
fillFields.phone = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.username &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
||||
) {
|
||||
fillFields.username = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.company &&
|
||||
this.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
||||
) {
|
||||
fillFields.company = f;
|
||||
break;
|
||||
|
@ -923,16 +954,16 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
if (fillFields.name && (identity.firstName || identity.lastName)) {
|
||||
let fullName = "";
|
||||
if (this.hasValue(identity.firstName)) {
|
||||
if (AutofillService.hasValue(identity.firstName)) {
|
||||
fullName = identity.firstName;
|
||||
}
|
||||
if (this.hasValue(identity.middleName)) {
|
||||
if (AutofillService.hasValue(identity.middleName)) {
|
||||
if (fullName !== "") {
|
||||
fullName += " ";
|
||||
}
|
||||
fullName += identity.middleName;
|
||||
}
|
||||
if (this.hasValue(identity.lastName)) {
|
||||
if (AutofillService.hasValue(identity.lastName)) {
|
||||
if (fullName !== "") {
|
||||
fullName += " ";
|
||||
}
|
||||
|
@ -942,18 +973,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
|
||||
}
|
||||
|
||||
if (fillFields.address && this.hasValue(identity.address1)) {
|
||||
if (fillFields.address && AutofillService.hasValue(identity.address1)) {
|
||||
let address = "";
|
||||
if (this.hasValue(identity.address1)) {
|
||||
if (AutofillService.hasValue(identity.address1)) {
|
||||
address = identity.address1;
|
||||
}
|
||||
if (this.hasValue(identity.address2)) {
|
||||
if (AutofillService.hasValue(identity.address2)) {
|
||||
if (address !== "") {
|
||||
address += ", ";
|
||||
}
|
||||
address += identity.address2;
|
||||
}
|
||||
if (this.hasValue(identity.address3)) {
|
||||
if (AutofillService.hasValue(identity.address3)) {
|
||||
if (address !== "") {
|
||||
address += ", ";
|
||||
}
|
||||
|
@ -970,7 +1001,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
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
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
@ -1011,12 +1046,15 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
filledFields: { [id: string]: AutofillField }
|
||||
) {
|
||||
let doFill = false;
|
||||
if (this.hasValue(dataValue) && field) {
|
||||
if (AutofillService.hasValue(dataValue) && field) {
|
||||
if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) {
|
||||
for (let i = 0; i < field.selectInfo.options.length; i++) {
|
||||
const option = field.selectInfo.options[i];
|
||||
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;
|
||||
if (option.length > 1) {
|
||||
dataValue = option[1];
|
||||
|
@ -1036,11 +1074,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
if (doFill) {
|
||||
filledFields[field.opid] = field;
|
||||
this.fillByOpid(fillScript, field, dataValue);
|
||||
AutofillService.fillByOpid(fillScript, field, dataValue);
|
||||
}
|
||||
}
|
||||
|
||||
private loadPasswordFields(
|
||||
static loadPasswordFields(
|
||||
pageDetails: AutofillPageDetails,
|
||||
canBeHidden: boolean,
|
||||
canBeReadOnly: boolean,
|
||||
|
@ -1049,7 +1087,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
) {
|
||||
const arr: AutofillField[] = [];
|
||||
pageDetails.fields.forEach((f) => {
|
||||
if (this.forCustomFieldsOnly(f)) {
|
||||
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1111,7 +1149,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
let usernameField: AutofillField = null;
|
||||
for (let i = 0; i < pageDetails.fields.length; i++) {
|
||||
const f = pageDetails.fields[i];
|
||||
if (this.forCustomFieldsOnly(f)) {
|
||||
if (AutofillService.forCustomFieldsOnly(f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1195,7 +1233,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
|
||||
private fieldPropertyIsMatch(field: any, property: string, name: string): boolean {
|
||||
let fieldVal = field[property] as string;
|
||||
if (!this.hasValue(fieldVal)) {
|
||||
if (!AutofillService.hasValue(fieldVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1227,33 +1265,45 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
return fieldVal.toLowerCase() === name;
|
||||
}
|
||||
|
||||
private fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
|
||||
if (this.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
|
||||
static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
|
||||
if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
|
||||
return true;
|
||||
}
|
||||
if (this.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
|
||||
if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
|
||||
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;
|
||||
}
|
||||
if (this.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
|
||||
if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 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 === "") {
|
||||
return false;
|
||||
}
|
||||
|
@ -1272,11 +1322,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
return false;
|
||||
}
|
||||
|
||||
private hasValue(str: string): boolean {
|
||||
static hasValue(str: string): boolean {
|
||||
return str && str !== "";
|
||||
}
|
||||
|
||||
private setFillScriptForFocus(
|
||||
static setFillScriptForFocus(
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
fillScript: AutofillScript
|
||||
): AutofillScript {
|
||||
|
@ -1304,7 +1354,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
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) {
|
||||
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]);
|
||||
}
|
||||
|
||||
private forCustomFieldsOnly(field: AutofillField): boolean {
|
||||
static forCustomFieldsOnly(field: AutofillField): boolean {
|
||||
return field.tagName === "span";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
||||
|
||||
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";
|
||||
|
||||
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";
|
||||
},
|
||||
},
|
||||
commons2: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "vendor",
|
||||
chunks: (chunk) => {
|
||||
return chunk.name === "background";
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -209,4 +202,16 @@ const config = {
|
|||
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;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
|||
templateUrl: "verify-email.component.html",
|
||||
})
|
||||
export class VerifyEmailComponent {
|
||||
actionPromise: Promise<any>;
|
||||
actionPromise: Promise<unknown>;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
|
|
|
@ -78,25 +78,4 @@ describe("ConsoleLogService", () => {
|
|||
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;
|
||||
error: (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 */
|
||||
import * as tldjs from "tldjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
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 {
|
||||
static inited = false;
|
||||
static isNode = false;
|
||||
static isBrowser = true;
|
||||
static isMobileBrowser = false;
|
||||
static isAppleMobileBrowser = false;
|
||||
static global: any = null;
|
||||
static global: typeof global = null;
|
||||
static tldEndingRegex =
|
||||
/.*\.(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.
|
||||
|
@ -29,16 +40,25 @@ export class Utils {
|
|||
(process as any).release != null &&
|
||||
(process as any).release.name === "node";
|
||||
Utils.isBrowser = typeof window !== "undefined";
|
||||
|
||||
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(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 {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "base64"));
|
||||
} else {
|
||||
const binaryString = window.atob(str);
|
||||
const binaryString = Utils.global.atob(str);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
|
@ -93,7 +113,7 @@ export class Utils {
|
|||
for (let i = 0; i < bytes.byteLength; 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) {
|
||||
return Buffer.from(utfStr, "utf8").toString("base64");
|
||||
} else {
|
||||
return decodeURIComponent(escape(window.btoa(utfStr)));
|
||||
return decodeURIComponent(escape(Utils.global.btoa(utfStr)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +189,7 @@ export class Utils {
|
|||
if (Utils.isNode) {
|
||||
return Buffer.from(b64Str, "base64").toString("utf8");
|
||||
} 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);
|
||||
} else if (typeof URL === "function") {
|
||||
return new URL(uriString);
|
||||
} else if (window != null) {
|
||||
} else if (typeof window !== "undefined") {
|
||||
const hasProtocol = uriString.indexOf("://") > -1;
|
||||
if (!hasProtocol && uriString.indexOf(".") > -1) {
|
||||
uriString = "http://" + uriString;
|
||||
|
|
|
@ -48,7 +48,7 @@ export class Attachment extends Domain {
|
|||
|
||||
if (this.key != null) {
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
|
|
|
@ -104,7 +104,7 @@ export class EncString implements IEncrypted {
|
|||
}
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
|
|
|
@ -341,7 +341,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
throw new Error("No key.");
|
||||
}
|
||||
|
||||
const promises: any[] = [];
|
||||
const promises: Promise<number>[] = [];
|
||||
const ciphers = await this.getAll();
|
||||
ciphers.forEach(async (cipher) => {
|
||||
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 { LogLevelType } from "../enums/logLevelType";
|
||||
|
||||
|
@ -56,17 +54,4 @@ export class ConsoleLogService implements LogServiceAbstraction {
|
|||
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";
|
||||
|
||||
export class SearchService implements SearchServiceAbstraction {
|
||||
private static registeredPipeline = false;
|
||||
|
||||
indexedEntityId?: string = null;
|
||||
private indexing = false;
|
||||
private index: lunr.Index = null;
|
||||
|
@ -31,8 +33,13 @@ export class SearchService implements SearchServiceAbstraction {
|
|||
}
|
||||
});
|
||||
|
||||
//register lunr pipeline function
|
||||
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
|
||||
// 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
|
||||
lunr.Pipeline.registerFunction(this.normalizeAccentsPipelineFunction, "normalizeAccents");
|
||||
}
|
||||
}
|
||||
|
||||
clearIndex(): void {
|
||||
|
@ -54,7 +61,6 @@ export class SearchService implements SearchServiceAbstraction {
|
|||
return;
|
||||
}
|
||||
|
||||
this.logService.time("search indexing");
|
||||
this.indexing = true;
|
||||
this.indexedEntityId = indexedEntityId;
|
||||
this.index = null;
|
||||
|
@ -95,7 +101,7 @@ export class SearchService implements SearchServiceAbstraction {
|
|||
|
||||
this.indexing = false;
|
||||
|
||||
this.logService.timeEnd("search indexing");
|
||||
this.logService.info("Finished search indexing");
|
||||
}
|
||||
|
||||
async searchCiphers(
|
||||
|
|
|
@ -610,7 +610,7 @@ export class StateService<
|
|||
);
|
||||
}
|
||||
|
||||
@withPrototypeForArrayMembers(CipherView)
|
||||
@withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
|
||||
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
|
|
|
@ -9,7 +9,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
|||
private crypto: Crypto;
|
||||
private subtle: SubtleCrypto;
|
||||
|
||||
constructor(win: Window) {
|
||||
constructor(win: Window | typeof global) {
|
||||
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
|
||||
this.subtle =
|
||||
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
"big-integer": "^1.6.51",
|
||||
"bootstrap": "4.6.0",
|
||||
"braintree-web-drop-in": "^1.33.1",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"bufferutil": "^4.0.6",
|
||||
"chalk": "^4.1.0",
|
||||
"commander": "^7.2.0",
|
||||
|
@ -83,7 +82,7 @@
|
|||
"@storybook/angular": "^6.5.7",
|
||||
"@storybook/builder-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/firefox-webext-browser": "^82.0.0",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
|
@ -12504,9 +12503,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/chrome": {
|
||||
"version": "0.0.139",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz",
|
||||
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==",
|
||||
"version": "0.0.190",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
|
||||
"integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
|
@ -52084,9 +52083,9 @@
|
|||
}
|
||||
},
|
||||
"@types/chrome": {
|
||||
"version": "0.0.139",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.139.tgz",
|
||||
"integrity": "sha512-YZDKFlSVGFp4zldJlO+PUpxMH8N9vLke0fD6K9PA+TzXxPXu8LBLo5X2dzlOs2N/n+uMdI1lw7OPT1Emop10lQ==",
|
||||
"version": "0.0.190",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.190.tgz",
|
||||
"integrity": "sha512-lCwwIBfaD+PhG62qFB46mIBpD+xBIa+PedNB24KR9YnmJ0Zn9h0OwP1NQBhI8Cbu1rKwTQHTxhs7GhWGyUvinw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/filesystem": "*",
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"@storybook/angular": "^6.5.7",
|
||||
"@storybook/builder-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/firefox-webext-browser": "^82.0.0",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
|
@ -155,7 +155,6 @@
|
|||
"big-integer": "^1.6.51",
|
||||
"bootstrap": "4.6.0",
|
||||
"braintree-web-drop-in": "^1.33.1",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"bufferutil": "^4.0.6",
|
||||
"chalk": "^4.1.0",
|
||||
"commander": "^7.2.0",
|
||||
|
|
Loading…
Reference in New Issue