1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

Apply Prettier (#2238)

This commit is contained in:
Oscar Hinton
2021-12-21 15:43:35 +01:00
committed by GitHub
parent cebee8aa81
commit 8fe821b9a3
174 changed files with 17599 additions and 14766 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -1,4 +1,5 @@
## Type of change ## Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature development - [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@@ -6,27 +7,26 @@
- [ ] Other - [ ] Other
## Objective ## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes ## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories--> <!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why - **file.ext:** Description of what was changed and why
## Screenshots ## Screenshots
<!--Required for any UI changes. Delete if not applicable--> <!--Required for any UI changes. Delete if not applicable-->
## Testing requirements ## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing--> <!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -104,8 +104,8 @@ jobs:
npm run dist npm run dist
npm run test npm run test
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- name: Gulp - name: Gulp
run: gulp ci run: gulp ci

View File

@@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved: Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one - **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code - **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Report a bug or submit a bugfix:** Use Github issues and pull requests - **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (i10n) section below
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (i10n) section below
## Contributor Agreement ## Contributor Agreement
@@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/brows
## Pull Request Guidelines ## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request - use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch - commit any pull requests against the `master` branch
* include a link to your Community Forums post - include a link to your Community Forums post
# Localization (l10n) # Localization (l10n)

View File

@@ -1,39 +1,37 @@
const gulp = require('gulp'), const gulp = require("gulp"),
gulpif = require('gulp-if'), gulpif = require("gulp-if"),
filter = require('gulp-filter'), filter = require("gulp-filter"),
replace = require('gulp-replace'), replace = require("gulp-replace"),
jeditor = require("gulp-json-editor"), jeditor = require("gulp-json-editor"),
child = require('child_process'), child = require("child_process"),
zip = require('gulp-zip'), zip = require("gulp-zip"),
manifest = require('./src/manifest.json'), manifest = require("./src/manifest.json"),
del = require('del'), del = require("del"),
fs = require('fs'); fs = require("fs");
const paths = { const paths = {
build: './build/', build: "./build/",
dist: './dist/', dist: "./dist/",
coverage: './coverage/', coverage: "./coverage/",
node_modules: './node_modules/', node_modules: "./node_modules/",
popupDir: './src/popup/', popupDir: "./src/popup/",
cssDir: './src/popup/css/', cssDir: "./src/popup/css/",
safari: './src/safari/' safari: "./src/safari/",
}; };
const filters = { const filters = {
fonts: [ fonts: [
'!build/popup/fonts/*', "!build/popup/fonts/*",
'build/popup/fonts/Open_Sans*.woff', "build/popup/fonts/Open_Sans*.woff",
'build/popup/fonts/fontawesome*.woff2', "build/popup/fonts/fontawesome*.woff2",
'build/popup/fonts/fontawesome*.woff' "build/popup/fonts/fontawesome*.woff",
],
safari: [
'!build/safari/**/*'
], ],
safari: ["!build/safari/**/*"],
}; };
function buildString() { function buildString() {
var build = ''; var build = "";
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== '') { if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
build = `-${process.env.BUILD_NUMBER}`; build = `-${process.env.BUILD_NUMBER}`;
} }
return build; return build;
@@ -44,16 +42,17 @@ function distFileName(browserName, ext) {
} }
function dist(browserName, manifest) { function dist(browserName, manifest) {
return gulp.src(paths.build + '**/*') return gulp
.pipe(filter(['**'].concat(filters.fonts).concat(filters.safari))) .src(paths.build + "**/*")
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_' + browserName))) .pipe(filter(["**"].concat(filters.fonts).concat(filters.safari)))
.pipe(gulpif('manifest.json', jeditor(manifest))) .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName)))
.pipe(zip(distFileName(browserName, 'zip'))) .pipe(gulpif("manifest.json", jeditor(manifest)))
.pipe(zip(distFileName(browserName, "zip")))
.pipe(gulp.dest(paths.dist)); .pipe(gulp.dest(paths.dist));
} }
function distFirefox() { function distFirefox() {
return dist('firefox', (manifest) => { return dist("firefox", (manifest) => {
delete manifest.content_security_policy; delete manifest.content_security_policy;
removeShortcuts(manifest); removeShortcuts(manifest);
return manifest; return manifest;
@@ -61,7 +60,7 @@ function distFirefox() {
} }
function distOpera() { function distOpera() {
return dist('opera', (manifest) => { return dist("opera", (manifest) => {
delete manifest.applications; delete manifest.applications;
delete manifest.content_security_policy; delete manifest.content_security_policy;
removeShortcuts(manifest); removeShortcuts(manifest);
@@ -70,7 +69,7 @@ function distOpera() {
} }
function distChrome() { function distChrome() {
return dist('chrome', (manifest) => { return dist("chrome", (manifest) => {
delete manifest.applications; delete manifest.applications;
delete manifest.content_security_policy; delete manifest.content_security_policy;
delete manifest.sidebar_action; delete manifest.sidebar_action;
@@ -80,7 +79,7 @@ function distChrome() {
} }
function distEdge() { function distEdge() {
return dist('edge', (manifest) => { return dist("edge", (manifest) => {
delete manifest.applications; delete manifest.applications;
delete manifest.content_security_policy; delete manifest.content_security_policy;
delete manifest.sidebar_action; delete manifest.sidebar_action;
@@ -92,132 +91,152 @@ function distEdge() {
function removeShortcuts(manifest) { function removeShortcuts(manifest) {
if (manifest.content_scripts && manifest.content_scripts.length > 1) { if (manifest.content_scripts && manifest.content_scripts.length > 1) {
const shortcutsScript = manifest.content_scripts[1]; const shortcutsScript = manifest.content_scripts[1];
if (shortcutsScript.js.indexOf('content/shortcuts.js') > -1) { if (shortcutsScript.js.indexOf("content/shortcuts.js") > -1) {
manifest.content_scripts.splice(1, 1); manifest.content_scripts.splice(1, 1);
} }
} }
} }
function distSafariMas(cb) { function distSafariMas(cb) {
return distSafariApp(cb, 'mas'); return distSafariApp(cb, "mas");
} }
function distSafariMasDev(cb) { function distSafariMasDev(cb) {
return distSafariApp(cb, 'masdev'); return distSafariApp(cb, "masdev");
} }
function distSafariDmg(cb) { function distSafariDmg(cb) {
return distSafariApp(cb, 'dmg'); return distSafariApp(cb, "dmg");
} }
function distSafariApp(cb, subBuildPath) { function distSafariApp(cb, subBuildPath) {
const buildPath = paths.dist + 'Safari/' + subBuildPath + '/'; const buildPath = paths.dist + "Safari/" + subBuildPath + "/";
const builtAppexPath = buildPath + 'build/Release/safari.appex'; const builtAppexPath = buildPath + "build/Release/safari.appex";
const builtAppexFrameworkPath = buildPath + 'build/Release/safari.appex/Contents/Frameworks/'; const builtAppexFrameworkPath = buildPath + "build/Release/safari.appex/Contents/Frameworks/";
const entitlementsPath = paths.safari + 'safari/safari.entitlements'; const entitlementsPath = paths.safari + "safari/safari.entitlements";
var args = [ var args = [
'--verbose', "--verbose",
'--force', "--force",
'-o', "-o",
'runtime', "runtime",
'--sign', "--sign",
'Developer ID Application: 8bit Solutions LLC', "Developer ID Application: 8bit Solutions LLC",
'--entitlements', "--entitlements",
entitlementsPath entitlementsPath,
]; ];
if (subBuildPath !== 'dmg') { if (subBuildPath !== "dmg") {
args = [ args = [
'--verbose', "--verbose",
'--force', "--force",
'--sign', "--sign",
subBuildPath === 'mas' ? '3rd Party Mac Developer Application: 8bit Solutions LLC' : subBuildPath === "mas"
'6B287DD81FF922D86FD836128B0F62F358B38726', ? "3rd Party Mac Developer Application: 8bit Solutions LLC"
'--entitlements', : "6B287DD81FF922D86FD836128B0F62F358B38726",
entitlementsPath "--entitlements",
entitlementsPath,
]; ];
} }
return del([buildPath + '**/*']) return del([buildPath + "**/*"])
.then(() => safariCopyAssets(paths.safari + '**/*', buildPath)) .then(() => safariCopyAssets(paths.safari + "**/*", buildPath))
.then(() => safariCopyBuild(paths.build + '**/*', buildPath + 'safari/app')) .then(() => safariCopyBuild(paths.build + "**/*", buildPath + "safari/app"))
.then(() => { .then(() => {
const proc = child.spawn('xcodebuild', [ const proc = child.spawn("xcodebuild", [
'-project', "-project",
buildPath + 'desktop.xcodeproj', buildPath + "desktop.xcodeproj",
'-alltargets', "-alltargets",
'-configuration', "-configuration",
'Release']); "Release",
]);
stdOutProc(proc); stdOutProc(proc);
return new Promise((resolve) => proc.on('close', resolve)); return new Promise((resolve) => proc.on("close", resolve));
}).then(() => { })
const libs = fs.readdirSync(builtAppexFrameworkPath).filter((p) => p.endsWith('.dylib')) .then(() => {
const libs = fs
.readdirSync(builtAppexFrameworkPath)
.filter((p) => p.endsWith(".dylib"))
.map((p) => builtAppexFrameworkPath + p); .map((p) => builtAppexFrameworkPath + p);
const libPromises = []; const libPromises = [];
libs.forEach((i) => { libs.forEach((i) => {
const proc = child.spawn('codesign', args.concat([i])); const proc = child.spawn("codesign", args.concat([i]));
stdOutProc(proc); stdOutProc(proc);
libPromises.push(new Promise((resolve) => proc.on('close', resolve))); libPromises.push(new Promise((resolve) => proc.on("close", resolve)));
}); });
return Promise.all(libPromises); return Promise.all(libPromises);
}).then(() => { })
const proc = child.spawn('codesign', args.concat([builtAppexPath])); .then(() => {
const proc = child.spawn("codesign", args.concat([builtAppexPath]));
stdOutProc(proc); stdOutProc(proc);
return new Promise((resolve) => proc.on('close', resolve)); return new Promise((resolve) => proc.on("close", resolve));
}).then(() => { })
.then(
() => {
return cb; return cb;
}, () => { },
() => {
return cb; return cb;
}); }
);
} }
function safariCopyAssets(source, dest) { function safariCopyAssets(source, dest) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gulp.src(source) gulp
.on('error', reject) .src(source)
.pipe(gulpif('safari/Info.plist', replace('0.0.1', manifest.version))) .on("error", reject)
.pipe(gulpif('safari/Info.plist', replace('0.0.2', process.env.BUILD_NUMBER || manifest.version))) .pipe(gulpif("safari/Info.plist", replace("0.0.1", manifest.version)))
.pipe(gulpif('desktop.xcodeproj/project.pbxproj', replace('../../../build', '../safari/app'))) .pipe(
gulpif("safari/Info.plist", replace("0.0.2", process.env.BUILD_NUMBER || manifest.version))
)
.pipe(gulpif("desktop.xcodeproj/project.pbxproj", replace("../../../build", "../safari/app")))
.pipe(gulp.dest(dest)) .pipe(gulp.dest(dest))
.on('end', resolve); .on("end", resolve);
}); });
} }
function safariCopyBuild(source, dest) { function safariCopyBuild(source, dest) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gulp.src(source) gulp
.on('error', reject) .src(source)
.pipe(filter(['**'].concat(filters.fonts))) .on("error", reject)
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_safari'))) .pipe(filter(["**"].concat(filters.fonts)))
.pipe(gulpif('manifest.json', jeditor((manifest) => { .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari")))
.pipe(
gulpif(
"manifest.json",
jeditor((manifest) => {
delete manifest.optional_permissions; delete manifest.optional_permissions;
manifest.permissions.push("nativeMessaging"); manifest.permissions.push("nativeMessaging");
return manifest; return manifest;
}))) })
)
)
.pipe(gulp.dest(dest)) .pipe(gulp.dest(dest))
.on('end', resolve); .on("end", resolve);
}); });
} }
function stdOutProc(proc) { function stdOutProc(proc) {
proc.stdout.on('data', (data) => console.log(data.toString())); proc.stdout.on("data", (data) => console.log(data.toString()));
proc.stderr.on('data', (data) => console.error(data.toString())); proc.stderr.on("data", (data) => console.error(data.toString()));
} }
function ciCoverage(cb) { function ciCoverage(cb) {
return gulp.src(paths.coverage + '**/*') return gulp
.pipe(filter(['**', '!coverage/coverage*.zip'])) .src(paths.coverage + "**/*")
.pipe(filter(["**", "!coverage/coverage*.zip"]))
.pipe(zip(`coverage${buildString()}.zip`)) .pipe(zip(`coverage${buildString()}.zip`))
.pipe(gulp.dest(paths.coverage)); .pipe(gulp.dest(paths.coverage));
} }
exports['dist:firefox'] = distFirefox; exports["dist:firefox"] = distFirefox;
exports['dist:chrome'] = distChrome; exports["dist:chrome"] = distChrome;
exports['dist:opera'] = distOpera; exports["dist:opera"] = distOpera;
exports['dist:edge'] = distEdge; exports["dist:edge"] = distEdge;
exports['dist:safari'] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg); exports["dist:safari"] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg);
exports['dist:safari:mas'] = distSafariMas; exports["dist:safari:mas"] = distSafariMas;
exports['dist:safari:masdev'] = distSafariMasDev; exports["dist:safari:masdev"] = distSafariMasDev;
exports['dist:safari:dmg'] = distSafariDmg; exports["dist:safari:dmg"] = distSafariDmg;
exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge); exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge);
exports['ci:coverage'] = ciCoverage; exports["ci:coverage"] = ciCoverage;
exports.ci = ciCoverage; exports.ci = ciCoverage;

View File

@@ -1,32 +1,29 @@
const path = require('path'); const path = require("path");
module.exports = function(config) { module.exports = function (config) {
config.set({ config.set({
// base path that will be used to resolve all patterns (eg. files, exclude) // base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '', basePath: "",
// frameworks to use // frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'webpack'], frameworks: ["jasmine", "webpack"],
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
files: [ files: [{ pattern: "src/**/*.spec.ts", watch: false }],
{ pattern: 'src/**/*.spec.ts', watch: false },
],
exclude: [ exclude: [],
],
// preprocess matching files before serving them to the browser // preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: { preprocessors: {
'src/**/*.ts': 'webpack' "src/**/*.ts": "webpack",
}, },
// test results reporter to use // test results reporter to use
// possible values: 'dots', 'progress' // possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter // available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'kjhtml'], reporters: ["progress", "kjhtml"],
// web server port // web server port
port: 9876, port: 9876,
@@ -40,37 +37,35 @@ module.exports = function(config) {
// start these browsers // start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'], browsers: ["Chrome"],
// Concurrency level // Concurrency level
// how many browser should be started simultaneous // how many browser should be started simultaneous
concurrency: Infinity, concurrency: Infinity,
client:{ client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false, // leave Jasmine Spec Runner output visible in browser
}, },
webpack: { webpack: {
mode: 'production', mode: "production",
resolve: { resolve: {
extensions: ['.js', '.ts', '.tsx'], extensions: [".js", ".ts", ".tsx"],
alias: { alias: {
"jslib-common": path.join(__dirname, 'jslib/common/src'), "jslib-common": path.join(__dirname, "jslib/common/src"),
"jslib-angular": path.join(__dirname, 'jslib/angular/src'), "jslib-angular": path.join(__dirname, "jslib/angular/src"),
}, },
}, },
module: { module: {
rules: [ rules: [{ test: /\.tsx?$/, loader: "ts-loader" }],
{test: /\.tsx?$/, loader: 'ts-loader'}
]
}, },
stats: { stats: {
colors: true, colors: true,
modules: true, modules: true,
reasons: true, reasons: true,
errorDetails: true errorDetails: true,
}, },
devtool: 'inline-source-map', devtool: "inline-source-map",
}, },
}) });
} };

View File

@@ -1,7 +1,6 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
</head> </head>
<body> <body></body>
</body>
</html> </html>

View File

@@ -1,6 +1,6 @@
import MainBackground from './background/main.background'; import MainBackground from "./background/main.background";
const bitwardenMain = (window as any).bitwardenMain = new MainBackground(); const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
bitwardenMain.bootstrap().then(() => { bitwardenMain.bootstrap().then(() => {
// Finished bootstrapping // Finished bootstrapping
}); });

View File

@@ -1,32 +1,42 @@
import { BrowserApi } from '../browser/browserApi'; import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background'; import MainBackground from "./main.background";
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 { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class CommandsBackground { export default class CommandsBackground {
private isSafari: boolean; private isSafari: boolean;
private isVivaldi: boolean; private isVivaldi: boolean;
constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService, constructor(
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService) { private main: MainBackground,
private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService
) {
this.isSafari = this.platformUtilsService.isSafari(); this.isSafari = this.platformUtilsService.isSafari();
this.isVivaldi = this.platformUtilsService.isVivaldi(); this.isVivaldi = this.platformUtilsService.isVivaldi();
} }
async init() { async init() {
BrowserApi.messageListener('commands.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => { BrowserApi.messageListener(
if (msg.command === 'unlockCompleted' && msg.data.target === 'commands.background') { "commands.background",
await this.processCommand(msg.data.commandToRetry.msg.command, msg.data.commandToRetry.sender); async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === "unlockCompleted" && msg.data.target === "commands.background") {
await this.processCommand(
msg.data.commandToRetry.msg.command,
msg.data.commandToRetry.sender
);
} }
if (this.isVivaldi && msg.command === 'keyboardShortcutTriggered' && msg.shortcut) { if (this.isVivaldi && msg.command === "keyboardShortcutTriggered" && msg.shortcut) {
await this.processCommand(msg.shortcut, sender); await this.processCommand(msg.shortcut, sender);
} }
}); }
);
if (!this.isVivaldi && chrome && chrome.commands) { if (!this.isVivaldi && chrome && chrome.commands) {
chrome.commands.onCommand.addListener(async (command: string) => { chrome.commands.onCommand.addListener(async (command: string) => {
@@ -37,16 +47,16 @@ export default class CommandsBackground {
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) { private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
switch (command) { switch (command) {
case 'generate_password': case "generate_password":
await this.generatePasswordToClipboard(); await this.generatePasswordToClipboard();
break; break;
case 'autofill_login': case "autofill_login":
await this.autoFillLogin(sender ? sender.tab : null); await this.autoFillLogin(sender ? sender.tab : null);
break; break;
case 'open_popup': case "open_popup":
await this.openPopup(); await this.openPopup();
break; break;
case 'lock_vault': case "lock_vault":
await this.vaultTimeoutService.lock(true); await this.vaultTimeoutService.lock(true);
break; break;
default: default:
@@ -73,18 +83,22 @@ export default class CommandsBackground {
if (await this.vaultTimeoutService.isLocked()) { if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = { const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: { commandToRetry: {
msg: { command: 'autofill_login' }, msg: { command: "autofill_login" },
sender: { tab: tab }, sender: { tab: tab },
}, },
target: 'commands.background', target: "commands.background",
}; };
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage); await BrowserApi.tabSendMessageData(
tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
BrowserApi.tabSendMessageData(tab, 'promptForLogin'); BrowserApi.tabSendMessageData(tab, "promptForLogin");
return; return;
} }
await this.main.collectPageDetailsForContentScript(tab, 'autofill_cmd'); await this.main.collectPageDetailsForContentScript(tab, "autofill_cmd");
} }
private async openPopup() { private async openPopup() {

View File

@@ -1,27 +1,32 @@
import { BrowserApi } from '../browser/browserApi'; import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background'; import MainBackground from "./main.background";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.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 { TotpService } from 'jslib-common/abstractions/totp.service'; import { TotpService } from "jslib-common/abstractions/totp.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
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 LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class ContextMenusBackground { export default class ContextMenusBackground {
private readonly noopCommandSuffix = 'noop'; private readonly noopCommandSuffix = "noop";
private contextMenus: any; private contextMenus: any;
constructor(private main: MainBackground, private cipherService: CipherService, constructor(
private main: MainBackground,
private cipherService: CipherService,
private passwordGenerationService: PasswordGenerationService, private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService, private platformUtilsService: PlatformUtilsService,
private eventService: EventService, private totpService: TotpService) { private vaultTimeoutService: VaultTimeoutService,
private eventService: EventService,
private totpService: TotpService
) {
this.contextMenus = chrome.contextMenus; this.contextMenus = chrome.contextMenus;
} }
@@ -30,24 +35,34 @@ export default class ContextMenusBackground {
return; return;
} }
this.contextMenus.onClicked.addListener(async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => { this.contextMenus.onClicked.addListener(
if (info.menuItemId === 'generate-password') { async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
if (info.menuItemId === "generate-password") {
await this.generatePasswordToClipboard(); await this.generatePasswordToClipboard();
} else if (info.menuItemId === 'copy-identifier') { } else if (info.menuItemId === "copy-identifier") {
await this.getClickedElement(tab, info.frameId); await this.getClickedElement(tab, info.frameId);
} else if (info.parentMenuItemId === 'autofill' || } else if (
info.parentMenuItemId === 'copy-username' || info.parentMenuItemId === "autofill" ||
info.parentMenuItemId === 'copy-password' || info.parentMenuItemId === "copy-username" ||
info.parentMenuItemId === 'copy-totp') { info.parentMenuItemId === "copy-password" ||
info.parentMenuItemId === "copy-totp"
) {
await this.cipherAction(tab, info); await this.cipherAction(tab, info);
} }
});
BrowserApi.messageListener('contextmenus.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === 'unlockCompleted' && msg.data.target === 'contextmenus.background') {
await this.cipherAction(msg.data.commandToRetry.sender.tab, msg.data.commandToRetry.msg.data);
} }
}); );
BrowserApi.messageListener(
"contextmenus.background",
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") {
await this.cipherAction(
msg.data.commandToRetry.sender.tab,
msg.data.commandToRetry.msg.data
);
}
}
);
} }
private async generatePasswordToClipboard() { private async generatePasswordToClipboard() {
@@ -62,11 +77,11 @@ export default class ContextMenusBackground {
return; return;
} }
BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId }); BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId });
} }
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
const id = info.menuItemId.split('_')[1]; const id = info.menuItemId.split("_")[1];
if (await this.vaultTimeoutService.isLocked()) { if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = { const retryMessage: LockedVaultPendingNotificationsItem = {
@@ -74,35 +89,39 @@ export default class ContextMenusBackground {
msg: { command: this.noopCommandSuffix, data: info }, msg: { command: this.noopCommandSuffix, data: info },
sender: { tab: tab }, sender: { tab: tab },
}, },
target: 'contextmenus.background', target: "contextmenus.background",
}; };
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage); await BrowserApi.tabSendMessageData(
tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
BrowserApi.tabSendMessageData(tab, 'promptForLogin'); BrowserApi.tabSendMessageData(tab, "promptForLogin");
return; return;
} }
let cipher: CipherView; let cipher: CipherView;
if (id === this.noopCommandSuffix) { if (id === this.noopCommandSuffix) {
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url); const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
cipher = ciphers.find(c => c.reprompt === CipherRepromptType.None); cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None);
} else { } else {
const ciphers = await this.cipherService.getAllDecrypted(); const ciphers = await this.cipherService.getAllDecrypted();
cipher = ciphers.find(c => c.id === id); cipher = ciphers.find((c) => c.id === id);
} }
if (cipher == null) { if (cipher == null) {
return; return;
} }
if (info.parentMenuItemId === 'autofill') { if (info.parentMenuItemId === "autofill") {
await this.startAutofillPage(tab, cipher); await this.startAutofillPage(tab, cipher);
} else if (info.parentMenuItemId === 'copy-username') { } else if (info.parentMenuItemId === "copy-username") {
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window }); this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
} else if (info.parentMenuItemId === 'copy-password') { } else if (info.parentMenuItemId === "copy-password") {
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window }); this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
} else if (info.parentMenuItemId === 'copy-totp') { } else if (info.parentMenuItemId === "copy-totp") {
const totpValue = await this.totpService.getCode(cipher.login.totp); const totpValue = await this.totpService.getCode(cipher.login.totp);
this.platformUtilsService.copyToClipboard(totpValue, { window: window }); this.platformUtilsService.copyToClipboard(totpValue, { window: window });
} }
@@ -115,9 +134,9 @@ export default class ContextMenusBackground {
} }
BrowserApi.tabSendMessage(tab, { BrowserApi.tabSendMessage(tab, {
command: 'collectPageDetails', command: "collectPageDetails",
tab: tab, tab: tab,
sender: 'contextMenu', sender: "contextMenu",
}); });
} }
} }

View File

@@ -1,18 +1,21 @@
import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService } from "jslib-common/abstractions/notifications.service";
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.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 storageService: StorageService, constructor(
private notificationsService: NotificationsService) { private vaultTimeoutService: VaultTimeoutService,
private storageService: StorageService,
private notificationsService: NotificationsService
) {
this.idle = chrome.idle || (browser != null ? browser.idle : null); this.idle = chrome.idle || (browser != null ? browser.idle : null);
} }
@@ -22,7 +25,7 @@ export default class IdleBackground {
} }
const idleHandler = (newState: string) => { const idleHandler = (newState: string) => {
if (newState === 'active') { if (newState === "active") {
this.notificationsService.reconnectFromActivity(); this.notificationsService.reconnectFromActivity();
} else { } else {
this.notificationsService.disconnectFromInactivity(); this.notificationsService.disconnectFromInactivity();
@@ -37,11 +40,15 @@ export default class IdleBackground {
if (this.idle.onStateChanged) { if (this.idle.onStateChanged) {
this.idle.onStateChanged.addListener(async (newState: string) => { this.idle.onStateChanged.addListener(async (newState: string) => {
if (newState === 'locked') { // If the screen is locked or the screensaver activates if (newState === "locked") {
// If the screen is locked or the screensaver activates
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
if (timeout === -2) { // On System Lock vault timeout option if (timeout === -2) {
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey); // On System Lock vault timeout option
if (action === 'logOut') { const action = await this.storageService.get<string>(
ConstantsService.vaultTimeoutActionKey
);
if (action === "logOut") {
await this.vaultTimeoutService.logOut(); await this.vaultTimeoutService.logOut();
} else { } else {
await this.vaultTimeoutService.lock(true); await this.vaultTimeoutService.lock(true);

View File

@@ -1,95 +1,95 @@
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 { ApiService } from 'jslib-common/services/api.service'; import { ApiService } from "jslib-common/services/api.service";
import { AppIdService } from 'jslib-common/services/appId.service'; import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from 'jslib-common/services/audit.service'; import { AuditService } from "jslib-common/services/audit.service";
import { AuthService } from 'jslib-common/services/auth.service'; import { AuthService } from "jslib-common/services/auth.service";
import { CipherService } from 'jslib-common/services/cipher.service'; import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from 'jslib-common/services/collection.service'; import { CollectionService } from "jslib-common/services/collection.service";
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { EnvironmentService } from 'jslib-common/services/environment.service'; import { EnvironmentService } from "jslib-common/services/environment.service";
import { EventService } from 'jslib-common/services/event.service'; import { EventService } from "jslib-common/services/event.service";
import { ExportService } from 'jslib-common/services/export.service'; import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from 'jslib-common/services/fileUpload.service'; import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from 'jslib-common/services/folder.service'; import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NotificationsService } from 'jslib-common/services/notifications.service'; import { NotificationsService } from "jslib-common/services/notifications.service";
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from 'jslib-common/services/policy.service'; import { PolicyService } from "jslib-common/services/policy.service";
import { SearchService } from 'jslib-common/services/search.service'; import { SearchService } from "jslib-common/services/search.service";
import { SendService } from 'jslib-common/services/send.service'; import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from 'jslib-common/services/settings.service'; import { SettingsService } from "jslib-common/services/settings.service";
import { StateService } from 'jslib-common/services/state.service'; import { StateService } from "jslib-common/services/state.service";
import { SyncService } from 'jslib-common/services/sync.service'; import { SyncService } from "jslib-common/services/sync.service";
import { SystemService } from 'jslib-common/services/system.service'; import { SystemService } from "jslib-common/services/system.service";
import { TokenService } from 'jslib-common/services/token.service'; import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from 'jslib-common/services/totp.service'; import { TotpService } from "jslib-common/services/totp.service";
import { UserService } from 'jslib-common/services/user.service'; import { UserService } from "jslib-common/services/user.service";
import { UserVerificationService } from 'jslib-common/services/userVerification.service'; import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service'; import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service'; import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service'; import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service'; import { AuditService as AuditServiceAbstraction } 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 as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service'; import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service'; import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service'; import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service'; import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service'; import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService as KeyConnectorServiceAbstraction } 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 as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service'; import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service'; import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service";
import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service'; import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service'; import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service'; import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service'; import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service'; import { UserService as UserServiceAbstraction } from "jslib-common/abstractions/user.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service'; import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service'; import { AutofillService as AutofillServiceAbstraction } from "../services/abstractions/autofill.service";
import { BrowserApi } from '../browser/browserApi'; import { BrowserApi } from "../browser/browserApi";
import { SafariApp } from '../browser/safariApp'; import { SafariApp } from "../browser/safariApp";
import CommandsBackground from './commands.background'; import CommandsBackground from "./commands.background";
import ContextMenusBackground from './contextMenus.background'; import ContextMenusBackground from "./contextMenus.background";
import IdleBackground from './idle.background'; import IdleBackground from "./idle.background";
import { NativeMessagingBackground } from './nativeMessaging.background'; import { NativeMessagingBackground } from "./nativeMessaging.background";
import NotificationBackground from './notification.background'; import NotificationBackground from "./notification.background";
import RuntimeBackground from './runtime.background'; import RuntimeBackground from "./runtime.background";
import TabsBackground from './tabs.background'; import TabsBackground from "./tabs.background";
import WebRequestBackground from './webRequest.background'; import WebRequestBackground from "./webRequest.background";
import WindowsBackground from './windows.background'; import WindowsBackground from "./windows.background";
import { PopupUtilsService } from '../popup/services/popup-utils.service'; import { PopupUtilsService } from "../popup/services/popup-utils.service";
import AutofillService from '../services/autofill.service'; import AutofillService from "../services/autofill.service";
import { BrowserCryptoService } from '../services/browserCrypto.service'; import { BrowserCryptoService } from "../services/browserCrypto.service";
import BrowserMessagingService from '../services/browserMessaging.service'; import BrowserMessagingService from "../services/browserMessaging.service";
import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import BrowserStorageService from '../services/browserStorage.service'; import BrowserStorageService from "../services/browserStorage.service";
import I18nService from '../services/i18n.service'; import I18nService from "../services/i18n.service";
import VaultTimeoutService from '../services/vaultTimeout.service'; import VaultTimeoutService from "../services/vaultTimeout.service";
export default class MainBackground { export default class MainBackground {
messagingService: MessagingServiceAbstraction; messagingService: MessagingServiceAbstraction;
@@ -155,7 +155,9 @@ export default class MainBackground {
// Services // Services
this.messagingService = new BrowserMessagingService(); this.messagingService = new BrowserMessagingService();
this.storageService = new BrowserStorageService(); this.storageService = new BrowserStorageService();
this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService, this.storageService, this.platformUtilsService = new BrowserPlatformUtilsService(
this.messagingService,
this.storageService,
(clipboardValue, clearMs) => { (clipboardValue, clearMs) => {
if (this.systemService != null) { if (this.systemService != null) {
this.systemService.clearClipboard(clipboardValue, clearMs); this.systemService.clearClipboard(clipboardValue, clearMs);
@@ -166,45 +168,95 @@ export default class MainBackground {
const promise = this.nativeMessagingBackground.getResponse(); const promise = this.nativeMessagingBackground.getResponse();
try { try {
await this.nativeMessagingBackground.send({ command: 'biometricUnlock' }); await this.nativeMessagingBackground.send({ command: "biometricUnlock" });
} catch (e) { } catch (e) {
return Promise.reject(e); return Promise.reject(e);
} }
return promise.then(result => result.response === 'unlocked'); return promise.then((result) => result.response === "unlocked");
} }
}); }
);
this.secureStorageService = new BrowserStorageService(); this.secureStorageService = new BrowserStorageService();
this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService); this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService);
this.logService = new ConsoleLogService(false); this.logService = new ConsoleLogService(false);
this.cryptoService = new BrowserCryptoService(this.storageService, this.secureStorageService, this.cryptoService = new BrowserCryptoService(
this.cryptoFunctionService, this.platformUtilsService, this.logService); this.storageService,
this.secureStorageService,
this.cryptoFunctionService,
this.platformUtilsService,
this.logService
);
this.tokenService = new TokenService(this.storageService); this.tokenService = new TokenService(this.storageService);
this.appIdService = new AppIdService(this.storageService); this.appIdService = new AppIdService(this.storageService);
this.environmentService = new EnvironmentService(this.storageService); this.environmentService = new EnvironmentService(this.storageService);
this.apiService = new ApiService(this.tokenService, this.platformUtilsService, this.environmentService, this.apiService = new ApiService(
(expired: boolean) => this.logout(expired)); this.tokenService,
this.platformUtilsService,
this.environmentService,
(expired: boolean) => this.logout(expired)
);
this.userService = new UserService(this.tokenService, this.storageService); this.userService = new UserService(this.tokenService, this.storageService);
this.settingsService = new SettingsService(this.userService, this.storageService); this.settingsService = new SettingsService(this.userService, this.storageService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, this.cipherService = new CipherService(
this.apiService, this.fileUploadService, this.storageService, this.i18nService, () => this.searchService, this.cryptoService,
this.logService); this.userService,
this.folderService = new FolderService(this.cryptoService, this.userService, this.apiService, this.settingsService,
this.storageService, this.i18nService, this.cipherService); this.apiService,
this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.fileUploadService,
this.i18nService); this.storageService,
this.i18nService,
() => this.searchService,
this.logService
);
this.folderService = new FolderService(
this.cryptoService,
this.userService,
this.apiService,
this.storageService,
this.i18nService,
this.cipherService
);
this.collectionService = new CollectionService(
this.cryptoService,
this.userService,
this.storageService,
this.i18nService
);
this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService);
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService, this.sendService = new SendService(
this.storageService, this.i18nService, this.cryptoFunctionService); this.cryptoService,
this.userService,
this.apiService,
this.fileUploadService,
this.storageService,
this.i18nService,
this.cryptoFunctionService
);
this.stateService = new StateService(); this.stateService = new StateService();
this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); this.policyService = new PolicyService(this.userService, this.storageService, this.apiService);
this.keyConnectorService = new KeyConnectorService(this.storageService, this.userService, this.cryptoService, this.keyConnectorService = new KeyConnectorService(
this.apiService, this.tokenService, this.logService); this.storageService,
this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService, this.userService,
this.collectionService, this.cryptoService, this.platformUtilsService, this.storageService, this.cryptoService,
this.messagingService, this.searchService, this.userService, this.tokenService, this.policyService, this.apiService,
this.tokenService,
this.logService
);
this.vaultTimeoutService = new VaultTimeoutService(
this.cipherService,
this.folderService,
this.collectionService,
this.cryptoService,
this.platformUtilsService,
this.storageService,
this.messagingService,
this.searchService,
this.userService,
this.tokenService,
this.policyService,
this.keyConnectorService, this.keyConnectorService,
async () => { async () => {
if (this.notificationsService != null) { if (this.notificationsService != null) {
@@ -216,74 +268,183 @@ export default class MainBackground {
this.systemService.startProcessReload(); this.systemService.startProcessReload();
await this.systemService.clearPendingClipboard(); await this.systemService.clearPendingClipboard();
} }
}, async () => await this.logout(false)); },
this.syncService = new SyncService(this.userService, this.apiService, this.settingsService, async () => await this.logout(false)
this.folderService, this.cipherService, this.cryptoService, this.collectionService, );
this.storageService, this.messagingService, this.policyService, this.sendService, this.syncService = new SyncService(
this.logService, this.tokenService, this.keyConnectorService, this.userService,
async (expired: boolean) => await this.logout(expired)); this.apiService,
this.eventService = new EventService(this.storageService, this.apiService, this.userService, this.settingsService,
this.cipherService, this.logService); this.folderService,
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, this.cipherService,
this.policyService); this.cryptoService,
this.totpService = new TotpService(this.storageService, this.cryptoFunctionService, this.logService); this.collectionService,
this.autofillService = new AutofillService(this.cipherService, this.userService, this.totpService, this.storageService,
this.eventService, this.logService); this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.tokenService,
this.keyConnectorService,
async (expired: boolean) => await this.logout(expired)
);
this.eventService = new EventService(
this.storageService,
this.apiService,
this.userService,
this.cipherService,
this.logService
);
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.storageService,
this.policyService
);
this.totpService = new TotpService(
this.storageService,
this.cryptoFunctionService,
this.logService
);
this.autofillService = new AutofillService(
this.cipherService,
this.userService,
this.totpService,
this.eventService,
this.logService
);
this.containerService = new ContainerService(this.cryptoService); this.containerService = new ContainerService(this.cryptoService);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.exportService = new ExportService(this.folderService, this.cipherService, this.apiService, this.exportService = new ExportService(
this.cryptoService); this.folderService,
this.notificationsService = new NotificationsService(this.userService, this.syncService, this.appIdService, this.cipherService,
this.apiService, this.vaultTimeoutService, this.environmentService, () => this.logout(true), this.logService); this.apiService,
this.cryptoService
);
this.notificationsService = new NotificationsService(
this.userService,
this.syncService,
this.appIdService,
this.apiService,
this.vaultTimeoutService,
this.environmentService,
() => this.logout(true),
this.logService
);
this.popupUtilsService = new PopupUtilsService(this.platformUtilsService); this.popupUtilsService = new PopupUtilsService(this.platformUtilsService);
this.systemService = new SystemService(this.storageService, this.vaultTimeoutService, this.systemService = new SystemService(
this.messagingService, this.platformUtilsService, () => { this.storageService,
const forceWindowReload = this.platformUtilsService.isSafari() || this.vaultTimeoutService,
this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera(); this.messagingService,
this.platformUtilsService,
() => {
const forceWindowReload =
this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() ||
this.platformUtilsService.isOpera();
BrowserApi.reloadExtension(forceWindowReload ? window : null); BrowserApi.reloadExtension(forceWindowReload ? window : null);
return Promise.resolve(); return Promise.resolve();
}); }
this.userVerificationService = new UserVerificationService(this.cryptoService, this.i18nService, );
this.apiService); this.userVerificationService = new UserVerificationService(
this.cryptoService,
this.i18nService,
this.apiService
);
// Other fields // Other fields
this.isSafari = this.platformUtilsService.isSafari(); this.isSafari = this.platformUtilsService.isSafari();
this.sidebarAction = this.isSafari ? null : (typeof opr !== 'undefined') && opr.sidebarAction ? this.sidebarAction = this.isSafari
opr.sidebarAction : (window as any).chrome.sidebarAction; ? null
: typeof opr !== "undefined" && opr.sidebarAction
? opr.sidebarAction
: (window as any).chrome.sidebarAction;
// Background // Background
this.runtimeBackground = new RuntimeBackground(this, this.autofillService, this.runtimeBackground = new RuntimeBackground(
this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, this,
this.notificationsService, this.systemService, this.environmentService, this.messagingService, this.autofillService,
this.logService); this.platformUtilsService as BrowserPlatformUtilsService,
this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService, this.storageService,
this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService, this.i18nService,
this.platformUtilsService); this.notificationsService,
this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.systemService,
this.platformUtilsService, this.vaultTimeoutService); this.environmentService,
this.notificationBackground = new NotificationBackground(this, this.autofillService, this.cipherService, this.messagingService,
this.storageService, this.vaultTimeoutService, this.policyService, this.folderService, this.userService); this.logService
);
this.nativeMessagingBackground = new NativeMessagingBackground(
this.storageService,
this.cryptoService,
this.cryptoFunctionService,
this.vaultTimeoutService,
this.runtimeBackground,
this.i18nService,
this.userService,
this.messagingService,
this.appIdService,
this.platformUtilsService
);
this.commandsBackground = new CommandsBackground(
this,
this.passwordGenerationService,
this.platformUtilsService,
this.vaultTimeoutService
);
this.notificationBackground = new NotificationBackground(
this,
this.autofillService,
this.cipherService,
this.storageService,
this.vaultTimeoutService,
this.policyService,
this.folderService,
this.userService
);
this.tabsBackground = new TabsBackground(this, this.notificationBackground); this.tabsBackground = new TabsBackground(this, this.notificationBackground);
this.contextMenusBackground = new ContextMenusBackground(this, this.cipherService, this.passwordGenerationService, this.contextMenusBackground = new ContextMenusBackground(
this.platformUtilsService, this.vaultTimeoutService, this.eventService, this.totpService); this,
this.idleBackground = new IdleBackground(this.vaultTimeoutService, this.storageService, this.cipherService,
this.notificationsService); this.passwordGenerationService,
this.webRequestBackground = new WebRequestBackground(this.platformUtilsService, this.cipherService, this.platformUtilsService,
this.vaultTimeoutService); this.vaultTimeoutService,
this.eventService,
this.totpService
);
this.idleBackground = new IdleBackground(
this.vaultTimeoutService,
this.storageService,
this.notificationsService
);
this.webRequestBackground = new WebRequestBackground(
this.platformUtilsService,
this.cipherService,
this.vaultTimeoutService
);
this.windowsBackground = new WindowsBackground(this); this.windowsBackground = new WindowsBackground(this);
const that = this; const that = this;
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.authService = new AuthService(
this.tokenService, this.appIdService, this.i18nService, this.platformUtilsService, this.cryptoService,
new class extends MessagingServiceAbstraction { this.apiService,
this.userService,
this.tokenService,
this.appIdService,
this.i18nService,
this.platformUtilsService,
new (class extends MessagingServiceAbstraction {
// AuthService should send the messages to the background not popup. // AuthService should send the messages to the background not popup.
send = (subscriber: string, arg: any = {}) => { send = (subscriber: string, arg: any = {}) => {
const message = Object.assign({}, { command: subscriber }, arg); const message = Object.assign({}, { command: subscriber }, arg);
that.runtimeBackground.processMessage(message, that, null); that.runtimeBackground.processMessage(message, that, null);
} };
}(), this.vaultTimeoutService, this.logService, this.cryptoFunctionService, this.environmentService, })(),
this.keyConnectorService); this.vaultTimeoutService,
this.logService,
this.cryptoFunctionService,
this.environmentService,
this.keyConnectorService
);
} }
async bootstrap() { async bootstrap() {
@@ -303,7 +464,7 @@ export default class MainBackground {
await this.webRequestBackground.init(); await this.webRequestBackground.init();
await this.windowsBackground.init(); await this.windowsBackground.init();
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await this.environmentService.setUrlsFromStorage(); await this.environmentService.setUrlsFromStorage();
await this.setIcon(); await this.setIcon();
@@ -322,11 +483,11 @@ export default class MainBackground {
const isAuthenticated = await this.userService.isAuthenticated(); const isAuthenticated = await this.userService.isAuthenticated();
const locked = await this.vaultTimeoutService.isLocked(); const locked = await this.vaultTimeoutService.isLocked();
let suffix = ''; let suffix = "";
if (!isAuthenticated) { if (!isAuthenticated) {
suffix = '_gray'; suffix = "_gray";
} else if (locked) { } else if (locked) {
suffix = '_locked'; suffix = "_locked";
} }
await this.actionSetIcon(chrome.browserAction, suffix); await this.actionSetIcon(chrome.browserAction, suffix);
@@ -338,7 +499,9 @@ export default class MainBackground {
return; return;
} }
const menuDisabled = await this.storageService.get<boolean>(ConstantsService.disableContextMenuItemKey); const menuDisabled = await this.storageService.get<boolean>(
ConstantsService.disableContextMenuItemKey
);
if (!menuDisabled) { if (!menuDisabled) {
await this.buildContextMenu(); await this.buildContextMenu();
} else { } else {
@@ -378,7 +541,7 @@ export default class MainBackground {
]); ]);
this.searchService.clearIndex(); this.searchService.clearIndex();
this.messagingService.send('doneLoggingOut', { expired: expired }); this.messagingService.send("doneLoggingOut", { expired: expired });
await this.setIcon(); await this.setIcon();
await this.refreshBadgeAndMenu(); await this.refreshBadgeAndMenu();
@@ -398,11 +561,15 @@ export default class MainBackground {
options.frameId = frameId; options.frameId = frameId;
} }
BrowserApi.tabSendMessage(tab, { BrowserApi.tabSendMessage(
command: 'collectPageDetails', tab,
{
command: "collectPageDetails",
tab: tab, tab: tab,
sender: sender, sender: sender,
}, options); },
options
);
} }
async openPopup() { async openPopup() {
@@ -412,25 +579,32 @@ export default class MainBackground {
if (!this.isSafari) { if (!this.isSafari) {
return; return;
} }
await SafariApp.sendMessageToApp('showPopover', null, true); await SafariApp.sendMessageToApp("showPopover", null, true);
} }
async reseedStorage() { async reseedStorage() {
if (!this.platformUtilsService.isChrome() && !this.platformUtilsService.isVivaldi() && if (
!this.platformUtilsService.isOpera()) { !this.platformUtilsService.isChrome() &&
!this.platformUtilsService.isVivaldi() &&
!this.platformUtilsService.isOpera()
) {
return; return;
} }
const currentVaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const currentVaultTimeout = await this.storageService.get<number>(
ConstantsService.vaultTimeoutKey
);
if (currentVaultTimeout == null) { if (currentVaultTimeout == null) {
return; return;
} }
const getStorage = (): Promise<any> => new Promise(resolve => { const getStorage = (): Promise<any> =>
new Promise((resolve) => {
chrome.storage.local.get(null, (o: any) => resolve(o)); chrome.storage.local.get(null, (o: any) => resolve(o));
}); });
const clearStorage = (): Promise<void> => new Promise(resolve => { const clearStorage = (): Promise<void> =>
new Promise((resolve) => {
chrome.storage.local.clear(() => resolve()); chrome.storage.local.clear(() => resolve());
}); });
@@ -454,65 +628,65 @@ export default class MainBackground {
await this.contextMenusRemoveAll(); await this.contextMenusRemoveAll();
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'root', id: "root",
contexts: ['all'], contexts: ["all"],
title: 'Bitwarden', title: "Bitwarden",
}); });
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'autofill', id: "autofill",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('autoFill'), title: this.i18nService.t("autoFill"),
}); });
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-username', id: "copy-username",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('copyUsername'), title: this.i18nService.t("copyUsername"),
}); });
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-password', id: "copy-password",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('copyPassword'), title: this.i18nService.t("copyPassword"),
}); });
if (await this.userService.canAccessPremium()) { if (await this.userService.canAccessPremium()) {
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-totp', id: "copy-totp",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('copyVerificationCode'), title: this.i18nService.t("copyVerificationCode"),
}); });
} }
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'separator', type: "separator",
parentId: 'root', parentId: "root",
}); });
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'generate-password', id: "generate-password",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('generatePasswordCopied'), title: this.i18nService.t("generatePasswordCopied"),
}); });
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-identifier', id: "copy-identifier",
parentId: 'root', parentId: "root",
contexts: ['all'], contexts: ["all"],
title: this.i18nService.t('copyElementIdentifier'), title: this.i18nService.t("copyElementIdentifier"),
}); });
this.buildingContextMenu = false; this.buildingContextMenu = false;
@@ -539,24 +713,26 @@ export default class MainBackground {
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
if (contextMenuEnabled) { if (contextMenuEnabled) {
ciphers.forEach(cipher => { ciphers.forEach((cipher) => {
this.loadLoginContextMenuOptions(cipher); this.loadLoginContextMenuOptions(cipher);
}); });
} }
const disableBadgeCounter = await this.storageService.get<boolean>(ConstantsService.disableBadgeCounterKey); const disableBadgeCounter = await this.storageService.get<boolean>(
let theText = ''; ConstantsService.disableBadgeCounterKey
);
let theText = "";
if (!disableBadgeCounter) { if (!disableBadgeCounter) {
if (ciphers.length > 0 && ciphers.length <= 9) { if (ciphers.length > 0 && ciphers.length <= 9) {
theText = ciphers.length.toString(); theText = ciphers.length.toString();
} else if (ciphers.length > 0) { } else if (ciphers.length > 0) {
theText = '9+'; theText = "9+";
} }
} }
if (contextMenuEnabled && ciphers.length === 0) { if (contextMenuEnabled && ciphers.length === 0) {
await this.loadNoLoginsContextMenuOptions(this.i18nService.t('noMatchingLogins')); await this.loadNoLoginsContextMenuOptions(this.i18nService.t("noMatchingLogins"));
} }
this.sidebarActionSetBadgeText(theText, tabId); this.sidebarActionSetBadgeText(theText, tabId);
@@ -574,88 +750,100 @@ export default class MainBackground {
private async loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled: boolean) { private async loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled: boolean) {
if (contextMenuEnabled) { if (contextMenuEnabled) {
const authed = await this.userService.isAuthenticated(); const authed = await this.userService.isAuthenticated();
await this.loadNoLoginsContextMenuOptions(this.i18nService.t(authed ? 'vaultLocked' : 'vaultLoggedOut')); await this.loadNoLoginsContextMenuOptions(
this.i18nService.t(authed ? "vaultLocked" : "vaultLoggedOut")
);
} }
const tabs = await BrowserApi.getActiveTabs(); const tabs = await BrowserApi.getActiveTabs();
if (tabs != null) { if (tabs != null) {
tabs.forEach(tab => { tabs.forEach((tab) => {
if (tab.id != null) { if (tab.id != null) {
this.browserActionSetBadgeText('', tab.id); this.browserActionSetBadgeText("", tab.id);
this.sidebarActionSetBadgeText('', tab.id); this.sidebarActionSetBadgeText("", tab.id);
} }
}); });
} }
} }
private async loadLoginContextMenuOptions(cipher: any) { private async loadLoginContextMenuOptions(cipher: any) {
if (cipher == null || cipher.type !== CipherType.Login || cipher.reprompt !== CipherRepromptType.None) { if (
cipher == null ||
cipher.type !== CipherType.Login ||
cipher.reprompt !== CipherRepromptType.None
) {
return; return;
} }
let title = cipher.name; let title = cipher.name;
if (cipher.login.username && cipher.login.username !== '') { if (cipher.login.username && cipher.login.username !== "") {
title += (' (' + cipher.login.username + ')'); title += " (" + cipher.login.username + ")";
} }
await this.loadContextMenuOptions(title, cipher.id, cipher); await this.loadContextMenuOptions(title, cipher.id, cipher);
} }
private async loadNoLoginsContextMenuOptions(noLoginsMessage: string) { private async loadNoLoginsContextMenuOptions(noLoginsMessage: string) {
await this.loadContextMenuOptions(noLoginsMessage, 'noop', null); await this.loadContextMenuOptions(noLoginsMessage, "noop", null);
} }
private async loadContextMenuOptions(title: string, idSuffix: string, cipher: any) { private async loadContextMenuOptions(title: string, idSuffix: string, cipher: any) {
if (!chrome.contextMenus || this.menuOptionsLoaded.indexOf(idSuffix) > -1 || if (
(cipher != null && cipher.type !== CipherType.Login)) { !chrome.contextMenus ||
this.menuOptionsLoaded.indexOf(idSuffix) > -1 ||
(cipher != null && cipher.type !== CipherType.Login)
) {
return; return;
} }
this.menuOptionsLoaded.push(idSuffix); this.menuOptionsLoaded.push(idSuffix);
if (cipher == null || (cipher.login.password && cipher.login.password !== '')) { if (cipher == null || (cipher.login.password && cipher.login.password !== "")) {
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'autofill_' + idSuffix, id: "autofill_" + idSuffix,
parentId: 'autofill', parentId: "autofill",
contexts: ['all'], contexts: ["all"],
title: this.sanitizeContextMenuTitle(title), title: this.sanitizeContextMenuTitle(title),
}); });
} }
if (cipher == null || (cipher.login.username && cipher.login.username !== '')) { if (cipher == null || (cipher.login.username && cipher.login.username !== "")) {
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-username_' + idSuffix, id: "copy-username_" + idSuffix,
parentId: 'copy-username', parentId: "copy-username",
contexts: ['all'], contexts: ["all"],
title: this.sanitizeContextMenuTitle(title), title: this.sanitizeContextMenuTitle(title),
}); });
} }
if (cipher == null || (cipher.login.password && cipher.login.password !== '' && cipher.viewPassword)) { if (
cipher == null ||
(cipher.login.password && cipher.login.password !== "" && cipher.viewPassword)
) {
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-password_' + idSuffix, id: "copy-password_" + idSuffix,
parentId: 'copy-password', parentId: "copy-password",
contexts: ['all'], contexts: ["all"],
title: this.sanitizeContextMenuTitle(title), title: this.sanitizeContextMenuTitle(title),
}); });
} }
const canAccessPremium = await this.userService.canAccessPremium(); const canAccessPremium = await this.userService.canAccessPremium();
if (canAccessPremium && (cipher == null || (cipher.login.totp && cipher.login.totp !== ''))) { if (canAccessPremium && (cipher == null || (cipher.login.totp && cipher.login.totp !== ""))) {
await this.contextMenusCreate({ await this.contextMenusCreate({
type: 'normal', type: "normal",
id: 'copy-totp_' + idSuffix, id: "copy-totp_" + idSuffix,
parentId: 'copy-totp', parentId: "copy-totp",
contexts: ['all'], contexts: ["all"],
title: this.sanitizeContextMenuTitle(title), title: this.sanitizeContextMenuTitle(title),
}); });
} }
} }
private sanitizeContextMenuTitle(title: string): string { private sanitizeContextMenuTitle(title: string): string {
return title.replace(/&/g, '&&'); return title.replace(/&/g, "&&");
} }
private async fullSync(override: boolean = false) { private async fullSync(override: boolean = false) {
@@ -686,7 +874,7 @@ export default class MainBackground {
// Browser API Helpers // Browser API Helpers
private contextMenusRemoveAll() { private contextMenusRemoveAll() {
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
chrome.contextMenus.removeAll(() => { chrome.contextMenus.removeAll(() => {
resolve(); resolve();
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
@@ -697,7 +885,7 @@ export default class MainBackground {
} }
private contextMenusCreate(options: any) { private contextMenusCreate(options: any) {
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
chrome.contextMenus.create(options, () => { chrome.contextMenus.create(options, () => {
resolve(); resolve();
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
@@ -714,8 +902,8 @@ export default class MainBackground {
const options = { const options = {
path: { path: {
19: 'images/icon19' + suffix + '.png', 19: "images/icon19" + suffix + ".png",
38: 'images/icon38' + suffix + '.png', 38: "images/icon38" + suffix + ".png",
}, },
}; };
@@ -726,7 +914,7 @@ export default class MainBackground {
// which doesn't resolve within a reasonable time. // which doesn't resolve within a reasonable time.
theAction.setIcon(options); theAction.setIcon(options);
} else { } else {
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
theAction.setIcon(options, () => resolve()); theAction.setIcon(options, () => resolve());
}); });
} }
@@ -734,7 +922,7 @@ export default class MainBackground {
private actionSetBadgeBackgroundColor(action: any) { private actionSetBadgeBackgroundColor(action: any) {
if (action && action.setBadgeBackgroundColor) { if (action && action.setBadgeBackgroundColor) {
action.setBadgeBackgroundColor({ color: '#294e5f' }); action.setBadgeBackgroundColor({ color: "#294e5f" });
} }
} }
@@ -758,9 +946,9 @@ export default class MainBackground {
tabId: tabId, tabId: tabId,
}); });
} else if (this.sidebarAction.setTitle) { } else if (this.sidebarAction.setTitle) {
let title = 'Bitwarden'; let title = "Bitwarden";
if (text && text !== '') { if (text && text !== "") {
title += (' [' + text + ']'); title += " [" + text + "]";
} }
this.sidebarAction.setTitle({ this.sidebarAction.setTitle({

View File

@@ -1,4 +1,4 @@
import NotificationQueueMessage from './notificationQueueMessage'; import NotificationQueueMessage from "./notificationQueueMessage";
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage { export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
cipherId: string; cipherId: string;

View File

@@ -1,4 +1,4 @@
import NotificationQueueMessage from './notificationQueueMessage'; import NotificationQueueMessage from "./notificationQueueMessage";
export default class AddLoginQueueMessage extends NotificationQueueMessage { export default class AddLoginQueueMessage extends NotificationQueueMessage {
username: string; username: string;

View File

@@ -1,4 +1,4 @@
import { NotificationQueueMessageType } from './notificationQueueMessageType'; import { NotificationQueueMessageType } from "./notificationQueueMessageType";
export default class NotificationQueueMessage { export default class NotificationQueueMessage {
type: NotificationQueueMessageType; type: NotificationQueueMessageType;

View File

@@ -1,4 +1,4 @@
export enum NotificationQueueMessageType { export enum NotificationQueueMessageType {
addLogin = 'addLogin', addLogin = "addLogin",
changePassword = 'changePassword', changePassword = "changePassword",
} }

View File

@@ -1,22 +1,22 @@
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 { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.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;
@@ -31,16 +31,23 @@ export class NativeMessagingBackground {
private appId: string; private appId: string;
private validatingFingerprint: boolean; private validatingFingerprint: boolean;
constructor(private storageService: StorageService, private cryptoService: CryptoService, constructor(
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService, private cryptoService: CryptoService,
private messagingService: MessagingService, private appIdService: AppIdService, private cryptoFunctionService: CryptoFunctionService,
private platformUtilsService: PlatformUtilsService) { private vaultTimeoutService: VaultTimeoutService,
private runtimeBackground: RuntimeBackground,
private i18nService: I18nService,
private userService: UserService,
private messagingService: MessagingService,
private appIdService: AppIdService,
private platformUtilsService: PlatformUtilsService
) {
this.storageService.save(ConstantsService.biometricFingerprintValidated, false); this.storageService.save(ConstantsService.biometricFingerprintValidated, 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);
}); });
} }
@@ -51,7 +58,7 @@ export class NativeMessagingBackground {
this.storageService.save(ConstantsService.biometricFingerprintValidated, false); this.storageService.save(ConstantsService.biometricFingerprintValidated, 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;
@@ -69,30 +76,34 @@ export class NativeMessagingBackground {
this.port.onMessage.addListener(async (message: any) => { this.port.onMessage.addListener(async (message: any) => {
switch (message.command) { switch (message.command) {
case 'connected': case "connected":
connectedCallback(); connectedCallback();
break; break;
case 'disconnected': case "disconnected":
if (this.connecting) { if (this.connecting) {
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
text: this.i18nService.t('startDesktopDesc'), text: this.i18nService.t("startDesktopDesc"),
title: this.i18nService.t('startDesktopTitle'), title: this.i18nService.t("startDesktopTitle"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'error', type: "error",
}); });
reject(); reject();
} }
this.connected = false; this.connected = false;
this.port.disconnect(); this.port.disconnect();
break; break;
case 'setupEncryption': case "setupEncryption":
// Ignore since it belongs to another device // Ignore since it belongs to another device
if (message.appId !== this.appId) { if (message.appId !== this.appId) {
return; return;
} }
const encrypted = Utils.fromB64ToArray(message.sharedSecret); const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm); const decrypted = await this.cryptoFunctionService.rsaDecrypt(
encrypted.buffer,
this.privateKey,
EncryptionAlgorithm
);
if (this.validatingFingerprint) { if (this.validatingFingerprint) {
this.validatingFingerprint = false; this.validatingFingerprint = false;
@@ -101,7 +112,7 @@ export class NativeMessagingBackground {
this.sharedSecret = new SymmetricCryptoKey(decrypted); this.sharedSecret = new SymmetricCryptoKey(decrypted);
this.secureSetupResolve(); this.secureSetupResolve();
break; break;
case 'invalidateEncryption': case "invalidateEncryption":
// Ignore since it belongs to another device // Ignore since it belongs to another device
if (message.appId !== this.appId) { if (message.appId !== this.appId) {
return; return;
@@ -111,21 +122,21 @@ export class NativeMessagingBackground {
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; break;
case 'verifyFingerprint': { case "verifyFingerprint": {
if (this.sharedSecret == null) { if (this.sharedSecret == null) {
this.validatingFingerprint = true; this.validatingFingerprint = true;
this.showFingerprintDialog(); this.showFingerprintDialog();
} }
break; break;
} }
case 'wrongUserId': case "wrongUserId":
this.showWrongUserDialog(); this.showWrongUserDialog();
default: default:
// Ignore since it belongs to another device // Ignore since it belongs to another device
@@ -146,11 +157,11 @@ export class NativeMessagingBackground {
} }
if (error != null) { if (error != null) {
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
text: this.i18nService.t('desktopIntegrationDisabledDesc'), text: this.i18nService.t("desktopIntegrationDisabledDesc"),
title: this.i18nService.t('desktopIntegrationDisabledTitle'), title: this.i18nService.t("desktopIntegrationDisabledTitle"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'error', type: "error",
}); });
} }
this.sharedSecret = null; this.sharedSecret = null;
@@ -162,11 +173,11 @@ export class NativeMessagingBackground {
} }
showWrongUserDialog() { showWrongUserDialog() {
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
text: this.i18nService.t('nativeMessagingWrongUserDesc'), text: this.i18nService.t("nativeMessagingWrongUserDesc"),
title: this.i18nService.t('nativeMessagingWrongUserTitle'), title: this.i18nService.t("nativeMessagingWrongUserTitle"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'error', type: "error",
}); });
} }
@@ -178,7 +189,7 @@ export class NativeMessagingBackground {
if (this.platformUtilsService.isSafari()) { if (this.platformUtilsService.isSafari()) {
this.postMessage(message); this.postMessage(message);
} else { } else {
this.postMessage({appId: this.appId, message: await this.encryptMessage(message)}); this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) });
} }
} }
@@ -210,11 +221,11 @@ export class NativeMessagingBackground {
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",
}); });
} }
} }
@@ -227,35 +238,35 @@ export class NativeMessagingBackground {
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
// tslint:disable-next-line // tslint:disable-next-line
console.error('NativeMessage is to old, ignoring.'); console.error("NativeMessage is to old, ignoring.");
return; return;
} }
switch (message.command) { switch (message.command) {
case 'biometricUnlock': case "biometricUnlock":
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
if (message.response === 'not enabled') { if (message.response === "not enabled") {
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
text: this.i18nService.t('biometricsNotEnabledDesc'), text: this.i18nService.t("biometricsNotEnabledDesc"),
title: this.i18nService.t('biometricsNotEnabledTitle'), title: this.i18nService.t("biometricsNotEnabledTitle"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'error', type: "error",
}); });
break; break;
} else if (message.response === 'not supported') { } else if (message.response === "not supported") {
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
text: this.i18nService.t('biometricsNotSupportedDesc'), text: this.i18nService.t("biometricsNotSupportedDesc"),
title: this.i18nService.t('biometricsNotSupportedTitle'), title: this.i18nService.t("biometricsNotSupportedTitle"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'error', type: "error",
}); });
break; break;
} }
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
if (enabled === null || enabled === false) { if (enabled === null || enabled === false) {
if (message.response === 'unlocked') { if (message.response === "unlocked") {
await this.storageService.save(ConstantsService.biometricUnlockKey, true); await this.storageService.save(ConstantsService.biometricUnlockKey, true);
} }
break; break;
@@ -266,15 +277,17 @@ export class NativeMessagingBackground {
break; break;
} }
if (message.response === 'unlocked') { if (message.response === "unlocked") {
await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); await this.cryptoService.setKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
);
// Verify key is correct by attempting to decrypt a secret // Verify key is correct by attempting to decrypt a secret
try { try {
await this.cryptoService.getFingerprint(await this.userService.getUserId()); await this.cryptoService.getFingerprint(await this.userService.getUserId());
} catch (e) { } catch (e) {
// tslint:disable-next-line // tslint:disable-next-line
console.error('Unable to verify key:', e); console.error("Unable to verify key:", e);
await this.cryptoService.clearKey(); await this.cryptoService.clearKey();
this.showWrongUserDialog(); this.showWrongUserDialog();
@@ -283,12 +296,12 @@ export class NativeMessagingBackground {
} }
this.vaultTimeoutService.biometricLocked = false; this.vaultTimeoutService.biometricLocked = false;
this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); this.runtimeBackground.processMessage({ command: "unlocked" }, null, null);
} }
break; break;
default: default:
// tslint:disable-next-line // tslint:disable-next-line
console.error('NativeMessage, got unknown command: ', message.command); console.error("NativeMessage, got unknown command: ", message.command);
} }
if (this.resolver) { if (this.resolver) {
@@ -302,12 +315,12 @@ export class NativeMessagingBackground {
this.privateKey = privateKey; this.privateKey = privateKey;
this.sendUnencrypted({ this.sendUnencrypted({
command: 'setupEncryption', command: "setupEncryption",
publicKey: Utils.fromBufferToB64(publicKey), publicKey: Utils.fromBufferToB64(publicKey),
userId: await this.userService.getUserId(), userId: await this.userService.getUserId(),
}); });
return new Promise((resolve, reject) => this.secureSetupResolve = resolve); return new Promise((resolve, reject) => (this.secureSetupResolve = resolve));
} }
private async sendUnencrypted(message: any) { private async sendUnencrypted(message: any) {
@@ -317,17 +330,21 @@ export class NativeMessagingBackground {
message.timestamp = Date.now(); message.timestamp = Date.now();
this.postMessage({appId: this.appId, message: message}); this.postMessage({ appId: this.appId, message: message });
} }
private async showFingerprintDialog() { private async showFingerprintDialog() {
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' '); const fingerprint = (
await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)
).join(" ");
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`, html: `${this.i18nService.t(
title: this.i18nService.t('desktopSyncVerificationTitle'), "desktopIntegrationVerificationText"
confirmText: this.i18nService.t('ok'), )}<br><br><strong>${fingerprint}</strong>`,
type: 'warning', title: this.i18nService.t("desktopSyncVerificationTitle"),
confirmText: this.i18nService.t("ok"),
type: "warning",
}); });
} }
} }

View File

@@ -1,108 +1,119 @@
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 { LoginUriView } from 'jslib-common/models/view/loginUriView'; import { LoginUriView } from "jslib-common/models/view/loginUriView";
import { LoginView } from 'jslib-common/models/view/loginView'; import { LoginView } from "jslib-common/models/view/loginView";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { AutofillService } from '../services/abstractions/autofill.service'; import { AutofillService } from "../services/abstractions/autofill.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 { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from "jslib-common/enums/policyType";
import AddChangePasswordQueueMessage from './models/addChangePasswordQueueMessage'; import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage";
import AddLoginQueueMessage from './models/addLoginQueueMessage'; import AddLoginQueueMessage from "./models/addLoginQueueMessage";
import AddLoginRuntimeMessage from './models/addLoginRuntimeMessage'; import AddLoginRuntimeMessage from "./models/addLoginRuntimeMessage";
import ChangePasswordRuntimeMessage from './models/changePasswordRuntimeMessage'; import ChangePasswordRuntimeMessage from "./models/changePasswordRuntimeMessage";
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
import { NotificationQueueMessageType } from './models/notificationQueueMessageType'; import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
export default class NotificationBackground { export default class NotificationBackground {
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = []; private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
constructor(private main: MainBackground, private autofillService: AutofillService, constructor(
private cipherService: CipherService, private storageService: StorageService, private main: MainBackground,
private vaultTimeoutService: VaultTimeoutService, private policyService: PolicyService, private autofillService: AutofillService,
private folderService: FolderService, private userService: UserService) { private cipherService: CipherService,
} private storageService: StorageService,
private vaultTimeoutService: VaultTimeoutService,
private policyService: PolicyService,
private folderService: FolderService,
private userService: UserService
) {}
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) => { BrowserApi.messageListener(
"notification.background",
async (msg: any, sender: chrome.runtime.MessageSender) => {
await this.processMessage(msg, sender); await this.processMessage(msg, sender);
}); }
);
this.cleanupNotificationQueue(); this.cleanupNotificationQueue();
} }
async processMessage(msg: any, sender: chrome.runtime.MessageSender) { async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
switch (msg.command) { switch (msg.command) {
case 'unlockCompleted': case "unlockCompleted":
if (msg.data.target !== 'notification.background') { if (msg.data.target !== "notification.background") {
return; return;
} }
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender); await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
break; break;
case 'bgGetDataForTab': case "bgGetDataForTab":
await this.getDataForTab(sender.tab, msg.responseCommand); await this.getDataForTab(sender.tab, msg.responseCommand);
break; break;
case 'bgCloseNotificationBar': case "bgCloseNotificationBar":
await BrowserApi.tabSendMessageData(sender.tab, 'closeNotificationBar'); await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
break; break;
case 'bgAdjustNotificationBar': case "bgAdjustNotificationBar":
await BrowserApi.tabSendMessageData(sender.tab, 'adjustNotificationBar', msg.data); await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data);
break; break;
case 'bgAddLogin': case "bgAddLogin":
await this.addLogin(msg.login, sender.tab); await this.addLogin(msg.login, sender.tab);
break; break;
case 'bgChangedPassword': case "bgChangedPassword":
await this.changedPassword(msg.data, sender.tab); await this.changedPassword(msg.data, sender.tab);
break; break;
case 'bgAddClose': case "bgAddClose":
case 'bgChangeClose': case "bgChangeClose":
this.removeTabFromNotificationQueue(sender.tab); this.removeTabFromNotificationQueue(sender.tab);
break; break;
case 'bgAddSave': case "bgAddSave":
case 'bgChangeSave': case "bgChangeSave":
if (await this.vaultTimeoutService.isLocked()) { if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = { const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: { commandToRetry: {
msg: msg, msg: msg,
sender: sender, sender: sender,
}, },
target: 'notification.background', target: "notification.background",
}; };
await BrowserApi.tabSendMessageData(sender.tab, 'addToLockedVaultPendingNotifications', retryMessage); await BrowserApi.tabSendMessageData(
await BrowserApi.tabSendMessageData(sender.tab, 'promptForLogin'); sender.tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
return; return;
} }
await this.saveOrUpdateCredentials(sender.tab, msg.folder); await this.saveOrUpdateCredentials(sender.tab, msg.folder);
break; break;
case 'bgNeverSave': case "bgNeverSave":
await this.saveNever(sender.tab); await this.saveNever(sender.tab);
break; break;
case 'collectPageDetailsResponse': case "collectPageDetailsResponse":
switch (msg.sender) { switch (msg.sender) {
case 'notificationBar': case "notificationBar":
const forms = this.autofillService.getFormsWithPasswordFields(msg.details); const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
await BrowserApi.tabSendMessageData(msg.tab, 'notificationBarPageDetails', { await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", {
details: msg.details, details: msg.details,
forms: forms, forms: forms,
}); });
@@ -152,20 +163,23 @@ export default class NotificationBackground {
} }
for (let i = 0; i < this.notificationQueue.length; i++) { for (let i = 0; i < this.notificationQueue.length; i++) {
if (this.notificationQueue[i].tabId !== tab.id || this.notificationQueue[i].domain !== tabDomain) { if (
this.notificationQueue[i].tabId !== tab.id ||
this.notificationQueue[i].domain !== tabDomain
) {
continue; continue;
} }
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) { if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', { BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
type: 'add', type: "add",
typeData: { typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked, isVaultLocked: this.notificationQueue[i].wasVaultLocked,
}, },
}); });
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) { } else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', { BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
type: 'change', type: "change",
typeData: { typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked, isVaultLocked: this.notificationQueue[i].wasVaultLocked,
}, },
@@ -184,7 +198,7 @@ export default class NotificationBackground {
} }
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) { private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
if (!await this.userService.isAuthenticated()) { if (!(await this.userService.isAuthenticated())) {
return; return;
} }
@@ -198,13 +212,15 @@ export default class NotificationBackground {
normalizedUsername = normalizedUsername.toLowerCase(); normalizedUsername = normalizedUsername.toLowerCase();
} }
const disabledAddLogin = await this.storageService.get<boolean>(ConstantsService.disableAddLoginNotificationKey); const disabledAddLogin = await this.storageService.get<boolean>(
ConstantsService.disableAddLoginNotificationKey
);
if (await this.vaultTimeoutService.isLocked()) { if (await this.vaultTimeoutService.isLocked()) {
if (disabledAddLogin) { if (disabledAddLogin) {
return; return;
} }
if (!await this.allowPersonalOwnership()) { if (!(await this.allowPersonalOwnership())) {
return; return;
} }
@@ -213,22 +229,26 @@ export default class NotificationBackground {
} }
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url); const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
const usernameMatches = ciphers.filter(c => const usernameMatches = ciphers.filter(
c.login.username != null && c.login.username.toLowerCase() === normalizedUsername); (c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername
);
if (usernameMatches.length === 0) { if (usernameMatches.length === 0) {
if (disabledAddLogin) { if (disabledAddLogin) {
return; return;
} }
if (!await this.allowPersonalOwnership()) { if (!(await this.allowPersonalOwnership())) {
return; return;
} }
this.pushAddLoginToQueue(loginDomain, loginInfo, tab); this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
} else if (
} else if (usernameMatches.length === 1 && usernameMatches[0].login.password !== loginInfo.password) { usernameMatches.length === 1 &&
usernameMatches[0].login.password !== loginInfo.password
) {
const disabledChangePassword = await this.storageService.get<boolean>( const disabledChangePassword = await this.storageService.get<boolean>(
ConstantsService.disableChangedPasswordNotificationKey); ConstantsService.disableChangedPasswordNotificationKey
);
if (disabledChangePassword) { if (disabledChangePassword) {
return; return;
} }
@@ -236,7 +256,12 @@ export default class NotificationBackground {
} }
} }
private async pushAddLoginToQueue(loginDomain: string, loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) { private async pushAddLoginToQueue(
loginDomain: string,
loginInfo: AddLoginRuntimeMessage,
tab: chrome.tabs.Tab,
isVaultLocked: boolean = false
) {
// remove any old messages for this tab // remove any old messages for this tab
this.removeTabFromNotificationQueue(tab); this.removeTabFromNotificationQueue(tab);
const message: AddLoginQueueMessage = { const message: AddLoginQueueMessage = {
@@ -246,7 +271,7 @@ export default class NotificationBackground {
domain: loginDomain, domain: loginDomain,
uri: loginInfo.url, uri: loginInfo.url,
tabId: tab.id, tabId: tab.id,
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked, wasVaultLocked: isVaultLocked,
}; };
this.notificationQueue.push(message); this.notificationQueue.push(message);
@@ -267,7 +292,9 @@ export default class NotificationBackground {
let id: string = null; let id: string = null;
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url); const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
if (changeData.currentPassword != null) { if (changeData.currentPassword != null) {
const passwordMatches = ciphers.filter(c => c.login.password === changeData.currentPassword); const passwordMatches = ciphers.filter(
(c) => c.login.password === changeData.currentPassword
);
if (passwordMatches.length === 1) { if (passwordMatches.length === 1) {
id = passwordMatches[0].id; id = passwordMatches[0].id;
} }
@@ -279,7 +306,13 @@ export default class NotificationBackground {
} }
} }
private async pushChangePasswordToQueue(cipherId: string, loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) { private async pushChangePasswordToQueue(
cipherId: string,
loginDomain: string,
newPassword: string,
tab: chrome.tabs.Tab,
isVaultLocked: boolean = false
) {
// remove any old messages for this tab // remove any old messages for this tab
this.removeTabFromNotificationQueue(tab); this.removeTabFromNotificationQueue(tab);
const message: AddChangePasswordQueueMessage = { const message: AddChangePasswordQueueMessage = {
@@ -288,7 +321,7 @@ export default class NotificationBackground {
newPassword: newPassword, newPassword: newPassword,
domain: loginDomain, domain: loginDomain,
tabId: tab.id, tabId: tab.id,
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked, wasVaultLocked: isVaultLocked,
}; };
this.notificationQueue.push(message); this.notificationQueue.push(message);
@@ -298,8 +331,11 @@ export default class NotificationBackground {
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) { private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) { for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i]; const queueMessage = this.notificationQueue[i];
if (queueMessage.tabId !== tab.id || if (
(queueMessage.type !== NotificationQueueMessageType.addLogin && queueMessage.type !== NotificationQueueMessageType.changePassword)) { queueMessage.tabId !== tab.id ||
(queueMessage.type !== NotificationQueueMessageType.addLogin &&
queueMessage.type !== NotificationQueueMessageType.changePassword)
) {
continue; continue;
} }
@@ -309,10 +345,10 @@ export default class NotificationBackground {
} }
this.notificationQueue.splice(i, 1); this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar'); BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
if (queueMessage.type === NotificationQueueMessageType.changePassword) { if (queueMessage.type === NotificationQueueMessageType.changePassword) {
const message = (queueMessage as AddChangePasswordQueueMessage); const message = queueMessage as AddChangePasswordQueueMessage;
const cipher = await this.getDecryptedCipherById(message.cipherId); const cipher = await this.getDecryptedCipherById(message.cipherId);
if (cipher == null) { if (cipher == null) {
return; return;
@@ -326,11 +362,15 @@ export default class NotificationBackground {
} }
// If the vault was locked, check if a cipher needs updating instead of creating a new one // 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) { if (
const message = (queueMessage as AddLoginQueueMessage); queueMessage.type === NotificationQueueMessageType.addLogin &&
queueMessage.wasVaultLocked === true
) {
const message = queueMessage as AddLoginQueueMessage;
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri); const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
const usernameMatches = ciphers.filter(c => c.login.username != null && const usernameMatches = ciphers.filter(
c.login.username.toLowerCase() === message.username); (c) => c.login.username != null && c.login.username.toLowerCase() === message.username
);
if (usernameMatches.length >= 1) { if (usernameMatches.length >= 1) {
await this.updateCipher(usernameMatches[0], message.password); await this.updateCipher(usernameMatches[0], message.password);
@@ -351,13 +391,13 @@ export default class NotificationBackground {
loginModel.password = queueMessage.password; loginModel.password = queueMessage.password;
const model = new CipherView(); const model = new CipherView();
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain; model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
model.name = model.name.replace(/^www\./, ''); model.name = model.name.replace(/^www\./, "");
model.type = CipherType.Login; model.type = CipherType.Login;
model.login = loginModel; model.login = loginModel;
if (!Utils.isNullOrWhitespace(folderId)) { if (!Utils.isNullOrWhitespace(folderId)) {
const folders = await this.folderService.getAllDecrypted(); const folders = await this.folderService.getAllDecrypted();
if (folders.some(x => x.id === folderId)) { if (folders.some((x) => x.id === folderId)) {
model.folderId = folderId; model.folderId = folderId;
} }
} }
@@ -385,7 +425,10 @@ export default class NotificationBackground {
private async saveNever(tab: chrome.tabs.Tab) { private async saveNever(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) { for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i]; const queueMessage = this.notificationQueue[i];
if (queueMessage.tabId !== tab.id || queueMessage.type !== NotificationQueueMessageType.addLogin) { if (
queueMessage.tabId !== tab.id ||
queueMessage.type !== NotificationQueueMessageType.addLogin
) {
continue; continue;
} }
@@ -395,7 +438,7 @@ export default class NotificationBackground {
} }
this.notificationQueue.splice(i, 1); this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar'); BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
const hostname = Utils.getHostname(tab.url); const hostname = Utils.getHostname(tab.url);
await this.cipherService.saveNeverDomain(hostname); await this.cipherService.saveNeverDomain(hostname);
@@ -404,7 +447,7 @@ export default class NotificationBackground {
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) { private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
const responseData: any = {}; const responseData: any = {};
if (responseCommand === 'notificationBarGetFoldersList') { if (responseCommand === "notificationBarGetFoldersList") {
responseData.folders = await this.folderService.getAllDecrypted(); responseData.folders = await this.folderService.getAllDecrypted();
} }
@@ -412,6 +455,6 @@ export default class NotificationBackground {
} }
private async allowPersonalOwnership(): Promise<boolean> { private async allowPersonalOwnership(): Promise<boolean> {
return !await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership); return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
} }
} }

View File

@@ -1,21 +1,21 @@
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 { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { SystemService } from 'jslib-common/abstractions/system.service'; import { SystemService } from "jslib-common/abstractions/system.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.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 LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class RuntimeBackground { export default class RuntimeBackground {
private autofillTimeout: any; private autofillTimeout: any;
@@ -23,13 +23,18 @@ export default class RuntimeBackground {
private onInstalledReason: string = null; private onInstalledReason: string = null;
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = []; private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
constructor(private main: MainBackground, private autofillService: AutofillService, constructor(
private main: MainBackground,
private autofillService: AutofillService,
private platformUtilsService: BrowserPlatformUtilsService, private platformUtilsService: BrowserPlatformUtilsService,
private storageService: StorageService, private i18nService: I18nService, private storageService: StorageService,
private notificationsService: NotificationsService, private systemService: SystemService, private i18nService: I18nService,
private environmentService: EnvironmentService, private messagingService: MessagingService, private notificationsService: NotificationsService,
private logService: LogService) { private systemService: SystemService,
private environmentService: EnvironmentService,
private messagingService: MessagingService,
private logService: LogService
) {
// onInstalled listener must be wired up before anything else, so we do it in the ctor // onInstalled listener must be wired up before anything else, so we do it in the ctor
chrome.runtime.onInstalled.addListener((details: any) => { chrome.runtime.onInstalled.addListener((details: any) => {
this.onInstalledReason = details.reason; this.onInstalledReason = details.reason;
@@ -42,15 +47,18 @@ export default class RuntimeBackground {
} }
await this.checkOnInstalled(); await this.checkOnInstalled();
BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => { BrowserApi.messageListener(
"runtime.background",
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
await this.processMessage(msg, sender, sendResponse); await this.processMessage(msg, sender, sendResponse);
}); }
);
} }
async processMessage(msg: any, sender: any, sendResponse: any) { async processMessage(msg: any, sender: any, sendResponse: any) {
switch (msg.command) { switch (msg.command) {
case 'loggedIn': case "loggedIn":
case 'unlocked': case "unlocked":
let item: LockedVaultPendingNotificationsItem; let item: LockedVaultPendingNotificationsItem;
if (this.lockedVaultPendingNotifications.length > 0) { if (this.lockedVaultPendingNotifications.length > 0) {
@@ -64,59 +72,68 @@ export default class RuntimeBackground {
await this.main.setIcon(); await this.main.setIcon();
await this.main.refreshBadgeAndMenu(false); await this.main.refreshBadgeAndMenu(false);
this.notificationsService.updateConnection(msg.command === 'unlocked'); this.notificationsService.updateConnection(msg.command === "unlocked");
this.systemService.cancelProcessReload(); this.systemService.cancelProcessReload();
if (item) { if (item) {
await BrowserApi.tabSendMessageData(item.commandToRetry.sender.tab, 'unlockCompleted', item); await BrowserApi.tabSendMessageData(
item.commandToRetry.sender.tab,
"unlockCompleted",
item
);
} }
break; break;
case 'addToLockedVaultPendingNotifications': case "addToLockedVaultPendingNotifications":
this.lockedVaultPendingNotifications.push(msg.data); this.lockedVaultPendingNotifications.push(msg.data);
break; break;
case 'logout': case "logout":
await this.main.logout(msg.expired); await this.main.logout(msg.expired);
break; break;
case 'syncCompleted': case "syncCompleted":
if (msg.successfully) { if (msg.successfully) {
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000); setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
} }
break; break;
case 'openPopup': case "openPopup":
await this.main.openPopup(); await this.main.openPopup();
break; break;
case 'promptForLogin': case "promptForLogin":
await BrowserApi.createNewTab('popup/index.html?uilocation=popout', true, true); await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
break; break;
case 'showDialogResolve': case "showDialogResolve":
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed); this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
break; break;
case 'bgCollectPageDetails': case "bgCollectPageDetails":
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId); await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
break; break;
case 'bgUpdateContextMenu': case "bgUpdateContextMenu":
case 'editedCipher': case "editedCipher":
case 'addedCipher': case "addedCipher":
case 'deletedCipher': case "deletedCipher":
await this.main.refreshBadgeAndMenu(); await this.main.refreshBadgeAndMenu();
break; break;
case 'bgReseedStorage': case "bgReseedStorage":
await this.main.reseedStorage(); await this.main.reseedStorage();
break; break;
case 'collectPageDetailsResponse': case "collectPageDetailsResponse":
switch (msg.sender) { switch (msg.sender) {
case 'autofiller': case "autofiller":
case 'autofill_cmd': case "autofill_cmd":
const totpCode = await this.autofillService.doAutoFillActiveTab([{ const totpCode = await this.autofillService.doAutoFillActiveTab(
[
{
frameId: sender.frameId, frameId: sender.frameId,
tab: msg.tab, tab: msg.tab,
details: msg.details, details: msg.details,
}], msg.sender === 'autofill_cmd'); },
],
msg.sender === "autofill_cmd"
);
if (totpCode != null) { if (totpCode != null) {
this.platformUtilsService.copyToClipboard(totpCode, { window: window }); this.platformUtilsService.copyToClipboard(totpCode, { window: window });
} }
break; break;
case 'contextMenu': case "contextMenu":
clearTimeout(this.autofillTimeout); clearTimeout(this.autofillTimeout);
this.pageDetailsToAutoFill.push({ this.pageDetailsToAutoFill.push({
frameId: sender.frameId, frameId: sender.frameId,
@@ -129,7 +146,7 @@ export default class RuntimeBackground {
break; break;
} }
break; break;
case 'authResult': case "authResult":
const vaultUrl = this.environmentService.getWebVaultUrl(); const vaultUrl = this.environmentService.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
@@ -137,37 +154,45 @@ export default class RuntimeBackground {
} }
try { try {
BrowserApi.createNewTab('popup/index.html?uilocation=popout#/sso?code=' + BrowserApi.createNewTab(
encodeURIComponent(msg.code) + '&state=' + encodeURIComponent(msg.state)); "popup/index.html?uilocation=popout#/sso?code=" +
} encodeURIComponent(msg.code) +
catch { "&state=" +
this.logService.error('Unable to open sso popout tab'); encodeURIComponent(msg.state)
);
} catch {
this.logService.error("Unable to open sso popout tab");
} }
break; break;
case 'webAuthnResult': case "webAuthnResult":
const vaultUrl2 = this.environmentService.getWebVaultUrl(); const vaultUrl2 = this.environmentService.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) { if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
return; return;
} }
const params = `webAuthnResponse=${encodeURIComponent(msg.data)};` + const params =
`webAuthnResponse=${encodeURIComponent(msg.data)};` +
`remember=${encodeURIComponent(msg.remember)}`; `remember=${encodeURIComponent(msg.remember)}`;
BrowserApi.createNewTab(`popup/index.html?uilocation=popout#/2fa;${params}`, undefined, false); BrowserApi.createNewTab(
`popup/index.html?uilocation=popout#/2fa;${params}`,
undefined,
false
);
break; break;
case 'reloadPopup': case "reloadPopup":
this.messagingService.send('reloadPopup'); this.messagingService.send("reloadPopup");
break; break;
case 'emailVerificationRequired': case "emailVerificationRequired":
this.messagingService.send('showDialog', { this.messagingService.send("showDialog", {
dialogId: 'emailVerificationRequired', dialogId: "emailVerificationRequired",
title: this.i18nService.t('emailVerificationRequired'), title: this.i18nService.t("emailVerificationRequired"),
text: this.i18nService.t('emailVerificationRequiredDesc'), text: this.i18nService.t("emailVerificationRequiredDesc"),
confirmText: this.i18nService.t('ok'), confirmText: this.i18nService.t("ok"),
type: 'info', type: "info",
}); });
break; break;
case 'getClickedElementResponse': case "getClickedElementResponse":
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window }); this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
default: default:
break; break;
@@ -193,8 +218,8 @@ export default class RuntimeBackground {
private async checkOnInstalled() { private async checkOnInstalled() {
setTimeout(async () => { setTimeout(async () => {
if (this.onInstalledReason != null) { if (this.onInstalledReason != null) {
if (this.onInstalledReason === 'install') { if (this.onInstalledReason === "install") {
BrowserApi.createNewTab('https://bitwarden.com/browser-start/'); BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
await this.setDefaultSettings(); await this.setDefaultSettings();
} }
@@ -205,15 +230,19 @@ export default class RuntimeBackground {
private async setDefaultSettings() { private async setDefaultSettings() {
// Default timeout option to "on restart". // Default timeout option to "on restart".
const currentVaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const currentVaultTimeout = await this.storageService.get<number>(
ConstantsService.vaultTimeoutKey
);
if (currentVaultTimeout == null) { if (currentVaultTimeout == null) {
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1); await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
} }
// Default action to "lock". // Default action to "lock".
const currentVaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey); const currentVaultTimeoutAction = await this.storageService.get<string>(
ConstantsService.vaultTimeoutActionKey
);
if (currentVaultTimeoutAction == null) { if (currentVaultTimeoutAction == null) {
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, 'lock'); await this.storageService.save(ConstantsService.vaultTimeoutActionKey, "lock");
} }
} }
} }

View File

@@ -1,9 +1,11 @@
import MainBackground from './main.background'; import MainBackground from "./main.background";
import NotificationBackground from './notification.background'; import NotificationBackground from "./notification.background";
export default class TabsBackground { export default class TabsBackground {
constructor(private main: MainBackground, private notificationBackground: NotificationBackground) { constructor(
} private main: MainBackground,
private notificationBackground: NotificationBackground
) {}
async init() { async init() {
if (!chrome.tabs) { if (!chrome.tabs) {
@@ -12,8 +14,8 @@ export default class TabsBackground {
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => { chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
await this.main.refreshBadgeAndMenu(); await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabActivated'); this.main.messagingService.send("tabActivated");
this.main.messagingService.send('tabChanged'); this.main.messagingService.send("tabChanged");
}); });
chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => { chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => {
@@ -23,19 +25,21 @@ export default class TabsBackground {
this.main.onReplacedRan = true; this.main.onReplacedRan = true;
await this.notificationBackground.checkNotificationQueue(); await this.notificationBackground.checkNotificationQueue();
await this.main.refreshBadgeAndMenu(); await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabReplaced'); this.main.messagingService.send("tabReplaced");
this.main.messagingService.send('tabChanged'); this.main.messagingService.send("tabChanged");
}); });
chrome.tabs.onUpdated.addListener(async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => { chrome.tabs.onUpdated.addListener(
async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
if (this.main.onUpdatedRan) { if (this.main.onUpdatedRan) {
return; return;
} }
this.main.onUpdatedRan = true; this.main.onUpdatedRan = true;
await this.notificationBackground.checkNotificationQueue(tab); await this.notificationBackground.checkNotificationQueue(tab);
await this.main.refreshBadgeAndMenu(); await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabUpdated'); this.main.messagingService.send("tabUpdated");
this.main.messagingService.send('tabChanged'); this.main.messagingService.send("tabChanged");
}); }
);
} }
} }

View File

@@ -1,16 +1,19 @@
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.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 { UriMatchType } from 'jslib-common/enums/uriMatchType'; import { UriMatchType } from "jslib-common/enums/uriMatchType";
export default class WebRequestBackground { export default class WebRequestBackground {
private pendingAuthRequests: any[] = []; private pendingAuthRequests: any[] = [];
private webRequest: any; private webRequest: any;
private isFirefox: boolean; private isFirefox: boolean;
constructor(platformUtilsService: PlatformUtilsService, private cipherService: CipherService, constructor(
private vaultTimeoutService: VaultTimeoutService) { platformUtilsService: PlatformUtilsService,
private cipherService: CipherService,
private vaultTimeoutService: VaultTimeoutService
) {
this.webRequest = (window as any).chrome.webRequest; this.webRequest = (window as any).chrome.webRequest;
this.isFirefox = platformUtilsService.isFirefox(); this.isFirefox = platformUtilsService.isFirefox();
} }
@@ -20,7 +23,8 @@ export default class WebRequestBackground {
return; return;
} }
this.webRequest.onAuthRequired.addListener(async (details: any, callback: any) => { this.webRequest.onAuthRequired.addListener(
async (details: any, callback: any) => {
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) { if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
if (callback) { if (callback) {
callback(); callback();
@@ -37,12 +41,20 @@ export default class WebRequestBackground {
} else { } else {
await this.resolveAuthCredentials(details.url, callback, callback); await this.resolveAuthCredentials(details.url, callback, callback);
} }
}, { urls: ['http://*/*', 'https://*/*'] }, [this.isFirefox ? 'blocking' : 'asyncBlocking']); },
{ urls: ["http://*/*", "https://*/*"] },
[this.isFirefox ? "blocking" : "asyncBlocking"]
);
this.webRequest.onCompleted.addListener( this.webRequest.onCompleted.addListener((details: any) => this.completeAuthRequest(details), {
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] }); urls: ["http://*/*"],
});
this.webRequest.onErrorOccurred.addListener( this.webRequest.onErrorOccurred.addListener(
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] }); (details: any) => this.completeAuthRequest(details),
{
urls: ["http://*/*"],
}
);
} }
private async resolveAuthCredentials(domain: string, success: Function, error: Function) { private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
@@ -52,7 +64,11 @@ export default class WebRequestBackground {
} }
try { try {
const ciphers = await this.cipherService.getAllDecryptedForUrl(domain, null, UriMatchType.Host); const ciphers = await this.cipherService.getAllDecryptedForUrl(
domain,
null,
UriMatchType.Host
);
if (ciphers == null || ciphers.length !== 1) { if (ciphers == null || ciphers.length !== 1) {
error(); error();
return; return;

View File

@@ -1,4 +1,4 @@
import MainBackground from './main.background'; import MainBackground from "./main.background";
export default class WindowsBackground { export default class WindowsBackground {
private windows: any; private windows: any;
@@ -18,8 +18,8 @@ export default class WindowsBackground {
} }
await this.main.refreshBadgeAndMenu(); await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('windowFocused'); this.main.messagingService.send("windowFocused");
this.main.messagingService.send('windowChanged'); this.main.messagingService.send("windowChanged");
}); });
} }
} }

View File

@@ -1,15 +1,16 @@
import { SafariApp } from './safariApp'; import { SafariApp } from "./safariApp";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class BrowserApi { export class BrowserApi {
static isWebExtensionsApi: boolean = (typeof browser !== 'undefined'); static isWebExtensionsApi: boolean = typeof browser !== "undefined";
static isSafariApi: boolean = navigator.userAgent.indexOf(' Safari/') !== -1 && static isSafariApi: boolean =
navigator.userAgent.indexOf(' Chrome/') === -1 && navigator.userAgent.indexOf(" Safari/") !== -1 &&
navigator.userAgent.indexOf(' Chromium/') === -1; navigator.userAgent.indexOf(" Chrome/") === -1 &&
static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined'); navigator.userAgent.indexOf(" Chromium/") === -1;
static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf('Firefox/') !== -1 && static isChromeApi: boolean = !BrowserApi.isSafariApi && typeof chrome !== "undefined";
navigator.userAgent.indexOf('Android') !== -1; static isFirefoxOnAndroid: boolean =
navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1;
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null { static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({ return await BrowserApi.tabsQueryFirst({
@@ -32,7 +33,7 @@ export class BrowserApi {
} }
static async tabsQuery(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> { static async tabsQuery(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> {
return new Promise(resolve => { return new Promise((resolve) => {
chrome.tabs.query(options, (tabs: any[]) => { chrome.tabs.query(options, (tabs: any[]) => {
resolve(tabs); resolve(tabs);
}); });
@@ -48,7 +49,11 @@ export class BrowserApi {
return null; return null;
} }
static tabSendMessageData(tab: chrome.tabs.Tab, command: string, data: any = null): Promise<any[]> { static tabSendMessageData(
tab: chrome.tabs.Tab,
command: string,
data: any = null
): Promise<any[]> {
const obj: any = { const obj: any = {
command: command, command: command,
}; };
@@ -60,12 +65,16 @@ export class BrowserApi {
return BrowserApi.tabSendMessage(tab, obj); return BrowserApi.tabSendMessage(tab, obj);
} }
static async tabSendMessage(tab: chrome.tabs.Tab, obj: any, options: chrome.tabs.MessageSendOptions = null): Promise<any> { static async tabSendMessage(
tab: chrome.tabs.Tab,
obj: any,
options: chrome.tabs.MessageSendOptions = null
): Promise<any> {
if (!tab || !tab.id) { if (!tab || !tab.id) {
return; return;
} }
return new Promise<void>(resolve => { return new Promise<void>((resolve) => {
chrome.tabs.sendMessage(tab.id, obj, options, () => { chrome.tabs.sendMessage(tab.id, obj, options, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
// Some error happened // Some error happened
@@ -84,24 +93,29 @@ export class BrowserApi {
} }
static async isPopupOpen(): Promise<boolean> { static async isPopupOpen(): Promise<boolean> {
return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0); return Promise.resolve(chrome.extension.getViews({ type: "popup" }).length > 0);
} }
static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) { static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) {
chrome.tabs.create({ url: url, active: active }); chrome.tabs.create({ url: url, active: active });
} }
static messageListener(name: string, callback: (message: any, sender: chrome.runtime.MessageSender, response: any) => void) { static messageListener(
chrome.runtime.onMessage.addListener((msg: any, sender: chrome.runtime.MessageSender, response: any) => { name: string,
callback: (message: any, sender: chrome.runtime.MessageSender, response: any) => void
) {
chrome.runtime.onMessage.addListener(
(msg: any, sender: chrome.runtime.MessageSender, response: any) => {
callback(msg, sender, response); callback(msg, sender, response);
}); }
);
} }
static async closeLoginTab() { static async closeLoginTab() {
const tabs = await BrowserApi.tabsQuery({ const tabs = await BrowserApi.tabsQuery({
active: true, active: true,
title: 'Bitwarden', title: "Bitwarden",
windowType: 'normal', windowType: "normal",
currentWindow: true, currentWindow: true,
}); });
@@ -132,22 +146,26 @@ export class BrowserApi {
if (BrowserApi.isSafariApi) { if (BrowserApi.isSafariApi) {
const type = blobOptions != null ? blobOptions.type : null; const type = blobOptions != null ? blobOptions.type : null;
let data: string = null; let data: string = null;
if (type === 'text/plain' && typeof (blobData) === 'string') { if (type === "text/plain" && typeof blobData === "string") {
data = blobData; data = blobData;
} else { } else {
data = Utils.fromBufferToB64(blobData); data = Utils.fromBufferToB64(blobData);
} }
SafariApp.sendMessageToApp('downloadFile', JSON.stringify({ SafariApp.sendMessageToApp(
"downloadFile",
JSON.stringify({
blobData: data, blobData: data,
blobOptions: blobOptions, blobOptions: blobOptions,
fileName: fileName, fileName: fileName,
}), true); }),
true
);
} else { } else {
const blob = new Blob([blobData], blobOptions); const blob = new Blob([blobData], blobOptions);
if (navigator.msSaveOrOpenBlob) { if (navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName); navigator.msSaveBlob(blob, fileName);
} else { } else {
const a = win.document.createElement('a'); const a = win.document.createElement("a");
a.href = URL.createObjectURL(blob); a.href = URL.createObjectURL(blob);
a.download = fileName; a.download = fileName;
win.document.body.appendChild(a); win.document.body.appendChild(a);
@@ -158,7 +176,7 @@ export class BrowserApi {
} }
static gaFilter() { static gaFilter() {
return process.env.ENV !== 'production'; return process.env.ENV !== "production";
} }
static getUILanguage(win: Window) { static getUILanguage(win: Window) {
@@ -175,7 +193,9 @@ export class BrowserApi {
static reloadOpenWindows() { static reloadOpenWindows() {
const views = chrome.extension.getViews() as Window[]; const views = chrome.extension.getViews() as Window[];
views.filter(w => w.location.href != null).forEach(w => { views
.filter((w) => w.location.href != null)
.forEach((w) => {
w.location.reload(); w.location.reload();
}); });
} }
@@ -201,7 +221,7 @@ export class BrowserApi {
if (BrowserApi.isWebExtensionsApi) { if (BrowserApi.isWebExtensionsApi) {
return browser.runtime.getPlatformInfo(); return browser.runtime.getPlatformInfo();
} }
return new Promise(resolve => { return new Promise((resolve) => {
chrome.runtime.getPlatformInfo(resolve); chrome.runtime.getPlatformInfo(resolve);
}); });
} }

View File

@@ -1,21 +1,26 @@
import { BrowserApi } from './browserApi'; import { BrowserApi } from "./browserApi";
export class SafariApp { export class SafariApp {
static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> { static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> {
if (!BrowserApi.isSafariApi) { if (!BrowserApi.isSafariApi) {
return Promise.resolve(null); return Promise.resolve(null);
} }
return new Promise(resolve => { return new Promise((resolve) => {
const now = new Date(); const now = new Date();
const messageId = now.getTime().toString() + '_' + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); const messageId =
(browser as any).runtime.sendNativeMessage('com.bitwarden.desktop', { now.getTime().toString() + "_" + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
(browser as any).runtime.sendNativeMessage(
"com.bitwarden.desktop",
{
id: messageId, id: messageId,
command: command, command: command,
data: data, data: data,
responseData: null, responseData: null,
}, (response: any) => { },
(response: any) => {
resolve(response); resolve(response);
}); }
);
}); });
} }
} }

View File

@@ -1,6 +1,6 @@
@-webkit-keyframes bitwardenfill { @-webkit-keyframes bitwardenfill {
0% { 0% {
-webkit-transform: scale(1.0, 1.0); -webkit-transform: scale(1, 1);
} }
50% { 50% {
@@ -8,13 +8,13 @@
} }
100% { 100% {
-webkit-transform: scale(1.0, 1.0); -webkit-transform: scale(1, 1);
} }
} }
@-moz-keyframes bitwardenfill { @-moz-keyframes bitwardenfill {
0% { 0% {
transform: scale(1.0, 1.0); transform: scale(1, 1);
} }
50% { 50% {
@@ -22,7 +22,7 @@
} }
100% { 100% {
transform: scale(1.0, 1.0); transform: scale(1, 1);
} }
} }

View File

@@ -1,16 +1,16 @@
document.addEventListener('DOMContentLoaded', event => { document.addEventListener("DOMContentLoaded", (event) => {
let pageHref: string = null; let pageHref: string = null;
let filledThisHref = false; let filledThisHref = false;
let delayFillTimeout: number; let delayFillTimeout: number;
const enabledKey = 'enableAutoFillOnPageLoad'; const enabledKey = "enableAutoFillOnPageLoad";
chrome.storage.local.get(enabledKey, (obj: any) => { chrome.storage.local.get(enabledKey, (obj: any) => {
if (obj != null && obj[enabledKey] === true) { if (obj != null && obj[enabledKey] === true) {
setInterval(() => doFillIfNeeded(), 500); setInterval(() => doFillIfNeeded(), 500);
} }
}); });
chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => {
if (msg.command === 'fillForm' && pageHref === msg.url) { if (msg.command === "fillForm" && pageHref === msg.url) {
filledThisHref = true; filledThisHref = true;
} }
}); });
@@ -33,8 +33,8 @@ document.addEventListener('DOMContentLoaded', event => {
pageHref = window.location.href; pageHref = window.location.href;
const msg: any = { const msg: any = {
command: 'bgCollectPageDetails', command: "bgCollectPageDetails",
sender: 'autofiller', sender: "autofiller",
}; };
chrome.runtime.sendMessage(msg); chrome.runtime.sendMessage(msg);

View File

@@ -1,8 +1,8 @@
const inputTags = ['input', 'textarea', 'select']; const inputTags = ["input", "textarea", "select"];
const labelTags = ['label', 'span']; const labelTags = ["label", "span"];
const attributes = ['id', 'name', 'label-aria', 'placeholder']; const attributes = ["id", "name", "label-aria", "placeholder"];
const invalidElement = chrome.i18n.getMessage('copyCustomFieldNameInvalidElement'); const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
const noUniqueIdentifier = chrome.i18n.getMessage('copyCustomFieldNameNotUnique'); const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
let clickedEl: HTMLElement = null; let clickedEl: HTMLElement = null;
@@ -18,10 +18,10 @@ function getClickedElementIdentifier() {
// Try to identify the input element (which may not be the clicked element) // Try to identify the input element (which may not be the clicked element)
if (labelTags.includes(clickedTag)) { if (labelTags.includes(clickedTag)) {
let inputId = null; let inputId = null;
if (clickedTag === 'label') { if (clickedTag === "label") {
inputId = clickedEl.getAttribute('for'); inputId = clickedEl.getAttribute("for");
} else { } else {
inputId = clickedEl.closest('label')?.getAttribute('for'); inputId = clickedEl.closest("label")?.getAttribute("for");
} }
inputEl = document.getElementById(inputId); inputEl = document.getElementById(inputId);
@@ -35,7 +35,7 @@ function getClickedElementIdentifier() {
for (const attr of attributes) { for (const attr of attributes) {
const attributeValue = inputEl.getAttribute(attr); const attributeValue = inputEl.getAttribute(attr);
const selector = '[' + attr + '="' + attributeValue + '"]'; const selector = "[" + attr + '="' + attributeValue + '"]';
if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) { if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) {
return attributeValue; return attributeValue;
} }
@@ -44,22 +44,22 @@ function getClickedElementIdentifier() {
} }
function isNullOrEmpty(s: string) { function isNullOrEmpty(s: string) {
return s == null || s === ''; return s == null || s === "";
} }
// We only have access to the element that's been clicked when the context menu is first opened. // We only have access to the element that's been clicked when the context menu is first opened.
// Remember it for use later. // Remember it for use later.
document.addEventListener('contextmenu', event => { document.addEventListener("contextmenu", (event) => {
clickedEl = event.target as HTMLElement; clickedEl = event.target as HTMLElement;
}); });
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked. // Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
chrome.runtime.onMessage.addListener(event => { chrome.runtime.onMessage.addListener((event) => {
if (event.command === 'getClickedElement') { if (event.command === "getClickedElement") {
const identifier = getClickedElementIdentifier(); const identifier = getClickedElementIdentifier();
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
command: 'getClickedElementResponse', command: "getClickedElementResponse",
sender: 'contextMenuHandler', sender: "contextMenuHandler",
identifier: identifier, identifier: identifier,
}); });
} }

View File

@@ -1,8 +1,9 @@
window.addEventListener('message', event => { window.addEventListener(
if (event.source !== window) "message",
return; (event) => {
if (event.source !== window) return;
if (event.data.command && (event.data.command === 'authResult')) { if (event.data.command && event.data.command === "authResult") {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
command: event.data.command, command: event.data.command,
code: event.data.code, code: event.data.code,
@@ -11,7 +12,7 @@ window.addEventListener('message', event => {
}); });
} }
if (event.data.command && (event.data.command === 'webAuthnResult')) { if (event.data.command && event.data.command === "webAuthnResult") {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
command: event.data.command, command: event.data.command,
data: event.data.data, data: event.data.data,
@@ -19,11 +20,17 @@ window.addEventListener('message', event => {
referrer: event.source.location.hostname, referrer: event.source.location.hostname,
}); });
} }
}, false); },
false
);
const forwardCommands = ['promptForLogin', 'addToLockedVaultPendingNotifications', 'unlockCompleted']; const forwardCommands = [
"promptForLogin",
"addToLockedVaultPendingNotifications",
"unlockCompleted",
];
chrome.runtime.onMessage.addListener(event => { chrome.runtime.onMessage.addListener((event) => {
if (forwardCommands.includes(event.command)) { if (forwardCommands.includes(event.command)) {
chrome.runtime.sendMessage(event); chrome.runtime.sendMessage(event);
} }

View File

@@ -1,8 +1,8 @@
import AddLoginRuntimeMessage from 'src/background/models/addLoginRuntimeMessage'; import AddLoginRuntimeMessage from "src/background/models/addLoginRuntimeMessage";
import ChangePasswordRuntimeMessage from 'src/background/models/changePasswordRuntimeMessage'; import ChangePasswordRuntimeMessage from "src/background/models/changePasswordRuntimeMessage";
document.addEventListener('DOMContentLoaded', event => { document.addEventListener("DOMContentLoaded", (event) => {
if (window.location.hostname.indexOf('vault.bitwarden.com') > -1) { if (window.location.hostname.indexOf("vault.bitwarden.com") > -1) {
return; return;
} }
@@ -11,29 +11,55 @@ document.addEventListener('DOMContentLoaded', event => {
let barType: string = null; let barType: string = null;
let pageHref: string = null; let pageHref: string = null;
let observer: MutationObserver = null; let observer: MutationObserver = null;
const observeIgnoredElements = new Set(['a', 'i', 'b', 'strong', 'span', 'code', 'br', 'img', 'small', 'em', 'hr']); const observeIgnoredElements = new Set([
"a",
"i",
"b",
"strong",
"span",
"code",
"br",
"img",
"small",
"em",
"hr",
]);
let domObservationCollectTimeout: number = null; let domObservationCollectTimeout: number = null;
let collectIfNeededTimeout: number = null; let collectIfNeededTimeout: number = null;
let observeDomTimeout: number = null; let observeDomTimeout: number = null;
const inIframe = isInIframe(); const inIframe = isInIframe();
const cancelButtonNames = new Set(['cancel', 'close', 'back']); const cancelButtonNames = new Set(["cancel", "close", "back"]);
const logInButtonNames = new Set(['log in', 'sign in', 'login', 'go', 'submit', 'continue', 'next']); const logInButtonNames = new Set([
const changePasswordButtonNames = new Set(['save password', 'update password', 'change password', 'change']); "log in",
const changePasswordButtonContainsNames = new Set(['pass', 'change', 'contras', 'senha']); "sign in",
"login",
"go",
"submit",
"continue",
"next",
]);
const changePasswordButtonNames = new Set([
"save password",
"update password",
"change password",
"change",
]);
const changePasswordButtonContainsNames = new Set(["pass", "change", "contras", "senha"]);
let disabledAddLoginNotification = false; let disabledAddLoginNotification = false;
let disabledChangedPasswordNotification = false; let disabledChangedPasswordNotification = false;
chrome.storage.local.get('neverDomains', (ndObj: any) => { chrome.storage.local.get("neverDomains", (ndObj: any) => {
const domains = ndObj.neverDomains; const domains = ndObj.neverDomains;
if (domains != null && domains.hasOwnProperty(window.location.hostname)) { if (domains != null && domains.hasOwnProperty(window.location.hostname)) {
return; return;
} }
chrome.storage.local.get('disableAddLoginNotification', (disAddObj: any) => { chrome.storage.local.get("disableAddLoginNotification", (disAddObj: any) => {
disabledAddLoginNotification = disAddObj != null && disAddObj.disableAddLoginNotification === true; disabledAddLoginNotification =
chrome.storage.local.get('disableChangedPasswordNotification', (disChangedObj: any) => { disAddObj != null && disAddObj.disableAddLoginNotification === true;
disabledChangedPasswordNotification = disChangedObj != null && chrome.storage.local.get("disableChangedPasswordNotification", (disChangedObj: any) => {
disChangedObj.disableChangedPasswordNotification === true; disabledChangedPasswordNotification =
disChangedObj != null && disChangedObj.disableChangedPasswordNotification === true;
if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) { if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) {
collectIfNeededWithTimeout(); collectIfNeededWithTimeout();
} }
@@ -46,28 +72,28 @@ document.addEventListener('DOMContentLoaded', event => {
}); });
function processMessages(msg: any, sendResponse: Function) { function processMessages(msg: any, sendResponse: Function) {
if (msg.command === 'openNotificationBar') { if (msg.command === "openNotificationBar") {
if (inIframe) { if (inIframe) {
return; return;
} }
closeExistingAndOpenBar(msg.data.type, msg.data.typeData); closeExistingAndOpenBar(msg.data.type, msg.data.typeData);
sendResponse(); sendResponse();
return true; return true;
} else if (msg.command === 'closeNotificationBar') { } else if (msg.command === "closeNotificationBar") {
if (inIframe) { if (inIframe) {
return; return;
} }
closeBar(true); closeBar(true);
sendResponse(); sendResponse();
return true; return true;
} else if (msg.command === 'adjustNotificationBar') { } else if (msg.command === "adjustNotificationBar") {
if (inIframe) { if (inIframe) {
return; return;
} }
adjustBar(msg.data); adjustBar(msg.data);
sendResponse(); sendResponse();
return true; return true;
} else if (msg.command === 'notificationBarPageDetails') { } else if (msg.command === "notificationBarPageDetails") {
pageDetails.push(msg.data.details); pageDetails.push(msg.data.details);
watchForms(msg.data.forms); watchForms(msg.data.forms);
sendResponse(); sendResponse();
@@ -84,9 +110,9 @@ document.addEventListener('DOMContentLoaded', event => {
} }
function observeDom() { function observeDom() {
const bodies = document.querySelectorAll('body'); const bodies = document.querySelectorAll("body");
if (bodies && bodies.length > 0) { if (bodies && bodies.length > 0) {
observer = new MutationObserver(mutations => { observer = new MutationObserver((mutations) => {
if (mutations == null || mutations.length === 0 || pageHref !== window.location.href) { if (mutations == null || mutations.length === 0 || pageHref !== window.location.href) {
return; return;
} }
@@ -105,18 +131,23 @@ document.addEventListener('DOMContentLoaded', event => {
} }
const tagName = addedNode.tagName != null ? addedNode.tagName.toLowerCase() : null; const tagName = addedNode.tagName != null ? addedNode.tagName.toLowerCase() : null;
if (tagName != null && tagName === 'form' && if (
(addedNode.dataset == null || !addedNode.dataset.bitwardenWatching)) { tagName != null &&
tagName === "form" &&
(addedNode.dataset == null || !addedNode.dataset.bitwardenWatching)
) {
doCollect = true; doCollect = true;
break; break;
} }
if ((tagName != null && observeIgnoredElements.has(tagName)) || if (
addedNode.querySelectorAll == null) { (tagName != null && observeIgnoredElements.has(tagName)) ||
addedNode.querySelectorAll == null
) {
continue; continue;
} }
const forms = addedNode.querySelectorAll('form:not([data-bitwarden-watching])'); const forms = addedNode.querySelectorAll("form:not([data-bitwarden-watching])");
if (forms != null && forms.length > 0) { if (forms != null && forms.length > 0) {
doCollect = true; doCollect = true;
break; break;
@@ -172,8 +203,8 @@ document.addEventListener('DOMContentLoaded', event => {
function collect() { function collect() {
sendPlatformMessage({ sendPlatformMessage({
command: 'bgCollectPageDetails', command: "bgCollectPageDetails",
sender: 'notificationBar', sender: "notificationBar",
}); });
} }
@@ -185,16 +216,16 @@ document.addEventListener('DOMContentLoaded', event => {
forms.forEach((f: any) => { forms.forEach((f: any) => {
const formId: string = f.form != null ? f.form.htmlID : null; const formId: string = f.form != null ? f.form.htmlID : null;
let formEl: HTMLFormElement = null; let formEl: HTMLFormElement = null;
if (formId != null && formId !== '') { if (formId != null && formId !== "") {
formEl = document.getElementById(formId) as HTMLFormElement; formEl = document.getElementById(formId) as HTMLFormElement;
} }
if (formEl == null) { if (formEl == null) {
const index = parseInt(f.form.opid.split('__')[2], null); const index = parseInt(f.form.opid.split("__")[2], null);
formEl = document.getElementsByTagName('form')[index]; formEl = document.getElementsByTagName("form")[index];
} }
if (formEl != null && formEl.dataset.bitwardenWatching !== '1') { if (formEl != null && formEl.dataset.bitwardenWatching !== "1") {
const formDataObj: any = { const formDataObj: any = {
data: f, data: f,
formEl: formEl, formEl: formEl,
@@ -205,26 +236,31 @@ document.addEventListener('DOMContentLoaded', event => {
locateFields(formDataObj); locateFields(formDataObj);
formData.push(formDataObj); formData.push(formDataObj);
listen(formEl); listen(formEl);
formEl.dataset.bitwardenWatching = '1'; formEl.dataset.bitwardenWatching = "1";
} }
}); });
} }
function listen(form: HTMLFormElement) { function listen(form: HTMLFormElement) {
form.removeEventListener('submit', formSubmitted, false); form.removeEventListener("submit", formSubmitted, false);
form.addEventListener('submit', formSubmitted, false); form.addEventListener("submit", formSubmitted, false);
const submitButton = getSubmitButton(form, logInButtonNames); const submitButton = getSubmitButton(form, logInButtonNames);
if (submitButton != null) { if (submitButton != null) {
submitButton.removeEventListener('click', formSubmitted, false); submitButton.removeEventListener("click", formSubmitted, false);
submitButton.addEventListener('click', formSubmitted, false); submitButton.addEventListener("click", formSubmitted, false);
} }
} }
function locateFields(formDataObj: any) { function locateFields(formDataObj: any) {
const inputs = Array.from(document.getElementsByTagName('input')); const inputs = Array.from(document.getElementsByTagName("input"));
formDataObj.usernameEl = locateField(formDataObj.formEl, formDataObj.data.username, inputs); formDataObj.usernameEl = locateField(formDataObj.formEl, formDataObj.data.username, inputs);
if (formDataObj.usernameEl != null && formDataObj.data.password != null) { if (formDataObj.usernameEl != null && formDataObj.data.password != null) {
formDataObj.passwordEl = locatePassword(formDataObj.formEl, formDataObj.data.password, inputs, true); formDataObj.passwordEl = locatePassword(
formDataObj.formEl,
formDataObj.data.password,
inputs,
true
);
} else if (formDataObj.data.passwords != null) { } else if (formDataObj.data.passwords != null) {
formDataObj.passwordEls = []; formDataObj.passwordEls = [];
formDataObj.data.passwords.forEach((pData: any) => { formDataObj.data.passwords.forEach((pData: any) => {
@@ -239,10 +275,14 @@ document.addEventListener('DOMContentLoaded', event => {
} }
} }
function locatePassword(form: HTMLFormElement, passwordData: any, inputs: HTMLInputElement[], function locatePassword(
doLastFallback: boolean) { form: HTMLFormElement,
passwordData: any,
inputs: HTMLInputElement[],
doLastFallback: boolean
) {
let el = locateField(form, passwordData, inputs); let el = locateField(form, passwordData, inputs);
if (el != null && el.type !== 'password') { if (el != null && el.type !== "password") {
el = null; el = null;
} }
if (doLastFallback && el == null) { if (doLastFallback && el == null) {
@@ -256,14 +296,14 @@ document.addEventListener('DOMContentLoaded', event => {
return; return;
} }
let el: HTMLInputElement = null; let el: HTMLInputElement = null;
if (fieldData.htmlID != null && fieldData.htmlID !== '') { if (fieldData.htmlID != null && fieldData.htmlID !== "") {
try { try {
el = form.querySelector('#' + fieldData.htmlID); el = form.querySelector("#" + fieldData.htmlID);
} catch { } catch {
// Ignore error, we perform fallbacks below. // Ignore error, we perform fallbacks below.
} }
} }
if (el == null && fieldData.htmlName != null && fieldData.htmlName !== '') { if (el == null && fieldData.htmlName != null && fieldData.htmlName !== "") {
el = form.querySelector('input[name="' + fieldData.htmlName + '"]'); el = form.querySelector('input[name="' + fieldData.htmlName + '"]');
} }
if (el == null && fieldData.elementNumber != null) { if (el == null && fieldData.elementNumber != null) {
@@ -274,12 +314,12 @@ document.addEventListener('DOMContentLoaded', event => {
function formSubmitted(e: Event) { function formSubmitted(e: Event) {
let form: HTMLFormElement = null; let form: HTMLFormElement = null;
if (e.type === 'click') { if (e.type === "click") {
form = (e.target as HTMLElement).closest('form'); form = (e.target as HTMLElement).closest("form");
if (form == null) { if (form == null) {
const parentModal = (e.target as HTMLElement).closest('div.modal'); const parentModal = (e.target as HTMLElement).closest("div.modal");
if (parentModal != null) { if (parentModal != null) {
const modalForms = parentModal.querySelectorAll('form'); const modalForms = parentModal.querySelectorAll("form");
if (modalForms.length === 1) { if (modalForms.length === 1) {
form = modalForms[0]; form = modalForms[0];
} }
@@ -289,7 +329,7 @@ document.addEventListener('DOMContentLoaded', event => {
form = e.target as HTMLFormElement; form = e.target as HTMLFormElement;
} }
if (form == null || form.dataset.bitwardenProcessed === '1') { if (form == null || form.dataset.bitwardenProcessed === "1") {
return; return;
} }
@@ -305,11 +345,15 @@ document.addEventListener('DOMContentLoaded', event => {
url: document.URL, url: document.URL,
}; };
if (login.username != null && login.username !== '' && if (
login.password != null && login.password !== '') { login.username != null &&
login.username !== "" &&
login.password != null &&
login.password !== ""
) {
processedForm(form); processedForm(form);
sendPlatformMessage({ sendPlatformMessage({
command: 'bgAddLogin', command: "bgAddLogin",
login: login, login: login,
}); });
break; break;
@@ -317,7 +361,7 @@ document.addEventListener('DOMContentLoaded', event => {
} }
if (!disabledChangedPasswordNotification && formData[i].passwordEls != null) { if (!disabledChangedPasswordNotification && formData[i].passwordEls != null) {
const passwords: string[] = formData[i].passwordEls const passwords: string[] = formData[i].passwordEls
.filter((el: HTMLInputElement) => el.value != null && el.value !== '') .filter((el: HTMLInputElement) => el.value != null && el.value !== "")
.map((el: HTMLInputElement) => el.value); .map((el: HTMLInputElement) => el.value);
let curPass: string = null; let curPass: string = null;
@@ -337,8 +381,9 @@ document.addEventListener('DOMContentLoaded', event => {
curPass = null; curPass = null;
} else { } else {
const buttonText = getButtonText(getSubmitButton(form, changePasswordButtonNames)); const buttonText = getButtonText(getSubmitButton(form, changePasswordButtonNames));
const matches = Array.from(changePasswordButtonContainsNames) const matches = Array.from(changePasswordButtonContainsNames).filter(
.filter(n => buttonText.indexOf(n) > -1); (n) => buttonText.indexOf(n) > -1
);
if (matches.length > 0) { if (matches.length > 0) {
curPass = passwords[0]; curPass = passwords[0];
newPass = passwords[1]; newPass = passwords[1];
@@ -346,7 +391,7 @@ document.addEventListener('DOMContentLoaded', event => {
} }
} }
if (newPass != null && curPass != null || (newPassOnly && newPass != null)) { if ((newPass != null && curPass != null) || (newPassOnly && newPass != null)) {
processedForm(form); processedForm(form);
const changePasswordRuntimeMessage: ChangePasswordRuntimeMessage = { const changePasswordRuntimeMessage: ChangePasswordRuntimeMessage = {
@@ -355,7 +400,7 @@ document.addEventListener('DOMContentLoaded', event => {
url: document.URL, url: document.URL,
}; };
sendPlatformMessage({ sendPlatformMessage({
command: 'bgChangedPassword', command: "bgChangedPassword",
data: changePasswordRuntimeMessage, data: changePasswordRuntimeMessage,
}); });
break; break;
@@ -369,12 +414,13 @@ document.addEventListener('DOMContentLoaded', event => {
return null; return null;
} }
const wrappingElIsForm = wrappingEl.tagName.toLowerCase() === 'form'; const wrappingElIsForm = wrappingEl.tagName.toLowerCase() === "form";
let submitButton = wrappingEl.querySelector('input[type="submit"], input[type="image"], ' + let submitButton = wrappingEl.querySelector(
'button[type="submit"]') as HTMLElement; 'input[type="submit"], input[type="image"], ' + 'button[type="submit"]'
) as HTMLElement;
if (submitButton == null && wrappingElIsForm) { if (submitButton == null && wrappingElIsForm) {
submitButton = wrappingEl.querySelector('button:not([type])'); submitButton = wrappingEl.querySelector("button:not([type])");
if (submitButton != null) { if (submitButton != null) {
const buttonText = getButtonText(submitButton); const buttonText = getButtonText(submitButton);
if (buttonText != null && cancelButtonNames.has(buttonText.trim().toLowerCase())) { if (buttonText != null && cancelButtonNames.has(buttonText.trim().toLowerCase())) {
@@ -383,18 +429,24 @@ document.addEventListener('DOMContentLoaded', event => {
} }
} }
if (submitButton == null) { if (submitButton == null) {
const possibleSubmitButtons = Array.from(wrappingEl.querySelectorAll('a, span, button[type="button"], ' + const possibleSubmitButtons = Array.from(
'input[type="button"], button:not([type])')) as HTMLElement[]; wrappingEl.querySelectorAll(
'a, span, button[type="button"], ' + 'input[type="button"], button:not([type])'
)
) as HTMLElement[];
let typelessButton: HTMLElement = null; let typelessButton: HTMLElement = null;
possibleSubmitButtons.forEach(button => { possibleSubmitButtons.forEach((button) => {
if (submitButton != null || button == null || button.tagName == null) { if (submitButton != null || button == null || button.tagName == null) {
return; return;
} }
const buttonText = getButtonText(button); const buttonText = getButtonText(button);
if (buttonText != null) { if (buttonText != null) {
if (typelessButton != null && button.tagName.toLowerCase() === 'button' && if (
button.getAttribute('type') == null && typelessButton != null &&
!cancelButtonNames.has(buttonText.trim().toLowerCase())) { button.tagName.toLowerCase() === "button" &&
button.getAttribute("type") == null &&
!cancelButtonNames.has(buttonText.trim().toLowerCase())
) {
typelessButton = button; typelessButton = button;
} else if (buttonNames.has(buttonText.trim().toLowerCase())) { } else if (buttonNames.has(buttonText.trim().toLowerCase())) {
submitButton = button; submitButton = button;
@@ -407,9 +459,9 @@ document.addEventListener('DOMContentLoaded', event => {
} }
if (submitButton == null && wrappingElIsForm) { if (submitButton == null && wrappingElIsForm) {
// Maybe it's in a modal? // Maybe it's in a modal?
const parentModal = wrappingEl.closest('div.modal') as HTMLElement; const parentModal = wrappingEl.closest("div.modal") as HTMLElement;
if (parentModal != null) { if (parentModal != null) {
const modalForms = parentModal.querySelectorAll('form'); const modalForms = parentModal.querySelectorAll("form");
if (modalForms.length === 1) { if (modalForms.length === 1) {
submitButton = getSubmitButton(parentModal, buttonNames); submitButton = getSubmitButton(parentModal, buttonNames);
} }
@@ -420,7 +472,7 @@ document.addEventListener('DOMContentLoaded', event => {
function getButtonText(button: HTMLElement) { function getButtonText(button: HTMLElement) {
let buttonText: string = null; let buttonText: string = null;
if (button.tagName.toLowerCase() === 'input') { if (button.tagName.toLowerCase() === "input") {
buttonText = (button as HTMLInputElement).value; buttonText = (button as HTMLInputElement).value;
} else { } else {
buttonText = button.innerText; buttonText = button.innerText;
@@ -429,26 +481,26 @@ document.addEventListener('DOMContentLoaded', event => {
} }
function processedForm(form: HTMLFormElement) { function processedForm(form: HTMLFormElement) {
form.dataset.bitwardenProcessed = '1'; form.dataset.bitwardenProcessed = "1";
window.setTimeout(() => { window.setTimeout(() => {
form.dataset.bitwardenProcessed = '0'; form.dataset.bitwardenProcessed = "0";
}, 500); }, 500);
} }
function closeExistingAndOpenBar(type: string, typeData: any) { function closeExistingAndOpenBar(type: string, typeData: any) {
let barPage = 'notification/bar.html'; let barPage = "notification/bar.html";
switch (type) { switch (type) {
case 'add': case "add":
barPage = barPage + '?add=1&isVaultLocked=' + typeData.isVaultLocked; barPage = barPage + "?add=1&isVaultLocked=" + typeData.isVaultLocked;
break; break;
case 'change': case "change":
barPage = barPage + '?change=1&isVaultLocked=' + typeData.isVaultLocked; barPage = barPage + "?change=1&isVaultLocked=" + typeData.isVaultLocked;
break; break;
default: default:
break; break;
} }
const frame = document.getElementById('bit-notification-bar-iframe') as HTMLIFrameElement; const frame = document.getElementById("bit-notification-bar-iframe") as HTMLIFrameElement;
if (frame != null && frame.src.indexOf(barPage) >= 0) { if (frame != null && frame.src.indexOf(barPage) >= 0) {
return; return;
} }
@@ -466,34 +518,35 @@ document.addEventListener('DOMContentLoaded', event => {
const barPageUrl: string = chrome.extension.getURL(barPage); const barPageUrl: string = chrome.extension.getURL(barPage);
const iframe = document.createElement('iframe'); const iframe = document.createElement("iframe");
iframe.style.cssText = 'height: 42px; width: 100%; border: 0; min-height: initial;'; iframe.style.cssText = "height: 42px; width: 100%; border: 0; min-height: initial;";
iframe.id = 'bit-notification-bar-iframe'; iframe.id = "bit-notification-bar-iframe";
iframe.src = barPageUrl; iframe.src = barPageUrl;
const frameDiv = document.createElement('div'); const frameDiv = document.createElement("div");
frameDiv.setAttribute('aria-live', 'polite'); frameDiv.setAttribute("aria-live", "polite");
frameDiv.id = 'bit-notification-bar'; frameDiv.id = "bit-notification-bar";
frameDiv.style.cssText = 'height: 42px; width: 100%; top: 0; left: 0; padding: 0; position: fixed; ' + frameDiv.style.cssText =
'z-index: 2147483647; visibility: visible;'; "height: 42px; width: 100%; top: 0; left: 0; padding: 0; position: fixed; " +
"z-index: 2147483647; visibility: visible;";
frameDiv.appendChild(iframe); frameDiv.appendChild(iframe);
document.body.appendChild(frameDiv); document.body.appendChild(frameDiv);
(iframe.contentWindow.location as any) = barPageUrl; (iframe.contentWindow.location as any) = barPageUrl;
const spacer = document.createElement('div'); const spacer = document.createElement("div");
spacer.id = 'bit-notification-bar-spacer'; spacer.id = "bit-notification-bar-spacer";
spacer.style.cssText = 'height: 42px;'; spacer.style.cssText = "height: 42px;";
document.body.insertBefore(spacer, document.body.firstChild); document.body.insertBefore(spacer, document.body.firstChild);
} }
function closeBar(explicitClose: boolean) { function closeBar(explicitClose: boolean) {
const barEl = document.getElementById('bit-notification-bar'); const barEl = document.getElementById("bit-notification-bar");
if (barEl != null) { if (barEl != null) {
barEl.parentElement.removeChild(barEl); barEl.parentElement.removeChild(barEl);
} }
const spacerEl = document.getElementById('bit-notification-bar-spacer'); const spacerEl = document.getElementById("bit-notification-bar-spacer");
if (spacerEl) { if (spacerEl) {
spacerEl.parentElement.removeChild(spacerEl); spacerEl.parentElement.removeChild(spacerEl);
} }
@@ -503,14 +556,14 @@ document.addEventListener('DOMContentLoaded', event => {
} }
switch (barType) { switch (barType) {
case 'add': case "add":
sendPlatformMessage({ sendPlatformMessage({
command: 'bgAddClose', command: "bgAddClose",
}); });
break; break;
case 'change': case "change":
sendPlatformMessage({ sendPlatformMessage({
command: 'bgChangeClose', command: "bgChangeClose",
}); });
break; break;
default: default:
@@ -520,10 +573,10 @@ document.addEventListener('DOMContentLoaded', event => {
function adjustBar(data: any) { function adjustBar(data: any) {
if (data != null && data.height !== 42) { if (data != null && data.height !== 42) {
const newHeight = data.height + 'px'; const newHeight = data.height + "px";
doHeightAdjustment('bit-notification-bar-iframe', newHeight); doHeightAdjustment("bit-notification-bar-iframe", newHeight);
doHeightAdjustment('bit-notification-bar', newHeight); doHeightAdjustment("bit-notification-bar", newHeight);
doHeightAdjustment('bit-notification-bar-spacer', newHeight); doHeightAdjustment("bit-notification-bar-spacer", newHeight);
} }
} }

View File

@@ -1,9 +1,11 @@
import * as Mousetrap from 'mousetrap'; import * as Mousetrap from "mousetrap";
document.addEventListener('DOMContentLoaded', event => { document.addEventListener("DOMContentLoaded", (event) => {
const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && const isSafari =
navigator.userAgent.indexOf('Chrome') === -1; typeof safari !== "undefined" &&
const isVivaldi = !isSafari && navigator.userAgent.indexOf(' Vivaldi/') !== -1; navigator.userAgent.indexOf(" Safari/") !== -1 &&
navigator.userAgent.indexOf("Chrome") === -1;
const isVivaldi = !isSafari && navigator.userAgent.indexOf(" Vivaldi/") !== -1;
if (!isSafari && !isVivaldi) { if (!isSafari && !isVivaldi) {
return; return;
@@ -17,31 +19,31 @@ document.addEventListener('DOMContentLoaded', event => {
return false; return false;
}; };
let autofillCommand = ['mod+shift+l']; let autofillCommand = ["mod+shift+l"];
if (isSafari) { if (isSafari) {
autofillCommand = ['mod+\\', 'mod+8', 'mod+shift+p']; autofillCommand = ["mod+\\", "mod+8", "mod+shift+p"];
} }
Mousetrap.bind(autofillCommand, () => { Mousetrap.bind(autofillCommand, () => {
sendMessage('autofill_login'); sendMessage("autofill_login");
}); });
if (isSafari) { if (isSafari) {
Mousetrap.bind('mod+shift+y', () => { Mousetrap.bind("mod+shift+y", () => {
sendMessage('open_popup'); sendMessage("open_popup");
}); });
Mousetrap.bind('mod+shift+s', () => { Mousetrap.bind("mod+shift+s", () => {
sendMessage('lock_vault'); sendMessage("lock_vault");
}); });
} else { } else {
Mousetrap.bind('mod+shift+9', () => { Mousetrap.bind("mod+shift+9", () => {
sendMessage('generate_password'); sendMessage("generate_password");
}); });
} }
function sendMessage(shortcut: string) { function sendMessage(shortcut: string) {
const msg: any = { const msg: any = {
command: 'keyboardShortcutTriggered', command: "keyboardShortcutTriggered",
shortcut: shortcut, shortcut: shortcut,
}; };

View File

@@ -23,47 +23,25 @@
"content/notificationBar.js", "content/notificationBar.js",
"content/contextMenuHandler.js" "content/contextMenuHandler.js"
], ],
"matches": [ "matches": ["http://*/*", "https://*/*", "file:///*"],
"http://*/*",
"https://*/*",
"file:///*"
],
"run_at": "document_start" "run_at": "document_start"
}, },
{ {
"all_frames": false, "all_frames": false,
"js": [ "js": ["content/shortcuts.js"],
"content/shortcuts.js" "matches": ["http://*/*", "https://*/*", "file:///*"],
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"run_at": "document_start" "run_at": "document_start"
}, },
{ {
"all_frames": false, "all_frames": false,
"js": [ "js": ["content/message_handler.js"],
"content/message_handler.js" "matches": ["http://*/*", "https://*/*", "file:///*"],
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"run_at": "document_start" "run_at": "document_start"
}, },
{ {
"all_frames": true, "all_frames": true,
"css": [ "css": ["content/autofill.css"],
"content/autofill.css" "matches": ["http://*/*", "https://*/*", "file:///*"],
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"run_at": "document_end" "run_at": "document_end"
} }
], ],
@@ -92,9 +70,7 @@
"webRequest", "webRequest",
"webRequestBlocking" "webRequestBlocking"
], ],
"optional_permissions": [ "optional_permissions": ["nativeMessaging"],
"nativeMessaging"
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"commands": { "commands": {
"_execute_browser_action": { "_execute_browser_action": {

View File

@@ -6,11 +6,11 @@ export default class AutofillField {
htmlID: string; htmlID: string;
htmlName: string; htmlName: string;
htmlClass: string; htmlClass: string;
'label-left': string; "label-left": string;
'label-right': string; "label-right": string;
'label-top': string; "label-top": string;
'label-tag': string; "label-tag": string;
'label-aria': string; "label-aria": string;
placeholder: string; placeholder: string;
type: string; type: string;
value: string; value: string;

View File

@@ -1,5 +1,5 @@
import AutofillField from './autofillField'; import AutofillField from "./autofillField";
import AutofillForm from './autofillForm'; import AutofillForm from "./autofillForm";
export default class AutofillPageDetails { export default class AutofillPageDetails {
documentUUID: string; documentUUID: string;
@@ -7,7 +7,7 @@ export default class AutofillPageDetails {
url: string; url: string;
documentUrl: string; documentUrl: string;
tabUrl: string; tabUrl: string;
forms: { [id: string]: AutofillForm; }; forms: { [id: string]: AutofillForm };
fields: AutofillField[]; fields: AutofillField[];
collectedTimestamp: number; collectedTimestamp: number;
} }

View File

@@ -1,12 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head>
<title>Bitwarden</title> <title>Bitwarden</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body> <body>
<div class="outer-wrapper"> <div class="outer-wrapper">
<div class="logo"> <div class="logo">
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link"> <a href="https://vault.bitwarden.com" target="_blank" id="logo-link">
@@ -20,7 +19,7 @@
</button> </button>
</div> </div>
</div> </div>
<div id="templates" style="display: none;"> <div id="templates" style="display: none">
<div class="inner-wrapper" id="template-add"> <div class="inner-wrapper" id="template-add">
<div class="add-text"></div> <div class="add-text"></div>
<div class="add-buttons"> <div class="add-buttons">
@@ -36,6 +35,5 @@
</div> </div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,108 +1,109 @@
require('./bar.scss'); require("./bar.scss");
document.addEventListener('DOMContentLoaded', () => { document.addEventListener("DOMContentLoaded", () => {
var i18n = {}; var i18n = {};
var lang = window.navigator.language; var lang = window.navigator.language;
i18n.appName = chrome.i18n.getMessage('appName'); i18n.appName = chrome.i18n.getMessage("appName");
i18n.close = chrome.i18n.getMessage('close'); i18n.close = chrome.i18n.getMessage("close");
i18n.never = chrome.i18n.getMessage('never'); i18n.never = chrome.i18n.getMessage("never");
i18n.folder = chrome.i18n.getMessage('folder'); i18n.folder = chrome.i18n.getMessage("folder");
i18n.notificationAddSave = chrome.i18n.getMessage('notificationAddSave'); i18n.notificationAddSave = chrome.i18n.getMessage("notificationAddSave");
i18n.notificationAddDesc = chrome.i18n.getMessage('notificationAddDesc'); i18n.notificationAddDesc = chrome.i18n.getMessage("notificationAddDesc");
i18n.notificationChangeSave = chrome.i18n.getMessage('notificationChangeSave'); i18n.notificationChangeSave = chrome.i18n.getMessage("notificationChangeSave");
i18n.notificationChangeDesc = chrome.i18n.getMessage('notificationChangeDesc'); i18n.notificationChangeDesc = chrome.i18n.getMessage("notificationChangeDesc");
lang = chrome.i18n.getUILanguage(); lang = chrome.i18n.getUILanguage();
// delay 50ms so that we get proper body dimensions // delay 50ms so that we get proper body dimensions
setTimeout(load, 50); setTimeout(load, 50);
function load() { function load() {
const isVaultLocked = getQueryVariable('isVaultLocked') == 'true'; const isVaultLocked = getQueryVariable("isVaultLocked") == "true";
document.getElementById('logo').src = isVaultLocked document.getElementById("logo").src = isVaultLocked
? chrome.runtime.getURL('images/icon38_locked.png') ? chrome.runtime.getURL("images/icon38_locked.png")
: chrome.runtime.getURL('images/icon38.png'); : chrome.runtime.getURL("images/icon38.png");
document.getElementById('logo-link').title = i18n.appName; document.getElementById("logo-link").title = i18n.appName;
var neverButton = document.querySelector('#template-add .never-save'); var neverButton = document.querySelector("#template-add .never-save");
neverButton.textContent = i18n.never; neverButton.textContent = i18n.never;
var selectFolder = document.querySelector('#template-add .select-folder'); var selectFolder = document.querySelector("#template-add .select-folder");
selectFolder.setAttribute('aria-label', i18n.folder); selectFolder.setAttribute("aria-label", i18n.folder);
selectFolder.setAttribute('isVaultLocked', isVaultLocked.toString()); selectFolder.setAttribute("isVaultLocked", isVaultLocked.toString());
var addButton = document.querySelector('#template-add .add-save'); var addButton = document.querySelector("#template-add .add-save");
addButton.textContent = i18n.notificationAddSave; addButton.textContent = i18n.notificationAddSave;
var changeButton = document.querySelector('#template-change .change-save'); var changeButton = document.querySelector("#template-change .change-save");
changeButton.textContent = i18n.notificationChangeSave; changeButton.textContent = i18n.notificationChangeSave;
var closeIcon = document.getElementById('close'); var closeIcon = document.getElementById("close");
closeIcon.src = chrome.runtime.getURL('images/close.png'); closeIcon.src = chrome.runtime.getURL("images/close.png");
closeIcon.alt = i18n.close; closeIcon.alt = i18n.close;
var closeButton = document.getElementById('close-button') var closeButton = document.getElementById("close-button");
closeButton.title = i18n.close; closeButton.title = i18n.close;
closeButton.setAttribute('aria-label', i18n.close); closeButton.setAttribute("aria-label", i18n.close);
document.querySelector('#template-add .add-text').textContent = i18n.notificationAddDesc; document.querySelector("#template-add .add-text").textContent = i18n.notificationAddDesc;
document.querySelector('#template-change .change-text').textContent = i18n.notificationChangeDesc; document.querySelector("#template-change .change-text").textContent =
i18n.notificationChangeDesc;
if (getQueryVariable('add')) { if (getQueryVariable("add")) {
setContent(document.getElementById('template-add')); setContent(document.getElementById("template-add"));
var addButton = document.querySelector('#template-add-clone .add-save'), var addButton = document.querySelector("#template-add-clone .add-save"),
neverButton = document.querySelector('#template-add-clone .never-save'); neverButton = document.querySelector("#template-add-clone .never-save");
addButton.addEventListener('click', (e) => { addButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
const folderId = document.querySelector('#template-add-clone .select-folder').value; const folderId = document.querySelector("#template-add-clone .select-folder").value;
const bgAddSaveMessage = { const bgAddSaveMessage = {
command: 'bgAddSave', command: "bgAddSave",
folder: folderId, folder: folderId,
}; };
sendPlatformMessage(bgAddSaveMessage); sendPlatformMessage(bgAddSaveMessage);
}); });
neverButton.addEventListener('click', (e) => { neverButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
sendPlatformMessage({ sendPlatformMessage({
command: 'bgNeverSave' command: "bgNeverSave",
}); });
}); });
if (!isVaultLocked) { if (!isVaultLocked) {
const responseFoldersCommand = 'notificationBarGetFoldersList'; const responseFoldersCommand = "notificationBarGetFoldersList";
chrome.runtime.onMessage.addListener((msg) => { chrome.runtime.onMessage.addListener((msg) => {
if (msg.command === responseFoldersCommand && msg.data) { if (msg.command === responseFoldersCommand && msg.data) {
fillSelectorWithFolders(msg.data.folders); fillSelectorWithFolders(msg.data.folders);
} }
}); });
sendPlatformMessage({ sendPlatformMessage({
command: 'bgGetDataForTab', command: "bgGetDataForTab",
responseCommand: responseFoldersCommand responseCommand: responseFoldersCommand,
}); });
} }
} else if (getQueryVariable('change')) { } else if (getQueryVariable("change")) {
setContent(document.getElementById('template-change')); setContent(document.getElementById("template-change"));
var changeButton = document.querySelector('#template-change-clone .change-save'); var changeButton = document.querySelector("#template-change-clone .change-save");
changeButton.addEventListener('click', (e) => { changeButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
const bgChangeSaveMessage = { const bgChangeSaveMessage = {
command: 'bgChangeSave' command: "bgChangeSave",
}; };
sendPlatformMessage(bgChangeSaveMessage); sendPlatformMessage(bgChangeSaveMessage);
}); });
} }
closeButton.addEventListener('click', (e) => { closeButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
sendPlatformMessage({ sendPlatformMessage({
command: 'bgCloseNotificationBar' command: "bgCloseNotificationBar",
}); });
}); });
@@ -112,10 +113,10 @@ document.addEventListener('DOMContentLoaded', () => {
function getQueryVariable(variable) { function getQueryVariable(variable) {
var query = window.location.search.substring(1); var query = window.location.search.substring(1);
var vars = query.split('&'); var vars = query.split("&");
for (var i = 0; i < vars.length; i++) { for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('='); var pair = vars[i].split("=");
if (pair[0] === variable) { if (pair[0] === variable) {
return pair[1]; return pair[1];
} }
@@ -125,13 +126,13 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function setContent(element) { function setContent(element) {
const content = document.getElementById('content'); const content = document.getElementById("content");
while (content.firstChild) { while (content.firstChild) {
content.removeChild(content.firstChild); content.removeChild(content.firstChild);
} }
var newElement = element.cloneNode(true); var newElement = element.cloneNode(true);
newElement.id = newElement.id + '-clone'; newElement.id = newElement.id + "-clone";
content.appendChild(newElement); content.appendChild(newElement);
} }
@@ -140,20 +141,20 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function fillSelectorWithFolders(folders) { function fillSelectorWithFolders(folders) {
const select = document.querySelector('#template-add-clone .select-folder'); const select = document.querySelector("#template-add-clone .select-folder");
select.appendChild(new Option(chrome.i18n.getMessage('selectFolder'), null, true)); select.appendChild(new Option(chrome.i18n.getMessage("selectFolder"), null, true));
folders.forEach((folder) => { folders.forEach((folder) => {
//Select "No Folder" (id=null) folder by default //Select "No Folder" (id=null) folder by default
select.appendChild(new Option(folder.name, folder.id || '', false)); select.appendChild(new Option(folder.name, folder.id || "", false));
}); });
} }
function adjustHeight() { function adjustHeight() {
sendPlatformMessage({ sendPlatformMessage({
command: 'bgAdjustNotificationBar', command: "bgAdjustNotificationBar",
data: { data: {
height: document.querySelector('body').scrollHeight height: document.querySelector("body").scrollHeight,
} },
}); });
} }
}); });

View File

@@ -6,7 +6,7 @@
font-size: 14px; font-size: 14px;
line-height: 16px; line-height: 16px;
color: #333333; color: #333333;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
} }
.outer-wrapper { .outer-wrapper {
@@ -24,7 +24,8 @@
grid-template-columns: auto max-content; grid-template-columns: auto max-content;
} }
.outer-wrapper > *, .inner-wrapper > * { .outer-wrapper > *,
.inner-wrapper > * {
align-self: center; align-self: center;
} }
@@ -50,7 +51,7 @@ img {
button:not(.link), button:not(.link),
button:not(.neutral) { button:not(.neutral) {
background-color: #175DDC; background-color: #175ddc;
padding: 5px 15px; padding: 5px 15px;
border-radius: 3px; border-radius: 3px;
color: #ffffff; color: #ffffff;
@@ -66,7 +67,7 @@ button.link,
button.neutral { button.neutral {
background: none; background: none;
padding: 5px 15px; padding: 5px 15px;
color: #175DDC; color: #175ddc;
border: 0; border: 0;
&:hover { &:hover {
@@ -77,12 +78,12 @@ button.neutral {
} }
.select-folder[isVaultLocked="true"] { .select-folder[isVaultLocked="true"] {
display: none display: none;
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.select-folder { .select-folder {
display: none display: none;
} }
} }
@@ -91,4 +92,3 @@ button.neutral {
display: none; display: none;
} }
} }

View File

@@ -1,14 +1,14 @@
<form #form (ngSubmit)="submit()"> <form #form (ngSubmit)="submit()">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/home">{{'close' | i18n}}</a> <a routerLink="/home">{{ "close" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'appName' | i18n}}</span> <span class="title">{{ "appName" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'save' | i18n}}</span> <span [hidden]="form.loading">{{ "save" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -16,51 +16,88 @@
<content> <content>
<div class="box"> <div class="box">
<h2 class="box-header"> <h2 class="box-header">
{{'selfHostedEnvironment' | i18n}} {{ "selfHostedEnvironment" | i18n }}
</h2> </h2>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label> <label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" <input
placeholder="ex. https://bitwarden.company.com" appInputVerbatim> id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="ex. https://bitwarden.company.com"
appInputVerbatim
/>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}} {{ "selfHostedEnvironmentFooter" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<h2 class="box-header"> <h2 class="box-header">
{{'customEnvironment' | i18n}} {{ "customEnvironment" | i18n }}
</h2> </h2>
<div class="box-content" [hidden]="!showCustom"> <div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label> <label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl" inputmode="url" <input
appInputVerbatim> id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
inputmode="url"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label> <label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" inputmode="url" appInputVerbatim> <input
id="apiUrl"
type="text"
name="ApiUrl"
[(ngModel)]="apiUrl"
inputmode="url"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label> <label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl" inputmode="url" <input
appInputVerbatim> id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
inputmode="url"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label> <label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl" inputmode="url" <input
[(ngModel)]="notificationsUrl" appInputVerbatim> id="notificationsUrl"
type="text"
name="NotificationsUrl"
inputmode="url"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label> <label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl" inputmode="url" <input
appInputVerbatim> id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
inputmode="url"
appInputVerbatim
/>
</div> </div>
</div> </div>
<div class="box-footer" [hidden]="!showCustom"> <div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}} {{ "customEnvironmentFooter" | i18n }}
</div> </div>
</div> </div>
</content> </content>

View File

@@ -1,25 +1,29 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component'; import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
@Component({ @Component({
selector: 'app-environment', selector: "app-environment",
templateUrl: 'environment.component.html', templateUrl: "environment.component.html",
}) })
export class EnvironmentComponent extends BaseEnvironmentComponent { export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, constructor(
i18nService: I18nService, private router: Router) { platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService,
private router: Router
) {
super(platformUtilsService, environmentService, i18nService); super(platformUtilsService, environmentService, i18nService);
this.showCustom = true; this.showCustom = true;
} }
saved() { saved() {
super.saved(); super.saved();
this.router.navigate(['']); this.router.navigate([""]);
} }
} }

View File

@@ -1,14 +1,14 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/login">{{'cancel' | i18n}}</a> <a routerLink="/login">{{ "cancel" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'passwordHint' | i18n}}</span> <span class="title">{{ "passwordHint" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span> <span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -17,13 +17,21 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus <input
inputmode="email" appInputVerbatim="false"> id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'enterEmailToGetHint' | i18n}} {{ "enterEmailToGetHint" | i18n }}
</div> </div>
</div> </div>
</content> </content>

View File

@@ -1,20 +1,25 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
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 { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component'; import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({ @Component({
selector: 'app-hint', selector: "app-hint",
templateUrl: 'hint.component.html', templateUrl: "hint.component.html",
}) })
export class HintComponent extends BaseHintComponent { export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService, apiService: ApiService, logService: LogService) { router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService); super(router, i18nService, apiService, platformUtilsService, logService);
} }
} }

View File

@@ -1,14 +1,16 @@
<div class="center-content"> <div class="center-content">
<div class="content"> <div class="content">
<div class="logo-image"></div> <div class="logo-image"></div>
<p class="lead text-center">{{'loginOrCreateNewAccount' | i18n}}</p> <p class="lead text-center">{{ "loginOrCreateNewAccount" | i18n }}</p>
<a class="btn primary block" routerLink="/login"><b>{{'login' | i18n}}</b></a> <a class="btn primary block" routerLink="/login"
><b>{{ "login" | i18n }}</b></a
>
<button type="button" (click)="launchSsoBrowser()" class="btn block"> <button type="button" (click)="launchSsoBrowser()" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}} <i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</button> </button>
<a class="btn block" routerLink="/register">{{'createAccount' | i18n}}</a> <a class="btn block" routerLink="/register">{{ "createAccount" | i18n }}</a>
</div> </div>
</div> </div>
<a routerLink="/environment" class="settings-icon"> <a routerLink="/environment" class="settings-icon">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span>&nbsp;{{'settings' | i18n}}</span> <i class="fa fa-cog fa-lg" aria-hidden="true"></i><span>&nbsp;{{ "settings" | i18n }}</span>
</a> </a>

View File

@@ -1,28 +1,32 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.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 { 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 { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.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 storageService: StorageService, protected platformUtilsService: PlatformUtilsService,
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { } private passwordGenerationService: PasswordGenerationService,
private storageService: StorageService,
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,
@@ -30,9 +34,11 @@ export class HomeComponent {
special: false, special: false,
}; };
const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser'; const state =
(await this.passwordGenerationService.generatePassword(passwordOptions)) +
":clientId=browser";
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
@@ -40,14 +46,21 @@ export class HomeComponent {
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'; const redirectUri = url + "/sso-connector.html";
// Launch browser // Launch browser
this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' + this.platformUtilsService.launchUri(
'&redirectUri=' + encodeURIComponent(redirectUri) + url +
'&state=' + state + '&codeChallenge=' + codeChallenge); "/#/sso?clientId=browser" +
"&redirectUri=" +
encodeURIComponent(redirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge
);
} }
} }

View File

@@ -2,10 +2,10 @@
<header> <header>
<div class="left"></div> <div class="left"></div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'verifyIdentity' | i18n}}</span> <span class="title">{{ "verifyIdentity" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick *ngIf="!hideInput">{{'unlock' | i18n}}</button> <button type="submit" appBlurClick *ngIf="!hideInput">{{ "unlock" | i18n }}</button>
</div> </div>
</header> </header>
<content> <content>
@@ -13,37 +13,62 @@
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput"> <div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock"> <div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label> <label for="pin">{{ "pin" | i18n }}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced" <input
[(ngModel)]="pin" required appInputVerbatim> id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div> </div>
<div class="row-main" *ngIf="!pinLock"> <div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" <input
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim> id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}" class="row-btn"
aria-hidden="true"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
<p>{{'yourVaultIsLocked' | i18n}}</p> <p>{{ "yourVaultIsLocked" | i18n }}</p>
{{'loggedInAsOn' | i18n : email : webVaultHostname}} {{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div> </div>
</div> </div>
<div class="box" *ngIf="biometricLock"> <div class="box" *ngIf="biometricLock">
<div class="box-footer"> <div class="box-footer">
<button type="button" class="btn primary block" (click)="unlockBiometric()" <button type="button" class="btn primary block" (click)="unlockBiometric()" appStopClick>
appStopClick>{{'unlockWithBiometrics' | i18n}}</button> {{ "unlockWithBiometrics" | i18n }}
</button>
</div> </div>
</div> </div>
<p class="text-center"> <p class="text-center">
<button type="button" appStopClick (click)="logOut()">{{'logOut' | i18n}}</button> <button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button>
</p> </p>
</content> </content>
</form> </form>

View File

@@ -1,55 +1,75 @@
import { import { Component, NgZone } from "@angular/core";
Component, import { Router } from "@angular/router";
NgZone, import Swal from "sweetalert2";
} from '@angular/core';
import { Router } from '@angular/router';
import Swal from 'sweetalert2';
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
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 { 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 { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component'; import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@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, router: Router,
userService: UserService, cryptoService: CryptoService, i18nService: I18nService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, stateService: StateService, messagingService: MessagingService,
apiService: ApiService, logService: LogService, keyConnectorService: KeyConnectorService, userService: UserService,
ngZone: NgZone) { cryptoService: CryptoService,
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, storageService: StorageService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService, vaultTimeoutService: VaultTimeoutService,
keyConnectorService, ngZone); environmentService: EnvironmentService,
this.successRoute = '/tabs/current'; stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
userService,
cryptoService,
storageService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null; this.isInitialLockScreen = (window as any).previousPopupUrl == null;
} }
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
const disableAutoBiometricsPrompt = await this.storageService.get<boolean>( const disableAutoBiometricsPrompt =
ConstantsService.disableAutoBiometricsPromptKey) ?? true; (await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey)) ??
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();
@@ -63,15 +83,15 @@ export class LockComponent extends BaseLockComponent {
return; return;
} }
const div = document.createElement('div'); const div = document.createElement("div");
div.innerHTML = `<div class="swal2-text">${this.i18nService.t('awaitDesktop')}</div>`; div.innerHTML = `<div class="swal2-text">${this.i18nService.t("awaitDesktop")}</div>`;
Swal.fire({ Swal.fire({
heightAuto: false, heightAuto: false,
buttonsStyling: false, buttonsStyling: false,
html: div, html: div,
showCancelButton: true, showCancelButton: true,
cancelButtonText: this.i18nService.t('cancel'), cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: false, showConfirmButton: false,
}); });

View File

@@ -1,14 +1,14 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a> <a routerLink="/home">{{ "cancel" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'appName' | i18n}}</span> <span class="title">{{ "appName" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'login' | i18n}}</span> <span [hidden]="form.loading">{{ "login" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -17,21 +17,45 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required inputmode="email" <input
appInputVerbatim="false"> id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" <input
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim> id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}" class="row-btn"
aria-hidden="true"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -41,7 +65,7 @@
</div> </div>
</div> </div>
<p class="text-center"> <p class="text-center">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a> <a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</p> </p>
</content> </content>
</form> </form>

View File

@@ -1,42 +1,58 @@
import { import { Component, NgZone } from "@angular/core";
Component, import { Router } from "@angular/router";
NgZone,
} from '@angular/core';
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 platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected stateService: StateService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService, protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService, protected cryptoFunctionService: CryptoFunctionService,
syncService: SyncService, logService: LogService, ngZone: NgZone) { storageService: StorageService,
super(authService, router, platformUtilsService, i18nService, stateService, environmentService, syncService: SyncService,
passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone); logService: LogService,
ngZone: NgZone
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
storageService,
logService,
ngZone
);
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
await syncService.fullSync(true); await syncService.fullSync(true);
}; };
super.successRoute = '/tabs/vault'; super.successRoute = "/tabs/vault";
} }
settings() { settings() {
this.router.navigate(['environment']); this.router.navigate(["environment"]);
} }
} }

View File

@@ -1,14 +1,14 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a> <a routerLink="/home">{{ "cancel" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'createAccount' | i18n}}</span> <span class="title">{{ "createAccount" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span> <span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -17,81 +17,141 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required <input
[appAutofocus]="email === ''" inputmode="email" appInputVerbatim="false"> id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<div class="box-content-row-flex"> <div class="box-content-row-flex">
<div class="row-main"> <div class="row-main">
<label for="masterPassword"> <label for="masterPassword">
{{'masterPass' | i18n}} {{ "masterPass" | i18n }}
<strong class="sub-label text-{{masterPasswordScoreColor}}" <strong
*ngIf="masterPasswordScoreText"> class="sub-label text-{{ masterPasswordScoreColor }}"
{{masterPasswordScoreText}} *ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong> </strong>
</label> </label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required id="masterPassword"
[appAutofocus]="email !== ''" appInputVerbatim (input)="updatePasswordStrength()"> type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
appInputVerbatim
(input)="updatePasswordStrength()"
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="progress"> <div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0" <div
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}" class="progress-bar bg-{{ masterPasswordScoreColor }}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div> role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'masterPassDesc' | i18n}} {{ "masterPassDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required id="masterPasswordRetype"
appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint"> <input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'masterPassHintDesc' | i18n}} {{ "masterPassHintDesc" | i18n }}
</div> </div>
</div> </div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div> <div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="box last" *ngIf="showTerms"> <div class="box last" *ngIf="showTerms">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break" <div
appBoxRow> class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies"> appBoxRow
>
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies"> <label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br> {{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" <a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
rel="noopener">{{'termsOfService' | i18n}}</a>, "termsOfService" | i18n
<a href="https://bitwarden.com/privacy/" target="_blank" }}</a
rel="noopener">{{'privacyPolicy' | i18n}}</a> >,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -1,29 +1,46 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { 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 { 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 { 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 { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component'; import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
@Component({ @Component({
selector: 'app-register', selector: "app-register",
templateUrl: 'register.component.html', templateUrl: "register.component.html",
}) })
export class RegisterComponent extends BaseRegisterComponent { export class RegisterComponent extends BaseRegisterComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, cryptoService: CryptoService, authService: AuthService,
apiService: ApiService, stateService: StateService, platformUtilsService: PlatformUtilsService, router: Router,
passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService, i18nService: I18nService,
logService: LogService) { cryptoService: CryptoService,
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService, apiService: ApiService,
passwordGenerationService, environmentService, logService); stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
} }
} }

View File

@@ -1,7 +1,7 @@
<header> <header>
<div class="left"></div> <div class="left"></div>
<div class="center"> <div class="center">
<span class="title">{{'removeMasterPassword' | i18n}}</span> <span class="title">{{ "removeMasterPassword" | i18n }}</span>
</div> </div>
<div class="right"></div> <div class="right"></div>
</header> </header>
@@ -10,18 +10,38 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p> <p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
</div> </div>
<div class="box-content-row"> <div class="box-content-row">
<button type="button" class="btn block primary" (click)="convert()" [disabled]="actionPromise"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="continuing"></i> type="button"
{{'removeMasterPassword' | i18n}} class="btn block primary"
(click)="convert()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="continuing"
></i>
{{ "removeMasterPassword" | i18n }}
</button> </button>
</div> </div>
<div class="box-content-row"> <div class="box-content-row">
<button type="button" class="btn btn-outline-secondary block" (click)="leave()" [disabled]="actionPromise"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i> type="button"
{{'leaveOrganization' | i18n}} class="btn btn-outline-secondary block"
(click)="leave()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="leaving"
></i>
{{ "leaveOrganization" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component'; import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({ @Component({
selector: 'app-remove-password', selector: "app-remove-password",
templateUrl: 'remove-password.component.html', templateUrl: "remove-password.component.html",
}) })
export class RemovePasswordComponent extends BaseRemovePasswordComponent { export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
}

View File

@@ -1,14 +1,14 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a> <a routerLink="/home">{{ "cancel" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'setMasterPassword' | i18n}}</span> <span class="title">{{ "setMasterPassword" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span> <span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -19,12 +19,19 @@
</div> </div>
<div *ngIf="!syncLoading"> <div *ngIf="!syncLoading">
<div class="box"> <div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout> <app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}" <app-callout
*ngIf="resetPasswordAutoEnroll"> type="warning"
{{'resetPasswordAutoEnrollInviteWarning' | i18n}} title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout> </app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions"> <app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout> </app-callout>
</div> </div>
<div class="box"> <div class="box">
@@ -32,35 +39,60 @@
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<div class="box-content-row-flex"> <div class="box-content-row-flex">
<div class="row-main"> <div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}} <label for="masterPassword"
<strong class="sub-label text-{{masterPasswordScoreColor}}" >{{ "masterPass" | i18n }}
*ngIf="masterPasswordScoreText"> <strong
{{masterPasswordScoreText}} class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong> </strong>
</label> </label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required id="masterPassword"
(input)="updatePasswordStrength()" appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="progress"> <div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" <div
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="progress-bar bg-{{ masterPasswordScoreColor }}"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}" role="progressbar"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"> aria-valuenow="0"
</div> aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'masterPassDesc' | i18n}} {{ "masterPassDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
@@ -68,16 +100,34 @@
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<div class="box-content-row-flex"> <div class="box-content-row-flex">
<div class="row-main"> <div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype" <input
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim id="masterPasswordRetype"
autocomplete="new-password"> type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -87,12 +137,12 @@
<div class="box last"> <div class="box last">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint"> <input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'masterPassHintDesc' | i18n}} {{ "masterPassHintDesc" | i18n }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,36 +1,50 @@
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 { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.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,
userService: UserService, passwordGenerationService: PasswordGenerationService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router, cryptoService: CryptoService,
syncService: SyncService, route: ActivatedRoute) { messagingService: MessagingService,
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, userService: UserService,
platformUtilsService, policyService, router, apiService, syncService, route); passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute
) {
super(
i18nService,
cryptoService,
messagingService,
userService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route
);
} }
get masterPasswordScoreWidth() { get masterPasswordScoreWidth() {
@@ -40,26 +54,26 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
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;
} }
} }
} }

View File

@@ -1,45 +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 { 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 { 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 { 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";
@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, authService: AuthService,
storageService: StorageService, stateService: StateService, router: Router,
platformUtilsService: PlatformUtilsService, apiService: ApiService, i18nService: I18nService,
cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, route: ActivatedRoute,
syncService: SyncService, environmentService: EnvironmentService, logService: LogService, storageService: StorageService,
private vaultTimeoutService: VaultTimeoutService) { stateService: StateService,
super(authService, router, i18nService, route, storageService, 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,
storageService,
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);
@@ -48,7 +66,7 @@ export class SsoComponent extends BaseSsoComponent {
BrowserApi.reloadOpenWindows(); BrowserApi.reloadOpenWindows();
} }
const thisWindow = window.open('', '_self'); const thisWindow = window.open("", "_self");
thisWindow.close(); thisWindow.close();
}; };
} }

View File

@@ -1,22 +1,28 @@
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/2fa">{{'close' | i18n}}</a> <a routerLink="/2fa">{{ "close" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'twoStepOptions' | i18n}}</span> <span class="title">{{ "twoStepOptions" | i18n }}</span>
</h1> </h1>
<div class="right"></div> <div class="right"></div>
</header> </header>
<content> <content>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<button type="button" appStopClick *ngFor="let p of providers" class="box-content-row" (click)="choose(p)"> <button
<span class="text">{{p.name}}</span> type="button"
<span class="detail">{{p.description}}</span> appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</button> </button>
<button type="button" appStopClick class="box-content-row" (click)="recover()"> <button type="button" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span> <span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span> <span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,27 +1,29 @@
import { Component } from '@angular/core'; import { Component } 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 { 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 { import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
@Component({ @Component({
selector: 'app-two-factor-options', selector: "app-two-factor-options",
templateUrl: 'two-factor-options.component.html', templateUrl: "two-factor-options.component.html",
}) })
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, platformUtilsService: PlatformUtilsService) { authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window); super(authService, router, i18nService, platformUtilsService, window);
} }
choose(p: any) { choose(p: any) {
super.choose(p); super.choose(p);
this.authService.selectedTwoFactorProviderType = p.type; this.authService.selectedTwoFactorProviderType = p.type;
this.router.navigate(['2fa']); this.router.navigate(["2fa"]);
} }
} }

View File

@@ -1,60 +1,87 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a routerLink="/login">{{'back' | i18n}}</a> <a routerLink="/login">{{ "back" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{title}}</span> <span class="title">{{ title }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo && <button
type="submit"
appBlurClick
[disabled]="form.loading"
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.OrganizationDuo &&
(selectedProviderType !== providerType.WebAuthn || form.loading)"> (selectedProviderType !== providerType.WebAuthn || form.loading)
<span [hidden]="form.loading">{{'continue' | i18n}}</span> "
>
<span [hidden]="form.loading">{{ "continue" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
</header> </header>
<content> <content>
<ng-container *ngIf="selectedProviderType === providerType.Authenticator || <ng-container
selectedProviderType === providerType.Email"> *ngIf="
selectedProviderType === providerType.Authenticator ||
selectedProviderType === providerType.Email
"
>
<div class="content text-center"> <div class="content text-center">
<span *ngIf="selectedProviderType === providerType.Authenticator"> <span *ngIf="selectedProviderType === providerType.Authenticator">
{{'enterVerificationCodeApp' | i18n}} {{ "enterVerificationCodeApp" | i18n }}
</span> </span>
<span *ngIf="selectedProviderType === providerType.Email"> <span *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}} {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</span> </span>
</div> </div>
<div class="box first"> <div class="box first">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label> <label for="code">{{ "verificationCode" | i18n }}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus <input
inputmode="tel" appInputVerbatim> id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
inputmode="tel"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label> <label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember"> <input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div> </div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey"> <ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<div class="content text-center"> <div class="content text-center">
<p class="text-center">{{'insertYubiKey' | i18n}}</p> <p class="text-center">{{ "insertYubiKey" | i18n }}</p>
<img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt=""> <img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt="" />
</div> </div>
<div class="box first"> <div class="box first">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label> <label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus <input
appInputVerbatim> id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label> <label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember"> <input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div> </div>
</div> </div>
</div> </div>
@@ -64,41 +91,49 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label> <label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember"> <input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div> </div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab"> <ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab">
<div class="content text-center" *ngIf="webAuthnNewTab"> <div class="content text-center" *ngIf="webAuthnNewTab">
<p class="text-center">{{'webAuthnNewTab' | i18n}}</p> <p class="text-center">{{ "webAuthnNewTab" | i18n }}</p>
<button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>{{'webAuthnNewTabOpen' | i18n}}</button> <button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>
{{ "webAuthnNewTabOpen" | i18n }}
</button>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo || <ng-container
selectedProviderType === providerType.OrganizationDuo"> *ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div> <div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label> <label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember"> <input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div> </div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
<div class="content" *ngIf="selectedProviderType == null"> <div class="content" *ngIf="selectedProviderType == null">
<p class="text-center">{{'noTwoStepProviders' | i18n}}</p> <p class="text-center">{{ "noTwoStepProviders" | i18n }}</p>
<p class="text-center">{{'noTwoStepProviders2' | i18n}}</p> <p class="text-center">{{ "noTwoStepProviders2" | i18n }}</p>
</div> </div>
<div class="content no-vpad" *ngIf="selectedProviderType != null"> <div class="content no-vpad" *ngIf="selectedProviderType != null">
<p class="text-center"> <p class="text-center">
<button type="button" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</button> <button type="button" appStopClick (click)="anotherMethod()">
{{ "useAnotherTwoStepMethod" | i18n }}
</button>
</p> </p>
<p *ngIf="selectedProviderType === providerType.Email" class="text-center"> <p *ngIf="selectedProviderType === providerType.Email" class="text-center">
<button type="button" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"> <button type="button" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise">
{{'sendVerificationCodeEmailAgain' | i18n}} {{ "sendVerificationCodeEmailAgain" | i18n }}
</button> </button>
</p> </p>
</div> </div>

View File

@@ -1,66 +1,84 @@
import { Component } from '@angular/core'; import { Component } 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 { 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 { 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 { 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 broadcasterService: BroadcasterService, i18nService: I18nService,
private popupUtilsService: PopupUtilsService, stateService: StateService, apiService: ApiService,
storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService, platformUtilsService: PlatformUtilsService,
logService: LogService) { private syncService: SyncService,
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, environmentService: EnvironmentService,
stateService, storageService, route, logService); private broadcasterService: BroadcasterService,
private popupUtilsService: PopupUtilsService,
stateService: StateService,
storageService: StorageService,
route: ActivatedRoute,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
storageService,
route,
logService
);
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = () => {
return syncService.fullSync(true); return syncService.fullSync(true);
}; };
super.successRoute = '/tabs/vault'; super.successRoute = "/tabs/vault";
this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); this.webAuthnNewTab =
this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
} }
async ngOnInit() { async ngOnInit() {
if (this.route.snapshot.paramMap.has('webAuthnResponse')) { if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
// WebAuthn fallback response // WebAuthn fallback response
this.selectedProviderType = TwoFactorProviderType.WebAuthn; this.selectedProviderType = TwoFactorProviderType.WebAuthn;
this.token = this.route.snapshot.paramMap.get('webAuthnResponse'); this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
this.syncService.fullSync(true); this.syncService.fullSync(true);
this.messagingService.send('reloadPopup'); this.messagingService.send("reloadPopup");
window.close(); window.close();
}; };
this.remember = this.route.snapshot.paramMap.get('remember') === 'true'; this.remember = this.route.snapshot.paramMap.get("remember") === "true";
await this.doSubmit(); await this.doSubmit();
return; return;
} }
@@ -72,24 +90,30 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
// than usual to avoid cutting off the dialog. // than usual to avoid cutting off the dialog.
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) { if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.add('linux-webauthn'); document.body.classList.add("linux-webauthn");
} }
if (this.selectedProviderType === TwoFactorProviderType.Email && if (
this.popupUtilsService.inPopup(window)) { this.selectedProviderType === TwoFactorProviderType.Email &&
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'), this.popupUtilsService.inPopup(window)
null, this.i18nService.t('yes'), this.i18nService.t('no')); ) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("popup2faCloseMessage"),
null,
this.i18nService.t("yes"),
this.i18nService.t("no")
);
if (confirmed) { if (confirmed) {
this.popupUtilsService.popOut(window); this.popupUtilsService.popOut(window);
} }
} }
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.sso === 'true') { if (qParams.sso === "true") {
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = () => {
BrowserApi.reloadOpenWindows(); BrowserApi.reloadOpenWindows();
const thisWindow = window.open('', '_self'); const thisWindow = window.open("", "_self");
thisWindow.close(); thisWindow.close();
return this.syncService.fullSync(true); return this.syncService.fullSync(true);
}; };
@@ -100,17 +124,17 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
async ngOnDestroy() { async ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) { if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.remove('linux-webauthn'); document.body.classList.remove("linux-webauthn");
} }
super.ngOnDestroy(); super.ngOnDestroy();
} }
anotherMethod() { anotherMethod() {
this.router.navigate(['2fa-options']); this.router.navigate(["2fa-options"]);
} }
async isLinux() { async isLinux() {
return (await BrowserApi.getPlatformInfo()).os === 'linux'; return (await BrowserApi.getPlatformInfo()).os === "linux";
} }
} }

View File

@@ -1,23 +1,27 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<a (click)="logOut()">{{'logOut' | i18n}}</a> <a (click)="logOut()">{{ "logOut" | i18n }}</a>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'updateMasterPassword' | i18n}}</span> <span class="title">{{ "updateMasterPassword" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"> <button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span> <span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
</header> </header>
<content> <content>
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}"> <app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{'updateMasterPasswordWarning' | i18n}} {{ "updateMasterPasswordWarning" | i18n }}
</app-callout> </app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions"> <app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout> </app-callout>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
@@ -25,29 +29,53 @@
<div class="box-content-row-flex"> <div class="box-content-row-flex">
<div class="row-main"> <div class="row-main">
<label for="masterPassword"> <label for="masterPassword">
{{'masterPass' | i18n}} {{ "masterPass" | i18n }}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}" <strong
*ngIf="masterPasswordScoreStyle.Text"> class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
{{masterPasswordScoreStyle.Text}} *ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong> </strong>
</label> </label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required id="masterPassword"
appInputVerbatim (input)="updatePasswordStrength()"> type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
(input)="updatePasswordStrength()"
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="progress"> <div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar" <div
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}" role="progressbar"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div> aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div> </div>
</div> </div>
</div> </div>
@@ -56,16 +84,32 @@
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required id="masterPasswordRetype"
appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -74,12 +118,12 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint"> <input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'masterPassHintDesc' | i18n}} {{ "masterPassHintDesc" | i18n }}
</div> </div>
</div> </div>
</content> </content>

View File

@@ -1,17 +1,17 @@
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 { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.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";
interface MasterPasswordScore { interface MasterPasswordScore {
Color: string; Color: string;
@@ -20,8 +20,8 @@ interface MasterPasswordScore {
} }
@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 {
@@ -29,37 +29,54 @@ export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent
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, userService: UserService, platformUtilsService: PlatformUtilsService,
messagingService: MessagingService, apiService: ApiService, passwordGenerationService: PasswordGenerationService,
syncService: SyncService, logService: LogService) { policyService: PolicyService,
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, cryptoService: CryptoService,
userService, messagingService, apiService, syncService, logService); userService: UserService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
userService,
messagingService,
apiService,
syncService,
logService
);
} }
} }

View File

@@ -1,139 +1,144 @@
import { import { animate, group, query, style, transition, trigger } from "@angular/animations";
animate,
group,
query,
style,
transition,
trigger,
} from '@angular/animations';
import { BrowserApi } from '../browser/browserApi'; import { BrowserApi } from "../browser/browserApi";
const queryShown = query(':enter, :leave', [ const queryShown = query(
style({ position: 'fixed', width: '100%', height: '100%' }), ":enter, :leave",
], { optional: true }); [style({ position: "fixed", width: "100%", height: "100%" })],
{
optional: true,
}
);
// ref: https://github.com/angular/angular/issues/15477 // ref: https://github.com/angular/angular/issues/15477
const queryChildRoute = query('router-outlet ~ *', [ const queryChildRoute = query("router-outlet ~ *", [style({}), animate(1, style({}))], {
style({}), optional: true,
animate(1, style({})), });
], { optional: true });
const speed = '0.4s'; const speed = "0.4s";
export function queryTranslate(direction: string, axis: string, from: number, to: number, zIndex: number = 1000) { export function queryTranslate(
return query(':' + direction, [ direction: string,
style({ transform: 'translate' + axis + '(' + from + '%)', zIndex: zIndex, boxShadow: '0 3px 2px -2px gray' }), axis: string,
animate(speed + ' ease-in-out', style({ transform: 'translate' + axis + '(' + to + '%)' })), from: number,
], { optional: true }); to: number,
zIndex: number = 1000
) {
return query(
":" + direction,
[
style({
transform: "translate" + axis + "(" + from + "%)",
zIndex: zIndex,
boxShadow: "0 3px 2px -2px gray",
}),
animate(speed + " ease-in-out", style({ transform: "translate" + axis + "(" + to + "%)" })),
],
{ optional: true }
);
} }
export function queryTranslateX(direction: string, from: number, to: number, zIndex: number = 1000) { export function queryTranslateX(
return queryTranslate(direction, 'X', from, to, zIndex); direction: string,
from: number,
to: number,
zIndex: number = 1000
) {
return queryTranslate(direction, "X", from, to, zIndex);
} }
export function queryTranslateY(direction: string, from: number, to: number, zIndex: number = 1000) { export function queryTranslateY(
return queryTranslate(direction, 'Y', from, to, zIndex); direction: string,
from: number,
to: number,
zIndex: number = 1000
) {
return queryTranslate(direction, "Y", from, to, zIndex);
} }
const inSlideLeft = [ const inSlideLeft = [
queryShown, queryShown,
group([ group([queryTranslateX("enter", 100, 0), queryTranslateX("leave", 0, -100), queryChildRoute]),
queryTranslateX('enter', 100, 0),
queryTranslateX('leave', 0, -100),
queryChildRoute,
]),
]; ];
const outSlideRight = [ const outSlideRight = [
queryShown, queryShown,
group([ group([queryTranslateX("enter", -100, 0), queryTranslateX("leave", 0, 100)]),
queryTranslateX('enter', -100, 0),
queryTranslateX('leave', 0, 100),
]),
]; ];
const inSlideUp = [ const inSlideUp = [
queryShown, queryShown,
group([ group([queryTranslateY("enter", 100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
queryTranslateY('enter', 100, 0, 1010),
queryTranslateY('leave', 0, 0),
queryChildRoute,
]),
]; ];
const outSlideDown = [ const outSlideDown = [
queryShown, queryShown,
group([ group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, 100, 1010)]),
queryTranslateY('enter', 0, 0),
queryTranslateY('leave', 0, 100, 1010),
]),
]; ];
const inSlideDown = [ const inSlideDown = [
queryShown, queryShown,
group([ group([queryTranslateY("enter", -100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
queryTranslateY('enter', -100, 0, 1010),
queryTranslateY('leave', 0, 0),
queryChildRoute,
]),
]; ];
const outSlideUp = [ const outSlideUp = [
queryShown, queryShown,
group([ group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, -100, 1010)]),
queryTranslateY('enter', 0, 0),
queryTranslateY('leave', 0, -100, 1010),
]),
]; ];
export function tabsToCiphers(fromState: string, toState: string) { export function tabsToCiphers(fromState: string, toState: string) {
if (fromState == null || toState === null || toState.indexOf('ciphers_') === -1) { if (fromState == null || toState === null || toState.indexOf("ciphers_") === -1) {
return false; return false;
} }
return (fromState.indexOf('ciphers_') === 0 && fromState.indexOf('ciphers_direction=b') === -1) || return (
fromState === 'tabs'; (fromState.indexOf("ciphers_") === 0 && fromState.indexOf("ciphers_direction=b") === -1) ||
fromState === "tabs"
);
} }
export function ciphersToTabs(fromState: string, toState: string) { export function ciphersToTabs(fromState: string, toState: string) {
if (fromState == null || toState === null || fromState.indexOf('ciphers_') === -1) { if (fromState == null || toState === null || fromState.indexOf("ciphers_") === -1) {
return false; return false;
} }
return toState.indexOf('ciphers_direction=b') === 0 || toState === 'tabs'; return toState.indexOf("ciphers_direction=b") === 0 || toState === "tabs";
} }
export function ciphersToView(fromState: string, toState: string) { export function ciphersToView(fromState: string, toState: string) {
if (fromState == null || toState === null) { if (fromState == null || toState === null) {
return false; return false;
} }
return fromState.indexOf('ciphers_') === 0 && return (
(toState === 'view-cipher' || toState === 'add-cipher' || toState === 'clone-cipher'); fromState.indexOf("ciphers_") === 0 &&
(toState === "view-cipher" || toState === "add-cipher" || toState === "clone-cipher")
);
} }
export function viewToCiphers(fromState: string, toState: string) { export function viewToCiphers(fromState: string, toState: string) {
if (fromState == null || toState === null) { if (fromState == null || toState === null) {
return false; return false;
} }
return (fromState === 'view-cipher' || fromState === 'add-cipher' || fromState === 'clone-cipher') && return (
toState.indexOf('ciphers_') === 0; (fromState === "view-cipher" || fromState === "add-cipher" || fromState === "clone-cipher") &&
toState.indexOf("ciphers_") === 0
);
} }
export const routerTransition = trigger('routerTransition', [ export const routerTransition = trigger("routerTransition", [
transition('void => home', inSlideLeft), transition("void => home", inSlideLeft),
transition('void => tabs', inSlideLeft), transition("void => tabs", inSlideLeft),
transition('home => environment, home => login, home => register', inSlideUp), transition("home => environment, home => login, home => register", inSlideUp),
transition('login => home', outSlideDown), transition("login => home", outSlideDown),
transition('login => hint', inSlideUp), transition("login => hint", inSlideUp),
transition('login => tabs, login => 2fa', inSlideLeft), transition("login => tabs, login => 2fa", inSlideLeft),
transition('hint => login, register => home, environment => home', outSlideDown), transition("hint => login, register => home, environment => home", outSlideDown),
transition('2fa => login', outSlideRight), transition("2fa => login", outSlideRight),
transition('2fa => 2fa-options', inSlideUp), transition("2fa => 2fa-options", inSlideUp),
transition('2fa-options => 2fa', outSlideDown), transition("2fa-options => 2fa", outSlideDown),
transition('2fa => tabs', inSlideLeft), transition("2fa => tabs", inSlideLeft),
transition(tabsToCiphers, inSlideLeft), transition(tabsToCiphers, inSlideLeft),
transition(ciphersToTabs, outSlideRight), transition(ciphersToTabs, outSlideRight),
@@ -141,59 +146,68 @@ export const routerTransition = trigger('routerTransition', [
transition(ciphersToView, inSlideUp), transition(ciphersToView, inSlideUp),
transition(viewToCiphers, outSlideDown), transition(viewToCiphers, outSlideDown),
transition('tabs => view-cipher', inSlideUp), transition("tabs => view-cipher", inSlideUp),
transition('view-cipher => tabs', outSlideDown), transition("view-cipher => tabs", outSlideDown),
transition('view-cipher => edit-cipher, view-cipher => cipher-password-history', inSlideUp), transition("view-cipher => edit-cipher, view-cipher => cipher-password-history", inSlideUp),
transition('edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs', outSlideDown), transition(
"edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs",
outSlideDown
),
transition('view-cipher => clone-cipher', inSlideUp), transition("view-cipher => clone-cipher", inSlideUp),
transition('clone-cipher => view-cipher, clone-cipher => tabs', outSlideDown), transition("clone-cipher => view-cipher, clone-cipher => tabs", outSlideDown),
transition('view-cipher => share-cipher', inSlideUp), transition("view-cipher => share-cipher", inSlideUp),
transition('share-cipher => view-cipher', outSlideDown), transition("share-cipher => view-cipher", outSlideDown),
transition('tabs => add-cipher', inSlideUp), transition("tabs => add-cipher", inSlideUp),
transition('add-cipher => tabs', outSlideDown), transition("add-cipher => tabs", outSlideDown),
transition('generator => generator-history, tabs => generator-history', inSlideLeft), transition("generator => generator-history, tabs => generator-history", inSlideLeft),
transition('generator-history => generator, generator-history => tabs', outSlideRight), transition("generator-history => generator, generator-history => tabs", outSlideRight),
transition('add-cipher => generator, edit-cipher => generator, clone-cipher => generator', inSlideUp), transition(
transition('generator => add-cipher, generator => edit-cipher, generator => clone-cipher', outSlideDown), "add-cipher => generator, edit-cipher => generator, clone-cipher => generator",
inSlideUp
),
transition(
"generator => add-cipher, generator => edit-cipher, generator => clone-cipher",
outSlideDown
),
transition('edit-cipher => attachments, edit-cipher => collections', inSlideLeft), transition("edit-cipher => attachments, edit-cipher => collections", inSlideLeft),
transition('attachments => edit-cipher, collections => edit-cipher', outSlideRight), transition("attachments => edit-cipher, collections => edit-cipher", outSlideRight),
transition('clone-cipher => attachments, clone-cipher => collections', inSlideLeft), transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft),
transition('attachments => clone-cipher, collections => clone-cipher', outSlideRight), transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight),
transition('tabs => export', inSlideLeft), transition("tabs => export", inSlideLeft),
transition('export => tabs', outSlideRight), transition("export => tabs", outSlideRight),
transition('tabs => folders', inSlideLeft), transition("tabs => folders", inSlideLeft),
transition('folders => tabs', outSlideRight), transition("folders => tabs", outSlideRight),
transition('folders => edit-folder, folders => add-folder', inSlideUp), transition("folders => edit-folder, folders => add-folder", inSlideUp),
transition('edit-folder => folders, add-folder => folders', outSlideDown), transition("edit-folder => folders, add-folder => folders", outSlideDown),
transition('tabs => sync', inSlideLeft), transition("tabs => sync", inSlideLeft),
transition('sync => tabs', outSlideRight), transition("sync => tabs", outSlideRight),
transition('tabs => options', inSlideLeft), transition("tabs => options", inSlideLeft),
transition('options => tabs', outSlideRight), transition("options => tabs", outSlideRight),
transition('tabs => premium', inSlideLeft), transition("tabs => premium", inSlideLeft),
transition('premium => tabs', outSlideRight), transition("premium => tabs", outSlideRight),
transition('tabs => lock', inSlideDown), transition("tabs => lock", inSlideDown),
transition('tabs => send-type', inSlideLeft), transition("tabs => send-type", inSlideLeft),
transition('send-type => tabs', outSlideRight), transition("send-type => tabs", outSlideRight),
transition('tabs => add-send, send-type => add-send', inSlideUp), transition("tabs => add-send, send-type => add-send", inSlideUp),
transition('add-send => tabs, add-send => send-type', outSlideDown), transition("add-send => tabs, add-send => send-type", outSlideDown),
transition('tabs => edit-send, send-type => edit-send', inSlideUp), transition("tabs => edit-send, send-type => edit-send", inSlideUp),
transition('edit-send => tabs, edit-send => send-type', outSlideDown), transition("edit-send => tabs, edit-send => send-type", outSlideDown),
]); ]);

View File

@@ -1,320 +1,315 @@
import { Injectable, NgModule } from '@angular/core'; import { Injectable, NgModule } from "@angular/core";
import { import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
ActivatedRouteSnapshot,
RouteReuseStrategy,
RouterModule,
Routes,
} from '@angular/router';
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from 'jslib-angular/services/lock-guard.service'; import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { DebounceNavigationService } from './services/debounceNavigationService'; import { DebounceNavigationService } from "./services/debounceNavigationService";
import { LaunchGuardService } from './services/launch-guard.service'; import { LaunchGuardService } from "./services/launch-guard.service";
import { EnvironmentComponent } from './accounts/environment.component'; import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from './accounts/hint.component'; import { HintComponent } from "./accounts/hint.component";
import { HomeComponent } from './accounts/home.component'; import { HomeComponent } from "./accounts/home.component";
import { LockComponent } from './accounts/lock.component'; import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from './accounts/login.component'; import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from './accounts/remove-password.component'; import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from './accounts/set-password.component'; import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component'; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
import { PasswordGeneratorComponent } from './generator/password-generator.component'; import { PasswordGeneratorComponent } from "./generator/password-generator.component";
import { PrivateModeComponent } from './private-mode.component'; import { PrivateModeComponent } from "./private-mode.component";
import { TabsComponent } from './tabs.component'; import { TabsComponent } from "./tabs.component";
import { ExcludedDomainsComponent } from './settings/excluded-domains.component'; import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { ExportComponent } from './settings/export.component'; import { ExportComponent } from "./settings/export.component";
import { FolderAddEditComponent } from './settings/folder-add-edit.component'; import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from './settings/folders.component'; import { FoldersComponent } from "./settings/folders.component";
import { OptionsComponent } from './settings/options.component'; import { OptionsComponent } from "./settings/options.component";
import { PremiumComponent } from './settings/premium.component'; import { PremiumComponent } from "./settings/premium.component";
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from "./settings/settings.component";
import { SyncComponent } from './settings/sync.component'; import { SyncComponent } from "./settings/sync.component";
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from './vault/attachments.component'; import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from './vault/collections.component'; import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from './vault/current-tab.component'; import { CurrentTabComponent } from "./vault/current-tab.component";
import { GroupingsComponent } from './vault/groupings.component'; import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordHistoryComponent } from './vault/password-history.component'; import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from './vault/share.component'; import { ShareComponent } from "./vault/share.component";
import { ViewComponent } from './vault/view.component'; import { ViewComponent } from "./vault/view.component";
import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendAddEditComponent } from "./send/send-add-edit.component";
import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendGroupingsComponent } from "./send/send-groupings.component";
import { SendTypeComponent } from './send/send-type.component'; import { SendTypeComponent } from "./send/send-type.component";
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: "",
redirectTo: 'home', redirectTo: "home",
pathMatch: 'full', pathMatch: "full",
}, },
{ {
path: 'vault', path: "vault",
redirectTo: '/tabs/vault', redirectTo: "/tabs/vault",
pathMatch: 'full', pathMatch: "full",
}, },
{ {
path: 'home', path: "home",
component: HomeComponent, component: HomeComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'home' }, data: { state: "home" },
}, },
{ {
path: 'login', path: "login",
component: LoginComponent, component: LoginComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'login' }, data: { state: "login" },
}, },
{ {
path: 'lock', path: "lock",
component: LockComponent, component: LockComponent,
canActivate: [LockGuardService], canActivate: [LockGuardService],
data: { state: 'lock' }, data: { state: "lock" },
}, },
{ {
path: '2fa', path: "2fa",
component: TwoFactorComponent, component: TwoFactorComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: '2fa' }, data: { state: "2fa" },
}, },
{ {
path: '2fa-options', path: "2fa-options",
component: TwoFactorOptionsComponent, component: TwoFactorOptionsComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: '2fa-options' }, data: { state: "2fa-options" },
}, },
{ {
path: 'sso', path: "sso",
component: SsoComponent, component: SsoComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'sso' }, data: { state: "sso" },
}, },
{ {
path: 'set-password', path: "set-password",
component: SetPasswordComponent, component: SetPasswordComponent,
data: { state: 'set-password' }, data: { state: "set-password" },
}, },
{ {
path: 'remove-password', path: "remove-password",
component: RemovePasswordComponent, component: RemovePasswordComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'remove-password' }, data: { state: "remove-password" },
}, },
{ {
path: 'register', path: "register",
component: RegisterComponent, component: RegisterComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'register' }, data: { state: "register" },
}, },
{ {
path: 'hint', path: "hint",
component: HintComponent, component: HintComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'hint' }, data: { state: "hint" },
}, },
{ {
path: 'environment', path: "environment",
component: EnvironmentComponent, component: EnvironmentComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
data: { state: 'environment' }, data: { state: "environment" },
}, },
{ {
path: 'ciphers', path: "ciphers",
component: CiphersComponent, component: CiphersComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'ciphers' }, data: { state: "ciphers" },
}, },
{ {
path: 'view-cipher', path: "view-cipher",
component: ViewComponent, component: ViewComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'view-cipher' }, data: { state: "view-cipher" },
}, },
{ {
path: 'cipher-password-history', path: "cipher-password-history",
component: PasswordHistoryComponent, component: PasswordHistoryComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'cipher-password-history' }, data: { state: "cipher-password-history" },
}, },
{ {
path: 'add-cipher', path: "add-cipher",
component: AddEditComponent, component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService], canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: 'add-cipher' }, data: { state: "add-cipher" },
runGuardsAndResolvers: 'always', runGuardsAndResolvers: "always",
}, },
{ {
path: 'edit-cipher', path: "edit-cipher",
component: AddEditComponent, component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService], canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: 'edit-cipher' }, data: { state: "edit-cipher" },
runGuardsAndResolvers: 'always', runGuardsAndResolvers: "always",
}, },
{ {
path: 'share-cipher', path: "share-cipher",
component: ShareComponent, component: ShareComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'share-cipher' }, data: { state: "share-cipher" },
}, },
{ {
path: 'collections', path: "collections",
component: CollectionsComponent, component: CollectionsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'collections' }, data: { state: "collections" },
}, },
{ {
path: 'attachments', path: "attachments",
component: AttachmentsComponent, component: AttachmentsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'attachments' }, data: { state: "attachments" },
}, },
{ {
path: 'generator', path: "generator",
component: PasswordGeneratorComponent, component: PasswordGeneratorComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'generator' }, data: { state: "generator" },
}, },
{ {
path: 'generator-history', path: "generator-history",
component: PasswordGeneratorHistoryComponent, component: PasswordGeneratorHistoryComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'generator-history' }, data: { state: "generator-history" },
}, },
{ {
path: 'export', path: "export",
component: ExportComponent, component: ExportComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'export' }, data: { state: "export" },
}, },
{ {
path: 'folders', path: "folders",
component: FoldersComponent, component: FoldersComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'folders' }, data: { state: "folders" },
}, },
{ {
path: 'add-folder', path: "add-folder",
component: FolderAddEditComponent, component: FolderAddEditComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'add-folder' }, data: { state: "add-folder" },
}, },
{ {
path: 'edit-folder', path: "edit-folder",
component: FolderAddEditComponent, component: FolderAddEditComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'edit-folder' }, data: { state: "edit-folder" },
}, },
{ {
path: 'sync', path: "sync",
component: SyncComponent, component: SyncComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'sync' }, data: { state: "sync" },
}, },
{ {
path: 'excluded-domains', path: "excluded-domains",
component: ExcludedDomainsComponent, component: ExcludedDomainsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'excluded-domains' }, data: { state: "excluded-domains" },
}, },
{ {
path: 'premium', path: "premium",
component: PremiumComponent, component: PremiumComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'premium' }, data: { state: "premium" },
}, },
{ {
path: 'options', path: "options",
component: OptionsComponent, component: OptionsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'options' }, data: { state: "options" },
}, },
{ {
path: 'private-mode', path: "private-mode",
component: PrivateModeComponent, component: PrivateModeComponent,
data: { state: 'private-mode' }, data: { state: "private-mode" },
}, },
{ {
path: 'clone-cipher', path: "clone-cipher",
component: AddEditComponent, component: AddEditComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'clone-cipher' }, data: { state: "clone-cipher" },
}, },
{ {
path: 'send-type', path: "send-type",
component: SendTypeComponent, component: SendTypeComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'send-type' }, data: { state: "send-type" },
}, },
{ {
path: 'add-send', path: "add-send",
component: SendAddEditComponent, component: SendAddEditComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'add-send' }, data: { state: "add-send" },
}, },
{ {
path: 'edit-send', path: "edit-send",
component: SendAddEditComponent, component: SendAddEditComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'edit-send' }, data: { state: "edit-send" },
}, },
{ {
path: 'update-temp-password', path: "update-temp-password",
component: UpdateTempPasswordComponent, component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'update-temp-password' }, data: { state: "update-temp-password" },
}, },
{ {
path: 'tabs', path: "tabs",
component: TabsComponent, component: TabsComponent,
data: { state: 'tabs' }, data: { state: "tabs" },
children: [ children: [
{ {
path: '', path: "",
redirectTo: '/tabs/vault', redirectTo: "/tabs/vault",
pathMatch: 'full', pathMatch: "full",
}, },
{ {
path: 'current', path: "current",
component: CurrentTabComponent, component: CurrentTabComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_current' }, data: { state: "tabs_current" },
runGuardsAndResolvers: 'always', runGuardsAndResolvers: "always",
}, },
{ {
path: 'vault', path: "vault",
component: GroupingsComponent, component: GroupingsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_vault' }, data: { state: "tabs_vault" },
}, },
{ {
path: 'generator', path: "generator",
component: PasswordGeneratorComponent, component: PasswordGeneratorComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_generator' }, data: { state: "tabs_generator" },
}, },
{ {
path: 'settings', path: "settings",
component: SettingsComponent, component: SettingsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_settings' }, data: { state: "tabs_settings" },
}, },
{ {
path: 'send', path: "send",
component: SendGroupingsComponent, component: SendGroupingsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_send' }, data: { state: "tabs_send" },
}, },
], ],
}, },
@@ -326,7 +321,9 @@ export class NoRouteReuseStrategy implements RouteReuseStrategy {
return false; return false;
} }
store(route: ActivatedRouteSnapshot, handle: {}) { /* Nothing */ } store(route: ActivatedRouteSnapshot, handle: {}) {
/* Nothing */
}
shouldAttach(route: ActivatedRouteSnapshot) { shouldAttach(route: ActivatedRouteSnapshot) {
return false; return false;
@@ -342,14 +339,14 @@ export class NoRouteReuseStrategy implements RouteReuseStrategy {
} }
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { imports: [
RouterModule.forRoot(routes, {
useHash: true, useHash: true,
onSameUrlNavigation: 'reload', onSameUrlNavigation: "reload",
/*enableTracing: true,*/ /*enableTracing: true,*/
})], }),
exports: [RouterModule],
providers: [
{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy },
], ],
exports: [RouterModule],
providers: [{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy }],
}) })
export class AppRoutingModule { } export class AppRoutingModule {}

View File

@@ -1,56 +1,49 @@
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 { 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 { 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 { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { routerTransition } from './app-routing.animations'; import { routerTransition } from "./app-routing.animations";
@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, constructor(
private broadcasterService: BroadcasterService, private authService: AuthService, private toastrService: ToastrService,
private i18nService: I18nService, private router: Router, private storageService: StorageService,
private stateService: StateService, private messagingService: MessagingService, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, private authService: AuthService,
private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private keyConnectoService: KeyConnectorService) { } private router: Router,
private stateService: StateService,
private messagingService: MessagingService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private sanitizer: DomSanitizer,
private platformUtilsService: PlatformUtilsService,
private keyConnectoService: KeyConnectorService
) {}
ngOnInit() { ngOnInit() {
if (BrowserApi.getBackgroundPage() == null) { if (BrowserApi.getBackgroundPage() == null) {
@@ -66,52 +59,58 @@ export class AppComponent implements OnInit {
window.onkeypress = () => this.recordActivity(); window.onkeypress = () => this.recordActivity();
}); });
(window as any).bitwardenPopupMainMessageListener = async (msg: any, sender: any, sendResponse: any) => { (window as any).bitwardenPopupMainMessageListener = async (
if (msg.command === 'doneLoggingOut') { msg: any,
sender: any,
sendResponse: any
) => {
if (msg.command === "doneLoggingOut") {
this.ngZone.run(async () => { this.ngZone.run(async () => {
this.authService.logOut(() => { this.authService.logOut(() => {
if (msg.expired) { if (msg.expired) {
this.showToast({ this.showToast({
type: 'warning', type: "warning",
title: this.i18nService.t('loggedOut'), title: this.i18nService.t("loggedOut"),
text: this.i18nService.t('loginExpired'), text: this.i18nService.t("loginExpired"),
}); });
} }
this.router.navigate(['home']); this.router.navigate(["home"]);
this.stateService.purge(); this.stateService.purge();
}); });
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
}); });
} else if (msg.command === 'authBlocked') { } else if (msg.command === "authBlocked") {
this.ngZone.run(() => { this.ngZone.run(() => {
this.router.navigate(['home']); this.router.navigate(["home"]);
}); });
} else if (msg.command === 'locked') { } else if (msg.command === "locked") {
this.stateService.purge(); this.stateService.purge();
this.ngZone.run(() => { this.ngZone.run(() => {
this.router.navigate(['lock']); this.router.navigate(["lock"]);
}); });
} else if (msg.command === 'showDialog') { } else if (msg.command === "showDialog") {
await this.showDialog(msg); await this.showDialog(msg);
} else if (msg.command === 'showToast') { } else if (msg.command === "showToast") {
this.ngZone.run(() => { this.ngZone.run(() => {
this.showToast(msg); this.showToast(msg);
}); });
} else if (msg.command === 'reloadProcess') { } else if (msg.command === "reloadProcess") {
const windowReload = this.platformUtilsService.isSafari() || const windowReload =
this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera(); this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() ||
this.platformUtilsService.isOpera();
if (windowReload) { if (windowReload) {
// Wait to make sure background has reloaded first. // Wait to make sure background has reloaded first.
window.setTimeout(() => BrowserApi.reloadExtension(window), 2000); window.setTimeout(() => BrowserApi.reloadExtension(window), 2000);
} }
} else if (msg.command === 'reloadPopup') { } else if (msg.command === "reloadPopup") {
this.ngZone.run(() => { this.ngZone.run(() => {
this.router.navigate(['/']); this.router.navigate(["/"]);
}); });
} else if (msg.command === 'convertAccountToKeyConnector') { } else if (msg.command === "convertAccountToKeyConnector") {
this.ngZone.run(async () => { this.ngZone.run(async () => {
await this.keyConnectoService.setConvertAccountRequired(true); await this.keyConnectoService.setConvertAccountRequired(true);
this.router.navigate(['/remove-password']); this.router.navigate(["/remove-password"]);
}); });
} else { } else {
msg.webExtSender = sender; msg.webExtSender = sender;
@@ -119,22 +118,25 @@ export class AppComponent implements OnInit {
} }
}; };
BrowserApi.messageListener('app.component', (window as any).bitwardenPopupMainMessageListener); BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener);
this.router.events.subscribe(event => { this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url || ''; const url = event.urlAfterRedirects || event.url || "";
if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null && if (
(window as any).previousPopupUrl.startsWith('/tabs/')) { url.startsWith("/tabs/") &&
this.stateService.remove('GroupingsComponent'); (window as any).previousPopupUrl != null &&
this.stateService.remove('GroupingsComponentScope'); (window as any).previousPopupUrl.startsWith("/tabs/")
this.stateService.remove('CiphersComponent'); ) {
this.stateService.remove('SendGroupingsComponent'); this.stateService.remove("GroupingsComponent");
this.stateService.remove('SendGroupingsComponentScope'); this.stateService.remove("GroupingsComponentScope");
this.stateService.remove('SendTypeComponent'); this.stateService.remove("CiphersComponent");
this.stateService.remove("SendGroupingsComponent");
this.stateService.remove("SendGroupingsComponentScope");
this.stateService.remove("SendTypeComponent");
} }
if (url.startsWith('/tabs/')) { if (url.startsWith("/tabs/")) {
this.stateService.remove('addEditCipherInfo'); this.stateService.remove("addEditCipherInfo");
} }
(window as any).previousPopupUrl = url; (window as any).previousPopupUrl = url;
@@ -149,18 +151,24 @@ export class AppComponent implements OnInit {
} }
getState(outlet: RouterOutlet) { getState(outlet: RouterOutlet) {
if (outlet.activatedRouteData.state === 'ciphers') { if (outlet.activatedRouteData.state === "ciphers") {
const routeDirection = (window as any).routeDirection != null ? (window as any).routeDirection : ''; const routeDirection =
return 'ciphers_direction=' + routeDirection + '_' + (window as any).routeDirection != null ? (window as any).routeDirection : "";
(outlet.activatedRoute.queryParams as any).value.folderId + '_' + return (
(outlet.activatedRoute.queryParams as any).value.collectionId; "ciphers_direction=" +
routeDirection +
"_" +
(outlet.activatedRoute.queryParams as any).value.folderId +
"_" +
(outlet.activatedRoute.queryParams as any).value.collectionId
);
} else { } else {
return outlet.activatedRouteData.state; return outlet.activatedRouteData.state;
} }
} }
private async recordActivity() { private async recordActivity() {
const now = (new Date()).getTime(); const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) { if (this.lastActivity != null && now - this.lastActivity < 250) {
return; return;
} }
@@ -170,17 +178,19 @@ export class AppComponent implements OnInit {
} }
private showToast(msg: any) { private showToast(msg: any) {
let message = ''; let message = "";
const options: Partial<IndividualConfig> = {}; const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') { if (typeof msg.text === "string") {
message = msg.text; message = msg.text;
} else if (msg.text.length === 1) { } else if (msg.text.length === 1) {
message = msg.text[0]; message = msg.text[0];
} else { } else {
msg.text.forEach((t: string) => msg.text.forEach(
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>')); (t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true; options.enableHtml = true;
} }
if (msg.options != null) { if (msg.options != null) {
@@ -192,7 +202,7 @@ export class AppComponent implements OnInit {
} }
} }
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type); this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
} }
private async showDialog(msg: any) { private async showDialog(msg: any) {
@@ -201,17 +211,17 @@ export class AppComponent implements OnInit {
if (type != null) { if (type != null) {
// If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed. // If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed.
switch (type) { switch (type) {
case 'success': case "success":
iconClasses = 'fa-check text-success'; iconClasses = "fa-check text-success";
break; break;
case 'warning': case "warning":
iconClasses = 'fa-warning text-warning'; iconClasses = "fa-warning text-warning";
break; break;
case 'error': case "error":
iconClasses = 'fa-bolt text-danger'; iconClasses = "fa-bolt text-danger";
break; break;
case 'info': case "info":
iconClasses = 'fa-info-circle text-info'; iconClasses = "fa-info-circle text-info";
break; break;
default: default:
break; break;
@@ -224,18 +234,19 @@ export class AppComponent implements OnInit {
heightAuto: false, heightAuto: false,
buttonsStyling: false, buttonsStyling: false,
icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml. 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, iconHtml:
iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
text: msg.text, text: msg.text,
html: msg.html, html: msg.html,
titleText: msg.title, titleText: msg.title,
showCancelButton: (cancelText != null), showCancelButton: cancelText != null,
cancelButtonText: cancelText, cancelButtonText: cancelText,
showConfirmButton: true, showConfirmButton: true,
confirmButtonText: confirmText == null ? this.i18nService.t('ok') : confirmText, confirmButtonText: confirmText == null ? this.i18nService.t("ok") : confirmText,
timer: 300000, timer: 300000,
}); });
this.messagingService.send('showDialogResolve', { this.messagingService.send("showDialogResolve", {
dialogId: msg.dialogId, dialogId: msg.dialogId,
confirmed: confirmed.value, confirmed: confirmed.value,
}); });

View File

@@ -1,181 +1,177 @@
import { A11yModule } from '@angular/cdk/a11y'; import { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from "@angular/cdk/drag-drop";
import { ScrollingModule } from '@angular/cdk/scrolling'; import { ScrollingModule } from "@angular/cdk/scrolling";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from './services/services.module'; import { ServicesModule } from "./services/services.module";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { EnvironmentComponent } from './accounts/environment.component'; import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from './accounts/hint.component'; import { HintComponent } from "./accounts/hint.component";
import { HomeComponent } from './accounts/home.component'; import { HomeComponent } from "./accounts/home.component";
import { LockComponent } from './accounts/lock.component'; import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from './accounts/login.component'; import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from './accounts/remove-password.component'; import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from './accounts/set-password.component'; import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component'; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
import { PasswordGeneratorComponent } from './generator/password-generator.component'; import { PasswordGeneratorComponent } from "./generator/password-generator.component";
import { AppComponent } from './app.component'; import { AppComponent } from "./app.component";
import { PrivateModeComponent } from './private-mode.component'; import { PrivateModeComponent } from "./private-mode.component";
import { TabsComponent } from './tabs.component'; import { TabsComponent } from "./tabs.component";
import { ExcludedDomainsComponent } from './settings/excluded-domains.component'; import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { ExportComponent } from './settings/export.component'; import { ExportComponent } from "./settings/export.component";
import { FolderAddEditComponent } from './settings/folder-add-edit.component'; import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from './settings/folders.component'; import { FoldersComponent } from "./settings/folders.component";
import { OptionsComponent } from './settings/options.component'; import { OptionsComponent } from "./settings/options.component";
import { PremiumComponent } from './settings/premium.component'; import { PremiumComponent } from "./settings/premium.component";
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from "./settings/settings.component";
import { SyncComponent } from './settings/sync.component'; import { SyncComponent } from "./settings/sync.component";
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component'; import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component'; import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from './vault/attachments.component'; import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from './vault/collections.component'; import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from './vault/current-tab.component'; import { CurrentTabComponent } from "./vault/current-tab.component";
import { GroupingsComponent } from './vault/groupings.component'; import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordHistoryComponent } from './vault/password-history.component'; import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from './vault/share.component'; import { ShareComponent } from "./vault/share.component";
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component'; import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from './vault/view.component'; import { ViewComponent } from "./vault/view.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component'; import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendAddEditComponent } from './send/send-add-edit.component'; import { SendAddEditComponent } from "./send/send-add-edit.component";
import { SendGroupingsComponent } from './send/send-groupings.component'; import { SendGroupingsComponent } from "./send/send-groupings.component";
import { SendTypeComponent } from './send/send-type.component'; import { SendTypeComponent } from "./send/send-type.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive'; import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive'; import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive'; import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive'; import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive'; import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive'; import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive'; import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive'; import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive'; import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive'; import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive'; import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive'; import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe'; import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe'; import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe'; import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { ActionButtonsComponent } from './components/action-buttons.component'; import { ActionButtonsComponent } from "./components/action-buttons.component";
import { CipherRowComponent } from './components/cipher-row.component'; import { CipherRowComponent } from "./components/cipher-row.component";
import { PasswordRepromptComponent } from './components/password-reprompt.component'; import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PopOutComponent } from './components/pop-out.component'; import { PopOutComponent } from "./components/pop-out.component";
import { SendListComponent } from './components/send-list.component'; import { SendListComponent } from "./components/send-list.component";
import { SetPinComponent } from './components/set-pin.component'; import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component'; import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { CalloutComponent } from 'jslib-angular/components/callout.component'; import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from 'jslib-angular/components/icon.component'; import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { import { CurrencyPipe, DatePipe, registerLocaleData } from "@angular/common";
CurrencyPipe, import localeAz from "@angular/common/locales/az";
DatePipe, import localeBe from "@angular/common/locales/be";
registerLocaleData, import localeBg from "@angular/common/locales/bg";
} from '@angular/common'; import localeBn from "@angular/common/locales/bn";
import localeAz from '@angular/common/locales/az'; import localeCa from "@angular/common/locales/ca";
import localeBe from '@angular/common/locales/be'; import localeCs from "@angular/common/locales/cs";
import localeBg from '@angular/common/locales/bg'; import localeDa from "@angular/common/locales/da";
import localeBn from '@angular/common/locales/bn'; import localeDe from "@angular/common/locales/de";
import localeCa from '@angular/common/locales/ca'; import localeEl from "@angular/common/locales/el";
import localeCs from '@angular/common/locales/cs'; import localeEnGb from "@angular/common/locales/en-GB";
import localeDa from '@angular/common/locales/da'; import localeEnIn from "@angular/common/locales/en-IN";
import localeDe from '@angular/common/locales/de'; import localeEs from "@angular/common/locales/es";
import localeEl from '@angular/common/locales/el'; import localeEt from "@angular/common/locales/et";
import localeEnGb from '@angular/common/locales/en-GB'; import localeFa from "@angular/common/locales/fa";
import localeEnIn from '@angular/common/locales/en-IN'; import localeFi from "@angular/common/locales/fi";
import localeEs from '@angular/common/locales/es'; import localeFr from "@angular/common/locales/fr";
import localeEt from '@angular/common/locales/et'; import localeHe from "@angular/common/locales/he";
import localeFa from '@angular/common/locales/fa'; import localeHr from "@angular/common/locales/hr";
import localeFi from '@angular/common/locales/fi'; import localeHu from "@angular/common/locales/hu";
import localeFr from '@angular/common/locales/fr'; import localeId from "@angular/common/locales/id";
import localeHe from '@angular/common/locales/he'; import localeIt from "@angular/common/locales/it";
import localeHr from '@angular/common/locales/hr'; import localeJa from "@angular/common/locales/ja";
import localeHu from '@angular/common/locales/hu'; import localeKn from "@angular/common/locales/kn";
import localeId from '@angular/common/locales/id'; import localeKo from "@angular/common/locales/ko";
import localeIt from '@angular/common/locales/it'; import localeLv from "@angular/common/locales/lv";
import localeJa from '@angular/common/locales/ja'; import localeMl from "@angular/common/locales/ml";
import localeKn from '@angular/common/locales/kn'; import localeNb from "@angular/common/locales/nb";
import localeKo from '@angular/common/locales/ko'; import localeNl from "@angular/common/locales/nl";
import localeLv from '@angular/common/locales/lv'; import localePl from "@angular/common/locales/pl";
import localeMl from '@angular/common/locales/ml'; import localePtBr from "@angular/common/locales/pt";
import localeNb from '@angular/common/locales/nb'; import localePtPt from "@angular/common/locales/pt-PT";
import localeNl from '@angular/common/locales/nl'; import localeRo from "@angular/common/locales/ro";
import localePl from '@angular/common/locales/pl'; import localeRu from "@angular/common/locales/ru";
import localePtBr from '@angular/common/locales/pt'; import localeSk from "@angular/common/locales/sk";
import localePtPt from '@angular/common/locales/pt-PT'; import localeSr from "@angular/common/locales/sr";
import localeRo from '@angular/common/locales/ro'; import localeSv from "@angular/common/locales/sv";
import localeRu from '@angular/common/locales/ru'; import localeTh from "@angular/common/locales/th";
import localeSk from '@angular/common/locales/sk'; import localeTr from "@angular/common/locales/tr";
import localeSr from '@angular/common/locales/sr'; import localeUk from "@angular/common/locales/uk";
import localeSv from '@angular/common/locales/sv'; import localeVi from "@angular/common/locales/vi";
import localeTh from '@angular/common/locales/th'; import localeZhCn from "@angular/common/locales/zh-Hans";
import localeTr from '@angular/common/locales/tr'; import localeZhTw from "@angular/common/locales/zh-Hant";
import localeUk from '@angular/common/locales/uk';
import localeVi from '@angular/common/locales/vi';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
registerLocaleData(localeAz, 'az'); registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, 'be'); registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, 'bg'); registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, 'bn'); registerLocaleData(localeBn, "bn");
registerLocaleData(localeCa, 'ca'); registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, 'cs'); registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, 'da'); registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, 'de'); registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, 'el'); registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, 'en-GB'); registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, 'en-IN'); registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, 'es'); registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, 'et'); registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, 'fa'); registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, 'fi'); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFr, 'fr'); registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, 'he'); registerLocaleData(localeHe, "he");
registerLocaleData(localeHr, 'hr'); registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, 'hu'); registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, 'id'); registerLocaleData(localeId, "id");
registerLocaleData(localeIt, 'it'); registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, 'ja'); registerLocaleData(localeJa, "ja");
registerLocaleData(localeKo, 'ko'); registerLocaleData(localeKo, "ko");
registerLocaleData(localeKn, 'kn'); registerLocaleData(localeKn, "kn");
registerLocaleData(localeLv, 'lv'); registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, 'ml'); registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, 'nb'); registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, 'nl'); registerLocaleData(localeNl, "nl");
registerLocaleData(localePl, 'pl'); registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, 'pt-BR'); registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, 'pt-PT'); registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, 'ro'); registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, 'ru'); registerLocaleData(localeRu, "ru");
registerLocaleData(localeSk, 'sk'); registerLocaleData(localeSk, "sk");
registerLocaleData(localeSr, 'sr'); registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, 'sv'); registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, 'th'); registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, 'tr'); registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, 'uk'); registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, 'vi'); registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, 'zh-CN'); registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, 'zh-TW'); registerLocaleData(localeZhTw, "zh-TW");
@NgModule({ @NgModule({
imports: [ imports: [
@@ -192,7 +188,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
maxOpened: 2, maxOpened: 2,
autoDismiss: true, autoDismiss: true,
closeButton: true, closeButton: true,
positionClass: 'toast-bottom-full-width', positionClass: "toast-bottom-full-width",
}), }),
], ],
declarations: [ declarations: [
@@ -263,10 +259,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
RemovePasswordComponent, RemovePasswordComponent,
], ],
entryComponents: [], entryComponents: [],
providers: [ providers: [CurrencyPipe, DatePipe],
CurrencyPipe,
DatePipe,
],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}

View File

@@ -1,41 +1,87 @@
<span class="row-btn" (click)="view()" appStopClick appStopProp appA11yTitle="{{'view' | i18n}}" *ngIf="showView"> <span
class="row-btn"
(click)="view()"
appStopClick
appStopProp
appA11yTitle="{{ 'view' | i18n }}"
*ngIf="showView"
>
<i class="fa fa-lg fa-list-alt" aria-hidden="true"></i> <i class="fa fa-lg fa-list-alt" aria-hidden="true"></i>
</span> </span>
<ng-container *ngIf="cipher.type === cipherType.Login"> <ng-container *ngIf="cipher.type === cipherType.Login">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'launch' | i18n}}" (click)="launchCipher()" <span
*ngIf="!showView" [ngClass]="{disabled: !cipher.login.canLaunch}"> class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchCipher()"
*ngIf="!showView"
[ngClass]="{ disabled: !cipher.login.canLaunch }"
>
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i> <i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyUsername' | i18n}}" <span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher, cipher.login.username, 'username', 'Username')" (click)="copy(cipher, cipher.login.username, 'username', 'Username')"
[ngClass]="{disabled: !cipher.login.username}"> [ngClass]="{ disabled: !cipher.login.username }"
>
<i class="fa fa-lg fa-user" aria-hidden="true"></i> <i class="fa fa-lg fa-user" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyPassword' | i18n}}" <span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher, cipher.login.password, 'password', 'Password')" (click)="copy(cipher, cipher.login.password, 'password', 'Password')"
[ngClass]="{disabled: (!cipher.login.password || !cipher.viewPassword)}"> [ngClass]="{ disabled: !cipher.login.password || !cipher.viewPassword }"
>
<i class="fa fa-lg fa-key" aria-hidden="true"></i> <i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyVerificationCode' | i18n}}" <span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')" (click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')"
[ngClass]="{disabled: (!displayTotpCopyButton(cipher))}"> [ngClass]="{ disabled: !displayTotpCopyButton(cipher) }"
>
<i class="fa fa-lg fa-clock-o" aria-hidden="true"></i> <i class="fa fa-lg fa-clock-o" aria-hidden="true"></i>
</span> </span>
</ng-container> </ng-container>
<ng-container *ngIf="cipher.type === cipherType.Card"> <ng-container *ngIf="cipher.type === cipherType.Card">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNumber' | i18n}}" <span
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')" [ngClass]="{disabled: !cipher.card.number}"> class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')"
[ngClass]="{ disabled: !cipher.card.number }"
>
<i class="fa fa-lg fa-hashtag" aria-hidden="true"></i> <i class="fa fa-lg fa-hashtag" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySecurityCode' | i18n}}" <span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
(click)="copy(cipher, cipher.card.code, 'securityCode', 'Security Code')" (click)="copy(cipher, cipher.card.code, 'securityCode', 'Security Code')"
[ngClass]="{disabled: !cipher.card.code}"> [ngClass]="{ disabled: !cipher.card.code }"
>
<i class="fa fa-lg fa-key" aria-hidden="true"></i> <i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span> </span>
</ng-container> </ng-container>
<ng-container *ngIf="cipher.type === cipherType.SecureNote"> <ng-container *ngIf="cipher.type === cipherType.SecureNote">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNote' | i18n}}" <span
(click)="copy(cipher, cipher.notes, 'note', 'Note')" [ngClass]="{disabled: !cipher.notes}"> class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyNote' | i18n }}"
(click)="copy(cipher, cipher.notes, 'note', 'Note')"
[ngClass]="{ disabled: !cipher.notes }"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</span> </span>
</ng-container> </ng-container>

View File

@@ -1,26 +1,21 @@
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 { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.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>();
@@ -31,10 +26,14 @@ export class ActionButtonsComponent {
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 userService: UserService, private platformUtilsService: PlatformUtilsService,
private passwordRepromptService: PasswordRepromptService) { } private eventService: EventService,
private totpService: TotpService,
private userService: UserService,
private passwordRepromptService: PasswordRepromptService
) {}
async ngOnInit() { async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium(); this.userHasPremiumAccess = await this.userService.canAccessPremium();
@@ -45,12 +44,15 @@ export class ActionButtonsComponent {
} }
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) && if (
!await this.passwordRepromptService.showPasswordPrompt()) { this.cipher.reprompt !== CipherRepromptType.None &&
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.passwordRepromptService.showPasswordPrompt())
) {
return; return;
} }
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) { if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) {
return; return;
} else if (value === cipher.login.totp) { } else if (value === cipher.login.totp) {
value = await this.totpService.getCode(value); value = await this.totpService.getCode(value);
@@ -61,19 +63,23 @@ export class ActionButtonsComponent {
} }
this.platformUtilsService.copyToClipboard(value, { window: window }); this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast('info', null, this.platformUtilsService.showToast(
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey))); "info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
);
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') { if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id); this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
} else if (typeI18nKey === 'securityCode') { } else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
} }
} }
displayTotpCopyButton(cipher: CipherView) { displayTotpCopyButton(cipher: CipherView) {
return (cipher?.login?.hasTotp ?? false) && return (
(cipher.organizationUseTotp || this.userHasPremiumAccess); (cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess)
);
} }
view() { view() {

View File

@@ -1,23 +1,38 @@
<button type="button" (click)="selectCipher(cipher)" (dblclick)="launchCipher(cipher)" appStopClick <button
title="{{title}} - {{cipher.name}}" class="box-content-row box-content-row-flex virtual-scroll-item"> type="button"
(click)="selectCipher(cipher)"
(dblclick)="launchCipher(cipher)"
appStopClick
title="{{ title }} - {{ cipher.name }}"
class="box-content-row box-content-row-flex virtual-scroll-item"
>
<div class="row-main"> <div class="row-main">
<app-vault-icon [cipher]="cipher"></app-vault-icon> <app-vault-icon [cipher]="cipher"></app-vault-icon>
<div class="row-main-content"> <div class="row-main-content">
<span class="text"> <span class="text">
{{cipher.name}} {{ cipher.name }}
<ng-container *ngIf="cipher.organizationId"> <ng-container *ngIf="cipher.organizationId">
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i> <i class="fa fa-cube text-muted" title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span> <span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="cipher.hasAttachments"> <ng-container *ngIf="cipher.hasAttachments">
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'attachments' | i18n}}</span> class="fa fa-paperclip text-muted"
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container> </ng-container>
</span> </span>
<span class="detail">{{cipher.subTitle}}</span> <span class="detail">{{ cipher.subTitle }}</span>
</div> </div>
</div> </div>
<app-action-buttons [cipher]="cipher" [showView]="showView" (onView)="viewCipher(cipher)" (launchEvent)="launchCipher(cipher)" <app-action-buttons
class="action-buttons"> [cipher]="cipher"
[showView]="showView"
(onView)="viewCipher(cipher)"
(launchEvent)="launchCipher(cipher)"
class="action-buttons"
>
</app-action-buttons> </app-action-buttons>
</button> </button>

View File

@@ -1,15 +1,10 @@
import { import { Component, EventEmitter, Input, Output } from "@angular/core";
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from "jslib-common/models/view/cipherView";
@Component({ @Component({
selector: 'app-cipher-row', selector: "app-cipher-row",
templateUrl: 'cipher-row.component.html', templateUrl: "cipher-row.component.html",
}) })
export class CipherRowComponent { export class CipherRowComponent {
@Output() onSelected = new EventEmitter<CipherView>(); @Output() onSelected = new EventEmitter<CipherView>();

View File

@@ -3,34 +3,52 @@
<form class="modal-content" #form (ngSubmit)="submit()"> <form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<h1 class="box-header">{{'passwordConfirmation' | i18n}}</h1> <h1 class="box-header">{{ "passwordConfirmation" | i18n }}</h1>
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" <input
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus> id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appAutofocus
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'passwordConfirmationDesc' | i18n}} {{ "passwordConfirmationDesc" | i18n }}
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick> <button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span> <span>{{ "ok" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component'; import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({ @Component({
templateUrl: 'password-reprompt.component.html', templateUrl: "password-reprompt.component.html",
}) })
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@@ -1,5 +1,5 @@
<ng-container> <ng-container>
<button type="button" (click)="expand()" appA11yTitle="{{'popOutNewWindow' | i18n}}"> <button type="button" (click)="expand()" appA11yTitle="{{ 'popOutNewWindow' | i18n }}">
<i class="fa fa-external-link fa-rotate-270 fa-lg fa-fw" aria-hidden="true"></i> <i class="fa fa-external-link fa-rotate-270 fa-lg fa-fw" aria-hidden="true"></i>
</button> </button>
</ng-container> </ng-container>

