Run prettier
This commit is contained in:
parent
40cbf61ab7
commit
42bb8cd5f8
|
@ -1,66 +1,71 @@
|
||||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
import { StateService } from '../services/abstractions/state.service';
|
import { StateService } from "../services/abstractions/state.service";
|
||||||
|
|
||||||
const IdleInterval = 60 * 5; // 5 minutes
|
const IdleInterval = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
export default class IdleBackground {
|
export default class IdleBackground {
|
||||||
private idle: any;
|
private idle: any;
|
||||||
private idleTimer: number = null;
|
private idleTimer: number = null;
|
||||||
private idleState = 'active';
|
private idleState = "active";
|
||||||
|
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
|
constructor(
|
||||||
private notificationsService: NotificationsService) {
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
private stateService: StateService,
|
||||||
|
private notificationsService: NotificationsService
|
||||||
|
) {
|
||||||
|
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.idle) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
const idleHandler = (newState: string) => {
|
||||||
if (!this.idle) {
|
if (newState === "active") {
|
||||||
return;
|
this.notificationsService.reconnectFromActivity();
|
||||||
}
|
} else {
|
||||||
|
this.notificationsService.disconnectFromInactivity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
|
||||||
|
this.idle.setDetectionInterval(IdleInterval);
|
||||||
|
this.idle.onStateChanged.addListener(idleHandler);
|
||||||
|
} else {
|
||||||
|
this.pollIdle(idleHandler);
|
||||||
|
}
|
||||||
|
|
||||||
const idleHandler = (newState: string) => {
|
if (this.idle.onStateChanged) {
|
||||||
if (newState === 'active') {
|
this.idle.onStateChanged.addListener(async (newState: string) => {
|
||||||
this.notificationsService.reconnectFromActivity();
|
if (newState === "locked") {
|
||||||
|
// If the screen is locked or the screensaver activates
|
||||||
|
const timeout = await this.stateService.getVaultTimeout();
|
||||||
|
if (timeout === -2) {
|
||||||
|
// On System Lock vault timeout option
|
||||||
|
const action = await this.stateService.getVaultTimeoutAction();
|
||||||
|
if (action === "logOut") {
|
||||||
|
await this.vaultTimeoutService.logOut();
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.disconnectFromInactivity();
|
await this.vaultTimeoutService.lock(true);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
|
|
||||||
this.idle.setDetectionInterval(IdleInterval);
|
|
||||||
this.idle.onStateChanged.addListener(idleHandler);
|
|
||||||
} else {
|
|
||||||
this.pollIdle(idleHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.idle.onStateChanged) {
|
|
||||||
this.idle.onStateChanged.addListener(async (newState: string) => {
|
|
||||||
if (newState === 'locked') { // If the screen is locked or the screensaver activates
|
|
||||||
const timeout = await this.stateService.getVaultTimeout();
|
|
||||||
if (timeout === -2) { // On System Lock vault timeout option
|
|
||||||
const action = await this.stateService.getVaultTimeoutAction();
|
|
||||||
if (action === 'logOut') {
|
|
||||||
await this.vaultTimeoutService.logOut();
|
|
||||||
} else {
|
|
||||||
await this.vaultTimeoutService.lock(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private pollIdle(handler: (newState: string) => void) {
|
private pollIdle(handler: (newState: string) => void) {
|
||||||
if (this.idleTimer != null) {
|
if (this.idleTimer != null) {
|
||||||
window.clearTimeout(this.idleTimer);
|
window.clearTimeout(this.idleTimer);
|
||||||
this.idleTimer = null;
|
this.idleTimer = null;
|
||||||
}
|
|
||||||
this.idle.queryState(IdleInterval, (state: string) => {
|
|
||||||
if (state !== this.idleState) {
|
|
||||||
this.idleState = state;
|
|
||||||
handler(state);
|
|
||||||
}
|
|
||||||
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.idle.queryState(IdleInterval, (state: string) => {
|
||||||
|
if (state !== this.idleState) {
|
||||||
|
this.idleState = state;
|
||||||
|
handler(state);
|
||||||
|
}
|
||||||
|
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,331 +1,346 @@
|
||||||
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
import RuntimeBackground from './runtime.background';
|
import RuntimeBackground from "./runtime.background";
|
||||||
|
|
||||||
const MessageValidTimeout = 10 * 1000;
|
const MessageValidTimeout = 10 * 1000;
|
||||||
const EncryptionAlgorithm = 'sha1';
|
const EncryptionAlgorithm = "sha1";
|
||||||
|
|
||||||
export class NativeMessagingBackground {
|
export class NativeMessagingBackground {
|
||||||
private connected = false;
|
private connected = false;
|
||||||
private connecting: boolean;
|
private connecting: boolean;
|
||||||
private port: browser.runtime.Port | chrome.runtime.Port;
|
private port: browser.runtime.Port | chrome.runtime.Port;
|
||||||
|
|
||||||
private resolver: any = null;
|
private resolver: any = null;
|
||||||
private privateKey: ArrayBuffer = null;
|
private privateKey: ArrayBuffer = null;
|
||||||
private publicKey: ArrayBuffer = null;
|
private publicKey: ArrayBuffer = null;
|
||||||
private secureSetupResolve: any = null;
|
private secureSetupResolve: any = null;
|
||||||
private sharedSecret: SymmetricCryptoKey;
|
private sharedSecret: SymmetricCryptoKey;
|
||||||
private appId: string;
|
private appId: string;
|
||||||
private validatingFingerprint: boolean;
|
private validatingFingerprint: boolean;
|
||||||
|
|
||||||
constructor(private cryptoService: CryptoService,
|
constructor(
|
||||||
private cryptoFunctionService: CryptoFunctionService,
|
private cryptoService: CryptoService,
|
||||||
private runtimeBackground: RuntimeBackground, private i18nService: I18nService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
private messagingService: MessagingService, private appIdService: AppIdService,
|
private runtimeBackground: RuntimeBackground,
|
||||||
private platformUtilsService: PlatformUtilsService, private stateService: StateService) {
|
private i18nService: I18nService,
|
||||||
this.stateService.setBiometricFingerprintValidated(false);
|
private messagingService: MessagingService,
|
||||||
|
private appIdService: AppIdService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
|
this.stateService.setBiometricFingerprintValidated(false);
|
||||||
|
|
||||||
if (chrome?.permissions?.onAdded) {
|
if (chrome?.permissions?.onAdded) {
|
||||||
// Reload extension to activate nativeMessaging
|
// Reload extension to activate nativeMessaging
|
||||||
chrome.permissions.onAdded.addListener(permissions => {
|
chrome.permissions.onAdded.addListener((permissions) => {
|
||||||
BrowserApi.reloadExtension(null);
|
BrowserApi.reloadExtension(null);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
this.appId = await this.appIdService.getAppId();
|
this.appId = await this.appIdService.getAppId();
|
||||||
this.stateService.setBiometricFingerprintValidated(false);
|
this.stateService.setBiometricFingerprintValidated(false);
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
|
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
||||||
|
|
||||||
this.connecting = true;
|
this.connecting = true;
|
||||||
|
|
||||||
const connectedCallback = () => {
|
const connectedCallback = () => {
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
this.connecting = false;
|
this.connecting = false;
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safari has a bundled native component which is always available, no need to
|
// Safari has a bundled native component which is always available, no need to
|
||||||
// check if the desktop app is running.
|
// check if the desktop app is running.
|
||||||
if (this.platformUtilsService.isSafari()) {
|
if (this.platformUtilsService.isSafari()) {
|
||||||
connectedCallback();
|
connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.onMessage.addListener(async (message: any) => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "connected":
|
||||||
|
connectedCallback();
|
||||||
|
break;
|
||||||
|
case "disconnected":
|
||||||
|
if (this.connecting) {
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("startDesktopDesc"),
|
||||||
|
title: this.i18nService.t("startDesktopTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
this.connected = false;
|
||||||
|
this.port.disconnect();
|
||||||
|
break;
|
||||||
|
case "setupEncryption":
|
||||||
|
// Ignore since it belongs to another device
|
||||||
|
if (message.appId !== this.appId) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.port.onMessage.addListener(async (message: any) => {
|
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
|
||||||
switch (message.command) {
|
const decrypted = await this.cryptoFunctionService.rsaDecrypt(
|
||||||
case 'connected':
|
encrypted.buffer,
|
||||||
connectedCallback();
|
this.privateKey,
|
||||||
break;
|
EncryptionAlgorithm
|
||||||
case 'disconnected':
|
);
|
||||||
if (this.connecting) {
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: this.i18nService.t('startDesktopDesc'),
|
|
||||||
title: this.i18nService.t('startDesktopTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
this.connected = false;
|
|
||||||
this.port.disconnect();
|
|
||||||
break;
|
|
||||||
case 'setupEncryption':
|
|
||||||
// Ignore since it belongs to another device
|
|
||||||
if (message.appId !== this.appId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
|
if (this.validatingFingerprint) {
|
||||||
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm);
|
this.validatingFingerprint = false;
|
||||||
|
this.stateService.setBiometricFingerprintValidated(true);
|
||||||
if (this.validatingFingerprint) {
|
}
|
||||||
this.validatingFingerprint = false;
|
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||||
this.stateService.setBiometricFingerprintValidated(true);
|
this.secureSetupResolve();
|
||||||
}
|
break;
|
||||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
case "invalidateEncryption":
|
||||||
this.secureSetupResolve();
|
// Ignore since it belongs to another device
|
||||||
break;
|
if (message.appId !== this.appId) {
|
||||||
case 'invalidateEncryption':
|
return;
|
||||||
// Ignore since it belongs to another device
|
}
|
||||||
if (message.appId !== this.appId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sharedSecret = null;
|
|
||||||
this.privateKey = null;
|
|
||||||
this.connected = false;
|
|
||||||
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
|
|
||||||
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'verifyFingerprint': {
|
|
||||||
if (this.sharedSecret == null) {
|
|
||||||
this.validatingFingerprint = true;
|
|
||||||
this.showFingerprintDialog();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'wrongUserId':
|
|
||||||
this.showWrongUserDialog();
|
|
||||||
default:
|
|
||||||
// Ignore since it belongs to another device
|
|
||||||
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onMessage(message.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.port.onDisconnect.addListener((p: any) => {
|
|
||||||
let error;
|
|
||||||
if (BrowserApi.isWebExtensionsApi) {
|
|
||||||
error = p.error.message;
|
|
||||||
} else {
|
|
||||||
error = chrome.runtime.lastError.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error != null) {
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: this.i18nService.t('desktopIntegrationDisabledDesc'),
|
|
||||||
title: this.i18nService.t('desktopIntegrationDisabledTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.sharedSecret = null;
|
|
||||||
this.privateKey = null;
|
|
||||||
this.connected = false;
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showWrongUserDialog() {
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
|
|
||||||
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(message: any) {
|
|
||||||
if (!this.connected) {
|
|
||||||
await this.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.platformUtilsService.isSafari()) {
|
|
||||||
this.postMessage(message);
|
|
||||||
} else {
|
|
||||||
this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async encryptMessage(message: any) {
|
|
||||||
if (this.sharedSecret == null) {
|
|
||||||
await this.secureCommunication();
|
|
||||||
}
|
|
||||||
|
|
||||||
message.timestamp = Date.now();
|
|
||||||
|
|
||||||
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
getResponse(): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.resolver = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private postMessage(message: any) {
|
|
||||||
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
|
|
||||||
try {
|
|
||||||
this.port.postMessage(message);
|
|
||||||
} catch (e) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.error("NativeMessaging port disconnected, disconnecting.");
|
|
||||||
|
|
||||||
this.sharedSecret = null;
|
this.sharedSecret = null;
|
||||||
this.privateKey = null;
|
this.privateKey = null;
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
|
|
||||||
this.messagingService.send('showDialog', {
|
this.messagingService.send("showDialog", {
|
||||||
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
|
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
|
||||||
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
|
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
|
||||||
confirmText: this.i18nService.t('ok'),
|
confirmText: this.i18nService.t("ok"),
|
||||||
type: 'error',
|
type: "error",
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
case "verifyFingerprint": {
|
||||||
|
if (this.sharedSecret == null) {
|
||||||
|
this.validatingFingerprint = true;
|
||||||
|
this.showFingerprintDialog();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "wrongUserId":
|
||||||
|
this.showWrongUserDialog();
|
||||||
|
default:
|
||||||
|
// Ignore since it belongs to another device
|
||||||
|
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onMessage(message.message);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.port.onDisconnect.addListener((p: any) => {
|
||||||
|
let error;
|
||||||
|
if (BrowserApi.isWebExtensionsApi) {
|
||||||
|
error = p.error.message;
|
||||||
|
} else {
|
||||||
|
error = chrome.runtime.lastError.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("desktopIntegrationDisabledDesc"),
|
||||||
|
title: this.i18nService.t("desktopIntegrationDisabledTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.sharedSecret = null;
|
||||||
|
this.privateKey = null;
|
||||||
|
this.connected = false;
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showWrongUserDialog() {
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("nativeMessagingWrongUserDesc"),
|
||||||
|
title: this.i18nService.t("nativeMessagingWrongUserTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: any) {
|
||||||
|
if (!this.connected) {
|
||||||
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(rawMessage: any) {
|
if (this.platformUtilsService.isSafari()) {
|
||||||
let message = rawMessage;
|
this.postMessage(message);
|
||||||
if (!this.platformUtilsService.isSafari()) {
|
} else {
|
||||||
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async encryptMessage(message: any) {
|
||||||
|
if (this.sharedSecret == null) {
|
||||||
|
await this.secureCommunication();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.timestamp = Date.now();
|
||||||
|
|
||||||
|
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse(): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.resolver = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private postMessage(message: any) {
|
||||||
|
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
|
||||||
|
try {
|
||||||
|
this.port.postMessage(message);
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error("NativeMessaging port disconnected, disconnecting.");
|
||||||
|
|
||||||
|
this.sharedSecret = null;
|
||||||
|
this.privateKey = null;
|
||||||
|
this.connected = false;
|
||||||
|
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
|
||||||
|
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onMessage(rawMessage: any) {
|
||||||
|
let message = rawMessage;
|
||||||
|
if (!this.platformUtilsService.isSafari()) {
|
||||||
|
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error("NativeMessage is to old, ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.command) {
|
||||||
|
case "biometricUnlock":
|
||||||
|
await this.stateService.setBiometricAwaitingAcceptance(null);
|
||||||
|
|
||||||
|
if (message.response === "not enabled") {
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("biometricsNotEnabledDesc"),
|
||||||
|
title: this.i18nService.t("biometricsNotEnabledTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
} else if (message.response === "not supported") {
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: this.i18nService.t("biometricsNotSupportedDesc"),
|
||||||
|
title: this.i18nService.t("biometricsNotSupportedTitle"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
const enabled = await this.stateService.getBiometricUnlock();
|
||||||
|
if (enabled === null || enabled === false) {
|
||||||
|
if (message.response === "unlocked") {
|
||||||
|
await this.stateService.setBiometricUnlock(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore unlock if already unlockeded
|
||||||
|
if (!this.stateService.getBiometricLocked()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.response === "unlocked") {
|
||||||
|
await this.cryptoService.setKey(
|
||||||
|
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify key is correct by attempting to decrypt a secret
|
||||||
|
try {
|
||||||
|
await this.cryptoService.getFingerprint(await this.stateService.getUserId());
|
||||||
|
} catch (e) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
console.error('NativeMessage is to old, ignoring.');
|
console.error("Unable to verify key:", e);
|
||||||
return;
|
await this.cryptoService.clearKey();
|
||||||
}
|
this.showWrongUserDialog();
|
||||||
|
|
||||||
switch (message.command) {
|
message = false;
|
||||||
case 'biometricUnlock':
|
break;
|
||||||
await this.stateService.setBiometricAwaitingAcceptance(null);
|
}
|
||||||
|
|
||||||
if (message.response === 'not enabled') {
|
await this.stateService.setBiometricLocked(false);
|
||||||
this.messagingService.send('showDialog', {
|
this.runtimeBackground.processMessage({ command: "unlocked" }, null, null);
|
||||||
text: this.i18nService.t('biometricsNotEnabledDesc'),
|
|
||||||
title: this.i18nService.t('biometricsNotEnabledTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
} else if (message.response === 'not supported') {
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: this.i18nService.t('biometricsNotSupportedDesc'),
|
|
||||||
title: this.i18nService.t('biometricsNotSupportedTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabled = await this.stateService.getBiometricUnlock();
|
|
||||||
if (enabled === null || enabled === false) {
|
|
||||||
if (message.response === 'unlocked') {
|
|
||||||
await this.stateService.setBiometricUnlock(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore unlock if already unlockeded
|
|
||||||
if (!this.stateService.getBiometricLocked()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.response === 'unlocked') {
|
|
||||||
await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer));
|
|
||||||
|
|
||||||
// Verify key is correct by attempting to decrypt a secret
|
|
||||||
try {
|
|
||||||
await this.cryptoService.getFingerprint(await this.stateService.getUserId());
|
|
||||||
} catch (e) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.error('Unable to verify key:', e);
|
|
||||||
await this.cryptoService.clearKey();
|
|
||||||
this.showWrongUserDialog();
|
|
||||||
|
|
||||||
message = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.stateService.setBiometricLocked(false);
|
|
||||||
this.runtimeBackground.processMessage({ command: 'unlocked' }, null, null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.error('NativeMessage, got unknown command: ', message.command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.resolver) {
|
|
||||||
this.resolver(message);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error("NativeMessage, got unknown command: ", message.command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async secureCommunication() {
|
if (this.resolver) {
|
||||||
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
this.resolver(message);
|
||||||
this.publicKey = publicKey;
|
}
|
||||||
this.privateKey = privateKey;
|
}
|
||||||
|
|
||||||
this.sendUnencrypted({
|
private async secureCommunication() {
|
||||||
command: 'setupEncryption',
|
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||||
publicKey: Utils.fromBufferToB64(publicKey),
|
this.publicKey = publicKey;
|
||||||
userId: await this.stateService.getUserId(),
|
this.privateKey = privateKey;
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => this.secureSetupResolve = resolve);
|
this.sendUnencrypted({
|
||||||
|
command: "setupEncryption",
|
||||||
|
publicKey: Utils.fromBufferToB64(publicKey),
|
||||||
|
userId: await this.stateService.getUserId(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => (this.secureSetupResolve = resolve));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendUnencrypted(message: any) {
|
||||||
|
if (!this.connected) {
|
||||||
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendUnencrypted(message: any) {
|
message.timestamp = Date.now();
|
||||||
if (!this.connected) {
|
|
||||||
await this.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
message.timestamp = Date.now();
|
this.postMessage({ appId: this.appId, message: message });
|
||||||
|
}
|
||||||
|
|
||||||
this.postMessage({ appId: this.appId, message: message });
|
private async showFingerprintDialog() {
|
||||||
}
|
const fingerprint = (
|
||||||
|
await this.cryptoService.getFingerprint(await this.stateService.getUserId(), this.publicKey)
|
||||||
|
).join(" ");
|
||||||
|
|
||||||
private async showFingerprintDialog() {
|
this.messagingService.send("showDialog", {
|
||||||
const fingerprint = (await this.cryptoService.getFingerprint(await this.stateService.getUserId(), this.publicKey)).join(' ');
|
html: `${this.i18nService.t(
|
||||||
|
"desktopIntegrationVerificationText"
|
||||||
this.messagingService.send('showDialog', {
|
)}<br><br><strong>${fingerprint}</strong>`,
|
||||||
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
|
title: this.i18nService.t("desktopSyncVerificationTitle"),
|
||||||
title: this.i18nService.t('desktopSyncVerificationTitle'),
|
confirmText: this.i18nService.t("ok"),
|
||||||
confirmText: this.i18nService.t('ok'),
|
type: "warning",
|
||||||
type: 'warning',
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,505 +28,423 @@ import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNoti
|
||||||
import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
|
import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
|
||||||
|
|
||||||
export default class NotificationBackground {
|
export default class NotificationBackground {
|
||||||
private notificationQueue: (
|
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
|
||||||
| AddLoginQueueMessage
|
|
||||||
| AddChangePasswordQueueMessage
|
|
||||||
)[] = [];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private main: MainBackground,
|
private main: MainBackground,
|
||||||
private autofillService: AutofillService,
|
private autofillService: AutofillService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
private stateService: StateService
|
private stateService: StateService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (chrome.runtime == null) {
|
if (chrome.runtime == null) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
BrowserApi.messageListener(
|
|
||||||
"notification.background",
|
|
||||||
async (msg: any, sender: chrome.runtime.MessageSender) => {
|
|
||||||
await this.processMessage(msg, sender);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cleanupNotificationQueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
BrowserApi.messageListener(
|
||||||
switch (msg.command) {
|
"notification.background",
|
||||||
case "unlockCompleted":
|
async (msg: any, sender: chrome.runtime.MessageSender) => {
|
||||||
if (msg.data.target !== "notification.background") {
|
await this.processMessage(msg, sender);
|
||||||
return;
|
}
|
||||||
}
|
);
|
||||||
await this.processMessage(
|
|
||||||
msg.data.commandToRetry.msg,
|
this.cleanupNotificationQueue();
|
||||||
msg.data.commandToRetry.sender
|
}
|
||||||
);
|
|
||||||
break;
|
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
||||||
case "bgGetDataForTab":
|
switch (msg.command) {
|
||||||
await this.getDataForTab(sender.tab, msg.responseCommand);
|
case "unlockCompleted":
|
||||||
break;
|
if (msg.data.target !== "notification.background") {
|
||||||
case "bgCloseNotificationBar":
|
return;
|
||||||
await BrowserApi.tabSendMessageData(
|
|
||||||
sender.tab,
|
|
||||||
"closeNotificationBar"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "bgAdjustNotificationBar":
|
|
||||||
await BrowserApi.tabSendMessageData(
|
|
||||||
sender.tab,
|
|
||||||
"adjustNotificationBar",
|
|
||||||
msg.data
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "bgAddLogin":
|
|
||||||
await this.addLogin(msg.login, sender.tab);
|
|
||||||
break;
|
|
||||||
case "bgChangedPassword":
|
|
||||||
await this.changedPassword(msg.data, sender.tab);
|
|
||||||
break;
|
|
||||||
case "bgAddClose":
|
|
||||||
case "bgChangeClose":
|
|
||||||
this.removeTabFromNotificationQueue(sender.tab);
|
|
||||||
break;
|
|
||||||
case "bgAddSave":
|
|
||||||
case "bgChangeSave":
|
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
|
||||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
|
||||||
commandToRetry: {
|
|
||||||
msg: msg,
|
|
||||||
sender: sender,
|
|
||||||
},
|
|
||||||
target: "notification.background",
|
|
||||||
};
|
|
||||||
await BrowserApi.tabSendMessageData(
|
|
||||||
sender.tab,
|
|
||||||
"addToLockedVaultPendingNotifications",
|
|
||||||
retryMessage
|
|
||||||
);
|
|
||||||
await BrowserApi.tabSendMessageData(
|
|
||||||
sender.tab,
|
|
||||||
"promptForLogin"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
|
|
||||||
break;
|
|
||||||
case "bgNeverSave":
|
|
||||||
await this.saveNever(sender.tab);
|
|
||||||
break;
|
|
||||||
case "collectPageDetailsResponse":
|
|
||||||
switch (msg.sender) {
|
|
||||||
case "notificationBar":
|
|
||||||
const forms =
|
|
||||||
this.autofillService.getFormsWithPasswordFields(
|
|
||||||
msg.details
|
|
||||||
);
|
|
||||||
await BrowserApi.tabSendMessageData(
|
|
||||||
msg.tab,
|
|
||||||
"notificationBarPageDetails",
|
|
||||||
{
|
|
||||||
details: msg.details,
|
|
||||||
forms: forms,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
|
||||||
|
break;
|
||||||
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
|
case "bgGetDataForTab":
|
||||||
if (this.notificationQueue.length === 0) {
|
await this.getDataForTab(sender.tab, msg.responseCommand);
|
||||||
return;
|
break;
|
||||||
|
case "bgCloseNotificationBar":
|
||||||
|
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||||
|
break;
|
||||||
|
case "bgAdjustNotificationBar":
|
||||||
|
await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data);
|
||||||
|
break;
|
||||||
|
case "bgAddLogin":
|
||||||
|
await this.addLogin(msg.login, sender.tab);
|
||||||
|
break;
|
||||||
|
case "bgChangedPassword":
|
||||||
|
await this.changedPassword(msg.data, sender.tab);
|
||||||
|
break;
|
||||||
|
case "bgAddClose":
|
||||||
|
case "bgChangeClose":
|
||||||
|
this.removeTabFromNotificationQueue(sender.tab);
|
||||||
|
break;
|
||||||
|
case "bgAddSave":
|
||||||
|
case "bgChangeSave":
|
||||||
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
|
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||||
|
commandToRetry: {
|
||||||
|
msg: msg,
|
||||||
|
sender: sender,
|
||||||
|
},
|
||||||
|
target: "notification.background",
|
||||||
|
};
|
||||||
|
await BrowserApi.tabSendMessageData(
|
||||||
|
sender.tab,
|
||||||
|
"addToLockedVaultPendingNotifications",
|
||||||
|
retryMessage
|
||||||
|
);
|
||||||
|
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
|
||||||
if (tab != null) {
|
break;
|
||||||
this.doNotificationQueueCheck(tab);
|
case "bgNeverSave":
|
||||||
return;
|
await this.saveNever(sender.tab);
|
||||||
}
|
break;
|
||||||
|
case "collectPageDetailsResponse":
|
||||||
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
switch (msg.sender) {
|
||||||
if (currentTab != null) {
|
case "notificationBar":
|
||||||
this.doNotificationQueueCheck(currentTab);
|
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
|
||||||
}
|
await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", {
|
||||||
}
|
details: msg.details,
|
||||||
|
forms: forms,
|
||||||
private cleanupNotificationQueue() {
|
});
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
break;
|
||||||
if (this.notificationQueue[i].expires < new Date()) {
|
default:
|
||||||
this.notificationQueue.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
|
|
||||||
if (tab == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabDomain = Utils.getDomain(tab.url);
|
|
||||||
if (tabDomain == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.notificationQueue.length; i++) {
|
|
||||||
if (
|
|
||||||
this.notificationQueue[i].tabId !== tab.id ||
|
|
||||||
this.notificationQueue[i].domain !== tabDomain
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.notificationQueue[i].type ===
|
|
||||||
NotificationQueueMessageType.addLogin
|
|
||||||
) {
|
|
||||||
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
|
||||||
type: "add",
|
|
||||||
typeData: {
|
|
||||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
this.notificationQueue[i].type ===
|
|
||||||
NotificationQueueMessageType.changePassword
|
|
||||||
) {
|
|
||||||
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
|
||||||
type: "change",
|
|
||||||
typeData: {
|
|
||||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
|
||||||
|
if (this.notificationQueue.length === 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
|
if (tab != null) {
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
this.doNotificationQueueCheck(tab);
|
||||||
if (this.notificationQueue[i].tabId === tab.id) {
|
return;
|
||||||
this.notificationQueue.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addLogin(
|
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
||||||
loginInfo: AddLoginRuntimeMessage,
|
if (currentTab != null) {
|
||||||
tab: chrome.tabs.Tab
|
this.doNotificationQueueCheck(currentTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanupNotificationQueue() {
|
||||||
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
|
if (this.notificationQueue[i].expires < new Date()) {
|
||||||
|
this.notificationQueue.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
|
||||||
|
if (tab == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabDomain = Utils.getDomain(tab.url);
|
||||||
|
if (tabDomain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.notificationQueue.length; i++) {
|
||||||
|
if (
|
||||||
|
this.notificationQueue[i].tabId !== tab.id ||
|
||||||
|
this.notificationQueue[i].domain !== tabDomain
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
|
||||||
|
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||||
|
type: "add",
|
||||||
|
typeData: {
|
||||||
|
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
|
||||||
|
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||||
|
type: "change",
|
||||||
|
typeData: {
|
||||||
|
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
|
||||||
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
|
if (this.notificationQueue[i].tabId === tab.id) {
|
||||||
|
this.notificationQueue.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||||
|
if (!(await this.stateService.getIsAuthenticated())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginDomain = Utils.getDomain(loginInfo.url);
|
||||||
|
if (loginDomain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalizedUsername = loginInfo.username;
|
||||||
|
if (normalizedUsername != null) {
|
||||||
|
normalizedUsername = normalizedUsername.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
|
if (!(await this.allowPersonalOwnership())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
||||||
|
const usernameMatches = ciphers.filter(
|
||||||
|
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername
|
||||||
|
);
|
||||||
|
if (usernameMatches.length === 0) {
|
||||||
|
const disabledAddLogin = await this.stateService.getDisableAddLoginNotification();
|
||||||
|
if (disabledAddLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await this.allowPersonalOwnership())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
||||||
|
} else if (
|
||||||
|
usernameMatches.length === 1 &&
|
||||||
|
usernameMatches[0].login.password !== loginInfo.password
|
||||||
) {
|
) {
|
||||||
if (!(await this.stateService.getIsAuthenticated())) {
|
const disabledChangePassword =
|
||||||
return;
|
await this.stateService.getDisableChangedPasswordNotification();
|
||||||
|
if (disabledChangePassword) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pushAddLoginToQueue(
|
||||||
|
loginDomain: string,
|
||||||
|
loginInfo: AddLoginRuntimeMessage,
|
||||||
|
tab: chrome.tabs.Tab,
|
||||||
|
isVaultLocked: boolean = false
|
||||||
|
) {
|
||||||
|
// remove any old messages for this tab
|
||||||
|
this.removeTabFromNotificationQueue(tab);
|
||||||
|
const message: AddLoginQueueMessage = {
|
||||||
|
type: NotificationQueueMessageType.addLogin,
|
||||||
|
username: loginInfo.username,
|
||||||
|
password: loginInfo.password,
|
||||||
|
domain: loginDomain,
|
||||||
|
uri: loginInfo.url,
|
||||||
|
tabId: tab.id,
|
||||||
|
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
||||||
|
wasVaultLocked: isVaultLocked,
|
||||||
|
};
|
||||||
|
this.notificationQueue.push(message);
|
||||||
|
await this.checkNotificationQueue(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||||
|
const loginDomain = Utils.getDomain(changeData.url);
|
||||||
|
if (loginDomain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
|
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: string = null;
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
||||||
|
if (changeData.currentPassword != null) {
|
||||||
|
const passwordMatches = ciphers.filter(
|
||||||
|
(c) => c.login.password === changeData.currentPassword
|
||||||
|
);
|
||||||
|
if (passwordMatches.length === 1) {
|
||||||
|
id = passwordMatches[0].id;
|
||||||
|
}
|
||||||
|
} else if (ciphers.length === 1) {
|
||||||
|
id = ciphers[0].id;
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pushChangePasswordToQueue(
|
||||||
|
cipherId: string,
|
||||||
|
loginDomain: string,
|
||||||
|
newPassword: string,
|
||||||
|
tab: chrome.tabs.Tab,
|
||||||
|
isVaultLocked: boolean = false
|
||||||
|
) {
|
||||||
|
// remove any old messages for this tab
|
||||||
|
this.removeTabFromNotificationQueue(tab);
|
||||||
|
const message: AddChangePasswordQueueMessage = {
|
||||||
|
type: NotificationQueueMessageType.changePassword,
|
||||||
|
cipherId: cipherId,
|
||||||
|
newPassword: newPassword,
|
||||||
|
domain: loginDomain,
|
||||||
|
tabId: tab.id,
|
||||||
|
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
||||||
|
wasVaultLocked: isVaultLocked,
|
||||||
|
};
|
||||||
|
this.notificationQueue.push(message);
|
||||||
|
await this.checkNotificationQueue(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
|
||||||
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
|
const queueMessage = this.notificationQueue[i];
|
||||||
|
if (
|
||||||
|
queueMessage.tabId !== tab.id ||
|
||||||
|
(queueMessage.type !== NotificationQueueMessageType.addLogin &&
|
||||||
|
queueMessage.type !== NotificationQueueMessageType.changePassword)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabDomain = Utils.getDomain(tab.url);
|
||||||
|
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notificationQueue.splice(i, 1);
|
||||||
|
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
||||||
|
|
||||||
|
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
|
||||||
|
const message = queueMessage as AddChangePasswordQueueMessage;
|
||||||
|
const cipher = await this.getDecryptedCipherById(message.cipherId);
|
||||||
|
if (cipher == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await this.updateCipher(cipher, message.newPassword);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const loginDomain = Utils.getDomain(loginInfo.url);
|
if (!queueMessage.wasVaultLocked) {
|
||||||
if (loginDomain == null) {
|
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let normalizedUsername = loginInfo.username;
|
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||||
if (normalizedUsername != null) {
|
if (
|
||||||
normalizedUsername = normalizedUsername.toLowerCase();
|
queueMessage.type === NotificationQueueMessageType.addLogin &&
|
||||||
}
|
queueMessage.wasVaultLocked === true
|
||||||
|
) {
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
const message = queueMessage as AddLoginQueueMessage;
|
||||||
if (!(await this.allowPersonalOwnership())) {
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
|
||||||
loginInfo.url
|
|
||||||
);
|
|
||||||
const usernameMatches = ciphers.filter(
|
const usernameMatches = ciphers.filter(
|
||||||
(c) =>
|
(c) => c.login.username != null && c.login.username.toLowerCase() === message.username
|
||||||
c.login.username != null &&
|
|
||||||
c.login.username.toLowerCase() === normalizedUsername
|
|
||||||
);
|
);
|
||||||
if (usernameMatches.length === 0) {
|
|
||||||
const disabledAddLogin =
|
|
||||||
await this.stateService.getDisableAddLoginNotification();
|
|
||||||
if (disabledAddLogin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.allowPersonalOwnership())) {
|
if (usernameMatches.length >= 1) {
|
||||||
return;
|
await this.updateCipher(usernameMatches[0], message.password);
|
||||||
}
|
return;
|
||||||
|
|
||||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
|
||||||
} else if (
|
|
||||||
usernameMatches.length === 1 &&
|
|
||||||
usernameMatches[0].login.password !== loginInfo.password
|
|
||||||
) {
|
|
||||||
const disabledChangePassword =
|
|
||||||
await this.stateService.getDisableChangedPasswordNotification();
|
|
||||||
if (disabledChangePassword) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.pushChangePasswordToQueue(
|
|
||||||
usernameMatches[0].id,
|
|
||||||
loginDomain,
|
|
||||||
loginInfo.password,
|
|
||||||
tab
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async pushAddLoginToQueue(
|
|
||||||
loginDomain: string,
|
|
||||||
loginInfo: AddLoginRuntimeMessage,
|
|
||||||
tab: chrome.tabs.Tab,
|
|
||||||
isVaultLocked: boolean = false
|
|
||||||
) {
|
|
||||||
// remove any old messages for this tab
|
|
||||||
this.removeTabFromNotificationQueue(tab);
|
|
||||||
const message: AddLoginQueueMessage = {
|
|
||||||
type: NotificationQueueMessageType.addLogin,
|
|
||||||
username: loginInfo.username,
|
|
||||||
password: loginInfo.password,
|
|
||||||
domain: loginDomain,
|
|
||||||
uri: loginInfo.url,
|
|
||||||
tabId: tab.id,
|
|
||||||
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
|
||||||
wasVaultLocked: isVaultLocked,
|
|
||||||
};
|
|
||||||
this.notificationQueue.push(message);
|
|
||||||
await this.checkNotificationQueue(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async changedPassword(
|
|
||||||
changeData: ChangePasswordRuntimeMessage,
|
|
||||||
tab: chrome.tabs.Tab
|
|
||||||
) {
|
|
||||||
const loginDomain = Utils.getDomain(changeData.url);
|
|
||||||
if (loginDomain == null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
await this.createNewCipher(message, folderId);
|
||||||
this.pushChangePasswordToQueue(
|
}
|
||||||
null,
|
}
|
||||||
loginDomain,
|
}
|
||||||
changeData.newPassword,
|
|
||||||
tab,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id: string = null;
|
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
const loginModel = new LoginView();
|
||||||
changeData.url
|
const loginUri = new LoginUriView();
|
||||||
);
|
loginUri.uri = queueMessage.uri;
|
||||||
if (changeData.currentPassword != null) {
|
loginModel.uris = [loginUri];
|
||||||
const passwordMatches = ciphers.filter(
|
loginModel.username = queueMessage.username;
|
||||||
(c) => c.login.password === changeData.currentPassword
|
loginModel.password = queueMessage.password;
|
||||||
);
|
const model = new CipherView();
|
||||||
if (passwordMatches.length === 1) {
|
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
|
||||||
id = passwordMatches[0].id;
|
model.name = model.name.replace(/^www\./, "");
|
||||||
}
|
model.type = CipherType.Login;
|
||||||
} else if (ciphers.length === 1) {
|
model.login = loginModel;
|
||||||
id = ciphers[0].id;
|
|
||||||
}
|
if (!Utils.isNullOrWhitespace(folderId)) {
|
||||||
if (id != null) {
|
const folders = await this.folderService.getAllDecrypted();
|
||||||
this.pushChangePasswordToQueue(
|
if (folders.some((x) => x.id === folderId)) {
|
||||||
id,
|
model.folderId = folderId;
|
||||||
loginDomain,
|
}
|
||||||
changeData.newPassword,
|
|
||||||
tab
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pushChangePasswordToQueue(
|
const cipher = await this.cipherService.encrypt(model);
|
||||||
cipherId: string,
|
await this.cipherService.saveWithServer(cipher);
|
||||||
loginDomain: string,
|
}
|
||||||
newPassword: string,
|
|
||||||
tab: chrome.tabs.Tab,
|
private async getDecryptedCipherById(cipherId: string) {
|
||||||
isVaultLocked: boolean = false
|
const cipher = await this.cipherService.get(cipherId);
|
||||||
) {
|
if (cipher != null && cipher.type === CipherType.Login) {
|
||||||
// remove any old messages for this tab
|
return await cipher.decrypt();
|
||||||
this.removeTabFromNotificationQueue(tab);
|
}
|
||||||
const message: AddChangePasswordQueueMessage = {
|
return null;
|
||||||
type: NotificationQueueMessageType.changePassword,
|
}
|
||||||
cipherId: cipherId,
|
|
||||||
newPassword: newPassword,
|
private async updateCipher(cipher: CipherView, newPassword: string) {
|
||||||
domain: loginDomain,
|
if (cipher != null && cipher.type === CipherType.Login) {
|
||||||
tabId: tab.id,
|
cipher.login.password = newPassword;
|
||||||
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
|
const newCipher = await this.cipherService.encrypt(cipher);
|
||||||
wasVaultLocked: isVaultLocked,
|
await this.cipherService.saveWithServer(newCipher);
|
||||||
};
|
}
|
||||||
this.notificationQueue.push(message);
|
}
|
||||||
await this.checkNotificationQueue(tab);
|
|
||||||
|
private async saveNever(tab: chrome.tabs.Tab) {
|
||||||
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
|
const queueMessage = this.notificationQueue[i];
|
||||||
|
if (
|
||||||
|
queueMessage.tabId !== tab.id ||
|
||||||
|
queueMessage.type !== NotificationQueueMessageType.addLogin
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabDomain = Utils.getDomain(tab.url);
|
||||||
|
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notificationQueue.splice(i, 1);
|
||||||
|
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
||||||
|
|
||||||
|
const hostname = Utils.getHostname(tab.url);
|
||||||
|
await this.cipherService.saveNeverDomain(hostname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
||||||
|
const responseData: any = {};
|
||||||
|
if (responseCommand === "notificationBarGetFoldersList") {
|
||||||
|
responseData.folders = await this.folderService.getAllDecrypted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveOrUpdateCredentials(
|
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||||
tab: chrome.tabs.Tab,
|
}
|
||||||
folderId?: string
|
|
||||||
) {
|
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
|
||||||
const queueMessage = this.notificationQueue[i];
|
|
||||||
if (
|
|
||||||
queueMessage.tabId !== tab.id ||
|
|
||||||
(queueMessage.type !== NotificationQueueMessageType.addLogin &&
|
|
||||||
queueMessage.type !==
|
|
||||||
NotificationQueueMessageType.changePassword)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabDomain = Utils.getDomain(tab.url);
|
private async allowPersonalOwnership(): Promise<boolean> {
|
||||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.notificationQueue.splice(i, 1);
|
|
||||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
|
||||||
|
|
||||||
if (
|
|
||||||
queueMessage.type ===
|
|
||||||
NotificationQueueMessageType.changePassword
|
|
||||||
) {
|
|
||||||
const message = queueMessage as AddChangePasswordQueueMessage;
|
|
||||||
const cipher = await this.getDecryptedCipherById(
|
|
||||||
message.cipherId
|
|
||||||
);
|
|
||||||
if (cipher == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.updateCipher(cipher, message.newPassword);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queueMessage.wasVaultLocked) {
|
|
||||||
await this.createNewCipher(
|
|
||||||
queueMessage as AddLoginQueueMessage,
|
|
||||||
folderId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
|
||||||
if (
|
|
||||||
queueMessage.type === NotificationQueueMessageType.addLogin &&
|
|
||||||
queueMessage.wasVaultLocked === true
|
|
||||||
) {
|
|
||||||
const message = queueMessage as AddLoginQueueMessage;
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
|
||||||
message.uri
|
|
||||||
);
|
|
||||||
const usernameMatches = ciphers.filter(
|
|
||||||
(c) =>
|
|
||||||
c.login.username != null &&
|
|
||||||
c.login.username.toLowerCase() === message.username
|
|
||||||
);
|
|
||||||
|
|
||||||
if (usernameMatches.length >= 1) {
|
|
||||||
await this.updateCipher(
|
|
||||||
usernameMatches[0],
|
|
||||||
message.password
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.createNewCipher(message, folderId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createNewCipher(
|
|
||||||
queueMessage: AddLoginQueueMessage,
|
|
||||||
folderId: string
|
|
||||||
) {
|
|
||||||
const loginModel = new LoginView();
|
|
||||||
const loginUri = new LoginUriView();
|
|
||||||
loginUri.uri = queueMessage.uri;
|
|
||||||
loginModel.uris = [loginUri];
|
|
||||||
loginModel.username = queueMessage.username;
|
|
||||||
loginModel.password = queueMessage.password;
|
|
||||||
const model = new CipherView();
|
|
||||||
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
|
|
||||||
model.name = model.name.replace(/^www\./, "");
|
|
||||||
model.type = CipherType.Login;
|
|
||||||
model.login = loginModel;
|
|
||||||
|
|
||||||
if (!Utils.isNullOrWhitespace(folderId)) {
|
|
||||||
const folders = await this.folderService.getAllDecrypted();
|
|
||||||
if (folders.some((x) => x.id === folderId)) {
|
|
||||||
model.folderId = folderId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(model);
|
|
||||||
await this.cipherService.saveWithServer(cipher);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDecryptedCipherById(cipherId: string) {
|
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
|
||||||
if (cipher != null && cipher.type === CipherType.Login) {
|
|
||||||
return await cipher.decrypt();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateCipher(cipher: CipherView, newPassword: string) {
|
|
||||||
if (cipher != null && cipher.type === CipherType.Login) {
|
|
||||||
cipher.login.password = newPassword;
|
|
||||||
const newCipher = await this.cipherService.encrypt(cipher);
|
|
||||||
await this.cipherService.saveWithServer(newCipher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveNever(tab: chrome.tabs.Tab) {
|
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
|
||||||
const queueMessage = this.notificationQueue[i];
|
|
||||||
if (
|
|
||||||
queueMessage.tabId !== tab.id ||
|
|
||||||
queueMessage.type !== NotificationQueueMessageType.addLogin
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabDomain = Utils.getDomain(tab.url);
|
|
||||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notificationQueue.splice(i, 1);
|
|
||||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
|
||||||
|
|
||||||
const hostname = Utils.getHostname(tab.url);
|
|
||||||
await this.cipherService.saveNeverDomain(hostname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
|
|
||||||
const responseData: any = {};
|
|
||||||
if (responseCommand === "notificationBarGetFoldersList") {
|
|
||||||
responseData.folders = await this.folderService.getAllDecrypted();
|
|
||||||
}
|
|
||||||
|
|
||||||
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async allowPersonalOwnership(): Promise<boolean> {
|
|
||||||
return !(await this.policyService.policyAppliesToUser(
|
|
||||||
PolicyType.PersonalOwnership
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,220 +1,245 @@
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||||
import { SystemService } from 'jslib-common/abstractions/system.service';
|
import { SystemService } from "jslib-common/abstractions/system.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { AutofillService } from '../services/abstractions/autofill.service';
|
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||||
import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service';
|
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||||
|
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
|
|
||||||
import MainBackground from './main.background';
|
import MainBackground from "./main.background";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||||
|
|
||||||
export default class RuntimeBackground {
|
export default class RuntimeBackground {
|
||||||
private autofillTimeout: any;
|
private autofillTimeout: any;
|
||||||
private pageDetailsToAutoFill: any[] = [];
|
private pageDetailsToAutoFill: any[] = [];
|
||||||
private onInstalledReason: string = null;
|
private onInstalledReason: string = null;
|
||||||
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
||||||
|
|
||||||
constructor(private main: MainBackground, private autofillService: AutofillService,
|
constructor(
|
||||||
private platformUtilsService: BrowserPlatformUtilsService,
|
private main: MainBackground,
|
||||||
private i18nService: I18nService,
|
private autofillService: AutofillService,
|
||||||
private notificationsService: NotificationsService, private systemService: SystemService,
|
private platformUtilsService: BrowserPlatformUtilsService,
|
||||||
private environmentService: EnvironmentService, private messagingService: MessagingService,
|
private i18nService: I18nService,
|
||||||
private stateService: StateService, private logService: LogService) {
|
private notificationsService: NotificationsService,
|
||||||
|
private systemService: SystemService,
|
||||||
|
private environmentService: EnvironmentService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private logService: LogService
|
||||||
|
) {
|
||||||
|
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||||
|
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||||
|
this.onInstalledReason = details.reason;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
async init() {
|
||||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
if (!chrome.runtime) {
|
||||||
this.onInstalledReason = details.reason;
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
await this.checkOnInstalled();
|
||||||
if (!chrome.runtime) {
|
BrowserApi.messageListener(
|
||||||
return;
|
"runtime.background",
|
||||||
|
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||||
|
await this.processMessage(msg, sender, sendResponse);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async processMessage(msg: any, sender: any, sendResponse: any) {
|
||||||
|
switch (msg.command) {
|
||||||
|
case "loggedIn":
|
||||||
|
case "unlocked":
|
||||||
|
let item: LockedVaultPendingNotificationsItem;
|
||||||
|
|
||||||
|
if (this.lockedVaultPendingNotifications.length > 0) {
|
||||||
|
await BrowserApi.closeLoginTab();
|
||||||
|
|
||||||
|
item = this.lockedVaultPendingNotifications.pop();
|
||||||
|
if (item.commandToRetry.sender?.tab?.id) {
|
||||||
|
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.checkOnInstalled();
|
await this.main.setIcon();
|
||||||
BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
await this.main.refreshBadgeAndMenu(false);
|
||||||
await this.processMessage(msg, sender, sendResponse);
|
this.notificationsService.updateConnection(msg.command === "unlocked");
|
||||||
});
|
this.systemService.cancelProcessReload();
|
||||||
}
|
|
||||||
|
|
||||||
async processMessage(msg: any, sender: any, sendResponse: any) {
|
if (item) {
|
||||||
switch (msg.command) {
|
await BrowserApi.tabSendMessageData(
|
||||||
case 'loggedIn':
|
item.commandToRetry.sender.tab,
|
||||||
case 'unlocked':
|
"unlockCompleted",
|
||||||
let item: LockedVaultPendingNotificationsItem;
|
item
|
||||||
|
);
|
||||||
if (this.lockedVaultPendingNotifications.length > 0) {
|
|
||||||
await BrowserApi.closeLoginTab();
|
|
||||||
|
|
||||||
item = this.lockedVaultPendingNotifications.pop();
|
|
||||||
if (item.commandToRetry.sender?.tab?.id) {
|
|
||||||
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.main.setIcon();
|
|
||||||
await this.main.refreshBadgeAndMenu(false);
|
|
||||||
this.notificationsService.updateConnection(msg.command === 'unlocked');
|
|
||||||
this.systemService.cancelProcessReload();
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
await BrowserApi.tabSendMessageData(item.commandToRetry.sender.tab, 'unlockCompleted', item);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'addToLockedVaultPendingNotifications':
|
|
||||||
this.lockedVaultPendingNotifications.push(msg.data);
|
|
||||||
break;
|
|
||||||
case 'logout':
|
|
||||||
await this.main.logout(msg.expired);
|
|
||||||
break;
|
|
||||||
case 'syncCompleted':
|
|
||||||
if (msg.successfully) {
|
|
||||||
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'openPopup':
|
|
||||||
await this.main.openPopup();
|
|
||||||
break;
|
|
||||||
case 'promptForLogin':
|
|
||||||
await BrowserApi.createNewTab('popup/index.html?uilocation=popout', true, true);
|
|
||||||
break;
|
|
||||||
case 'showDialogResolve':
|
|
||||||
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
|
||||||
break;
|
|
||||||
case 'bgCollectPageDetails':
|
|
||||||
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
|
||||||
break;
|
|
||||||
case 'bgUpdateContextMenu':
|
|
||||||
case 'editedCipher':
|
|
||||||
case 'addedCipher':
|
|
||||||
case 'deletedCipher':
|
|
||||||
await this.main.refreshBadgeAndMenu();
|
|
||||||
break;
|
|
||||||
case 'bgReseedStorage':
|
|
||||||
await this.main.reseedStorage();
|
|
||||||
break;
|
|
||||||
case 'collectPageDetailsResponse':
|
|
||||||
switch (msg.sender) {
|
|
||||||
case 'autofiller':
|
|
||||||
case 'autofill_cmd':
|
|
||||||
const totpCode = await this.autofillService.doAutoFillActiveTab([{
|
|
||||||
frameId: sender.frameId,
|
|
||||||
tab: msg.tab,
|
|
||||||
details: msg.details,
|
|
||||||
}], msg.sender === 'autofill_cmd');
|
|
||||||
if (totpCode != null) {
|
|
||||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'contextMenu':
|
|
||||||
clearTimeout(this.autofillTimeout);
|
|
||||||
this.pageDetailsToAutoFill.push({
|
|
||||||
frameId: sender.frameId,
|
|
||||||
tab: msg.tab,
|
|
||||||
details: msg.details,
|
|
||||||
});
|
|
||||||
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'authResult':
|
|
||||||
const vaultUrl = this.environmentService.getWebVaultUrl();
|
|
||||||
|
|
||||||
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BrowserApi.createNewTab('popup/index.html?uilocation=popout#/sso?code=' +
|
|
||||||
encodeURIComponent(msg.code) + '&state=' + encodeURIComponent(msg.state));
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
this.logService.error('Unable to open sso popout tab');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'webAuthnResult':
|
|
||||||
const vaultUrl2 = this.environmentService.getWebVaultUrl();
|
|
||||||
|
|
||||||
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = `webAuthnResponse=${encodeURIComponent(msg.data)};` +
|
|
||||||
`remember=${encodeURIComponent(msg.remember)}`;
|
|
||||||
BrowserApi.createNewTab(`popup/index.html?uilocation=popout#/2fa;${params}`, undefined, false);
|
|
||||||
break;
|
|
||||||
case 'reloadPopup':
|
|
||||||
this.messagingService.send('reloadPopup');
|
|
||||||
break;
|
|
||||||
case 'emailVerificationRequired':
|
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
dialogId: 'emailVerificationRequired',
|
|
||||||
title: this.i18nService.t('emailVerificationRequired'),
|
|
||||||
text: this.i18nService.t('emailVerificationRequiredDesc'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'info',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'getClickedElementResponse':
|
|
||||||
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
case "addToLockedVaultPendingNotifications":
|
||||||
private async autofillPage() {
|
this.lockedVaultPendingNotifications.push(msg.data);
|
||||||
const totpCode = await this.autofillService.doAutoFill({
|
break;
|
||||||
cipher: this.main.loginToAutoFill,
|
case "logout":
|
||||||
pageDetails: this.pageDetailsToAutoFill,
|
await this.main.logout(msg.expired);
|
||||||
fillNewPassword: true,
|
break;
|
||||||
});
|
case "syncCompleted":
|
||||||
|
if (msg.successfully) {
|
||||||
if (totpCode != null) {
|
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
|
||||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// reset
|
case "openPopup":
|
||||||
this.main.loginToAutoFill = null;
|
await this.main.openPopup();
|
||||||
this.pageDetailsToAutoFill = [];
|
break;
|
||||||
}
|
case "promptForLogin":
|
||||||
|
await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
|
||||||
private async checkOnInstalled() {
|
break;
|
||||||
setTimeout(async () => {
|
case "showDialogResolve":
|
||||||
if (this.onInstalledReason != null) {
|
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
||||||
if (this.onInstalledReason === 'install') {
|
break;
|
||||||
BrowserApi.createNewTab('https://bitwarden.com/browser-start/');
|
case "bgCollectPageDetails":
|
||||||
await this.setDefaultSettings();
|
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||||
}
|
break;
|
||||||
|
case "bgUpdateContextMenu":
|
||||||
this.onInstalledReason = null;
|
case "editedCipher":
|
||||||
|
case "addedCipher":
|
||||||
|
case "deletedCipher":
|
||||||
|
await this.main.refreshBadgeAndMenu();
|
||||||
|
break;
|
||||||
|
case "bgReseedStorage":
|
||||||
|
await this.main.reseedStorage();
|
||||||
|
break;
|
||||||
|
case "collectPageDetailsResponse":
|
||||||
|
switch (msg.sender) {
|
||||||
|
case "autofiller":
|
||||||
|
case "autofill_cmd":
|
||||||
|
const totpCode = await this.autofillService.doAutoFillActiveTab(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
frameId: sender.frameId,
|
||||||
|
tab: msg.tab,
|
||||||
|
details: msg.details,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
msg.sender === "autofill_cmd"
|
||||||
|
);
|
||||||
|
if (totpCode != null) {
|
||||||
|
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||||
}
|
}
|
||||||
}, 100);
|
break;
|
||||||
}
|
case "contextMenu":
|
||||||
|
clearTimeout(this.autofillTimeout);
|
||||||
|
this.pageDetailsToAutoFill.push({
|
||||||
|
frameId: sender.frameId,
|
||||||
|
tab: msg.tab,
|
||||||
|
details: msg.details,
|
||||||
|
});
|
||||||
|
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "authResult":
|
||||||
|
const vaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
|
|
||||||
private async setDefaultSettings() {
|
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||||
// Default timeout option to "on restart".
|
return;
|
||||||
const currentVaultTimeout = await this.stateService.getVaultTimeout();
|
|
||||||
if (currentVaultTimeout == null) {
|
|
||||||
await this.stateService.setVaultTimeout(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default action to "lock".
|
try {
|
||||||
const currentVaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
BrowserApi.createNewTab(
|
||||||
if (currentVaultTimeoutAction == null) {
|
"popup/index.html?uilocation=popout#/sso?code=" +
|
||||||
await this.stateService.setVaultTimeoutAction('lock');
|
encodeURIComponent(msg.code) +
|
||||||
|
"&state=" +
|
||||||
|
encodeURIComponent(msg.state)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
this.logService.error("Unable to open sso popout tab");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case "webAuthnResult":
|
||||||
|
const vaultUrl2 = this.environmentService.getWebVaultUrl();
|
||||||
|
|
||||||
|
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params =
|
||||||
|
`webAuthnResponse=${encodeURIComponent(msg.data)};` +
|
||||||
|
`remember=${encodeURIComponent(msg.remember)}`;
|
||||||
|
BrowserApi.createNewTab(
|
||||||
|
`popup/index.html?uilocation=popout#/2fa;${params}`,
|
||||||
|
undefined,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "reloadPopup":
|
||||||
|
this.messagingService.send("reloadPopup");
|
||||||
|
break;
|
||||||
|
case "emailVerificationRequired":
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
dialogId: "emailVerificationRequired",
|
||||||
|
title: this.i18nService.t("emailVerificationRequired"),
|
||||||
|
text: this.i18nService.t("emailVerificationRequiredDesc"),
|
||||||
|
confirmText: this.i18nService.t("ok"),
|
||||||
|
type: "info",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "getClickedElementResponse":
|
||||||
|
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async autofillPage() {
|
||||||
|
const totpCode = await this.autofillService.doAutoFill({
|
||||||
|
cipher: this.main.loginToAutoFill,
|
||||||
|
pageDetails: this.pageDetailsToAutoFill,
|
||||||
|
fillNewPassword: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (totpCode != null) {
|
||||||
|
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
this.main.loginToAutoFill = null;
|
||||||
|
this.pageDetailsToAutoFill = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkOnInstalled() {
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (this.onInstalledReason != null) {
|
||||||
|
if (this.onInstalledReason === "install") {
|
||||||
|
BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
|
||||||
|
await this.setDefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onInstalledReason = null;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setDefaultSettings() {
|
||||||
|
// Default timeout option to "on restart".
|
||||||
|
const currentVaultTimeout = await this.stateService.getVaultTimeout();
|
||||||
|
if (currentVaultTimeout == null) {
|
||||||
|
await this.stateService.setVaultTimeout(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default action to "lock".
|
||||||
|
const currentVaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
||||||
|
if (currentVaultTimeoutAction == null) {
|
||||||
|
await this.stateService.setVaultTimeoutAction("lock");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
import { Account as BaseAccount } from 'jslib-common/models/domain/account';
|
import { Account as BaseAccount } from "jslib-common/models/domain/account";
|
||||||
|
|
||||||
import { BrowserComponentState } from './browserComponentState';
|
import { BrowserComponentState } from "./browserComponentState";
|
||||||
import { BrowserGroupingsComponentState } from './browserGroupingsComponentState';
|
import { BrowserGroupingsComponentState } from "./browserGroupingsComponentState";
|
||||||
import { BrowserSendComponentState } from './browserSendComponentState';
|
import { BrowserSendComponentState } from "./browserSendComponentState";
|
||||||
|
|
||||||
export class Account extends BaseAccount {
|
export class Account extends BaseAccount {
|
||||||
groupings?: BrowserGroupingsComponentState;
|
groupings?: BrowserGroupingsComponentState;
|
||||||
send?: BrowserSendComponentState;
|
send?: BrowserSendComponentState;
|
||||||
ciphers?: BrowserComponentState;
|
ciphers?: BrowserComponentState;
|
||||||
sendType?: BrowserComponentState;
|
sendType?: BrowserComponentState;
|
||||||
|
|
||||||
constructor(init: Partial<Account>) {
|
constructor(init: Partial<Account>) {
|
||||||
super(init);
|
super(init);
|
||||||
this.groupings = init.groupings ??
|
this.groupings = init.groupings ?? new BrowserGroupingsComponentState();
|
||||||
new BrowserGroupingsComponentState();
|
this.send = init.send ?? new BrowserSendComponentState();
|
||||||
this.send = init.send ??
|
this.ciphers = init.ciphers ?? new BrowserComponentState();
|
||||||
new BrowserSendComponentState();
|
this.sendType = init.sendType ?? new BrowserComponentState();
|
||||||
this.ciphers = init.ciphers ??
|
}
|
||||||
new BrowserComponentState();
|
|
||||||
this.sendType = init.sendType ??
|
|
||||||
new BrowserComponentState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export class BrowserComponentState {
|
export class BrowserComponentState {
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||||
import { FolderView } from 'jslib-common/models/view/folderView';
|
import { FolderView } from "jslib-common/models/view/folderView";
|
||||||
import { BrowserComponentState } from './browserComponentState';
|
import { BrowserComponentState } from "./browserComponentState";
|
||||||
|
|
||||||
export class BrowserGroupingsComponentState extends BrowserComponentState {
|
export class BrowserGroupingsComponentState extends BrowserComponentState {
|
||||||
favoriteCiphers: CipherView[];
|
favoriteCiphers: CipherView[];
|
||||||
noFolderCiphers: CipherView[];
|
noFolderCiphers: CipherView[];
|
||||||
collectionCounts: Map<string, number>;
|
collectionCounts: Map<string, number>;
|
||||||
typeCounts: Map<CipherType, number>;
|
typeCounts: Map<CipherType, number>;
|
||||||
folders: FolderView[];
|
folders: FolderView[];
|
||||||
collections: CollectionView[];
|
collections: CollectionView[];
|
||||||
deletedCount: number;
|
deletedCount: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { SendType } from 'jslib-common/enums/sendType';
|
import { SendType } from "jslib-common/enums/sendType";
|
||||||
import { SendView } from 'jslib-common/models/view/sendView';
|
import { SendView } from "jslib-common/models/view/sendView";
|
||||||
import { BrowserComponentState } from './browserComponentState';
|
import { BrowserComponentState } from "./browserComponentState";
|
||||||
|
|
||||||
export class BrowserSendComponentState extends BrowserComponentState {
|
export class BrowserSendComponentState extends BrowserComponentState {
|
||||||
sends: SendView[];
|
sends: SendView[];
|
||||||
typeCounts: Map<SendType, number>;
|
typeCounts: Map<SendType, number>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,64 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: "app-home",
|
||||||
templateUrl: 'home.component.html',
|
templateUrl: "home.component.html",
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent {
|
||||||
constructor(protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
private passwordGenerationService: PasswordGenerationService, private stateService: StateService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { }
|
private passwordGenerationService: PasswordGenerationService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private environmentService: EnvironmentService
|
||||||
|
) {}
|
||||||
|
|
||||||
async launchSsoBrowser() {
|
async launchSsoBrowser() {
|
||||||
// Generate necessary sso params
|
// Generate necessary sso params
|
||||||
const passwordOptions: any = {
|
const passwordOptions: any = {
|
||||||
type: 'password',
|
type: "password",
|
||||||
length: 64,
|
length: 64,
|
||||||
uppercase: true,
|
uppercase: true,
|
||||||
lowercase: true,
|
lowercase: true,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
special: false,
|
special: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser';
|
const state =
|
||||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
(await this.passwordGenerationService.generatePassword(passwordOptions)) +
|
||||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
":clientId=browser";
|
||||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||||
|
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
|
||||||
|
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||||
|
|
||||||
await this.stateService.setSsoCodeVerifier(codeVerifier);
|
await this.stateService.setSsoCodeVerifier(codeVerifier);
|
||||||
await this.stateService.setSsoState(state);
|
await this.stateService.setSsoState(state);
|
||||||
|
|
||||||
let url = this.environmentService.getWebVaultUrl();
|
let url = this.environmentService.getWebVaultUrl();
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
url = 'https://vault.bitwarden.com';
|
url = "https://vault.bitwarden.com";
|
||||||
}
|
|
||||||
|
|
||||||
const redirectUri = url + '/sso-connector.html';
|
|
||||||
|
|
||||||
// Launch browser
|
|
||||||
this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' +
|
|
||||||
'&redirectUri=' + encodeURIComponent(redirectUri) +
|
|
||||||
'&state=' + state + '&codeChallenge=' + codeChallenge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const redirectUri = url + "/sso-connector.html";
|
||||||
|
|
||||||
|
// Launch browser
|
||||||
|
this.platformUtilsService.launchUri(
|
||||||
|
url +
|
||||||
|
"/#/sso?clientId=browser" +
|
||||||
|
"&redirectUri=" +
|
||||||
|
encodeURIComponent(redirectUri) +
|
||||||
|
"&state=" +
|
||||||
|
state +
|
||||||
|
"&codeChallenge=" +
|
||||||
|
codeChallenge
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +1,99 @@
|
||||||
import { Component, NgZone } from '@angular/core';
|
import { Component, NgZone } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
import Swal from 'sweetalert2';
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
|
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-lock',
|
selector: "app-lock",
|
||||||
templateUrl: 'lock.component.html',
|
templateUrl: "lock.component.html",
|
||||||
})
|
})
|
||||||
export class LockComponent extends BaseLockComponent {
|
export class LockComponent extends BaseLockComponent {
|
||||||
private isInitialLockScreen: boolean;
|
private isInitialLockScreen: boolean;
|
||||||
|
|
||||||
constructor(router: Router, i18nService: I18nService,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService, messagingService: MessagingService, cryptoService: CryptoService,
|
router: Router,
|
||||||
vaultTimeoutService: VaultTimeoutService, environmentService: EnvironmentService,
|
i18nService: I18nService,
|
||||||
stateService: StateService, apiService: ApiService, logService: LogService, keyConnectorService: KeyConnectorService, ngZone: NgZone) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(router, i18nService, platformUtilsService,
|
messagingService: MessagingService,
|
||||||
messagingService, cryptoService, vaultTimeoutService,
|
cryptoService: CryptoService,
|
||||||
environmentService, stateService, apiService, logService, keyConnectorService, ngZone);
|
vaultTimeoutService: VaultTimeoutService,
|
||||||
this.successRoute = '/tabs/current';
|
environmentService: EnvironmentService,
|
||||||
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
stateService: StateService,
|
||||||
}
|
apiService: ApiService,
|
||||||
|
logService: LogService,
|
||||||
|
keyConnectorService: KeyConnectorService,
|
||||||
|
ngZone: NgZone
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
router,
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
messagingService,
|
||||||
|
cryptoService,
|
||||||
|
vaultTimeoutService,
|
||||||
|
environmentService,
|
||||||
|
stateService,
|
||||||
|
apiService,
|
||||||
|
logService,
|
||||||
|
keyConnectorService,
|
||||||
|
ngZone
|
||||||
|
);
|
||||||
|
this.successRoute = "/tabs/current";
|
||||||
|
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
const disableAutoBiometricsPrompt = await this.stateService.getDisableAutoBiometricsPrompt() ?? true;
|
const disableAutoBiometricsPrompt =
|
||||||
|
(await this.stateService.getDisableAutoBiometricsPrompt()) ?? true;
|
||||||
|
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
|
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
|
||||||
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
|
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
await this.unlockBiometric();
|
await this.unlockBiometric();
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
async unlockBiometric(): Promise<boolean> {
|
|
||||||
if (!this.biometricLock) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
const div = document.createElement('div');
|
async unlockBiometric(): Promise<boolean> {
|
||||||
div.innerHTML = `<div class="swal2-text">${this.i18nService.t('awaitDesktop')}</div>`;
|
if (!this.biometricLock) {
|
||||||
|
return;
|
||||||
Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
html: div,
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: this.i18nService.t('cancel'),
|
|
||||||
showConfirmButton: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const success = await super.unlockBiometric();
|
|
||||||
|
|
||||||
// Avoid closing the error dialogs
|
|
||||||
if (success) {
|
|
||||||
Swal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = `<div class="swal2-text">${this.i18nService.t("awaitDesktop")}</div>`;
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
html: div,
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t("cancel"),
|
||||||
|
showConfirmButton: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const success = await super.unlockBiometric();
|
||||||
|
|
||||||
|
// Avoid closing the error dialogs
|
||||||
|
if (success) {
|
||||||
|
Swal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,61 @@
|
||||||
import { Component, NgZone } from '@angular/core';
|
import { Component, NgZone } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
|
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: "app-login",
|
||||||
templateUrl: 'login.component.html',
|
templateUrl: "login.component.html",
|
||||||
})
|
})
|
||||||
export class LoginComponent extends BaseLoginComponent {
|
export class LoginComponent extends BaseLoginComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
authService: AuthService,
|
||||||
protected stateService: StateService, protected environmentService: EnvironmentService,
|
router: Router,
|
||||||
protected passwordGenerationService: PasswordGenerationService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected cryptoFunctionService: CryptoFunctionService, logService: LogService,
|
protected i18nService: I18nService,
|
||||||
syncService: SyncService, ngZone: NgZone) {
|
protected stateService: StateService,
|
||||||
super(authService, router, platformUtilsService, i18nService,
|
protected environmentService: EnvironmentService,
|
||||||
stateService, environmentService, passwordGenerationService, cryptoFunctionService,
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
logService, ngZone);
|
protected cryptoFunctionService: CryptoFunctionService,
|
||||||
super.onSuccessfulLogin = async () => {
|
logService: LogService,
|
||||||
await syncService.fullSync(true);
|
syncService: SyncService,
|
||||||
};
|
ngZone: NgZone
|
||||||
super.successRoute = '/tabs/vault';
|
) {
|
||||||
}
|
super(
|
||||||
|
authService,
|
||||||
|
router,
|
||||||
|
platformUtilsService,
|
||||||
|
i18nService,
|
||||||
|
stateService,
|
||||||
|
environmentService,
|
||||||
|
passwordGenerationService,
|
||||||
|
cryptoFunctionService,
|
||||||
|
logService,
|
||||||
|
ngZone
|
||||||
|
);
|
||||||
|
super.onSuccessfulLogin = async () => {
|
||||||
|
await syncService.fullSync(true);
|
||||||
|
};
|
||||||
|
super.successRoute = "/tabs/vault";
|
||||||
|
}
|
||||||
|
|
||||||
settings() {
|
settings() {
|
||||||
this.router.navigate(['environment']);
|
this.router.navigate(["environment"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
await super.submit();
|
await super.submit();
|
||||||
this.stateService.setRememberedEmail(this.email);
|
this.stateService.setRememberedEmail(this.email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +1,79 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import {
|
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
|
||||||
SetPasswordComponent as BaseSetPasswordComponent,
|
|
||||||
} from 'jslib-angular/components/set-password.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-set-password',
|
selector: "app-set-password",
|
||||||
templateUrl: 'set-password.component.html',
|
templateUrl: "set-password.component.html",
|
||||||
})
|
})
|
||||||
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||||
constructor(apiService: ApiService, i18nService: I18nService,
|
constructor(
|
||||||
cryptoService: CryptoService, messagingService: MessagingService,
|
apiService: ApiService,
|
||||||
stateService: StateService, passwordGenerationService: PasswordGenerationService,
|
i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
|
cryptoService: CryptoService,
|
||||||
syncService: SyncService, route: ActivatedRoute) {
|
messagingService: MessagingService,
|
||||||
super(i18nService, cryptoService, messagingService, passwordGenerationService,
|
stateService: StateService,
|
||||||
platformUtilsService, policyService, router, apiService, syncService, route, stateService);
|
passwordGenerationService: PasswordGenerationService,
|
||||||
}
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
policyService: PolicyService,
|
||||||
|
router: Router,
|
||||||
|
syncService: SyncService,
|
||||||
|
route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
i18nService,
|
||||||
|
cryptoService,
|
||||||
|
messagingService,
|
||||||
|
passwordGenerationService,
|
||||||
|
platformUtilsService,
|
||||||
|
policyService,
|
||||||
|
router,
|
||||||
|
apiService,
|
||||||
|
syncService,
|
||||||
|
route,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get masterPasswordScoreWidth() {
|
get masterPasswordScoreWidth() {
|
||||||
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
get masterPasswordScoreColor() {
|
get masterPasswordScoreColor() {
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return 'success';
|
return "success";
|
||||||
case 3:
|
case 3:
|
||||||
return 'primary';
|
return "primary";
|
||||||
case 2:
|
case 2:
|
||||||
return 'warning';
|
return "warning";
|
||||||
default:
|
default:
|
||||||
return 'danger';
|
return "danger";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get masterPasswordScoreText() {
|
get masterPasswordScoreText() {
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return this.i18nService.t('strong');
|
return this.i18nService.t("strong");
|
||||||
case 3:
|
case 3:
|
||||||
return this.i18nService.t('good');
|
return this.i18nService.t("good");
|
||||||
case 2:
|
case 2:
|
||||||
return this.i18nService.t('weak');
|
return this.i18nService.t("weak");
|
||||||
default:
|
default:
|
||||||
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
|
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,71 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
|
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sso',
|
selector: "app-sso",
|
||||||
templateUrl: 'sso.component.html',
|
templateUrl: "sso.component.html",
|
||||||
})
|
})
|
||||||
export class SsoComponent extends BaseSsoComponent {
|
export class SsoComponent extends BaseSsoComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, route: ActivatedRoute, stateService: StateService,
|
authService: AuthService,
|
||||||
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
router: Router,
|
||||||
cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService,
|
i18nService: I18nService,
|
||||||
syncService: SyncService, environmentService: EnvironmentService, logService: LogService,
|
route: ActivatedRoute,
|
||||||
private vaultTimeoutService: VaultTimeoutService) {
|
stateService: StateService,
|
||||||
super(authService, router, i18nService, route, stateService, platformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
|
apiService: ApiService,
|
||||||
|
cryptoFunctionService: CryptoFunctionService,
|
||||||
|
passwordGenerationService: PasswordGenerationService,
|
||||||
|
syncService: SyncService,
|
||||||
|
environmentService: EnvironmentService,
|
||||||
|
logService: LogService,
|
||||||
|
private vaultTimeoutService: VaultTimeoutService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
authService,
|
||||||
|
router,
|
||||||
|
i18nService,
|
||||||
|
route,
|
||||||
|
stateService,
|
||||||
|
platformUtilsService,
|
||||||
|
apiService,
|
||||||
|
cryptoFunctionService,
|
||||||
|
environmentService,
|
||||||
|
passwordGenerationService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
|
||||||
const url = this.environmentService.getWebVaultUrl();
|
const url = this.environmentService.getWebVaultUrl();
|
||||||
|
|
||||||
this.redirectUri = url + '/sso-connector.html';
|
this.redirectUri = url + "/sso-connector.html";
|
||||||
this.clientId = 'browser';
|
this.clientId = "browser";
|
||||||
|
|
||||||
super.onSuccessfulLogin = async () => {
|
super.onSuccessfulLogin = async () => {
|
||||||
await syncService.fullSync(true);
|
await syncService.fullSync(true);
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
|
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
|
||||||
BrowserApi.reloadOpenWindows();
|
BrowserApi.reloadOpenWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
const thisWindow = window.open('', '_self');
|
const thisWindow = window.open("", "_self");
|
||||||
thisWindow.close();
|
thisWindow.close();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,115 +1,139 @@
|
||||||
import { ChangeDetectorRef, Component, NgZone } from '@angular/core';
|
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
import { first } from "rxjs/operators";
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
|
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
|
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'TwoFactorComponent';
|
const BroadcasterSubscriptionId = "TwoFactorComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-two-factor',
|
selector: "app-two-factor",
|
||||||
templateUrl: 'two-factor.component.html',
|
templateUrl: "two-factor.component.html",
|
||||||
})
|
})
|
||||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
showNewWindowMessage = false;
|
showNewWindowMessage = false;
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, apiService: ApiService,
|
authService: AuthService,
|
||||||
platformUtilsService: PlatformUtilsService, private syncService: SyncService,
|
router: Router,
|
||||||
environmentService: EnvironmentService, private ngZone: NgZone,
|
i18nService: I18nService,
|
||||||
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
|
apiService: ApiService,
|
||||||
private popupUtilsService: PopupUtilsService, stateService: StateService, route: ActivatedRoute,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private messagingService: MessagingService, logService: LogService) {
|
private syncService: SyncService,
|
||||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
environmentService: EnvironmentService,
|
||||||
stateService, route, logService);
|
private ngZone: NgZone,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private popupUtilsService: PopupUtilsService,
|
||||||
|
stateService: StateService,
|
||||||
|
route: ActivatedRoute,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
authService,
|
||||||
|
router,
|
||||||
|
i18nService,
|
||||||
|
apiService,
|
||||||
|
platformUtilsService,
|
||||||
|
window,
|
||||||
|
environmentService,
|
||||||
|
stateService,
|
||||||
|
route,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
super.onSuccessfulLogin = () => {
|
||||||
|
return syncService.fullSync(true);
|
||||||
|
};
|
||||||
|
super.successRoute = "/tabs/vault";
|
||||||
|
this.webAuthnNewTab =
|
||||||
|
this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
||||||
|
// WebAuthn fallback response
|
||||||
|
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||||
|
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
|
||||||
|
super.onSuccessfulLogin = async () => {
|
||||||
|
this.syncService.fullSync(true);
|
||||||
|
this.messagingService.send("reloadPopup");
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
this.remember = this.route.snapshot.paramMap.get("remember") === "true";
|
||||||
|
await this.doSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.ngOnInit();
|
||||||
|
if (this.selectedProviderType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
|
||||||
|
// than usual to avoid cutting off the dialog.
|
||||||
|
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
|
||||||
|
document.body.classList.add("linux-webauthn");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.selectedProviderType === TwoFactorProviderType.Email &&
|
||||||
|
this.popupUtilsService.inPopup(window)
|
||||||
|
) {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("popup2faCloseMessage"),
|
||||||
|
null,
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("no")
|
||||||
|
);
|
||||||
|
if (confirmed) {
|
||||||
|
this.popupUtilsService.popOut(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
|
if (qParams.sso === "true") {
|
||||||
super.onSuccessfulLogin = () => {
|
super.onSuccessfulLogin = () => {
|
||||||
return syncService.fullSync(true);
|
BrowserApi.reloadOpenWindows();
|
||||||
|
const thisWindow = window.open("", "_self");
|
||||||
|
thisWindow.close();
|
||||||
|
return this.syncService.fullSync(true);
|
||||||
};
|
};
|
||||||
super.successRoute = '/tabs/vault';
|
}
|
||||||
this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnDestroy() {
|
||||||
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
|
||||||
|
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
|
||||||
|
document.body.classList.remove("linux-webauthn");
|
||||||
}
|
}
|
||||||
|
super.ngOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
anotherMethod() {
|
||||||
if (this.route.snapshot.paramMap.has('webAuthnResponse')) {
|
this.router.navigate(["2fa-options"]);
|
||||||
// WebAuthn fallback response
|
}
|
||||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
|
||||||
this.token = this.route.snapshot.paramMap.get('webAuthnResponse');
|
|
||||||
super.onSuccessfulLogin = async () => {
|
|
||||||
this.syncService.fullSync(true);
|
|
||||||
this.messagingService.send('reloadPopup');
|
|
||||||
window.close();
|
|
||||||
};
|
|
||||||
this.remember = this.route.snapshot.paramMap.get('remember') === 'true';
|
|
||||||
await this.doSubmit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await super.ngOnInit();
|
async isLinux() {
|
||||||
if (this.selectedProviderType == null) {
|
return (await BrowserApi.getPlatformInfo()).os === "linux";
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
|
|
||||||
// than usual to avoid cutting off the dialog.
|
|
||||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
|
|
||||||
document.body.classList.add('linux-webauthn');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedProviderType === TwoFactorProviderType.Email &&
|
|
||||||
this.popupUtilsService.inPopup(window)) {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'),
|
|
||||||
null, this.i18nService.t('yes'), this.i18nService.t('no'));
|
|
||||||
if (confirmed) {
|
|
||||||
this.popupUtilsService.popOut(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
|
||||||
if (qParams.sso === 'true') {
|
|
||||||
super.onSuccessfulLogin = () => {
|
|
||||||
BrowserApi.reloadOpenWindows();
|
|
||||||
const thisWindow = window.open('', '_self');
|
|
||||||
thisWindow.close();
|
|
||||||
return this.syncService.fullSync(true);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnDestroy() {
|
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
|
||||||
|
|
||||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
|
|
||||||
document.body.classList.remove('linux-webauthn');
|
|
||||||
}
|
|
||||||
super.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
anotherMethod() {
|
|
||||||
this.router.navigate(['2fa-options']);
|
|
||||||
}
|
|
||||||
|
|
||||||
async isLinux() {
|
|
||||||
return (await BrowserApi.getPlatformInfo()).os === 'linux';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,82 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
|
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
interface MasterPasswordScore {
|
interface MasterPasswordScore {
|
||||||
Color: string;
|
Color: string;
|
||||||
Text: string;
|
Text: string;
|
||||||
Width: number;
|
Width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-update-temp-password',
|
selector: "app-update-temp-password",
|
||||||
templateUrl: 'update-temp-password.component.html',
|
templateUrl: "update-temp-password.component.html",
|
||||||
})
|
})
|
||||||
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
||||||
get masterPasswordScoreStyle(): MasterPasswordScore {
|
get masterPasswordScoreStyle(): MasterPasswordScore {
|
||||||
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-success',
|
Color: "bg-success",
|
||||||
Text: 'strong',
|
Text: "strong",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
case 3:
|
case 3:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-primary',
|
Color: "bg-primary",
|
||||||
Text: 'good',
|
Text: "good",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
case 2:
|
case 2:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-warning',
|
Color: "bg-warning",
|
||||||
Text: 'weak',
|
Text: "weak",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-danger',
|
Color: "bg-danger",
|
||||||
Text: 'weak',
|
Text: "weak",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
i18nService: I18nService,
|
||||||
cryptoService: CryptoService, stateService: StateService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
messagingService: MessagingService, apiService: ApiService,
|
passwordGenerationService: PasswordGenerationService,
|
||||||
syncService: SyncService, logService: LogService) {
|
policyService: PolicyService,
|
||||||
super(i18nService, platformUtilsService, passwordGenerationService, policyService,
|
cryptoService: CryptoService,
|
||||||
cryptoService, messagingService, apiService, stateService,
|
stateService: StateService,
|
||||||
syncService, logService);
|
messagingService: MessagingService,
|
||||||
}
|
apiService: ApiService,
|
||||||
|
syncService: SyncService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
passwordGenerationService,
|
||||||
|
policyService,
|
||||||
|
cryptoService,
|
||||||
|
messagingService,
|
||||||
|
apiService,
|
||||||
|
stateService,
|
||||||
|
syncService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,238 +1,249 @@
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
Component,
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||||
NgZone,
|
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||||
OnInit,
|
import Swal, { SweetAlertIcon } from "sweetalert2/src/sweetalert2.js";
|
||||||
SecurityContext,
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
} from '@angular/core';
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
|
||||||
import {
|
|
||||||
NavigationEnd,
|
|
||||||
Router,
|
|
||||||
RouterOutlet,
|
|
||||||
} from '@angular/router';
|
|
||||||
import {
|
|
||||||
IndividualConfig,
|
|
||||||
ToastrService,
|
|
||||||
} from 'ngx-toastr';
|
|
||||||
import Swal, { SweetAlertIcon } from 'sweetalert2/src/sweetalert2.js';
|
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
|
||||||
|
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
|
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { routerTransition } from './app-routing.animations';
|
import { routerTransition } from "./app-routing.animations";
|
||||||
|
|
||||||
import { StateService } from '../services/abstractions/state.service';
|
import { StateService } from "../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: "app-root",
|
||||||
styles: [],
|
styles: [],
|
||||||
animations: [routerTransition],
|
animations: [routerTransition],
|
||||||
template: `
|
template: ` <main [@routerTransition]="getState(o)">
|
||||||
<main [@routerTransition]="getState(o)">
|
<router-outlet #o="outlet"></router-outlet>
|
||||||
<router-outlet #o="outlet"></router-outlet>
|
</main>`,
|
||||||
</main>`,
|
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
|
private lastActivity: number = null;
|
||||||
|
|
||||||
private lastActivity: number = null;
|
constructor(
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
private storageService: StorageService,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private router: Router,
|
||||||
|
private stateService: StateService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private keyConnectoService: KeyConnectorService
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(private toastrService: ToastrService, private storageService: StorageService,
|
ngOnInit() {
|
||||||
private broadcasterService: BroadcasterService, private authService: AuthService,
|
if (BrowserApi.getBackgroundPage() == null) {
|
||||||
private i18nService: I18nService, private router: Router,
|
return;
|
||||||
private stateService: StateService, private messagingService: MessagingService,
|
}
|
||||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
|
||||||
private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService,
|
|
||||||
private keyConnectoService: KeyConnectorService) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
if (BrowserApi.getBackgroundPage() == null) {
|
window.onmousedown = () => this.recordActivity();
|
||||||
return;
|
window.ontouchstart = () => this.recordActivity();
|
||||||
}
|
window.onclick = () => this.recordActivity();
|
||||||
|
window.onscroll = () => this.recordActivity();
|
||||||
|
window.onkeypress = () => this.recordActivity();
|
||||||
|
});
|
||||||
|
|
||||||
this.ngZone.runOutsideAngular(() => {
|
(window as any).bitwardenPopupMainMessageListener = async (
|
||||||
window.onmousedown = () => this.recordActivity();
|
msg: any,
|
||||||
window.ontouchstart = () => this.recordActivity();
|
sender: any,
|
||||||
window.onclick = () => this.recordActivity();
|
sendResponse: any
|
||||||
window.onscroll = () => this.recordActivity();
|
) => {
|
||||||
window.onkeypress = () => this.recordActivity();
|
if (msg.command === "doneLoggingOut") {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
this.authService.logOut(async () => {
|
||||||
|
if (msg.expired) {
|
||||||
|
this.showToast({
|
||||||
|
type: "warning",
|
||||||
|
title: this.i18nService.t("loggedOut"),
|
||||||
|
text: this.i18nService.t("loginExpired"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.stateService.clean();
|
||||||
|
this.router.navigate(["home"]);
|
||||||
|
});
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
});
|
});
|
||||||
|
} else if (msg.command === "authBlocked") {
|
||||||
(window as any).bitwardenPopupMainMessageListener = async (msg: any, sender: any, sendResponse: any) => {
|
this.ngZone.run(() => {
|
||||||
if (msg.command === 'doneLoggingOut') {
|
this.router.navigate(["home"]);
|
||||||
this.ngZone.run(async () => {
|
|
||||||
this.authService.logOut(async () => {
|
|
||||||
if (msg.expired) {
|
|
||||||
this.showToast({
|
|
||||||
type: 'warning',
|
|
||||||
title: this.i18nService.t('loggedOut'),
|
|
||||||
text: this.i18nService.t('loginExpired'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.stateService.clean();
|
|
||||||
this.router.navigate(['home']);
|
|
||||||
});
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
} else if (msg.command === 'authBlocked') {
|
|
||||||
this.ngZone.run(() => {
|
|
||||||
this.router.navigate(['home']);
|
|
||||||
});
|
|
||||||
} else if (msg.command === 'locked') {
|
|
||||||
this.ngZone.run(() => {
|
|
||||||
this.router.navigate(['lock']);
|
|
||||||
});
|
|
||||||
} else if (msg.command === 'showDialog') {
|
|
||||||
await this.showDialog(msg);
|
|
||||||
} else if (msg.command === 'showToast') {
|
|
||||||
this.ngZone.run(() => {
|
|
||||||
this.showToast(msg);
|
|
||||||
});
|
|
||||||
} else if (msg.command === 'reloadProcess') {
|
|
||||||
const windowReload = this.platformUtilsService.isSafari() ||
|
|
||||||
this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera();
|
|
||||||
if (windowReload) {
|
|
||||||
// Wait to make sure background has reloaded first.
|
|
||||||
window.setTimeout(() => BrowserApi.reloadExtension(window), 2000);
|
|
||||||
}
|
|
||||||
} else if (msg.command === 'reloadPopup') {
|
|
||||||
this.ngZone.run(() => {
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
});
|
|
||||||
} else if (msg.command === 'convertAccountToKeyConnector') {
|
|
||||||
this.ngZone.run(async () => {
|
|
||||||
await this.keyConnectoService.setConvertAccountRequired(true);
|
|
||||||
this.router.navigate(['/remove-password']);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
msg.webExtSender = sender;
|
|
||||||
this.broadcasterService.send(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BrowserApi.messageListener('app.component', (window as any).bitwardenPopupMainMessageListener);
|
|
||||||
|
|
||||||
this.router.events.subscribe(async event => {
|
|
||||||
if (event instanceof NavigationEnd) {
|
|
||||||
const url = event.urlAfterRedirects || event.url || '';
|
|
||||||
if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null &&
|
|
||||||
(window as any).previousPopupUrl.startsWith('/tabs/')) {
|
|
||||||
await this.stateService.setBrowserGroupingComponentState(null);
|
|
||||||
await this.stateService.setBrowserCipherComponentState(null);
|
|
||||||
await this.stateService.setBrowserSendComponentState(null);
|
|
||||||
await this.stateService.setBrowserSendTypeComponentState(null);
|
|
||||||
}
|
|
||||||
if (url.startsWith('/tabs/')) {
|
|
||||||
await this.stateService.setAddEditCipherInfo(null);
|
|
||||||
}
|
|
||||||
(window as any).previousPopupUrl = url;
|
|
||||||
|
|
||||||
// Clear route direction after animation (400ms)
|
|
||||||
if ((window as any).routeDirection != null) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
(window as any).routeDirection = null;
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
} else if (msg.command === "locked") {
|
||||||
|
this.ngZone.run(() => {
|
||||||
getState(outlet: RouterOutlet) {
|
this.router.navigate(["lock"]);
|
||||||
if (outlet.activatedRouteData.state === 'ciphers') {
|
|
||||||
const routeDirection = (window as any).routeDirection != null ? (window as any).routeDirection : '';
|
|
||||||
return 'ciphers_direction=' + routeDirection + '_' +
|
|
||||||
(outlet.activatedRoute.queryParams as any).value.folderId + '_' +
|
|
||||||
(outlet.activatedRoute.queryParams as any).value.collectionId;
|
|
||||||
} else {
|
|
||||||
return outlet.activatedRouteData.state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async recordActivity() {
|
|
||||||
const now = (new Date()).getTime();
|
|
||||||
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastActivity = now;
|
|
||||||
await this.stateService.setLastActive(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
private showToast(msg: any) {
|
|
||||||
let message = '';
|
|
||||||
|
|
||||||
const options: Partial<IndividualConfig> = {};
|
|
||||||
|
|
||||||
if (typeof (msg.text) === 'string') {
|
|
||||||
message = msg.text;
|
|
||||||
} else if (msg.text.length === 1) {
|
|
||||||
message = msg.text[0];
|
|
||||||
} else {
|
|
||||||
msg.text.forEach((t: string) =>
|
|
||||||
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options != null) {
|
|
||||||
if (msg.options.trustedHtml === true) {
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
|
||||||
options.timeOut = msg.options.timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async showDialog(msg: any) {
|
|
||||||
let iconClasses: string = null;
|
|
||||||
const type = msg.type;
|
|
||||||
if (type != null) {
|
|
||||||
// If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed.
|
|
||||||
switch (type) {
|
|
||||||
case 'success':
|
|
||||||
iconClasses = 'fa-check text-success';
|
|
||||||
break;
|
|
||||||
case 'warning':
|
|
||||||
iconClasses = 'fa-warning text-warning';
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
iconClasses = 'fa-bolt text-danger';
|
|
||||||
break;
|
|
||||||
case 'info':
|
|
||||||
iconClasses = 'fa-info-circle text-info';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelText = msg.cancelText;
|
|
||||||
const confirmText = msg.confirmText;
|
|
||||||
const confirmed = await Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml.
|
|
||||||
iconHtml: iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
|
|
||||||
text: msg.text,
|
|
||||||
html: msg.html,
|
|
||||||
titleText: msg.title,
|
|
||||||
showCancelButton: (cancelText != null),
|
|
||||||
cancelButtonText: cancelText,
|
|
||||||
showConfirmButton: true,
|
|
||||||
confirmButtonText: confirmText == null ? this.i18nService.t('ok') : confirmText,
|
|
||||||
timer: 300000,
|
|
||||||
});
|
});
|
||||||
|
} else if (msg.command === "showDialog") {
|
||||||
this.messagingService.send('showDialogResolve', {
|
await this.showDialog(msg);
|
||||||
dialogId: msg.dialogId,
|
} else if (msg.command === "showToast") {
|
||||||
confirmed: confirmed.value,
|
this.ngZone.run(() => {
|
||||||
|
this.showToast(msg);
|
||||||
});
|
});
|
||||||
|
} else if (msg.command === "reloadProcess") {
|
||||||
|
const windowReload =
|
||||||
|
this.platformUtilsService.isSafari() ||
|
||||||
|
this.platformUtilsService.isFirefox() ||
|
||||||
|
this.platformUtilsService.isOpera();
|
||||||
|
if (windowReload) {
|
||||||
|
// Wait to make sure background has reloaded first.
|
||||||
|
window.setTimeout(() => BrowserApi.reloadExtension(window), 2000);
|
||||||
|
}
|
||||||
|
} else if (msg.command === "reloadPopup") {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
});
|
||||||
|
} else if (msg.command === "convertAccountToKeyConnector") {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
await this.keyConnectoService.setConvertAccountRequired(true);
|
||||||
|
this.router.navigate(["/remove-password"]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
msg.webExtSender = sender;
|
||||||
|
this.broadcasterService.send(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener);
|
||||||
|
|
||||||
|
this.router.events.subscribe(async (event) => {
|
||||||
|
if (event instanceof NavigationEnd) {
|
||||||
|
const url = event.urlAfterRedirects || event.url || "";
|
||||||
|
if (
|
||||||
|
url.startsWith("/tabs/") &&
|
||||||
|
(window as any).previousPopupUrl != null &&
|
||||||
|
(window as any).previousPopupUrl.startsWith("/tabs/")
|
||||||
|
) {
|
||||||
|
await this.stateService.setBrowserGroupingComponentState(null);
|
||||||
|
await this.stateService.setBrowserCipherComponentState(null);
|
||||||
|
await this.stateService.setBrowserSendComponentState(null);
|
||||||
|
await this.stateService.setBrowserSendTypeComponentState(null);
|
||||||
|
}
|
||||||
|
if (url.startsWith("/tabs/")) {
|
||||||
|
await this.stateService.setAddEditCipherInfo(null);
|
||||||
|
}
|
||||||
|
(window as any).previousPopupUrl = url;
|
||||||
|
|
||||||
|
// Clear route direction after animation (400ms)
|
||||||
|
if ((window as any).routeDirection != null) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
(window as any).routeDirection = null;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(outlet: RouterOutlet) {
|
||||||
|
if (outlet.activatedRouteData.state === "ciphers") {
|
||||||
|
const routeDirection =
|
||||||
|
(window as any).routeDirection != null ? (window as any).routeDirection : "";
|
||||||
|
return (
|
||||||
|
"ciphers_direction=" +
|
||||||
|
routeDirection +
|
||||||
|
"_" +
|
||||||
|
(outlet.activatedRoute.queryParams as any).value.folderId +
|
||||||
|
"_" +
|
||||||
|
(outlet.activatedRoute.queryParams as any).value.collectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return outlet.activatedRouteData.state;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async recordActivity() {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastActivity = now;
|
||||||
|
await this.stateService.setLastActive(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private showToast(msg: any) {
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
const options: Partial<IndividualConfig> = {};
|
||||||
|
|
||||||
|
if (typeof msg.text === "string") {
|
||||||
|
message = msg.text;
|
||||||
|
} else if (msg.text.length === 1) {
|
||||||
|
message = msg.text[0];
|
||||||
|
} else {
|
||||||
|
msg.text.forEach(
|
||||||
|
(t: string) =>
|
||||||
|
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
|
||||||
|
);
|
||||||
|
options.enableHtml = true;
|
||||||
|
}
|
||||||
|
if (msg.options != null) {
|
||||||
|
if (msg.options.trustedHtml === true) {
|
||||||
|
options.enableHtml = true;
|
||||||
|
}
|
||||||
|
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
||||||
|
options.timeOut = msg.options.timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async showDialog(msg: any) {
|
||||||
|
let iconClasses: string = null;
|
||||||
|
const type = msg.type;
|
||||||
|
if (type != null) {
|
||||||
|
// If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed.
|
||||||
|
switch (type) {
|
||||||
|
case "success":
|
||||||
|
iconClasses = "fa-check text-success";
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
iconClasses = "fa-warning text-warning";
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
iconClasses = "fa-bolt text-danger";
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
iconClasses = "fa-info-circle text-info";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelText = msg.cancelText;
|
||||||
|
const confirmText = msg.confirmText;
|
||||||
|
const confirmed = await Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml.
|
||||||
|
iconHtml:
|
||||||
|
iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
|
||||||
|
text: msg.text,
|
||||||
|
html: msg.html,
|
||||||
|
titleText: msg.title,
|
||||||
|
showCancelButton: cancelText != null,
|
||||||
|
cancelButtonText: cancelText,
|
||||||
|
showConfirmButton: true,
|
||||||
|
confirmButtonText: confirmText == null ? this.i18nService.t("ok") : confirmText,
|
||||||
|
timer: 300000,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.messagingService.send("showDialogResolve", {
|
||||||
|
dialogId: msg.dialogId,
|
||||||
|
confirmed: confirmed.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +1,89 @@
|
||||||
import {
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-action-buttons',
|
selector: "app-action-buttons",
|
||||||
templateUrl: 'action-buttons.component.html',
|
templateUrl: "action-buttons.component.html",
|
||||||
})
|
})
|
||||||
export class ActionButtonsComponent {
|
export class ActionButtonsComponent {
|
||||||
@Output() onView = new EventEmitter<CipherView>();
|
@Output() onView = new EventEmitter<CipherView>();
|
||||||
@Output() launchEvent = new EventEmitter<CipherView>();
|
@Output() launchEvent = new EventEmitter<CipherView>();
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() showView = false;
|
@Input() showView = false;
|
||||||
|
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
userHasPremiumAccess = false;
|
userHasPremiumAccess = false;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService,
|
constructor(
|
||||||
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
|
private i18nService: I18nService,
|
||||||
private totpService: TotpService, private stateService: StateService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private passwordRepromptService: PasswordRepromptService) { }
|
private eventService: EventService,
|
||||||
|
private totpService: TotpService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private passwordRepromptService: PasswordRepromptService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
||||||
|
}
|
||||||
|
|
||||||
|
launchCipher() {
|
||||||
|
this.launchEvent.emit(this.cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
|
||||||
|
if (
|
||||||
|
this.cipher.reprompt !== CipherRepromptType.None &&
|
||||||
|
this.passwordRepromptService.protectedFields().includes(aType) &&
|
||||||
|
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
launchCipher() {
|
if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) {
|
||||||
this.launchEvent.emit(this.cipher);
|
return;
|
||||||
|
} else if (value === cipher.login.totp) {
|
||||||
|
value = await this.totpService.getCode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
|
if (!cipher.viewPassword) {
|
||||||
if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) &&
|
return;
|
||||||
!await this.passwordRepromptService.showPasswordPrompt()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
|
|
||||||
return;
|
|
||||||
} else if (value === cipher.login.totp) {
|
|
||||||
value = await this.totpService.getCode(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cipher.viewPassword) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
|
||||||
this.platformUtilsService.showToast('info', null,
|
|
||||||
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
|
|
||||||
|
|
||||||
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') {
|
|
||||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
|
|
||||||
} else if (typeI18nKey === 'securityCode') {
|
|
||||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayTotpCopyButton(cipher: CipherView) {
|
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||||
return (cipher?.login?.hasTotp ?? false) &&
|
this.platformUtilsService.showToast(
|
||||||
(cipher.organizationUseTotp || this.userHasPremiumAccess);
|
"info",
|
||||||
}
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
|
||||||
|
);
|
||||||
|
|
||||||
view() {
|
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
|
||||||
this.onView.emit(this.cipher);
|
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
|
||||||
|
} else if (typeI18nKey === "securityCode") {
|
||||||
|
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayTotpCopyButton(cipher: CipherView) {
|
||||||
|
return (
|
||||||
|
(cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
this.onView.emit(this.cipher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,48 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
import {
|
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
|
||||||
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
|
|
||||||
} from 'jslib-angular/components/password-generator.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-password-generator',
|
selector: "app-password-generator",
|
||||||
templateUrl: 'password-generator.component.html',
|
templateUrl: "password-generator.component.html",
|
||||||
})
|
})
|
||||||
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
||||||
private cipherState: CipherView;
|
private cipherState: CipherView;
|
||||||
|
|
||||||
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, private stateService: StateService,
|
passwordGenerationService: PasswordGenerationService,
|
||||||
private location: Location) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
i18nService: I18nService,
|
||||||
}
|
private stateService: StateService,
|
||||||
|
private location: Location
|
||||||
|
) {
|
||||||
|
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
const addEditCipherInfo = await this.stateService.getAddEditCipherInfo();
|
const addEditCipherInfo = await this.stateService.getAddEditCipherInfo();
|
||||||
if (addEditCipherInfo != null) {
|
if (addEditCipherInfo != null) {
|
||||||
this.cipherState = addEditCipherInfo.cipher;
|
this.cipherState = addEditCipherInfo.cipher;
|
||||||
}
|
|
||||||
this.showSelect = this.cipherState != null;
|
|
||||||
}
|
}
|
||||||
|
this.showSelect = this.cipherState != null;
|
||||||
|
}
|
||||||
|
|
||||||
select() {
|
select() {
|
||||||
super.select();
|
super.select();
|
||||||
this.cipherState.login.password = this.password;
|
this.cipherState.login.password = this.password;
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,129 +1,150 @@
|
||||||
import {
|
import { DatePipe, Location } from "@angular/common";
|
||||||
DatePipe,
|
|
||||||
Location,
|
|
||||||
} from '@angular/common';
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
|
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/send/add-edit.component";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-send-add-edit',
|
selector: "app-send-add-edit",
|
||||||
templateUrl: 'send-add-edit.component.html',
|
templateUrl: "send-add-edit.component.html",
|
||||||
})
|
})
|
||||||
export class SendAddEditComponent extends BaseAddEditComponent {
|
export class SendAddEditComponent extends BaseAddEditComponent {
|
||||||
// Options header
|
// Options header
|
||||||
showOptions = false;
|
showOptions = false;
|
||||||
// File visibility
|
// File visibility
|
||||||
isFirefox = false;
|
isFirefox = false;
|
||||||
inPopout = false;
|
inPopout = false;
|
||||||
inSidebar = false;
|
inSidebar = false;
|
||||||
isLinux = false;
|
isLinux = false;
|
||||||
isUnsupportedMac = false;
|
isUnsupportedMac = false;
|
||||||
|
|
||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
stateService: StateService, messagingService: MessagingService, policyService: PolicyService,
|
i18nService: I18nService,
|
||||||
environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private route: ActivatedRoute, private router: Router, private location: Location,
|
stateService: StateService,
|
||||||
private popupUtilsService: PopupUtilsService, logService: LogService) {
|
messagingService: MessagingService,
|
||||||
super(i18nService, platformUtilsService, environmentService, datePipe,
|
policyService: PolicyService,
|
||||||
sendService, messagingService, policyService, logService, stateService);
|
environmentService: EnvironmentService,
|
||||||
|
datePipe: DatePipe,
|
||||||
|
sendService: SendService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private location: Location,
|
||||||
|
private popupUtilsService: PopupUtilsService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
environmentService,
|
||||||
|
datePipe,
|
||||||
|
sendService,
|
||||||
|
messagingService,
|
||||||
|
policyService,
|
||||||
|
logService,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showFileSelector(): boolean {
|
||||||
|
return !(this.editMode || this.showFilePopoutMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showFilePopoutMessage(): boolean {
|
||||||
|
return (
|
||||||
|
!this.editMode &&
|
||||||
|
(this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showFirefoxFileWarning(): boolean {
|
||||||
|
return this.isFirefox && !(this.inSidebar || this.inPopout);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showSafariFileWarning(): boolean {
|
||||||
|
return this.isSafari && !this.inPopout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show this for Chromium based browsers in Linux and Mac > Big Sur
|
||||||
|
get showChromiumFileWarning(): boolean {
|
||||||
|
return (
|
||||||
|
(this.isLinux || this.isUnsupportedMac) &&
|
||||||
|
!this.isFirefox &&
|
||||||
|
!(this.inSidebar || this.inPopout)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
popOutWindow() {
|
||||||
|
this.popupUtilsService.popOut(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
// File visilibity
|
||||||
|
this.isFirefox = this.platformUtilsService.isFirefox();
|
||||||
|
this.inPopout = this.popupUtilsService.inPopout(window);
|
||||||
|
this.inSidebar = this.popupUtilsService.inSidebar(window);
|
||||||
|
this.isLinux = window?.navigator?.userAgent.indexOf("Linux") !== -1;
|
||||||
|
this.isUnsupportedMac =
|
||||||
|
this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes("Mac OS X 11");
|
||||||
|
|
||||||
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
|
if (params.sendId) {
|
||||||
|
this.sendId = params.sendId;
|
||||||
|
}
|
||||||
|
if (params.type) {
|
||||||
|
const type = parseInt(params.type, null);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
await this.load();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (!this.editMode) {
|
||||||
|
document.getElementById("name").focus();
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit(): Promise<boolean> {
|
||||||
|
if (await super.submit()) {
|
||||||
|
this.cancel();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showFileSelector(): boolean {
|
return false;
|
||||||
return !(this.editMode || this.showFilePopoutMessage);
|
}
|
||||||
|
|
||||||
|
async delete(): Promise<boolean> {
|
||||||
|
if (await super.delete()) {
|
||||||
|
this.cancel();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showFilePopoutMessage(): boolean {
|
return false;
|
||||||
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning);
|
}
|
||||||
}
|
|
||||||
|
cancel() {
|
||||||
get showFirefoxFileWarning(): boolean {
|
// If true, the window was pop'd out on the add-send page. location.back will not work
|
||||||
return this.isFirefox && !(this.inSidebar || this.inPopout);
|
if ((window as any).previousPopupUrl.startsWith("/add-send")) {
|
||||||
}
|
this.router.navigate(["tabs/send"]);
|
||||||
|
} else {
|
||||||
get showSafariFileWarning(): boolean {
|
this.location.back();
|
||||||
return this.isSafari && !this.inPopout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only show this for Chromium based browsers in Linux and Mac > Big Sur
|
|
||||||
get showChromiumFileWarning(): boolean {
|
|
||||||
return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout);
|
|
||||||
}
|
|
||||||
|
|
||||||
popOutWindow() {
|
|
||||||
this.popupUtilsService.popOut(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
// File visilibity
|
|
||||||
this.isFirefox = this.platformUtilsService.isFirefox();
|
|
||||||
this.inPopout = this.popupUtilsService.inPopout(window);
|
|
||||||
this.inSidebar = this.popupUtilsService.inSidebar(window);
|
|
||||||
this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1;
|
|
||||||
this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11');
|
|
||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
|
||||||
if (params.sendId) {
|
|
||||||
this.sendId = params.sendId;
|
|
||||||
}
|
|
||||||
if (params.type) {
|
|
||||||
const type = parseInt(params.type, null);
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
await this.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
if (!this.editMode) {
|
|
||||||
document.getElementById('name').focus();
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit(): Promise<boolean> {
|
|
||||||
if (await super.submit()) {
|
|
||||||
this.cancel();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(): Promise<boolean> {
|
|
||||||
if (await super.delete()) {
|
|
||||||
this.cancel();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
// If true, the window was pop'd out on the add-send page. location.back will not work
|
|
||||||
if ((window as any).previousPopupUrl.startsWith('/add-send')) {
|
|
||||||
this.router.navigate(['tabs/send']);
|
|
||||||
} else {
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,185 +1,200 @@
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import {
|
import { Router } from "@angular/router";
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { SendView } from 'jslib-common/models/view/sendView';
|
import { SendView } from "jslib-common/models/view/sendView";
|
||||||
|
|
||||||
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
|
import { SendComponent as BaseSendComponent } from "jslib-angular/components/send/send.component";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { SendType } from 'jslib-common/enums/sendType';
|
import { SendType } from "jslib-common/enums/sendType";
|
||||||
import { BrowserSendComponentState } from '../../models/browserSendComponentState';
|
import { BrowserSendComponentState } from "../../models/browserSendComponentState";
|
||||||
|
|
||||||
const ComponentId = 'SendComponent';
|
const ComponentId = "SendComponent";
|
||||||
const ScopeStateId = ComponentId + 'Scope';
|
const ScopeStateId = ComponentId + "Scope";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-send-groupings',
|
selector: "app-send-groupings",
|
||||||
templateUrl: 'send-groupings.component.html',
|
templateUrl: "send-groupings.component.html",
|
||||||
})
|
})
|
||||||
export class SendGroupingsComponent extends BaseSendComponent {
|
export class SendGroupingsComponent extends BaseSendComponent {
|
||||||
// Header
|
// Header
|
||||||
showLeftHeader = true;
|
showLeftHeader = true;
|
||||||
// Send Type Calculations
|
// Send Type Calculations
|
||||||
typeCounts = new Map<SendType, number>();
|
typeCounts = new Map<SendType, number>();
|
||||||
// State Handling
|
// State Handling
|
||||||
state: BrowserSendComponentState;
|
state: BrowserSendComponentState;
|
||||||
private loadedTimeout: number;
|
private loadedTimeout: number;
|
||||||
|
|
||||||
constructor(sendService: SendService, i18nService: I18nService,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone,
|
sendService: SendService,
|
||||||
policyService: PolicyService, searchService: SearchService,
|
i18nService: I18nService,
|
||||||
private popupUtils: PopupUtilsService, private stateService: StateService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private router: Router, private syncService: SyncService,
|
environmentService: EnvironmentService,
|
||||||
private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService,
|
ngZone: NgZone,
|
||||||
logService: LogService) {
|
policyService: PolicyService,
|
||||||
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
|
searchService: SearchService,
|
||||||
policyService, logService);
|
private popupUtils: PopupUtilsService,
|
||||||
super.onSuccessfulLoad = async () => {
|
private stateService: StateService,
|
||||||
this.calculateTypeCounts();
|
private router: Router,
|
||||||
this.selectAll();
|
private syncService: SyncService,
|
||||||
};
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
sendService,
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
environmentService,
|
||||||
|
ngZone,
|
||||||
|
searchService,
|
||||||
|
policyService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
super.onSuccessfulLoad = async () => {
|
||||||
|
this.calculateTypeCounts();
|
||||||
|
this.selectAll();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
// Determine Header details
|
||||||
|
this.showLeftHeader = !(
|
||||||
|
this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()
|
||||||
|
);
|
||||||
|
// Clear state of Send Type Component
|
||||||
|
this.stateService.setBrowserSendComponentState(null);
|
||||||
|
// Let super class finish
|
||||||
|
await super.ngOnInit();
|
||||||
|
// Handle State Restore if necessary
|
||||||
|
const restoredScopeState = await this.restoreState();
|
||||||
|
if (this.state?.searchText != null) {
|
||||||
|
this.searchText = this.state.searchText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
if (!this.syncService.syncInProgress) {
|
||||||
// Determine Header details
|
this.load();
|
||||||
this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox());
|
} else {
|
||||||
// Clear state of Send Type Component
|
this.loadedTimeout = window.setTimeout(() => {
|
||||||
this.stateService.setBrowserSendComponentState(null);
|
if (!this.loaded) {
|
||||||
// Let super class finish
|
this.load();
|
||||||
await super.ngOnInit();
|
}
|
||||||
// Handle State Restore if necessary
|
}, 5000);
|
||||||
const restoredScopeState = await this.restoreState();
|
}
|
||||||
if (this.state?.searchText != null) {
|
|
||||||
this.searchText = this.state.searchText;
|
if (!this.syncService.syncInProgress || restoredScopeState) {
|
||||||
|
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all sends if sync completed in background
|
||||||
|
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "syncCompleted":
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.load();
|
||||||
|
}, 500);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.syncService.syncInProgress) {
|
this.changeDetectorRef.detectChanges();
|
||||||
this.load();
|
});
|
||||||
} else {
|
});
|
||||||
this.loadedTimeout = window.setTimeout(() => {
|
}
|
||||||
if (!this.loaded) {
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.syncService.syncInProgress || restoredScopeState) {
|
ngOnDestroy() {
|
||||||
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0);
|
// Remove timeout
|
||||||
}
|
if (this.loadedTimeout != null) {
|
||||||
|
window.clearTimeout(this.loadedTimeout);
|
||||||
|
}
|
||||||
|
// Save state
|
||||||
|
this.saveState();
|
||||||
|
// Unsubscribe
|
||||||
|
this.broadcasterService.unsubscribe(ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
// Load all sends if sync completed in background
|
async selectType(type: SendType) {
|
||||||
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
this.router.navigate(["/send-type"], { queryParams: { type: type } });
|
||||||
this.ngZone.run(async () => {
|
}
|
||||||
switch (message.command) {
|
|
||||||
case 'syncCompleted':
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.load();
|
|
||||||
}, 500);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
async selectSend(s: SendView) {
|
||||||
});
|
this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } });
|
||||||
});
|
}
|
||||||
|
|
||||||
|
async addSend() {
|
||||||
|
if (this.disableSend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.router.navigate(["/add-send"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async removePassword(s: SendView): Promise<boolean> {
|
||||||
|
if (this.disableSend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.removePassword(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
showSearching() {
|
||||||
|
return (
|
||||||
|
this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTypeCounts() {
|
||||||
|
// Create type counts
|
||||||
|
const typeCounts = new Map<SendType, number>();
|
||||||
|
this.sends.forEach((s) => {
|
||||||
|
if (typeCounts.has(s.type)) {
|
||||||
|
typeCounts.set(s.type, typeCounts.get(s.type) + 1);
|
||||||
|
} else {
|
||||||
|
typeCounts.set(s.type, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.typeCounts = typeCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveState() {
|
||||||
|
this.state = {
|
||||||
|
scrollY: this.popupUtils.getContentScrollY(window),
|
||||||
|
searchText: this.searchText,
|
||||||
|
sends: this.sends,
|
||||||
|
typeCounts: this.typeCounts,
|
||||||
|
};
|
||||||
|
await this.stateService.setBrowserSendComponentState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async restoreState(): Promise<boolean> {
|
||||||
|
this.state = await this.stateService.getBrowserSendComponentState();
|
||||||
|
if (this.state == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.state.sends != null) {
|
||||||
|
this.sends = this.state.sends;
|
||||||
|
}
|
||||||
|
if (this.state.typeCounts != null) {
|
||||||
|
this.typeCounts = this.state.typeCounts;
|
||||||
|
}
|
||||||
|
if (this.state.searchText != null) {
|
||||||
|
this.searchText = this.state.searchText;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
return true;
|
||||||
// Remove timeout
|
}
|
||||||
if (this.loadedTimeout != null) {
|
|
||||||
window.clearTimeout(this.loadedTimeout);
|
|
||||||
}
|
|
||||||
// Save state
|
|
||||||
this.saveState();
|
|
||||||
// Unsubscribe
|
|
||||||
this.broadcasterService.unsubscribe(ComponentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectType(type: SendType) {
|
|
||||||
this.router.navigate(['/send-type'], { queryParams: { type: type } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectSend(s: SendView) {
|
|
||||||
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async addSend() {
|
|
||||||
if (this.disableSend) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.router.navigate(['/add-send']);
|
|
||||||
}
|
|
||||||
|
|
||||||
async removePassword(s: SendView): Promise<boolean> {
|
|
||||||
if (this.disableSend) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.removePassword(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
showSearching() {
|
|
||||||
return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText));
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateTypeCounts() {
|
|
||||||
// Create type counts
|
|
||||||
const typeCounts = new Map<SendType, number>();
|
|
||||||
this.sends.forEach(s => {
|
|
||||||
if (typeCounts.has(s.type)) {
|
|
||||||
typeCounts.set(s.type, typeCounts.get(s.type) + 1);
|
|
||||||
} else {
|
|
||||||
typeCounts.set(s.type, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.typeCounts = typeCounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveState() {
|
|
||||||
this.state = {
|
|
||||||
scrollY: this.popupUtils.getContentScrollY(window),
|
|
||||||
searchText: this.searchText,
|
|
||||||
sends: this.sends,
|
|
||||||
typeCounts: this.typeCounts,
|
|
||||||
};
|
|
||||||
await this.stateService.setBrowserSendComponentState(this.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async restoreState(): Promise<boolean> {
|
|
||||||
this.state = await this.stateService.getBrowserSendComponentState();
|
|
||||||
if (this.state == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.state.sends != null) {
|
|
||||||
this.sends = this.state.sends;
|
|
||||||
}
|
|
||||||
if (this.state.typeCounts != null) {
|
|
||||||
this.typeCounts = this.state.typeCounts;
|
|
||||||
}
|
|
||||||
if (this.state.searchText != null) {
|
|
||||||
this.searchText = this.state.searchText;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,158 +1,170 @@
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
|
|
||||||
import { SendView } from 'jslib-common/models/view/sendView';
|
import { SendView } from "jslib-common/models/view/sendView";
|
||||||
|
|
||||||
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
|
import { SendComponent as BaseSendComponent } from "jslib-angular/components/send/send.component";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { SendType } from 'jslib-common/enums/sendType';
|
import { SendType } from "jslib-common/enums/sendType";
|
||||||
import { BrowserComponentState } from '../../models/browserComponentState';
|
import { BrowserComponentState } from "../../models/browserComponentState";
|
||||||
|
|
||||||
const ComponentId = 'SendTypeComponent';
|
const ComponentId = "SendTypeComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-send-type',
|
selector: "app-send-type",
|
||||||
templateUrl: 'send-type.component.html',
|
templateUrl: "send-type.component.html",
|
||||||
})
|
})
|
||||||
export class SendTypeComponent extends BaseSendComponent {
|
export class SendTypeComponent extends BaseSendComponent {
|
||||||
groupingTitle: string;
|
groupingTitle: string;
|
||||||
// State Handling
|
// State Handling
|
||||||
state: BrowserComponentState;
|
state: BrowserComponentState;
|
||||||
private refreshTimeout: number;
|
private refreshTimeout: number;
|
||||||
private applySavedState = true;
|
private applySavedState = true;
|
||||||
|
|
||||||
constructor(sendService: SendService, i18nService: I18nService,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone,
|
sendService: SendService,
|
||||||
policyService: PolicyService, searchService: SearchService,
|
i18nService: I18nService,
|
||||||
private popupUtils: PopupUtilsService, private stateService: StateService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef,
|
environmentService: EnvironmentService,
|
||||||
private broadcasterService: BroadcasterService, private router: Router, logService: LogService) {
|
ngZone: NgZone,
|
||||||
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
|
policyService: PolicyService,
|
||||||
policyService, logService);
|
searchService: SearchService,
|
||||||
super.onSuccessfulLoad = async () => {
|
private popupUtils: PopupUtilsService,
|
||||||
this.selectType(this.type);
|
private stateService: StateService,
|
||||||
};
|
private route: ActivatedRoute,
|
||||||
this.applySavedState = (window as any).previousPopupUrl != null &&
|
private location: Location,
|
||||||
!(window as any).previousPopupUrl.startsWith('/send-type');
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
}
|
private broadcasterService: BroadcasterService,
|
||||||
|
private router: Router,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
sendService,
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
environmentService,
|
||||||
|
ngZone,
|
||||||
|
searchService,
|
||||||
|
policyService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
super.onSuccessfulLoad = async () => {
|
||||||
|
this.selectType(this.type);
|
||||||
|
};
|
||||||
|
this.applySavedState =
|
||||||
|
(window as any).previousPopupUrl != null &&
|
||||||
|
!(window as any).previousPopupUrl.startsWith("/send-type");
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
// Let super class finish
|
// Let super class finish
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
if (this.applySavedState) {
|
if (this.applySavedState) {
|
||||||
this.state = (await this.stateService.getBrowserSendTypeComponentState());
|
this.state = await this.stateService.getBrowserSendTypeComponentState();
|
||||||
if (this.state.searchText != null) {
|
if (this.state.searchText != null) {
|
||||||
this.searchText = this.state.searchText;
|
this.searchText = this.state.searchText;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.type != null) {
|
|
||||||
this.type = parseInt(params.type, null);
|
|
||||||
switch (this.type) {
|
|
||||||
case SendType.Text:
|
|
||||||
this.groupingTitle = this.i18nService.t('sendTypeText');
|
|
||||||
break;
|
|
||||||
case SendType.File:
|
|
||||||
this.groupingTitle = this.i18nService.t('sendTypeFile');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await this.load(s => s.type === this.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore state and remove reference
|
|
||||||
if (this.applySavedState && this.state != null) {
|
|
||||||
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state?.scrollY), 0);
|
|
||||||
}
|
|
||||||
this.stateService.setBrowserSendComponentState(null);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh Send list if sync completed in background
|
|
||||||
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
|
||||||
this.ngZone.run(async () => {
|
|
||||||
switch (message.command) {
|
|
||||||
case 'syncCompleted':
|
|
||||||
if (message.successfully) {
|
|
||||||
this.refreshTimeout = window.setTimeout(() => {
|
|
||||||
this.refresh();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
// Remove timeout
|
|
||||||
if (this.refreshTimeout != null) {
|
|
||||||
window.clearTimeout(this.refreshTimeout);
|
|
||||||
}
|
}
|
||||||
// Save state
|
}
|
||||||
this.saveState();
|
|
||||||
// Unsubscribe
|
|
||||||
this.broadcasterService.unsubscribe(ComponentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectSend(s: SendView) {
|
if (params.type != null) {
|
||||||
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
|
this.type = parseInt(params.type, null);
|
||||||
}
|
switch (this.type) {
|
||||||
|
case SendType.Text:
|
||||||
async addSend() {
|
this.groupingTitle = this.i18nService.t("sendTypeText");
|
||||||
if (this.disableSend) {
|
break;
|
||||||
return;
|
case SendType.File:
|
||||||
|
this.groupingTitle = this.i18nService.t("sendTypeFile");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.router.navigate(['/add-send'], { queryParams: { type: this.type } });
|
await this.load((s) => s.type === this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePassword(s: SendView): Promise<boolean> {
|
// Restore state and remove reference
|
||||||
if (this.disableSend) {
|
if (this.applySavedState && this.state != null) {
|
||||||
return;
|
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state?.scrollY), 0);
|
||||||
|
}
|
||||||
|
this.stateService.setBrowserSendComponentState(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh Send list if sync completed in background
|
||||||
|
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "syncCompleted":
|
||||||
|
if (message.successfully) {
|
||||||
|
this.refreshTimeout = window.setTimeout(() => {
|
||||||
|
this.refresh();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
super.removePassword(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
back() {
|
this.changeDetectorRef.detectChanges();
|
||||||
(window as any).routeDirection = 'b';
|
});
|
||||||
this.location.back();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveState() {
|
ngOnDestroy() {
|
||||||
this.state = {
|
// Remove timeout
|
||||||
scrollY: this.popupUtils.getContentScrollY(window),
|
if (this.refreshTimeout != null) {
|
||||||
searchText: this.searchText,
|
window.clearTimeout(this.refreshTimeout);
|
||||||
};
|
|
||||||
await this.stateService.setBrowserSendTypeComponentState(this.state);
|
|
||||||
}
|
}
|
||||||
|
// Save state
|
||||||
|
this.saveState();
|
||||||
|
// Unsubscribe
|
||||||
|
this.broadcasterService.unsubscribe(ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectSend(s: SendView) {
|
||||||
|
this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSend() {
|
||||||
|
if (this.disableSend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.router.navigate(["/add-send"], { queryParams: { type: this.type } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async removePassword(s: SendView): Promise<boolean> {
|
||||||
|
if (this.disableSend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.removePassword(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
back() {
|
||||||
|
(window as any).routeDirection = "b";
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveState() {
|
||||||
|
this.state = {
|
||||||
|
scrollY: this.popupUtils.getContentScrollY(window),
|
||||||
|
searchText: this.searchText,
|
||||||
|
};
|
||||||
|
await this.stateService.setBrowserSendTypeComponentState(this.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,223 +1,282 @@
|
||||||
import {
|
import { APP_INITIALIZER, LOCALE_ID, NgModule } from "@angular/core";
|
||||||
APP_INITIALIZER,
|
|
||||||
LOCALE_ID,
|
|
||||||
NgModule,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { DebounceNavigationService } from './debounceNavigationService';
|
import { DebounceNavigationService } from "./debounceNavigationService";
|
||||||
import { LaunchGuardService } from './launch-guard.service';
|
import { LaunchGuardService } from "./launch-guard.service";
|
||||||
import { LockGuardService } from './lock-guard.service';
|
import { LockGuardService } from "./lock-guard.service";
|
||||||
import { PasswordRepromptService } from './password-reprompt.service';
|
import { PasswordRepromptService } from "./password-reprompt.service";
|
||||||
import { UnauthGuardService } from './unauth-guard.service';
|
import { UnauthGuardService } from "./unauth-guard.service";
|
||||||
|
|
||||||
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module';
|
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
|
||||||
import { LockGuardService as BaseLockGuardService } from 'jslib-angular/services/lock-guard.service';
|
import { LockGuardService as BaseLockGuardService } from "jslib-angular/services/lock-guard.service";
|
||||||
import { UnauthGuardService as BaseUnauthGuardService } from 'jslib-angular/services/unauth-guard.service';
|
import { UnauthGuardService as BaseUnauthGuardService } from "jslib-angular/services/unauth-guard.service";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||||
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
|
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { ExportService } from 'jslib-common/abstractions/export.service';
|
import { ExportService } from "jslib-common/abstractions/export.service";
|
||||||
import { FileUploadService } from 'jslib-common/abstractions/fileUpload.service';
|
import { FileUploadService } from "jslib-common/abstractions/fileUpload.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
|
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { ProviderService } from 'jslib-common/abstractions/provider.service';
|
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||||
import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service';
|
import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
|
||||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
import { SendService } from "jslib-common/abstractions/send.service";
|
||||||
import { SettingsService } from 'jslib-common/abstractions/settings.service';
|
import { SettingsService } from "jslib-common/abstractions/settings.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
|
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { AutofillService } from '../../services/abstractions/autofill.service';
|
import { AutofillService } from "../../services/abstractions/autofill.service";
|
||||||
import BrowserMessagingService from '../../services/browserMessaging.service';
|
import BrowserMessagingService from "../../services/browserMessaging.service";
|
||||||
|
|
||||||
import { AuthService } from 'jslib-common/services/auth.service';
|
import { AuthService } from "jslib-common/services/auth.service";
|
||||||
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service';
|
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
|
||||||
import { SearchService } from 'jslib-common/services/search.service';
|
import { SearchService } from "jslib-common/services/search.service";
|
||||||
|
|
||||||
import { PopupSearchService } from './popup-search.service';
|
import { PopupSearchService } from "./popup-search.service";
|
||||||
import { PopupUtilsService } from './popup-utils.service';
|
import { PopupUtilsService } from "./popup-utils.service";
|
||||||
|
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
import { ThemeType } from "jslib-common/enums/themeType";
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from "../../services/state.service";
|
||||||
|
|
||||||
import { StateService as StateServiceAbstraction } from '../../services/abstractions/state.service';
|
import { StateService as StateServiceAbstraction } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
function getBgService<T>(service: string) {
|
function getBgService<T>(service: string) {
|
||||||
return (): T => {
|
return (): T => {
|
||||||
const page = BrowserApi.getBackgroundPage();
|
const page = BrowserApi.getBackgroundPage();
|
||||||
return page ? page.bitwardenMain[service] as T : null;
|
return page ? (page.bitwardenMain[service] as T) : null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPrivateMode = BrowserApi.getBackgroundPage() == null;
|
const isPrivateMode = BrowserApi.getBackgroundPage() == null;
|
||||||
|
|
||||||
export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService,
|
export function initFactory(
|
||||||
storageService: StorageService, popupUtilsService: PopupUtilsService, stateService: StateServiceAbstraction,
|
platformUtilsService: PlatformUtilsService,
|
||||||
logService: LogServiceAbstraction): Function {
|
i18nService: I18nService,
|
||||||
return async () => {
|
storageService: StorageService,
|
||||||
if (!popupUtilsService.inPopup(window)) {
|
popupUtilsService: PopupUtilsService,
|
||||||
window.document.body.classList.add('body-full');
|
stateService: StateServiceAbstraction,
|
||||||
} else if (window.screen.availHeight < 600) {
|
logService: LogServiceAbstraction
|
||||||
window.document.body.classList.add('body-xs');
|
): Function {
|
||||||
} else if (window.screen.availHeight <= 800) {
|
return async () => {
|
||||||
window.document.body.classList.add('body-sm');
|
if (!popupUtilsService.inPopup(window)) {
|
||||||
}
|
window.document.body.classList.add("body-full");
|
||||||
|
} else if (window.screen.availHeight < 600) {
|
||||||
|
window.document.body.classList.add("body-xs");
|
||||||
|
} else if (window.screen.availHeight <= 800) {
|
||||||
|
window.document.body.classList.add("body-sm");
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPrivateMode) {
|
if (!isPrivateMode) {
|
||||||
const htmlEl = window.document.documentElement;
|
const htmlEl = window.document.documentElement;
|
||||||
const theme = await platformUtilsService.getEffectiveTheme();
|
const theme = await platformUtilsService.getEffectiveTheme();
|
||||||
htmlEl.classList.add('theme_' + theme);
|
htmlEl.classList.add("theme_" + theme);
|
||||||
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
|
platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
|
||||||
const bwTheme = await stateService.getTheme();
|
const bwTheme = await stateService.getTheme();
|
||||||
if (bwTheme == null || bwTheme === ThemeType.System) {
|
if (bwTheme == null || bwTheme === ThemeType.System) {
|
||||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
|
||||||
htmlEl.classList.add('theme_' + sysTheme);
|
htmlEl.classList.add("theme_" + sysTheme);
|
||||||
}
|
|
||||||
});
|
|
||||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
|
||||||
|
|
||||||
// Workaround for slow performance on external monitors on Chrome + MacOS
|
|
||||||
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
|
|
||||||
if (platformUtilsService.isChrome() &&
|
|
||||||
navigator.platform.indexOf('Mac') > -1 &&
|
|
||||||
popupUtilsService.inPopup(window) &&
|
|
||||||
(window.screenLeft < 0 ||
|
|
||||||
window.screenTop < 0 ||
|
|
||||||
window.screenLeft > window.screen.width ||
|
|
||||||
window.screenTop > window.screen.height)) {
|
|
||||||
htmlEl.classList.add('force_redraw');
|
|
||||||
logService.info('Force redraw is on');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
htmlEl.classList.add("locale_" + i18nService.translationLocale);
|
||||||
|
|
||||||
|
// Workaround for slow performance on external monitors on Chrome + MacOS
|
||||||
|
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
|
||||||
|
if (
|
||||||
|
platformUtilsService.isChrome() &&
|
||||||
|
navigator.platform.indexOf("Mac") > -1 &&
|
||||||
|
popupUtilsService.inPopup(window) &&
|
||||||
|
(window.screenLeft < 0 ||
|
||||||
|
window.screenTop < 0 ||
|
||||||
|
window.screenLeft > window.screen.width ||
|
||||||
|
window.screenTop > window.screen.height)
|
||||||
|
) {
|
||||||
|
htmlEl.classList.add("force_redraw");
|
||||||
|
logService.info("Force redraw is on");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [JslibServicesModule],
|
||||||
JslibServicesModule,
|
declarations: [],
|
||||||
],
|
providers: [
|
||||||
declarations: [],
|
{
|
||||||
providers: [
|
provide: LOCALE_ID,
|
||||||
{
|
useFactory: () =>
|
||||||
provide: LOCALE_ID,
|
isPrivateMode ? null : getBgService<I18nService>("i18nService")().translationLocale,
|
||||||
useFactory: () => isPrivateMode ? null : getBgService<I18nService>('i18nService')().translationLocale,
|
deps: [],
|
||||||
deps: [],
|
},
|
||||||
},
|
{
|
||||||
{
|
provide: APP_INITIALIZER,
|
||||||
provide: APP_INITIALIZER,
|
useFactory: initFactory,
|
||||||
useFactory: initFactory,
|
deps: [
|
||||||
deps: [
|
PlatformUtilsService,
|
||||||
PlatformUtilsService,
|
I18nService,
|
||||||
I18nService,
|
StorageService,
|
||||||
StorageService,
|
|
||||||
PopupUtilsService,
|
|
||||||
StateServiceAbstraction,
|
|
||||||
LogServiceAbstraction,
|
|
||||||
],
|
|
||||||
multi: true,
|
|
||||||
},
|
|
||||||
LaunchGuardService,
|
|
||||||
{ provide: BaseLockGuardService, useClass: LockGuardService },
|
|
||||||
{ provide: BaseUnauthGuardService, useClass: UnauthGuardService },
|
|
||||||
DebounceNavigationService,
|
|
||||||
PopupUtilsService,
|
PopupUtilsService,
|
||||||
{ provide: MessagingService, useClass: BrowserMessagingService },
|
StateServiceAbstraction,
|
||||||
{ provide: AuthServiceAbstraction, useFactory: getBgService<AuthService>('authService'), deps: [] },
|
LogServiceAbstraction,
|
||||||
{ provide: StateServiceAbstraction, useFactory: getBgService<StateService>('stateService') },
|
],
|
||||||
{
|
multi: true,
|
||||||
provide: SearchServiceAbstraction,
|
},
|
||||||
useFactory: (cipherService: CipherService, logService: ConsoleLogService, i18nService: I18nService) => {
|
LaunchGuardService,
|
||||||
return isPrivateMode ? null : new PopupSearchService(getBgService<SearchService>('searchService')(),
|
{ provide: BaseLockGuardService, useClass: LockGuardService },
|
||||||
cipherService, logService, i18nService);
|
{ provide: BaseUnauthGuardService, useClass: UnauthGuardService },
|
||||||
},
|
DebounceNavigationService,
|
||||||
deps: [
|
PopupUtilsService,
|
||||||
CipherService,
|
{ provide: MessagingService, useClass: BrowserMessagingService },
|
||||||
LogServiceAbstraction,
|
{
|
||||||
I18nService,
|
provide: AuthServiceAbstraction,
|
||||||
],
|
useFactory: getBgService<AuthService>("authService"),
|
||||||
},
|
deps: [],
|
||||||
{ provide: AuditService, useFactory: getBgService<AuditService>('auditService'), deps: [] },
|
},
|
||||||
{ provide: FileUploadService, useFactory: getBgService<FileUploadService>('fileUploadService'), deps: [] },
|
{ provide: StateServiceAbstraction, useFactory: getBgService<StateService>("stateService") },
|
||||||
{ provide: CipherService, useFactory: getBgService<CipherService>('cipherService'), deps: [] },
|
{
|
||||||
{
|
provide: SearchServiceAbstraction,
|
||||||
provide: CryptoFunctionService,
|
useFactory: (
|
||||||
useFactory: getBgService<CryptoFunctionService>('cryptoFunctionService'),
|
cipherService: CipherService,
|
||||||
deps: [],
|
logService: ConsoleLogService,
|
||||||
},
|
i18nService: I18nService
|
||||||
{ provide: FolderService, useFactory: getBgService<FolderService>('folderService'), deps: [] },
|
) => {
|
||||||
{ provide: CollectionService, useFactory: getBgService<CollectionService>('collectionService'), deps: [] },
|
return isPrivateMode
|
||||||
{ provide: LogServiceAbstraction, useFactory: getBgService<ConsoleLogService>('logService'), deps: [] },
|
? null
|
||||||
{ provide: EnvironmentService, useFactory: getBgService<EnvironmentService>('environmentService'), deps: [] },
|
: new PopupSearchService(
|
||||||
{ provide: TotpService, useFactory: getBgService<TotpService>('totpService'), deps: [] },
|
getBgService<SearchService>("searchService")(),
|
||||||
{ provide: TokenService, useFactory: getBgService<TokenService>('tokenService'), deps: [] },
|
cipherService,
|
||||||
{ provide: I18nService, useFactory: getBgService<I18nService>('i18nService'), deps: [] },
|
logService,
|
||||||
{ provide: CryptoService, useFactory: getBgService<CryptoService>('cryptoService'), deps: [] },
|
i18nService
|
||||||
{ provide: EventService, useFactory: getBgService<EventService>('eventService'), deps: [] },
|
);
|
||||||
{ provide: PolicyService, useFactory: getBgService<PolicyService>('policyService'), deps: [] },
|
},
|
||||||
{
|
deps: [CipherService, LogServiceAbstraction, I18nService],
|
||||||
provide: PlatformUtilsService,
|
},
|
||||||
useFactory: getBgService<PlatformUtilsService>('platformUtilsService'),
|
{ provide: AuditService, useFactory: getBgService<AuditService>("auditService"), deps: [] },
|
||||||
deps: [],
|
{
|
||||||
},
|
provide: FileUploadService,
|
||||||
{
|
useFactory: getBgService<FileUploadService>("fileUploadService"),
|
||||||
provide: PasswordGenerationService,
|
deps: [],
|
||||||
useFactory: getBgService<PasswordGenerationService>('passwordGenerationService'),
|
},
|
||||||
deps: [],
|
{ provide: CipherService, useFactory: getBgService<CipherService>("cipherService"), deps: [] },
|
||||||
},
|
{
|
||||||
{ provide: ApiService, useFactory: getBgService<ApiService>('apiService'), deps: [] },
|
provide: CryptoFunctionService,
|
||||||
{ provide: SyncService, useFactory: getBgService<SyncService>('syncService'), deps: [] },
|
useFactory: getBgService<CryptoFunctionService>("cryptoFunctionService"),
|
||||||
{ provide: SettingsService, useFactory: getBgService<SettingsService>('settingsService'), deps: [] },
|
deps: [],
|
||||||
{ provide: StorageService, useFactory: getBgService<StorageService>('storageService'), deps: [] },
|
},
|
||||||
{ provide: AppIdService, useFactory: getBgService<AppIdService>('appIdService'), deps: [] },
|
{ provide: FolderService, useFactory: getBgService<FolderService>("folderService"), deps: [] },
|
||||||
{ provide: AutofillService, useFactory: getBgService<AutofillService>('autofillService'), deps: [] },
|
{
|
||||||
{ provide: ExportService, useFactory: getBgService<ExportService>('exportService'), deps: [] },
|
provide: CollectionService,
|
||||||
{ provide: SendService, useFactory: getBgService<SendService>('sendService'), deps: [] },
|
useFactory: getBgService<CollectionService>("collectionService"),
|
||||||
{ provide: KeyConnectorService, useFactory: getBgService<KeyConnectorService>('keyConnectorService'), deps: [] },
|
deps: [],
|
||||||
{
|
},
|
||||||
provide: UserVerificationService,
|
{
|
||||||
useFactory: getBgService<UserVerificationService>('userVerificationService'),
|
provide: LogServiceAbstraction,
|
||||||
deps: [],
|
useFactory: getBgService<ConsoleLogService>("logService"),
|
||||||
},
|
deps: [],
|
||||||
{
|
},
|
||||||
provide: VaultTimeoutService,
|
{
|
||||||
useFactory: getBgService<VaultTimeoutService>('vaultTimeoutService'),
|
provide: EnvironmentService,
|
||||||
deps: [],
|
useFactory: getBgService<EnvironmentService>("environmentService"),
|
||||||
},
|
deps: [],
|
||||||
{
|
},
|
||||||
provide: NotificationsService,
|
{ provide: TotpService, useFactory: getBgService<TotpService>("totpService"), deps: [] },
|
||||||
useFactory: getBgService<NotificationsService>('notificationsService'),
|
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
|
||||||
deps: [],
|
{ provide: I18nService, useFactory: getBgService<I18nService>("i18nService"), deps: [] },
|
||||||
},
|
{ provide: CryptoService, useFactory: getBgService<CryptoService>("cryptoService"), deps: [] },
|
||||||
{ provide: LogServiceAbstraction, useFactory: getBgService<ConsoleLogService>('logService'), deps: [] },
|
{ provide: EventService, useFactory: getBgService<EventService>("eventService"), deps: [] },
|
||||||
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
{ provide: PolicyService, useFactory: getBgService<PolicyService>("policyService"), deps: [] },
|
||||||
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
{
|
||||||
{ provide: OrganizationService, useFactory: getBgService<OrganizationService>('organizationService'), deps: [] },
|
provide: PlatformUtilsService,
|
||||||
{ provide: ProviderService, useFactory: getBgService<ProviderService>('providerService'), deps: [] },
|
useFactory: getBgService<PlatformUtilsService>("platformUtilsService"),
|
||||||
{ provide: 'SECURE_STORAGE', useFactory: getBgService<StorageService>('secureStorageService'), deps: [] },
|
deps: [],
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordGenerationService,
|
||||||
|
useFactory: getBgService<PasswordGenerationService>("passwordGenerationService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{ provide: ApiService, useFactory: getBgService<ApiService>("apiService"), deps: [] },
|
||||||
|
{ provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] },
|
||||||
|
{
|
||||||
|
provide: SettingsService,
|
||||||
|
useFactory: getBgService<SettingsService>("settingsService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StorageService,
|
||||||
|
useFactory: getBgService<StorageService>("storageService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{ provide: AppIdService, useFactory: getBgService<AppIdService>("appIdService"), deps: [] },
|
||||||
|
{
|
||||||
|
provide: AutofillService,
|
||||||
|
useFactory: getBgService<AutofillService>("autofillService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{ provide: ExportService, useFactory: getBgService<ExportService>("exportService"), deps: [] },
|
||||||
|
{ provide: SendService, useFactory: getBgService<SendService>("sendService"), deps: [] },
|
||||||
|
{
|
||||||
|
provide: KeyConnectorService,
|
||||||
|
useFactory: getBgService<KeyConnectorService>("keyConnectorService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserVerificationService,
|
||||||
|
useFactory: getBgService<UserVerificationService>("userVerificationService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: VaultTimeoutService,
|
||||||
|
useFactory: getBgService<VaultTimeoutService>("vaultTimeoutService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NotificationsService,
|
||||||
|
useFactory: getBgService<NotificationsService>("notificationsService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: LogServiceAbstraction,
|
||||||
|
useFactory: getBgService<ConsoleLogService>("logService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||||
|
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useFactory: getBgService<OrganizationService>("organizationService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ProviderService,
|
||||||
|
useFactory: getBgService<ProviderService>("providerService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: "SECURE_STORAGE",
|
||||||
|
useFactory: getBgService<StorageService>("secureStorageService"),
|
||||||
|
deps: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ServicesModule {
|
export class ServicesModule {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,114 +1,118 @@
|
||||||
import {
|
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
interface ExcludedDomain {
|
interface ExcludedDomain {
|
||||||
uri: string;
|
uri: string;
|
||||||
showCurrentUris: boolean;
|
showCurrentUris: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'excludedDomains';
|
const BroadcasterSubscriptionId = "excludedDomains";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-excluded-domains',
|
selector: "app-excluded-domains",
|
||||||
templateUrl: 'excluded-domains.component.html',
|
templateUrl: "excluded-domains.component.html",
|
||||||
})
|
})
|
||||||
export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
||||||
excludedDomains: ExcludedDomain[] = [];
|
excludedDomains: ExcludedDomain[] = [];
|
||||||
currentUris: string[];
|
currentUris: string[];
|
||||||
loadCurrentUrisTimeout: number;
|
loadCurrentUrisTimeout: number;
|
||||||
|
|
||||||
constructor(private stateService: StateService,
|
constructor(
|
||||||
private i18nService: I18nService, private router: Router,
|
private stateService: StateService,
|
||||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService) {
|
private router: Router,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private platformUtilsService: PlatformUtilsService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
const savedDomains = await this.stateService.getNeverDomains();
|
||||||
|
if (savedDomains) {
|
||||||
|
for (const uri of Object.keys(savedDomains)) {
|
||||||
|
this.excludedDomains.push({ uri: uri, showCurrentUris: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
await this.loadCurrentUris();
|
||||||
const savedDomains = await this.stateService.getNeverDomains();
|
|
||||||
if (savedDomains) {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
for (const uri of Object.keys(savedDomains)) {
|
this.ngZone.run(async () => {
|
||||||
this.excludedDomains.push({ uri: uri, showCurrentUris: false });
|
switch (message.command) {
|
||||||
|
case "tabChanged":
|
||||||
|
case "windowChanged":
|
||||||
|
if (this.loadCurrentUrisTimeout != null) {
|
||||||
|
window.clearTimeout(this.loadCurrentUrisTimeout);
|
||||||
}
|
}
|
||||||
|
this.loadCurrentUrisTimeout = window.setTimeout(
|
||||||
|
async () => await this.loadCurrentUris(),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await this.loadCurrentUris();
|
ngOnDestroy() {
|
||||||
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
async addUri() {
|
||||||
this.ngZone.run(async () => {
|
this.excludedDomains.push({ uri: "", showCurrentUris: false });
|
||||||
switch (message.command) {
|
}
|
||||||
case 'tabChanged':
|
|
||||||
case 'windowChanged':
|
|
||||||
if (this.loadCurrentUrisTimeout != null) {
|
|
||||||
window.clearTimeout(this.loadCurrentUrisTimeout);
|
|
||||||
}
|
|
||||||
this.loadCurrentUrisTimeout = window.setTimeout(async () => await this.loadCurrentUris(), 500);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
async removeUri(i: number) {
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
this.excludedDomains.splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUri() {
|
async submit() {
|
||||||
this.excludedDomains.push({ uri: '', showCurrentUris: false });
|
const savedDomains: { [name: string]: null } = {};
|
||||||
}
|
for (const domain of this.excludedDomains) {
|
||||||
|
if (domain.uri && domain.uri !== "") {
|
||||||
async removeUri(i: number) {
|
const validDomain = Utils.getHostname(domain.uri);
|
||||||
this.excludedDomains.splice(i, 1);
|
if (!validDomain) {
|
||||||
}
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
async submit() {
|
null,
|
||||||
const savedDomains: { [name: string]: null } = {};
|
this.i18nService.t("excludedDomainsInvalidDomain", domain.uri)
|
||||||
for (const domain of this.excludedDomains) {
|
);
|
||||||
if (domain.uri && domain.uri !== '') {
|
return;
|
||||||
const validDomain = Utils.getHostname(domain.uri);
|
|
||||||
if (!validDomain) {
|
|
||||||
this.platformUtilsService.showToast('error', null,
|
|
||||||
this.i18nService.t('excludedDomainsInvalidDomain', domain.uri));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
savedDomains[validDomain] = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await this.stateService.setNeverDomains(savedDomains);
|
savedDomains[validDomain] = null;
|
||||||
this.router.navigate(['/tabs/settings']);
|
}
|
||||||
}
|
}
|
||||||
|
await this.stateService.setNeverDomains(savedDomains);
|
||||||
|
this.router.navigate(["/tabs/settings"]);
|
||||||
|
}
|
||||||
|
|
||||||
trackByFunction(index: number, item: any) {
|
trackByFunction(index: number, item: any) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleUriInput(domain: ExcludedDomain) {
|
toggleUriInput(domain: ExcludedDomain) {
|
||||||
domain.showCurrentUris = !domain.showCurrentUris;
|
domain.showCurrentUris = !domain.showCurrentUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCurrentUris() {
|
async loadCurrentUris() {
|
||||||
const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' });
|
const tabs = await BrowserApi.tabsQuery({ windowType: "normal" });
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
const uriSet = new Set(tabs.map(tab => Utils.getHostname(tab.url)));
|
const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url)));
|
||||||
uriSet.delete(null);
|
uriSet.delete(null);
|
||||||
this.currentUris = Array.from(uriSet);
|
this.currentUris = Array.from(uriSet);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,156 +1,161 @@
|
||||||
import {
|
import { Component, OnInit } from "@angular/core";
|
||||||
Component,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
import { ThemeType } from "jslib-common/enums/themeType";
|
||||||
import { UriMatchType } from 'jslib-common/enums/uriMatchType';
|
import { UriMatchType } from "jslib-common/enums/uriMatchType";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-options',
|
selector: "app-options",
|
||||||
templateUrl: 'options.component.html',
|
templateUrl: "options.component.html",
|
||||||
})
|
})
|
||||||
export class OptionsComponent implements OnInit {
|
export class OptionsComponent implements OnInit {
|
||||||
disableFavicon = false;
|
disableFavicon = false;
|
||||||
disableBadgeCounter = false;
|
disableBadgeCounter = false;
|
||||||
enableAutoFillOnPageLoad = false;
|
enableAutoFillOnPageLoad = false;
|
||||||
autoFillOnPageLoadDefault = false;
|
autoFillOnPageLoadDefault = false;
|
||||||
autoFillOnPageLoadOptions: any[];
|
autoFillOnPageLoadOptions: any[];
|
||||||
disableAutoTotpCopy = false;
|
disableAutoTotpCopy = false;
|
||||||
disableContextMenuItem = false;
|
disableContextMenuItem = false;
|
||||||
disableAddLoginNotification = false;
|
disableAddLoginNotification = false;
|
||||||
disableChangedPasswordNotification = false;
|
disableChangedPasswordNotification = false;
|
||||||
dontShowCards = false;
|
dontShowCards = false;
|
||||||
dontShowIdentities = false;
|
dontShowIdentities = false;
|
||||||
showClearClipboard = true;
|
showClearClipboard = true;
|
||||||
theme: string;
|
theme: string;
|
||||||
themeOptions: any[];
|
themeOptions: any[];
|
||||||
defaultUriMatch = UriMatchType.Domain;
|
defaultUriMatch = UriMatchType.Domain;
|
||||||
uriMatchOptions: any[];
|
uriMatchOptions: any[];
|
||||||
clearClipboard: number;
|
clearClipboard: number;
|
||||||
clearClipboardOptions: any[];
|
clearClipboardOptions: any[];
|
||||||
showGeneral: boolean = true;
|
showGeneral: boolean = true;
|
||||||
showAutofill: boolean = true;
|
showAutofill: boolean = true;
|
||||||
showDisplay: boolean = true;
|
showDisplay: boolean = true;
|
||||||
|
|
||||||
constructor(private messagingService: MessagingService, private stateService: StateService,
|
constructor(
|
||||||
private totpService: TotpService, i18nService: I18nService) {
|
private messagingService: MessagingService,
|
||||||
this.themeOptions = [
|
private stateService: StateService,
|
||||||
{ name: i18nService.t('default'), value: null },
|
private totpService: TotpService,
|
||||||
{ name: i18nService.t('light'), value: ThemeType.Light },
|
i18nService: I18nService
|
||||||
{ name: i18nService.t('dark'), value: ThemeType.Dark },
|
) {
|
||||||
{ name: 'Nord', value: ThemeType.Nord },
|
this.themeOptions = [
|
||||||
{ name: i18nService.t('solarizedDark'), value: ThemeType.SolarizedDark },
|
{ name: i18nService.t("default"), value: null },
|
||||||
];
|
{ name: i18nService.t("light"), value: ThemeType.Light },
|
||||||
this.uriMatchOptions = [
|
{ name: i18nService.t("dark"), value: ThemeType.Dark },
|
||||||
{ name: i18nService.t('baseDomain'), value: UriMatchType.Domain },
|
{ name: "Nord", value: ThemeType.Nord },
|
||||||
{ name: i18nService.t('host'), value: UriMatchType.Host },
|
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
||||||
{ name: i18nService.t('startsWith'), value: UriMatchType.StartsWith },
|
];
|
||||||
{ name: i18nService.t('regEx'), value: UriMatchType.RegularExpression },
|
this.uriMatchOptions = [
|
||||||
{ name: i18nService.t('exact'), value: UriMatchType.Exact },
|
{ name: i18nService.t("baseDomain"), value: UriMatchType.Domain },
|
||||||
{ name: i18nService.t('never'), value: UriMatchType.Never },
|
{ name: i18nService.t("host"), value: UriMatchType.Host },
|
||||||
];
|
{ name: i18nService.t("startsWith"), value: UriMatchType.StartsWith },
|
||||||
this.clearClipboardOptions = [
|
{ name: i18nService.t("regEx"), value: UriMatchType.RegularExpression },
|
||||||
{ name: i18nService.t('never'), value: null },
|
{ name: i18nService.t("exact"), value: UriMatchType.Exact },
|
||||||
{ name: i18nService.t('tenSeconds'), value: 10 },
|
{ name: i18nService.t("never"), value: UriMatchType.Never },
|
||||||
{ name: i18nService.t('twentySeconds'), value: 20 },
|
];
|
||||||
{ name: i18nService.t('thirtySeconds'), value: 30 },
|
this.clearClipboardOptions = [
|
||||||
{ name: i18nService.t('oneMinute'), value: 60 },
|
{ name: i18nService.t("never"), value: null },
|
||||||
{ name: i18nService.t('twoMinutes'), value: 120 },
|
{ name: i18nService.t("tenSeconds"), value: 10 },
|
||||||
{ name: i18nService.t('fiveMinutes'), value: 300 },
|
{ name: i18nService.t("twentySeconds"), value: 20 },
|
||||||
];
|
{ name: i18nService.t("thirtySeconds"), value: 30 },
|
||||||
this.autoFillOnPageLoadOptions = [
|
{ name: i18nService.t("oneMinute"), value: 60 },
|
||||||
{ name: i18nService.t('autoFillOnPageLoadYes'), value: true },
|
{ name: i18nService.t("twoMinutes"), value: 120 },
|
||||||
{ name: i18nService.t('autoFillOnPageLoadNo'), value: false },
|
{ name: i18nService.t("fiveMinutes"), value: 300 },
|
||||||
];
|
];
|
||||||
}
|
this.autoFillOnPageLoadOptions = [
|
||||||
|
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true },
|
||||||
|
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.enableAutoFillOnPageLoad = await this.stateService.getEnableAutoFillOnPageLoad();
|
this.enableAutoFillOnPageLoad = await this.stateService.getEnableAutoFillOnPageLoad();
|
||||||
|
|
||||||
this.autoFillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault() ?? true;
|
this.autoFillOnPageLoadDefault =
|
||||||
|
(await this.stateService.getAutoFillOnPageLoadDefault()) ?? true;
|
||||||
|
|
||||||
this.disableAddLoginNotification = await this.stateService.getDisableAddLoginNotification();
|
this.disableAddLoginNotification = await this.stateService.getDisableAddLoginNotification();
|
||||||
|
|
||||||
this.disableChangedPasswordNotification = await this.stateService.getDisableChangedPasswordNotification();
|
this.disableChangedPasswordNotification =
|
||||||
|
await this.stateService.getDisableChangedPasswordNotification();
|
||||||
|
|
||||||
this.disableContextMenuItem = await this.stateService.getDisableContextMenuItem();
|
this.disableContextMenuItem = await this.stateService.getDisableContextMenuItem();
|
||||||
|
|
||||||
this.dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
|
this.dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
|
||||||
this.dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
|
this.dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
|
||||||
|
|
||||||
this.disableAutoTotpCopy = !(await this.totpService.isAutoCopyEnabled());
|
this.disableAutoTotpCopy = !(await this.totpService.isAutoCopyEnabled());
|
||||||
|
|
||||||
this.disableFavicon = await this.stateService.getDisableFavicon();
|
this.disableFavicon = await this.stateService.getDisableFavicon();
|
||||||
|
|
||||||
this.disableBadgeCounter = await this.stateService.getDisableBadgeCounter();
|
this.disableBadgeCounter = await this.stateService.getDisableBadgeCounter();
|
||||||
|
|
||||||
this.theme = await this.stateService.getTheme();
|
this.theme = await this.stateService.getTheme();
|
||||||
|
|
||||||
const defaultUriMatch = await this.stateService.getDefaultUriMatch();
|
const defaultUriMatch = await this.stateService.getDefaultUriMatch();
|
||||||
this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch;
|
this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch;
|
||||||
|
|
||||||
this.clearClipboard = await this.stateService.getClearClipboard();
|
this.clearClipboard = await this.stateService.getClearClipboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAddLoginNotification() {
|
async updateAddLoginNotification() {
|
||||||
await this.stateService.setDisableAddLoginNotification(this.disableAddLoginNotification);
|
await this.stateService.setDisableAddLoginNotification(this.disableAddLoginNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateChangedPasswordNotification() {
|
async updateChangedPasswordNotification() {
|
||||||
await this.stateService.setDisableChangedPasswordNotification(this.disableChangedPasswordNotification);
|
await this.stateService.setDisableChangedPasswordNotification(
|
||||||
}
|
this.disableChangedPasswordNotification
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async updateDisableContextMenuItem() {
|
async updateDisableContextMenuItem() {
|
||||||
await this.stateService.setDisableContextMenuItem(this.disableContextMenuItem);
|
await this.stateService.setDisableContextMenuItem(this.disableContextMenuItem);
|
||||||
this.messagingService.send('bgUpdateContextMenu');
|
this.messagingService.send("bgUpdateContextMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAutoTotpCopy() {
|
async updateAutoTotpCopy() {
|
||||||
await this.stateService.setDisableAutoTotpCopy(this.disableAutoTotpCopy);
|
await this.stateService.setDisableAutoTotpCopy(this.disableAutoTotpCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAutoFillOnPageLoad() {
|
async updateAutoFillOnPageLoad() {
|
||||||
await this.stateService.setEnableAutoFillOnPageLoad(this.enableAutoFillOnPageLoad);
|
await this.stateService.setEnableAutoFillOnPageLoad(this.enableAutoFillOnPageLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAutoFillOnPageLoadDefault() {
|
async updateAutoFillOnPageLoadDefault() {
|
||||||
await this.stateService.setAutoFillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
|
await this.stateService.setAutoFillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDisableFavicon() {
|
async updateDisableFavicon() {
|
||||||
await this.stateService.setDisableFavicon(this.disableFavicon);
|
await this.stateService.setDisableFavicon(this.disableFavicon);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDisableBadgeCounter() {
|
async updateDisableBadgeCounter() {
|
||||||
await this.stateService.setDisableBadgeCounter(this.disableBadgeCounter);
|
await this.stateService.setDisableBadgeCounter(this.disableBadgeCounter);
|
||||||
this.messagingService.send('bgUpdateContextMenu');
|
this.messagingService.send("bgUpdateContextMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateShowCards() {
|
async updateShowCards() {
|
||||||
await this.stateService.setDontShowCardsCurrentTab(this.dontShowCards);
|
await this.stateService.setDontShowCardsCurrentTab(this.dontShowCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateShowIdentities() {
|
async updateShowIdentities() {
|
||||||
await this.stateService.setDontShowIdentitiesCurrentTab(this.dontShowIdentities);
|
await this.stateService.setDontShowIdentitiesCurrentTab(this.dontShowIdentities);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveTheme() {
|
async saveTheme() {
|
||||||
await this.stateService.setTheme(this.theme);
|
await this.stateService.setTheme(this.theme);
|
||||||
window.setTimeout(() => window.location.reload(), 200);
|
window.setTimeout(() => window.location.reload(), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveDefaultUriMatch() {
|
async saveDefaultUriMatch() {
|
||||||
await this.stateService.setDefaultUriMatch(this.defaultUriMatch);
|
await this.stateService.setDefaultUriMatch(this.defaultUriMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveClearClipboard() {
|
async saveClearClipboard() {
|
||||||
await this.stateService.setClearClipboard(this.clearClipboard);
|
await this.stateService.setClearClipboard(this.clearClipboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
import { CurrencyPipe } from '@angular/common';
|
import { CurrencyPipe } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
|
import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-premium',
|
selector: "app-premium",
|
||||||
templateUrl: 'premium.component.html',
|
templateUrl: "premium.component.html",
|
||||||
})
|
})
|
||||||
export class PremiumComponent extends BasePremiumComponent {
|
export class PremiumComponent extends BasePremiumComponent {
|
||||||
priceString: string;
|
priceString: string;
|
||||||
|
|
||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
apiService: ApiService, logService: LogService, stateService: StateService,
|
i18nService: I18nService,
|
||||||
private currencyPipe: CurrencyPipe) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(i18nService, platformUtilsService, apiService, logService, stateService);
|
apiService: ApiService,
|
||||||
|
logService: LogService,
|
||||||
|
stateService: StateService,
|
||||||
|
private currencyPipe: CurrencyPipe
|
||||||
|
) {
|
||||||
|
super(i18nService, platformUtilsService, apiService, logService, stateService);
|
||||||
|
|
||||||
// Support old price string. Can be removed in future once all translations are properly updated.
|
// Support old price string. Can be removed in future once all translations are properly updated.
|
||||||
const thePrice = this.currencyPipe.transform(this.price, '$');
|
const thePrice = this.currencyPipe.transform(this.price, "$");
|
||||||
this.priceString = i18nService.t('premiumPrice', thePrice);
|
this.priceString = i18nService.t("premiumPrice", thePrice);
|
||||||
if (this.priceString.indexOf('%price%') > -1) {
|
if (this.priceString.indexOf("%price%") > -1) {
|
||||||
this.priceString = this.priceString.replace('%price%', thePrice);
|
this.priceString = this.priceString.replace("%price%", thePrice);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,364 +1,410 @@
|
||||||
import {
|
import { Component, ElementRef, OnInit, ViewChild } from "@angular/core";
|
||||||
Component,
|
import { FormControl } from "@angular/forms";
|
||||||
ElementRef,
|
import { Router } from "@angular/router";
|
||||||
OnInit,
|
import Swal from "sweetalert2/src/sweetalert2.js";
|
||||||
ViewChild,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FormControl } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import Swal from 'sweetalert2/src/sweetalert2.js';
|
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { DeviceType } from 'jslib-common/enums/deviceType';
|
import { DeviceType } from "jslib-common/enums/deviceType";
|
||||||
|
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from "jslib-angular/services/modal.service";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
import { SetPinComponent } from '../components/set-pin.component';
|
import { SetPinComponent } from "../components/set-pin.component";
|
||||||
|
|
||||||
const RateUrls = {
|
const RateUrls = {
|
||||||
[DeviceType.ChromeExtension]:
|
[DeviceType.ChromeExtension]:
|
||||||
'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews',
|
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||||
[DeviceType.FirefoxExtension]:
|
[DeviceType.FirefoxExtension]:
|
||||||
'https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews',
|
"https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews",
|
||||||
[DeviceType.OperaExtension]:
|
[DeviceType.OperaExtension]:
|
||||||
'https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container',
|
"https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container",
|
||||||
[DeviceType.EdgeExtension]:
|
[DeviceType.EdgeExtension]:
|
||||||
'https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh',
|
"https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh",
|
||||||
[DeviceType.VivaldiExtension]:
|
[DeviceType.VivaldiExtension]:
|
||||||
'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews',
|
"https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||||
[DeviceType.SafariExtension]:
|
[DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147",
|
||||||
'https://apps.apple.com/app/bitwarden/id1352778147',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: "app-settings",
|
||||||
templateUrl: 'settings.component.html',
|
templateUrl: "settings.component.html",
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
@ViewChild('vaultTimeoutActionSelect', { read: ElementRef, static: true }) vaultTimeoutActionSelectRef: ElementRef;
|
@ViewChild("vaultTimeoutActionSelect", { read: ElementRef, static: true })
|
||||||
vaultTimeouts: any[];
|
vaultTimeoutActionSelectRef: ElementRef;
|
||||||
vaultTimeoutActions: any[];
|
vaultTimeouts: any[];
|
||||||
vaultTimeoutAction: string;
|
vaultTimeoutActions: any[];
|
||||||
pin: boolean = null;
|
vaultTimeoutAction: string;
|
||||||
supportsBiometric: boolean;
|
pin: boolean = null;
|
||||||
biometric: boolean = false;
|
supportsBiometric: boolean;
|
||||||
disableAutoBiometricsPrompt = true;
|
biometric: boolean = false;
|
||||||
previousVaultTimeout: number = null;
|
disableAutoBiometricsPrompt = true;
|
||||||
showChangeMasterPass = true;
|
previousVaultTimeout: number = null;
|
||||||
|
showChangeMasterPass = true;
|
||||||
|
|
||||||
vaultTimeout: FormControl = new FormControl(null);
|
vaultTimeout: FormControl = new FormControl(null);
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
constructor(
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
public messagingService: MessagingService, private router: Router,
|
private i18nService: I18nService,
|
||||||
private environmentService: EnvironmentService, private cryptoService: CryptoService,
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
private stateService: StateService, private popupUtilsService: PopupUtilsService,
|
public messagingService: MessagingService,
|
||||||
private modalService: ModalService,
|
private router: Router,
|
||||||
private keyConnectorService: KeyConnectorService) {
|
private environmentService: EnvironmentService,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private popupUtilsService: PopupUtilsService,
|
||||||
|
private modalService: ModalService,
|
||||||
|
private keyConnectorService: KeyConnectorService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
const showOnLocked =
|
||||||
|
!this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari();
|
||||||
|
|
||||||
|
this.vaultTimeouts = [
|
||||||
|
{ name: this.i18nService.t("immediately"), value: 0 },
|
||||||
|
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||||
|
{ name: this.i18nService.t("fiveMinutes"), value: 5 },
|
||||||
|
{ name: this.i18nService.t("fifteenMinutes"), value: 15 },
|
||||||
|
{ name: this.i18nService.t("thirtyMinutes"), value: 30 },
|
||||||
|
{ name: this.i18nService.t("oneHour"), value: 60 },
|
||||||
|
{ name: this.i18nService.t("fourHours"), value: 240 },
|
||||||
|
// { name: i18nService.t('onIdle'), value: -4 },
|
||||||
|
// { name: i18nService.t('onSleep'), value: -3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (showOnLocked) {
|
||||||
|
this.vaultTimeouts.push({ name: this.i18nService.t("onLocked"), value: -2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
this.vaultTimeouts.push({ name: this.i18nService.t("onRestart"), value: -1 });
|
||||||
const showOnLocked = !this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari();
|
this.vaultTimeouts.push({ name: this.i18nService.t("never"), value: null });
|
||||||
|
|
||||||
this.vaultTimeouts = [
|
this.vaultTimeoutActions = [
|
||||||
{ name: this.i18nService.t('immediately'), value: 0 },
|
{ name: this.i18nService.t("lock"), value: "lock" },
|
||||||
{ name: this.i18nService.t('oneMinute'), value: 1 },
|
{ name: this.i18nService.t("logOut"), value: "logOut" },
|
||||||
{ name: this.i18nService.t('fiveMinutes'), value: 5 },
|
];
|
||||||
{ name: this.i18nService.t('fifteenMinutes'), value: 15 },
|
|
||||||
{ name: this.i18nService.t('thirtyMinutes'), value: 30 },
|
|
||||||
{ name: this.i18nService.t('oneHour'), value: 60 },
|
|
||||||
{ name: this.i18nService.t('fourHours'), value: 240 },
|
|
||||||
// { name: i18nService.t('onIdle'), value: -4 },
|
|
||||||
// { name: i18nService.t('onSleep'), value: -3 },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (showOnLocked) {
|
let timeout = await this.vaultTimeoutService.getVaultTimeout();
|
||||||
this.vaultTimeouts.push({ name: this.i18nService.t('onLocked'), value: -2 });
|
if (timeout != null) {
|
||||||
}
|
if (timeout === -2 && !showOnLocked) {
|
||||||
|
timeout = -1;
|
||||||
|
}
|
||||||
|
this.vaultTimeout.setValue(timeout);
|
||||||
|
}
|
||||||
|
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||||
|
this.vaultTimeout.valueChanges.subscribe((value) => {
|
||||||
|
this.saveVaultTimeout(value);
|
||||||
|
});
|
||||||
|
|
||||||
this.vaultTimeouts.push({ name: this.i18nService.t('onRestart'), value: -1 });
|
const action = await this.stateService.getVaultTimeoutAction();
|
||||||
this.vaultTimeouts.push({ name: this.i18nService.t('never'), value: null });
|
this.vaultTimeoutAction = action == null ? "lock" : action;
|
||||||
|
|
||||||
this.vaultTimeoutActions = [
|
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
||||||
{ name: this.i18nService.t('lock'), value: 'lock' },
|
this.pin = pinSet[0] || pinSet[1];
|
||||||
{ name: this.i18nService.t('logOut'), value: 'logOut' },
|
|
||||||
];
|
|
||||||
|
|
||||||
let timeout = await this.vaultTimeoutService.getVaultTimeout();
|
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||||
if (timeout != null) {
|
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
||||||
if (timeout === -2 && !showOnLocked) {
|
this.disableAutoBiometricsPrompt =
|
||||||
timeout = -1;
|
(await this.stateService.getDisableAutoBiometricsPrompt()) ?? true;
|
||||||
}
|
this.showChangeMasterPass = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||||
this.vaultTimeout.setValue(timeout);
|
}
|
||||||
}
|
|
||||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
async saveVaultTimeout(newValue: number) {
|
||||||
this.vaultTimeout.valueChanges.subscribe(value => {
|
if (newValue == null) {
|
||||||
this.saveVaultTimeout(value);
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("neverLockWarning"),
|
||||||
|
null,
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("cancel"),
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
this.vaultTimeout.setValue(this.previousVaultTimeout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.vaultTimeout.valid) {
|
||||||
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("vaultTimeoutToLarge"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||||
|
|
||||||
|
await this.vaultTimeoutService.setVaultTimeoutOptions(
|
||||||
|
this.vaultTimeout.value,
|
||||||
|
this.vaultTimeoutAction
|
||||||
|
);
|
||||||
|
if (this.previousVaultTimeout == null) {
|
||||||
|
this.messagingService.send("bgReseedStorage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveVaultTimeoutAction(newValue: string) {
|
||||||
|
if (newValue === "logOut") {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||||
|
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("cancel"),
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
this.vaultTimeoutActions.forEach((option: any, i) => {
|
||||||
|
if (option.value === this.vaultTimeoutAction) {
|
||||||
|
this.vaultTimeoutActionSelectRef.nativeElement.value =
|
||||||
|
i + ": " + this.vaultTimeoutAction;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
const action = await this.stateService.getVaultTimeoutAction();
|
}
|
||||||
this.vaultTimeoutAction = action == null ? 'lock' : action;
|
|
||||||
|
|
||||||
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
|
||||||
this.pin = pinSet[0] || pinSet[1];
|
|
||||||
|
|
||||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
|
||||||
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
|
||||||
this.disableAutoBiometricsPrompt = await this.stateService.getDisableAutoBiometricsPrompt() ?? true;
|
|
||||||
this.showChangeMasterPass = !await this.keyConnectorService.getUsesKeyConnector();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveVaultTimeout(newValue: number) {
|
if (!this.vaultTimeout.valid) {
|
||||||
if (newValue == null) {
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("vaultTimeoutToLarge"));
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
return;
|
||||||
this.i18nService.t('neverLockWarning'), null,
|
}
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
|
|
||||||
if (!confirmed) {
|
this.vaultTimeoutAction = newValue;
|
||||||
this.vaultTimeout.setValue(this.previousVaultTimeout);
|
await this.vaultTimeoutService.setVaultTimeoutOptions(
|
||||||
return;
|
this.vaultTimeout.value,
|
||||||
|
this.vaultTimeoutAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePin() {
|
||||||
|
if (this.pin) {
|
||||||
|
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
|
||||||
|
|
||||||
|
if (ref == null) {
|
||||||
|
this.pin = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pin = await ref.onClosedPromise();
|
||||||
|
} else {
|
||||||
|
await this.cryptoService.clearPinProtectedKey();
|
||||||
|
await this.vaultTimeoutService.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateBiometric() {
|
||||||
|
if (this.biometric && this.supportsBiometric) {
|
||||||
|
let granted;
|
||||||
|
try {
|
||||||
|
granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] });
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
|
if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) {
|
||||||
|
await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("nativeMessaginPermissionSidebarDesc"),
|
||||||
|
this.i18nService.t("nativeMessaginPermissionSidebarTitle"),
|
||||||
|
this.i18nService.t("ok"),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
this.biometric = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!granted) {
|
||||||
|
await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("nativeMessaginPermissionErrorDesc"),
|
||||||
|
this.i18nService.t("nativeMessaginPermissionErrorTitle"),
|
||||||
|
this.i18nService.t("ok"),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
this.biometric = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitted = Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
titleText: this.i18nService.t("awaitDesktop"),
|
||||||
|
text: this.i18nService.t("awaitDesktopDesc"),
|
||||||
|
icon: "info",
|
||||||
|
iconHtml: '<i class="swal-custom-icon fa fa-info-circle text-info"></i>',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t("cancel"),
|
||||||
|
showConfirmButton: false,
|
||||||
|
allowOutsideClick: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.stateService.setBiometricAwaitingAcceptance(true);
|
||||||
|
await this.cryptoService.toggleKey();
|
||||||
|
|
||||||
|
await Promise.race([
|
||||||
|
submitted.then(async (result) => {
|
||||||
|
if (result.dismiss === Swal.DismissReason.cancel) {
|
||||||
|
this.biometric = false;
|
||||||
|
await this.stateService.setBiometricAwaitingAcceptance(null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
this.platformUtilsService
|
||||||
|
.authenticateBiometric()
|
||||||
|
.then((result) => {
|
||||||
|
this.biometric = result;
|
||||||
|
|
||||||
|
Swal.close();
|
||||||
|
if (this.biometric === false) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorEnableBiometricTitle"),
|
||||||
|
this.i18nService.t("errorEnableBiometricDesc")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.catch((e) => {
|
||||||
if (!this.vaultTimeout.valid) {
|
// Handle connection errors
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutToLarge'));
|
this.biometric = false;
|
||||||
return;
|
}),
|
||||||
}
|
]);
|
||||||
|
} else {
|
||||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
await this.stateService.setBiometricUnlock(null);
|
||||||
|
await this.stateService.setBiometricLocked(false);
|
||||||
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
|
|
||||||
if (this.previousVaultTimeout == null) {
|
|
||||||
this.messagingService.send('bgReseedStorage');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async saveVaultTimeoutAction(newValue: string) {
|
async updateAutoBiometricsPrompt() {
|
||||||
if (newValue === 'logOut') {
|
await this.stateService.setDisableAutoBiometricsPrompt(this.disableAutoBiometricsPrompt);
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
}
|
||||||
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
|
|
||||||
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
|
|
||||||
if (!confirmed) {
|
|
||||||
this.vaultTimeoutActions.forEach((option: any, i) => {
|
|
||||||
if (option.value === this.vaultTimeoutAction) {
|
|
||||||
this.vaultTimeoutActionSelectRef.nativeElement.value = i + ': ' + this.vaultTimeoutAction;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.vaultTimeout.valid) {
|
async lock() {
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutToLarge'));
|
await this.vaultTimeoutService.lock(true);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.vaultTimeoutAction = newValue;
|
async logOut() {
|
||||||
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("logOutConfirmation"),
|
||||||
|
this.i18nService.t("logOut"),
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("cancel")
|
||||||
|
);
|
||||||
|
if (confirmed) {
|
||||||
|
this.messagingService.send("logout");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updatePin() {
|
async changePassword() {
|
||||||
if (this.pin) {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
|
this.i18nService.t("changeMasterPasswordConfirmation"),
|
||||||
|
this.i18nService.t("changeMasterPassword"),
|
||||||
if (ref == null) {
|
this.i18nService.t("yes"),
|
||||||
this.pin = false;
|
this.i18nService.t("cancel")
|
||||||
return;
|
);
|
||||||
}
|
if (confirmed) {
|
||||||
|
BrowserApi.createNewTab("https://help.bitwarden.com/article/change-your-master-password/");
|
||||||
this.pin = await ref.onClosedPromise();
|
|
||||||
} else {
|
|
||||||
await this.cryptoService.clearPinProtectedKey();
|
|
||||||
await this.vaultTimeoutService.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateBiometric() {
|
async twoStep() {
|
||||||
if (this.biometric && this.supportsBiometric) {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("twoStepLoginConfirmation"),
|
||||||
let granted;
|
this.i18nService.t("twoStepLogin"),
|
||||||
try {
|
this.i18nService.t("yes"),
|
||||||
granted = await BrowserApi.requestPermission({ permissions: ['nativeMessaging'] });
|
this.i18nService.t("cancel")
|
||||||
} catch (e) {
|
);
|
||||||
// tslint:disable-next-line
|
if (confirmed) {
|
||||||
console.error(e);
|
BrowserApi.createNewTab("https://help.bitwarden.com/article/setup-two-step-login/");
|
||||||
|
|
||||||
if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) {
|
|
||||||
await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('nativeMessaginPermissionSidebarDesc'), this.i18nService.t('nativeMessaginPermissionSidebarTitle'),
|
|
||||||
this.i18nService.t('ok'), null);
|
|
||||||
this.biometric = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!granted) {
|
|
||||||
await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'),
|
|
||||||
this.i18nService.t('ok'), null);
|
|
||||||
this.biometric = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitted = Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
titleText: this.i18nService.t('awaitDesktop'),
|
|
||||||
text: this.i18nService.t('awaitDesktopDesc'),
|
|
||||||
icon: 'info',
|
|
||||||
iconHtml: '<i class="swal-custom-icon fa fa-info-circle text-info"></i>',
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: this.i18nService.t('cancel'),
|
|
||||||
showConfirmButton: false,
|
|
||||||
allowOutsideClick: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.stateService.setBiometricAwaitingAcceptance(true);
|
|
||||||
await this.cryptoService.toggleKey();
|
|
||||||
|
|
||||||
await Promise.race([
|
|
||||||
submitted.then(async result => {
|
|
||||||
if (result.dismiss === Swal.DismissReason.cancel) {
|
|
||||||
this.biometric = false;
|
|
||||||
await this.stateService.setBiometricAwaitingAcceptance(null);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
this.platformUtilsService.authenticateBiometric().then(result => {
|
|
||||||
this.biometric = result;
|
|
||||||
|
|
||||||
Swal.close();
|
|
||||||
if (this.biometric === false) {
|
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorEnableBiometricTitle'), this.i18nService.t('errorEnableBiometricDesc'));
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
// Handle connection errors
|
|
||||||
this.biometric = false;
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
await this.stateService.setBiometricUnlock(null);
|
|
||||||
await this.stateService.setBiometricLocked(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateAutoBiometricsPrompt() {
|
async share() {
|
||||||
await this.stateService.setDisableAutoBiometricsPrompt(this.disableAutoBiometricsPrompt);
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("learnOrgConfirmation"),
|
||||||
|
this.i18nService.t("learnOrg"),
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("cancel")
|
||||||
|
);
|
||||||
|
if (confirmed) {
|
||||||
|
BrowserApi.createNewTab("https://help.bitwarden.com/article/what-is-an-organization/");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async lock() {
|
async webVault() {
|
||||||
await this.vaultTimeoutService.lock(true);
|
const url = this.environmentService.getWebVaultUrl();
|
||||||
|
BrowserApi.createNewTab(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
import() {
|
||||||
|
BrowserApi.createNewTab("https://help.bitwarden.com/article/import-data/");
|
||||||
|
}
|
||||||
|
|
||||||
|
export() {
|
||||||
|
this.router.navigate(["/export"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
help() {
|
||||||
|
BrowserApi.createNewTab("https://help.bitwarden.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
about() {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const versionText = document.createTextNode(
|
||||||
|
this.i18nService.t("version") + ": " + BrowserApi.getApplicationVersion()
|
||||||
|
);
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML =
|
||||||
|
`<p class="text-center"><i class="fa fa-shield fa-3x" aria-hidden="true"></i></p>
|
||||||
|
<p class="text-center"><b>Bitwarden</b><br>© Bitwarden Inc. 2015-` +
|
||||||
|
year +
|
||||||
|
`</p>`;
|
||||||
|
div.appendChild(versionText);
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
html: div,
|
||||||
|
showConfirmButton: false,
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t("close"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fingerprint() {
|
||||||
|
const fingerprint = await this.cryptoService.getFingerprint(
|
||||||
|
await this.stateService.getUserId()
|
||||||
|
);
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.innerText = this.i18nService.t("yourAccountsFingerprint") + ":";
|
||||||
|
const p2 = document.createElement("p");
|
||||||
|
p2.innerText = fingerprint.join("-");
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.appendChild(p);
|
||||||
|
div.appendChild(p2);
|
||||||
|
|
||||||
|
const result = await Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
html: div,
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t("close"),
|
||||||
|
showConfirmButton: true,
|
||||||
|
confirmButtonText: this.i18nService.t("learnMore"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.value) {
|
||||||
|
this.platformUtilsService.launchUri("https://help.bitwarden.com/article/fingerprint-phrase/");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async logOut() {
|
rate() {
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
const deviceType = this.platformUtilsService.getDevice();
|
||||||
this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'),
|
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
}
|
||||||
if (confirmed) {
|
|
||||||
this.messagingService.send('logout');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async changePassword() {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('changeMasterPasswordConfirmation'), this.i18nService.t('changeMasterPassword'),
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
|
||||||
if (confirmed) {
|
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/change-your-master-password/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async twoStep() {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('twoStepLoginConfirmation'), this.i18nService.t('twoStepLogin'),
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
|
||||||
if (confirmed) {
|
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/setup-two-step-login/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async share() {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('learnOrgConfirmation'), this.i18nService.t('learnOrg'),
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
|
||||||
if (confirmed) {
|
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/what-is-an-organization/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async webVault() {
|
|
||||||
const url = this.environmentService.getWebVaultUrl();
|
|
||||||
BrowserApi.createNewTab(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
import() {
|
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/import-data/');
|
|
||||||
}
|
|
||||||
|
|
||||||
export() {
|
|
||||||
this.router.navigate(['/export']);
|
|
||||||
}
|
|
||||||
|
|
||||||
help() {
|
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/');
|
|
||||||
}
|
|
||||||
|
|
||||||
about() {
|
|
||||||
const year = (new Date()).getFullYear();
|
|
||||||
const versionText = document.createTextNode(
|
|
||||||
this.i18nService.t('version') + ': ' + BrowserApi.getApplicationVersion());
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerHTML = `<p class="text-center"><i class="fa fa-shield fa-3x" aria-hidden="true"></i></p>
|
|
||||||
<p class="text-center"><b>Bitwarden</b><br>© Bitwarden Inc. 2015-` + year + `</p>`;
|
|
||||||
div.appendChild(versionText);
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
html: div,
|
|
||||||
showConfirmButton: false,
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: this.i18nService.t('close'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fingerprint() {
|
|
||||||
const fingerprint = await this.cryptoService.getFingerprint(await this.stateService.getUserId());
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.innerText = this.i18nService.t('yourAccountsFingerprint') + ':';
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
p2.innerText = fingerprint.join('-');
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.appendChild(p);
|
|
||||||
div.appendChild(p2);
|
|
||||||
|
|
||||||
const result = await Swal.fire({
|
|
||||||
heightAuto: false,
|
|
||||||
buttonsStyling: false,
|
|
||||||
html: div,
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonText: this.i18nService.t('close'),
|
|
||||||
showConfirmButton: true,
|
|
||||||
confirmButtonText: this.i18nService.t('learnMore'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.value) {
|
|
||||||
this.platformUtilsService.launchUri('https://help.bitwarden.com/article/fingerprint-phrase/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rate() {
|
|
||||||
const deviceType = this.platformUtilsService.getDevice();
|
|
||||||
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,190 +1,225 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||||
|
|
||||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
|
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/add-edit.component";
|
||||||
|
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-add-edit',
|
selector: "app-vault-add-edit",
|
||||||
templateUrl: 'add-edit.component.html',
|
templateUrl: "add-edit.component.html",
|
||||||
})
|
})
|
||||||
export class AddEditComponent extends BaseAddEditComponent {
|
export class AddEditComponent extends BaseAddEditComponent {
|
||||||
currentUris: string[];
|
currentUris: string[];
|
||||||
showAttachments = true;
|
showAttachments = true;
|
||||||
openAttachmentsInPopup: boolean;
|
openAttachmentsInPopup: boolean;
|
||||||
showAutoFillOnPageLoadOptions: boolean;
|
showAutoFillOnPageLoadOptions: boolean;
|
||||||
|
|
||||||
constructor(cipherService: CipherService, folderService: FolderService,
|
constructor(
|
||||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
cipherService: CipherService,
|
||||||
auditService: AuditService, stateService: StateService, collectionService: CollectionService,
|
folderService: FolderService,
|
||||||
messagingService: MessagingService, private route: ActivatedRoute,
|
i18nService: I18nService,
|
||||||
private router: Router, private location: Location,
|
platformUtilsService: PlatformUtilsService,
|
||||||
eventService: EventService, policyService: PolicyService,
|
auditService: AuditService,
|
||||||
private popupUtilsService: PopupUtilsService, organizationService: OrganizationService,
|
stateService: StateService,
|
||||||
passwordRepromptService: PasswordRepromptService, logService: LogService) {
|
collectionService: CollectionService,
|
||||||
super(cipherService, folderService, i18nService, platformUtilsService,
|
messagingService: MessagingService,
|
||||||
auditService, stateService, collectionService, messagingService,
|
private route: ActivatedRoute,
|
||||||
eventService, policyService, logService, passwordRepromptService, organizationService);
|
private router: Router,
|
||||||
}
|
private location: Location,
|
||||||
|
eventService: EventService,
|
||||||
|
policyService: PolicyService,
|
||||||
|
private popupUtilsService: PopupUtilsService,
|
||||||
|
organizationService: OrganizationService,
|
||||||
|
passwordRepromptService: PasswordRepromptService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
cipherService,
|
||||||
|
folderService,
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
auditService,
|
||||||
|
stateService,
|
||||||
|
collectionService,
|
||||||
|
messagingService,
|
||||||
|
eventService,
|
||||||
|
policyService,
|
||||||
|
logService,
|
||||||
|
passwordRepromptService,
|
||||||
|
organizationService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
if (params.cipherId) {
|
if (params.cipherId) {
|
||||||
this.cipherId = params.cipherId;
|
this.cipherId = params.cipherId;
|
||||||
}
|
}
|
||||||
if (params.folderId) {
|
if (params.folderId) {
|
||||||
this.folderId = params.folderId;
|
this.folderId = params.folderId;
|
||||||
}
|
}
|
||||||
if (params.collectionId) {
|
if (params.collectionId) {
|
||||||
const collection = this.writeableCollections.find(c => c.id === params.collectionId);
|
const collection = this.writeableCollections.find((c) => c.id === params.collectionId);
|
||||||
if (collection != null) {
|
if (collection != null) {
|
||||||
this.collectionIds = [collection.id];
|
this.collectionIds = [collection.id];
|
||||||
this.organizationId = collection.organizationId;
|
this.organizationId = collection.organizationId;
|
||||||
}
|
|
||||||
}
|
|
||||||
if (params.type) {
|
|
||||||
const type = parseInt(params.type, null);
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
this.editMode = !params.cipherId;
|
|
||||||
|
|
||||||
if (params.cloneMode != null) {
|
|
||||||
this.cloneMode = params.cloneMode === 'true';
|
|
||||||
}
|
|
||||||
await this.load();
|
|
||||||
|
|
||||||
if (!this.editMode || this.cloneMode) {
|
|
||||||
if (!this.popupUtilsService.inPopout(window) && params.name &&
|
|
||||||
(this.cipher.name == null || this.cipher.name === '')) {
|
|
||||||
this.cipher.name = params.name;
|
|
||||||
}
|
|
||||||
if (!this.popupUtilsService.inPopout(window) && params.uri &&
|
|
||||||
(this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === '')) {
|
|
||||||
this.cipher.login.uris[0].uri = params.uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openAttachmentsInPopup = this.popupUtilsService.inPopup(window);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.editMode) {
|
|
||||||
const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' });
|
|
||||||
this.currentUris = tabs == null ? null :
|
|
||||||
tabs.filter(tab => tab.url != null && tab.url !== '').map(tab => tab.url);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (params.type) {
|
||||||
|
const type = parseInt(params.type, null);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
this.editMode = !params.cipherId;
|
||||||
|
|
||||||
window.setTimeout(() => {
|
if (params.cloneMode != null) {
|
||||||
if (!this.editMode) {
|
this.cloneMode = params.cloneMode === "true";
|
||||||
if (this.cipher.name != null && this.cipher.name !== '') {
|
}
|
||||||
document.getElementById('loginUsername').focus();
|
await this.load();
|
||||||
} else {
|
|
||||||
document.getElementById('name').focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
if (!this.editMode || this.cloneMode) {
|
||||||
await super.load();
|
if (
|
||||||
this.showAutoFillOnPageLoadOptions = this.cipher.type === CipherType.Login &&
|
!this.popupUtilsService.inPopout(window) &&
|
||||||
await this.stateService.getEnableAutoFillOnPageLoad();
|
params.name &&
|
||||||
}
|
(this.cipher.name == null || this.cipher.name === "")
|
||||||
|
) {
|
||||||
async submit(): Promise<boolean> {
|
this.cipher.name = params.name;
|
||||||
if (await super.submit()) {
|
|
||||||
if (this.cloneMode) {
|
|
||||||
this.router.navigate(['/tabs/vault']);
|
|
||||||
} else {
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
!this.popupUtilsService.inPopout(window) &&
|
||||||
|
params.uri &&
|
||||||
|
(this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "")
|
||||||
|
) {
|
||||||
|
this.cipher.login.uris[0].uri = params.uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
this.openAttachmentsInPopup = this.popupUtilsService.inPopup(window);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.editMode) {
|
||||||
|
const tabs = await BrowserApi.tabsQuery({ windowType: "normal" });
|
||||||
|
this.currentUris =
|
||||||
|
tabs == null
|
||||||
|
? null
|
||||||
|
: tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments() {
|
window.setTimeout(() => {
|
||||||
super.attachments();
|
if (!this.editMode) {
|
||||||
|
if (this.cipher.name != null && this.cipher.name !== "") {
|
||||||
if (this.openAttachmentsInPopup) {
|
document.getElementById("loginUsername").focus();
|
||||||
const destinationUrl = this.router.createUrlTree(['/attachments'], { queryParams: { cipherId: this.cipher.id } }).toString();
|
|
||||||
const currentBaseUrl = window.location.href.replace(this.router.url, '');
|
|
||||||
this.popupUtilsService.popOut(window, currentBaseUrl + destinationUrl);
|
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(['/attachments'], { queryParams: { cipherId: this.cipher.id } });
|
document.getElementById("name").focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
await super.load();
|
||||||
|
this.showAutoFillOnPageLoadOptions =
|
||||||
|
this.cipher.type === CipherType.Login &&
|
||||||
|
(await this.stateService.getEnableAutoFillOnPageLoad());
|
||||||
|
}
|
||||||
|
|
||||||
editCollections() {
|
async submit(): Promise<boolean> {
|
||||||
super.editCollections();
|
if (await super.submit()) {
|
||||||
if (this.cipher.organizationId != null) {
|
if (this.cloneMode) {
|
||||||
this.router.navigate(['/collections'], { queryParams: { cipherId: this.cipher.id } });
|
this.router.navigate(["/tabs/vault"]);
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
super.cancel();
|
|
||||||
this.location.back();
|
this.location.back();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generatePassword(): Promise<boolean> {
|
return false;
|
||||||
const confirmed = await super.generatePassword();
|
}
|
||||||
if (confirmed) {
|
|
||||||
this.stateService.setAddEditCipherInfo({
|
|
||||||
cipher: this.cipher,
|
|
||||||
collectionIds: this.collections == null ? [] :
|
|
||||||
this.collections.filter(c => (c as any).checked).map(c => c.id),
|
|
||||||
});
|
|
||||||
this.router.navigate(['generator']);
|
|
||||||
}
|
|
||||||
return confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(): Promise<boolean> {
|
attachments() {
|
||||||
const confirmed = await super.delete();
|
super.attachments();
|
||||||
if (confirmed) {
|
|
||||||
this.router.navigate(['/tabs/vault']);
|
|
||||||
}
|
|
||||||
return confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleUriInput(uri: LoginUriView) {
|
if (this.openAttachmentsInPopup) {
|
||||||
const u = (uri as any);
|
const destinationUrl = this.router
|
||||||
u.showCurrentUris = !u.showCurrentUris;
|
.createUrlTree(["/attachments"], { queryParams: { cipherId: this.cipher.id } })
|
||||||
|
.toString();
|
||||||
|
const currentBaseUrl = window.location.href.replace(this.router.url, "");
|
||||||
|
this.popupUtilsService.popOut(window, currentBaseUrl + destinationUrl);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(["/attachments"], { queryParams: { cipherId: this.cipher.id } });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allowOwnershipOptions(): boolean {
|
editCollections() {
|
||||||
return (!this.editMode || this.cloneMode) && this.ownershipOptions
|
super.editCollections();
|
||||||
&& (this.ownershipOptions.length > 1 || !this.allowPersonal);
|
if (this.cipher.organizationId != null) {
|
||||||
|
this.router.navigate(["/collections"], { queryParams: { cipherId: this.cipher.id } });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
super.cancel();
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
async generatePassword(): Promise<boolean> {
|
||||||
|
const confirmed = await super.generatePassword();
|
||||||
|
if (confirmed) {
|
||||||
|
this.stateService.setAddEditCipherInfo({
|
||||||
|
cipher: this.cipher,
|
||||||
|
collectionIds:
|
||||||
|
this.collections == null
|
||||||
|
? []
|
||||||
|
: this.collections.filter((c) => (c as any).checked).map((c) => c.id),
|
||||||
|
});
|
||||||
|
this.router.navigate(["generator"]);
|
||||||
|
}
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(): Promise<boolean> {
|
||||||
|
const confirmed = await super.delete();
|
||||||
|
if (confirmed) {
|
||||||
|
this.router.navigate(["/tabs/vault"]);
|
||||||
|
}
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleUriInput(uri: LoginUriView) {
|
||||||
|
const u = uri as any;
|
||||||
|
u.showCurrentUris = !u.showCurrentUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowOwnershipOptions(): boolean {
|
||||||
|
return (
|
||||||
|
(!this.editMode || this.cloneMode) &&
|
||||||
|
this.ownershipOptions &&
|
||||||
|
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,63 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
|
import { AttachmentsComponent as BaseAttachmentsComponent } from "jslib-angular/components/attachments.component";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-attachments',
|
selector: "app-vault-attachments",
|
||||||
templateUrl: 'attachments.component.html',
|
templateUrl: "attachments.component.html",
|
||||||
})
|
})
|
||||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||||
openedAttachmentsInPopup: boolean;
|
openedAttachmentsInPopup: boolean;
|
||||||
|
|
||||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
constructor(
|
||||||
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
|
cipherService: CipherService,
|
||||||
apiService: ApiService, private location: Location,
|
i18nService: I18nService,
|
||||||
private route: ActivatedRoute, stateService: StateService, logService: LogService) {
|
cryptoService: CryptoService,
|
||||||
super(cipherService, i18nService, cryptoService, platformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
apiService, window, logService, stateService);
|
apiService: ApiService,
|
||||||
}
|
private location: Location,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
stateService: StateService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
cipherService,
|
||||||
|
i18nService,
|
||||||
|
cryptoService,
|
||||||
|
platformUtilsService,
|
||||||
|
apiService,
|
||||||
|
window,
|
||||||
|
logService,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
this.cipherId = params.cipherId;
|
this.cipherId = params.cipherId;
|
||||||
await this.init();
|
await this.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openedAttachmentsInPopup = history.length === 1;
|
this.openedAttachmentsInPopup = history.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
back() {
|
back() {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,235 +1,247 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||||
import { FolderView } from 'jslib-common/models/view/folderView';
|
import { FolderView } from "jslib-common/models/view/folderView";
|
||||||
|
|
||||||
import { TreeNode } from 'jslib-common/models/domain/treeNode';
|
import { TreeNode } from "jslib-common/models/domain/treeNode";
|
||||||
|
|
||||||
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
|
import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
|
||||||
|
|
||||||
import { BrowserComponentState } from '../../models/browserComponentState';
|
import { BrowserComponentState } from "../../models/browserComponentState";
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
const ComponentId = 'CiphersComponent';
|
const ComponentId = "CiphersComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-ciphers',
|
selector: "app-vault-ciphers",
|
||||||
templateUrl: 'ciphers.component.html',
|
templateUrl: "ciphers.component.html",
|
||||||
})
|
})
|
||||||
export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy {
|
export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy {
|
||||||
groupingTitle: string;
|
groupingTitle: string;
|
||||||
state: BrowserComponentState;
|
state: BrowserComponentState;
|
||||||
folderId: string = null;
|
folderId: string = null;
|
||||||
collectionId: string = null;
|
collectionId: string = null;
|
||||||
type: CipherType = null;
|
type: CipherType = null;
|
||||||
nestedFolders: TreeNode<FolderView>[];
|
nestedFolders: TreeNode<FolderView>[];
|
||||||
nestedCollections: TreeNode<CollectionView>[];
|
nestedCollections: TreeNode<CollectionView>[];
|
||||||
searchTypeSearch = false;
|
searchTypeSearch = false;
|
||||||
|
|
||||||
private selectedTimeout: number;
|
private selectedTimeout: number;
|
||||||
private preventSelected = false;
|
private preventSelected = false;
|
||||||
private applySavedState = true;
|
private applySavedState = true;
|
||||||
private scrollingContainer = 'cdk-virtual-scroll-viewport';
|
private scrollingContainer = "cdk-virtual-scroll-viewport";
|
||||||
|
|
||||||
constructor(searchService: SearchService, private route: ActivatedRoute,
|
constructor(
|
||||||
private router: Router, private location: Location,
|
searchService: SearchService,
|
||||||
private ngZone: NgZone, private broadcasterService: BroadcasterService,
|
private route: ActivatedRoute,
|
||||||
private changeDetectorRef: ChangeDetectorRef, private stateService: StateService,
|
private router: Router,
|
||||||
private popupUtils: PopupUtilsService, private i18nService: I18nService,
|
private location: Location,
|
||||||
private folderService: FolderService, private collectionService: CollectionService,
|
private ngZone: NgZone,
|
||||||
private platformUtilsService: PlatformUtilsService, private cipherService: CipherService) {
|
private broadcasterService: BroadcasterService,
|
||||||
super(searchService);
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
this.applySavedState = (window as any).previousPopupUrl != null &&
|
private stateService: StateService,
|
||||||
!(window as any).previousPopupUrl.startsWith('/ciphers');
|
private popupUtils: PopupUtilsService,
|
||||||
}
|
private i18nService: I18nService,
|
||||||
|
private folderService: FolderService,
|
||||||
|
private collectionService: CollectionService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private cipherService: CipherService
|
||||||
|
) {
|
||||||
|
super(searchService);
|
||||||
|
this.applySavedState =
|
||||||
|
(window as any).previousPopupUrl != null &&
|
||||||
|
!(window as any).previousPopupUrl.startsWith("/ciphers");
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
if (this.applySavedState) {
|
if (this.applySavedState) {
|
||||||
this.state = await this.stateService.getBrowserCipherComponentState();
|
this.state = await this.stateService.getBrowserCipherComponentState();
|
||||||
if (this.state?.searchText) {
|
if (this.state?.searchText) {
|
||||||
this.searchText = this.state.searchText;
|
this.searchText = this.state.searchText;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.deleted) {
|
||||||
|
this.groupingTitle = this.i18nService.t("trash");
|
||||||
|
this.searchPlaceholder = this.i18nService.t("searchTrash");
|
||||||
|
await this.load(null, true);
|
||||||
|
} else if (params.type) {
|
||||||
|
this.searchPlaceholder = this.i18nService.t("searchType");
|
||||||
|
this.type = parseInt(params.type, null);
|
||||||
|
switch (this.type) {
|
||||||
|
case CipherType.Login:
|
||||||
|
this.groupingTitle = this.i18nService.t("logins");
|
||||||
|
break;
|
||||||
|
case CipherType.Card:
|
||||||
|
this.groupingTitle = this.i18nService.t("cards");
|
||||||
|
break;
|
||||||
|
case CipherType.Identity:
|
||||||
|
this.groupingTitle = this.i18nService.t("identities");
|
||||||
|
break;
|
||||||
|
case CipherType.SecureNote:
|
||||||
|
this.groupingTitle = this.i18nService.t("secureNotes");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await this.load((c) => c.type === this.type);
|
||||||
|
} else if (params.folderId) {
|
||||||
|
this.folderId = params.folderId === "none" ? null : params.folderId;
|
||||||
|
this.searchPlaceholder = this.i18nService.t("searchFolder");
|
||||||
|
if (this.folderId != null) {
|
||||||
|
const folderNode = await this.folderService.getNested(this.folderId);
|
||||||
|
if (folderNode != null && folderNode.node != null) {
|
||||||
|
this.groupingTitle = folderNode.node.name;
|
||||||
|
this.nestedFolders =
|
||||||
|
folderNode.children != null && folderNode.children.length > 0
|
||||||
|
? folderNode.children
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.groupingTitle = this.i18nService.t("noneFolder");
|
||||||
|
}
|
||||||
|
await this.load((c) => c.folderId === this.folderId);
|
||||||
|
} else if (params.collectionId) {
|
||||||
|
this.collectionId = params.collectionId;
|
||||||
|
this.searchPlaceholder = this.i18nService.t("searchCollection");
|
||||||
|
const collectionNode = await this.collectionService.getNested(this.collectionId);
|
||||||
|
if (collectionNode != null && collectionNode.node != null) {
|
||||||
|
this.groupingTitle = collectionNode.node.name;
|
||||||
|
this.nestedCollections =
|
||||||
|
collectionNode.children != null && collectionNode.children.length > 0
|
||||||
|
? collectionNode.children
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
await this.load(
|
||||||
|
(c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.groupingTitle = this.i18nService.t("allItems");
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.applySavedState && this.state != null) {
|
||||||
|
window.setTimeout(
|
||||||
|
() =>
|
||||||
|
this.popupUtils.setContentScrollY(window, this.state?.scrollY, this.scrollingContainer),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.stateService.setBrowserCipherComponentState(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "syncCompleted":
|
||||||
|
if (message.successfully) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.refresh();
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
if (params.deleted) {
|
default:
|
||||||
this.groupingTitle = this.i18nService.t('trash');
|
break;
|
||||||
this.searchPlaceholder = this.i18nService.t('searchTrash');
|
|
||||||
await this.load(null, true);
|
|
||||||
} else if (params.type) {
|
|
||||||
this.searchPlaceholder = this.i18nService.t('searchType');
|
|
||||||
this.type = parseInt(params.type, null);
|
|
||||||
switch (this.type) {
|
|
||||||
case CipherType.Login:
|
|
||||||
this.groupingTitle = this.i18nService.t('logins');
|
|
||||||
break;
|
|
||||||
case CipherType.Card:
|
|
||||||
this.groupingTitle = this.i18nService.t('cards');
|
|
||||||
break;
|
|
||||||
case CipherType.Identity:
|
|
||||||
this.groupingTitle = this.i18nService.t('identities');
|
|
||||||
break;
|
|
||||||
case CipherType.SecureNote:
|
|
||||||
this.groupingTitle = this.i18nService.t('secureNotes');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await this.load(c => c.type === this.type);
|
|
||||||
} else if (params.folderId) {
|
|
||||||
this.folderId = params.folderId === 'none' ? null : params.folderId;
|
|
||||||
this.searchPlaceholder = this.i18nService.t('searchFolder');
|
|
||||||
if (this.folderId != null) {
|
|
||||||
const folderNode = await this.folderService.getNested(this.folderId);
|
|
||||||
if (folderNode != null && folderNode.node != null) {
|
|
||||||
this.groupingTitle = folderNode.node.name;
|
|
||||||
this.nestedFolders = folderNode.children != null && folderNode.children.length > 0 ?
|
|
||||||
folderNode.children : null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.groupingTitle = this.i18nService.t('noneFolder');
|
|
||||||
}
|
|
||||||
await this.load(c => c.folderId === this.folderId);
|
|
||||||
} else if (params.collectionId) {
|
|
||||||
this.collectionId = params.collectionId;
|
|
||||||
this.searchPlaceholder = this.i18nService.t('searchCollection');
|
|
||||||
const collectionNode = await this.collectionService.getNested(this.collectionId);
|
|
||||||
if (collectionNode != null && collectionNode.node != null) {
|
|
||||||
this.groupingTitle = collectionNode.node.name;
|
|
||||||
this.nestedCollections = collectionNode.children != null && collectionNode.children.length > 0 ?
|
|
||||||
collectionNode.children : null;
|
|
||||||
}
|
|
||||||
await this.load(c => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1);
|
|
||||||
} else {
|
|
||||||
this.groupingTitle = this.i18nService.t('allItems');
|
|
||||||
await this.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.applySavedState && this.state != null) {
|
|
||||||
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state?.scrollY,
|
|
||||||
this.scrollingContainer), 0);
|
|
||||||
}
|
|
||||||
await this.stateService.setBrowserCipherComponentState(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
|
||||||
this.ngZone.run(async () => {
|
|
||||||
switch (message.command) {
|
|
||||||
case 'syncCompleted':
|
|
||||||
if (message.successfully) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.refresh();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.saveState();
|
|
||||||
this.broadcasterService.unsubscribe(ComponentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectCipher(cipher: CipherView) {
|
|
||||||
this.selectedTimeout = window.setTimeout(() => {
|
|
||||||
if (!this.preventSelected) {
|
|
||||||
super.selectCipher(cipher);
|
|
||||||
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
|
|
||||||
}
|
|
||||||
this.preventSelected = false;
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectFolder(folder: FolderView) {
|
|
||||||
if (folder.id != null) {
|
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { folderId: folder.id } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectCollection(collection: CollectionView) {
|
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchCipher(cipher: CipherView) {
|
|
||||||
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedTimeout != null) {
|
this.changeDetectorRef.detectChanges();
|
||||||
window.clearTimeout(this.selectedTimeout);
|
});
|
||||||
}
|
});
|
||||||
this.preventSelected = true;
|
}
|
||||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
|
||||||
BrowserApi.createNewTab(cipher.login.launchUri);
|
ngOnDestroy() {
|
||||||
if (this.popupUtils.inPopup(window)) {
|
this.saveState();
|
||||||
BrowserApi.closePopup(window);
|
this.broadcasterService.unsubscribe(ComponentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectCipher(cipher: CipherView) {
|
||||||
|
this.selectedTimeout = window.setTimeout(() => {
|
||||||
|
if (!this.preventSelected) {
|
||||||
|
super.selectCipher(cipher);
|
||||||
|
this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } });
|
||||||
|
}
|
||||||
|
this.preventSelected = false;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFolder(folder: FolderView) {
|
||||||
|
if (folder.id != null) {
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCollection(collection: CollectionView) {
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async launchCipher(cipher: CipherView) {
|
||||||
|
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCipher() {
|
if (this.selectedTimeout != null) {
|
||||||
if (this.deleted) {
|
window.clearTimeout(this.selectedTimeout);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
super.addCipher();
|
|
||||||
this.router.navigate(['/add-cipher'], {
|
|
||||||
queryParams: {
|
|
||||||
folderId: this.folderId,
|
|
||||||
type: this.type,
|
|
||||||
collectionId: this.collectionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.preventSelected = true;
|
||||||
|
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||||
|
BrowserApi.createNewTab(cipher.login.launchUri);
|
||||||
|
if (this.popupUtils.inPopup(window)) {
|
||||||
|
BrowserApi.closePopup(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
back() {
|
addCipher() {
|
||||||
(window as any).routeDirection = 'b';
|
if (this.deleted) {
|
||||||
this.location.back();
|
return false;
|
||||||
}
|
}
|
||||||
|
super.addCipher();
|
||||||
|
this.router.navigate(["/add-cipher"], {
|
||||||
|
queryParams: {
|
||||||
|
folderId: this.folderId,
|
||||||
|
type: this.type,
|
||||||
|
collectionId: this.collectionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
showGroupings() {
|
back() {
|
||||||
return !this.isSearching() &&
|
(window as any).routeDirection = "b";
|
||||||
((this.nestedFolders && this.nestedFolders.length) ||
|
this.location.back();
|
||||||
(this.nestedCollections && this.nestedCollections.length));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async saveState() {
|
showGroupings() {
|
||||||
this.state = {
|
return (
|
||||||
scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer),
|
!this.isSearching() &&
|
||||||
searchText: this.searchText,
|
((this.nestedFolders && this.nestedFolders.length) ||
|
||||||
};
|
(this.nestedCollections && this.nestedCollections.length))
|
||||||
await this.stateService.setBrowserCipherComponentState(this.state);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async saveState() {
|
||||||
|
this.state = {
|
||||||
|
scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer),
|
||||||
|
searchText: this.searchText,
|
||||||
|
};
|
||||||
|
await this.stateService.setBrowserCipherComponentState(this.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,44 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib-angular/components/collections.component';
|
import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-collections',
|
selector: "app-vault-collections",
|
||||||
templateUrl: 'collections.component.html',
|
templateUrl: "collections.component.html",
|
||||||
})
|
})
|
||||||
export class CollectionsComponent extends BaseCollectionsComponent {
|
export class CollectionsComponent extends BaseCollectionsComponent {
|
||||||
constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, cipherService: CipherService,
|
collectionService: CollectionService,
|
||||||
private route: ActivatedRoute, private location: Location, logService: LogService) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
i18nService: I18nService,
|
||||||
}
|
cipherService: CipherService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private location: Location,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.onSavedCollections.subscribe(() => {
|
this.onSavedCollections.subscribe(() => {
|
||||||
this.back();
|
this.back();
|
||||||
});
|
});
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
this.cipherId = params.cipherId;
|
this.cipherId = params.cipherId;
|
||||||
await this.load();
|
await this.load();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
back() {
|
back() {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,241 +1,249 @@
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { Router } from "@angular/router";
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { AutofillService } from '../../services/abstractions/autofill.service';
|
import { AutofillService } from "../../services/abstractions/autofill.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'CurrentTabComponent';
|
const BroadcasterSubscriptionId = "CurrentTabComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-current-tab',
|
selector: "app-current-tab",
|
||||||
templateUrl: 'current-tab.component.html',
|
templateUrl: "current-tab.component.html",
|
||||||
})
|
})
|
||||||
export class CurrentTabComponent implements OnInit, OnDestroy {
|
export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||||
pageDetails: any[] = [];
|
pageDetails: any[] = [];
|
||||||
cardCiphers: CipherView[];
|
cardCiphers: CipherView[];
|
||||||
identityCiphers: CipherView[];
|
identityCiphers: CipherView[];
|
||||||
loginCiphers: CipherView[];
|
loginCiphers: CipherView[];
|
||||||
url: string;
|
url: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
inSidebar = false;
|
inSidebar = false;
|
||||||
searchTypeSearch = false;
|
searchTypeSearch = false;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
private totpCode: string;
|
private totpCode: string;
|
||||||
private totpTimeout: number;
|
private totpTimeout: number;
|
||||||
private loadedTimeout: number;
|
private loadedTimeout: number;
|
||||||
private searchTimeout: number;
|
private searchTimeout: number;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
|
constructor(
|
||||||
private popupUtilsService: PopupUtilsService, private autofillService: AutofillService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService, private router: Router,
|
private cipherService: CipherService,
|
||||||
private ngZone: NgZone, private broadcasterService: BroadcasterService,
|
private popupUtilsService: PopupUtilsService,
|
||||||
private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService,
|
private autofillService: AutofillService,
|
||||||
private searchService: SearchService, private stateService: StateService,
|
private i18nService: I18nService,
|
||||||
private passwordRepromptService: PasswordRepromptService) {
|
private router: Router,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private syncService: SyncService,
|
||||||
|
private searchService: SearchService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private passwordRepromptService: PasswordRepromptService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||||
|
this.inSidebar = this.popupUtilsService.inSidebar(window);
|
||||||
|
|
||||||
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case "syncCompleted":
|
||||||
|
if (this.loaded) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.load();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "collectPageDetailsResponse":
|
||||||
|
if (message.sender === BroadcasterSubscriptionId) {
|
||||||
|
this.pageDetails.push({
|
||||||
|
frameId: message.webExtSender.frameId,
|
||||||
|
tab: message.tab,
|
||||||
|
details: message.details,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.syncService.syncInProgress) {
|
||||||
|
await this.load();
|
||||||
|
} else {
|
||||||
|
this.loadedTimeout = window.setTimeout(async () => {
|
||||||
|
if (!this.loaded) {
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
window.setTimeout(() => {
|
||||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
document.getElementById("search").focus();
|
||||||
this.inSidebar = this.popupUtilsService.inSidebar(window);
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
ngOnDestroy() {
|
||||||
this.ngZone.run(async () => {
|
window.clearTimeout(this.loadedTimeout);
|
||||||
switch (message.command) {
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
case 'syncCompleted':
|
}
|
||||||
if (this.loaded) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.load();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'collectPageDetailsResponse':
|
|
||||||
if (message.sender === BroadcasterSubscriptionId) {
|
|
||||||
this.pageDetails.push({
|
|
||||||
frameId: message.webExtSender.frameId,
|
|
||||||
tab: message.tab,
|
|
||||||
details: message.details,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
async refresh() {
|
||||||
});
|
await this.load();
|
||||||
});
|
}
|
||||||
|
|
||||||
if (!this.syncService.syncInProgress) {
|
addCipher() {
|
||||||
await this.load();
|
this.router.navigate(["/add-cipher"], { queryParams: { name: this.hostname, uri: this.url } });
|
||||||
|
}
|
||||||
|
|
||||||
|
viewCipher(cipher: CipherView) {
|
||||||
|
this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillCipher(cipher: CipherView) {
|
||||||
|
if (
|
||||||
|
cipher.reprompt !== CipherRepromptType.None &&
|
||||||
|
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.totpCode = null;
|
||||||
|
if (this.totpTimeout != null) {
|
||||||
|
window.clearTimeout(this.totpTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pageDetails == null || this.pageDetails.length === 0) {
|
||||||
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.totpCode = await this.autofillService.doAutoFill({
|
||||||
|
cipher: cipher,
|
||||||
|
pageDetails: this.pageDetails,
|
||||||
|
doc: window.document,
|
||||||
|
fillNewPassword: true,
|
||||||
|
});
|
||||||
|
if (this.totpCode != null) {
|
||||||
|
this.platformUtilsService.copyToClipboard(this.totpCode, { window: window });
|
||||||
|
}
|
||||||
|
if (this.popupUtilsService.inPopup(window)) {
|
||||||
|
if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) {
|
||||||
|
BrowserApi.closePopup(window);
|
||||||
} else {
|
} else {
|
||||||
this.loadedTimeout = window.setTimeout(async () => {
|
// Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard
|
||||||
if (!this.loaded) {
|
setTimeout(() => BrowserApi.closePopup(window), 50);
|
||||||
await this.load();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError"));
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.setTimeout(() => {
|
searchVault() {
|
||||||
document.getElementById('search').focus();
|
if (this.searchTimeout != null) {
|
||||||
}, 100);
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
if (!this.searchService.isSearchable(this.searchText)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.searchTimeout = window.setTimeout(async () => {
|
||||||
|
this.router.navigate(["/tabs/vault"], { queryParams: { searchText: this.searchText } });
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeOnEsc(e: KeyboardEvent) {
|
||||||
|
// If input not empty, use browser default behavior of clearing input instead
|
||||||
|
if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) {
|
||||||
|
BrowserApi.closePopup(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
const tab = await BrowserApi.getTabFromCurrentWindow();
|
||||||
|
if (tab != null) {
|
||||||
|
this.url = tab.url;
|
||||||
|
} else {
|
||||||
|
this.loginCiphers = [];
|
||||||
|
this.loaded = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
this.hostname = Utils.getHostname(this.url);
|
||||||
window.clearTimeout(this.loadedTimeout);
|
this.pageDetails = [];
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
BrowserApi.tabSendMessage(tab, {
|
||||||
|
command: "collectPageDetails",
|
||||||
|
tab: tab,
|
||||||
|
sender: BroadcasterSubscriptionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherTypes: CipherType[] = [];
|
||||||
|
const dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
|
||||||
|
const dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
|
||||||
|
if (!dontShowCards) {
|
||||||
|
otherTypes.push(CipherType.Card);
|
||||||
|
}
|
||||||
|
if (!dontShowIdentities) {
|
||||||
|
otherTypes.push(CipherType.Identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refresh() {
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
await this.load();
|
this.url,
|
||||||
}
|
otherTypes.length > 0 ? otherTypes : null
|
||||||
|
);
|
||||||
|
|
||||||
addCipher() {
|
this.loginCiphers = [];
|
||||||
this.router.navigate(['/add-cipher'], { queryParams: { name: this.hostname, uri: this.url } });
|
this.cardCiphers = [];
|
||||||
}
|
this.identityCiphers = [];
|
||||||
|
|
||||||
viewCipher(cipher: CipherView) {
|
ciphers.forEach((c) => {
|
||||||
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
|
switch (c.type) {
|
||||||
}
|
case CipherType.Login:
|
||||||
|
this.loginCiphers.push(c);
|
||||||
|
break;
|
||||||
|
case CipherType.Card:
|
||||||
|
this.cardCiphers.push(c);
|
||||||
|
break;
|
||||||
|
case CipherType.Identity:
|
||||||
|
this.identityCiphers.push(c);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async fillCipher(cipher: CipherView) {
|
this.loginCiphers = this.loginCiphers.sort((a, b) =>
|
||||||
if (cipher.reprompt !== CipherRepromptType.None && !await this.passwordRepromptService.showPasswordPrompt()) {
|
this.cipherService.sortCiphersByLastUsedThenName(a, b)
|
||||||
return;
|
);
|
||||||
}
|
this.loaded = true;
|
||||||
|
}
|
||||||
this.totpCode = null;
|
|
||||||
if (this.totpTimeout != null) {
|
|
||||||
window.clearTimeout(this.totpTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pageDetails == null || this.pageDetails.length === 0) {
|
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('autofillError'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.totpCode = await this.autofillService.doAutoFill({
|
|
||||||
cipher: cipher,
|
|
||||||
pageDetails: this.pageDetails,
|
|
||||||
doc: window.document,
|
|
||||||
fillNewPassword: true,
|
|
||||||
});
|
|
||||||
if (this.totpCode != null) {
|
|
||||||
this.platformUtilsService.copyToClipboard(this.totpCode, { window: window });
|
|
||||||
}
|
|
||||||
if (this.popupUtilsService.inPopup(window)) {
|
|
||||||
if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) {
|
|
||||||
BrowserApi.closePopup(window);
|
|
||||||
} else {
|
|
||||||
// Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard
|
|
||||||
setTimeout(() => BrowserApi.closePopup(window), 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.ngZone.run(() => {
|
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('autofillError'));
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchVault() {
|
|
||||||
if (this.searchTimeout != null) {
|
|
||||||
clearTimeout(this.searchTimeout);
|
|
||||||
}
|
|
||||||
if (!this.searchService.isSearchable(this.searchText)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.searchTimeout = window.setTimeout(async () => {
|
|
||||||
this.router.navigate(['/tabs/vault'], { queryParams: { searchText: this.searchText } });
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
closeOnEsc(e: KeyboardEvent) {
|
|
||||||
// If input not empty, use browser default behavior of clearing input instead
|
|
||||||
if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) {
|
|
||||||
BrowserApi.closePopup(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async load() {
|
|
||||||
const tab = await BrowserApi.getTabFromCurrentWindow();
|
|
||||||
if (tab != null) {
|
|
||||||
this.url = tab.url;
|
|
||||||
} else {
|
|
||||||
this.loginCiphers = [];
|
|
||||||
this.loaded = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hostname = Utils.getHostname(this.url);
|
|
||||||
this.pageDetails = [];
|
|
||||||
BrowserApi.tabSendMessage(tab, {
|
|
||||||
command: 'collectPageDetails',
|
|
||||||
tab: tab,
|
|
||||||
sender: BroadcasterSubscriptionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const otherTypes: CipherType[] = [];
|
|
||||||
const dontShowCards = await this.stateService.getDontShowCardsCurrentTab();
|
|
||||||
const dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab();
|
|
||||||
if (!dontShowCards) {
|
|
||||||
otherTypes.push(CipherType.Card);
|
|
||||||
}
|
|
||||||
if (!dontShowIdentities) {
|
|
||||||
otherTypes.push(CipherType.Identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(this.url,
|
|
||||||
otherTypes.length > 0 ? otherTypes : null);
|
|
||||||
|
|
||||||
this.loginCiphers = [];
|
|
||||||
this.cardCiphers = [];
|
|
||||||
this.identityCiphers = [];
|
|
||||||
|
|
||||||
ciphers.forEach(c => {
|
|
||||||
switch (c.type) {
|
|
||||||
case CipherType.Login:
|
|
||||||
this.loginCiphers.push(c);
|
|
||||||
break;
|
|
||||||
case CipherType.Card:
|
|
||||||
this.cardCiphers.push(c);
|
|
||||||
break;
|
|
||||||
case CipherType.Identity:
|
|
||||||
this.identityCiphers.push(c);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loginCiphers = this.loginCiphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,355 +1,369 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||||
import { FolderView } from 'jslib-common/models/view/folderView';
|
import { FolderView } from "jslib-common/models/view/folderView";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
|
|
||||||
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
|
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
|
||||||
|
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
const ComponentId = 'GroupingsComponent';
|
const ComponentId = "GroupingsComponent";
|
||||||
const ScopeStateId = ComponentId + 'Scope';
|
const ScopeStateId = ComponentId + "Scope";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-groupings',
|
selector: "app-vault-groupings",
|
||||||
templateUrl: 'groupings.component.html',
|
templateUrl: "groupings.component.html",
|
||||||
})
|
})
|
||||||
export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy {
|
export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy {
|
||||||
|
get showNoFolderCiphers(): boolean {
|
||||||
|
return (
|
||||||
|
this.noFolderCiphers != null &&
|
||||||
|
this.noFolderCiphers.length < this.noFolderListSize &&
|
||||||
|
this.collections.length === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get showNoFolderCiphers(): boolean {
|
get folderCount(): number {
|
||||||
return this.noFolderCiphers != null && this.noFolderCiphers.length < this.noFolderListSize &&
|
return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1);
|
||||||
this.collections.length === 0;
|
}
|
||||||
}
|
ciphers: CipherView[];
|
||||||
|
favoriteCiphers: CipherView[];
|
||||||
|
noFolderCiphers: CipherView[];
|
||||||
|
folderCounts = new Map<string, number>();
|
||||||
|
collectionCounts = new Map<string, number>();
|
||||||
|
typeCounts = new Map<CipherType, number>();
|
||||||
|
searchText: string;
|
||||||
|
state: any;
|
||||||
|
scopeState: any;
|
||||||
|
showLeftHeader = true;
|
||||||
|
searchPending = false;
|
||||||
|
searchTypeSearch = false;
|
||||||
|
deletedCount = 0;
|
||||||
|
|
||||||
get folderCount(): number {
|
private loadedTimeout: number;
|
||||||
return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1);
|
private selectedTimeout: number;
|
||||||
}
|
private preventSelected = false;
|
||||||
ciphers: CipherView[];
|
private noFolderListSize = 100;
|
||||||
favoriteCiphers: CipherView[];
|
private searchTimeout: any = null;
|
||||||
noFolderCiphers: CipherView[];
|
private hasSearched = false;
|
||||||
folderCounts = new Map<string, number>();
|
private hasLoadedAllCiphers = false;
|
||||||
collectionCounts = new Map<string, number>();
|
private allCiphers: CipherView[] = null;
|
||||||
typeCounts = new Map<CipherType, number>();
|
|
||||||
searchText: string;
|
|
||||||
state: any;
|
|
||||||
scopeState: any;
|
|
||||||
showLeftHeader = true;
|
|
||||||
searchPending = false;
|
|
||||||
searchTypeSearch = false;
|
|
||||||
deletedCount = 0;
|
|
||||||
|
|
||||||
private loadedTimeout: number;
|
constructor(
|
||||||
private selectedTimeout: number;
|
collectionService: CollectionService,
|
||||||
private preventSelected = false;
|
folderService: FolderService,
|
||||||
private noFolderListSize = 100;
|
private cipherService: CipherService,
|
||||||
private searchTimeout: any = null;
|
private router: Router,
|
||||||
private hasSearched = false;
|
private ngZone: NgZone,
|
||||||
private hasLoadedAllCiphers = false;
|
private broadcasterService: BroadcasterService,
|
||||||
private allCiphers: CipherView[] = null;
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
baseStateService: StateService,
|
||||||
|
private popupUtils: PopupUtilsService,
|
||||||
|
private syncService: SyncService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private searchService: SearchService,
|
||||||
|
private location: Location,
|
||||||
|
private browserStateService: StateService
|
||||||
|
) {
|
||||||
|
super(collectionService, folderService, baseStateService);
|
||||||
|
this.noFolderListSize = 100;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(collectionService: CollectionService, folderService: FolderService,
|
async ngOnInit() {
|
||||||
private cipherService: CipherService, private router: Router,
|
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||||
private ngZone: NgZone, private broadcasterService: BroadcasterService,
|
this.showLeftHeader = !(
|
||||||
private changeDetectorRef: ChangeDetectorRef, private route: ActivatedRoute,
|
this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()
|
||||||
baseStateService: StateService, private popupUtils: PopupUtilsService,
|
);
|
||||||
private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
|
await this.browserStateService.setBrowserCipherComponentState(null);
|
||||||
private searchService: SearchService, private location: Location,
|
|
||||||
private browserStateService: StateService) {
|
|
||||||
super(collectionService, folderService, baseStateService);
|
|
||||||
this.noFolderListSize = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
||||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
this.ngZone.run(async () => {
|
||||||
this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox());
|
switch (message.command) {
|
||||||
await this.browserStateService.setBrowserCipherComponentState(null);
|
case "syncCompleted":
|
||||||
|
window.setTimeout(() => {
|
||||||
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
this.load();
|
||||||
this.ngZone.run(async () => {
|
}, 500);
|
||||||
switch (message.command) {
|
break;
|
||||||
case 'syncCompleted':
|
default:
|
||||||
window.setTimeout(() => {
|
break;
|
||||||
this.load();
|
|
||||||
}, 500);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const restoredScopeState = await this.restoreState();
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
|
||||||
this.state = (await this.browserStateService.getBrowserGroupingComponentState()) || {};
|
|
||||||
if (this.state?.searchText) {
|
|
||||||
this.searchText = this.state.searchText;
|
|
||||||
} else if (params.searchText) {
|
|
||||||
this.searchText = params.searchText;
|
|
||||||
this.location.replaceState('vault');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.syncService.syncInProgress) {
|
|
||||||
this.load();
|
|
||||||
} else {
|
|
||||||
this.loadedTimeout = window.setTimeout(() => {
|
|
||||||
if (!this.loaded) {
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.syncService.syncInProgress || restoredScopeState) {
|
|
||||||
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state?.scrollY), 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
if (this.loadedTimeout != null) {
|
|
||||||
window.clearTimeout(this.loadedTimeout);
|
|
||||||
}
|
}
|
||||||
if (this.selectedTimeout != null) {
|
|
||||||
window.clearTimeout(this.selectedTimeout);
|
this.changeDetectorRef.detectChanges();
|
||||||
}
|
});
|
||||||
this.saveState();
|
});
|
||||||
this.broadcasterService.unsubscribe(ComponentId);
|
|
||||||
|
const restoredScopeState = await this.restoreState();
|
||||||
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
|
this.state = (await this.browserStateService.getBrowserGroupingComponentState()) || {};
|
||||||
|
if (this.state?.searchText) {
|
||||||
|
this.searchText = this.state.searchText;
|
||||||
|
} else if (params.searchText) {
|
||||||
|
this.searchText = params.searchText;
|
||||||
|
this.location.replaceState("vault");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.syncService.syncInProgress) {
|
||||||
|
this.load();
|
||||||
|
} else {
|
||||||
|
this.loadedTimeout = window.setTimeout(() => {
|
||||||
|
if (!this.loaded) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.syncService.syncInProgress || restoredScopeState) {
|
||||||
|
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state?.scrollY), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.loadedTimeout != null) {
|
||||||
|
window.clearTimeout(this.loadedTimeout);
|
||||||
|
}
|
||||||
|
if (this.selectedTimeout != null) {
|
||||||
|
window.clearTimeout(this.selectedTimeout);
|
||||||
|
}
|
||||||
|
this.saveState();
|
||||||
|
this.broadcasterService.unsubscribe(ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
await super.load(false);
|
||||||
|
await this.loadCiphers();
|
||||||
|
if (this.showNoFolderCiphers && this.nestedFolders.length > 0) {
|
||||||
|
// Remove "No Folder" from folder listing
|
||||||
|
this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
super.loaded = true;
|
||||||
await super.load(false);
|
}
|
||||||
|
|
||||||
|
async loadCiphers() {
|
||||||
|
this.allCiphers = await this.cipherService.getAllDecrypted();
|
||||||
|
if (!this.hasLoadedAllCiphers) {
|
||||||
|
this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText);
|
||||||
|
}
|
||||||
|
this.deletedCount = this.allCiphers.filter((c) => c.isDeleted).length;
|
||||||
|
await this.search(null);
|
||||||
|
let favoriteCiphers: CipherView[] = null;
|
||||||
|
let noFolderCiphers: CipherView[] = null;
|
||||||
|
const folderCounts = new Map<string, number>();
|
||||||
|
const collectionCounts = new Map<string, number>();
|
||||||
|
const typeCounts = new Map<CipherType, number>();
|
||||||
|
|
||||||
|
this.ciphers.forEach((c) => {
|
||||||
|
if (c.isDeleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (c.favorite) {
|
||||||
|
if (favoriteCiphers == null) {
|
||||||
|
favoriteCiphers = [];
|
||||||
|
}
|
||||||
|
favoriteCiphers.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.folderId == null) {
|
||||||
|
if (noFolderCiphers == null) {
|
||||||
|
noFolderCiphers = [];
|
||||||
|
}
|
||||||
|
noFolderCiphers.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeCounts.has(c.type)) {
|
||||||
|
typeCounts.set(c.type, typeCounts.get(c.type) + 1);
|
||||||
|
} else {
|
||||||
|
typeCounts.set(c.type, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderCounts.has(c.folderId)) {
|
||||||
|
folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1);
|
||||||
|
} else {
|
||||||
|
folderCounts.set(c.folderId, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.collectionIds != null) {
|
||||||
|
c.collectionIds.forEach((colId) => {
|
||||||
|
if (collectionCounts.has(colId)) {
|
||||||
|
collectionCounts.set(colId, collectionCounts.get(colId) + 1);
|
||||||
|
} else {
|
||||||
|
collectionCounts.set(colId, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.favoriteCiphers = favoriteCiphers;
|
||||||
|
this.noFolderCiphers = noFolderCiphers;
|
||||||
|
this.typeCounts = typeCounts;
|
||||||
|
this.folderCounts = folderCounts;
|
||||||
|
this.collectionCounts = collectionCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(timeout: number = null) {
|
||||||
|
this.searchPending = false;
|
||||||
|
if (this.searchTimeout != null) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
const filterDeleted = (c: CipherView) => !c.isDeleted;
|
||||||
|
if (timeout == null) {
|
||||||
|
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
||||||
|
this.ciphers = await this.searchService.searchCiphers(
|
||||||
|
this.searchText,
|
||||||
|
filterDeleted,
|
||||||
|
this.allCiphers
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.searchPending = true;
|
||||||
|
this.searchTimeout = setTimeout(async () => {
|
||||||
|
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
||||||
|
if (!this.hasLoadedAllCiphers && !this.hasSearched) {
|
||||||
await this.loadCiphers();
|
await this.loadCiphers();
|
||||||
if (this.showNoFolderCiphers && this.nestedFolders.length > 0) {
|
} else {
|
||||||
// Remove "No Folder" from folder listing
|
this.ciphers = await this.searchService.searchCiphers(
|
||||||
this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1);
|
this.searchText,
|
||||||
}
|
filterDeleted,
|
||||||
|
this.allCiphers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.searchPending = false;
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
super.loaded = true;
|
async selectType(type: CipherType) {
|
||||||
|
super.selectType(type);
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { type: type } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectFolder(folder: FolderView) {
|
||||||
|
super.selectFolder(folder);
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id || "none" } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectCollection(collection: CollectionView) {
|
||||||
|
super.selectCollection(collection);
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectTrash() {
|
||||||
|
super.selectTrash();
|
||||||
|
this.router.navigate(["/ciphers"], { queryParams: { deleted: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectCipher(cipher: CipherView) {
|
||||||
|
this.selectedTimeout = window.setTimeout(() => {
|
||||||
|
if (!this.preventSelected) {
|
||||||
|
this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } });
|
||||||
|
}
|
||||||
|
this.preventSelected = false;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async launchCipher(cipher: CipherView) {
|
||||||
|
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCiphers() {
|
if (this.selectedTimeout != null) {
|
||||||
this.allCiphers = await this.cipherService.getAllDecrypted();
|
window.clearTimeout(this.selectedTimeout);
|
||||||
if (!this.hasLoadedAllCiphers) {
|
}
|
||||||
this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText);
|
this.preventSelected = true;
|
||||||
}
|
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||||
this.deletedCount = this.allCiphers.filter(c => c.isDeleted).length;
|
BrowserApi.createNewTab(cipher.login.launchUri);
|
||||||
await this.search(null);
|
if (this.popupUtils.inPopup(window)) {
|
||||||
let favoriteCiphers: CipherView[] = null;
|
BrowserApi.closePopup(window);
|
||||||
let noFolderCiphers: CipherView[] = null;
|
}
|
||||||
const folderCounts = new Map<string, number>();
|
}
|
||||||
const collectionCounts = new Map<string, number>();
|
|
||||||
const typeCounts = new Map<CipherType, number>();
|
|
||||||
|
|
||||||
this.ciphers.forEach(c => {
|
async addCipher() {
|
||||||
if (c.isDeleted) {
|
this.router.navigate(["/add-cipher"]);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
if (c.favorite) {
|
|
||||||
if (favoriteCiphers == null) {
|
|
||||||
favoriteCiphers = [];
|
|
||||||
}
|
|
||||||
favoriteCiphers.push(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.folderId == null) {
|
showSearching() {
|
||||||
if (noFolderCiphers == null) {
|
return (
|
||||||
noFolderCiphers = [];
|
this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText))
|
||||||
}
|
);
|
||||||
noFolderCiphers.push(c);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (typeCounts.has(c.type)) {
|
closeOnEsc(e: KeyboardEvent) {
|
||||||
typeCounts.set(c.type, typeCounts.get(c.type) + 1);
|
// If input not empty, use browser default behavior of clearing input instead
|
||||||
} else {
|
if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) {
|
||||||
typeCounts.set(c.type, 1);
|
BrowserApi.closePopup(window);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (folderCounts.has(c.folderId)) {
|
private async saveState() {
|
||||||
folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1);
|
this.state = {
|
||||||
} else {
|
scrollY: this.popupUtils.getContentScrollY(window),
|
||||||
folderCounts.set(c.folderId, 1);
|
searchText: this.searchText,
|
||||||
}
|
favoriteCiphers: this.favoriteCiphers,
|
||||||
|
noFolderCiphers: this.noFolderCiphers,
|
||||||
|
ciphers: this.ciphers,
|
||||||
|
collectionCounts: this.collectionCounts,
|
||||||
|
folderCounts: this.folderCounts,
|
||||||
|
typeCounts: this.typeCounts,
|
||||||
|
folders: this.folders,
|
||||||
|
collections: this.collections,
|
||||||
|
deletedCount: this.deletedCount,
|
||||||
|
};
|
||||||
|
await this.browserStateService.setBrowserGroupingComponentState(this.scopeState);
|
||||||
|
}
|
||||||
|
|
||||||
if (c.collectionIds != null) {
|
private async restoreState(): Promise<boolean> {
|
||||||
c.collectionIds.forEach(colId => {
|
this.scopeState = await this.browserStateService.getBrowserGroupingComponentState();
|
||||||
if (collectionCounts.has(colId)) {
|
if (this.scopeState == null) {
|
||||||
collectionCounts.set(colId, collectionCounts.get(colId) + 1);
|
return false;
|
||||||
} else {
|
|
||||||
collectionCounts.set(colId, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.favoriteCiphers = favoriteCiphers;
|
|
||||||
this.noFolderCiphers = noFolderCiphers;
|
|
||||||
this.typeCounts = typeCounts;
|
|
||||||
this.folderCounts = folderCounts;
|
|
||||||
this.collectionCounts = collectionCounts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(timeout: number = null) {
|
if (this.scopeState.favoriteCiphers != null) {
|
||||||
this.searchPending = false;
|
this.favoriteCiphers = this.scopeState.favoriteCiphers;
|
||||||
if (this.searchTimeout != null) {
|
}
|
||||||
clearTimeout(this.searchTimeout);
|
if (this.scopeState.noFolderCiphers != null) {
|
||||||
}
|
this.noFolderCiphers = this.scopeState.noFolderCiphers;
|
||||||
const filterDeleted = (c: CipherView) => !c.isDeleted;
|
}
|
||||||
if (timeout == null) {
|
if (this.scopeState.ciphers != null) {
|
||||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
this.ciphers = this.scopeState.ciphers;
|
||||||
this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers);
|
}
|
||||||
return;
|
if (this.scopeState.collectionCounts != null) {
|
||||||
}
|
this.collectionCounts = this.scopeState.collectionCounts;
|
||||||
this.searchPending = true;
|
}
|
||||||
this.searchTimeout = setTimeout(async () => {
|
if (this.scopeState.folderCounts != null) {
|
||||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
this.folderCounts = this.scopeState.folderCounts;
|
||||||
if (!this.hasLoadedAllCiphers && !this.hasSearched) {
|
}
|
||||||
await this.loadCiphers();
|
if (this.scopeState.typeCounts != null) {
|
||||||
} else {
|
this.typeCounts = this.scopeState.typeCounts;
|
||||||
this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers);
|
}
|
||||||
}
|
if (this.scopeState.folders != null) {
|
||||||
this.searchPending = false;
|
this.folders = this.scopeState.folders;
|
||||||
}, timeout);
|
}
|
||||||
|
if (this.scopeState.collections != null) {
|
||||||
|
this.collections = this.scopeState.collections;
|
||||||
|
}
|
||||||
|
if (this.scopeState.deletedCiphers != null) {
|
||||||
|
this.deletedCount = this.scopeState.deletedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectType(type: CipherType) {
|
return true;
|
||||||
super.selectType(type);
|
}
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { type: type } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectFolder(folder: FolderView) {
|
|
||||||
super.selectFolder(folder);
|
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { folderId: folder.id || 'none' } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectCollection(collection: CollectionView) {
|
|
||||||
super.selectCollection(collection);
|
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectTrash() {
|
|
||||||
super.selectTrash();
|
|
||||||
this.router.navigate(['/ciphers'], { queryParams: { deleted: true } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectCipher(cipher: CipherView) {
|
|
||||||
this.selectedTimeout = window.setTimeout(() => {
|
|
||||||
if (!this.preventSelected) {
|
|
||||||
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
|
|
||||||
}
|
|
||||||
this.preventSelected = false;
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchCipher(cipher: CipherView) {
|
|
||||||
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedTimeout != null) {
|
|
||||||
window.clearTimeout(this.selectedTimeout);
|
|
||||||
}
|
|
||||||
this.preventSelected = true;
|
|
||||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
|
||||||
BrowserApi.createNewTab(cipher.login.launchUri);
|
|
||||||
if (this.popupUtils.inPopup(window)) {
|
|
||||||
BrowserApi.closePopup(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addCipher() {
|
|
||||||
this.router.navigate(['/add-cipher']);
|
|
||||||
}
|
|
||||||
|
|
||||||
showSearching() {
|
|
||||||
return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText));
|
|
||||||
}
|
|
||||||
|
|
||||||
closeOnEsc(e: KeyboardEvent) {
|
|
||||||
// If input not empty, use browser default behavior of clearing input instead
|
|
||||||
if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) {
|
|
||||||
BrowserApi.closePopup(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveState() {
|
|
||||||
this.state = {
|
|
||||||
scrollY: this.popupUtils.getContentScrollY(window),
|
|
||||||
searchText: this.searchText,
|
|
||||||
favoriteCiphers: this.favoriteCiphers,
|
|
||||||
noFolderCiphers: this.noFolderCiphers,
|
|
||||||
ciphers: this.ciphers,
|
|
||||||
collectionCounts: this.collectionCounts,
|
|
||||||
folderCounts: this.folderCounts,
|
|
||||||
typeCounts: this.typeCounts,
|
|
||||||
folders: this.folders,
|
|
||||||
collections: this.collections,
|
|
||||||
deletedCount: this.deletedCount,
|
|
||||||
};
|
|
||||||
await this.browserStateService.setBrowserGroupingComponentState(this.scopeState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async restoreState(): Promise<boolean> {
|
|
||||||
this.scopeState = await this.browserStateService.getBrowserGroupingComponentState();
|
|
||||||
if (this.scopeState == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scopeState.favoriteCiphers != null) {
|
|
||||||
this.favoriteCiphers = this.scopeState.favoriteCiphers;
|
|
||||||
}
|
|
||||||
if (this.scopeState.noFolderCiphers != null) {
|
|
||||||
this.noFolderCiphers = this.scopeState.noFolderCiphers;
|
|
||||||
}
|
|
||||||
if (this.scopeState.ciphers != null) {
|
|
||||||
this.ciphers = this.scopeState.ciphers;
|
|
||||||
}
|
|
||||||
if (this.scopeState.collectionCounts != null) {
|
|
||||||
this.collectionCounts = this.scopeState.collectionCounts;
|
|
||||||
}
|
|
||||||
if (this.scopeState.folderCounts != null) {
|
|
||||||
this.folderCounts = this.scopeState.folderCounts;
|
|
||||||
}
|
|
||||||
if (this.scopeState.typeCounts != null) {
|
|
||||||
this.typeCounts = this.scopeState.typeCounts;
|
|
||||||
}
|
|
||||||
if (this.scopeState.folders != null) {
|
|
||||||
this.folders = this.scopeState.folders;
|
|
||||||
}
|
|
||||||
if (this.scopeState.collections != null) {
|
|
||||||
this.collections = this.scopeState.collections;
|
|
||||||
}
|
|
||||||
if (this.scopeState.deletedCiphers != null) {
|
|
||||||
this.deletedCount = this.scopeState.deletedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,63 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
|
import { ShareComponent as BaseShareComponent } from "jslib-angular/components/share.component";
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-share',
|
selector: "app-vault-share",
|
||||||
templateUrl: 'share.component.html',
|
templateUrl: "share.component.html",
|
||||||
})
|
})
|
||||||
export class ShareComponent extends BaseShareComponent {
|
export class ShareComponent extends BaseShareComponent {
|
||||||
constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, logService: LogService,
|
collectionService: CollectionService,
|
||||||
cipherService: CipherService, private route: ActivatedRoute,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private router: Router, organizationService: OrganizationService) {
|
i18nService: I18nService,
|
||||||
super(collectionService, platformUtilsService, i18nService, cipherService,
|
logService: LogService,
|
||||||
logService, organizationService);
|
cipherService: CipherService,
|
||||||
}
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
organizationService: OrganizationService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
collectionService,
|
||||||
|
platformUtilsService,
|
||||||
|
i18nService,
|
||||||
|
cipherService,
|
||||||
|
logService,
|
||||||
|
organizationService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.onSharedCipher.subscribe(() => {
|
this.onSharedCipher.subscribe(() => {
|
||||||
this.router.navigate(['view-cipher', { cipherId: this.cipherId }]);
|
this.router.navigate(["view-cipher", { cipherId: this.cipherId }]);
|
||||||
});
|
});
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
this.cipherId = params.cipherId;
|
this.cipherId = params.cipherId;
|
||||||
await this.load();
|
await this.load();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit(): Promise<boolean> {
|
async submit(): Promise<boolean> {
|
||||||
const success = await super.submit();
|
const success = await super.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
this.router.navigate(['/view-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
|
this.router.navigate(["/view-cipher"], {
|
||||||
}
|
replaceUrl: true,
|
||||||
|
queryParams: { cipherId: this.cipher.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,273 +1,297 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
|
|
||||||
import { Cipher } from 'jslib-common/models/domain/cipher';
|
import { Cipher } from "jslib-common/models/domain/cipher";
|
||||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||||
|
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
|
||||||
import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component';
|
import { ViewComponent as BaseViewComponent } from "jslib-angular/components/view.component";
|
||||||
|
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
import { AutofillService } from '../../services/abstractions/autofill.service';
|
import { AutofillService } from "../../services/abstractions/autofill.service";
|
||||||
import { StateService } from '../../services/abstractions/state.service';
|
import { StateService } from "../../services/abstractions/state.service";
|
||||||
|
|
||||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
import { PopupUtilsService } from "../services/popup-utils.service";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'ChildViewComponent';
|
const BroadcasterSubscriptionId = "ChildViewComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-view',
|
selector: "app-vault-view",
|
||||||
templateUrl: 'view.component.html',
|
templateUrl: "view.component.html",
|
||||||
})
|
})
|
||||||
export class ViewComponent extends BaseViewComponent {
|
export class ViewComponent extends BaseViewComponent {
|
||||||
showAttachments = true;
|
showAttachments = true;
|
||||||
pageDetails: any[] = [];
|
pageDetails: any[] = [];
|
||||||
tab: any;
|
tab: any;
|
||||||
loadPageDetailsTimeout: number;
|
loadPageDetailsTimeout: number;
|
||||||
inPopout = false;
|
inPopout = false;
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
|
|
||||||
constructor(cipherService: CipherService, totpService: TotpService,
|
constructor(
|
||||||
tokenService: TokenService, i18nService: I18nService,
|
cipherService: CipherService,
|
||||||
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
|
totpService: TotpService,
|
||||||
auditService: AuditService, private route: ActivatedRoute,
|
tokenService: TokenService,
|
||||||
private router: Router, private location: Location,
|
i18nService: I18nService,
|
||||||
broadcasterService: BroadcasterService, ngZone: NgZone,
|
cryptoService: CryptoService,
|
||||||
changeDetectorRef: ChangeDetectorRef, stateService: StateService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
eventService: EventService, private autofillService: AutofillService,
|
auditService: AuditService,
|
||||||
private messagingService: MessagingService, private popupUtilsService: PopupUtilsService,
|
private route: ActivatedRoute,
|
||||||
apiService: ApiService, passwordRepromptService: PasswordRepromptService,
|
private router: Router,
|
||||||
logService: LogService) {
|
private location: Location,
|
||||||
super(cipherService, totpService, tokenService, i18nService,
|
broadcasterService: BroadcasterService,
|
||||||
cryptoService, platformUtilsService, auditService, window,
|
ngZone: NgZone,
|
||||||
broadcasterService, ngZone, changeDetectorRef, eventService,
|
changeDetectorRef: ChangeDetectorRef,
|
||||||
apiService, passwordRepromptService, logService, stateService);
|
stateService: StateService,
|
||||||
}
|
eventService: EventService,
|
||||||
|
private autofillService: AutofillService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private popupUtilsService: PopupUtilsService,
|
||||||
|
apiService: ApiService,
|
||||||
|
passwordRepromptService: PasswordRepromptService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
cipherService,
|
||||||
|
totpService,
|
||||||
|
tokenService,
|
||||||
|
i18nService,
|
||||||
|
cryptoService,
|
||||||
|
platformUtilsService,
|
||||||
|
auditService,
|
||||||
|
window,
|
||||||
|
broadcasterService,
|
||||||
|
ngZone,
|
||||||
|
changeDetectorRef,
|
||||||
|
eventService,
|
||||||
|
apiService,
|
||||||
|
passwordRepromptService,
|
||||||
|
logService,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.inPopout = this.popupUtilsService.inPopout(window);
|
this.inPopout = this.popupUtilsService.inPopout(window);
|
||||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
if (params.cipherId) {
|
if (params.cipherId) {
|
||||||
this.cipherId = params.cipherId;
|
this.cipherId = params.cipherId;
|
||||||
} else {
|
} else {
|
||||||
this.close();
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.load();
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
await this.load();
|
case "tabChanged":
|
||||||
});
|
case "windowChanged":
|
||||||
|
if (this.loadPageDetailsTimeout != null) {
|
||||||
super.ngOnInit();
|
window.clearTimeout(this.loadPageDetailsTimeout);
|
||||||
|
|
||||||
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;
|
|
||||||
case 'tabChanged':
|
|
||||||
case 'windowChanged':
|
|
||||||
if (this.loadPageDetailsTimeout != null) {
|
|
||||||
window.clearTimeout(this.loadPageDetailsTimeout);
|
|
||||||
}
|
|
||||||
this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
super.ngOnDestroy();
|
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
await super.load();
|
|
||||||
await this.loadPageDetails();
|
|
||||||
}
|
|
||||||
|
|
||||||
async edit() {
|
|
||||||
if (this.cipher.isDeleted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!await super.edit()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async clone() {
|
|
||||||
if (this.cipher.isDeleted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await super.clone()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(['/clone-cipher'], {
|
|
||||||
queryParams: {
|
|
||||||
cloneMode: true,
|
|
||||||
cipherId: this.cipher.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async share() {
|
|
||||||
if (!await super.share()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.cipher.organizationId == null) {
|
|
||||||
this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fillCipher() {
|
|
||||||
const didAutofill = await this.doAutofill();
|
|
||||||
if (didAutofill) {
|
|
||||||
this.platformUtilsService.showToast('success', null,
|
|
||||||
this.i18nService.t('autoFillSuccess'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fillCipherAndSave() {
|
|
||||||
const didAutofill = await this.doAutofill();
|
|
||||||
|
|
||||||
if (didAutofill) {
|
|
||||||
if (this.tab == null) {
|
|
||||||
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 === this.tab.url)) {
|
|
||||||
this.platformUtilsService.showToast('success', null,
|
|
||||||
this.i18nService.t('autoFillSuccessAndSavedUri'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginUri = new LoginUriView();
|
|
||||||
loginUri.uri = this.tab.url;
|
|
||||||
this.cipher.login.uris.push(loginUri);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cipher: Cipher = await this.cipherService.encrypt(this.cipher);
|
|
||||||
await this.cipherService.saveWithServer(cipher);
|
|
||||||
this.platformUtilsService.showToast('success', null,
|
|
||||||
this.i18nService.t('autoFillSuccessAndSavedUri'));
|
|
||||||
this.messagingService.send('editedCipher');
|
|
||||||
} catch {
|
|
||||||
this.platformUtilsService.showToast('error', null,
|
|
||||||
this.i18nService.t('unexpectedError'));
|
|
||||||
}
|
}
|
||||||
|
this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
super.ngOnDestroy();
|
||||||
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
await super.load();
|
||||||
|
await this.loadPageDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
async edit() {
|
||||||
|
if (this.cipher.isDeleted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(await super.edit())) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async restore() {
|
this.router.navigate(["/edit-cipher"], { queryParams: { cipherId: this.cipher.id } });
|
||||||
if (!this.cipher.isDeleted) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
if (await super.restore()) {
|
async clone() {
|
||||||
this.close();
|
if (this.cipher.isDeleted) {
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete() {
|
if (!(await super.clone())) {
|
||||||
if (await super.delete()) {
|
return false;
|
||||||
this.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
this.router.navigate(["/clone-cipher"], {
|
||||||
this.location.back();
|
queryParams: {
|
||||||
|
cloneMode: true,
|
||||||
|
cipherId: this.cipher.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async share() {
|
||||||
|
if (!(await super.share())) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPageDetails() {
|
if (this.cipher.organizationId == null) {
|
||||||
this.pageDetails = [];
|
this.router.navigate(["/share-cipher"], {
|
||||||
this.tab = await BrowserApi.getTabFromCurrentWindow();
|
replaceUrl: true,
|
||||||
if (this.tab == null) {
|
queryParams: { cipherId: this.cipher.id },
|
||||||
return;
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillCipher() {
|
||||||
|
const didAutofill = await this.doAutofill();
|
||||||
|
if (didAutofill) {
|
||||||
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("autoFillSuccess"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillCipherAndSave() {
|
||||||
|
const didAutofill = await this.doAutofill();
|
||||||
|
|
||||||
|
if (didAutofill) {
|
||||||
|
if (this.tab == null) {
|
||||||
|
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 === this.tab.url)) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("autoFillSuccessAndSavedUri")
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
BrowserApi.tabSendMessage(this.tab, {
|
}
|
||||||
command: 'collectPageDetails',
|
|
||||||
tab: this.tab,
|
const loginUri = new LoginUriView();
|
||||||
sender: BroadcasterSubscriptionId,
|
loginUri.uri = this.tab.url;
|
||||||
});
|
this.cipher.login.uris.push(loginUri);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cipher: Cipher = await this.cipherService.encrypt(this.cipher);
|
||||||
|
await this.cipherService.saveWithServer(cipher);
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("autoFillSuccessAndSavedUri")
|
||||||
|
);
|
||||||
|
this.messagingService.send("editedCipher");
|
||||||
|
} catch {
|
||||||
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async restore() {
|
||||||
|
if (!this.cipher.isDeleted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (await super.restore()) {
|
||||||
|
this.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
if (await super.delete()) {
|
||||||
|
this.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPageDetails() {
|
||||||
|
this.pageDetails = [];
|
||||||
|
this.tab = await BrowserApi.getTabFromCurrentWindow();
|
||||||
|
if (this.tab == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BrowserApi.tabSendMessage(this.tab, {
|
||||||
|
command: "collectPageDetails",
|
||||||
|
tab: this.tab,
|
||||||
|
sender: BroadcasterSubscriptionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doAutofill() {
|
||||||
|
if (!(await this.promptPassword())) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doAutofill() {
|
if (this.pageDetails == null || this.pageDetails.length === 0) {
|
||||||
if (!await this.promptPassword()) {
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError"));
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
fillNewPassword: true,
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.totpCode = await this.autofillService.doAutoFill({
|
||||||
|
cipher: this.cipher,
|
||||||
|
pageDetails: this.pageDetails,
|
||||||
|
doc: window.document,
|
||||||
|
fillNewPassword: true,
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,33 @@
|
||||||
import { StateService as BaseStateServiceAbstraction } from 'jslib-common/abstractions/state.service';
|
import { StateService as BaseStateServiceAbstraction } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { StorageOptions } from 'jslib-common/models/domain/storageOptions';
|
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
|
||||||
|
|
||||||
import { Account } from 'src/models/account';
|
|
||||||
import { BrowserComponentState } from 'src/models/browserComponentState';
|
|
||||||
import { BrowserGroupingsComponentState } from 'src/models/browserGroupingsComponentState';
|
|
||||||
import { BrowserSendComponentState } from 'src/models/browserSendComponentState';
|
|
||||||
|
|
||||||
|
import { Account } from "src/models/account";
|
||||||
|
import { BrowserComponentState } from "src/models/browserComponentState";
|
||||||
|
import { BrowserGroupingsComponentState } from "src/models/browserGroupingsComponentState";
|
||||||
|
import { BrowserSendComponentState } from "src/models/browserSendComponentState";
|
||||||
|
|
||||||
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
|
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
|
||||||
getBrowserGroupingComponentState: (options?: StorageOptions) => Promise<BrowserGroupingsComponentState>;
|
getBrowserGroupingComponentState: (
|
||||||
setBrowserGroupingComponentState: (value: BrowserGroupingsComponentState, options?: StorageOptions) => Promise<void>;
|
options?: StorageOptions
|
||||||
getBrowserCipherComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>;
|
) => Promise<BrowserGroupingsComponentState>;
|
||||||
setBrowserCipherComponentState: (value: BrowserComponentState, options?: StorageOptions) => Promise<void>;
|
setBrowserGroupingComponentState: (
|
||||||
getBrowserSendComponentState: (options?: StorageOptions) => Promise<BrowserSendComponentState>;
|
value: BrowserGroupingsComponentState,
|
||||||
setBrowserSendComponentState: (value: BrowserSendComponentState, options?: StorageOptions) => Promise<void>;
|
options?: StorageOptions
|
||||||
getBrowserSendTypeComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>;
|
) => Promise<void>;
|
||||||
setBrowserSendTypeComponentState: (value: BrowserComponentState, options?: StorageOptions) => Promise<void>;
|
getBrowserCipherComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>;
|
||||||
|
setBrowserCipherComponentState: (
|
||||||
|
value: BrowserComponentState,
|
||||||
|
options?: StorageOptions
|
||||||
|
) => Promise<void>;
|
||||||
|
getBrowserSendComponentState: (options?: StorageOptions) => Promise<BrowserSendComponentState>;
|
||||||
|
setBrowserSendComponentState: (
|
||||||
|
value: BrowserSendComponentState,
|
||||||
|
options?: StorageOptions
|
||||||
|
) => Promise<void>;
|
||||||
|
getBrowserSendTypeComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>;
|
||||||
|
setBrowserSendTypeComponentState: (
|
||||||
|
value: BrowserComponentState,
|
||||||
|
options?: StorageOptions
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
|
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
|
||||||
|
|
||||||
import { CryptoService } from 'jslib-common/services/crypto.service';
|
import { CryptoService } from "jslib-common/services/crypto.service";
|
||||||
|
|
||||||
export class BrowserCryptoService extends CryptoService {
|
export class BrowserCryptoService extends CryptoService {
|
||||||
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) {
|
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) {
|
||||||
if (keySuffix === 'biometric') {
|
if (keySuffix === "biometric") {
|
||||||
await this.platformUtilService.authenticateBiometric();
|
await this.platformUtilService.authenticateBiometric();
|
||||||
return (await this.getKey())?.keyB64;
|
return (await this.getKey())?.keyB64;
|
||||||
}
|
|
||||||
|
|
||||||
return await super.retrieveKeyFromStorage(keySuffix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await super.retrieveKeyFromStorage(keySuffix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,340 +1,375 @@
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
import { SafariApp } from '../browser/safariApp';
|
import { SafariApp } from "../browser/safariApp";
|
||||||
|
|
||||||
import { DeviceType } from 'jslib-common/enums/deviceType';
|
import { DeviceType } from "jslib-common/enums/deviceType";
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
import { ThemeType } from "jslib-common/enums/themeType";
|
||||||
|
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from '../services/abstractions/state.service';
|
import { StateService } from "../services/abstractions/state.service";
|
||||||
|
|
||||||
const DialogPromiseExpiration = 600000; // 10 minutes
|
const DialogPromiseExpiration = 600000; // 10 minutes
|
||||||
|
|
||||||
export default class BrowserPlatformUtilsService implements PlatformUtilsService {
|
export default class BrowserPlatformUtilsService implements PlatformUtilsService {
|
||||||
identityClientId: string = 'browser';
|
identityClientId: string = "browser";
|
||||||
|
|
||||||
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>();
|
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void; date: Date }>();
|
||||||
private passwordDialogResolves = new Map<number, { tryResolve: (canceled: boolean, password: string) => Promise<boolean>, date: Date }>();
|
private passwordDialogResolves = new Map<
|
||||||
private deviceCache: DeviceType = null;
|
number,
|
||||||
private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)');
|
{ tryResolve: (canceled: boolean, password: string) => Promise<boolean>; date: Date }
|
||||||
|
>();
|
||||||
|
private deviceCache: DeviceType = null;
|
||||||
|
private prefersColorSchemeDark = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
constructor(private messagingService: MessagingService, private stateService: StateService,
|
constructor(
|
||||||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
private messagingService: MessagingService,
|
||||||
private biometricCallback: () => Promise<boolean>) { }
|
private stateService: StateService,
|
||||||
|
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
|
private biometricCallback: () => Promise<boolean>
|
||||||
|
) {}
|
||||||
|
|
||||||
getDevice(): DeviceType {
|
getDevice(): DeviceType {
|
||||||
if (this.deviceCache) {
|
if (this.deviceCache) {
|
||||||
return this.deviceCache;
|
return this.deviceCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
navigator.userAgent.indexOf(" Firefox/") !== -1 ||
|
||||||
|
navigator.userAgent.indexOf(" Gecko/") !== -1
|
||||||
|
) {
|
||||||
|
this.deviceCache = DeviceType.FirefoxExtension;
|
||||||
|
} else if (
|
||||||
|
(!!(window as any).opr && !!opr.addons) ||
|
||||||
|
!!(window as any).opera ||
|
||||||
|
navigator.userAgent.indexOf(" OPR/") >= 0
|
||||||
|
) {
|
||||||
|
this.deviceCache = DeviceType.OperaExtension;
|
||||||
|
} else if (navigator.userAgent.indexOf(" Edg/") !== -1) {
|
||||||
|
this.deviceCache = DeviceType.EdgeExtension;
|
||||||
|
} else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) {
|
||||||
|
this.deviceCache = DeviceType.VivaldiExtension;
|
||||||
|
} else if ((window as any).chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) {
|
||||||
|
this.deviceCache = DeviceType.ChromeExtension;
|
||||||
|
} else if (navigator.userAgent.indexOf(" Safari/") !== -1) {
|
||||||
|
this.deviceCache = DeviceType.SafariExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.deviceCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeviceString(): string {
|
||||||
|
const device = DeviceType[this.getDevice()].toLowerCase();
|
||||||
|
return device.replace("extension", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirefox(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.FirefoxExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isChrome(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.ChromeExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEdge(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.EdgeExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpera(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.OperaExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isVivaldi(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.VivaldiExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSafari(): boolean {
|
||||||
|
return this.getDevice() === DeviceType.SafariExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIE(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMacAppStore(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async isViewOpen(): Promise<boolean> {
|
||||||
|
if (await BrowserApi.isPopupOpen()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSafari()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarView = this.sidebarViewName();
|
||||||
|
const sidebarOpen =
|
||||||
|
sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0;
|
||||||
|
if (sidebarOpen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabOpen = chrome.extension.getViews({ type: "tab" }).length > 0;
|
||||||
|
return tabOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
lockTimeout(): number {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchUri(uri: string, options?: any): void {
|
||||||
|
BrowserApi.createNewTab(uri, options && options.extensionPage === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
|
||||||
|
BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplicationVersion(): Promise<string> {
|
||||||
|
return Promise.resolve(BrowserApi.getApplicationVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsWebAuthn(win: Window): boolean {
|
||||||
|
return typeof PublicKeyCredential !== "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsDuo(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
type: "error" | "success" | "warning" | "info",
|
||||||
|
title: string,
|
||||||
|
text: string | string[],
|
||||||
|
options?: any
|
||||||
|
): void {
|
||||||
|
this.messagingService.send("showToast", {
|
||||||
|
text: text,
|
||||||
|
title: title,
|
||||||
|
type: type,
|
||||||
|
options: options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
body: string,
|
||||||
|
title?: string,
|
||||||
|
confirmText?: string,
|
||||||
|
cancelText?: string,
|
||||||
|
type?: string,
|
||||||
|
bodyIsHtml: boolean = false
|
||||||
|
) {
|
||||||
|
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
text: bodyIsHtml ? null : body,
|
||||||
|
html: bodyIsHtml ? body : null,
|
||||||
|
title: title,
|
||||||
|
confirmText: confirmText,
|
||||||
|
cancelText: cancelText,
|
||||||
|
type: type,
|
||||||
|
dialogId: dialogId,
|
||||||
|
});
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isDev(): boolean {
|
||||||
|
return process.env.ENV === "development";
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelfHost(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(text: string, options?: any): void {
|
||||||
|
let win = window;
|
||||||
|
let doc = window.document;
|
||||||
|
if (options && (options.window || options.win)) {
|
||||||
|
win = options.window || options.win;
|
||||||
|
doc = win.document;
|
||||||
|
} else if (options && options.doc) {
|
||||||
|
doc = options.doc;
|
||||||
|
}
|
||||||
|
const clearing = options ? !!options.clearing : false;
|
||||||
|
const clearMs: number = options && options.clearMs ? options.clearMs : null;
|
||||||
|
|
||||||
|
if (this.isSafari()) {
|
||||||
|
SafariApp.sendMessageToApp("copyToClipboard", text).then(() => {
|
||||||
|
if (!clearing && this.clipboardWriteCallback != null) {
|
||||||
|
this.clipboardWriteCallback(text, clearMs);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) {
|
} else if (
|
||||||
this.deviceCache = DeviceType.FirefoxExtension;
|
this.isFirefox() &&
|
||||||
} else if ((!!(window as any).opr && !!opr.addons) || !!(window as any).opera ||
|
(win as any).navigator.clipboard &&
|
||||||
navigator.userAgent.indexOf(' OPR/') >= 0) {
|
(win as any).navigator.clipboard.writeText
|
||||||
this.deviceCache = DeviceType.OperaExtension;
|
) {
|
||||||
} else if (navigator.userAgent.indexOf(' Edg/') !== -1) {
|
(win as any).navigator.clipboard.writeText(text).then(() => {
|
||||||
this.deviceCache = DeviceType.EdgeExtension;
|
if (!clearing && this.clipboardWriteCallback != null) {
|
||||||
} else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) {
|
this.clipboardWriteCallback(text, clearMs);
|
||||||
this.deviceCache = DeviceType.VivaldiExtension;
|
|
||||||
} else if ((window as any).chrome && navigator.userAgent.indexOf(' Chrome/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.ChromeExtension;
|
|
||||||
} else if (navigator.userAgent.indexOf(' Safari/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.SafariExtension;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
} else if ((win as any).clipboardData && (win as any).clipboardData.setData) {
|
||||||
|
// IE specific code path to prevent textarea being shown while dialog is visible.
|
||||||
|
(win as any).clipboardData.setData("Text", text);
|
||||||
|
if (!clearing && this.clipboardWriteCallback != null) {
|
||||||
|
this.clipboardWriteCallback(text, clearMs);
|
||||||
|
}
|
||||||
|
} else if (doc.queryCommandSupported && doc.queryCommandSupported("copy")) {
|
||||||
|
if (this.isChrome() && text === "") {
|
||||||
|
text = "\u0000";
|
||||||
|
}
|
||||||
|
|
||||||
return this.deviceCache;
|
const textarea = doc.createElement("textarea");
|
||||||
}
|
textarea.textContent = text == null || text === "" ? " " : text;
|
||||||
|
// Prevent scrolling to bottom of page in MS Edge.
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
doc.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
|
||||||
getDeviceString(): string {
|
try {
|
||||||
const device = DeviceType[this.getDevice()].toLowerCase();
|
// Security exception may be thrown by some browsers.
|
||||||
return device.replace('extension', '');
|
if (doc.execCommand("copy") && !clearing && this.clipboardWriteCallback != null) {
|
||||||
}
|
this.clipboardWriteCallback(text, clearMs);
|
||||||
|
|
||||||
isFirefox(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.FirefoxExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isChrome(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.ChromeExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isEdge(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.EdgeExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isOpera(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.OperaExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isVivaldi(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.VivaldiExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSafari(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.SafariExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isIE(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isMacAppStore(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async isViewOpen(): Promise<boolean> {
|
|
||||||
if (await BrowserApi.isPopupOpen()) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.warn("Copy to clipboard failed.", e);
|
||||||
|
} finally {
|
||||||
|
doc.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isSafari()) {
|
async readFromClipboard(options?: any): Promise<string> {
|
||||||
return false;
|
let win = window;
|
||||||
|
let doc = window.document;
|
||||||
|
if (options && (options.window || options.win)) {
|
||||||
|
win = options.window || options.win;
|
||||||
|
doc = win.document;
|
||||||
|
} else if (options && options.doc) {
|
||||||
|
doc = options.doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSafari()) {
|
||||||
|
return await SafariApp.sendMessageToApp("readFromClipboard");
|
||||||
|
} else if (
|
||||||
|
this.isFirefox() &&
|
||||||
|
(win as any).navigator.clipboard &&
|
||||||
|
(win as any).navigator.clipboard.readText
|
||||||
|
) {
|
||||||
|
return await (win as any).navigator.clipboard.readText();
|
||||||
|
} else if (doc.queryCommandSupported && doc.queryCommandSupported("paste")) {
|
||||||
|
const textarea = doc.createElement("textarea");
|
||||||
|
// Prevent scrolling to bottom of page in MS Edge.
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
doc.body.appendChild(textarea);
|
||||||
|
textarea.focus();
|
||||||
|
try {
|
||||||
|
// Security exception may be thrown by some browsers.
|
||||||
|
if (doc.execCommand("paste")) {
|
||||||
|
return textarea.value;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.warn("Read from clipboard failed.", e);
|
||||||
|
} finally {
|
||||||
|
doc.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const sidebarView = this.sidebarViewName();
|
resolveDialogPromise(dialogId: number, confirmed: boolean) {
|
||||||
const sidebarOpen = sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0;
|
if (this.showDialogResolves.has(dialogId)) {
|
||||||
if (sidebarOpen) {
|
const resolveObj = this.showDialogResolves.get(dialogId);
|
||||||
return true;
|
resolveObj.resolve(confirmed);
|
||||||
}
|
this.showDialogResolves.delete(dialogId);
|
||||||
|
|
||||||
const tabOpen = chrome.extension.getViews({ type: 'tab' }).length > 0;
|
|
||||||
return tabOpen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lockTimeout(): number {
|
// Clean up old promises
|
||||||
return null;
|
this.showDialogResolves.forEach((val, key) => {
|
||||||
|
const age = new Date().getTime() - val.date.getTime();
|
||||||
|
if (age > DialogPromiseExpiration) {
|
||||||
|
this.showDialogResolves.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolvePasswordDialogPromise(
|
||||||
|
dialogId: number,
|
||||||
|
canceled: boolean,
|
||||||
|
password: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
let result = false;
|
||||||
|
if (this.passwordDialogResolves.has(dialogId)) {
|
||||||
|
const resolveObj = this.passwordDialogResolves.get(dialogId);
|
||||||
|
if (await resolveObj.tryResolve(canceled, password)) {
|
||||||
|
this.passwordDialogResolves.delete(dialogId);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launchUri(uri: string, options?: any): void {
|
// Clean up old promises
|
||||||
BrowserApi.createNewTab(uri, options && options.extensionPage === true);
|
this.passwordDialogResolves.forEach((val, key) => {
|
||||||
|
const age = new Date().getTime() - val.date.getTime();
|
||||||
|
if (age > DialogPromiseExpiration) {
|
||||||
|
this.passwordDialogResolves.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async supportsBiometric() {
|
||||||
|
const platformInfo = await BrowserApi.getPlatformInfo();
|
||||||
|
if (platformInfo.os === "android") {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
|
if (this.isFirefox()) {
|
||||||
BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
|
return parseInt((await browser.runtime.getBrowserInfo()).version.split(".")[0], 10) >= 87;
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplicationVersion(): Promise<string> {
|
return true;
|
||||||
return Promise.resolve(BrowserApi.getApplicationVersion());
|
}
|
||||||
|
|
||||||
|
authenticateBiometric() {
|
||||||
|
return this.biometricCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebarViewName(): string {
|
||||||
|
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
|
||||||
|
return "sidebar";
|
||||||
|
} else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) {
|
||||||
|
return "sidebar_panel";
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsWebAuthn(win: Window): boolean {
|
return null;
|
||||||
return (typeof (PublicKeyCredential) !== 'undefined');
|
}
|
||||||
}
|
|
||||||
|
supportsSecureStorage(): boolean {
|
||||||
supportsDuo(): boolean {
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
getDefaultSystemTheme(): Promise<ThemeType.Light | ThemeType.Dark> {
|
||||||
showToast(type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[],
|
return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light);
|
||||||
options?: any): void {
|
}
|
||||||
this.messagingService.send('showToast', {
|
|
||||||
text: text,
|
onDefaultSystemThemeChange(callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown) {
|
||||||
title: title,
|
this.prefersColorSchemeDark.addEventListener("change", ({ matches }) => {
|
||||||
type: type,
|
callback(matches ? ThemeType.Dark : ThemeType.Light);
|
||||||
options: options,
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
async getEffectiveTheme() {
|
||||||
showDialog(body: string, title?: string, confirmText?: string, cancelText?: string, type?: string,
|
const theme = (await this.stateService.getTheme()) as ThemeType;
|
||||||
bodyIsHtml: boolean = false) {
|
if (theme == null || theme === ThemeType.System) {
|
||||||
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
return this.getDefaultSystemTheme();
|
||||||
this.messagingService.send('showDialog', {
|
} else {
|
||||||
text: bodyIsHtml ? null : body,
|
return theme;
|
||||||
html: bodyIsHtml ? body : null,
|
|
||||||
title: title,
|
|
||||||
confirmText: confirmText,
|
|
||||||
cancelText: cancelText,
|
|
||||||
type: type,
|
|
||||||
dialogId: dialogId,
|
|
||||||
});
|
|
||||||
return new Promise<boolean>(resolve => {
|
|
||||||
this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isDev(): boolean {
|
|
||||||
return process.env.ENV === 'development';
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelfHost(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
copyToClipboard(text: string, options?: any): void {
|
|
||||||
let win = window;
|
|
||||||
let doc = window.document;
|
|
||||||
if (options && (options.window || options.win)) {
|
|
||||||
win = options.window || options.win;
|
|
||||||
doc = win.document;
|
|
||||||
} else if (options && options.doc) {
|
|
||||||
doc = options.doc;
|
|
||||||
}
|
|
||||||
const clearing = options ? !!options.clearing : false;
|
|
||||||
const clearMs: number = options && options.clearMs ? options.clearMs : null;
|
|
||||||
|
|
||||||
if (this.isSafari()) {
|
|
||||||
SafariApp.sendMessageToApp('copyToClipboard', text).then(() => {
|
|
||||||
if (!clearing && this.clipboardWriteCallback != null) {
|
|
||||||
this.clipboardWriteCallback(text, clearMs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.writeText) {
|
|
||||||
(win as any).navigator.clipboard.writeText(text).then(() => {
|
|
||||||
if (!clearing && this.clipboardWriteCallback != null) {
|
|
||||||
this.clipboardWriteCallback(text, clearMs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if ((win as any).clipboardData && (win as any).clipboardData.setData) {
|
|
||||||
// IE specific code path to prevent textarea being shown while dialog is visible.
|
|
||||||
(win as any).clipboardData.setData('Text', text);
|
|
||||||
if (!clearing && this.clipboardWriteCallback != null) {
|
|
||||||
this.clipboardWriteCallback(text, clearMs);
|
|
||||||
}
|
|
||||||
} else if (doc.queryCommandSupported && doc.queryCommandSupported('copy')) {
|
|
||||||
if (this.isChrome() && text === '') {
|
|
||||||
text = '\u0000';
|
|
||||||
}
|
|
||||||
|
|
||||||
const textarea = doc.createElement('textarea');
|
|
||||||
textarea.textContent = text == null || text === '' ? ' ' : text;
|
|
||||||
// Prevent scrolling to bottom of page in MS Edge.
|
|
||||||
textarea.style.position = 'fixed';
|
|
||||||
doc.body.appendChild(textarea);
|
|
||||||
textarea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Security exception may be thrown by some browsers.
|
|
||||||
if (doc.execCommand('copy') && !clearing && this.clipboardWriteCallback != null) {
|
|
||||||
this.clipboardWriteCallback(text, clearMs);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.warn('Copy to clipboard failed.', e);
|
|
||||||
} finally {
|
|
||||||
doc.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async readFromClipboard(options?: any): Promise<string> {
|
|
||||||
let win = window;
|
|
||||||
let doc = window.document;
|
|
||||||
if (options && (options.window || options.win)) {
|
|
||||||
win = options.window || options.win;
|
|
||||||
doc = win.document;
|
|
||||||
} else if (options && options.doc) {
|
|
||||||
doc = options.doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isSafari()) {
|
|
||||||
return await SafariApp.sendMessageToApp('readFromClipboard');
|
|
||||||
} else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.readText) {
|
|
||||||
return await (win as any).navigator.clipboard.readText();
|
|
||||||
} else if (doc.queryCommandSupported && doc.queryCommandSupported('paste')) {
|
|
||||||
const textarea = doc.createElement('textarea');
|
|
||||||
// Prevent scrolling to bottom of page in MS Edge.
|
|
||||||
textarea.style.position = 'fixed';
|
|
||||||
doc.body.appendChild(textarea);
|
|
||||||
textarea.focus();
|
|
||||||
try {
|
|
||||||
// Security exception may be thrown by some browsers.
|
|
||||||
if (doc.execCommand('paste')) {
|
|
||||||
return textarea.value;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.warn('Read from clipboard failed.', e);
|
|
||||||
} finally {
|
|
||||||
doc.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveDialogPromise(dialogId: number, confirmed: boolean) {
|
|
||||||
if (this.showDialogResolves.has(dialogId)) {
|
|
||||||
const resolveObj = this.showDialogResolves.get(dialogId);
|
|
||||||
resolveObj.resolve(confirmed);
|
|
||||||
this.showDialogResolves.delete(dialogId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up old promises
|
|
||||||
this.showDialogResolves.forEach((val, key) => {
|
|
||||||
const age = new Date().getTime() - val.date.getTime();
|
|
||||||
if (age > DialogPromiseExpiration) {
|
|
||||||
this.showDialogResolves.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async resolvePasswordDialogPromise(dialogId: number, canceled: boolean, password: string): Promise<boolean> {
|
|
||||||
let result = false;
|
|
||||||
if (this.passwordDialogResolves.has(dialogId)) {
|
|
||||||
const resolveObj = this.passwordDialogResolves.get(dialogId);
|
|
||||||
if (await resolveObj.tryResolve(canceled, password)) {
|
|
||||||
this.passwordDialogResolves.delete(dialogId);
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up old promises
|
|
||||||
this.passwordDialogResolves.forEach((val, key) => {
|
|
||||||
const age = new Date().getTime() - val.date.getTime();
|
|
||||||
if (age > DialogPromiseExpiration) {
|
|
||||||
this.passwordDialogResolves.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async supportsBiometric() {
|
|
||||||
const platformInfo = await BrowserApi.getPlatformInfo();
|
|
||||||
if (platformInfo.os === 'android') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isFirefox()) {
|
|
||||||
return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticateBiometric() {
|
|
||||||
return this.biometricCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
sidebarViewName(): string {
|
|
||||||
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
|
|
||||||
return 'sidebar';
|
|
||||||
} else if (this.isOpera() && (typeof opr !== 'undefined') && opr.sidebarAction) {
|
|
||||||
return 'sidebar_panel';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsSecureStorage(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultSystemTheme(): Promise<ThemeType.Light | ThemeType.Dark> {
|
|
||||||
return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDefaultSystemThemeChange(callback: ((theme: ThemeType.Light | ThemeType.Dark) => unknown)) {
|
|
||||||
this.prefersColorSchemeDark.addEventListener('change', ({ matches }) => {
|
|
||||||
callback(matches ? ThemeType.Dark : ThemeType.Light);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEffectiveTheme() {
|
|
||||||
const theme = await this.stateService.getTheme() as ThemeType;
|
|
||||||
if (theme == null || theme === ThemeType.System) {
|
|
||||||
return this.getDefaultSystemTheme();
|
|
||||||
} else {
|
|
||||||
return theme;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
|
|
||||||
export default class BrowserStorageService implements StorageService {
|
export default class BrowserStorageService implements StorageService {
|
||||||
private chromeStorageApi: any;
|
private chromeStorageApi: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.chromeStorageApi = chrome.storage.local;
|
this.chromeStorageApi = chrome.storage.local;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get<T>(key: string): Promise<T> {
|
async get<T>(key: string): Promise<T> {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
this.chromeStorageApi.get(key, (obj: any) => {
|
this.chromeStorageApi.get(key, (obj: any) => {
|
||||||
if (obj != null && obj[key] != null) {
|
if (obj != null && obj[key] != null) {
|
||||||
resolve(obj[key] as T);
|
resolve(obj[key] as T);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async has(key: string): Promise<boolean> {
|
|
||||||
return await this.get(key) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(key: string, obj: any): Promise<any> {
|
|
||||||
if (obj == null) {
|
|
||||||
// Fix safari not liking null in set
|
|
||||||
return this.remove(key);
|
|
||||||
}
|
}
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (obj instanceof Set) {
|
async has(key: string): Promise<boolean> {
|
||||||
obj = Array.from(obj);
|
return (await this.get(key)) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyedObj = { [key]: obj };
|
async save(key: string, obj: any): Promise<any> {
|
||||||
return new Promise<void>(resolve => {
|
if (obj == null) {
|
||||||
this.chromeStorageApi.set(keyedObj, () => {
|
// Fix safari not liking null in set
|
||||||
resolve();
|
return this.remove(key);
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(key: string): Promise<any> {
|
if (obj instanceof Set) {
|
||||||
return new Promise<void>(resolve => {
|
obj = Array.from(obj);
|
||||||
this.chromeStorageApi.remove(key, () => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyedObj = { [key]: obj };
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
this.chromeStorageApi.set(keyedObj, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(key: string): Promise<any> {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
this.chromeStorageApi.remove(key, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,75 @@
|
||||||
import { StorageOptions } from 'jslib-common/models/domain/storageOptions';
|
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
|
||||||
import { StateService as BaseStateService } from 'jslib-common/services/state.service';
|
import { StateService as BaseStateService } from "jslib-common/services/state.service";
|
||||||
|
|
||||||
import { Account } from 'src/models/account';
|
|
||||||
import { BrowserComponentState } from '../models/browserComponentState';
|
|
||||||
import { BrowserGroupingsComponentState } from '../models/browserGroupingsComponentState';
|
|
||||||
import { BrowserSendComponentState } from '../models/browserSendComponentState';
|
|
||||||
import { StateService as StateServiceAbstraction } from './abstractions/state.service';
|
|
||||||
|
|
||||||
|
import { Account } from "src/models/account";
|
||||||
|
import { BrowserComponentState } from "../models/browserComponentState";
|
||||||
|
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
||||||
|
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
||||||
|
import { StateService as StateServiceAbstraction } from "./abstractions/state.service";
|
||||||
|
|
||||||
export class StateService extends BaseStateService<Account> implements StateServiceAbstraction {
|
export class StateService extends BaseStateService<Account> implements StateServiceAbstraction {
|
||||||
|
async getBrowserGroupingComponentState(
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<BrowserGroupingsComponentState> {
|
||||||
|
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||||
|
?.groupings;
|
||||||
|
}
|
||||||
|
|
||||||
async getBrowserGroupingComponentState(options?: StorageOptions): Promise<BrowserGroupingsComponentState> {
|
async setBrowserGroupingComponentState(
|
||||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.groupings;
|
value: BrowserGroupingsComponentState,
|
||||||
}
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||||
|
);
|
||||||
|
account.groupings = value;
|
||||||
|
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||||
|
}
|
||||||
|
|
||||||
async setBrowserGroupingComponentState(value: BrowserGroupingsComponentState, options?: StorageOptions): Promise<void> {
|
async getBrowserCipherComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
||||||
const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions));
|
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||||
account.groupings = value;
|
?.ciphers;
|
||||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async getBrowserCipherComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
async setBrowserCipherComponentState(
|
||||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.ciphers;
|
value: BrowserComponentState,
|
||||||
}
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||||
|
);
|
||||||
|
account.ciphers = value;
|
||||||
|
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||||
|
}
|
||||||
|
|
||||||
async setBrowserCipherComponentState(value: BrowserComponentState, options?: StorageOptions): Promise<void> {
|
async getBrowserSendComponentState(options?: StorageOptions): Promise<BrowserSendComponentState> {
|
||||||
const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions));
|
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||||
account.ciphers = value;
|
?.send;
|
||||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async getBrowserSendComponentState(options?: StorageOptions): Promise<BrowserSendComponentState> {
|
async setBrowserSendComponentState(
|
||||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.send;
|
value: BrowserSendComponentState,
|
||||||
}
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
async setBrowserSendComponentState(value: BrowserSendComponentState, options?: StorageOptions): Promise<void> {
|
const account = await this.getAccount(
|
||||||
const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions));
|
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||||
account.send = value;
|
);
|
||||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
account.send = value;
|
||||||
}
|
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||||
async getBrowserSendTypeComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
}
|
||||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.sendType;
|
async getBrowserSendTypeComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
||||||
}
|
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||||
|
?.sendType;
|
||||||
async setBrowserSendTypeComponentState(value: BrowserComponentState, options?: StorageOptions): Promise<void> {
|
}
|
||||||
const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions));
|
|
||||||
account.sendType = value;
|
|
||||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async setBrowserSendTypeComponentState(
|
||||||
|
value: BrowserComponentState,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||||
|
);
|
||||||
|
account.sendType = value;
|
||||||
|
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue