diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ea0387fc6f..5471171a60 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1299,5 +1299,14 @@ }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Timeout Action Confirmation" + }, + "autoFillAndSave": { + "message": "Auto-fill and Save" + }, + "savedUri": { + "message": "Auto-filled and Saved Item" + }, + "autoFillSuccess": { + "message": "Auto-filled Item" } } \ No newline at end of file diff --git a/src/popup/vault/view.component.html b/src/popup/vault/view.component.html index 061ca996ac..e3f3da2165 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -268,6 +268,24 @@
+ +
+ + {{'autoFill' | i18n}} +
+
+ +
+ + {{'autoFillAndSave' | i18n}} +
+
diff --git a/src/popup/vault/view.component.ts b/src/popup/vault/view.component.ts index f74ed34fe0..d4fc9bdcae 100644 --- a/src/popup/vault/view.component.ts +++ b/src/popup/vault/view.component.ts @@ -14,14 +14,22 @@ import { CipherService } from 'jslib/abstractions/cipher.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { EventService } from 'jslib/abstractions/event.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { TokenService } from 'jslib/abstractions/token.service'; import { TotpService } from 'jslib/abstractions/totp.service'; import { UserService } from 'jslib/abstractions/user.service'; +import { Cipher } from 'jslib/models/domain'; +import { LoginUriView } from 'jslib/models/view'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/view.component'; +import { BrowserApi } from '../../browser/browserApi'; +import { AutofillService } from '../../services/abstractions/autofill.service'; +import { PopupUtilsService } from '../services/popup-utils.service'; + +const BroadcasterSubscriptionId = 'ChildViewComponent'; @Component({ selector: 'app-vault-view', @@ -29,6 +37,8 @@ import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/vie }) export class ViewComponent extends BaseViewComponent { showAttachments = true; + pageDetails: any[] = []; + inPopout: boolean = false; constructor(cipherService: CipherService, totpService: TotpService, tokenService: TokenService, i18nService: I18nService, @@ -37,13 +47,15 @@ export class ViewComponent extends BaseViewComponent { private router: Router, private location: Location, broadcasterService: BroadcasterService, ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, userService: UserService, - eventService: EventService) { + eventService: EventService, private autofillService: AutofillService, + private messagingService: MessagingService, private popupUtilsService: PopupUtilsService) { super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService); } ngOnInit() { this.showAttachments = !this.platformUtilsService.isEdge(); + this.inPopout = this.popupUtilsService.inPopout(window); const queryParamsSub = this.route.queryParams.subscribe(async (params) => { if (params.cipherId) { this.cipherId = params.cipherId; @@ -57,6 +69,29 @@ export class ViewComponent extends BaseViewComponent { } }); super.ngOnInit(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'collectPageDetailsResponse': + if (message.sender === BroadcasterSubscriptionId) { + this.pageDetails.push({ + frameId: message.webExtSender.frameId, + tab: message.tab, + details: message.details, + }); + } + break; + default: + break; + } + }); + }); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + super.ngOnDestroy(); } edit() { @@ -80,6 +115,91 @@ export class ViewComponent extends BaseViewComponent { }); } + async fillCipher() { + const didAutofill: boolean = await this.doAutofill(); + if (didAutofill) { + this.platformUtilsService.showToast('success', null, + this.i18nService.t('autoFillSuccess')); + } + } + + async fillCipherAndSave() { + const didAutofill: boolean = await this.doAutofill(); + + if (didAutofill) { + const tab = await BrowserApi.getTabFromCurrentWindow(); + if (!tab) { + throw new Error('No tab found.'); + } + + if (this.cipher.login.uris == null) { + this.cipher.login.uris = []; + } else { + if (this.cipher.login.uris.some((uri) => uri.uri === tab.url)) { + this.platformUtilsService.showToast('success', null, + this.i18nService.t('savedURI')); + return; + } + } + + const loginUri: LoginUriView = new LoginUriView(); + loginUri.uri = tab.url; + this.cipher.login.uris.push(loginUri); + + try { + const cipher: Cipher = await this.cipherService.encrypt(this.cipher); + await this.cipherService.saveWithServer(cipher); + this.cipher.id = cipher.id; + + this.platformUtilsService.showToast('success', null, + this.i18nService.t('savedURI')); + this.messagingService.send('editedCipher'); + } catch { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('unexpectedError')); + } + } + } + + async doAutofill() { + if (this.pageDetails == null || this.pageDetails.length === 0) { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('autofillError')); + return false; + } + + try { + this.totpCode = await this.autofillService.doAutoFill({ + cipher: this.cipher, + pageDetails: this.pageDetails, + doc: window.document, + }); + if (this.totpCode != null) { + this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); + } + } catch { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('autofillError')); + this.changeDetectorRef.detectChanges(); + return false; + } + + return true; + } + + async load() { + await super.load(); + + const tab = await BrowserApi.getTabFromCurrentWindow(); + + this.pageDetails = []; + BrowserApi.tabSendMessage(tab, { + command: 'collectPageDetails', + tab: tab, + sender: BroadcasterSubscriptionId, + }); + } + async restore() { if (!this.cipher.isDeleted) { return false;