View File

@@ -1,22 +1,20 @@
import { import { Component, Input, OnInit } from "@angular/core";
Component,
Input,
OnInit,
} from '@angular/core';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PopupUtilsService } from '../services/popup-utils.service'; import { PopupUtilsService } from "../services/popup-utils.service";
@Component({ @Component({
selector: 'app-pop-out', selector: "app-pop-out",
templateUrl: 'pop-out.component.html', templateUrl: "pop-out.component.html",
}) })
export class PopOutComponent implements OnInit { export class PopOutComponent implements OnInit {
@Input() show = true; @Input() show = true;
constructor(private platformUtilsService: PlatformUtilsService, constructor(
private popupUtilsService: PopupUtilsService) { } private platformUtilsService: PlatformUtilsService,
private popupUtilsService: PopupUtilsService
) {}
ngOnInit() { ngOnInit() {
if (this.show) { if (this.show) {

View File

@@ -1,5 +1,11 @@
<button type="button" *ngFor="let s of sends" (click)="selectSend(s)" appStopClick title="{{title}} - {{s.name}}" <button
class="box-content-row box-content-row-flex"> type="button"
*ngFor="let s of sends"
(click)="selectSend(s)"
appStopClick
title="{{ title }} - {{ s.name }}"
class="box-content-row box-content-row-flex"
>
<div class="row-main"> <div class="row-main">
<div class="app-vault-icon"> <div class="app-vault-icon">
<div class="icon" aria-hidden="true"> <div class="icon" aria-hidden="true">
@@ -9,41 +15,75 @@
</div> </div>
<div class="row-main-content"> <div class="row-main-content">
<span class="text"> <span class="text">
{{s.name}} {{ s.name }}
<ng-container *ngIf="s.disabled"> <ng-container *ngIf="s.disabled">
<i class="fa fa-warning text-muted" title="{{'disabled' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'disabled' | i18n}}</span> class="fa fa-warning text-muted"
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.password"> <ng-container *ngIf="s.password">
<i class="fa fa-key text-muted" title="{{'passwordProtected' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'passwordProtected' | i18n}}</span> class="fa fa-key text-muted"
title="{{ 'passwordProtected' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "passwordProtected" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.maxAccessCountReached"> <ng-container *ngIf="s.maxAccessCountReached">
<i class="fa fa-ban text-muted" title="{{'maxAccessCountReached' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span> class="fa fa-ban text-muted"
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.expired"> <ng-container *ngIf="s.expired">
<i class="fa fa-clock-o text-muted" title="{{'expired' | i18n}}" aria-hidden="true"></i> <i class="fa fa-clock-o text-muted" title="{{ 'expired' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span> <span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.pendingDelete"> <ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash text-muted" title="{{'pendingDeletion' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'pendingDeletion' | i18n}}</span> class="fa fa-trash text-muted"
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container> </ng-container>
</span> </span>
<span class="detail">{{s.deletionDate | date:'medium'}}</span> <span class="detail">{{ s.deletionDate | date: "medium" }}</span>
</div> </div>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySendLink' | i18n}}" <span
(click)="copySendLink(s)"> class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copySendLink' | i18n }}"
(click)="copySendLink(s)"
>
<i class="fa fa-lg fa-copy" aria-hidden="true"></i> <i class="fa fa-lg fa-copy" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" [ngClass]="{'disabled' : disabledByPolicy}" appStopClick appStopProp <span
appA11yTitle="{{'removePassword' | i18n}}" (click)="removePassword(s)" *ngIf="s.password"> class="row-btn"
[ngClass]="{ disabled: disabledByPolicy }"
appStopClick
appStopProp
appA11yTitle="{{ 'removePassword' | i18n }}"
(click)="removePassword(s)"
*ngIf="s.password"
>
<i class="fa fa-lg fa-undo" aria-hidden="true"></i> <i class="fa fa-lg fa-undo" aria-hidden="true"></i>
</span> </span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'delete' | i18n}}" (click)="delete(s)"> <span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(s)"
>
<i class="fa fa-lg fa-trash-o" aria-hidden="true"></i> <i class="fa fa-lg fa-trash-o" aria-hidden="true"></i>
</span> </span>
</div> </div>

View File

@@ -1,17 +1,12 @@
import { import { Component, EventEmitter, Input, Output } from "@angular/core";
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { SendView } from 'jslib-common/models/view/sendView'; import { SendView } from "jslib-common/models/view/sendView";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
@Component({ @Component({
selector: 'app-send-list', selector: "app-send-list",
templateUrl: 'send-list.component.html', templateUrl: "send-list.component.html",
}) })
export class SendListComponent { export class SendListComponent {
@Input() sends: SendView[]; @Input() sends: SendView[];

View File

@@ -3,21 +3,38 @@
<form class="modal-content" #form (ngSubmit)="submit()"> <form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body"> <div class="modal-body">
<div> <div>
{{'setYourPinCode' | i18n}} {{ "setYourPinCode" | i18n }}
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="pin">{{'pin' | i18n}}</label> <label for="pin">{{ "pin" | i18n }}</label>
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin" <input
class="monospaced" [(ngModel)]="pin" required appInputVerbatim> id="pin"
type="{{ showPin ? 'text' : 'password' }}"
name="Pin"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick appA11yTitle="{{'toggleVisibility' | i18n}}" <button
(click)="toggleVisibility()" [attr.aria-pressed]="showPin"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="row-btn"
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleVisibility()"
[attr.aria-pressed]="showPin"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -25,18 +42,22 @@
</div> </div>
<div class="checkbox" *ngIf="showMasterPassOnRestart"> <div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart"> <label for="masterPasswordOnRestart">
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart" <input
[(ngModel)]="masterPassOnRestart"> type="checkbox"
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span> id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label> </label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick> <button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span> <span>{{ "ok" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { SetPinComponent as BaseSetPinComponent } from 'jslib-angular/components/set-pin.component'; import { SetPinComponent as BaseSetPinComponent } from "jslib-angular/components/set-pin.component";
@Component({ @Component({
templateUrl: 'set-pin.component.html', templateUrl: "set-pin.component.html",
}) })
export class SetPinComponent extends BaseSetPinComponent {} export class SetPinComponent extends BaseSetPinComponent {}

View File

@@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector"> <ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" <input
[formControl]="secret" required appAutofocus appInputVerbatim> id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="usesKeyConnector"> <ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label class="d-block">{{'sendVerificationCode' | i18n}}</label> <label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP"> <button
{{'sendCode' | i18n}} type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button> </button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode"> <span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i> <i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}} {{ "codeSent" | i18n }}
</span> </span>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="verificationCode">{{'verificationCode' | i18n}}</label> <label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control" <input
[formControl]="secret" required appAutofocus appInputVerbatim> id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div> </div>
</ng-container> </ng-container>

View File

@@ -1,17 +1,12 @@
import { import { animate, style, transition, trigger } from "@angular/animations";
animate, import { Component } from "@angular/core";
style, import { NG_VALUE_ACCESSOR } from "@angular/forms";
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/components/verify-master-password.component'; import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({ @Component({
selector: 'app-verify-master-password', selector: "app-verify-master-password",
templateUrl: 'verify-master-password.component.html', templateUrl: "verify-master-password.component.html",
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@@ -20,12 +15,9 @@ import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/co
}, },
], ],
animations: [ animations: [
trigger('sent', [ trigger("sent", [
transition(':enter', [ transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]), ]),
], ],
}) })
export class VerifyMasterPasswordComponent extends BaseComponent { } export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@@ -2,15 +2,15 @@
<div class="left"> <div class="left">
<button type="button" appBlurClick type="button" (click)="close()"> <button type="button" appBlurClick type="button" (click)="close()">
<span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span> <span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span>
<span>{{'back' | i18n}}</span> <span>{{ "back" | i18n }}</span>
</button> </button>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'passwordHistory' | i18n}}</span> <span class="title">{{ "passwordHistory" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="button" appBlurClick type="button" (click)="clear()"> <button type="button" appBlurClick type="button" (click)="clear()">
{{'clear' | i18n}} {{ "clear" | i18n }}
</button> </button>
</div> </div>
</header> </header>
@@ -20,14 +20,22 @@
<div class="box-content-row box-content-row-flex" *ngFor="let h of history"> <div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main"> <div class="row-main">
<div class="row-main-content"> <div class="row-main-content">
<div class="monospaced password-wrapper" appSelectCopy <div
[innerHTML]="h.password | colorPassword"></div> class="monospaced password-wrapper"
<span class="detail">{{h.date | date:'medium'}}</span> appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div> </div>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" class="row-btn" appStopClick appA11yTitle="{{'copyPassword' | i18n}}" <button
(click)="copy(h.password)"> type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -35,6 +43,6 @@
</div> </div>
</div> </div>
<div class="no-items" *ngIf="!history || !history.length"> <div class="no-items" *ngIf="!history || !history.length">
<p>{{'noPasswordsInList' | i18n}}</p> <p>{{ "noPasswordsInList" | i18n }}</p>
</div> </div>
</content> </content>

View File

@@ -1,21 +1,23 @@
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 { import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
@Component({ @Component({
selector: 'app-password-generator-history', selector: "app-password-generator-history",
templateUrl: 'password-generator-history.component.html', templateUrl: "password-generator-history.component.html",
}) })
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService, private location: Location) { passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
private location: Location
) {
super(passwordGenerationService, platformUtilsService, i18nService, window); super(passwordGenerationService, platformUtilsService, i18nService, window);
} }

View File

@@ -1,50 +1,75 @@
<header> <header>
<div class="left"> <div class="left">
<app-pop-out [show]="!showSelect"></app-pop-out> <app-pop-out [show]="!showSelect"></app-pop-out>
<button type="button" appBlurClick (click)="close()" *ngIf="showSelect">{{'cancel' | i18n}}</button> <button type="button" appBlurClick (click)="close()" *ngIf="showSelect">
{{ "cancel" | i18n }}
</button>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{'passGen' | i18n}}</span> <span class="title">{{ "passGen" | i18n }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">{{'select' | i18n}}</button> <button type="button" appBlurClick (click)="select()" *ngIf="showSelect">
{{ "select" | i18n }}
</button>
</div> </div>
</header> </header>
<content> <content>
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()"> <app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{'passwordGeneratorPolicyInEffect' | i18n}} {{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<div class="password-block"> <div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div> <div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div> </div>
<div class="box list"> <div class="box list">
<div class="box-content single-line"> <div class="box-content single-line">
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick <button
(click)="regenerate()">{{'regeneratePassword' | i18n}}</button> type="button"
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick class="box-content-row text-primary"
(click)="copy()">{{'copyPassword' | i18n}}</button> appStopClick
appBlurClick
(click)="regenerate()"
>
{{ "regeneratePassword" | i18n }}
</button>
<button
type="button"
class="box-content-row text-primary"
appStopClick
appBlurClick
(click)="copy()"
>
{{ "copyPassword" | i18n }}
</button>
</div> </div>
</div> </div>
<div class="box list"> <div class="box list">
<div class="box-content single-line"> <div class="box-content single-line">
<a class="box-content-row box-content-row-flex" routerLink="/generator-history"> <a class="box-content-row box-content-row-flex" routerLink="/generator-history">
<div class="row-main">{{'passwordHistory' | i18n}}</div> <div class="row-main">{{ "passwordHistory" | i18n }}</div>
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i> <i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<h2 class="box-header"> <h2 class="box-header">
{{'options' | i18n}} {{ "options" | i18n }}
</h2> </h2>
<div class="box-content"> <div class="box-content">
<div class="box-content-row"> <div class="box-content-row">
<label class="sr-only radio-header">{{'type' | i18n}}</label> <label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions"> <div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
<input type="radio" [(ngModel)]="options.type" name="Type_{{o.value}}" id="type_{{o.value}}" <input
[value]="o.value" (change)="saveOptions()" [checked]="options.type === o.value"> type="radio"
<label for="type_{{o.value}}"> [(ngModel)]="options.type"
{{o.name}} name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label for="type_{{ o.value }}">
{{ o.name }}
</label> </label>
</div> </div>
</div> </div>
@@ -53,24 +78,45 @@
<div class="box" *ngIf="options.type === 'passphrase'"> <div class="box" *ngIf="options.type === 'passphrase'">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{'numWords' | i18n}}</label> <label for="num-words">{{ "numWords" | i18n }}</label>
<input id="num-words" type="number" min="3" max="20" (change)="saveOptions()" <input
[(ngModel)]="options.numWords"> id="num-words"
type="number"
min="3"
max="20"
(change)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div> </div>
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{'wordSeparator' | i18n}}</label> <label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input id="word-separator" type="text" maxlength="1" (input)="saveOptions()" <input
[(ngModel)]="options.wordSeparator"> id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{'capitalize' | i18n}}</label> <label for="capitalize">{{ "capitalize" | i18n }}</label>
<input id="capitalize" type="checkbox" (change)="saveOptions()" [(ngModel)]="options.capitalize" <input
[disabled]="enforcedPolicyOptions?.capitalize"> id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{'includeNumber' | i18n}}</label> <label for="include-number">{{ "includeNumber" | i18n }}</label>
<input id="include-number" type="checkbox" (change)="saveOptions()" [(ngModel)]="options.includeNumber" <input
[disabled]="enforcedPolicyOptions?.includeNumber"> id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -78,49 +124,104 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-slider" appBoxRow> <div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{'length' | i18n}}</label> <label for="length">{{ "length" | i18n }}</label>
<input id="length" type="number" min="5" max="128" [(ngModel)]="options.length" <input
(change)="saveOptions()"> id="length"
<input id="lengthRange" type="range" min="5" max="128" step="1" [(ngModel)]="options.length" type="number"
(change)="sliderChanged()" (input)="sliderInput()"> min="5"
max="128"
[(ngModel)]="options.length"
(change)="saveOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="options.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label> <label for="uppercase">A-Z</label>
<input id="uppercase" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'uppercase' | i18n}}" <input
[disabled]="enforcedPolicyOptions.useUppercase" [(ngModel)]="options.uppercase"> id="uppercase"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'uppercase' | i18n }}"
[disabled]="enforcedPolicyOptions.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label> <label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'lowercase' | i18n}}" <input
[disabled]="enforcedPolicyOptions.useLowercase" [(ngModel)]="options.lowercase"> id="lowercase"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'lowercase' | i18n }}"
[disabled]="enforcedPolicyOptions.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label> <label for="numbers">0-9</label>
<input id="numbers" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'numbers' | i18n}}" <input
[disabled]="enforcedPolicyOptions.useNumbers" [(ngModel)]="options.number"> id="numbers"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'numbers' | i18n }}"
[disabled]="enforcedPolicyOptions.useNumbers"
[(ngModel)]="options.number"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label> <label for="special">!@#$%^&*</label>
<input id="special" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'specialCharacters' | i18n}}" <input
[disabled]="enforcedPolicyOptions.useSpecial" [(ngModel)]="options.special"> id="special"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
[disabled]="enforcedPolicyOptions.useSpecial"
[(ngModel)]="options.special"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{'minNumbers' | i18n}}</label> <label for="min-number">{{ "minNumbers" | i18n }}</label>
<input id="min-number" type="number" min="0" max="9" (change)="saveOptions()" <input
[(ngModel)]="options.minNumber"> id="min-number"
type="number"
min="0"
max="9"
(change)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div> </div>
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{'minSpecial' | i18n}}</label> <label for="min-special">{{ "minSpecial" | i18n }}</label>
<input id="min-special" type="number" min="0" max="9" (change)="saveOptions()" <input
[(ngModel)]="options.minSpecial"> id="min-special"
type="number"
min="0"
max="9"
(change)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'avoidAmbChar' | i18n}}</label> <label for="ambiguous">{{ "avoidAmbChar" | i18n }}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()" [(ngModel)]="avoidAmbiguous"> <input
id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,33 +1,35 @@
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 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/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,
i18nService: I18nService,
private stateService: StateService,
private location: Location
) {
super(passwordGenerationService, platformUtilsService, i18nService, window); super(passwordGenerationService, platformUtilsService, i18nService, window);
} }
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
const addEditCipherInfo = await this.stateService.get<any>('addEditCipherInfo'); const addEditCipherInfo = await this.stateService.get<any>("addEditCipherInfo");
if (addEditCipherInfo != null) { if (addEditCipherInfo != null) {
this.cipherState = addEditCipherInfo.cipher; this.cipherState = addEditCipherInfo.cipher;
} }

View File

@@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="__BROWSER__"> <html class="__BROWSER__">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden</title> <title>Bitwarden</title>
<base href=""> <base href="" />
</head> </head>
<body> <body>
<app-root> <app-root>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div> <div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
</app-root> </app-root>
</body> </body>
</html> </html>

View File

@@ -1,12 +1,12 @@
import { enableProdMode } from '@angular/core'; import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
// tslint:disable-next-line // tslint:disable-next-line
require('./scss/popup.scss'); require("./scss/popup.scss");
import { AppModule } from './app.module'; import { AppModule } from "./app.module";
if (process.env.ENV === 'production') { if (process.env.ENV === "production") {
enableProdMode(); enableProdMode();
} }

View File

@@ -1,6 +1,6 @@
/* tslint:disable */ /* tslint:disable */
import 'core-js/stable'; import "core-js/stable";
import 'date-input-polyfill'; import "date-input-polyfill";
import 'web-animations-js'; import "web-animations-js";
import 'zone.js/dist/zone'; import "zone.js/dist/zone";
/* tslint:enable */ /* tslint:enable */

View File

@@ -1,6 +1,6 @@
<div class="content"> <div class="content">
<p class="text-center">{{privateModeMessage}}</p> <p class="text-center">{{ privateModeMessage }}</p>
<button type="button" class="btn primary block" (click)="learnMore()"> <button type="button" class="btn primary block" (click)="learnMore()">
<b>{{learnMoreMessage}}</b> <b>{{ learnMoreMessage }}</b>
</button> </button>
</div> </div>

View File

@@ -1,24 +1,23 @@
import { BrowserApi } from '../browser/browserApi'; import { BrowserApi } from "../browser/browserApi";
import { import { Component, OnInit } from "@angular/core";
Component,
OnInit,
} from '@angular/core';
@Component({ @Component({
selector: 'app-private-mode', selector: "app-private-mode",
templateUrl: 'private-mode.component.html', templateUrl: "private-mode.component.html",
}) })
export class PrivateModeComponent implements OnInit { export class PrivateModeComponent implements OnInit {
privateModeMessage: string; privateModeMessage: string;
learnMoreMessage: string; learnMoreMessage: string;
ngOnInit() { ngOnInit() {
this.privateModeMessage = chrome.i18n.getMessage('privateModeMessage'); this.privateModeMessage = chrome.i18n.getMessage("privateModeMessage");
this.learnMoreMessage = chrome.i18n.getMessage('learnMore'); this.learnMoreMessage = chrome.i18n.getMessage("learnMore");
} }
learnMore() { learnMore() {
BrowserApi.createNewTab('https://help.bitwarden.com/article/extension-wont-load-in-private-mode/'); BrowserApi.createNewTab(
"https://help.bitwarden.com/article/extension-wont-load-in-private-mode/"
);
} }
} }

View File

@@ -6,7 +6,8 @@
margin: 0; margin: 0;
} }
html, body { html,
body {
font-family: $font-family-sans-serif; font-family: $font-family-sans-serif;
font-size: $font-size-base; font-size: $font-size-base;
line-height: $line-height-base; line-height: $line-height-base;
@@ -21,8 +22,8 @@ body {
background-color: $background-color; background-color: $background-color;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
background-color: themed('backgroundColor'); background-color: themed("backgroundColor");
} }
&.body-sm { &.body-sm {
@@ -41,7 +42,12 @@ body {
} }
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-family-sans-serif; font-family: $font-family-sans-serif;
font-size: $font-size-base; font-size: $font-size-base;
font-weight: normal; font-weight: normal;
@@ -51,7 +57,8 @@ p {
margin-bottom: 10px; margin-bottom: 10px;
} }
ul, ol { ul,
ol {
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -63,24 +70,30 @@ a {
text-decoration: none; text-decoration: none;
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor'); color: themed("primaryColor");
} }
&:hover, &:focus { &:hover,
&:focus {
@include themify($themes) { @include themify($themes) {
color: darken(themed('primaryColor'), 6%); color: darken(themed("primaryColor"), 6%);
} }
} }
} }
input, select, textarea { input,
select,
textarea {
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
background-color: themed('inputBackgroundColor'); background-color: themed("inputBackgroundColor");
} }
} }
input, select, textarea, button { input,
select,
textarea,
button {
font-size: $font-size-base; font-size: $font-size-base;
font-family: $font-family-sans-serif; font-family: $font-family-sans-serif;
} }
@@ -98,7 +111,8 @@ main {
height: 100%; height: 100%;
} }
content::-webkit-scrollbar, cdk-virtual-scroll-viewport::-webkit-scrollbar { content::-webkit-scrollbar,
cdk-virtual-scroll-viewport::-webkit-scrollbar {
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
@@ -109,21 +123,22 @@ content::-webkit-scrollbar-track {
cdk-virtual-scroll-viewport::-webkit-scrollbar-track { cdk-virtual-scroll-viewport::-webkit-scrollbar-track {
@include themify($themes) { @include themify($themes) {
background-color: themed('backgroundColor'); background-color: themed("backgroundColor");
} }
} }
content::-webkit-scrollbar-thumb, cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb { content::-webkit-scrollbar-thumb,
cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
margin-right: 1px; margin-right: 1px;
@include themify($themes) { @include themify($themes) {
background-color: themed('scrollbarColor'); background-color: themed("scrollbarColor");
} }
&:hover { &:hover {
@include themify($themes) { @include themify($themes) {
background-color: themed('scrollbarHoverColor'); background-color: themed("scrollbarHoverColor");
} }
} }
} }
@@ -135,12 +150,13 @@ header {
border-bottom: 1px solid #000000; border-bottom: 1px solid #000000;
@include themify($themes) { @include themify($themes) {
color: themed('headerColor'); color: themed("headerColor");
background-color: themed('headerBackgroundColor'); background-color: themed("headerBackgroundColor");
border-bottom-color: themed('headerBorderColor'); border-bottom-color: themed("headerBorderColor");
} }
.left, .right { .left,
.right {
flex: 1; flex: 1;
display: flex; display: flex;
min-width: -webkit-min-content; /* Workaround to Chrome bug */ min-width: -webkit-min-content; /* Workaround to Chrome bug */
@@ -160,7 +176,9 @@ header {
min-width: 0; min-width: 0;
} }
app-pop-out > button, div > button, div > a { app-pop-out > button,
div > button,
div > a {
border: none; border: none;
padding: 0 10px; padding: 0 10px;
text-decoration: none; text-decoration: none;
@@ -170,14 +188,15 @@ header {
align-items: center; align-items: center;
@include themify($themes) { @include themify($themes) {
color: themed('headerColor'); color: themed("headerColor");
background-color: themed('headerBackgroundColor'); background-color: themed("headerBackgroundColor");
} }
&:hover, &:focus { &:hover,
&:focus {
@include themify($themes) { @include themify($themes) {
background-color: themed('headerBackgroundHoverColor'); background-color: themed("headerBackgroundHoverColor");
color: themed('headerColor'); color: themed("headerColor");
} }
} }
@@ -219,7 +238,7 @@ header {
left: 20px; left: 20px;
@include themify($themes) { @include themify($themes) {
color: themed('headerInputPlaceholderColor'); color: themed("headerInputPlaceholderColor");
} }
} }
@@ -231,8 +250,8 @@ header {
border-radius: $border-radius; border-radius: $border-radius;
@include themify($themes) { @include themify($themes) {
background-color: themed('headerInputBackgroundColor'); background-color: themed("headerInputBackgroundColor");
color: themed('headerInputColor'); color: themed("headerInputColor");
} }
&:focus { &:focus {
@@ -240,13 +259,13 @@ header {
outline: none; outline: none;
@include themify($themes) { @include themify($themes) {
background-color: themed('headerInputBackgroundFocusColor'); background-color: themed("headerInputBackgroundFocusColor");
} }
} }
&::-webkit-input-placeholder { &::-webkit-input-placeholder {
@include themify($themes) { @include themify($themes) {
color: themed('headerInputPlaceholderColor'); color: themed("headerInputPlaceholderColor");
} }
} }
} }
@@ -280,8 +299,8 @@ header {
overflow: hidden; overflow: hidden;
@include themify($themes) { @include themify($themes) {
background-color: themed('tabBackgroundColor'); background-color: themed("tabBackgroundColor");
border-top-color: themed('borderColor'); border-top-color: themed("borderColor");
} }
ul { ul {
@@ -307,12 +326,13 @@ header {
text-overflow: ellipsis; text-overflow: ellipsis;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
&:hover, &:focus { &:hover,
&:focus {
@include themify($themes) { @include themify($themes) {
background-color: themed('tabBackgroundHoverColor'); background-color: themed("tabBackgroundHoverColor");
} }
} }
@@ -326,7 +346,7 @@ header {
&.active { &.active {
a { a {
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor'); color: themed("primaryColor");
} }
} }
} }
@@ -340,7 +360,7 @@ app-root {
height: 100%; height: 100%;
z-index: 980; z-index: 980;
@include themify($themes) { @include themify($themes) {
background-color: themed('backgroundColor'); background-color: themed("backgroundColor");
} }
} }
@@ -380,7 +400,7 @@ content {
overflow-x: hidden; overflow-x: hidden;
@include themify($themes) { @include themify($themes) {
background-color: themed('backgroundColor'); background-color: themed("backgroundColor");
} }
&.no-header { &.no-header {
@@ -404,7 +424,9 @@ content {
} }
} }
.center-content, .no-items, .full-loading-spinner { .center-content,
.no-items,
.full-loading-spinner {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -413,14 +435,15 @@ content {
flex-grow: 1; flex-grow: 1;
} }
.no-items, .full-loading-spinner { .no-items,
.full-loading-spinner {
text-align: center; text-align: center;
.fa { .fa {
margin-bottom: 10px; margin-bottom: 10px;
@include themify($themes) { @include themify($themes) {
color: themed('disabledIconColor'); color: themed("disabledIconColor");
} }
} }
} }

View File

@@ -15,7 +15,7 @@
display: flex; display: flex;
@include themify($themes) { @include themify($themes) {
color: themed('headingColor'); color: themed("headingColor");
} }
} }
@@ -28,12 +28,14 @@
box-sizing: border-box; box-sizing: border-box;
@include themify($themes) { @include themify($themes) {
color: themed('headingColor'); color: themed("headingColor");
} }
&:hover, &:focus, &.active { &:hover,
&:focus,
&.active {
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundHoverColor'); background-color: themed("boxBackgroundHoverColor");
} }
} }
@@ -43,7 +45,7 @@
margin-left: 5px; margin-left: 5px;
@include themify($themes) { @include themify($themes) {
color: themed('headingColor'); color: themed("headingColor");
} }
} }
} }
@@ -53,21 +55,24 @@
border-bottom: 1px solid #000000; border-bottom: 1px solid #000000;
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundColor'); background-color: themed("boxBackgroundColor");
border-color: themed('borderColor'); border-color: themed("borderColor");
} }
&.box-content-padded { &.box-content-padded {
padding: 10px 15px; padding: 10px 15px;
} }
&.condensed .box-content-row, .box-content-row.condensed { &.condensed .box-content-row,
.box-content-row.condensed {
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
} }
&.no-hover .box-content-row, .box-content-row.no-hover { &.no-hover .box-content-row,
&:hover, &:focus { .box-content-row.no-hover {
&:hover,
&:focus {
background-color: initial !important; background-color: initial !important;
} }
} }
@@ -78,7 +83,7 @@
font-size: $font-size-small; font-size: $font-size-small;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -89,7 +94,7 @@
text-decoration: none; text-decoration: none;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
&.padded { &.padded {
@@ -97,9 +102,11 @@
padding-bottom: 10px; padding-bottom: 10px;
} }
&:hover, &:focus, &.active { &:hover,
&:focus,
&.active {
@include themify($themes) { @include themify($themes) {
background-color: themed('listItemBackgroundHoverColor'); background-color: themed("listItemBackgroundHoverColor");
} }
} }
@@ -108,7 +115,7 @@
padding-left: 5px; padding-left: 5px;
@include themify($themes) { @include themify($themes) {
border-left-color: themed('mutedColor'); border-left-color: themed("mutedColor");
} }
} }
@@ -119,7 +126,8 @@
} }
} }
.text:not(.no-ellipsis), .detail { .text:not(.no-ellipsis),
.detail {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -172,7 +180,7 @@
padding-top: 10px; padding-top: 10px;
@include themify($themes) { @include themify($themes) {
border-color: themed('borderColor'); border-color: themed("borderColor");
} }
} }
@@ -192,7 +200,7 @@
border-bottom: 1px solid #000000; border-bottom: 1px solid #000000;
@include themify($themes) { @include themify($themes) {
border-bottom-color: themed('boxBorderColor'); border-bottom-color: themed("boxBorderColor");
} }
} }
@@ -206,7 +214,7 @@
&.last:last-child:before { &.last:last-child:before {
border-bottom: 1px solid #000000; border-bottom: 1px solid #000000;
@include themify($themes) { @include themify($themes) {
border-bottom-color: themed('boxBorderColor'); border-bottom-color: themed("boxBorderColor");
} }
} }
@@ -216,9 +224,11 @@
clear: both; clear: both;
} }
&:hover, &:focus, &.active { &:hover,
&:focus,
&.active {
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundHoverColor'); background-color: themed("boxBackgroundHoverColor");
} }
} }
@@ -232,14 +242,15 @@
overflow-x: auto; overflow-x: auto;
} }
.row-label, label { .row-label,
label {
font-size: $font-size-small; font-size: $font-size-small;
display: block; display: block;
width: 100%; width: 100%;
margin-bottom: 5px; margin-bottom: 5px;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
.sub-label { .sub-label {
@@ -254,7 +265,7 @@
margin-bottom: 5px; margin-bottom: 5px;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
> a { > a {
@@ -262,11 +273,12 @@
} }
} }
.text, .detail { .text,
.detail {
display: block; display: block;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
} }
@@ -274,7 +286,7 @@
font-size: $font-size-small; font-size: $font-size-small;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -288,8 +300,12 @@
min-width: 0; min-width: 0;
} }
&.box-content-row-flex, .box-content-row-flex, &.box-content-row-checkbox, &.box-content-row-flex,
&.box-content-row-input, &.box-content-row-slider, &.box-content-row-multi { .box-content-row-flex,
&.box-content-row-checkbox,
&.box-content-row-input,
&.box-content-row-slider,
&.box-content-row-multi {
display: flex; display: flex;
align-items: center; align-items: center;
word-break: break-all; word-break: break-all;
@@ -316,37 +332,40 @@
margin: 0; margin: 0;
@include themify($themes) { @include themify($themes) {
color: themed('dangerColor'); color: themed("dangerColor");
} }
} }
} }
&.box-content-row-multi,
&.box-content-row-multi, &.box-content-row-newmulti { &.box-content-row-newmulti {
padding-left: 10px; padding-left: 10px;
} }
&.box-content-row-newmulti { &.box-content-row-newmulti {
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor'); color: themed("primaryColor");
} }
} }
&.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider { &.box-content-row-checkbox,
label, .row-label { &.box-content-row-input,
&.box-content-row-slider {
label,
.row-label {
font-size: $font-size-base; font-size: $font-size-base;
display: block; display: block;
width: initial; width: initial;
margin-bottom: 0; margin-bottom: 0;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
} }
> span { > span {
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -400,14 +419,15 @@
} }
} }
input:not([type="checkbox"]):not([type="radio"]), textarea { input:not([type="checkbox"]):not([type="radio"]),
textarea {
border: none; border: none;
width: 100%; width: 100%;
background-color: transparent !important; background-color: transparent !important;
&::-webkit-input-placeholder { &::-webkit-input-placeholder {
@include themify($themes) { @include themify($themes) {
color: themed('inputPlaceholderColor'); color: themed("inputPlaceholderColor");
} }
} }
@@ -422,7 +442,7 @@
border-radius: $border-radius; border-radius: $border-radius;
@include themify($themes) { @include themify($themes) {
border-color: themed('inputBorderColor'); border-color: themed("inputBorderColor");
} }
} }
@@ -437,25 +457,26 @@
border: none; border: none;
@include themify($themes) { @include themify($themes) {
color: themed('boxRowButtonColor'); color: themed("boxRowButtonColor");
} }
&:hover, &:focus { &:hover,
&:focus {
@include themify($themes) { @include themify($themes) {
color: themed('boxRowButtonHoverColor'); color: themed("boxRowButtonHoverColor");
} }
} }
&.disabled { &.disabled {
@include themify($themes) { @include themify($themes) {
color: themed('disabledIconColor'); color: themed("disabledIconColor");
opacity: themed('disabledBoxOpacity'); opacity: themed("disabledBoxOpacity");
} }
&:hover { &:hover {
@include themify($themes) { @include themify($themes) {
color: themed('disabledIconColor'); color: themed("disabledIconColor");
opacity: themed('disabledBoxOpacity'); opacity: themed("disabledBoxOpacity");
} }
} }
cursor: default !important; cursor: default !important;
@@ -488,7 +509,7 @@
user-select: none; user-select: none;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -499,7 +520,7 @@
opacity: 0.8; opacity: 0.8;
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundColor'); background-color: themed("boxBackgroundColor");
} }
} }
@@ -508,9 +529,10 @@
width: calc(100% - 25px); width: calc(100% - 25px);
} }
.row-sub-icon, .row-sub-label + i.fa { .row-sub-icon,
.row-sub-label + i.fa {
@include themify($themes) { @include themify($themes) {
color: themed('disabledIconColor'); color: themed("disabledIconColor");
} }
} }
@@ -519,7 +541,7 @@
white-space: nowrap; white-space: nowrap;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -531,7 +553,7 @@
margin-left: -5px; margin-left: -5px;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
&.icon-small { &.icon-small {
@@ -574,7 +596,7 @@
fill: none; fill: none;
@include themify($themes) { @include themify($themes) {
stroke: themed('totpStrokeColor'); stroke: themed("totpStrokeColor");
} }
&.inner { &.inner {
@@ -592,15 +614,16 @@
} }
&.low { &.low {
.totp-sec, .totp-code { .totp-sec,
.totp-code {
@include themify($themes) { @include themify($themes) {
color: themed('dangerColor'); color: themed("dangerColor");
} }
} }
.totp-circle { .totp-circle {
@include themify($themes) { @include themify($themes) {
stroke: themed('dangerColor'); stroke: themed("dangerColor");
} }
} }
} }
@@ -639,7 +662,7 @@
width: 100%; width: 100%;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
} }
} }

View File

@@ -10,20 +10,20 @@
cursor: pointer; cursor: pointer;
@include themify($themes) { @include themify($themes) {
background-color: themed('buttonBackgroundColor'); background-color: themed("buttonBackgroundColor");
border-color: themed('buttonBorderColor'); border-color: themed("buttonBorderColor");
color: themed('buttonColor'); color: themed("buttonColor");
} }
&.primary { &.primary {
@include themify($themes) { @include themify($themes) {
color: themed('buttonPrimaryColor'); color: themed("buttonPrimaryColor");
} }
} }
&.danger { &.danger {
@include themify($themes) { @include themify($themes) {
color: themed('buttonDangerColor'); color: themed("buttonDangerColor");
} }
} }
@@ -31,20 +31,20 @@
cursor: pointer; cursor: pointer;
@include themify($themes) { @include themify($themes) {
background-color: darken(themed('buttonBackgroundColor'), 1.5%); background-color: darken(themed("buttonBackgroundColor"), 1.5%);
border-color: darken(themed('buttonBorderColor'), 17%); border-color: darken(themed("buttonBorderColor"), 17%);
color: darken(themed('buttonColor'), 10%); color: darken(themed("buttonColor"), 10%);
} }
&.primary { &.primary {
@include themify($themes) { @include themify($themes) {
color: darken(themed('buttonPrimaryColor'), 6%); color: darken(themed("buttonPrimaryColor"), 6%);
} }
} }
&.danger { &.danger {
@include themify($themes) { @include themify($themes) {
color: darken(themed('buttonDangerColor'), 6%); color: darken(themed("buttonDangerColor"), 6%);
} }
} }
} }
@@ -54,8 +54,8 @@
outline: 0; outline: 0;
@include themify($themes) { @include themify($themes) {
background-color: darken(themed('buttonBackgroundColor'), 6%); background-color: darken(themed("buttonBackgroundColor"), 6%);
border-color: darken(themed('buttonBorderColor'), 25%); border-color: darken(themed("buttonBorderColor"), 25%);
} }
} }

View File

@@ -1,78 +1,79 @@
@import "variables.scss"; @import "variables.scss";
small, .small { small,
.small {
font-size: $font-size-small; font-size: $font-size-small;
} }
.bg-primary { .bg-primary {
@include themify($themes) { @include themify($themes) {
background-color: themed('primaryColor') !important; background-color: themed("primaryColor") !important;
} }
} }
.bg-success { .bg-success {
@include themify($themes) { @include themify($themes) {
background-color: themed('successColor') !important; background-color: themed("successColor") !important;
} }
} }
.bg-danger { .bg-danger {
@include themify($themes) { @include themify($themes) {
background-color: themed('dangerColor') !important; background-color: themed("dangerColor") !important;
} }
} }
.bg-info { .bg-info {
@include themify($themes) { @include themify($themes) {
background-color: themed('infoColor') !important; background-color: themed("infoColor") !important;
} }
} }
.bg-warning { .bg-warning {
@include themify($themes) { @include themify($themes) {
background-color: themed('warningColor') !important; background-color: themed("warningColor") !important;
} }
} }
.text-primary { .text-primary {
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor') !important; color: themed("primaryColor") !important;
} }
} }
.text-success { .text-success {
@include themify($themes) { @include themify($themes) {
color: themed('successColor') !important; color: themed("successColor") !important;
} }
} }
.text-muted { .text-muted {
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor') !important; color: themed("mutedColor") !important;
} }
} }
.text-default { .text-default {
@include themify($themes) { @include themify($themes) {
color: themed('textColor') !important; color: themed("textColor") !important;
} }
} }
.text-danger { .text-danger {
@include themify($themes) { @include themify($themes) {
color: themed('dangerColor') !important; color: themed("dangerColor") !important;
} }
} }
.text-info { .text-info {
@include themify($themes) { @include themify($themes) {
color: themed('infoColor') !important; color: themed("infoColor") !important;
} }
} }
.text-warning { .text-warning {
@include themify($themes) { @include themify($themes) {
color: themed('warningColor') !important; color: themed("warningColor") !important;
} }
} }
@@ -153,18 +154,18 @@ p.lead {
.password-number { .password-number {
@include themify($themes) { @include themify($themes) {
color: themed('passwordNumberColor'); color: themed("passwordNumberColor");
} }
} }
.password-special { .password-special {
@include themify($themes) { @include themify($themes) {
color: themed('passwordSpecialColor'); color: themed("passwordSpecialColor");
} }
} }
#duo-frame { #duo-frame {
background: url('../images/loading.svg') 0 0 no-repeat; background: url("../images/loading.svg") 0 0 no-repeat;
width: 100%; width: 100%;
height: 470px; height: 470px;
margin-bottom: -10px; margin-bottom: -10px;
@@ -177,7 +178,7 @@ p.lead {
} }
#web-authn-frame { #web-authn-frame {
background: url('../images/loading.svg') 0 0 no-repeat; background: url("../images/loading.svg") 0 0 no-repeat;
width: 100%; width: 100%;
height: 310px; height: 310px;
margin-bottom: -10px; margin-bottom: -10px;
@@ -215,7 +216,7 @@ app-root > #loading {
color: $text-muted; color: $text-muted;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
} }
@@ -230,7 +231,7 @@ app-vault-icon {
background-size: 142px 21px; background-size: 142px 21px;
background-repeat: no-repeat; background-repeat: no-repeat;
@include themify($themes) { @include themify($themes) {
background-image: url('../images/logo-' + themed('logoSuffix') + '@2x.png'); background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png");
} }
@media (min-width: 219px) { @media (min-width: 219px) {
width: 189px; width: 189px;
@@ -259,8 +260,8 @@ app-vault-icon {
border-left-width: 5px; border-left-width: 5px;
border-radius: 3px; border-radius: 3px;
@include themify($themes) { @include themify($themes) {
border-color: themed('calloutBorderColor'); border-color: themed("calloutBorderColor");
background-color: themed('calloutBackgroundColor'); background-color: themed("calloutBackgroundColor");
} }
.callout-heading { .callout-heading {
@@ -274,68 +275,70 @@ app-vault-icon {
&.callout-primary { &.callout-primary {
@include themify($themes) { @include themify($themes) {
border-left-color: themed('primaryColor'); border-left-color: themed("primaryColor");
} }
.callout-heading { .callout-heading {
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor'); color: themed("primaryColor");
} }
} }
} }
&.callout-info { &.callout-info {
@include themify($themes) { @include themify($themes) {
border-left-color: themed('infoColor'); border-left-color: themed("infoColor");
} }
.callout-heading { .callout-heading {
@include themify($themes) { @include themify($themes) {
color: themed('infoColor'); color: themed("infoColor");
} }
} }
} }
&.callout-danger { &.callout-danger {
@include themify($themes) { @include themify($themes) {
border-left-color: themed('dangerColor'); border-left-color: themed("dangerColor");
} }
.callout-heading { .callout-heading {
@include themify($themes) { @include themify($themes) {
color: themed('dangerColor'); color: themed("dangerColor");
} }
} }
} }
&.callout-success { &.callout-success {
@include themify($themes) { @include themify($themes) {
border-left-color: themed('successColor'); border-left-color: themed("successColor");
} }
.callout-heading { .callout-heading {
@include themify($themes) { @include themify($themes) {
color: themed('successColor'); color: themed("successColor");
} }
} }
} }
&.callout-warning { &.callout-warning {
@include themify($themes) { @include themify($themes) {
border-left-color: themed('warningColor'); border-left-color: themed("warningColor");
} }
.callout-heading { .callout-heading {
@include themify($themes) { @include themify($themes) {
color: themed('warningColor'); color: themed("warningColor");
} }
} }
} }
&.clickable { &.clickable {
&:hover, &:focus, &.active { &:hover,
&:focus,
&.active {
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundHoverColor'); background-color: themed("boxBackgroundHoverColor");
} }
} }
} }

View File

@@ -7,7 +7,13 @@ $border-radius-lg: $border-radius;
// ref: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss // ref: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss
$grid-breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px ) !default; $grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
) !default;
$zindex-modal-backdrop: 1040 !default; $zindex-modal-backdrop: 1040 !default;
$zindex-modal: 1050 !default; $zindex-modal: 1050 !default;
@@ -15,19 +21,19 @@ $zindex-modal: 1050 !default;
// Padding applied to the modal body // Padding applied to the modal body
$modal-inner-padding: 10px !default; $modal-inner-padding: 10px !default;
$modal-dialog-margin: .5rem !default; $modal-dialog-margin: 0.5rem !default;
$modal-dialog-margin-y-sm-up: 1.75rem !default; $modal-dialog-margin-y-sm-up: 1.75rem !default;
$modal-title-line-height: $line-height-base !default; $modal-title-line-height: $line-height-base !default;
//$modal-content-bg: $background-color-alt !default; //$modal-content-bg: $background-color-alt !default;
$modal-content-border-color: rgba($black, .2) !default; $modal-content-border-color: rgba($black, 0.2) !default;
$modal-content-border-width: 1px !default; $modal-content-border-width: 1px !default;
$modal-content-box-shadow-xs: none; $modal-content-box-shadow-xs: none;
$modal-content-box-shadow-sm-up: none; $modal-content-box-shadow-sm-up: none;
$modal-backdrop-bg: $black !default; $modal-backdrop-bg: $black !default;
$modal-backdrop-opacity: .5 !default; $modal-backdrop-opacity: 0.5 !default;
$modal-header-border-color: $border-color-dark !default; $modal-header-border-color: $border-color-dark !default;
$modal-footer-border-color: $modal-header-border-color !default; $modal-footer-border-color: $modal-header-border-color !default;
$modal-header-border-width: $modal-content-border-width !default; $modal-header-border-width: $modal-content-border-width !default;
@@ -38,7 +44,7 @@ $modal-lg: 800px !default;
$modal-md: 500px !default; $modal-md: 500px !default;
$modal-sm: 300px !default; $modal-sm: 300px !default;
$modal-transition: transform .3s ease-out !default; $modal-transition: transform 0.3s ease-out !default;
$close-font-size: $font-size-base * 1.5 !default; $close-font-size: $font-size-base * 1.5 !default;
$close-font-weight: bold !default; $close-font-weight: bold !default;
@@ -54,8 +60,7 @@ $close-text-shadow: 0 1px 0 $white !default;
@media (min-width: $min) { @media (min-width: $min) {
@content; @content;
} }
} } @else {
@else {
@content; @content;
} }
} }
@@ -96,7 +101,6 @@ $close-text-shadow: 0 1px 0 $white !default;
// .modal-dialog - positioning shell for the actual modal // .modal-dialog - positioning shell for the actual modal
// .modal-content - actual modal w/ bg and corners and stuff // .modal-content - actual modal w/ bg and corners and stuff
// Kill the scroll on the body // Kill the scroll on the body
.modal-open { .modal-open {
overflow: hidden; overflow: hidden;
@@ -168,7 +172,7 @@ $close-text-shadow: 0 1px 0 $white !default;
outline: 0; outline: 0;
@include themify($themes) { @include themify($themes) {
background-color: themed('backgroundColorAlt'); background-color: themed("backgroundColorAlt");
} }
} }
@@ -203,7 +207,7 @@ $close-text-shadow: 0 1px 0 $white !default;
//@include border-top-radius($border-radius-lg); //@include border-top-radius($border-radius-lg);
@include themify($themes) { @include themify($themes) {
border-bottom-color: themed('borderColor'); border-bottom-color: themed("borderColor");
} }
.close { .close {
@@ -249,7 +253,7 @@ $close-text-shadow: 0 1px 0 $white !default;
border-top: $modal-footer-border-width solid $modal-footer-border-color; border-top: $modal-footer-border-width solid $modal-footer-border-color;
@include themify($themes) { @include themify($themes) {
border-top-color: themed('borderColor'); border-top-color: themed("borderColor");
} }
// Easily place margin between footer elements // Easily place margin between footer elements
@@ -313,12 +317,13 @@ $close-text-shadow: 0 1px 0 $white !default;
line-height: 1; line-height: 1;
color: $close-color; color: $close-color;
text-shadow: $close-text-shadow; text-shadow: $close-text-shadow;
opacity: .5; opacity: 0.5;
&:hover, &:focus { &:hover,
&:focus {
color: $close-color; color: $close-color;
text-decoration: none; text-decoration: none;
opacity: .75; opacity: 0.75;
} }
// Opinionated: add "hand" cursor to non-disabled .close elements // Opinionated: add "hand" cursor to non-disabled .close elements
&:not(:disabled):not(.disabled) { &:not(:disabled):not(.disabled) {

View File

@@ -47,7 +47,7 @@ app-home {
left: 10px; left: 10px;
@include themify($themes) { @include themify($themes) {
color: themed('mutedColor'); color: themed("mutedColor");
} }
&:not(:hover):not(:focus) { &:not(:hover):not(:focus) {
@@ -62,17 +62,19 @@ app-home {
} }
} }
&:hover, &:focus { &:hover,
&:focus {
text-decoration: none; text-decoration: none;
@include themify($themes) { @include themify($themes) {
color: themed('primaryColor'); color: themed("primaryColor");
} }
} }
} }
} }
body.body-sm, body.body-xs { body.body-sm,
body.body-xs {
app-home { app-home {
.center-content { .center-content {
margin-top: 0; margin-top: 0;

View File

@@ -1,6 +1,6 @@
$fa-font-path: "~font-awesome/fonts"; $fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome.scss"; @import "~font-awesome/scss/font-awesome.scss";
@import '~ngx-toastr/toastr'; @import "~ngx-toastr/toastr";
@import "~sweetalert2/src/sweetalert2.scss"; @import "~sweetalert2/src/sweetalert2.scss";
@import "variables.scss"; @import "variables.scss";
@@ -51,9 +51,10 @@ $fa-font-path: "~font-awesome/fonts";
} }
} }
&.toast-danger, &.toast-error { &.toast-danger,
&.toast-error {
@include themify($themes) { @include themify($themes) {
background-color: themed('dangerColor'); background-color: themed("dangerColor");
} }
.icon i::before { .icon i::before {
@@ -63,7 +64,7 @@ $fa-font-path: "~font-awesome/fonts";
&.toast-warning { &.toast-warning {
@include themify($themes) { @include themify($themes) {
background-color: themed('warningColor'); background-color: themed("warningColor");
} }
.icon i::before { .icon i::before {
@@ -73,7 +74,7 @@ $fa-font-path: "~font-awesome/fonts";
&.toast-info { &.toast-info {
@include themify($themes) { @include themify($themes) {
background-color: themed('infoColor'); background-color: themed("infoColor");
} }
.icon i:before { .icon i:before {
@@ -83,7 +84,7 @@ $fa-font-path: "~font-awesome/fonts";
&.toast-success { &.toast-success {
@include themify($themes) { @include themify($themes) {
background-color: themed('successColor'); background-color: themed("successColor");
} }
.icon i:before { .icon i:before {
@@ -101,8 +102,8 @@ $fa-font-path: "~font-awesome/fonts";
width: 34em; width: 34em;
@include themify($themes) { @include themify($themes) {
background-color: themed('backgroundColorAlt'); background-color: themed("backgroundColorAlt");
color: themed('textColor'); color: themed("textColor");
} }
.swal2-icon { .swal2-icon {
@@ -116,7 +117,7 @@ $fa-font-path: "~font-awesome/fonts";
margin: 0; margin: 0;
font-size: $font-size-base; font-size: $font-size-base;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
label.checkbox { label.checkbox {
@@ -130,7 +131,8 @@ $fa-font-path: "~font-awesome/fonts";
} }
} }
.swal2-input, .swal2-textarea { .swal2-input,
.swal2-textarea {
border: 1px solid #000000; border: 1px solid #000000;
border-radius: $border-radius; border-radius: $border-radius;
margin-bottom: 0; margin-bottom: 0;
@@ -143,13 +145,13 @@ $fa-font-path: "~font-awesome/fonts";
box-shadow: none; box-shadow: none;
} }
@include themify($themes) { @include themify($themes) {
border-color: themed('inputBorderColor'); border-color: themed("inputBorderColor");
color: themed('textColor'); color: themed("textColor");
background-color: themed('inputBackgroundColor'); background-color: themed("inputBackgroundColor");
} }
&::-webkit-input-placeholder { &::-webkit-input-placeholder {
@include themify($themes) { @include themify($themes) {
color: themed('inputPlaceholderColor'); color: themed("inputPlaceholderColor");
} }
} }
} }
@@ -167,7 +169,7 @@ $fa-font-path: "~font-awesome/fonts";
font-size: $font-size-large; font-size: $font-size-large;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
} }
@@ -176,7 +178,7 @@ $fa-font-path: "~font-awesome/fonts";
font-size: $font-size-base; font-size: $font-size-base;
@include themify($themes) { @include themify($themes) {
color: themed('textColor'); color: themed("textColor");
} }
} }

View File

@@ -1,7 +1,7 @@
@import '~nord/src/sass/nord.scss'; @import "~nord/src/sass/nord.scss";
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif; $font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
$font-size-base: 14px; $font-size-base: 14px;
$font-size-large: 18px; $font-size-large: 18px;
$font-size-small: 12px; $font-size-small: 12px;
@@ -18,12 +18,12 @@ $gray: #555;
$gray-light: #777; $gray-light: #777;
$text-muted: $gray-light; $text-muted: $gray-light;
$brand-primary: #175DDC; $brand-primary: #175ddc;
$brand-danger: #dd4b39; $brand-danger: #dd4b39;
$brand-success: #00a65a; $brand-success: #00a65a;
$brand-info: #555555; $brand-info: #555555;
$brand-warning: #bf7e16; $brand-warning: #bf7e16;
$brand-primary-accent: #1252A3; $brand-primary-accent: #1252a3;
$background-color: #f0f0f0; $background-color: #f0f0f0;
@@ -60,8 +60,8 @@ $themes: (
borderColor: $border-color-dark, borderColor: $border-color-dark,
backgroundColor: $background-color, backgroundColor: $background-color,
backgroundColorAlt: #ffffff, backgroundColorAlt: #ffffff,
scrollbarColor: rgba(100,100,100,.2), scrollbarColor: rgba(100, 100, 100, 0.2),
scrollbarHoverColor: rgba(100,100,100,.4), scrollbarHoverColor: rgba(100, 100, 100, 0.4),
boxBackgroundColor: $box-background-color, boxBackgroundColor: $box-background-color,
boxBackgroundHoverColor: $box-background-hover-color, boxBackgroundHoverColor: $box-background-hover-color,
boxBorderColor: $box-border-color, boxBorderColor: $box-border-color,
@@ -98,7 +98,7 @@ $themes: (
successColor: $brand-success, successColor: $brand-success,
infoColor: $brand-info, infoColor: $brand-info,
warningColor: $brand-warning, warningColor: $brand-warning,
logoSuffix: 'dark', logoSuffix: "dark",
passwordNumberColor: #007fde, passwordNumberColor: #007fde,
passwordSpecialColor: #c40800, passwordSpecialColor: #c40800,
calloutBorderColor: $border-color-dark, calloutBorderColor: $border-color-dark,
@@ -147,7 +147,7 @@ $themes: (
successColor: $brand-success, successColor: $brand-success,
infoColor: $brand-info, infoColor: $brand-info,
warningColor: $brand-warning, warningColor: $brand-warning,
logoSuffix: 'white', logoSuffix: "white",
passwordNumberColor: #52bdfb, passwordNumberColor: #52bdfb,
passwordSpecialColor: #ff7c70, passwordSpecialColor: #ff7c70,
calloutBorderColor: #111111, calloutBorderColor: #111111,
@@ -196,7 +196,7 @@ $themes: (
successColor: $nord14, successColor: $nord14,
infoColor: $nord9, infoColor: $nord9,
warningColor: $nord12, warningColor: $nord12,
logoSuffix: 'white', logoSuffix: "white",
passwordNumberColor: $nord8, passwordNumberColor: $nord8,
passwordSpecialColor: $nord12, passwordSpecialColor: $nord12,
calloutBorderColor: $nord0, calloutBorderColor: $nord0,
@@ -245,7 +245,7 @@ $themes: (
successColor: $solarizedDarkGreen, successColor: $solarizedDarkGreen,
infoColor: $solarizedDarkGreen, infoColor: $solarizedDarkGreen,
warningColor: $solarizedDarkYellow, warningColor: $solarizedDarkYellow,
logoSuffix: 'white', logoSuffix: "white",
passwordNumberColor: $solarizedDarkCyan, passwordNumberColor: $solarizedDarkCyan,
passwordSpecialColor: $solarizedDarkYellow, passwordSpecialColor: $solarizedDarkYellow,
calloutBorderColor: $solarizedDarkBase03, calloutBorderColor: $solarizedDarkBase03,
@@ -258,8 +258,13 @@ $themes: (
html.theme_#{$theme} & { html.theme_#{$theme} & {
$theme-map: () !global; $theme-map: () !global;
@each $key, $submap in $map { @each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}'); $value: map-get(map-get($themes, $theme), "#{$key}");
$theme-map: map-merge($theme-map, ($key: $value)) !global; $theme-map: map-merge(
$theme-map,
(
$key: $value,
)
) !global;
} }
@content; @content;
$theme-map: null !global; $theme-map: null !global;

View File

@@ -3,10 +3,14 @@
<div class="box-content"> <div class="box-content">
<ng-container *ngIf="!editMode"> <ng-container *ngIf="!editMode">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="deletionDate">{{'deletionDate' | i18n}}</label> <label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<select id="deletionDate" name="DeletionDateSelect" formControlName="selectedDeletionDatePreset" required> <select
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{o.name}} id="deletionDate"
</option> name="DeletionDateSelect"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
</div> </div>
<div class="box-content-row" appBoxRow *ngIf="selectedDeletionDatePreset.value === 0"> <div class="box-content-row" appBoxRow *ngIf="selectedDeletionDatePreset.value === 0">
@@ -14,17 +18,22 @@
</div> </div>
</ng-container> </ng-container>
<div class="box-content-row" appBoxRow *ngIf="editMode"> <div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="deletionDate">{{'deletionDate' | i18n}}</label> <label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container> <ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'deletionDateDesc' | i18n}} {{ "deletionDateDesc" | i18n }}
<ng-container <ng-container
*ngIf="(!inPopout && browserPath == 'firefox') && (editMode || (selectedDeletionDatePreset.value === 0 && !editMode))"> *ngIf="
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a !inPopout &&
(click)="popOutWindow.emit()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a> browserPath == 'firefox' &&
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}} (editMode || (selectedDeletionDatePreset.value === 0 && !editMode))
"
>
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
<a (click)="popOutWindow.emit()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
</ng-container> </ng-container>
</div> </div>
</div> </div>
@@ -32,10 +41,15 @@
<div class="box-content"> <div class="box-content">
<ng-container *ngIf="!editMode"> <ng-container *ngIf="!editMode">
<div class="box-content-row" *ngIf="!editMode" appBoxRow> <div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="editExpirationDate">{{'expirationDate' | i18n}}</label> <label for="editExpirationDate">{{ "expirationDate" | i18n }}</label>
<select id="expirationDate" name="ExpirationDateSelect" <select
formControlName="selectedExpirationDatePreset" required> id="expirationDate"
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{o.name}} name="ExpirationDateSelect"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">
{{ o.name }}
</option> </option>
</select> </select>
</div> </div>
@@ -45,21 +59,26 @@
</ng-container> </ng-container>
<div class="box-content-row" *ngIf="editMode" appBoxRow> <div class="box-content-row" *ngIf="editMode" appBoxRow>
<div class="flex-label"> <div class="flex-label">
<label>{{'expirationDate' | i18n}}</label> <label>{{ "expirationDate" | i18n }}</label>
<button type="button" *ngIf="!disabled" appStopClick (click)="clearExpiration()"> <button type="button" *ngIf="!disabled" appStopClick (click)="clearExpiration()">
{{'clear' | i18n}} {{ "clear" | i18n }}
</button> </button>
</div> </div>
<ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container> <ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'expirationDateDesc' | i18n}} {{ "expirationDateDesc" | i18n }}
<ng-container <ng-container
*ngIf="(!inPopout && browserPath == 'firefox') && (editMode || (selectedExpirationDatePreset.value === 0 && !editMode))"> *ngIf="
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a !inPopout &&
(click)="popOutWindow.emit()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a> browserPath == 'firefox' &&
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}} (editMode || (selectedExpirationDatePreset.value === 0 && !editMode))
"
>
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
<a (click)="popOutWindow.emit()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
</ng-container> </ng-container>
</div> </div>
</div> </div>
@@ -67,29 +86,59 @@
<ng-container [ngSwitch]="browserPath"> <ng-container [ngSwitch]="browserPath">
<ng-container *ngSwitchCase="'firefox'"> <ng-container *ngSwitchCase="'firefox'">
<div class="flex flex-grow"> <div class="flex flex-grow">
<input id="deletionDateCustomFallback" type="date" name="DeletionDateFallback" <input
formControlName="fallbackDeletionDate" required placeholder="MM/DD/YYYY" id="deletionDateCustomFallback"
[readOnly]="disabled" data-date-format="mm/dd/yyyy"> type="date"
<input id="deletionTimeCustomFallback" type="time" name="DeletionTimeDate" name="DeletionDateFallback"
formControlName="fallbackDeletionTime" required placeholder="HH:MM AM/PM" formControlName="fallbackDeletionDate"
[readOnly]="disabled"> required
placeholder="MM/DD/YYYY"
[readOnly]="disabled"
data-date-format="mm/dd/yyyy"
/>
<input
id="deletionTimeCustomFallback"
type="time"
name="DeletionTimeDate"
formControlName="fallbackDeletionTime"
required
placeholder="HH:MM AM/PM"
[readOnly]="disabled"
/>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'safari'"> <ng-container *ngSwitchCase="'safari'">
<div class="flex flex-grow"> <div class="flex flex-grow">
<input id="deletionDateCustomFallback" type="date" name="DeletionDateFallback" <input
formControlName="fallbackDeletionDate" required placeholder="MM/DD/YYYY" id="deletionDateCustomFallback"
[readOnly]="disabled" data-date-format="mm/dd/yyyy"> type="date"
<select id="deletionTimeCustomFallback" formControlName="fallbackDeletionTime" name="DeletionDateFallback"
name="SafariDeletionTime"> formControlName="fallbackDeletionDate"
<option *ngFor="let o of safariDeletionTimePresetOptions" [ngValue]="o.twentyFourHour">{{o.twelveHour}} required
placeholder="MM/DD/YYYY"
[readOnly]="disabled"
data-date-format="mm/dd/yyyy"
/>
<select
id="deletionTimeCustomFallback"
formControlName="fallbackDeletionTime"
name="SafariDeletionTime"
>
<option *ngFor="let o of safariDeletionTimePresetOptions" [ngValue]="o.twentyFourHour">
{{ o.twelveHour }}
</option> </option>
</select> </select>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
<input id="deletionDateCustom" type="datetime-local" name="DeletionDate" <input
formControlName="defaultDeletionDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM"> id="deletionDateCustom"
type="datetime-local"
name="DeletionDate"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-template> </ng-template>
@@ -97,33 +146,64 @@
<ng-container [ngSwitch]="browserPath"> <ng-container [ngSwitch]="browserPath">
<ng-container *ngSwitchCase="'firefox'"> <ng-container *ngSwitchCase="'firefox'">
<div class="flex flex-grow"> <div class="flex flex-grow">
<input id="expirationDateCustomFallback" type="date" name="ExpirationDateFallback" <input
formControlName="fallbackExpirationDate" [required]="!editMode" placeholder="MM/DD/YYYY" id="expirationDateCustomFallback"
[readOnly]="disabled" data-date-format="mm/dd/yyyy"> type="date"
<input id="expirationTimeCustomFallback" type="time" name="ExpirationDateFallback"
name="ExpirationTimeFallback" formControlName="fallbackExpirationTime" formControlName="fallbackExpirationDate"
[required]="!editMode" placeholder="HH:MM AM/PM" [readOnly]="disabled"> [required]="!editMode"
placeholder="MM/DD/YYYY"
[readOnly]="disabled"
data-date-format="mm/dd/yyyy"
/>
<input
id="expirationTimeCustomFallback"
type="time"
name="ExpirationTimeFallback"
formControlName="fallbackExpirationTime"
[required]="!editMode"
placeholder="HH:MM AM/PM"
[readOnly]="disabled"
/>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'safari'"> <ng-container *ngSwitchCase="'safari'">
<div class="flex flex-grow"> <div class="flex flex-grow">
<input id="expirationDateCustomFallback" type="date" name="ExpirationDateFallback" <input
formControlName="fallbackExpirationDate" [required]="!editMode" placeholder="MM/DD/YYYY" id="expirationDateCustomFallback"
[readOnly]="disabled" data-date-format="mm/dd/yyyy"> type="date"
<select id="expirationTimeCustomFallback" name="ExpirationDateFallback"
formControlName="fallbackExpirationTime" name="SafariExpirationTime"> formControlName="fallbackExpirationDate"
<option *ngFor="let o of safariExpirationTimePresetOptions" [ngValue]="o.twentyFourHour"> [required]="!editMode"
{{o.twelveHour}} placeholder="MM/DD/YYYY"
[readOnly]="disabled"
data-date-format="mm/dd/yyyy"
/>
<select
id="expirationTimeCustomFallback"
formControlName="fallbackExpirationTime"
name="SafariExpirationTime"
>
<option
*ngFor="let o of safariExpirationTimePresetOptions"
[ngValue]="o.twentyFourHour"
>
{{ o.twelveHour }}
</option> </option>
</select> </select>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchDefault> <ng-container *ngSwitchDefault>
<input id="expirationDateCustom" type="datetime-local" name="ExpirationDate" <input
formControlName="defaultExpirationDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" id="expirationDateCustom"
[readOnly]="disabled"> type="datetime-local"
name="ExpirationDate"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disabled"
/>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-template> </ng-template>
</ng-container> </ng-container>

View File

@@ -1,30 +1,28 @@
import { DatePipe } from '@angular/common'; import { DatePipe } from "@angular/common";
import { import { Component, EventEmitter, Input, Output } from "@angular/core";
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms'; import { ControlContainer, NgForm } from "@angular/forms";
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 { EffluxDatesComponent as BaseEffluxDatesComponent } from 'jslib-angular/components/send/efflux-dates.component'; import { EffluxDatesComponent as BaseEffluxDatesComponent } from "jslib-angular/components/send/efflux-dates.component";
@Component({ @Component({
selector: 'app-send-efflux-dates', selector: "app-send-efflux-dates",
templateUrl: 'efflux-dates.component.html', templateUrl: "efflux-dates.component.html",
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
}) })
export class EffluxDatesComponent extends BaseEffluxDatesComponent { export class EffluxDatesComponent extends BaseEffluxDatesComponent {
@Input() readonly inPopout: boolean; @Input() readonly inPopout: boolean;
@Output() popOutWindow = new EventEmitter(); @Output() popOutWindow = new EventEmitter();
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, constructor(
protected datePipe: DatePipe) { protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {
super(i18nService, platformUtilsService, datePipe); super(i18nService, platformUtilsService, datePipe);
} }
} }

View File

@@ -1,58 +1,80 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header> <header>
<div class="left"> <div class="left">
<button type="button" appBlurClick (click)="cancel()">{{'cancel' | i18n}}</button> <button type="button" appBlurClick (click)="cancel()">{{ "cancel" | i18n }}</button>
</div> </div>
<h1 class="center"> <h1 class="center">
<span class="title">{{title}}</span> <span class="title">{{ title }}</span>
</h1> </h1>
<div class="right"> <div class="right">
<button type="submit" appBlurClick [disabled]="form.loading || disableSend"> <button type="submit" appBlurClick [disabled]="form.loading || disableSend">
<span [hidden]="form.loading">{{'save' | i18n}}</span> <span [hidden]="form.loading">{{ "save" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i> <i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
</header> </header>
<content *ngIf="send"> <content *ngIf="send">
<!-- Policy Banner --> <!-- Policy Banner -->
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend"> <app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{'sendDisabledWarning' | i18n}} {{ "sendDisabledWarning" | i18n }}
</app-callout> </app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend"> <app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}} {{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<!-- File Warning --> <!-- File Warning -->
<app-callout type="warning" icon="fa fa-external-link fa-rotate-270 fa-fw" [clickable]="true" <app-callout
title="{{'sendFileCalloutHeader' | i18n}}" type="warning"
*ngIf="showFilePopoutMessage && send.type === sendType.File && !disableSend" (click)="popOutWindow()"> icon="fa fa-external-link fa-rotate-270 fa-fw"
<div *ngIf="showChromiumFileWarning">{{'sendLinuxChromiumFileWarning' | i18n}}</div> [clickable]="true"
<div *ngIf="showFirefoxFileWarning">{{'sendFirefoxFileWarning' | i18n}}</div> title="{{ 'sendFileCalloutHeader' | i18n }}"
<div *ngIf="showSafariFileWarning">{{'sendSafariFileWarning' | i18n}}</div> *ngIf="showFilePopoutMessage && send.type === sendType.File && !disableSend"
(click)="popOutWindow()"
>
<div *ngIf="showChromiumFileWarning">{{ "sendLinuxChromiumFileWarning" | i18n }}</div>
<div *ngIf="showFirefoxFileWarning">{{ "sendFirefoxFileWarning" | i18n }}</div>
<div *ngIf="showSafariFileWarning">{{ "sendSafariFileWarning" | i18n }}</div>
</app-callout> </app-callout>
<!-- Name --> <!-- Name -->
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label> <label for="name">{{ "name" | i18n }}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" [readonly]="disableSend"> <input
id="name"
type="text"
name="Name"
[(ngModel)]="send.name"
[readonly]="disableSend"
/>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendNameDesc' | i18n}} {{ "sendNameDesc" | i18n }}
</div> </div>
</div> </div>
<!-- Type Options --> <!-- Type Options -->
<div class="box" *ngIf="!editMode"> <div class="box" *ngIf="!editMode">
<div class="box-content no-hover"> <div class="box-content no-hover">
<div class="box-content-row"> <div class="box-content-row">
<label for="sendTypeOptions">{{'sendTypeHeader' | i18n}}</label> <label for="sendTypeOptions">{{ "sendTypeHeader" | i18n }}</label>
<div class="radio-group text-default" appBoxRow name="SendTypeOptions" <div
*ngFor="let o of typeOptions"> class="radio-group text-default"
<input type="radio" [(ngModel)]="send.type" name="Type_{{o.value}}" id="type_{{o.value}}" appBoxRow
[value]="o.value" (change)="typeChanged()" [checked]="send.type === o.value" name="SendTypeOptions"
[readonly]="disableSend"> *ngFor="let o of typeOptions"
<label for="type_{{o.value}}"> >
{{o.name}} <input
type="radio"
[(ngModel)]="send.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged()"
[checked]="send.type === o.value"
[readonly]="disableSend"
/>
<label for="type_{{ o.value }}">
{{ o.name }}
</label> </label>
</div> </div>
</div> </div>
@@ -62,57 +84,77 @@
<div class="box" *ngIf="send.type === sendType.File && (editMode || showFileSelector)"> <div class="box" *ngIf="send.type === sendType.File && (editMode || showFileSelector)">
<div class="box-content no-hover"> <div class="box-content no-hover">
<div class="box-content-row" *ngIf="editMode"> <div class="box-content-row" *ngIf="editMode">
<label for="file">{{'file' | i18n}}</label> <label for="file">{{ "file" | i18n }}</label>
<div class="row-main">{{send.file.fileName}} ({{send.file.sizeName}})</div> <div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
</div> </div>
<div class="box-content-row" *ngIf="showFileSelector"> <div class="box-content-row" *ngIf="showFileSelector">
<label for="file">{{'file' | i18n}}</label> <label for="file">{{ "file" | i18n }}</label>
<input type="file" id="file" name="file" required [readonly]="disableSend"> <input type="file" id="file" name="file" required [readonly]="disableSend" />
</div> </div>
</div> </div>
<div class="box-footer" *ngIf="showFileSelector"> <div class="box-footer" *ngIf="showFileSelector">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}} {{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div> </div>
</div> </div>
<!-- Text --> <!-- Text -->
<div class="box" *ngIf="send.type === sendType.Text"> <div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="text">{{'sendTypeText' | i18n}}</label> <label for="text">{{ "sendTypeText" | i18n }}</label>
<textarea id="text" name="Text" rows="6" [(ngModel)]="send.text.text" <textarea
[readonly]="disableSend"></textarea> id="text"
name="Text"
rows="6"
[(ngModel)]="send.text.text"
[readonly]="disableSend"
></textarea>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendTextDesc' | i18n}} {{ "sendTextDesc" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{'sendHideText' | i18n}}</label> <label for="hideText">{{ "sendHideText" | i18n }}</label>
<input id="hideText" type="checkbox" name="HideText" [(ngModel)]="send.text.hidden" <input
[disabled]="disableSend"> id="hideText"
type="checkbox"
name="HideText"
[(ngModel)]="send.text.hidden"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
<!-- Share --> <!-- Share -->
<div class="box"> <div class="box">
<h2 class="box-header"> <h2 class="box-header">
{{'share' | i18n}} {{ "share" | i18n }}
</h2> </h2>
<div class="box-content"> <div class="box-content">
<!-- Copy Link on Save --> <!-- Copy Link on Save -->
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyOnSave">{{'sendShareDesc' | i18n}}</label> <label for="copyOnSave">{{ "sendShareDesc" | i18n }}</label>
<input id="copyOnSave" type="checkbox" name="CopyOnSave" [(ngModel)]="copyLink" <input
[disabled]="disableSend"> id="copyOnSave"
type="checkbox"
name="CopyOnSave"
[(ngModel)]="copyLink"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
<!-- Options --> <!-- Options -->
<div class="box"> <div class="box">
<h2> <h2>
<button type="button" class="box-header-expandable" (click)="showOptions = !showOptions" [attr.aria-expanded]="showOptions"> <button
{{'options' | i18n}} type="button"
class="box-header-expandable"
(click)="showOptions = !showOptions"
[attr.aria-expanded]="showOptions"
>
{{ "options" | i18n }}
<i *ngIf="!showOptions" class="fa fa-chevron-down fa-sm icon" aria-hidden="true"></i> <i *ngIf="!showOptions" class="fa fa-chevron-down fa-sm icon" aria-hidden="true"></i>
<i *ngIf="showOptions" class="fa fa-chevron-up fa-sm icon" aria-hidden="true"></i> <i *ngIf="showOptions" class="fa fa-chevron-up fa-sm icon" aria-hidden="true"></i>
</button> </button>
@@ -120,29 +162,45 @@
</div> </div>
<div [hidden]="!showOptions"> <div [hidden]="!showOptions">
<app-send-efflux-dates <app-send-efflux-dates
[initialDeletionDate]="send.deletionDate" [initialExpirationDate]="send.expirationDate" [initialDeletionDate]="send.deletionDate"
[editMode]="editMode" [disabled]="disableSend" (datesChanged)="setDates($event)" (popOutWindow)="popOutWindow()"> [initialExpirationDate]="send.expirationDate"
[editMode]="editMode"
[disabled]="disableSend"
(datesChanged)="setDates($event)"
(popOutWindow)="popOutWindow()"
>
</app-send-efflux-dates> </app-send-efflux-dates>
<!-- Maximum Access Count --> <!-- Maximum Access Count -->
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="maximumAccessCount">{{'maximumAccessCount' | i18n}}</label> <label for="maximumAccessCount">{{ "maximumAccessCount" | i18n }}</label>
<input id="maximumAccessCount" min="1" type="number" name="MaximumAccessCount" <input
[(ngModel)]="send.maxAccessCount" [readonly]="disableSend"> id="maximumAccessCount"
min="1"
type="number"
name="MaximumAccessCount"
[(ngModel)]="send.maxAccessCount"
[readonly]="disableSend"
/>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'maximumAccessCountDesc' | i18n}} {{ "maximumAccessCountDesc" | i18n }}
</div> </div>
</div> </div>
<!-- Current Access Count --> <!-- Current Access Count -->
<div class="box" *ngIf="editMode"> <div class="box" *ngIf="editMode">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="currentAccessCount">{{'currentAccessCount' | i18n}}</label> <label for="currentAccessCount">{{ "currentAccessCount" | i18n }}</label>
<input id="currentAccessCount" readonly type="text" name="CurrentAccessCount" <input
[(ngModel)]="send.accessCount"> id="currentAccessCount"
readonly
type="text"
name="CurrentAccessCount"
[(ngModel)]="send.accessCount"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -151,44 +209,71 @@
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label> <label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</label>
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label> <label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</label>
<input id="password" type="{{showPassword ? 'text' : 'password'}}" name="Password" <input
class="monospaced" [(ngModel)]="password" appInputVerbatim [readonly]="disableSend"> id="password"
type="{{ showPassword ? 'text' : 'password' }}"
name="Password"
class="monospaced"
[(ngModel)]="password"
appInputVerbatim
[readonly]="disableSend"
/>
</div> </div>
<div class="action-buttons" *ngIf="!disableSend"> <div class="action-buttons" *ngIf="!disableSend">
<button type="button" class="row-btn" appStopClick appBlurClick <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()" [attr.aria-pressed]="showPassword"> type="button"
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}" class="row-btn"
aria-hidden="true"></i> appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePasswordVisible()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendPasswordDesc' | i18n}} {{ "sendPasswordDesc" | i18n }}
</div> </div>
</div> </div>
<!-- Notes --> <!-- Notes -->
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="notes">{{'notes' | i18n}}</label> <label for="notes">{{ "notes" | i18n }}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes" <textarea
[readonly]="disableSend"></textarea> id="notes"
name="Notes"
rows="6"
[(ngModel)]="send.notes"
[readonly]="disableSend"
></textarea>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendNotesDesc' | i18n}} {{ "sendNotesDesc" | i18n }}
</div> </div>
</div> </div>
<!-- Hide Email --> <!-- Hide Email -->
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{'hideEmail' | i18n}}</label> <label for="hideEmail">{{ "hideEmail" | i18n }}</label>
<input id="hideEmail" type="checkbox" name="HideEmail" [(ngModel)]="send.hideEmail" <input
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"> id="hideEmail"
type="checkbox"
name="HideEmail"
[(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -196,9 +281,14 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disableSend">{{'sendDisableDesc' | i18n}}</label> <label for="disableSend">{{ "sendDisableDesc" | i18n }}</label>
<input id="disableSend" type="checkbox" name="DisableSend" [(ngModel)]="send.disabled" <input
[disabled]="disableSend"> id="disableSend"
type="checkbox"
name="DisableSend"
[(ngModel)]="send.disabled"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -206,14 +296,21 @@
<!-- Delete --> <!-- Delete -->
<div class="box list" *ngIf="editMode"> <div class="box list" *ngIf="editMode">
<div class="box-content single-line"> <div class="box-content single-line">
<button type="button" class="box-content-row" appStopClick appBlurClick (click)="delete()" <button
[appApiAction]="deletePromise" #deleteBtn> type="button"
class="box-content-row"
appStopClick
appBlurClick
(click)="delete()"
[appApiAction]="deletePromise"
#deleteBtn
>
<div class="row-main text-danger"> <div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true"> <div class="icon text-danger" aria-hidden="true">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i> <i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i> <i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
</div> </div>
<span>{{'deleteSend' | i18n}}</span> <span>{{ "deleteSend" | i18n }}</span>
</div> </div>
</button> </button>
</div> </div>

View File

@@ -1,33 +1,27 @@
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 { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.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";
@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
@@ -39,13 +33,32 @@ export class SendAddEditComponent extends BaseAddEditComponent {
isLinux = false; isLinux = false;
isUnsupportedMac = false; isUnsupportedMac = false;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
userService: UserService, messagingService: MessagingService, policyService: PolicyService, i18nService: I18nService,
environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService, platformUtilsService: PlatformUtilsService,
private route: ActivatedRoute, private router: Router, private location: Location, userService: UserService,
private popupUtilsService: PopupUtilsService, logService: LogService) { messagingService: MessagingService,
super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService, policyService: PolicyService,
messagingService, policyService, logService); 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,
userService,
messagingService,
policyService,
logService
);
} }
get showFileSelector(): boolean { get showFileSelector(): boolean {
@@ -53,7 +66,10 @@ export class SendAddEditComponent extends BaseAddEditComponent {
} }
get showFilePopoutMessage(): boolean { get showFilePopoutMessage(): boolean {
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning); return (
!this.editMode &&
(this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning)
);
} }
get showFirefoxFileWarning(): boolean { get showFirefoxFileWarning(): boolean {
@@ -66,7 +82,11 @@ export class SendAddEditComponent extends BaseAddEditComponent {
// Only show this for Chromium based browsers in Linux and Mac > Big Sur // Only show this for Chromium based browsers in Linux and Mac > Big Sur
get showChromiumFileWarning(): boolean { get showChromiumFileWarning(): boolean {
return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout); return (
(this.isLinux || this.isUnsupportedMac) &&
!this.isFirefox &&
!(this.inSidebar || this.inPopout)
);
} }
popOutWindow() { popOutWindow() {
@@ -78,10 +98,11 @@ export class SendAddEditComponent extends BaseAddEditComponent {
this.isFirefox = this.platformUtilsService.isFirefox(); this.isFirefox = this.platformUtilsService.isFirefox();
this.inPopout = this.popupUtilsService.inPopout(window); this.inPopout = this.popupUtilsService.inPopout(window);
this.inSidebar = this.popupUtilsService.inSidebar(window); this.inSidebar = this.popupUtilsService.inSidebar(window);
this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1; this.isLinux = window?.navigator?.userAgent.indexOf("Linux") !== -1;
this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11'); this.isUnsupportedMac =
this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes("Mac OS X 11");
this.route.queryParams.pipe(first()).subscribe(async params => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.sendId) { if (params.sendId) {
this.sendId = params.sendId; this.sendId = params.sendId;
} }
@@ -94,7 +115,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
window.setTimeout(() => { window.setTimeout(() => {
if (!this.editMode) { if (!this.editMode) {
document.getElementById('name').focus(); document.getElementById("name").focus();
} }
}, 200); }, 200);
} }
@@ -119,8 +140,8 @@ export class SendAddEditComponent extends BaseAddEditComponent {
cancel() { cancel() {
// If true, the window was pop'd out on the add-send page. location.back will not work // 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')) { if ((window as any).previousPopupUrl.startsWith("/add-send")) {
this.router.navigate(['tabs/send']); this.router.navigate(["tabs/send"]);
} else { } else {
this.location.back(); this.location.back();
} }

View File

@@ -2,76 +2,119 @@
<div class="left" *ngIf="showLeftHeader"> <div class="left" *ngIf="showLeftHeader">
<app-pop-out></app-pop-out> <app-pop-out></app-pop-out>
</div> </div>
<h1 class="sr-only">{{'send' | i18n}}</h1> <h1 class="sr-only">{{ "send" | i18n }}</h1>
<div class="search"> <div class="search">
<input type="search" placeholder="{{'searchSends' | i18n}}" id="search" [(ngModel)]="searchText" <input
(input)="search(200)" autocomplete="off" appAutofocus> type="search"
placeholder="{{ 'searchSends' | i18n }}"
id="search"
[(ngModel)]="searchText"
(input)="search(200)"
autocomplete="off"
appAutofocus
/>
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</div> </div>
<div class="right"> <div class="right">
<button type="button" appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}" [disabled]="disableSend"> <button
type="button"
appBlurClick
(click)="addSend()"
appA11yTitle="{{ 'addSend' | i18n }}"
[disabled]="disableSend"
>
<i class="fa fa-plus fa-lg fa-fw" aria-hidden="true"></i> <i class="fa fa-plus fa-lg fa-fw" aria-hidden="true"></i>
</button> </button>
</div> </div>
</header> </header>
<content [ngClass]="{'flex' : disableSend, 'tab-page' : disableSend}"> <content [ngClass]="{ flex: disableSend, 'tab-page': disableSend }">
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend"> <app-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{'sendDisabledWarning' | i18n}} {{ "sendDisabledWarning" | i18n }}
</app-callout> </app-callout>
<div class="no-items" *ngIf="(!sends || !sends.length) && !showSearching()"> <div class="no-items" *ngIf="(!sends || !sends.length) && !showSearching()">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i> <i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x"></i> <i class="fa fa-frown-o fa-4x"></i>
<p>{{'noItemsInList' | i18n}}</p> <p>{{ "noItemsInList" | i18n }}</p>
<button type="button" (click)="addSend()" class="btn block primary link" <button
[disabled]="disableSend">{{'addSend' | i18n}}</button> type="button"
(click)="addSend()"
class="btn block primary link"
[disabled]="disableSend"
>
{{ "addSend" | i18n }}
</button>
</ng-container> </ng-container>
</div> </div>
<ng-container *ngIf="sends && sends.length && !showSearching()"> <ng-container *ngIf="sends && sends.length && !showSearching()">
<div class="box list"> <div class="box list">
<h2 class="box-header"> <h2 class="box-header">
{{'types' | i18n}} {{ "types" | i18n }}
</h2> </h2>
<div class="box-content single-line"> <div class="box-content single-line">
<button type="button" class="box-content-row" appStopClick appBlurClick (click)="selectType(sendType.Text)"> <button
type="button"
class="box-content-row"
appStopClick
appBlurClick
(click)="selectType(sendType.Text)"
>
<div class="row-main"> <div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-file-text-o"></i></div> <div class="icon"><i class="fa fa-fw fa-lg fa-file-text-o"></i></div>
<span class="text">{{'sendTypeText' | i18n}}</span> <span class="text">{{ "sendTypeText" | i18n }}</span>
</div> </div>
<span class="row-sub-label">{{typeCounts.get(sendType.Text) || 0}}</span> <span class="row-sub-label">{{ typeCounts.get(sendType.Text) || 0 }}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span> <span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</button> </button>
<button type="button" class="box-content-row" appStopClick appBlurClick (click)="selectType(sendType.File)"> <button
type="button"
class="box-content-row"
appStopClick
appBlurClick
(click)="selectType(sendType.File)"
>
<div class="row-main"> <div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-file-o"></i></div> <div class="icon"><i class="fa fa-fw fa-lg fa-file-o"></i></div>
<span class="text">{{'sendTypeFile' | i18n}}</span> <span class="text">{{ "sendTypeFile" | i18n }}</span>
</div> </div>
<span class="row-sub-label">{{typeCounts.get(sendType.File) || 0}}</span> <span class="row-sub-label">{{ typeCounts.get(sendType.File) || 0 }}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span> <span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</button> </button>
</div> </div>
</div> </div>
<div class="box list"> <div class="box list">
<h2 class="box-header"> <h2 class="box-header">
{{'allSends' | i18n}} {{ "allSends" | i18n }}
<div class="flex-right">{{sends.length}}</div> <div class="flex-right">{{ sends.length }}</div>
</h2> </h2>
<div class="box-content"> <div class="box-content">
<app-send-list [sends]="sends" title="{{'editItem' | i18n}}" [disabledByPolicy]="disableSend" <app-send-list
(onSelected)="selectSend($event)" (onCopySendLink)="copy($event)" [sends]="sends"
(onRemovePassword)="removePassword($event)" (onDeleteSend)="delete($event)"></app-send-list> title="{{ 'editItem' | i18n }}"
[disabledByPolicy]="disableSend"
(onSelected)="selectSend($event)"
(onCopySendLink)="copy($event)"
(onRemovePassword)="removePassword($event)"
(onDeleteSend)="delete($event)"
></app-send-list>
</div> </div>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="showSearching()"> <ng-container *ngIf="showSearching()">
<div class="no-items" *ngIf="!filteredSends || !filteredSends.length"> <div class="no-items" *ngIf="!filteredSends || !filteredSends.length">
<p>{{'noItemsInList' | i18n}}</p> <p>{{ "noItemsInList" | i18n }}</p>
</div> </div>
<div class="box list full-list" *ngIf="filteredSends && filteredSends.length > 0"> <div class="box list full-list" *ngIf="filteredSends && filteredSends.length > 0">
<div class="box-content"> <div class="box-content">
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" [disabledByPolicy]="disableSend" <app-send-list
(onSelected)="selectSend($event)" (onCopySendLink)="copy($event)" [sends]="filteredSends"
(onRemovePassword)="removePassword($event)" (onDeleteSend)="delete($event)"> title="{{ 'editItem' | i18n }}"
[disabledByPolicy]="disableSend"
(onSelected)="selectSend($event)"
(onCopySendLink)="copy($event)"
(onRemovePassword)="removePassword($event)"
(onDeleteSend)="delete($event)"
>
</app-send-list> </app-send-list>
</div> </div>
</div> </div>

View File

@@ -1,39 +1,33 @@
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 { 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 { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.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";
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
@@ -45,15 +39,34 @@ export class SendGroupingsComponent extends BaseSendComponent {
scopeState: any; scopeState: any;
private loadedTimeout: number; private loadedTimeout: number;
constructor(sendService: SendService, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, sendService: SendService,
policyService: PolicyService, userService: UserService, 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, userService: UserService,
policyService, userService, logService); searchService: SearchService,
private popupUtils: PopupUtilsService,
private stateService: StateService,
private router: Router,
private syncService: SyncService,
private changeDetectorRef: ChangeDetectorRef,
private broadcasterService: BroadcasterService,
logService: LogService
) {
super(
sendService,
i18nService,
platformUtilsService,
environmentService,
ngZone,
searchService,
policyService,
userService,
logService
);
super.onSuccessfulLoad = async () => { super.onSuccessfulLoad = async () => {
this.calculateTypeCounts(); this.calculateTypeCounts();
this.selectAll(); this.selectAll();
@@ -62,9 +75,11 @@ export class SendGroupingsComponent extends BaseSendComponent {
async ngOnInit() { async ngOnInit() {
// Determine Header details // Determine Header details
this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()); this.showLeftHeader = !(
this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()
);
// Clear state of Send Type Component // Clear state of Send Type Component
this.stateService.remove('SendTypeComponent'); this.stateService.remove("SendTypeComponent");
// Let super class finish // Let super class finish
await super.ngOnInit(); await super.ngOnInit();
// Handle State Restore if necessary // Handle State Restore if necessary
@@ -92,7 +107,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
this.broadcasterService.subscribe(ComponentId, (message: any) => { this.broadcasterService.subscribe(ComponentId, (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'syncCompleted': case "syncCompleted":
window.setTimeout(() => { window.setTimeout(() => {
this.load(); this.load();
}, 500); }, 500);
@@ -118,18 +133,18 @@ export class SendGroupingsComponent extends BaseSendComponent {
} }
async selectType(type: SendType) { async selectType(type: SendType) {
this.router.navigate(['/send-type'], { queryParams: { type: type } }); this.router.navigate(["/send-type"], { queryParams: { type: type } });
} }
async selectSend(s: SendView) { async selectSend(s: SendView) {
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } }); this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } });
} }
async addSend() { async addSend() {
if (this.disableSend) { if (this.disableSend) {
return; return;
} }
this.router.navigate(['/add-send']); this.router.navigate(["/add-send"]);
} }
async removePassword(s: SendView): Promise<boolean> { async removePassword(s: SendView): Promise<boolean> {
@@ -140,13 +155,15 @@ export class SendGroupingsComponent extends BaseSendComponent {
} }
showSearching() { showSearching() {
return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)); return (
this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText))
);
} }
private calculateTypeCounts() { private calculateTypeCounts() {
// Create type counts // Create type counts
const typeCounts = new Map<SendType, number>(); const typeCounts = new Map<SendType, number>();
this.sends.forEach(s => { this.sends.forEach((s) => {
if (typeCounts.has(s.type)) { if (typeCounts.has(s.type)) {
typeCounts.set(s.type, typeCounts.get(s.type) + 1); typeCounts.set(s.type, typeCounts.get(s.type) + 1);
} else { } else {

Some files were not shown because too many files have changed in this diff Show More