diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..6313b56c57 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 44684cbb82..308f6675fd 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -6,7 +6,7 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report! - + Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests. - type: textarea id: reproduce diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab2b575450..a4441bfaff 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ ## Type of change + - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) @@ -6,27 +7,26 @@ - [ ] Other ## Objective + - - ## Code changes + -* **file.ext:** Description of what was changed and why +- **file.ext:** Description of what was changed and why ## Screenshots + - - ## Testing requirements + - - ## Before you submit + - [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change has particular **deployment requirements** (notify the DevOps team) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bcad8784d..8c9d9ed670 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -104,8 +104,8 @@ jobs: npm run dist npm run test - # - name: Run linter - # run: npm run lint + - name: Run linter + run: npm run lint - name: Gulp run: gulp ci diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 374b7ac622..4f719db10e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera 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 - -* **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) - -* **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 +- **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 +- **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) +- **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 @@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/brows ## Pull Request Guidelines -* use `npm run lint` and fix any linting suggestions before submitting a pull request -* commit any pull requests against the `master` branch -* include a link to your Community Forums post +- use `npm run lint` and fix any linting suggestions before submitting a pull request +- commit any pull requests against the `master` branch +- include a link to your Community Forums post # Localization (l10n) diff --git a/SECURITY.md b/SECURITY.md index ef94f0b494..7a055501ad 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in - Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue. - Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a - third-party. We may publicly disclose the issue before resolving it, if appropriate. + third-party. We may publicly disclose the issue before resolving it, if appropriate. - Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder. diff --git a/gulpfile.js b/gulpfile.js index e88e489398..f664b490f7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,223 +1,242 @@ -const gulp = require('gulp'), - gulpif = require('gulp-if'), - filter = require('gulp-filter'), - replace = require('gulp-replace'), - jeditor = require("gulp-json-editor"), - child = require('child_process'), - zip = require('gulp-zip'), - manifest = require('./src/manifest.json'), - del = require('del'), - fs = require('fs'); +const gulp = require("gulp"), + gulpif = require("gulp-if"), + filter = require("gulp-filter"), + replace = require("gulp-replace"), + jeditor = require("gulp-json-editor"), + child = require("child_process"), + zip = require("gulp-zip"), + manifest = require("./src/manifest.json"), + del = require("del"), + fs = require("fs"); const paths = { - build: './build/', - dist: './dist/', - coverage: './coverage/', - node_modules: './node_modules/', - popupDir: './src/popup/', - cssDir: './src/popup/css/', - safari: './src/safari/' + build: "./build/", + dist: "./dist/", + coverage: "./coverage/", + node_modules: "./node_modules/", + popupDir: "./src/popup/", + cssDir: "./src/popup/css/", + safari: "./src/safari/", }; const filters = { - fonts: [ - '!build/popup/fonts/*', - 'build/popup/fonts/Open_Sans*.woff', - 'build/popup/fonts/fontawesome*.woff2', - 'build/popup/fonts/fontawesome*.woff' - ], - safari: [ - '!build/safari/**/*' - ], + fonts: [ + "!build/popup/fonts/*", + "build/popup/fonts/Open_Sans*.woff", + "build/popup/fonts/fontawesome*.woff2", + "build/popup/fonts/fontawesome*.woff", + ], + safari: ["!build/safari/**/*"], }; function buildString() { - var build = ''; - if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== '') { - build = `-${process.env.BUILD_NUMBER}`; - } - return build; + var build = ""; + if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") { + build = `-${process.env.BUILD_NUMBER}`; + } + return build; } function distFileName(browserName, ext) { - return `dist-${browserName}${buildString()}.${ext}`; + return `dist-${browserName}${buildString()}.${ext}`; } function dist(browserName, manifest) { - return gulp.src(paths.build + '**/*') - .pipe(filter(['**'].concat(filters.fonts).concat(filters.safari))) - .pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_' + browserName))) - .pipe(gulpif('manifest.json', jeditor(manifest))) - .pipe(zip(distFileName(browserName, 'zip'))) - .pipe(gulp.dest(paths.dist)); + return gulp + .src(paths.build + "**/*") + .pipe(filter(["**"].concat(filters.fonts).concat(filters.safari))) + .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName))) + .pipe(gulpif("manifest.json", jeditor(manifest))) + .pipe(zip(distFileName(browserName, "zip"))) + .pipe(gulp.dest(paths.dist)); } function distFirefox() { - return dist('firefox', (manifest) => { - delete manifest.content_security_policy; - removeShortcuts(manifest); - return manifest; - }); + return dist("firefox", (manifest) => { + delete manifest.content_security_policy; + removeShortcuts(manifest); + return manifest; + }); } function distOpera() { - return dist('opera', (manifest) => { - delete manifest.applications; - delete manifest.content_security_policy; - removeShortcuts(manifest); - return manifest; - }); + return dist("opera", (manifest) => { + delete manifest.applications; + delete manifest.content_security_policy; + removeShortcuts(manifest); + return manifest; + }); } function distChrome() { - return dist('chrome', (manifest) => { - delete manifest.applications; - delete manifest.content_security_policy; - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - return manifest; - }); + return dist("chrome", (manifest) => { + delete manifest.applications; + delete manifest.content_security_policy; + delete manifest.sidebar_action; + delete manifest.commands._execute_sidebar_action; + return manifest; + }); } function distEdge() { - return dist('edge', (manifest) => { - delete manifest.applications; - delete manifest.content_security_policy; - delete manifest.sidebar_action; - delete manifest.commands._execute_sidebar_action; - return manifest; - }); + return dist("edge", (manifest) => { + delete manifest.applications; + delete manifest.content_security_policy; + delete manifest.sidebar_action; + delete manifest.commands._execute_sidebar_action; + return manifest; + }); } function removeShortcuts(manifest) { - if (manifest.content_scripts && manifest.content_scripts.length > 1) { - const shortcutsScript = manifest.content_scripts[1]; - if (shortcutsScript.js.indexOf('content/shortcuts.js') > -1) { - manifest.content_scripts.splice(1, 1); - } + if (manifest.content_scripts && manifest.content_scripts.length > 1) { + const shortcutsScript = manifest.content_scripts[1]; + if (shortcutsScript.js.indexOf("content/shortcuts.js") > -1) { + manifest.content_scripts.splice(1, 1); } + } } function distSafariMas(cb) { - return distSafariApp(cb, 'mas'); + return distSafariApp(cb, "mas"); } function distSafariMasDev(cb) { - return distSafariApp(cb, 'masdev'); + return distSafariApp(cb, "masdev"); } function distSafariDmg(cb) { - return distSafariApp(cb, 'dmg'); + return distSafariApp(cb, "dmg"); } function distSafariApp(cb, subBuildPath) { - const buildPath = paths.dist + 'Safari/' + subBuildPath + '/'; - const builtAppexPath = buildPath + 'build/Release/safari.appex'; - const builtAppexFrameworkPath = buildPath + 'build/Release/safari.appex/Contents/Frameworks/'; - const entitlementsPath = paths.safari + 'safari/safari.entitlements'; - var args = [ - '--verbose', - '--force', - '-o', - 'runtime', - '--sign', - 'Developer ID Application: 8bit Solutions LLC', - '--entitlements', - entitlementsPath + const buildPath = paths.dist + "Safari/" + subBuildPath + "/"; + const builtAppexPath = buildPath + "build/Release/safari.appex"; + const builtAppexFrameworkPath = buildPath + "build/Release/safari.appex/Contents/Frameworks/"; + const entitlementsPath = paths.safari + "safari/safari.entitlements"; + var args = [ + "--verbose", + "--force", + "-o", + "runtime", + "--sign", + "Developer ID Application: 8bit Solutions LLC", + "--entitlements", + entitlementsPath, + ]; + if (subBuildPath !== "dmg") { + args = [ + "--verbose", + "--force", + "--sign", + subBuildPath === "mas" + ? "3rd Party Mac Developer Application: 8bit Solutions LLC" + : "6B287DD81FF922D86FD836128B0F62F358B38726", + "--entitlements", + entitlementsPath, ]; - if (subBuildPath !== 'dmg') { - args = [ - '--verbose', - '--force', - '--sign', - subBuildPath === 'mas' ? '3rd Party Mac Developer Application: 8bit Solutions LLC' : - '6B287DD81FF922D86FD836128B0F62F358B38726', - '--entitlements', - entitlementsPath - ]; - } + } - return del([buildPath + '**/*']) - .then(() => safariCopyAssets(paths.safari + '**/*', buildPath)) - .then(() => safariCopyBuild(paths.build + '**/*', buildPath + 'safari/app')) - .then(() => { - const proc = child.spawn('xcodebuild', [ - '-project', - buildPath + 'desktop.xcodeproj', - '-alltargets', - '-configuration', - 'Release']); - stdOutProc(proc); - return new Promise((resolve) => proc.on('close', resolve)); - }).then(() => { - const libs = fs.readdirSync(builtAppexFrameworkPath).filter((p) => p.endsWith('.dylib')) - .map((p) => builtAppexFrameworkPath + p); - const libPromises = []; - libs.forEach((i) => { - const proc = child.spawn('codesign', args.concat([i])); - stdOutProc(proc); - libPromises.push(new Promise((resolve) => proc.on('close', resolve))); - }); - return Promise.all(libPromises); - }).then(() => { - const proc = child.spawn('codesign', args.concat([builtAppexPath])); - stdOutProc(proc); - return new Promise((resolve) => proc.on('close', resolve)); - }).then(() => { - return cb; - }, () => { - return cb; - }); + return del([buildPath + "**/*"]) + .then(() => safariCopyAssets(paths.safari + "**/*", buildPath)) + .then(() => safariCopyBuild(paths.build + "**/*", buildPath + "safari/app")) + .then(() => { + const proc = child.spawn("xcodebuild", [ + "-project", + buildPath + "desktop.xcodeproj", + "-alltargets", + "-configuration", + "Release", + ]); + stdOutProc(proc); + return new Promise((resolve) => proc.on("close", resolve)); + }) + .then(() => { + const libs = fs + .readdirSync(builtAppexFrameworkPath) + .filter((p) => p.endsWith(".dylib")) + .map((p) => builtAppexFrameworkPath + p); + const libPromises = []; + libs.forEach((i) => { + const proc = child.spawn("codesign", args.concat([i])); + stdOutProc(proc); + libPromises.push(new Promise((resolve) => proc.on("close", resolve))); + }); + return Promise.all(libPromises); + }) + .then(() => { + const proc = child.spawn("codesign", args.concat([builtAppexPath])); + stdOutProc(proc); + return new Promise((resolve) => proc.on("close", resolve)); + }) + .then( + () => { + return cb; + }, + () => { + return cb; + } + ); } function safariCopyAssets(source, dest) { - return new Promise((resolve, reject) => { - gulp.src(source) - .on('error', reject) - .pipe(gulpif('safari/Info.plist', replace('0.0.1', manifest.version))) - .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)) - .on('end', resolve); - }); + return new Promise((resolve, reject) => { + gulp + .src(source) + .on("error", reject) + .pipe(gulpif("safari/Info.plist", replace("0.0.1", manifest.version))) + .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)) + .on("end", resolve); + }); } function safariCopyBuild(source, dest) { - return new Promise((resolve, reject) => { - gulp.src(source) - .on('error', reject) - .pipe(filter(['**'].concat(filters.fonts))) - .pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_safari'))) - .pipe(gulpif('manifest.json', jeditor((manifest) => { - delete manifest.optional_permissions; - manifest.permissions.push("nativeMessaging"); - return manifest; - }))) - .pipe(gulp.dest(dest)) - .on('end', resolve); - }); + return new Promise((resolve, reject) => { + gulp + .src(source) + .on("error", reject) + .pipe(filter(["**"].concat(filters.fonts))) + .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari"))) + .pipe( + gulpif( + "manifest.json", + jeditor((manifest) => { + delete manifest.optional_permissions; + manifest.permissions.push("nativeMessaging"); + return manifest; + }) + ) + ) + .pipe(gulp.dest(dest)) + .on("end", resolve); + }); } function stdOutProc(proc) { - proc.stdout.on('data', (data) => console.log(data.toString())); - proc.stderr.on('data', (data) => console.error(data.toString())); + proc.stdout.on("data", (data) => console.log(data.toString())); + proc.stderr.on("data", (data) => console.error(data.toString())); } function ciCoverage(cb) { - return gulp.src(paths.coverage + '**/*') - .pipe(filter(['**', '!coverage/coverage*.zip'])) - .pipe(zip(`coverage${buildString()}.zip`)) - .pipe(gulp.dest(paths.coverage)); + return gulp + .src(paths.coverage + "**/*") + .pipe(filter(["**", "!coverage/coverage*.zip"])) + .pipe(zip(`coverage${buildString()}.zip`)) + .pipe(gulp.dest(paths.coverage)); } -exports['dist:firefox'] = distFirefox; -exports['dist:chrome'] = distChrome; -exports['dist:opera'] = distOpera; -exports['dist:edge'] = distEdge; -exports['dist:safari'] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg); -exports['dist:safari:mas'] = distSafariMas; -exports['dist:safari:masdev'] = distSafariMasDev; -exports['dist:safari:dmg'] = distSafariDmg; +exports["dist:firefox"] = distFirefox; +exports["dist:chrome"] = distChrome; +exports["dist:opera"] = distOpera; +exports["dist:edge"] = distEdge; +exports["dist:safari"] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg); +exports["dist:safari:mas"] = distSafariMas; +exports["dist:safari:masdev"] = distSafariMasDev; +exports["dist:safari:dmg"] = distSafariDmg; exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge); -exports['ci:coverage'] = ciCoverage; +exports["ci:coverage"] = ciCoverage; exports.ci = ciCoverage; diff --git a/karma.conf.js b/karma.conf.js index 83a7aa41e4..8423ed1ab6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,76 +1,71 @@ -const path = require('path'); +const path = require("path"); -module.exports = function(config) { - config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: "", - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine', 'webpack'], + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine", "webpack"], - // list of files / patterns to load in the browser - files: [ - { pattern: 'src/**/*.spec.ts', watch: false }, - ], + // list of files / patterns to load in the browser + files: [{ pattern: "src/**/*.spec.ts", watch: false }], - exclude: [ - ], + exclude: [], - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'src/**/*.ts': 'webpack' + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + "src/**/*.ts": "webpack", + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Chrome"], + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + client: { + clearContext: false, // leave Jasmine Spec Runner output visible in browser + }, + + webpack: { + mode: "production", + resolve: { + extensions: [".js", ".ts", ".tsx"], + alias: { + "jslib-common": path.join(__dirname, "jslib/common/src"), + "jslib-angular": path.join(__dirname, "jslib/angular/src"), }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'kjhtml'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) + }, + module: { + rules: [{ test: /\.tsx?$/, loader: "ts-loader" }], + }, + stats: { colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome'], - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity, - - client:{ - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - - webpack: { - mode: 'production', - resolve: { - extensions: ['.js', '.ts', '.tsx'], - alias: { - "jslib-common": path.join(__dirname, 'jslib/common/src'), - "jslib-angular": path.join(__dirname, 'jslib/angular/src'), - }, - }, - module: { - rules: [ - {test: /\.tsx?$/, loader: 'ts-loader'} - ] - }, - stats: { - colors: true, - modules: true, - reasons: true, - errorDetails: true - }, - devtool: 'inline-source-map', - }, - }) -} + modules: true, + reasons: true, + errorDetails: true, + }, + devtool: "inline-source-map", + }, + }); +}; diff --git a/src/background.html b/src/background.html index 21a5bd5827..0cd95f3f02 100644 --- a/src/background.html +++ b/src/background.html @@ -1,7 +1,6 @@  - - - - - + + + + diff --git a/src/background.ts b/src/background.ts index 301fe1a289..939a481bd5 100644 --- a/src/background.ts +++ b/src/background.ts @@ -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(() => { - // Finished bootstrapping + // Finished bootstrapping }); diff --git a/src/background/commands.background.ts b/src/background/commands.background.ts index 2e78cdd330..dd1363f898 100644 --- a/src/background/commands.background.ts +++ b/src/background/commands.background.ts @@ -1,98 +1,112 @@ -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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; +import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem"; export default class CommandsBackground { - private isSafari: boolean; - private isVivaldi: boolean; + private isSafari: boolean; + private isVivaldi: boolean; - constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService, - private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService) { - this.isSafari = this.platformUtilsService.isSafari(); - this.isVivaldi = this.platformUtilsService.isVivaldi(); - } + constructor( + private main: MainBackground, + private passwordGenerationService: PasswordGenerationService, + private platformUtilsService: PlatformUtilsService, + private vaultTimeoutService: VaultTimeoutService + ) { + this.isSafari = this.platformUtilsService.isSafari(); + this.isVivaldi = this.platformUtilsService.isVivaldi(); + } - async init() { - BrowserApi.messageListener('commands.background', 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) { - await this.processCommand(msg.shortcut, sender); - } - }); - - if (!this.isVivaldi && chrome && chrome.commands) { - chrome.commands.onCommand.addListener(async (command: string) => { - await this.processCommand(command); - }); - } - } - - private async processCommand(command: string, sender?: chrome.runtime.MessageSender) { - switch (command) { - case 'generate_password': - await this.generatePasswordToClipboard(); - break; - case 'autofill_login': - await this.autoFillLogin(sender ? sender.tab : null); - break; - case 'open_popup': - await this.openPopup(); - break; - case 'lock_vault': - await this.vaultTimeoutService.lock(true); - break; - default: - break; - } - } - - private async generatePasswordToClipboard() { - const options = (await this.passwordGenerationService.getOptions())[0]; - const password = await this.passwordGenerationService.generatePassword(options); - this.platformUtilsService.copyToClipboard(password, { window: window }); - this.passwordGenerationService.addHistory(password); - } - - private async autoFillLogin(tab?: chrome.tabs.Tab) { - if (!tab) { - tab = await BrowserApi.getTabFromCurrentWindowId(); + async init() { + BrowserApi.messageListener( + "commands.background", + 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 (tab == null) { - return; + if (this.isVivaldi && msg.command === "keyboardShortcutTriggered" && msg.shortcut) { + await this.processCommand(msg.shortcut, sender); } + } + ); - if (await this.vaultTimeoutService.isLocked()) { - const retryMessage: LockedVaultPendingNotificationsItem = { - commandToRetry: { - msg: { command: 'autofill_login' }, - sender: { tab: tab }, - }, - target: 'commands.background', - }; - await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage); + if (!this.isVivaldi && chrome && chrome.commands) { + chrome.commands.onCommand.addListener(async (command: string) => { + await this.processCommand(command); + }); + } + } - BrowserApi.tabSendMessageData(tab, 'promptForLogin'); - return; - } + private async processCommand(command: string, sender?: chrome.runtime.MessageSender) { + switch (command) { + case "generate_password": + await this.generatePasswordToClipboard(); + break; + case "autofill_login": + await this.autoFillLogin(sender ? sender.tab : null); + break; + case "open_popup": + await this.openPopup(); + break; + case "lock_vault": + await this.vaultTimeoutService.lock(true); + break; + default: + break; + } + } - await this.main.collectPageDetailsForContentScript(tab, 'autofill_cmd'); + private async generatePasswordToClipboard() { + const options = (await this.passwordGenerationService.getOptions())[0]; + const password = await this.passwordGenerationService.generatePassword(options); + this.platformUtilsService.copyToClipboard(password, { window: window }); + this.passwordGenerationService.addHistory(password); + } + + private async autoFillLogin(tab?: chrome.tabs.Tab) { + if (!tab) { + tab = await BrowserApi.getTabFromCurrentWindowId(); } - private async openPopup() { - // Chrome APIs cannot open popup - if (!this.isSafari) { - return; - } - - this.main.openPopup(); + if (tab == null) { + return; } + + if (await this.vaultTimeoutService.isLocked()) { + const retryMessage: LockedVaultPendingNotificationsItem = { + commandToRetry: { + msg: { command: "autofill_login" }, + sender: { tab: tab }, + }, + target: "commands.background", + }; + await BrowserApi.tabSendMessageData( + tab, + "addToLockedVaultPendingNotifications", + retryMessage + ); + + BrowserApi.tabSendMessageData(tab, "promptForLogin"); + return; + } + + await this.main.collectPageDetailsForContentScript(tab, "autofill_cmd"); + } + + private async openPopup() { + // Chrome APIs cannot open popup + if (!this.isSafari) { + return; + } + + this.main.openPopup(); + } } diff --git a/src/background/contextMenus.background.ts b/src/background/contextMenus.background.ts index 6852669dc4..1b0da1e213 100644 --- a/src/background/contextMenus.background.ts +++ b/src/background/contextMenus.background.ts @@ -1,123 +1,142 @@ -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 { EventService } from 'jslib-common/abstractions/event.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { TotpService } from 'jslib-common/abstractions/totp.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { EventService } from "jslib-common/abstractions/event.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { TotpService } from "jslib-common/abstractions/totp.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; -import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; -import { EventType } from 'jslib-common/enums/eventType'; -import { CipherView } from 'jslib-common/models/view/cipherView'; -import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; +import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; +import { EventType } from "jslib-common/enums/eventType"; +import { CipherView } from "jslib-common/models/view/cipherView"; +import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem"; export default class ContextMenusBackground { - private readonly noopCommandSuffix = 'noop'; - private contextMenus: any; + private readonly noopCommandSuffix = "noop"; + private contextMenus: any; - constructor(private main: MainBackground, private cipherService: CipherService, - private passwordGenerationService: PasswordGenerationService, - private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService, - private eventService: EventService, private totpService: TotpService) { - this.contextMenus = chrome.contextMenus; + constructor( + private main: MainBackground, + private cipherService: CipherService, + private passwordGenerationService: PasswordGenerationService, + private platformUtilsService: PlatformUtilsService, + private vaultTimeoutService: VaultTimeoutService, + private eventService: EventService, + private totpService: TotpService + ) { + this.contextMenus = chrome.contextMenus; + } + + async init() { + if (!this.contextMenus) { + return; } - async init() { - if (!this.contextMenus) { - return; + this.contextMenus.onClicked.addListener( + async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => { + if (info.menuItemId === "generate-password") { + await this.generatePasswordToClipboard(); + } else if (info.menuItemId === "copy-identifier") { + await this.getClickedElement(tab, info.frameId); + } else if ( + info.parentMenuItemId === "autofill" || + info.parentMenuItemId === "copy-username" || + info.parentMenuItemId === "copy-password" || + info.parentMenuItemId === "copy-totp" + ) { + await this.cipherAction(tab, info); } + } + ); - this.contextMenus.onClicked.addListener(async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => { - if (info.menuItemId === 'generate-password') { - await this.generatePasswordToClipboard(); - } else if (info.menuItemId === 'copy-identifier') { - await this.getClickedElement(tab, info.frameId); - } else if (info.parentMenuItemId === 'autofill' || - info.parentMenuItemId === 'copy-username' || - info.parentMenuItemId === 'copy-password' || - info.parentMenuItemId === 'copy-totp') { - 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() { + const options = (await this.passwordGenerationService.getOptions())[0]; + const password = await this.passwordGenerationService.generatePassword(options); + this.platformUtilsService.copyToClipboard(password, { window: window }); + this.passwordGenerationService.addHistory(password); + } + + private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) { + if (tab == null) { + return; } - private async generatePasswordToClipboard() { - const options = (await this.passwordGenerationService.getOptions())[0]; - const password = await this.passwordGenerationService.generatePassword(options); - this.platformUtilsService.copyToClipboard(password, { window: window }); - this.passwordGenerationService.addHistory(password); + BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId }); + } + + private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { + const id = info.menuItemId.split("_")[1]; + + if (await this.vaultTimeoutService.isLocked()) { + const retryMessage: LockedVaultPendingNotificationsItem = { + commandToRetry: { + msg: { command: this.noopCommandSuffix, data: info }, + sender: { tab: tab }, + }, + target: "contextmenus.background", + }; + await BrowserApi.tabSendMessageData( + tab, + "addToLockedVaultPendingNotifications", + retryMessage + ); + + BrowserApi.tabSendMessageData(tab, "promptForLogin"); + return; } - private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) { - if (tab == null) { - return; - } - - BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId }); + let cipher: CipherView; + if (id === this.noopCommandSuffix) { + const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url); + cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None); + } else { + const ciphers = await this.cipherService.getAllDecrypted(); + cipher = ciphers.find((c) => c.id === id); } - private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { - const id = info.menuItemId.split('_')[1]; - - if (await this.vaultTimeoutService.isLocked()) { - const retryMessage: LockedVaultPendingNotificationsItem = { - commandToRetry: { - msg: { command: this.noopCommandSuffix, data: info }, - sender: { tab: tab }, - }, - target: 'contextmenus.background', - }; - await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage); - - BrowserApi.tabSendMessageData(tab, 'promptForLogin'); - return; - } - - let cipher: CipherView; - if (id === this.noopCommandSuffix) { - const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url); - cipher = ciphers.find(c => c.reprompt === CipherRepromptType.None); - } else { - const ciphers = await this.cipherService.getAllDecrypted(); - cipher = ciphers.find(c => c.id === id); - } - - if (cipher == null) { - return; - } - - if (info.parentMenuItemId === 'autofill') { - await this.startAutofillPage(tab, cipher); - } else if (info.parentMenuItemId === 'copy-username') { - this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window }); - } else if (info.parentMenuItemId === 'copy-password') { - this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window }); - this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - } else if (info.parentMenuItemId === 'copy-totp') { - const totpValue = await this.totpService.getCode(cipher.login.totp); - this.platformUtilsService.copyToClipboard(totpValue, { window: window }); - } + if (cipher == null) { + return; } - private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) { - this.main.loginToAutoFill = cipher; - if (tab == null) { - return; - } - - BrowserApi.tabSendMessage(tab, { - command: 'collectPageDetails', - tab: tab, - sender: 'contextMenu', - }); + if (info.parentMenuItemId === "autofill") { + await this.startAutofillPage(tab, cipher); + } else if (info.parentMenuItemId === "copy-username") { + this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window }); + } else if (info.parentMenuItemId === "copy-password") { + this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window }); + this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); + } else if (info.parentMenuItemId === "copy-totp") { + const totpValue = await this.totpService.getCode(cipher.login.totp); + this.platformUtilsService.copyToClipboard(totpValue, { window: window }); } + } + + private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) { + this.main.loginToAutoFill = cipher; + if (tab == null) { + return; + } + + BrowserApi.tabSendMessage(tab, { + command: "collectPageDetails", + tab: tab, + sender: "contextMenu", + }); + } } diff --git a/src/background/idle.background.ts b/src/background/idle.background.ts index 8b66fb5473..9c59b8801a 100644 --- a/src/background/idle.background.ts +++ b/src/background/idle.background.ts @@ -1,68 +1,75 @@ -import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { NotificationsService } from "jslib-common/abstractions/notifications.service"; +import { StorageService } from "jslib-common/abstractions/storage.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 export default class IdleBackground { - private idle: any; - private idleTimer: number = null; - private idleState = 'active'; + private idle: any; + private idleTimer: number = null; + private idleState = "active"; - constructor(private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService, - private notificationsService: NotificationsService) { - this.idle = chrome.idle || (browser != null ? browser.idle : null); + constructor( + private vaultTimeoutService: VaultTimeoutService, + private storageService: StorageService, + private notificationsService: NotificationsService + ) { + this.idle = chrome.idle || (browser != null ? browser.idle : null); + } + + async init() { + if (!this.idle) { + return; } - async init() { - if (!this.idle) { - return; - } + const idleHandler = (newState: string) => { + if (newState === "active") { + this.notificationsService.reconnectFromActivity(); + } else { + this.notificationsService.disconnectFromInactivity(); + } + }; + if (this.idle.onStateChanged && this.idle.setDetectionInterval) { + this.idle.setDetectionInterval(IdleInterval); + this.idle.onStateChanged.addListener(idleHandler); + } else { + this.pollIdle(idleHandler); + } - const idleHandler = (newState: string) => { - if (newState === 'active') { - this.notificationsService.reconnectFromActivity(); + if (this.idle.onStateChanged) { + this.idle.onStateChanged.addListener(async (newState: string) => { + if (newState === "locked") { + // If the screen is locked or the screensaver activates + const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); + if (timeout === -2) { + // On System Lock vault timeout option + const action = await this.storageService.get( + ConstantsService.vaultTimeoutActionKey + ); + if (action === "logOut") { + await this.vaultTimeoutService.logOut(); } else { - this.notificationsService.disconnectFromInactivity(); + await this.vaultTimeoutService.lock(true); } - }; - if (this.idle.onStateChanged && this.idle.setDetectionInterval) { - this.idle.setDetectionInterval(IdleInterval); - this.idle.onStateChanged.addListener(idleHandler); - } else { - this.pollIdle(idleHandler); - } - - if (this.idle.onStateChanged) { - this.idle.onStateChanged.addListener(async (newState: string) => { - if (newState === 'locked') { // If the screen is locked or the screensaver activates - const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); - if (timeout === -2) { // On System Lock vault timeout option - const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); - if (action === 'logOut') { - await this.vaultTimeoutService.logOut(); - } else { - await this.vaultTimeoutService.lock(true); - } - } - } - }); + } } + }); } + } - private pollIdle(handler: (newState: string) => void) { - if (this.idleTimer != null) { - window.clearTimeout(this.idleTimer); - this.idleTimer = null; - } - this.idle.queryState(IdleInterval, (state: string) => { - if (state !== this.idleState) { - this.idleState = state; - handler(state); - } - this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000); - }); + private pollIdle(handler: (newState: string) => void) { + if (this.idleTimer != null) { + window.clearTimeout(this.idleTimer); + this.idleTimer = null; } + this.idle.queryState(IdleInterval, (state: string) => { + if (state !== this.idleState) { + this.idleState = state; + handler(state); + } + this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000); + }); + } } diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 6e4a6ee75a..dda50fcb39 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -1,772 +1,960 @@ -import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; +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 { AppIdService } from 'jslib-common/services/appId.service'; -import { AuditService } from 'jslib-common/services/audit.service'; -import { AuthService } from 'jslib-common/services/auth.service'; -import { CipherService } from 'jslib-common/services/cipher.service'; -import { CollectionService } from 'jslib-common/services/collection.service'; -import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; -import { ContainerService } from 'jslib-common/services/container.service'; -import { EnvironmentService } from 'jslib-common/services/environment.service'; -import { EventService } from 'jslib-common/services/event.service'; -import { ExportService } from 'jslib-common/services/export.service'; -import { FileUploadService } from 'jslib-common/services/fileUpload.service'; -import { FolderService } from 'jslib-common/services/folder.service'; -import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; -import { NotificationsService } from 'jslib-common/services/notifications.service'; -import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; -import { PolicyService } from 'jslib-common/services/policy.service'; -import { SearchService } from 'jslib-common/services/search.service'; -import { SendService } from 'jslib-common/services/send.service'; -import { SettingsService } from 'jslib-common/services/settings.service'; -import { StateService } from 'jslib-common/services/state.service'; -import { SyncService } from 'jslib-common/services/sync.service'; -import { SystemService } from 'jslib-common/services/system.service'; -import { TokenService } from 'jslib-common/services/token.service'; -import { TotpService } from 'jslib-common/services/totp.service'; -import { UserService } from 'jslib-common/services/user.service'; -import { UserVerificationService } from 'jslib-common/services/userVerification.service'; -import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service'; +import { ApiService } from "jslib-common/services/api.service"; +import { AppIdService } from "jslib-common/services/appId.service"; +import { AuditService } from "jslib-common/services/audit.service"; +import { AuthService } from "jslib-common/services/auth.service"; +import { CipherService } from "jslib-common/services/cipher.service"; +import { CollectionService } from "jslib-common/services/collection.service"; +import { ConsoleLogService } from "jslib-common/services/consoleLog.service"; +import { ConstantsService } from "jslib-common/services/constants.service"; +import { ContainerService } from "jslib-common/services/container.service"; +import { EnvironmentService } from "jslib-common/services/environment.service"; +import { EventService } from "jslib-common/services/event.service"; +import { ExportService } from "jslib-common/services/export.service"; +import { FileUploadService } from "jslib-common/services/fileUpload.service"; +import { FolderService } from "jslib-common/services/folder.service"; +import { KeyConnectorService } from "jslib-common/services/keyConnector.service"; +import { NotificationsService } from "jslib-common/services/notifications.service"; +import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service"; +import { PolicyService } from "jslib-common/services/policy.service"; +import { SearchService } from "jslib-common/services/search.service"; +import { SendService } from "jslib-common/services/send.service"; +import { SettingsService } from "jslib-common/services/settings.service"; +import { StateService } from "jslib-common/services/state.service"; +import { SyncService } from "jslib-common/services/sync.service"; +import { SystemService } from "jslib-common/services/system.service"; +import { TokenService } from "jslib-common/services/token.service"; +import { TotpService } from "jslib-common/services/totp.service"; +import { UserService } from "jslib-common/services/user.service"; +import { UserVerificationService } from "jslib-common/services/userVerification.service"; +import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service"; -import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service'; -import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service'; -import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service'; -import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service'; -import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service'; -import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; -import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; -import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; -import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; -import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service'; -import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service'; -import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service'; -import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; -import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service'; -import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service'; -import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; -import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; -import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service'; -import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service'; -import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service'; -import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service'; -import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; -import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; -import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; -import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service'; -import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; -import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service'; -import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service'; -import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service'; -import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; +import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service"; +import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service"; +import { AuditService as AuditServiceAbstraction } from "jslib-common/abstractions/audit.service"; +import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service"; +import { CipherService as CipherServiceAbstraction } from "jslib-common/abstractions/cipher.service"; +import { CollectionService as CollectionServiceAbstraction } from "jslib-common/abstractions/collection.service"; +import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service"; +import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service"; +import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service"; +import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service"; +import { ExportService as ExportServiceAbstraction } from "jslib-common/abstractions/export.service"; +import { FileUploadService as FileUploadServiceAbstraction } from "jslib-common/abstractions/fileUpload.service"; +import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service"; +import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service"; +import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service"; +import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service"; +import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service"; +import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service"; +import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service"; +import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service"; +import { SendService as SendServiceAbstraction } from "jslib-common/abstractions/send.service"; +import { SettingsService as SettingsServiceAbstraction } from "jslib-common/abstractions/settings.service"; +import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service"; +import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; +import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service"; +import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service"; +import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service"; +import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service"; +import { UserService as UserServiceAbstraction } from "jslib-common/abstractions/user.service"; +import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.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 { SafariApp } from '../browser/safariApp'; +import { BrowserApi } from "../browser/browserApi"; +import { SafariApp } from "../browser/safariApp"; -import CommandsBackground from './commands.background'; -import ContextMenusBackground from './contextMenus.background'; -import IdleBackground from './idle.background'; -import { NativeMessagingBackground } from './nativeMessaging.background'; -import NotificationBackground from './notification.background'; -import RuntimeBackground from './runtime.background'; -import TabsBackground from './tabs.background'; -import WebRequestBackground from './webRequest.background'; -import WindowsBackground from './windows.background'; +import CommandsBackground from "./commands.background"; +import ContextMenusBackground from "./contextMenus.background"; +import IdleBackground from "./idle.background"; +import { NativeMessagingBackground } from "./nativeMessaging.background"; +import NotificationBackground from "./notification.background"; +import RuntimeBackground from "./runtime.background"; +import TabsBackground from "./tabs.background"; +import WebRequestBackground from "./webRequest.background"; +import WindowsBackground from "./windows.background"; -import { PopupUtilsService } from '../popup/services/popup-utils.service'; -import AutofillService from '../services/autofill.service'; -import { BrowserCryptoService } from '../services/browserCrypto.service'; -import BrowserMessagingService from '../services/browserMessaging.service'; -import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; -import BrowserStorageService from '../services/browserStorage.service'; -import I18nService from '../services/i18n.service'; -import VaultTimeoutService from '../services/vaultTimeout.service'; +import { PopupUtilsService } from "../popup/services/popup-utils.service"; +import AutofillService from "../services/autofill.service"; +import { BrowserCryptoService } from "../services/browserCrypto.service"; +import BrowserMessagingService from "../services/browserMessaging.service"; +import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; +import BrowserStorageService from "../services/browserStorage.service"; +import I18nService from "../services/i18n.service"; +import VaultTimeoutService from "../services/vaultTimeout.service"; export default class MainBackground { - messagingService: MessagingServiceAbstraction; - storageService: StorageServiceAbstraction; - secureStorageService: StorageServiceAbstraction; - i18nService: I18nServiceAbstraction; - platformUtilsService: PlatformUtilsServiceAbstraction; - constantsService: ConstantsService; - logService: LogServiceAbstraction; - cryptoService: CryptoServiceAbstraction; - cryptoFunctionService: CryptoFunctionServiceAbstraction; - tokenService: TokenServiceAbstraction; - appIdService: AppIdServiceAbstraction; - apiService: ApiServiceAbstraction; - environmentService: EnvironmentServiceAbstraction; - userService: UserServiceAbstraction; - settingsService: SettingsServiceAbstraction; - cipherService: CipherServiceAbstraction; - folderService: FolderServiceAbstraction; - collectionService: CollectionServiceAbstraction; - vaultTimeoutService: VaultTimeoutServiceAbstraction; - syncService: SyncServiceAbstraction; - passwordGenerationService: PasswordGenerationServiceAbstraction; - totpService: TotpServiceAbstraction; - autofillService: AutofillServiceAbstraction; - containerService: ContainerService; - auditService: AuditServiceAbstraction; - authService: AuthServiceAbstraction; - exportService: ExportServiceAbstraction; - searchService: SearchServiceAbstraction; - notificationsService: NotificationsServiceAbstraction; - stateService: StateServiceAbstraction; - systemService: SystemServiceAbstraction; - eventService: EventServiceAbstraction; - policyService: PolicyServiceAbstraction; - popupUtilsService: PopupUtilsService; - sendService: SendServiceAbstraction; - fileUploadService: FileUploadServiceAbstraction; - keyConnectorService: KeyConnectorServiceAbstraction; - userVerificationService: UserVerificationServiceAbstraction; + messagingService: MessagingServiceAbstraction; + storageService: StorageServiceAbstraction; + secureStorageService: StorageServiceAbstraction; + i18nService: I18nServiceAbstraction; + platformUtilsService: PlatformUtilsServiceAbstraction; + constantsService: ConstantsService; + logService: LogServiceAbstraction; + cryptoService: CryptoServiceAbstraction; + cryptoFunctionService: CryptoFunctionServiceAbstraction; + tokenService: TokenServiceAbstraction; + appIdService: AppIdServiceAbstraction; + apiService: ApiServiceAbstraction; + environmentService: EnvironmentServiceAbstraction; + userService: UserServiceAbstraction; + settingsService: SettingsServiceAbstraction; + cipherService: CipherServiceAbstraction; + folderService: FolderServiceAbstraction; + collectionService: CollectionServiceAbstraction; + vaultTimeoutService: VaultTimeoutServiceAbstraction; + syncService: SyncServiceAbstraction; + passwordGenerationService: PasswordGenerationServiceAbstraction; + totpService: TotpServiceAbstraction; + autofillService: AutofillServiceAbstraction; + containerService: ContainerService; + auditService: AuditServiceAbstraction; + authService: AuthServiceAbstraction; + exportService: ExportServiceAbstraction; + searchService: SearchServiceAbstraction; + notificationsService: NotificationsServiceAbstraction; + stateService: StateServiceAbstraction; + systemService: SystemServiceAbstraction; + eventService: EventServiceAbstraction; + policyService: PolicyServiceAbstraction; + popupUtilsService: PopupUtilsService; + sendService: SendServiceAbstraction; + fileUploadService: FileUploadServiceAbstraction; + keyConnectorService: KeyConnectorServiceAbstraction; + userVerificationService: UserVerificationServiceAbstraction; - onUpdatedRan: boolean; - onReplacedRan: boolean; - loginToAutoFill: CipherView = null; + onUpdatedRan: boolean; + onReplacedRan: boolean; + loginToAutoFill: CipherView = null; - private commandsBackground: CommandsBackground; - private contextMenusBackground: ContextMenusBackground; - private idleBackground: IdleBackground; - private notificationBackground: NotificationBackground; - private runtimeBackground: RuntimeBackground; - private tabsBackground: TabsBackground; - private webRequestBackground: WebRequestBackground; - private windowsBackground: WindowsBackground; + private commandsBackground: CommandsBackground; + private contextMenusBackground: ContextMenusBackground; + private idleBackground: IdleBackground; + private notificationBackground: NotificationBackground; + private runtimeBackground: RuntimeBackground; + private tabsBackground: TabsBackground; + private webRequestBackground: WebRequestBackground; + private windowsBackground: WindowsBackground; - private sidebarAction: any; - private buildingContextMenu: boolean; - private menuOptionsLoaded: any[] = []; - private syncTimeout: any; - private isSafari: boolean; - private nativeMessagingBackground: NativeMessagingBackground; + private sidebarAction: any; + private buildingContextMenu: boolean; + private menuOptionsLoaded: any[] = []; + private syncTimeout: any; + private isSafari: boolean; + private nativeMessagingBackground: NativeMessagingBackground; - constructor() { - // Services - this.messagingService = new BrowserMessagingService(); - this.storageService = new BrowserStorageService(); - this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService, this.storageService, - (clipboardValue, clearMs) => { - if (this.systemService != null) { - this.systemService.clearClipboard(clipboardValue, clearMs); - } - }, - async () => { - if (this.nativeMessagingBackground != null) { - const promise = this.nativeMessagingBackground.getResponse(); - - try { - await this.nativeMessagingBackground.send({ command: 'biometricUnlock' }); - } catch (e) { - return Promise.reject(e); - } - - return promise.then(result => result.response === 'unlocked'); - } - }); - this.secureStorageService = new BrowserStorageService(); - this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); - this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService); - this.logService = new ConsoleLogService(false); - this.cryptoService = new BrowserCryptoService(this.storageService, this.secureStorageService, - this.cryptoFunctionService, this.platformUtilsService, this.logService); - this.tokenService = new TokenService(this.storageService); - this.appIdService = new AppIdService(this.storageService); - this.environmentService = new EnvironmentService(this.storageService); - this.apiService = new ApiService(this.tokenService, this.platformUtilsService, this.environmentService, - (expired: boolean) => this.logout(expired)); - this.userService = new UserService(this.tokenService, this.storageService); - this.settingsService = new SettingsService(this.userService, this.storageService); - this.fileUploadService = new FileUploadService(this.logService, this.apiService); - this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, - this.apiService, this.fileUploadService, 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.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService, - this.storageService, this.i18nService, this.cryptoFunctionService); - this.stateService = new StateService(); - this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); - this.keyConnectorService = new KeyConnectorService(this.storageService, this.userService, this.cryptoService, - 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, - async () => { - if (this.notificationsService != null) { - this.notificationsService.updateConnection(false); - } - await this.setIcon(); - await this.refreshBadgeAndMenu(true); - if (this.systemService != null) { - this.systemService.startProcessReload(); - await this.systemService.clearPendingClipboard(); - } - }, async () => await this.logout(false)); - this.syncService = new SyncService(this.userService, this.apiService, this.settingsService, - this.folderService, this.cipherService, this.cryptoService, this.collectionService, - this.storageService, 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.auditService = new AuditService(this.cryptoFunctionService, this.apiService); - this.exportService = new ExportService(this.folderService, this.cipherService, 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.systemService = new SystemService(this.storageService, this.vaultTimeoutService, - this.messagingService, this.platformUtilsService, () => { - const forceWindowReload = this.platformUtilsService.isSafari() || - this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera(); - BrowserApi.reloadExtension(forceWindowReload ? window : null); - return Promise.resolve(); - }); - this.userVerificationService = new UserVerificationService(this.cryptoService, this.i18nService, - this.apiService); - - // Other fields - this.isSafari = this.platformUtilsService.isSafari(); - this.sidebarAction = this.isSafari ? null : (typeof opr !== 'undefined') && opr.sidebarAction ? - opr.sidebarAction : (window as any).chrome.sidebarAction; - - // Background - this.runtimeBackground = new RuntimeBackground(this, this.autofillService, - this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, - this.notificationsService, this.systemService, this.environmentService, this.messagingService, - 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.contextMenusBackground = new ContextMenusBackground(this, this.cipherService, this.passwordGenerationService, - this.platformUtilsService, 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); - - const that = this; - this.authService = new AuthService(this.cryptoService, 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. - send = (subscriber: string, arg: any = {}) => { - const message = Object.assign({}, { command: subscriber }, arg); - that.runtimeBackground.processMessage(message, that, null); - } - }(), this.vaultTimeoutService, this.logService, this.cryptoFunctionService, this.environmentService, - this.keyConnectorService); - } - - async bootstrap() { - this.containerService.attachToWindow(window); - - (this.authService as AuthService).init(); - await (this.vaultTimeoutService as VaultTimeoutService).init(true); - await (this.i18nService as I18nService).init(); - await (this.eventService as EventService).init(true); - await this.runtimeBackground.init(); - await this.notificationBackground.init(); - await this.commandsBackground.init(); - - await this.tabsBackground.init(); - await this.contextMenusBackground.init(); - await this.idleBackground.init(); - await this.webRequestBackground.init(); - await this.windowsBackground.init(); - - return new Promise(resolve => { - setTimeout(async () => { - await this.environmentService.setUrlsFromStorage(); - await this.setIcon(); - this.fullSync(true); - setTimeout(() => this.notificationsService.init(), 2500); - resolve(); - }, 500); - }); - } - - async setIcon() { - if (!chrome.browserAction && !this.sidebarAction) { - return; + constructor() { + // Services + this.messagingService = new BrowserMessagingService(); + this.storageService = new BrowserStorageService(); + this.platformUtilsService = new BrowserPlatformUtilsService( + this.messagingService, + this.storageService, + (clipboardValue, clearMs) => { + if (this.systemService != null) { + this.systemService.clearClipboard(clipboardValue, clearMs); } + }, + async () => { + if (this.nativeMessagingBackground != null) { + const promise = this.nativeMessagingBackground.getResponse(); - const isAuthenticated = await this.userService.isAuthenticated(); - const locked = await this.vaultTimeoutService.isLocked(); + try { + await this.nativeMessagingBackground.send({ command: "biometricUnlock" }); + } catch (e) { + return Promise.reject(e); + } - let suffix = ''; - if (!isAuthenticated) { - suffix = '_gray'; - } else if (locked) { - suffix = '_locked'; + return promise.then((result) => result.response === "unlocked"); } - - await this.actionSetIcon(chrome.browserAction, suffix); - await this.actionSetIcon(this.sidebarAction, suffix); - } - - async refreshBadgeAndMenu(forLocked: boolean = false) { - if (!chrome.windows || !chrome.contextMenus) { - return; + } + ); + this.secureStorageService = new BrowserStorageService(); + this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); + this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService); + this.logService = new ConsoleLogService(false); + this.cryptoService = new BrowserCryptoService( + this.storageService, + this.secureStorageService, + this.cryptoFunctionService, + this.platformUtilsService, + this.logService + ); + this.tokenService = new TokenService(this.storageService); + this.appIdService = new AppIdService(this.storageService); + this.environmentService = new EnvironmentService(this.storageService); + this.apiService = new ApiService( + this.tokenService, + this.platformUtilsService, + this.environmentService, + (expired: boolean) => this.logout(expired) + ); + this.userService = new UserService(this.tokenService, this.storageService); + this.settingsService = new SettingsService(this.userService, this.storageService); + this.fileUploadService = new FileUploadService(this.logService, this.apiService); + this.cipherService = new CipherService( + this.cryptoService, + this.userService, + this.settingsService, + this.apiService, + this.fileUploadService, + 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.sendService = new SendService( + this.cryptoService, + this.userService, + this.apiService, + this.fileUploadService, + this.storageService, + this.i18nService, + this.cryptoFunctionService + ); + this.stateService = new StateService(); + this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); + this.keyConnectorService = new KeyConnectorService( + this.storageService, + this.userService, + this.cryptoService, + 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, + async () => { + if (this.notificationsService != null) { + this.notificationsService.updateConnection(false); } - - const menuDisabled = await this.storageService.get(ConstantsService.disableContextMenuItemKey); - if (!menuDisabled) { - await this.buildContextMenu(); - } else { - await this.contextMenusRemoveAll(); - } - - if (forLocked) { - await this.loadMenuAndUpdateBadgeForNoAccessState(!menuDisabled); - this.onUpdatedRan = this.onReplacedRan = false; - return; - } - - const tab = await BrowserApi.getTabFromCurrentWindow(); - if (tab) { - await this.contextMenuReady(tab, !menuDisabled); - } - } - - async logout(expired: boolean) { - await this.eventService.uploadEvents(); - const userId = await this.userService.getUserId(); - - await Promise.all([ - this.eventService.clearEvents(), - this.syncService.setLastSync(new Date(0)), - this.tokenService.clearToken(), - this.cryptoService.clearKeys(), - this.userService.clear(), - this.settingsService.clear(userId), - this.cipherService.clear(userId), - this.folderService.clear(userId), - this.collectionService.clear(userId), - this.policyService.clear(userId), - this.passwordGenerationService.clear(), - this.vaultTimeoutService.clear(), - this.keyConnectorService.clear(), - ]); - - this.searchService.clearIndex(); - this.messagingService.send('doneLoggingOut', { expired: expired }); - await this.setIcon(); - await this.refreshBadgeAndMenu(); - await this.reseedStorage(); - this.notificationsService.updateConnection(false); - this.systemService.startProcessReload(); - await this.systemService.clearPendingClipboard(); - } - - async collectPageDetailsForContentScript(tab: any, sender: string, frameId: number = null) { - if (tab == null || !tab.id) { - return; + await this.refreshBadgeAndMenu(true); + if (this.systemService != null) { + this.systemService.startProcessReload(); + await this.systemService.clearPendingClipboard(); } + }, + async () => await this.logout(false) + ); + this.syncService = new SyncService( + this.userService, + this.apiService, + this.settingsService, + this.folderService, + this.cipherService, + this.cryptoService, + this.collectionService, + this.storageService, + 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.auditService = new AuditService(this.cryptoFunctionService, this.apiService); + this.exportService = new ExportService( + this.folderService, + this.cipherService, + 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.systemService = new SystemService( + this.storageService, + this.vaultTimeoutService, + this.messagingService, + this.platformUtilsService, + () => { + const forceWindowReload = + this.platformUtilsService.isSafari() || + this.platformUtilsService.isFirefox() || + this.platformUtilsService.isOpera(); + BrowserApi.reloadExtension(forceWindowReload ? window : null); + return Promise.resolve(); + } + ); + this.userVerificationService = new UserVerificationService( + this.cryptoService, + this.i18nService, + this.apiService + ); - const options: any = {}; - if (frameId != null) { - options.frameId = frameId; - } + // Other fields + this.isSafari = this.platformUtilsService.isSafari(); + this.sidebarAction = this.isSafari + ? null + : typeof opr !== "undefined" && opr.sidebarAction + ? opr.sidebarAction + : (window as any).chrome.sidebarAction; - BrowserApi.tabSendMessage(tab, { - command: 'collectPageDetails', - tab: tab, - sender: sender, - }, options); - } + // Background + this.runtimeBackground = new RuntimeBackground( + this, + this.autofillService, + this.platformUtilsService as BrowserPlatformUtilsService, + this.storageService, + this.i18nService, + this.notificationsService, + this.systemService, + this.environmentService, + this.messagingService, + 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 + ); - async openPopup() { - // Chrome APIs cannot open popup + this.tabsBackground = new TabsBackground(this, this.notificationBackground); + this.contextMenusBackground = new ContextMenusBackground( + this, + this.cipherService, + this.passwordGenerationService, + this.platformUtilsService, + 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); - // TODO: Do we need to open this popup? - if (!this.isSafari) { - return; - } - await SafariApp.sendMessageToApp('showPopover', null, true); - } - - async reseedStorage() { - if (!this.platformUtilsService.isChrome() && !this.platformUtilsService.isVivaldi() && - !this.platformUtilsService.isOpera()) { - return; - } - - const currentVaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); - if (currentVaultTimeout == null) { - return; - } - - const getStorage = (): Promise => new Promise(resolve => { - chrome.storage.local.get(null, (o: any) => resolve(o)); - }); - - const clearStorage = (): Promise => new Promise(resolve => { - chrome.storage.local.clear(() => resolve()); - }); - - const storage = await getStorage(); - await clearStorage(); - - for (const key in storage) { - if (!storage.hasOwnProperty(key)) { - continue; - } - await this.storageService.save(key, storage[key]); - } - } - - private async buildContextMenu() { - if (!chrome.contextMenus || this.buildingContextMenu) { - return; - } - - this.buildingContextMenu = true; - await this.contextMenusRemoveAll(); - - await this.contextMenusCreate({ - type: 'normal', - id: 'root', - contexts: ['all'], - title: 'Bitwarden', - }); - - await this.contextMenusCreate({ - type: 'normal', - id: 'autofill', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('autoFill'), - }); - - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-username', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('copyUsername'), - }); - - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-password', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('copyPassword'), - }); - - if (await this.userService.canAccessPremium()) { - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-totp', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('copyVerificationCode'), - }); - } - - await this.contextMenusCreate({ - type: 'separator', - parentId: 'root', - }); - - await this.contextMenusCreate({ - type: 'normal', - id: 'generate-password', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('generatePasswordCopied'), - }); - - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-identifier', - parentId: 'root', - contexts: ['all'], - title: this.i18nService.t('copyElementIdentifier'), - }); - - this.buildingContextMenu = false; - } - - private async contextMenuReady(tab: any, contextMenuEnabled: boolean) { - await this.loadMenuAndUpdateBadge(tab.url, tab.id, contextMenuEnabled); - this.onUpdatedRan = this.onReplacedRan = false; - } - - private async loadMenuAndUpdateBadge(url: string, tabId: number, contextMenuEnabled: boolean) { - if (!url || (!chrome.browserAction && !this.sidebarAction)) { - return; - } - - this.actionSetBadgeBackgroundColor(chrome.browserAction); - this.actionSetBadgeBackgroundColor(this.sidebarAction); - - this.menuOptionsLoaded = []; - const locked = await this.vaultTimeoutService.isLocked(); - if (!locked) { - try { - const ciphers = await this.cipherService.getAllDecryptedForUrl(url); - ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); - - if (contextMenuEnabled) { - ciphers.forEach(cipher => { - this.loadLoginContextMenuOptions(cipher); - }); - } - - const disableBadgeCounter = await this.storageService.get(ConstantsService.disableBadgeCounterKey); - let theText = ''; - - if (!disableBadgeCounter) { - if (ciphers.length > 0 && ciphers.length <= 9) { - theText = ciphers.length.toString(); - } else if (ciphers.length > 0) { - theText = '9+'; - } - } - - if (contextMenuEnabled && ciphers.length === 0) { - await this.loadNoLoginsContextMenuOptions(this.i18nService.t('noMatchingLogins')); - } - - this.sidebarActionSetBadgeText(theText, tabId); - this.browserActionSetBadgeText(theText, tabId); - - return; - } catch (e) { - this.logService.error(e); - } - } - - await this.loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled); - } - - private async loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled: boolean) { - if (contextMenuEnabled) { - const authed = await this.userService.isAuthenticated(); - await this.loadNoLoginsContextMenuOptions(this.i18nService.t(authed ? 'vaultLocked' : 'vaultLoggedOut')); - } - - const tabs = await BrowserApi.getActiveTabs(); - if (tabs != null) { - tabs.forEach(tab => { - if (tab.id != null) { - this.browserActionSetBadgeText('', tab.id); - this.sidebarActionSetBadgeText('', tab.id); - } - }); - } - } - - private async loadLoginContextMenuOptions(cipher: any) { - if (cipher == null || cipher.type !== CipherType.Login || cipher.reprompt !== CipherRepromptType.None) { - return; - } - - let title = cipher.name; - if (cipher.login.username && cipher.login.username !== '') { - title += (' (' + cipher.login.username + ')'); - } - await this.loadContextMenuOptions(title, cipher.id, cipher); - } - - private async loadNoLoginsContextMenuOptions(noLoginsMessage: string) { - await this.loadContextMenuOptions(noLoginsMessage, 'noop', null); - } - - private async loadContextMenuOptions(title: string, idSuffix: string, cipher: any) { - if (!chrome.contextMenus || this.menuOptionsLoaded.indexOf(idSuffix) > -1 || - (cipher != null && cipher.type !== CipherType.Login)) { - return; - } - - this.menuOptionsLoaded.push(idSuffix); - - if (cipher == null || (cipher.login.password && cipher.login.password !== '')) { - await this.contextMenusCreate({ - type: 'normal', - id: 'autofill_' + idSuffix, - parentId: 'autofill', - contexts: ['all'], - title: this.sanitizeContextMenuTitle(title), - }); - } - - if (cipher == null || (cipher.login.username && cipher.login.username !== '')) { - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-username_' + idSuffix, - parentId: 'copy-username', - contexts: ['all'], - title: this.sanitizeContextMenuTitle(title), - }); - } - - if (cipher == null || (cipher.login.password && cipher.login.password !== '' && cipher.viewPassword)) { - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-password_' + idSuffix, - parentId: 'copy-password', - contexts: ['all'], - title: this.sanitizeContextMenuTitle(title), - }); - } - - const canAccessPremium = await this.userService.canAccessPremium(); - if (canAccessPremium && (cipher == null || (cipher.login.totp && cipher.login.totp !== ''))) { - await this.contextMenusCreate({ - type: 'normal', - id: 'copy-totp_' + idSuffix, - parentId: 'copy-totp', - contexts: ['all'], - title: this.sanitizeContextMenuTitle(title), - }); - } - } - - private sanitizeContextMenuTitle(title: string): string { - return title.replace(/&/g, '&&'); - } - - private async fullSync(override: boolean = false) { - const syncInternal = 6 * 60 * 60 * 1000; // 6 hours - const lastSync = await this.syncService.getLastSync(); - - let lastSyncAgo = syncInternal + 1; - if (lastSync != null) { - lastSyncAgo = new Date().getTime() - lastSync.getTime(); - } - - if (override || lastSyncAgo >= syncInternal) { - await this.syncService.fullSync(override); - this.scheduleNextSync(); - } else { - this.scheduleNextSync(); - } - } - - private scheduleNextSync() { - if (this.syncTimeout) { - clearTimeout(this.syncTimeout); - } - - this.syncTimeout = setTimeout(async () => await this.fullSync(), 5 * 60 * 1000); // check every 5 minutes - } - - // Browser API Helpers - - private contextMenusRemoveAll() { - return new Promise(resolve => { - chrome.contextMenus.removeAll(() => { - resolve(); - if (chrome.runtime.lastError) { - return; - } - }); - }); - } - - private contextMenusCreate(options: any) { - return new Promise(resolve => { - chrome.contextMenus.create(options, () => { - resolve(); - if (chrome.runtime.lastError) { - return; - } - }); - }); - } - - private async actionSetIcon(theAction: any, suffix: string): Promise { - if (!theAction || !theAction.setIcon) { - return; - } - - const options = { - path: { - 19: 'images/icon19' + suffix + '.png', - 38: 'images/icon38' + suffix + '.png', - }, + const that = this; + this.authService = new AuthService( + this.cryptoService, + 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. + send = (subscriber: string, arg: any = {}) => { + const message = Object.assign({}, { command: subscriber }, arg); + that.runtimeBackground.processMessage(message, that, null); }; + })(), + this.vaultTimeoutService, + this.logService, + this.cryptoFunctionService, + this.environmentService, + this.keyConnectorService + ); + } - if (this.platformUtilsService.isFirefox()) { - await theAction.setIcon(options); - } else if (this.platformUtilsService.isSafari()) { - // Workaround since Safari 14.0.3 returns a pending promise - // which doesn't resolve within a reasonable time. - theAction.setIcon(options); - } else { - return new Promise(resolve => { - theAction.setIcon(options, () => resolve()); - }); - } + async bootstrap() { + this.containerService.attachToWindow(window); + + (this.authService as AuthService).init(); + await (this.vaultTimeoutService as VaultTimeoutService).init(true); + await (this.i18nService as I18nService).init(); + await (this.eventService as EventService).init(true); + await this.runtimeBackground.init(); + await this.notificationBackground.init(); + await this.commandsBackground.init(); + + await this.tabsBackground.init(); + await this.contextMenusBackground.init(); + await this.idleBackground.init(); + await this.webRequestBackground.init(); + await this.windowsBackground.init(); + + return new Promise((resolve) => { + setTimeout(async () => { + await this.environmentService.setUrlsFromStorage(); + await this.setIcon(); + this.fullSync(true); + setTimeout(() => this.notificationsService.init(), 2500); + resolve(); + }, 500); + }); + } + + async setIcon() { + if (!chrome.browserAction && !this.sidebarAction) { + return; } - private actionSetBadgeBackgroundColor(action: any) { - if (action && action.setBadgeBackgroundColor) { - action.setBadgeBackgroundColor({ color: '#294e5f' }); - } + const isAuthenticated = await this.userService.isAuthenticated(); + const locked = await this.vaultTimeoutService.isLocked(); + + let suffix = ""; + if (!isAuthenticated) { + suffix = "_gray"; + } else if (locked) { + suffix = "_locked"; } - private browserActionSetBadgeText(text: string, tabId: number) { - if (chrome.browserAction && chrome.browserAction.setBadgeText) { - chrome.browserAction.setBadgeText({ - text: text, - tabId: tabId, - }); - } + await this.actionSetIcon(chrome.browserAction, suffix); + await this.actionSetIcon(this.sidebarAction, suffix); + } + + async refreshBadgeAndMenu(forLocked: boolean = false) { + if (!chrome.windows || !chrome.contextMenus) { + return; } - private sidebarActionSetBadgeText(text: string, tabId: number) { - if (!this.sidebarAction) { - return; - } - - if (this.sidebarAction.setBadgeText) { - this.sidebarAction.setBadgeText({ - text: text, - tabId: tabId, - }); - } else if (this.sidebarAction.setTitle) { - let title = 'Bitwarden'; - if (text && text !== '') { - title += (' [' + text + ']'); - } - - this.sidebarAction.setTitle({ - title: title, - tabId: tabId, - }); - } + const menuDisabled = await this.storageService.get( + ConstantsService.disableContextMenuItemKey + ); + if (!menuDisabled) { + await this.buildContextMenu(); + } else { + await this.contextMenusRemoveAll(); } + + if (forLocked) { + await this.loadMenuAndUpdateBadgeForNoAccessState(!menuDisabled); + this.onUpdatedRan = this.onReplacedRan = false; + return; + } + + const tab = await BrowserApi.getTabFromCurrentWindow(); + if (tab) { + await this.contextMenuReady(tab, !menuDisabled); + } + } + + async logout(expired: boolean) { + await this.eventService.uploadEvents(); + const userId = await this.userService.getUserId(); + + await Promise.all([ + this.eventService.clearEvents(), + this.syncService.setLastSync(new Date(0)), + this.tokenService.clearToken(), + this.cryptoService.clearKeys(), + this.userService.clear(), + this.settingsService.clear(userId), + this.cipherService.clear(userId), + this.folderService.clear(userId), + this.collectionService.clear(userId), + this.policyService.clear(userId), + this.passwordGenerationService.clear(), + this.vaultTimeoutService.clear(), + this.keyConnectorService.clear(), + ]); + + this.searchService.clearIndex(); + this.messagingService.send("doneLoggingOut", { expired: expired }); + + await this.setIcon(); + await this.refreshBadgeAndMenu(); + await this.reseedStorage(); + this.notificationsService.updateConnection(false); + this.systemService.startProcessReload(); + await this.systemService.clearPendingClipboard(); + } + + async collectPageDetailsForContentScript(tab: any, sender: string, frameId: number = null) { + if (tab == null || !tab.id) { + return; + } + + const options: any = {}; + if (frameId != null) { + options.frameId = frameId; + } + + BrowserApi.tabSendMessage( + tab, + { + command: "collectPageDetails", + tab: tab, + sender: sender, + }, + options + ); + } + + async openPopup() { + // Chrome APIs cannot open popup + + // TODO: Do we need to open this popup? + if (!this.isSafari) { + return; + } + await SafariApp.sendMessageToApp("showPopover", null, true); + } + + async reseedStorage() { + if ( + !this.platformUtilsService.isChrome() && + !this.platformUtilsService.isVivaldi() && + !this.platformUtilsService.isOpera() + ) { + return; + } + + const currentVaultTimeout = await this.storageService.get( + ConstantsService.vaultTimeoutKey + ); + if (currentVaultTimeout == null) { + return; + } + + const getStorage = (): Promise => + new Promise((resolve) => { + chrome.storage.local.get(null, (o: any) => resolve(o)); + }); + + const clearStorage = (): Promise => + new Promise((resolve) => { + chrome.storage.local.clear(() => resolve()); + }); + + const storage = await getStorage(); + await clearStorage(); + + for (const key in storage) { + if (!storage.hasOwnProperty(key)) { + continue; + } + await this.storageService.save(key, storage[key]); + } + } + + private async buildContextMenu() { + if (!chrome.contextMenus || this.buildingContextMenu) { + return; + } + + this.buildingContextMenu = true; + await this.contextMenusRemoveAll(); + + await this.contextMenusCreate({ + type: "normal", + id: "root", + contexts: ["all"], + title: "Bitwarden", + }); + + await this.contextMenusCreate({ + type: "normal", + id: "autofill", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("autoFill"), + }); + + await this.contextMenusCreate({ + type: "normal", + id: "copy-username", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("copyUsername"), + }); + + await this.contextMenusCreate({ + type: "normal", + id: "copy-password", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("copyPassword"), + }); + + if (await this.userService.canAccessPremium()) { + await this.contextMenusCreate({ + type: "normal", + id: "copy-totp", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("copyVerificationCode"), + }); + } + + await this.contextMenusCreate({ + type: "separator", + parentId: "root", + }); + + await this.contextMenusCreate({ + type: "normal", + id: "generate-password", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("generatePasswordCopied"), + }); + + await this.contextMenusCreate({ + type: "normal", + id: "copy-identifier", + parentId: "root", + contexts: ["all"], + title: this.i18nService.t("copyElementIdentifier"), + }); + + this.buildingContextMenu = false; + } + + private async contextMenuReady(tab: any, contextMenuEnabled: boolean) { + await this.loadMenuAndUpdateBadge(tab.url, tab.id, contextMenuEnabled); + this.onUpdatedRan = this.onReplacedRan = false; + } + + private async loadMenuAndUpdateBadge(url: string, tabId: number, contextMenuEnabled: boolean) { + if (!url || (!chrome.browserAction && !this.sidebarAction)) { + return; + } + + this.actionSetBadgeBackgroundColor(chrome.browserAction); + this.actionSetBadgeBackgroundColor(this.sidebarAction); + + this.menuOptionsLoaded = []; + const locked = await this.vaultTimeoutService.isLocked(); + if (!locked) { + try { + const ciphers = await this.cipherService.getAllDecryptedForUrl(url); + ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + + if (contextMenuEnabled) { + ciphers.forEach((cipher) => { + this.loadLoginContextMenuOptions(cipher); + }); + } + + const disableBadgeCounter = await this.storageService.get( + ConstantsService.disableBadgeCounterKey + ); + let theText = ""; + + if (!disableBadgeCounter) { + if (ciphers.length > 0 && ciphers.length <= 9) { + theText = ciphers.length.toString(); + } else if (ciphers.length > 0) { + theText = "9+"; + } + } + + if (contextMenuEnabled && ciphers.length === 0) { + await this.loadNoLoginsContextMenuOptions(this.i18nService.t("noMatchingLogins")); + } + + this.sidebarActionSetBadgeText(theText, tabId); + this.browserActionSetBadgeText(theText, tabId); + + return; + } catch (e) { + this.logService.error(e); + } + } + + await this.loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled); + } + + private async loadMenuAndUpdateBadgeForNoAccessState(contextMenuEnabled: boolean) { + if (contextMenuEnabled) { + const authed = await this.userService.isAuthenticated(); + await this.loadNoLoginsContextMenuOptions( + this.i18nService.t(authed ? "vaultLocked" : "vaultLoggedOut") + ); + } + + const tabs = await BrowserApi.getActiveTabs(); + if (tabs != null) { + tabs.forEach((tab) => { + if (tab.id != null) { + this.browserActionSetBadgeText("", tab.id); + this.sidebarActionSetBadgeText("", tab.id); + } + }); + } + } + + private async loadLoginContextMenuOptions(cipher: any) { + if ( + cipher == null || + cipher.type !== CipherType.Login || + cipher.reprompt !== CipherRepromptType.None + ) { + return; + } + + let title = cipher.name; + if (cipher.login.username && cipher.login.username !== "") { + title += " (" + cipher.login.username + ")"; + } + await this.loadContextMenuOptions(title, cipher.id, cipher); + } + + private async loadNoLoginsContextMenuOptions(noLoginsMessage: string) { + await this.loadContextMenuOptions(noLoginsMessage, "noop", null); + } + + private async loadContextMenuOptions(title: string, idSuffix: string, cipher: any) { + if ( + !chrome.contextMenus || + this.menuOptionsLoaded.indexOf(idSuffix) > -1 || + (cipher != null && cipher.type !== CipherType.Login) + ) { + return; + } + + this.menuOptionsLoaded.push(idSuffix); + + if (cipher == null || (cipher.login.password && cipher.login.password !== "")) { + await this.contextMenusCreate({ + type: "normal", + id: "autofill_" + idSuffix, + parentId: "autofill", + contexts: ["all"], + title: this.sanitizeContextMenuTitle(title), + }); + } + + if (cipher == null || (cipher.login.username && cipher.login.username !== "")) { + await this.contextMenusCreate({ + type: "normal", + id: "copy-username_" + idSuffix, + parentId: "copy-username", + contexts: ["all"], + title: this.sanitizeContextMenuTitle(title), + }); + } + + if ( + cipher == null || + (cipher.login.password && cipher.login.password !== "" && cipher.viewPassword) + ) { + await this.contextMenusCreate({ + type: "normal", + id: "copy-password_" + idSuffix, + parentId: "copy-password", + contexts: ["all"], + title: this.sanitizeContextMenuTitle(title), + }); + } + + const canAccessPremium = await this.userService.canAccessPremium(); + if (canAccessPremium && (cipher == null || (cipher.login.totp && cipher.login.totp !== ""))) { + await this.contextMenusCreate({ + type: "normal", + id: "copy-totp_" + idSuffix, + parentId: "copy-totp", + contexts: ["all"], + title: this.sanitizeContextMenuTitle(title), + }); + } + } + + private sanitizeContextMenuTitle(title: string): string { + return title.replace(/&/g, "&&"); + } + + private async fullSync(override: boolean = false) { + const syncInternal = 6 * 60 * 60 * 1000; // 6 hours + const lastSync = await this.syncService.getLastSync(); + + let lastSyncAgo = syncInternal + 1; + if (lastSync != null) { + lastSyncAgo = new Date().getTime() - lastSync.getTime(); + } + + if (override || lastSyncAgo >= syncInternal) { + await this.syncService.fullSync(override); + this.scheduleNextSync(); + } else { + this.scheduleNextSync(); + } + } + + private scheduleNextSync() { + if (this.syncTimeout) { + clearTimeout(this.syncTimeout); + } + + this.syncTimeout = setTimeout(async () => await this.fullSync(), 5 * 60 * 1000); // check every 5 minutes + } + + // Browser API Helpers + + private contextMenusRemoveAll() { + return new Promise((resolve) => { + chrome.contextMenus.removeAll(() => { + resolve(); + if (chrome.runtime.lastError) { + return; + } + }); + }); + } + + private contextMenusCreate(options: any) { + return new Promise((resolve) => { + chrome.contextMenus.create(options, () => { + resolve(); + if (chrome.runtime.lastError) { + return; + } + }); + }); + } + + private async actionSetIcon(theAction: any, suffix: string): Promise { + if (!theAction || !theAction.setIcon) { + return; + } + + const options = { + path: { + 19: "images/icon19" + suffix + ".png", + 38: "images/icon38" + suffix + ".png", + }, + }; + + if (this.platformUtilsService.isFirefox()) { + await theAction.setIcon(options); + } else if (this.platformUtilsService.isSafari()) { + // Workaround since Safari 14.0.3 returns a pending promise + // which doesn't resolve within a reasonable time. + theAction.setIcon(options); + } else { + return new Promise((resolve) => { + theAction.setIcon(options, () => resolve()); + }); + } + } + + private actionSetBadgeBackgroundColor(action: any) { + if (action && action.setBadgeBackgroundColor) { + action.setBadgeBackgroundColor({ color: "#294e5f" }); + } + } + + private browserActionSetBadgeText(text: string, tabId: number) { + if (chrome.browserAction && chrome.browserAction.setBadgeText) { + chrome.browserAction.setBadgeText({ + text: text, + tabId: tabId, + }); + } + } + + private sidebarActionSetBadgeText(text: string, tabId: number) { + if (!this.sidebarAction) { + return; + } + + if (this.sidebarAction.setBadgeText) { + this.sidebarAction.setBadgeText({ + text: text, + tabId: tabId, + }); + } else if (this.sidebarAction.setTitle) { + let title = "Bitwarden"; + if (text && text !== "") { + title += " [" + text + "]"; + } + + this.sidebarAction.setTitle({ + title: title, + tabId: tabId, + }); + } + } } diff --git a/src/background/models/addChangePasswordQueueMessage.ts b/src/background/models/addChangePasswordQueueMessage.ts index 65309ce7cd..51a745643d 100644 --- a/src/background/models/addChangePasswordQueueMessage.ts +++ b/src/background/models/addChangePasswordQueueMessage.ts @@ -1,6 +1,6 @@ -import NotificationQueueMessage from './notificationQueueMessage'; +import NotificationQueueMessage from "./notificationQueueMessage"; export default class AddChangePasswordQueueMessage extends NotificationQueueMessage { - cipherId: string; - newPassword: string; + cipherId: string; + newPassword: string; } diff --git a/src/background/models/addLoginQueueMessage.ts b/src/background/models/addLoginQueueMessage.ts index 1de8b721d2..ba13071262 100644 --- a/src/background/models/addLoginQueueMessage.ts +++ b/src/background/models/addLoginQueueMessage.ts @@ -1,7 +1,7 @@ -import NotificationQueueMessage from './notificationQueueMessage'; +import NotificationQueueMessage from "./notificationQueueMessage"; export default class AddLoginQueueMessage extends NotificationQueueMessage { - username: string; - password: string; - uri: string; + username: string; + password: string; + uri: string; } diff --git a/src/background/models/addLoginRuntimeMessage.ts b/src/background/models/addLoginRuntimeMessage.ts index 3426bc595d..e234edeb67 100644 --- a/src/background/models/addLoginRuntimeMessage.ts +++ b/src/background/models/addLoginRuntimeMessage.ts @@ -1,5 +1,5 @@ export default class AddLoginRuntimeMessage { - username: string; - password: string; - url: string; + username: string; + password: string; + url: string; } diff --git a/src/background/models/changePasswordRuntimeMessage.ts b/src/background/models/changePasswordRuntimeMessage.ts index 8f3f6aa577..072848226e 100644 --- a/src/background/models/changePasswordRuntimeMessage.ts +++ b/src/background/models/changePasswordRuntimeMessage.ts @@ -1,5 +1,5 @@ export default class ChangePasswordRuntimeMessage { - currentPassword: string; - newPassword: string; - url: string; + currentPassword: string; + newPassword: string; + url: string; } diff --git a/src/background/models/lockedVaultPendingNotificationsItem.ts b/src/background/models/lockedVaultPendingNotificationsItem.ts index 248868e6ac..ec697b1699 100644 --- a/src/background/models/lockedVaultPendingNotificationsItem.ts +++ b/src/background/models/lockedVaultPendingNotificationsItem.ts @@ -1,7 +1,7 @@ export default class LockedVaultPendingNotificationsItem { - commandToRetry: { - msg: any; - sender: chrome.runtime.MessageSender; - }; - target: string; + commandToRetry: { + msg: any; + sender: chrome.runtime.MessageSender; + }; + target: string; } diff --git a/src/background/models/notificationQueueMessage.ts b/src/background/models/notificationQueueMessage.ts index 00a05d15a4..f0984133ae 100644 --- a/src/background/models/notificationQueueMessage.ts +++ b/src/background/models/notificationQueueMessage.ts @@ -1,9 +1,9 @@ -import { NotificationQueueMessageType } from './notificationQueueMessageType'; +import { NotificationQueueMessageType } from "./notificationQueueMessageType"; export default class NotificationQueueMessage { - type: NotificationQueueMessageType; - domain: string; - tabId: number; - expires: Date; - wasVaultLocked: boolean; + type: NotificationQueueMessageType; + domain: string; + tabId: number; + expires: Date; + wasVaultLocked: boolean; } diff --git a/src/background/models/notificationQueueMessageType.ts b/src/background/models/notificationQueueMessageType.ts index 5c27572dac..70cb9922ba 100644 --- a/src/background/models/notificationQueueMessageType.ts +++ b/src/background/models/notificationQueueMessageType.ts @@ -1,4 +1,4 @@ export enum NotificationQueueMessageType { - addLogin = 'addLogin', - changePassword = 'changePassword', + addLogin = "addLogin", + changePassword = "changePassword", } diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 4f11bc3302..0b29f1b960 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -1,333 +1,350 @@ -import { AppIdService } from 'jslib-common/abstractions/appId.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { AppIdService } from "jslib-common/abstractions/appId.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { UserService } from "jslib-common/abstractions/user.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; +import { ConstantsService } from "jslib-common/services/constants.service"; -import { Utils } from 'jslib-common/misc/utils'; -import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; +import { Utils } from "jslib-common/misc/utils"; +import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; -import { BrowserApi } from '../browser/browserApi'; -import RuntimeBackground from './runtime.background'; +import { BrowserApi } from "../browser/browserApi"; +import RuntimeBackground from "./runtime.background"; const MessageValidTimeout = 10 * 1000; -const EncryptionAlgorithm = 'sha1'; +const EncryptionAlgorithm = "sha1"; export class NativeMessagingBackground { - private connected = false; - private connecting: boolean; - private port: browser.runtime.Port | chrome.runtime.Port; + private connected = false; + private connecting: boolean; + private port: browser.runtime.Port | chrome.runtime.Port; - private resolver: any = null; - private privateKey: ArrayBuffer = null; - private publicKey: ArrayBuffer = null; - private secureSetupResolve: any = null; - private sharedSecret: SymmetricCryptoKey; - private appId: string; - private validatingFingerprint: boolean; + private resolver: any = null; + private privateKey: ArrayBuffer = null; + private publicKey: ArrayBuffer = null; + private secureSetupResolve: any = null; + private sharedSecret: SymmetricCryptoKey; + private appId: string; + private validatingFingerprint: boolean; - constructor(private storageService: StorageService, private cryptoService: CryptoService, - private cryptoFunctionService: CryptoFunctionService, 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); + constructor( + private storageService: StorageService, + private cryptoService: CryptoService, + private cryptoFunctionService: CryptoFunctionService, + 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); - if (chrome?.permissions?.onAdded) { - // Reload extension to activate nativeMessaging - chrome.permissions.onAdded.addListener(permissions => { - BrowserApi.reloadExtension(null); - }); + if (chrome?.permissions?.onAdded) { + // Reload extension to activate nativeMessaging + chrome.permissions.onAdded.addListener((permissions) => { + BrowserApi.reloadExtension(null); + }); + } + } + + async connect() { + this.appId = await this.appIdService.getAppId(); + this.storageService.save(ConstantsService.biometricFingerprintValidated, false); + + return new Promise((resolve, reject) => { + this.port = BrowserApi.connectNative("com.8bit.bitwarden"); + + this.connecting = true; + + const connectedCallback = () => { + this.connected = true; + this.connecting = false; + resolve(); + }; + + // Safari has a bundled native component which is always available, no need to + // check if the desktop app is running. + if (this.platformUtilsService.isSafari()) { + connectedCallback(); + } + + this.port.onMessage.addListener(async (message: any) => { + switch (message.command) { + case "connected": + connectedCallback(); + break; + case "disconnected": + if (this.connecting) { + this.messagingService.send("showDialog", { + text: this.i18nService.t("startDesktopDesc"), + title: this.i18nService.t("startDesktopTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + reject(); } - } - - async connect() { - this.appId = await this.appIdService.getAppId(); - this.storageService.save(ConstantsService.biometricFingerprintValidated, false); - - return new Promise((resolve, reject) => { - this.port = BrowserApi.connectNative('com.8bit.bitwarden'); - - this.connecting = true; - - const connectedCallback = () => { - this.connected = true; - this.connecting = false; - resolve(); - }; - - // Safari has a bundled native component which is always available, no need to - // check if the desktop app is running. - if (this.platformUtilsService.isSafari()) { - connectedCallback(); + this.connected = false; + this.port.disconnect(); + break; + case "setupEncryption": + // Ignore since it belongs to another device + if (message.appId !== this.appId) { + return; } - this.port.onMessage.addListener(async (message: any) => { - switch (message.command) { - case 'connected': - connectedCallback(); - break; - case 'disconnected': - if (this.connecting) { - this.messagingService.send('showDialog', { - text: this.i18nService.t('startDesktopDesc'), - title: this.i18nService.t('startDesktopTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - reject(); - } - this.connected = false; - this.port.disconnect(); - break; - case 'setupEncryption': - // Ignore since it belongs to another device - if (message.appId !== this.appId) { - return; - } + const encrypted = Utils.fromB64ToArray(message.sharedSecret); + const decrypted = await this.cryptoFunctionService.rsaDecrypt( + encrypted.buffer, + this.privateKey, + EncryptionAlgorithm + ); - const encrypted = Utils.fromB64ToArray(message.sharedSecret); - const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm); - - if (this.validatingFingerprint) { - this.validatingFingerprint = false; - this.storageService.save(ConstantsService.biometricFingerprintValidated, true); - } - this.sharedSecret = new SymmetricCryptoKey(decrypted); - this.secureSetupResolve(); - break; - case 'invalidateEncryption': - // Ignore since it belongs to another device - if (message.appId !== this.appId) { - return; - } - - this.sharedSecret = null; - this.privateKey = null; - this.connected = false; - - this.messagingService.send('showDialog', { - text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'), - title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - break; - case 'verifyFingerprint': { - if (this.sharedSecret == null) { - this.validatingFingerprint = true; - this.showFingerprintDialog(); - } - break; - } - case 'wrongUserId': - this.showWrongUserDialog(); - default: - // Ignore since it belongs to another device - if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) { - return; - } - - this.onMessage(message.message); - } - }); - - this.port.onDisconnect.addListener((p: any) => { - let error; - if (BrowserApi.isWebExtensionsApi) { - error = p.error.message; - } else { - error = chrome.runtime.lastError.message; - } - - if (error != null) { - this.messagingService.send('showDialog', { - text: this.i18nService.t('desktopIntegrationDisabledDesc'), - title: this.i18nService.t('desktopIntegrationDisabledTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - } - this.sharedSecret = null; - this.privateKey = null; - this.connected = false; - reject(); - }); - }); - } - - showWrongUserDialog() { - this.messagingService.send('showDialog', { - text: this.i18nService.t('nativeMessagingWrongUserDesc'), - title: this.i18nService.t('nativeMessagingWrongUserTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - } - - async send(message: any) { - if (!this.connected) { - await this.connect(); - } - - if (this.platformUtilsService.isSafari()) { - this.postMessage(message); - } else { - this.postMessage({appId: this.appId, message: await this.encryptMessage(message)}); - } - } - - async encryptMessage(message: any) { - if (this.sharedSecret == null) { - await this.secureCommunication(); - } - - message.timestamp = Date.now(); - - return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); - } - - getResponse(): Promise { - return new Promise((resolve, reject) => { - this.resolver = resolve; - }); - } - - private postMessage(message: any) { - // Wrap in try-catch to when the port disconnected without triggering `onDisconnect`. - try { - this.port.postMessage(message); - } catch (e) { - // tslint:disable-next-line - console.error("NativeMessaging port disconnected, disconnecting."); + if (this.validatingFingerprint) { + this.validatingFingerprint = false; + this.storageService.save(ConstantsService.biometricFingerprintValidated, true); + } + this.sharedSecret = new SymmetricCryptoKey(decrypted); + this.secureSetupResolve(); + break; + case "invalidateEncryption": + // Ignore since it belongs to another device + if (message.appId !== this.appId) { + return; + } this.sharedSecret = null; this.privateKey = null; this.connected = false; - this.messagingService.send('showDialog', { - text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'), - title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', + this.messagingService.send("showDialog", { + text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"), + title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", }); + break; + case "verifyFingerprint": { + if (this.sharedSecret == null) { + this.validatingFingerprint = true; + this.showFingerprintDialog(); + } + break; + } + case "wrongUserId": + this.showWrongUserDialog(); + default: + // Ignore since it belongs to another device + if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) { + return; + } + + this.onMessage(message.message); } + }); + + this.port.onDisconnect.addListener((p: any) => { + let error; + if (BrowserApi.isWebExtensionsApi) { + error = p.error.message; + } else { + error = chrome.runtime.lastError.message; + } + + if (error != null) { + this.messagingService.send("showDialog", { + text: this.i18nService.t("desktopIntegrationDisabledDesc"), + title: this.i18nService.t("desktopIntegrationDisabledTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + } + this.sharedSecret = null; + this.privateKey = null; + this.connected = false; + reject(); + }); + }); + } + + showWrongUserDialog() { + this.messagingService.send("showDialog", { + text: this.i18nService.t("nativeMessagingWrongUserDesc"), + title: this.i18nService.t("nativeMessagingWrongUserTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + } + + async send(message: any) { + if (!this.connected) { + await this.connect(); } - private async onMessage(rawMessage: any) { - let message = rawMessage; - if (!this.platformUtilsService.isSafari()) { - message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); + if (this.platformUtilsService.isSafari()) { + this.postMessage(message); + } else { + this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) }); + } + } + + async encryptMessage(message: any) { + if (this.sharedSecret == null) { + await this.secureCommunication(); + } + + message.timestamp = Date.now(); + + return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); + } + + getResponse(): Promise { + return new Promise((resolve, reject) => { + this.resolver = resolve; + }); + } + + private postMessage(message: any) { + // Wrap in try-catch to when the port disconnected without triggering `onDisconnect`. + try { + this.port.postMessage(message); + } catch (e) { + // tslint:disable-next-line + console.error("NativeMessaging port disconnected, disconnecting."); + + this.sharedSecret = null; + this.privateKey = null; + this.connected = false; + + this.messagingService.send("showDialog", { + text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"), + title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + } + } + + private async onMessage(rawMessage: any) { + let message = rawMessage; + if (!this.platformUtilsService.isSafari()) { + message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); + } + + if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { + // tslint:disable-next-line + console.error("NativeMessage is to old, ignoring."); + return; + } + + switch (message.command) { + case "biometricUnlock": + await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); + + if (message.response === "not enabled") { + this.messagingService.send("showDialog", { + text: this.i18nService.t("biometricsNotEnabledDesc"), + title: this.i18nService.t("biometricsNotEnabledTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + break; + } else if (message.response === "not supported") { + this.messagingService.send("showDialog", { + text: this.i18nService.t("biometricsNotSupportedDesc"), + title: this.i18nService.t("biometricsNotSupportedTitle"), + confirmText: this.i18nService.t("ok"), + type: "error", + }); + break; } - if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { + const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); + if (enabled === null || enabled === false) { + if (message.response === "unlocked") { + await this.storageService.save(ConstantsService.biometricUnlockKey, true); + } + break; + } + + // Ignore unlock if already unlockeded + if (!this.vaultTimeoutService.biometricLocked) { + break; + } + + if (message.response === "unlocked") { + await this.cryptoService.setKey( + new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer) + ); + + // Verify key is correct by attempting to decrypt a secret + try { + await this.cryptoService.getFingerprint(await this.userService.getUserId()); + } catch (e) { // tslint:disable-next-line - console.error('NativeMessage is to old, ignoring.'); - return; - } - - switch (message.command) { - case 'biometricUnlock': - await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); - - if (message.response === 'not enabled') { - this.messagingService.send('showDialog', { - text: this.i18nService.t('biometricsNotEnabledDesc'), - title: this.i18nService.t('biometricsNotEnabledTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - break; - } else if (message.response === 'not supported') { - this.messagingService.send('showDialog', { - text: this.i18nService.t('biometricsNotSupportedDesc'), - title: this.i18nService.t('biometricsNotSupportedTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); - break; - } - - const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey); - if (enabled === null || enabled === false) { - if (message.response === 'unlocked') { - await this.storageService.save(ConstantsService.biometricUnlockKey, true); - } - break; - } - - // Ignore unlock if already unlockeded - if (!this.vaultTimeoutService.biometricLocked) { - break; - } - - if (message.response === 'unlocked') { - await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); - - // Verify key is correct by attempting to decrypt a secret - try { - await this.cryptoService.getFingerprint(await this.userService.getUserId()); - } catch (e) { - // tslint:disable-next-line - console.error('Unable to verify key:', e); - await this.cryptoService.clearKey(); - this.showWrongUserDialog(); - - message = false; - break; - } - - this.vaultTimeoutService.biometricLocked = false; - this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); - } - break; - default: - // tslint:disable-next-line - console.error('NativeMessage, got unknown command: ', message.command); - } - - if (this.resolver) { - this.resolver(message); + console.error("Unable to verify key:", e); + await this.cryptoService.clearKey(); + this.showWrongUserDialog(); + + message = false; + break; + } + + this.vaultTimeoutService.biometricLocked = false; + this.runtimeBackground.processMessage({ command: "unlocked" }, null, null); } + break; + default: + // tslint:disable-next-line + console.error("NativeMessage, got unknown command: ", message.command); } - private async secureCommunication() { - const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); - this.publicKey = publicKey; - this.privateKey = privateKey; + if (this.resolver) { + this.resolver(message); + } + } - this.sendUnencrypted({ - command: 'setupEncryption', - publicKey: Utils.fromBufferToB64(publicKey), - userId: await this.userService.getUserId(), - }); + private async secureCommunication() { + const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); + this.publicKey = publicKey; + this.privateKey = privateKey; - return new Promise((resolve, reject) => this.secureSetupResolve = resolve); + this.sendUnencrypted({ + command: "setupEncryption", + publicKey: Utils.fromBufferToB64(publicKey), + userId: await this.userService.getUserId(), + }); + + return new Promise((resolve, reject) => (this.secureSetupResolve = resolve)); + } + + private async sendUnencrypted(message: any) { + if (!this.connected) { + await this.connect(); } - private async sendUnencrypted(message: any) { - if (!this.connected) { - await this.connect(); - } + 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() { + const fingerprint = ( + await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey) + ).join(" "); - private async showFingerprintDialog() { - const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' '); - - this.messagingService.send('showDialog', { - html: `${this.i18nService.t('desktopIntegrationVerificationText')}

${fingerprint}`, - title: this.i18nService.t('desktopSyncVerificationTitle'), - confirmText: this.i18nService.t('ok'), - type: 'warning', - }); - } + this.messagingService.send("showDialog", { + html: `${this.i18nService.t( + "desktopIntegrationVerificationText" + )}

${fingerprint}`, + title: this.i18nService.t("desktopSyncVerificationTitle"), + confirmText: this.i18nService.t("ok"), + type: "warning", + }); + } } diff --git a/src/background/notification.background.ts b/src/background/notification.background.ts index d7f319fb57..dc6e03e7ed 100644 --- a/src/background/notification.background.ts +++ b/src/background/notification.background.ts @@ -1,417 +1,460 @@ -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherType } from "jslib-common/enums/cipherType"; -import { CipherView } from 'jslib-common/models/view/cipherView'; -import { LoginUriView } from 'jslib-common/models/view/loginUriView'; -import { LoginView } from 'jslib-common/models/view/loginView'; +import { CipherView } from "jslib-common/models/view/cipherView"; +import { LoginUriView } from "jslib-common/models/view/loginUriView"; +import { LoginView } from "jslib-common/models/view/loginView"; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { UserService } from "jslib-common/abstractions/user.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 AddLoginQueueMessage from './models/addLoginQueueMessage'; -import AddLoginRuntimeMessage from './models/addLoginRuntimeMessage'; -import ChangePasswordRuntimeMessage from './models/changePasswordRuntimeMessage'; -import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; -import { NotificationQueueMessageType } from './models/notificationQueueMessageType'; +import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage"; +import AddLoginQueueMessage from "./models/addLoginQueueMessage"; +import AddLoginRuntimeMessage from "./models/addLoginRuntimeMessage"; +import ChangePasswordRuntimeMessage from "./models/changePasswordRuntimeMessage"; +import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem"; +import { NotificationQueueMessageType } from "./models/notificationQueueMessageType"; export default class NotificationBackground { + private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = []; - private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = []; + constructor( + private main: MainBackground, + private autofillService: AutofillService, + private cipherService: CipherService, + private storageService: StorageService, + private vaultTimeoutService: VaultTimeoutService, + private policyService: PolicyService, + private folderService: FolderService, + private userService: UserService + ) {} - constructor(private main: MainBackground, private autofillService: AutofillService, - private cipherService: CipherService, private storageService: StorageService, - private vaultTimeoutService: VaultTimeoutService, private policyService: PolicyService, - private folderService: FolderService, private userService: UserService) { + async init() { + if (chrome.runtime == null) { + return; } - async init() { - if (chrome.runtime == null) { - return; + BrowserApi.messageListener( + "notification.background", + async (msg: any, sender: chrome.runtime.MessageSender) => { + await this.processMessage(msg, sender); + } + ); + + this.cleanupNotificationQueue(); + } + + async processMessage(msg: any, sender: chrome.runtime.MessageSender) { + switch (msg.command) { + case "unlockCompleted": + if (msg.data.target !== "notification.background") { + return; } - - BrowserApi.messageListener('notification.background', async (msg: any, sender: chrome.runtime.MessageSender) => { - await this.processMessage(msg, sender); - }); - - this.cleanupNotificationQueue(); - } - - async processMessage(msg: any, sender: chrome.runtime.MessageSender) { - switch (msg.command) { - case 'unlockCompleted': - if (msg.data.target !== 'notification.background') { - return; - } - await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender); - break; - case 'bgGetDataForTab': - await this.getDataForTab(sender.tab, msg.responseCommand); - break; - case 'bgCloseNotificationBar': - await BrowserApi.tabSendMessageData(sender.tab, 'closeNotificationBar'); - break; - case 'bgAdjustNotificationBar': - await BrowserApi.tabSendMessageData(sender.tab, 'adjustNotificationBar', msg.data); - break; - case 'bgAddLogin': - await this.addLogin(msg.login, sender.tab); - break; - case 'bgChangedPassword': - await this.changedPassword(msg.data, sender.tab); - break; - case 'bgAddClose': - case 'bgChangeClose': - this.removeTabFromNotificationQueue(sender.tab); - break; - case 'bgAddSave': - case 'bgChangeSave': - if (await this.vaultTimeoutService.isLocked()) { - const retryMessage: LockedVaultPendingNotificationsItem = { - commandToRetry: { - msg: msg, - sender: sender, - }, - target: 'notification.background', - }; - await BrowserApi.tabSendMessageData(sender.tab, 'addToLockedVaultPendingNotifications', retryMessage); - await BrowserApi.tabSendMessageData(sender.tab, 'promptForLogin'); - return; - } - await this.saveOrUpdateCredentials(sender.tab, msg.folder); - break; - case 'bgNeverSave': - await this.saveNever(sender.tab); - break; - case 'collectPageDetailsResponse': - switch (msg.sender) { - case 'notificationBar': - const forms = this.autofillService.getFormsWithPasswordFields(msg.details); - await BrowserApi.tabSendMessageData(msg.tab, 'notificationBarPageDetails', { - details: msg.details, - forms: forms, - }); - break; - default: - break; - } - break; - default: - break; + await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender); + break; + case "bgGetDataForTab": + await this.getDataForTab(sender.tab, msg.responseCommand); + break; + case "bgCloseNotificationBar": + await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar"); + break; + case "bgAdjustNotificationBar": + await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data); + break; + case "bgAddLogin": + await this.addLogin(msg.login, sender.tab); + break; + case "bgChangedPassword": + await this.changedPassword(msg.data, sender.tab); + break; + case "bgAddClose": + case "bgChangeClose": + this.removeTabFromNotificationQueue(sender.tab); + break; + case "bgAddSave": + case "bgChangeSave": + if (await this.vaultTimeoutService.isLocked()) { + const retryMessage: LockedVaultPendingNotificationsItem = { + commandToRetry: { + msg: msg, + sender: sender, + }, + target: "notification.background", + }; + await BrowserApi.tabSendMessageData( + sender.tab, + "addToLockedVaultPendingNotifications", + retryMessage + ); + await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin"); + return; } - } - - async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise { - if (this.notificationQueue.length === 0) { - return; - } - - if (tab != null) { - this.doNotificationQueueCheck(tab); - return; - } - - const currentTab = await BrowserApi.getTabFromCurrentWindow(); - if (currentTab != null) { - this.doNotificationQueueCheck(currentTab); - } - } - - private cleanupNotificationQueue() { - for (let i = this.notificationQueue.length - 1; i >= 0; i--) { - if (this.notificationQueue[i].expires < new Date()) { - this.notificationQueue.splice(i, 1); - } - } - setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes - } - - private doNotificationQueueCheck(tab: chrome.tabs.Tab): void { - if (tab == null) { - return; - } - - const tabDomain = Utils.getDomain(tab.url); - if (tabDomain == null) { - return; - } - - for (let i = 0; i < this.notificationQueue.length; i++) { - if (this.notificationQueue[i].tabId !== tab.id || this.notificationQueue[i].domain !== tabDomain) { - continue; - } - - if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) { - BrowserApi.tabSendMessageData(tab, 'openNotificationBar', { - type: 'add', - typeData: { - isVaultLocked: this.notificationQueue[i].wasVaultLocked, - }, - }); - } else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) { - BrowserApi.tabSendMessageData(tab, 'openNotificationBar', { - type: 'change', - typeData: { - isVaultLocked: this.notificationQueue[i].wasVaultLocked, - }, - }); - } + await this.saveOrUpdateCredentials(sender.tab, msg.folder); + break; + case "bgNeverSave": + await this.saveNever(sender.tab); + break; + case "collectPageDetailsResponse": + switch (msg.sender) { + case "notificationBar": + const forms = this.autofillService.getFormsWithPasswordFields(msg.details); + await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", { + details: msg.details, + forms: forms, + }); + break; + default: break; } + break; + default: + break; + } + } + + async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise { + if (this.notificationQueue.length === 0) { + return; } - private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) { - for (let i = this.notificationQueue.length - 1; i >= 0; i--) { - if (this.notificationQueue[i].tabId === tab.id) { - this.notificationQueue.splice(i, 1); - } - } + if (tab != null) { + this.doNotificationQueueCheck(tab); + return; } - private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) { - if (!await this.userService.isAuthenticated()) { - return; - } + const currentTab = await BrowserApi.getTabFromCurrentWindow(); + if (currentTab != null) { + this.doNotificationQueueCheck(currentTab); + } + } - const loginDomain = Utils.getDomain(loginInfo.url); - if (loginDomain == null) { - return; - } + private cleanupNotificationQueue() { + for (let i = this.notificationQueue.length - 1; i >= 0; i--) { + if (this.notificationQueue[i].expires < new Date()) { + this.notificationQueue.splice(i, 1); + } + } + setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes + } - let normalizedUsername = loginInfo.username; - if (normalizedUsername != null) { - normalizedUsername = normalizedUsername.toLowerCase(); - } - - const disabledAddLogin = await this.storageService.get(ConstantsService.disableAddLoginNotificationKey); - if (await this.vaultTimeoutService.isLocked()) { - if (disabledAddLogin) { - return; - } - - if (!await this.allowPersonalOwnership()) { - return; - } - - this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true); - return; - } - - const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url); - const usernameMatches = ciphers.filter(c => - c.login.username != null && c.login.username.toLowerCase() === normalizedUsername); - if (usernameMatches.length === 0) { - if (disabledAddLogin) { - return; - } - - if (!await this.allowPersonalOwnership()) { - return; - } - - this.pushAddLoginToQueue(loginDomain, loginInfo, tab); - - } else if (usernameMatches.length === 1 && usernameMatches[0].login.password !== loginInfo.password) { - const disabledChangePassword = await this.storageService.get( - ConstantsService.disableChangedPasswordNotificationKey); - if (disabledChangePassword) { - return; - } - this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab); - } + private doNotificationQueueCheck(tab: chrome.tabs.Tab): void { + if (tab == null) { + return; } - private async pushAddLoginToQueue(loginDomain: string, loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) { - // remove any old messages for this tab - this.removeTabFromNotificationQueue(tab); - const message: AddLoginQueueMessage = { - type: NotificationQueueMessageType.addLogin, - username: loginInfo.username, - password: loginInfo.password, - domain: loginDomain, - uri: loginInfo.url, - tabId: tab.id, - expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes - wasVaultLocked: isVaultLocked, - }; - this.notificationQueue.push(message); - await this.checkNotificationQueue(tab); + const tabDomain = Utils.getDomain(tab.url); + if (tabDomain == null) { + return; } - private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) { - const loginDomain = Utils.getDomain(changeData.url); - if (loginDomain == null) { - return; - } + for (let i = 0; i < this.notificationQueue.length; i++) { + if ( + this.notificationQueue[i].tabId !== tab.id || + this.notificationQueue[i].domain !== tabDomain + ) { + continue; + } - if (await this.vaultTimeoutService.isLocked()) { - this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true); - return; - } + if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) { + BrowserApi.tabSendMessageData(tab, "openNotificationBar", { + type: "add", + typeData: { + isVaultLocked: this.notificationQueue[i].wasVaultLocked, + }, + }); + } else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) { + BrowserApi.tabSendMessageData(tab, "openNotificationBar", { + type: "change", + typeData: { + isVaultLocked: this.notificationQueue[i].wasVaultLocked, + }, + }); + } + break; + } + } - let id: string = null; - const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url); - if (changeData.currentPassword != null) { - const passwordMatches = ciphers.filter(c => c.login.password === changeData.currentPassword); - if (passwordMatches.length === 1) { - id = passwordMatches[0].id; - } - } else if (ciphers.length === 1) { - id = ciphers[0].id; - } - if (id != null) { - this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab); - } + private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) { + for (let i = this.notificationQueue.length - 1; i >= 0; i--) { + if (this.notificationQueue[i].tabId === tab.id) { + this.notificationQueue.splice(i, 1); + } + } + } + + private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) { + if (!(await this.userService.isAuthenticated())) { + return; } - private async pushChangePasswordToQueue(cipherId: string, loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) { - // remove any old messages for this tab - this.removeTabFromNotificationQueue(tab); - const message: AddChangePasswordQueueMessage = { - type: NotificationQueueMessageType.changePassword, - cipherId: cipherId, - newPassword: newPassword, - domain: loginDomain, - tabId: tab.id, - expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes - wasVaultLocked: isVaultLocked, - }; - this.notificationQueue.push(message); - await this.checkNotificationQueue(tab); + const loginDomain = Utils.getDomain(loginInfo.url); + if (loginDomain == null) { + return; } - private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) { - for (let i = this.notificationQueue.length - 1; i >= 0; i--) { - const queueMessage = this.notificationQueue[i]; - if (queueMessage.tabId !== tab.id || - (queueMessage.type !== NotificationQueueMessageType.addLogin && queueMessage.type !== NotificationQueueMessageType.changePassword)) { - continue; - } + let normalizedUsername = loginInfo.username; + if (normalizedUsername != null) { + normalizedUsername = normalizedUsername.toLowerCase(); + } - const tabDomain = Utils.getDomain(tab.url); - if (tabDomain != null && tabDomain !== queueMessage.domain) { - continue; - } + const disabledAddLogin = await this.storageService.get( + ConstantsService.disableAddLoginNotificationKey + ); + if (await this.vaultTimeoutService.isLocked()) { + if (disabledAddLogin) { + return; + } - this.notificationQueue.splice(i, 1); - BrowserApi.tabSendMessageData(tab, 'closeNotificationBar'); + if (!(await this.allowPersonalOwnership())) { + return; + } - if (queueMessage.type === NotificationQueueMessageType.changePassword) { - const message = (queueMessage as AddChangePasswordQueueMessage); - const cipher = await this.getDecryptedCipherById(message.cipherId); - if (cipher == null) { - return; - } - await this.updateCipher(cipher, message.newPassword); - return; - } + this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true); + return; + } - if (!queueMessage.wasVaultLocked) { - await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId); - } + const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url); + const usernameMatches = ciphers.filter( + (c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername + ); + if (usernameMatches.length === 0) { + if (disabledAddLogin) { + return; + } - // If the vault was locked, check if a cipher needs updating instead of creating a new one - if (queueMessage.type === NotificationQueueMessageType.addLogin && queueMessage.wasVaultLocked === true) { - const message = (queueMessage as AddLoginQueueMessage); - const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri); - const usernameMatches = ciphers.filter(c => c.login.username != null && - c.login.username.toLowerCase() === message.username); + if (!(await this.allowPersonalOwnership())) { + return; + } - if (usernameMatches.length >= 1) { - await this.updateCipher(usernameMatches[0], message.password); - return; - } + this.pushAddLoginToQueue(loginDomain, loginInfo, tab); + } else if ( + usernameMatches.length === 1 && + usernameMatches[0].login.password !== loginInfo.password + ) { + const disabledChangePassword = await this.storageService.get( + ConstantsService.disableChangedPasswordNotificationKey + ); + if (disabledChangePassword) { + return; + } + this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab); + } + } - await this.createNewCipher(message, folderId); - } + private async pushAddLoginToQueue( + loginDomain: string, + loginInfo: AddLoginRuntimeMessage, + tab: chrome.tabs.Tab, + isVaultLocked: boolean = false + ) { + // remove any old messages for this tab + this.removeTabFromNotificationQueue(tab); + const message: AddLoginQueueMessage = { + type: NotificationQueueMessageType.addLogin, + username: loginInfo.username, + password: loginInfo.password, + domain: loginDomain, + uri: loginInfo.url, + tabId: tab.id, + expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes + wasVaultLocked: isVaultLocked, + }; + this.notificationQueue.push(message); + await this.checkNotificationQueue(tab); + } + + private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) { + const loginDomain = Utils.getDomain(changeData.url); + if (loginDomain == null) { + return; + } + + if (await this.vaultTimeoutService.isLocked()) { + this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true); + return; + } + + let id: string = null; + const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url); + if (changeData.currentPassword != null) { + const passwordMatches = ciphers.filter( + (c) => c.login.password === changeData.currentPassword + ); + if (passwordMatches.length === 1) { + id = passwordMatches[0].id; + } + } else if (ciphers.length === 1) { + id = ciphers[0].id; + } + if (id != null) { + this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab); + } + } + + private async pushChangePasswordToQueue( + cipherId: string, + loginDomain: string, + newPassword: string, + tab: chrome.tabs.Tab, + isVaultLocked: boolean = false + ) { + // remove any old messages for this tab + this.removeTabFromNotificationQueue(tab); + const message: AddChangePasswordQueueMessage = { + type: NotificationQueueMessageType.changePassword, + cipherId: cipherId, + newPassword: newPassword, + domain: loginDomain, + tabId: tab.id, + expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes + wasVaultLocked: isVaultLocked, + }; + this.notificationQueue.push(message); + await this.checkNotificationQueue(tab); + } + + private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) { + for (let i = this.notificationQueue.length - 1; i >= 0; i--) { + const queueMessage = this.notificationQueue[i]; + if ( + queueMessage.tabId !== tab.id || + (queueMessage.type !== NotificationQueueMessageType.addLogin && + queueMessage.type !== NotificationQueueMessageType.changePassword) + ) { + continue; + } + + const tabDomain = Utils.getDomain(tab.url); + if (tabDomain != null && tabDomain !== queueMessage.domain) { + continue; + } + + this.notificationQueue.splice(i, 1); + BrowserApi.tabSendMessageData(tab, "closeNotificationBar"); + + if (queueMessage.type === NotificationQueueMessageType.changePassword) { + const message = queueMessage as AddChangePasswordQueueMessage; + const cipher = await this.getDecryptedCipherById(message.cipherId); + if (cipher == null) { + return; } - } + await this.updateCipher(cipher, message.newPassword); + return; + } - private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) { - const loginModel = new LoginView(); - const loginUri = new LoginUriView(); - loginUri.uri = queueMessage.uri; - loginModel.uris = [loginUri]; - loginModel.username = queueMessage.username; - loginModel.password = queueMessage.password; - const model = new CipherView(); - model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain; - model.name = model.name.replace(/^www\./, ''); - model.type = CipherType.Login; - model.login = loginModel; + if (!queueMessage.wasVaultLocked) { + await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId); + } - if (!Utils.isNullOrWhitespace(folderId)) { - const folders = await this.folderService.getAllDecrypted(); - if (folders.some(x => x.id === folderId)) { - model.folderId = folderId; - } + // If the vault was locked, check if a cipher needs updating instead of creating a new one + if ( + queueMessage.type === NotificationQueueMessageType.addLogin && + queueMessage.wasVaultLocked === true + ) { + const message = queueMessage as AddLoginQueueMessage; + const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri); + const usernameMatches = ciphers.filter( + (c) => c.login.username != null && c.login.username.toLowerCase() === message.username + ); + + if (usernameMatches.length >= 1) { + await this.updateCipher(usernameMatches[0], message.password); + return; } - const cipher = await this.cipherService.encrypt(model); - await this.cipherService.saveWithServer(cipher); + await this.createNewCipher(message, folderId); + } + } + } + + private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) { + const loginModel = new LoginView(); + const loginUri = new LoginUriView(); + loginUri.uri = queueMessage.uri; + loginModel.uris = [loginUri]; + loginModel.username = queueMessage.username; + loginModel.password = queueMessage.password; + const model = new CipherView(); + model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain; + model.name = model.name.replace(/^www\./, ""); + model.type = CipherType.Login; + model.login = loginModel; + + if (!Utils.isNullOrWhitespace(folderId)) { + const folders = await this.folderService.getAllDecrypted(); + if (folders.some((x) => x.id === folderId)) { + model.folderId = folderId; + } } - private async getDecryptedCipherById(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); - if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt(); - } - return null; + const cipher = await this.cipherService.encrypt(model); + await this.cipherService.saveWithServer(cipher); + } + + private async getDecryptedCipherById(cipherId: string) { + const cipher = await this.cipherService.get(cipherId); + if (cipher != null && cipher.type === CipherType.Login) { + return await cipher.decrypt(); + } + return null; + } + + private async updateCipher(cipher: CipherView, newPassword: string) { + if (cipher != null && cipher.type === CipherType.Login) { + cipher.login.password = newPassword; + const newCipher = await this.cipherService.encrypt(cipher); + await this.cipherService.saveWithServer(newCipher); + } + } + + private async saveNever(tab: chrome.tabs.Tab) { + for (let i = this.notificationQueue.length - 1; i >= 0; i--) { + const queueMessage = this.notificationQueue[i]; + if ( + queueMessage.tabId !== tab.id || + queueMessage.type !== NotificationQueueMessageType.addLogin + ) { + continue; + } + + const tabDomain = Utils.getDomain(tab.url); + if (tabDomain != null && tabDomain !== queueMessage.domain) { + continue; + } + + this.notificationQueue.splice(i, 1); + BrowserApi.tabSendMessageData(tab, "closeNotificationBar"); + + const hostname = Utils.getHostname(tab.url); + await this.cipherService.saveNeverDomain(hostname); + } + } + + private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) { + const responseData: any = {}; + if (responseCommand === "notificationBarGetFoldersList") { + responseData.folders = await this.folderService.getAllDecrypted(); } - private async updateCipher(cipher: CipherView, newPassword: string) { - if (cipher != null && cipher.type === CipherType.Login) { - cipher.login.password = newPassword; - const newCipher = await this.cipherService.encrypt(cipher); - await this.cipherService.saveWithServer(newCipher); - } - } + await BrowserApi.tabSendMessageData(tab, responseCommand, responseData); + } - private async saveNever(tab: chrome.tabs.Tab) { - for (let i = this.notificationQueue.length - 1; i >= 0; i--) { - const queueMessage = this.notificationQueue[i]; - if (queueMessage.tabId !== tab.id || queueMessage.type !== NotificationQueueMessageType.addLogin) { - continue; - } - - const tabDomain = Utils.getDomain(tab.url); - if (tabDomain != null && tabDomain !== queueMessage.domain) { - continue; - } - - this.notificationQueue.splice(i, 1); - BrowserApi.tabSendMessageData(tab, 'closeNotificationBar'); - - const hostname = Utils.getHostname(tab.url); - await this.cipherService.saveNeverDomain(hostname); - } - } - - private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) { - const responseData: any = {}; - if (responseCommand === 'notificationBarGetFoldersList') { - responseData.folders = await this.folderService.getAllDecrypted(); - } - - await BrowserApi.tabSendMessageData(tab, responseCommand, responseData); - } - - private async allowPersonalOwnership(): Promise { - return !await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership); - } + private async allowPersonalOwnership(): Promise { + return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)); + } } diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index 41bb287d5f..9c999c00b3 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -1,219 +1,248 @@ -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SystemService } from 'jslib-common/abstractions/system.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { NotificationsService } from "jslib-common/abstractions/notifications.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { SystemService } from "jslib-common/abstractions/system.service"; +import { ConstantsService } from "jslib-common/services/constants.service"; -import { AutofillService } from '../services/abstractions/autofill.service'; -import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; +import { AutofillService } from "../services/abstractions/autofill.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 LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem'; +import { Utils } from "jslib-common/misc/utils"; +import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem"; export default class RuntimeBackground { - private autofillTimeout: any; - private pageDetailsToAutoFill: any[] = []; - private onInstalledReason: string = null; - private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = []; + private autofillTimeout: any; + private pageDetailsToAutoFill: any[] = []; + private onInstalledReason: string = null; + private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = []; - constructor(private main: MainBackground, private autofillService: AutofillService, - private platformUtilsService: BrowserPlatformUtilsService, - private storageService: StorageService, private i18nService: I18nService, - private notificationsService: NotificationsService, private systemService: SystemService, - private environmentService: EnvironmentService, private messagingService: MessagingService, - private logService: LogService) { + constructor( + private main: MainBackground, + private autofillService: AutofillService, + private platformUtilsService: BrowserPlatformUtilsService, + private storageService: StorageService, + private i18nService: I18nService, + private notificationsService: NotificationsService, + 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 + chrome.runtime.onInstalled.addListener((details: any) => { + this.onInstalledReason = details.reason; + }); + } - // onInstalled listener must be wired up before anything else, so we do it in the ctor - chrome.runtime.onInstalled.addListener((details: any) => { - this.onInstalledReason = details.reason; - }); + async init() { + if (!chrome.runtime) { + return; } - async init() { - if (!chrome.runtime) { - return; + await this.checkOnInstalled(); + BrowserApi.messageListener( + "runtime.background", + async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => { + await this.processMessage(msg, sender, sendResponse); + } + ); + } + + async processMessage(msg: any, sender: any, sendResponse: any) { + switch (msg.command) { + case "loggedIn": + case "unlocked": + let item: LockedVaultPendingNotificationsItem; + + if (this.lockedVaultPendingNotifications.length > 0) { + await BrowserApi.closeLoginTab(); + + item = this.lockedVaultPendingNotifications.pop(); + if (item.commandToRetry.sender?.tab?.id) { + await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id); + } } - await this.checkOnInstalled(); - BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => { - await this.processMessage(msg, sender, sendResponse); - }); - } + await this.main.setIcon(); + await this.main.refreshBadgeAndMenu(false); + this.notificationsService.updateConnection(msg.command === "unlocked"); + this.systemService.cancelProcessReload(); - async processMessage(msg: any, sender: any, sendResponse: any) { - switch (msg.command) { - case 'loggedIn': - case 'unlocked': - let item: LockedVaultPendingNotificationsItem; - - if (this.lockedVaultPendingNotifications.length > 0) { - await BrowserApi.closeLoginTab(); - - item = this.lockedVaultPendingNotifications.pop(); - if (item.commandToRetry.sender?.tab?.id) { - await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id); - } - } - - await this.main.setIcon(); - await this.main.refreshBadgeAndMenu(false); - this.notificationsService.updateConnection(msg.command === 'unlocked'); - this.systemService.cancelProcessReload(); - - if (item) { - await BrowserApi.tabSendMessageData(item.commandToRetry.sender.tab, 'unlockCompleted', item); - } - break; - case 'addToLockedVaultPendingNotifications': - this.lockedVaultPendingNotifications.push(msg.data); - break; - case 'logout': - await this.main.logout(msg.expired); - break; - case 'syncCompleted': - if (msg.successfully) { - setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000); - } - break; - case 'openPopup': - await this.main.openPopup(); - break; - case 'promptForLogin': - await BrowserApi.createNewTab('popup/index.html?uilocation=popout', true, true); - break; - case 'showDialogResolve': - this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed); - break; - case 'bgCollectPageDetails': - await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId); - break; - case 'bgUpdateContextMenu': - case 'editedCipher': - case 'addedCipher': - case 'deletedCipher': - await this.main.refreshBadgeAndMenu(); - break; - case 'bgReseedStorage': - await this.main.reseedStorage(); - break; - case 'collectPageDetailsResponse': - switch (msg.sender) { - case 'autofiller': - case 'autofill_cmd': - const totpCode = await this.autofillService.doAutoFillActiveTab([{ - frameId: sender.frameId, - tab: msg.tab, - details: msg.details, - }], msg.sender === 'autofill_cmd'); - if (totpCode != null) { - this.platformUtilsService.copyToClipboard(totpCode, { window: window }); - } - break; - case 'contextMenu': - clearTimeout(this.autofillTimeout); - this.pageDetailsToAutoFill.push({ - frameId: sender.frameId, - tab: msg.tab, - details: msg.details, - }); - this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300); - break; - default: - break; - } - break; - case 'authResult': - const vaultUrl = this.environmentService.getWebVaultUrl(); - - if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { - return; - } - - try { - BrowserApi.createNewTab('popup/index.html?uilocation=popout#/sso?code=' + - encodeURIComponent(msg.code) + '&state=' + encodeURIComponent(msg.state)); - } - catch { - this.logService.error('Unable to open sso popout tab'); - } - break; - case 'webAuthnResult': - const vaultUrl2 = this.environmentService.getWebVaultUrl(); - - if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) { - return; - } - - const params = `webAuthnResponse=${encodeURIComponent(msg.data)};` + - `remember=${encodeURIComponent(msg.remember)}`; - BrowserApi.createNewTab(`popup/index.html?uilocation=popout#/2fa;${params}`, undefined, false); - break; - case 'reloadPopup': - this.messagingService.send('reloadPopup'); - break; - case 'emailVerificationRequired': - this.messagingService.send('showDialog', { - dialogId: 'emailVerificationRequired', - title: this.i18nService.t('emailVerificationRequired'), - text: this.i18nService.t('emailVerificationRequiredDesc'), - confirmText: this.i18nService.t('ok'), - type: 'info', - }); - break; - case 'getClickedElementResponse': - this.platformUtilsService.copyToClipboard(msg.identifier, { window: window }); - default: - break; + if (item) { + await BrowserApi.tabSendMessageData( + item.commandToRetry.sender.tab, + "unlockCompleted", + item + ); } - } - - private async autofillPage() { - const totpCode = await this.autofillService.doAutoFill({ - cipher: this.main.loginToAutoFill, - pageDetails: this.pageDetailsToAutoFill, - fillNewPassword: true, - }); - - if (totpCode != null) { - this.platformUtilsService.copyToClipboard(totpCode, { window: window }); + break; + case "addToLockedVaultPendingNotifications": + this.lockedVaultPendingNotifications.push(msg.data); + break; + case "logout": + await this.main.logout(msg.expired); + break; + case "syncCompleted": + if (msg.successfully) { + setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000); } - - // reset - this.main.loginToAutoFill = null; - this.pageDetailsToAutoFill = []; - } - - private async checkOnInstalled() { - setTimeout(async () => { - if (this.onInstalledReason != null) { - if (this.onInstalledReason === 'install') { - BrowserApi.createNewTab('https://bitwarden.com/browser-start/'); - await this.setDefaultSettings(); - } - - this.onInstalledReason = null; + break; + case "openPopup": + await this.main.openPopup(); + break; + case "promptForLogin": + await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true); + break; + case "showDialogResolve": + this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed); + break; + case "bgCollectPageDetails": + await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId); + break; + case "bgUpdateContextMenu": + case "editedCipher": + case "addedCipher": + case "deletedCipher": + await this.main.refreshBadgeAndMenu(); + break; + case "bgReseedStorage": + await this.main.reseedStorage(); + break; + case "collectPageDetailsResponse": + switch (msg.sender) { + case "autofiller": + case "autofill_cmd": + const totpCode = await this.autofillService.doAutoFillActiveTab( + [ + { + frameId: sender.frameId, + tab: msg.tab, + details: msg.details, + }, + ], + msg.sender === "autofill_cmd" + ); + if (totpCode != null) { + this.platformUtilsService.copyToClipboard(totpCode, { window: window }); } - }, 100); - } + break; + case "contextMenu": + clearTimeout(this.autofillTimeout); + this.pageDetailsToAutoFill.push({ + frameId: sender.frameId, + tab: msg.tab, + details: msg.details, + }); + this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300); + break; + default: + break; + } + break; + case "authResult": + const vaultUrl = this.environmentService.getWebVaultUrl(); - private async setDefaultSettings() { - // Default timeout option to "on restart". - const currentVaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); - if (currentVaultTimeout == null) { - await this.storageService.save(ConstantsService.vaultTimeoutKey, -1); + if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { + return; } - // Default action to "lock". - const currentVaultTimeoutAction = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); - if (currentVaultTimeoutAction == null) { - await this.storageService.save(ConstantsService.vaultTimeoutActionKey, 'lock'); + try { + BrowserApi.createNewTab( + "popup/index.html?uilocation=popout#/sso?code=" + + encodeURIComponent(msg.code) + + "&state=" + + encodeURIComponent(msg.state) + ); + } catch { + this.logService.error("Unable to open sso popout tab"); } + break; + case "webAuthnResult": + const vaultUrl2 = this.environmentService.getWebVaultUrl(); + + if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) { + return; + } + + const params = + `webAuthnResponse=${encodeURIComponent(msg.data)};` + + `remember=${encodeURIComponent(msg.remember)}`; + BrowserApi.createNewTab( + `popup/index.html?uilocation=popout#/2fa;${params}`, + undefined, + false + ); + break; + case "reloadPopup": + this.messagingService.send("reloadPopup"); + break; + case "emailVerificationRequired": + this.messagingService.send("showDialog", { + dialogId: "emailVerificationRequired", + title: this.i18nService.t("emailVerificationRequired"), + text: this.i18nService.t("emailVerificationRequiredDesc"), + confirmText: this.i18nService.t("ok"), + type: "info", + }); + break; + case "getClickedElementResponse": + this.platformUtilsService.copyToClipboard(msg.identifier, { window: window }); + default: + break; } + } + + private async autofillPage() { + const totpCode = await this.autofillService.doAutoFill({ + cipher: this.main.loginToAutoFill, + pageDetails: this.pageDetailsToAutoFill, + fillNewPassword: true, + }); + + if (totpCode != null) { + this.platformUtilsService.copyToClipboard(totpCode, { window: window }); + } + + // reset + this.main.loginToAutoFill = null; + this.pageDetailsToAutoFill = []; + } + + private async checkOnInstalled() { + setTimeout(async () => { + if (this.onInstalledReason != null) { + if (this.onInstalledReason === "install") { + BrowserApi.createNewTab("https://bitwarden.com/browser-start/"); + await this.setDefaultSettings(); + } + + this.onInstalledReason = null; + } + }, 100); + } + + private async setDefaultSettings() { + // Default timeout option to "on restart". + const currentVaultTimeout = await this.storageService.get( + ConstantsService.vaultTimeoutKey + ); + if (currentVaultTimeout == null) { + await this.storageService.save(ConstantsService.vaultTimeoutKey, -1); + } + + // Default action to "lock". + const currentVaultTimeoutAction = await this.storageService.get( + ConstantsService.vaultTimeoutActionKey + ); + if (currentVaultTimeoutAction == null) { + await this.storageService.save(ConstantsService.vaultTimeoutActionKey, "lock"); + } + } } diff --git a/src/background/tabs.background.ts b/src/background/tabs.background.ts index 7b43d7696a..5ec6f7f124 100644 --- a/src/background/tabs.background.ts +++ b/src/background/tabs.background.ts @@ -1,41 +1,45 @@ -import MainBackground from './main.background'; -import NotificationBackground from './notification.background'; +import MainBackground from "./main.background"; +import NotificationBackground from "./notification.background"; export default class TabsBackground { - constructor(private main: MainBackground, private notificationBackground: NotificationBackground) { + constructor( + private main: MainBackground, + private notificationBackground: NotificationBackground + ) {} + + async init() { + if (!chrome.tabs) { + return; } - async init() { - if (!chrome.tabs) { - return; + chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => { + await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send("tabActivated"); + this.main.messagingService.send("tabChanged"); + }); + + chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => { + if (this.main.onReplacedRan) { + return; + } + this.main.onReplacedRan = true; + await this.notificationBackground.checkNotificationQueue(); + await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send("tabReplaced"); + this.main.messagingService.send("tabChanged"); + }); + + chrome.tabs.onUpdated.addListener( + async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => { + if (this.main.onUpdatedRan) { + return; } - - chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => { - await this.main.refreshBadgeAndMenu(); - this.main.messagingService.send('tabActivated'); - this.main.messagingService.send('tabChanged'); - }); - - chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => { - if (this.main.onReplacedRan) { - return; - } - this.main.onReplacedRan = true; - await this.notificationBackground.checkNotificationQueue(); - await this.main.refreshBadgeAndMenu(); - this.main.messagingService.send('tabReplaced'); - this.main.messagingService.send('tabChanged'); - }); - - chrome.tabs.onUpdated.addListener(async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => { - if (this.main.onUpdatedRan) { - return; - } - this.main.onUpdatedRan = true; - await this.notificationBackground.checkNotificationQueue(tab); - await this.main.refreshBadgeAndMenu(); - this.main.messagingService.send('tabUpdated'); - this.main.messagingService.send('tabChanged'); - }); - } + this.main.onUpdatedRan = true; + await this.notificationBackground.checkNotificationQueue(tab); + await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send("tabUpdated"); + this.main.messagingService.send("tabChanged"); + } + ); + } } diff --git a/src/background/webRequest.background.ts b/src/background/webRequest.background.ts index 4c858c1034..e120efb5a0 100644 --- a/src/background/webRequest.background.ts +++ b/src/background/webRequest.background.ts @@ -1,78 +1,94 @@ -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.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 { - private pendingAuthRequests: any[] = []; - private webRequest: any; - private isFirefox: boolean; + private pendingAuthRequests: any[] = []; + private webRequest: any; + private isFirefox: boolean; - constructor(platformUtilsService: PlatformUtilsService, private cipherService: CipherService, - private vaultTimeoutService: VaultTimeoutService) { - this.webRequest = (window as any).chrome.webRequest; - this.isFirefox = platformUtilsService.isFirefox(); + constructor( + platformUtilsService: PlatformUtilsService, + private cipherService: CipherService, + private vaultTimeoutService: VaultTimeoutService + ) { + this.webRequest = (window as any).chrome.webRequest; + this.isFirefox = platformUtilsService.isFirefox(); + } + + async init() { + if (!this.webRequest || !this.webRequest.onAuthRequired) { + return; } - async init() { - if (!this.webRequest || !this.webRequest.onAuthRequired) { - return; + this.webRequest.onAuthRequired.addListener( + async (details: any, callback: any) => { + if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) { + if (callback) { + callback(); + } + return; } - this.webRequest.onAuthRequired.addListener(async (details: any, callback: any) => { - if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) { - if (callback) { - callback(); - } - return; - } + this.pendingAuthRequests.push(details.requestId); - this.pendingAuthRequests.push(details.requestId); + if (this.isFirefox) { + return new Promise(async (resolve, reject) => { + await this.resolveAuthCredentials(details.url, resolve, reject); + }); + } else { + await this.resolveAuthCredentials(details.url, callback, callback); + } + }, + { urls: ["http://*/*", "https://*/*"] }, + [this.isFirefox ? "blocking" : "asyncBlocking"] + ); - if (this.isFirefox) { - return new Promise(async (resolve, reject) => { - await this.resolveAuthCredentials(details.url, resolve, reject); - }); - } else { - await this.resolveAuthCredentials(details.url, callback, callback); - } - }, { urls: ['http://*/*', 'https://*/*'] }, [this.isFirefox ? 'blocking' : 'asyncBlocking']); + this.webRequest.onCompleted.addListener((details: any) => this.completeAuthRequest(details), { + urls: ["http://*/*"], + }); + this.webRequest.onErrorOccurred.addListener( + (details: any) => this.completeAuthRequest(details), + { + urls: ["http://*/*"], + } + ); + } - this.webRequest.onCompleted.addListener( - (details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] }); - this.webRequest.onErrorOccurred.addListener( - (details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] }); + private async resolveAuthCredentials(domain: string, success: Function, error: Function) { + if (await this.vaultTimeoutService.isLocked()) { + error(); + return; } - private async resolveAuthCredentials(domain: string, success: Function, error: Function) { - if (await this.vaultTimeoutService.isLocked()) { - error(); - return; - } + try { + const ciphers = await this.cipherService.getAllDecryptedForUrl( + domain, + null, + UriMatchType.Host + ); + if (ciphers == null || ciphers.length !== 1) { + error(); + return; + } - try { - const ciphers = await this.cipherService.getAllDecryptedForUrl(domain, null, UriMatchType.Host); - if (ciphers == null || ciphers.length !== 1) { - error(); - return; - } - - success({ - authCredentials: { - username: ciphers[0].login.username, - password: ciphers[0].login.password, - }, - }); - } catch { - error(); - } + success({ + authCredentials: { + username: ciphers[0].login.username, + password: ciphers[0].login.password, + }, + }); + } catch { + error(); } + } - private completeAuthRequest(details: any) { - const i = this.pendingAuthRequests.indexOf(details.requestId); - if (i > -1) { - this.pendingAuthRequests.splice(i, 1); - } + private completeAuthRequest(details: any) { + const i = this.pendingAuthRequests.indexOf(details.requestId); + if (i > -1) { + this.pendingAuthRequests.splice(i, 1); } + } } diff --git a/src/background/windows.background.ts b/src/background/windows.background.ts index 6239ddd782..1c57d295b0 100644 --- a/src/background/windows.background.ts +++ b/src/background/windows.background.ts @@ -1,25 +1,25 @@ -import MainBackground from './main.background'; +import MainBackground from "./main.background"; export default class WindowsBackground { - private windows: any; + private windows: any; - constructor(private main: MainBackground) { - this.windows = chrome.windows; + constructor(private main: MainBackground) { + this.windows = chrome.windows; + } + + async init() { + if (!this.windows) { + return; } - async init() { - if (!this.windows) { - return; - } + this.windows.onFocusChanged.addListener(async (windowId: any) => { + if (windowId === null || windowId < 0) { + return; + } - this.windows.onFocusChanged.addListener(async (windowId: any) => { - if (windowId === null || windowId < 0) { - return; - } - - await this.main.refreshBadgeAndMenu(); - this.main.messagingService.send('windowFocused'); - this.main.messagingService.send('windowChanged'); - }); - } + await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send("windowFocused"); + this.main.messagingService.send("windowChanged"); + }); + } } diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 5efb4bc821..0c3b7a79f1 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -1,208 +1,228 @@ -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 { - static isWebExtensionsApi: boolean = (typeof browser !== 'undefined'); - static isSafariApi: boolean = navigator.userAgent.indexOf(' Safari/') !== -1 && - navigator.userAgent.indexOf(' Chrome/') === -1 && - navigator.userAgent.indexOf(' Chromium/') === -1; - static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined'); - static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf('Firefox/') !== -1 && - navigator.userAgent.indexOf('Android') !== -1; + static isWebExtensionsApi: boolean = typeof browser !== "undefined"; + static isSafariApi: boolean = + navigator.userAgent.indexOf(" Safari/") !== -1 && + navigator.userAgent.indexOf(" Chrome/") === -1 && + navigator.userAgent.indexOf(" Chromium/") === -1; + static isChromeApi: boolean = !BrowserApi.isSafariApi && typeof chrome !== "undefined"; + static isFirefoxOnAndroid: boolean = + navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1; - static async getTabFromCurrentWindowId(): Promise | null { - return await BrowserApi.tabsQueryFirst({ - active: true, - windowId: chrome.windows.WINDOW_ID_CURRENT, - }); + static async getTabFromCurrentWindowId(): Promise | null { + return await BrowserApi.tabsQueryFirst({ + active: true, + windowId: chrome.windows.WINDOW_ID_CURRENT, + }); + } + + static async getTabFromCurrentWindow(): Promise | null { + return await BrowserApi.tabsQueryFirst({ + active: true, + currentWindow: true, + }); + } + + static async getActiveTabs(): Promise { + return await BrowserApi.tabsQuery({ + active: true, + }); + } + + static async tabsQuery(options: chrome.tabs.QueryInfo): Promise { + return new Promise((resolve) => { + chrome.tabs.query(options, (tabs: any[]) => { + resolve(tabs); + }); + }); + } + + static async tabsQueryFirst(options: chrome.tabs.QueryInfo): Promise | null { + const tabs = await BrowserApi.tabsQuery(options); + if (tabs.length > 0) { + return tabs[0]; } - static async getTabFromCurrentWindow(): Promise | null { - return await BrowserApi.tabsQueryFirst({ - active: true, - currentWindow: true, - }); + return null; + } + + static tabSendMessageData( + tab: chrome.tabs.Tab, + command: string, + data: any = null + ): Promise { + const obj: any = { + command: command, + }; + + if (data != null) { + obj.data = data; } - static async getActiveTabs(): Promise { - return await BrowserApi.tabsQuery({ - active: true, - }); + return BrowserApi.tabSendMessage(tab, obj); + } + + static async tabSendMessage( + tab: chrome.tabs.Tab, + obj: any, + options: chrome.tabs.MessageSendOptions = null + ): Promise { + if (!tab || !tab.id) { + return; } - static async tabsQuery(options: chrome.tabs.QueryInfo): Promise { - return new Promise(resolve => { - chrome.tabs.query(options, (tabs: any[]) => { - resolve(tabs); - }); - }); - } - - static async tabsQueryFirst(options: chrome.tabs.QueryInfo): Promise | null { - const tabs = await BrowserApi.tabsQuery(options); - if (tabs.length > 0) { - return tabs[0]; + return new Promise((resolve) => { + chrome.tabs.sendMessage(tab.id, obj, options, () => { + if (chrome.runtime.lastError) { + // Some error happened } + resolve(); + }); + }); + } - return null; + static getBackgroundPage(): any { + return chrome.extension.getBackgroundPage(); + } + + static getApplicationVersion(): string { + return chrome.runtime.getManifest().version; + } + + static async isPopupOpen(): Promise { + return Promise.resolve(chrome.extension.getViews({ type: "popup" }).length > 0); + } + + static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) { + chrome.tabs.create({ url: url, active: active }); + } + + static messageListener( + 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); + } + ); + } + + static async closeLoginTab() { + const tabs = await BrowserApi.tabsQuery({ + active: true, + title: "Bitwarden", + windowType: "normal", + currentWindow: true, + }); + + if (tabs.length === 0) { + return; } - static tabSendMessageData(tab: chrome.tabs.Tab, command: string, data: any = null): Promise { - const obj: any = { - command: command, - }; + const tabToClose = tabs[tabs.length - 1].id; + chrome.tabs.remove(tabToClose); + } - if (data != null) { - obj.data = data; - } + static async focusSpecifiedTab(tabId: number) { + chrome.tabs.update(tabId, { active: true, highlighted: true }); + } - return BrowserApi.tabSendMessage(tab, obj); + static closePopup(win: Window) { + if (BrowserApi.isWebExtensionsApi && BrowserApi.isFirefoxOnAndroid) { + // Reactivating the active tab dismisses the popup tab. The promise final + // condition is only called if the popup wasn't already dismissed (future proofing). + // ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604 + browser.tabs.update({ active: true }).finally(win.close); + } else { + win.close(); } + } - static async tabSendMessage(tab: chrome.tabs.Tab, obj: any, options: chrome.tabs.MessageSendOptions = null): Promise { - if (!tab || !tab.id) { - return; - } - - return new Promise(resolve => { - chrome.tabs.sendMessage(tab.id, obj, options, () => { - if (chrome.runtime.lastError) { - // Some error happened - } - resolve(); - }); - }); + static downloadFile(win: Window, blobData: any, blobOptions: any, fileName: string) { + if (BrowserApi.isSafariApi) { + const type = blobOptions != null ? blobOptions.type : null; + let data: string = null; + if (type === "text/plain" && typeof blobData === "string") { + data = blobData; + } else { + data = Utils.fromBufferToB64(blobData); + } + SafariApp.sendMessageToApp( + "downloadFile", + JSON.stringify({ + blobData: data, + blobOptions: blobOptions, + fileName: fileName, + }), + true + ); + } else { + const blob = new Blob([blobData], blobOptions); + if (navigator.msSaveOrOpenBlob) { + navigator.msSaveBlob(blob, fileName); + } else { + const a = win.document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = fileName; + win.document.body.appendChild(a); + a.click(); + win.document.body.removeChild(a); + } } + } - static getBackgroundPage(): any { - return chrome.extension.getBackgroundPage(); + static gaFilter() { + return process.env.ENV !== "production"; + } + + static getUILanguage(win: Window) { + return chrome.i18n.getUILanguage(); + } + + static reloadExtension(win: Window) { + if (win != null) { + return win.location.reload(true); + } else { + return chrome.runtime.reload(); } + } - static getApplicationVersion(): string { - return chrome.runtime.getManifest().version; + static reloadOpenWindows() { + const views = chrome.extension.getViews() as Window[]; + views + .filter((w) => w.location.href != null) + .forEach((w) => { + w.location.reload(); + }); + } + + static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port { + if (BrowserApi.isWebExtensionsApi) { + return browser.runtime.connectNative(application); + } else if (BrowserApi.isChromeApi) { + return chrome.runtime.connectNative(application); } + } - static async isPopupOpen(): Promise { - return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0); + static requestPermission(permission: any) { + if (BrowserApi.isWebExtensionsApi) { + return browser.permissions.request(permission); } + return new Promise((resolve, reject) => { + chrome.permissions.request(permission, resolve); + }); + } - static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) { - chrome.tabs.create({ url: url, active: active }); - } - - static messageListener(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); - }); - } - - static async closeLoginTab() { - const tabs = await BrowserApi.tabsQuery({ - active: true, - title: 'Bitwarden', - windowType: 'normal', - currentWindow: true, - }); - - if (tabs.length === 0) { - return; - } - - const tabToClose = tabs[tabs.length - 1].id; - chrome.tabs.remove(tabToClose); - } - - static async focusSpecifiedTab(tabId: number) { - chrome.tabs.update(tabId, { active: true, highlighted: true }); - } - - static closePopup(win: Window) { - if (BrowserApi.isWebExtensionsApi && BrowserApi.isFirefoxOnAndroid) { - // Reactivating the active tab dismisses the popup tab. The promise final - // condition is only called if the popup wasn't already dismissed (future proofing). - // ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604 - browser.tabs.update({ active: true }).finally(win.close); - } else { - win.close(); - } - } - - static downloadFile(win: Window, blobData: any, blobOptions: any, fileName: string) { - if (BrowserApi.isSafariApi) { - const type = blobOptions != null ? blobOptions.type : null; - let data: string = null; - if (type === 'text/plain' && typeof (blobData) === 'string') { - data = blobData; - } else { - data = Utils.fromBufferToB64(blobData); - } - SafariApp.sendMessageToApp('downloadFile', JSON.stringify({ - blobData: data, - blobOptions: blobOptions, - fileName: fileName, - }), true); - } else { - const blob = new Blob([blobData], blobOptions); - if (navigator.msSaveOrOpenBlob) { - navigator.msSaveBlob(blob, fileName); - } else { - const a = win.document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = fileName; - win.document.body.appendChild(a); - a.click(); - win.document.body.removeChild(a); - } - } - } - - static gaFilter() { - return process.env.ENV !== 'production'; - } - - static getUILanguage(win: Window) { - return chrome.i18n.getUILanguage(); - } - - static reloadExtension(win: Window) { - if (win != null) { - return win.location.reload(true); - } else { - return chrome.runtime.reload(); - } - } - - static reloadOpenWindows() { - const views = chrome.extension.getViews() as Window[]; - views.filter(w => w.location.href != null).forEach(w => { - w.location.reload(); - }); - } - - static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port { - if (BrowserApi.isWebExtensionsApi) { - return browser.runtime.connectNative(application); - } else if (BrowserApi.isChromeApi) { - return chrome.runtime.connectNative(application); - } - } - - static requestPermission(permission: any) { - if (BrowserApi.isWebExtensionsApi) { - return browser.permissions.request(permission); - } - return new Promise((resolve, reject) => { - chrome.permissions.request(permission, resolve); - }); - } - - static getPlatformInfo(): Promise { - if (BrowserApi.isWebExtensionsApi) { - return browser.runtime.getPlatformInfo(); - } - return new Promise(resolve => { - chrome.runtime.getPlatformInfo(resolve); - }); + static getPlatformInfo(): Promise { + if (BrowserApi.isWebExtensionsApi) { + return browser.runtime.getPlatformInfo(); } + return new Promise((resolve) => { + chrome.runtime.getPlatformInfo(resolve); + }); + } } diff --git a/src/browser/safariApp.ts b/src/browser/safariApp.ts index 80930a3d57..7a86295df6 100644 --- a/src/browser/safariApp.ts +++ b/src/browser/safariApp.ts @@ -1,21 +1,26 @@ -import { BrowserApi } from './browserApi'; +import { BrowserApi } from "./browserApi"; export class SafariApp { - static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise { - if (!BrowserApi.isSafariApi) { - return Promise.resolve(null); - } - return new Promise(resolve => { - const now = new Date(); - const messageId = now.getTime().toString() + '_' + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - (browser as any).runtime.sendNativeMessage('com.bitwarden.desktop', { - id: messageId, - command: command, - data: data, - responseData: null, - }, (response: any) => { - resolve(response); - }); - }); + static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise { + if (!BrowserApi.isSafariApi) { + return Promise.resolve(null); } + return new Promise((resolve) => { + const now = new Date(); + const messageId = + now.getTime().toString() + "_" + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + (browser as any).runtime.sendNativeMessage( + "com.bitwarden.desktop", + { + id: messageId, + command: command, + data: data, + responseData: null, + }, + (response: any) => { + resolve(response); + } + ); + }); + } } diff --git a/src/content/autofill.css b/src/content/autofill.css index 453556777c..e495cbf5c4 100644 --- a/src/content/autofill.css +++ b/src/content/autofill.css @@ -1,36 +1,36 @@ @-webkit-keyframes bitwardenfill { - 0% { - -webkit-transform: scale(1.0, 1.0); - } + 0% { + -webkit-transform: scale(1, 1); + } - 50% { - -webkit-transform: scale(1.2, 1.2); - } + 50% { + -webkit-transform: scale(1.2, 1.2); + } - 100% { - -webkit-transform: scale(1.0, 1.0); - } + 100% { + -webkit-transform: scale(1, 1); + } } @-moz-keyframes bitwardenfill { - 0% { - transform: scale(1.0, 1.0); - } + 0% { + transform: scale(1, 1); + } - 50% { - transform: scale(1.2, 1.2); - } + 50% { + transform: scale(1.2, 1.2); + } - 100% { - transform: scale(1.0, 1.0); - } + 100% { + transform: scale(1, 1); + } } span[data-bwautofill].com-bitwarden-browser-animated-fill { - display: inline-block; + display: inline-block; } .com-bitwarden-browser-animated-fill { - animation: bitwardenfill 200ms ease-in-out 0ms 1; - -webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1; + animation: bitwardenfill 200ms ease-in-out 0ms 1; + -webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1; } diff --git a/src/content/autofill.js b/src/content/autofill.js index 260350a3ea..7b285d4cf4 100644 --- a/src/content/autofill.js +++ b/src/content/autofill.js @@ -1,1042 +1,1042 @@ !(function () { - /* - 1Password Extension - - Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. - Copyright (c) 2014 AgileBits. All rights reserved. - - ================================================================================ - - Copyright (c) 2014 AgileBits Inc. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - - /* - MODIFICATIONS FROM ORIGINAL - - 1. Populate isFirefox - 2. Remove isChrome and isSafari since they are not used. - 3. Unminify and format to meet Mozilla review requirements. - 4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned. - 5. Remove fakeTested prop. - 6. Rename com.agilebits.* stuff to com.bitwarden.* - 7. Remove "some useful globals" on window - 8. Add ability to autofill span[data-bwautofill] elements - */ - - function collect(document, undefined) { - // START MODIFICATION - var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; - // END MODIFICATION - - document.elementsByOPID = {}; - document.addEventListener('input', function (inputevent) { - inputevent.a !== false && - inputevent.target.tagName.toLowerCase() === 'input' && - (inputevent.target.dataset['com.bitwarden.browser.userEdited'] = 'yes'); - }, true); - - function getPageDetails(theDoc, oneShotId) { - // start helpers - - // get the value of a dom element's attribute - function getElementAttrValue(el, attrName) { - var attrVal = el[attrName]; - if ('string' == typeof attrVal) { - return attrVal; - } - attrVal = el.getAttribute(attrName); - return 'string' == typeof attrVal ? attrVal : null; - } - - // has the element been fake tested? - function checkIfFakeTested(field, el) { - if (-1 === ['text', 'password'].indexOf(el.type.toLowerCase()) || - !(passwordRegEx.test(field.value) || - passwordRegEx.test(field.htmlID) || passwordRegEx.test(field.htmlName) || - passwordRegEx.test(field.placeholder) || passwordRegEx.test(field['label-tag']) || - passwordRegEx.test(field['label-data']) || passwordRegEx.test(field['label-aria']))) { - return false; - } - - if (!field.visible) { - return true; - } - - if ('password' == el.type.toLowerCase()) { - return false; - } - - var elType = el.type; - focusElement(el, true); - return elType !== el.type; - } - - // get the value of a dom element - function getElementValue(el) { - switch (toLowerString(el.type)) { - case 'checkbox': - return el.checked ? '✓' : ''; - - case 'hidden': - el = el.value; - if (!el || 'number' != typeof el.length) { - return ''; - } - 254 < el.length && (el = el.substr(0, 254) + '...SNIPPED'); - return el; - - default: - // START MODIFICATION - if (!el.type && el.tagName.toLowerCase() === 'span') { - return el.innerText; - } - // END MODIFICATION - return el.value; - } - } - - // get all the options for a "select" element - function getSelectElementOptions(el) { - if (!el.options) { - return null; - } - - var options = Array.prototype.slice.call(el.options).map(function (option) { - var optionText = option.text ? - toLowerString(option.text).replace(/\\s/gm, '').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/gm, '') : - null; - - return [optionText ? optionText : null, option.value]; - }) - - return { - options: options - }; - } - - // get the top label - function getLabelTop(el) { - var parent; - for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName);) { - el = el.parentElement || el.parentNode; - } - - if (!el || void 0 === el) { - return null; - } - - parent = el.parentElement || el.parentNode; - if ('tr' != parent.tagName.toLowerCase()) { - return null; - } - - parent = parent.previousElementSibling; - if (!parent || 'tr' != (parent.tagName + '').toLowerCase() || - parent.cells && el.cellIndex >= parent.cells.length) { - return null; - } - - el = parent.cells[el.cellIndex]; - var elText = el.textContent || el.innerText; - return elText = cleanText(elText); - } - - // get all the tags for a given label - function getLabelTag(el) { - var docLabel, - theLabels = []; - - if (el.labels && el.labels.length && 0 < el.labels.length) { - theLabels = Array.prototype.slice.call(el.labels); - } else { - if (el.id) { - theLabels = theLabels.concat(Array.prototype.slice.call( - queryDoc(theDoc, 'label[for=' + JSON.stringify(el.id) + ']'))); - } - - if (el.name) { - docLabel = queryDoc(theDoc, 'label[for=' + JSON.stringify(el.name) + ']'); - - for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) { - if (-1 === theLabels.indexOf(docLabel[labelIndex])) { - theLabels.push(docLabel[labelIndex]) - } - } - } - - for (var theEl = el; theEl && theEl != theDoc; theEl = theEl.parentNode) { - if ('label' === toLowerString(theEl.tagName) && -1 === theLabels.indexOf(theEl)) { - theLabels.push(theEl); - } - } - } - - if (0 === theLabels.length) { - theEl = el.parentNode; - if ('dd' === theEl.tagName.toLowerCase() && null !== theEl.previousElementSibling - && 'dt' === theEl.previousElementSibling.tagName.toLowerCase()) { - theLabels.push(theEl.previousElementSibling); - } - } - - if (0 > theLabels.length) { - return null; - } - - return theLabels.map(function (l) { - return (l.textContent || l.innerText) - .replace(/^\\s+/, '').replace(/\\s+$/, '').replace('\\n', '').replace(/\\s{2,}/, ' '); - }).join(''); - } - - // add property and value to the object if there is a value - function addProp(obj, prop, val, d) { - if (0 !== d && d === val || null === val || void 0 === val) { - return; - } - - obj[prop] = val; - } - - // lowercase helper - function toLowerString(s) { - return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase(); - } - - // query the document helper - function queryDoc(doc, query) { - var els = []; - try { - els = doc.querySelectorAll(query); - } catch (e) { } - return els; - } - - // end helpers - - var theView = theDoc.defaultView ? theDoc.defaultView : window, - passwordRegEx = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i'); - - // get all the docs - var theForms = Array.prototype.slice.call(queryDoc(theDoc, 'form')).map(function (formEl, elIndex) { - var op = {}, - formOpId = '__form__' + elIndex; - - formEl.opid = formOpId; - op.opid = formOpId; - addProp(op, 'htmlName', getElementAttrValue(formEl, 'name')); - addProp(op, 'htmlID', getElementAttrValue(formEl, 'id')); - formOpId = getElementAttrValue(formEl, 'action'); - formOpId = new URL(formOpId, window.location.href); - addProp(op, 'htmlAction', formOpId ? formOpId.href : null); - addProp(op, 'htmlMethod', getElementAttrValue(formEl, 'method')); - - return op; - }); - - // get all the form fields - var theFields = Array.prototype.slice.call(getFormElements(theDoc, 50)).map(function (el, elIndex) { - var field = {}, - opId = '__' + elIndex, - elMaxLen = -1 == el.maxLength ? 999 : el.maxLength; - - if (!elMaxLen || 'number' === typeof elMaxLen && isNaN(elMaxLen)) { - elMaxLen = 999; - } - - theDoc.elementsByOPID[opId] = el; - el.opid = opId; - field.opid = opId; - field.elementNumber = elIndex; - addProp(field, 'maxLength', Math.min(elMaxLen, 999), 999); - field.visible = isElementVisible(el); - field.viewable = isElementViewable(el); - addProp(field, 'htmlID', getElementAttrValue(el, 'id')); - addProp(field, 'htmlName', getElementAttrValue(el, 'name')); - addProp(field, 'htmlClass', getElementAttrValue(el, 'class')); - addProp(field, 'tabindex', getElementAttrValue(el, 'tabindex')); - addProp(field, 'title', getElementAttrValue(el, 'title')); - - // START MODIFICATION - addProp(field, 'userEdited', !!el.dataset['com.browser.browser.userEdited']); - - var elTagName = el.tagName.toLowerCase(); - addProp(field, 'tagName', elTagName); - - if (elTagName === 'span') { - return field; - } - // END MODIFICATION - - if ('hidden' != toLowerString(el.type)) { - addProp(field, 'label-tag', getLabelTag(el)); - addProp(field, 'label-data', getElementAttrValue(el, 'data-label')); - addProp(field, 'label-aria', getElementAttrValue(el, 'aria-label')); - addProp(field, 'label-top', getLabelTop(el)); - var labelArr = []; - for (var sib = el; sib && sib.nextSibling;) { - sib = sib.nextSibling; - if (isKnownTag(sib)) { - break; - } - checkNodeType(labelArr, sib); - } - addProp(field, 'label-right', labelArr.join('')); - labelArr = []; - shiftForLeftLabel(el, labelArr); - labelArr = labelArr.reverse().join(''); - addProp(field, 'label-left', labelArr); - addProp(field, 'placeholder', getElementAttrValue(el, 'placeholder')); - } - - addProp(field, 'rel', getElementAttrValue(el, 'rel')); - addProp(field, 'type', toLowerString(getElementAttrValue(el, 'type'))); - addProp(field, 'value', getElementValue(el)); - addProp(field, 'checked', el.checked, false); - addProp(field, 'autoCompleteType', el.getAttribute('x-autocompletetype') || el.getAttribute('autocompletetype') || el.getAttribute('autocomplete'), 'off'); - addProp(field, 'disabled', el.disabled); - addProp(field, 'readonly', el.b || el.readOnly); - addProp(field, 'selectInfo', getSelectElementOptions(el)); - addProp(field, 'aria-hidden', 'true' == el.getAttribute('aria-hidden'), false); - addProp(field, 'aria-disabled', 'true' == el.getAttribute('aria-disabled'), false); - addProp(field, 'aria-haspopup', 'true' == el.getAttribute('aria-haspopup'), false); - addProp(field, 'data-unmasked', el.dataset.unmasked); - addProp(field, 'data-stripe', getElementAttrValue(el, 'data-stripe')); - addProp(field, 'onepasswordFieldType', el.dataset.onepasswordFieldType || el.type); - addProp(field, 'onepasswordDesignation', el.dataset.onepasswordDesignation); - addProp(field, 'onepasswordSignInUrl', el.dataset.onepasswordSignInUrl); - addProp(field, 'onepasswordSectionTitle', el.dataset.onepasswordSectionTitle); - addProp(field, 'onepasswordSectionFieldKind', el.dataset.onepasswordSectionFieldKind); - addProp(field, 'onepasswordSectionFieldTitle', el.dataset.onepasswordSectionFieldTitle); - addProp(field, 'onepasswordSectionFieldValue', el.dataset.onepasswordSectionFieldValue); - - if (el.form) { - field.form = getElementAttrValue(el.form, 'opid'); - } - - // START MODIFICATION - //addProp(field, 'fakeTested', checkIfFakeTested(field, el), false); - // END MODIFICATION - - return field; - }); - - // test form fields - theFields.filter(function (f) { - return f.fakeTested; - }).forEach(function (f) { - var el = theDoc.elementsByOPID[f.opid]; - el.getBoundingClientRect(); - - var originalValue = el.value; - // click it - !el || el && 'function' !== typeof el.click || el.click(); - focusElement(el, false); - - el.dispatchEvent(doEventOnElement(el, 'keydown')); - el.dispatchEvent(doEventOnElement(el, 'keypress')); - el.dispatchEvent(doEventOnElement(el, 'keyup')); - - el.value !== originalValue && (el.value = originalValue); - - el.click && el.click(); - f.postFakeTestVisible = isElementVisible(el); - f.postFakeTestViewable = isElementViewable(el); - f.postFakeTestType = el.type; - - var elValue = el.value; - - var event1 = el.ownerDocument.createEvent('HTMLEvents'), - event2 = el.ownerDocument.createEvent('HTMLEvents'); - el.dispatchEvent(doEventOnElement(el, 'keydown')); - el.dispatchEvent(doEventOnElement(el, 'keypress')); - el.dispatchEvent(doEventOnElement(el, 'keyup')); - event2.initEvent('input', true, true); - el.dispatchEvent(event2); - event1.initEvent('change', true, true); - el.dispatchEvent(event1); - - el.blur(); - el.value !== elValue && (el.value = elValue); - }); - - // build out the page details object. this is the final result - var pageDetails = { - documentUUID: oneShotId, - title: theDoc.title, - url: theView.location.href, - documentUrl: theDoc.location.href, - tabUrl: theView.location.href, - forms: function (forms) { - var formObj = {}; - forms.forEach(function (f) { - formObj[f.opid] = f; - }); - return formObj; - }(theForms), - fields: theFields, - collectedTimestamp: new Date().getTime() - }; - - // get proper page title. maybe they are using the special meta tag? - var theTitle = document.querySelector('[data-onepassword-title]') - if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) { - pageDetails.displayTitle = theTitle.dataset.onepasswordTitle; - } - - return pageDetails; - } - - document.elementForOPID = getElementForOPID; - - function doEventOnElement(kedol, fonor) { - var quebo; - isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'), - quebo.initEvent(fonor, true, false), quebo.charCode = 0, quebo.keyCode = 0, quebo.which = 0, - quebo.srcElement = kedol, quebo.target = kedol); - return quebo; - } - - // clean up the text - function cleanText(s) { - var sVal = null; - s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null); - return sVal; - } - - // check the node type and adjust the array accordingly - function checkNodeType(arr, el) { - var theText = ''; - 3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText); - (theText = cleanText(theText)) && arr.push(theText); - } - - function isKnownTag(el) { - if (el && void 0 !== el) { - var tags = 'select option input form textarea button table iframe body head script'.split(' '); - - if (el) { - var elTag = el ? (el.tagName || '').toLowerCase() : ''; - return tags.constructor == Array ? 0 <= tags.indexOf(elTag) : elTag === tags; - } - else { - return false; - } - } - else { - return true; - } - } - - function shiftForLeftLabel(el, arr, steps) { - var sib; - for (steps || (steps = 0); el && el.previousSibling;) { - el = el.previousSibling; - if (isKnownTag(el)) { - return; - } - - checkNodeType(arr, el); - } - if (el && 0 === arr.length) { - for (sib = null; !sib;) { - el = el.parentElement || el.parentNode; - if (!el) { - return; - } - for (sib = el.previousSibling; sib && !isKnownTag(sib) && sib.lastChild;) { - sib = sib.lastChild; - } - } - - // base case and recurse - isKnownTag(sib) || (checkNodeType(arr, sib), 0 === arr.length && shiftForLeftLabel(sib, arr, steps + 1)); - } - } - - // is a dom element visible on screen? - function isElementVisible(el) { - var theEl = el; - el = (el = el.ownerDocument) ? el.defaultView : {}; - - // walk the dom tree - for (var elStyle; theEl && theEl !== document;) { - elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style; - if (!elStyle) { - return true; - } - - if ('none' === elStyle.display || 'hidden' == elStyle.visibility) { - return false; - } - - // walk up - theEl = theEl.parentNode; - } - - return theEl === document; - } - - // is a dom element "viewable" on screen? - function isElementViewable(el) { - var theDoc = el.ownerDocument.documentElement, - rect = el.getBoundingClientRect(), - docScrollWidth = theDoc.scrollWidth, - docScrollHeight = theDoc.scrollHeight, - leftOffset = rect.left - theDoc.clientLeft, - topOffset = rect.top - theDoc.clientTop, - theRect; - - if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) { - return false; - } - - var rects = el.getClientRects(); - if (0 === rects.length) { - return false; - } - - for (var i = 0; i < rects.length; i++) { - if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) { - return false; - } - } - - if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > docScrollHeight) { - return false; - } - - // walk the tree - for (var pointEl = el.ownerDocument.elementFromPoint(leftOffset + (rect.right > window.innerWidth ? (window.innerWidth - leftOffset) / 2 : rect.width / 2), topOffset + (rect.bottom > window.innerHeight ? (window.innerHeight - topOffset) / 2 : rect.height / 2)); pointEl && pointEl !== el && pointEl !== document;) { - if (pointEl.tagName && 'string' === typeof pointEl.tagName && 'label' === pointEl.tagName.toLowerCase() - && el.labels && 0 < el.labels.length) { - return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl); - } - - // walk up - pointEl = pointEl.parentNode; - } - - return pointEl === el; - } - - function getElementForOPID(opId) { - var theEl; - if (void 0 === opId || null === opId) { - return null; - } - - try { - var formEls = Array.prototype.slice.call(getFormElements(document)); - var filteredFormEls = formEls.filter(function (el) { - return el.opid == opId; - }); - - if (0 < filteredFormEls.length) { - theEl = filteredFormEls[0], 1 < filteredFormEls.length && console.warn('More than one element found with opid ' + opId); - } else { - var theIndex = parseInt(opId.split('__')[1], 10); - isNaN(theIndex) || (theEl = formEls[theIndex]); - } - } catch (e) { - console.error('An unexpected error occurred: ' + e); - } finally { - return theEl; - } - } - - // get all the form elements that we care about - function getFormElements(theDoc, limit) { - // START MODIFICATION - var els = []; - try { - var elsList = theDoc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="reset"])' + - ':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, ' + - 'span[data-bwautofill]'); - els = Array.prototype.slice.call(elsList); - } catch (e) { } - - if (!limit || els.length <= limit) { - return els; - } - - // non-checkboxes/radios have higher priority - var returnEls = []; - var unimportantEls = []; - for (var i = 0; i < els.length; i++) { - if (returnEls.length >= limit) { - break; - } - - var el = els[i]; - var type = el.type ? el.type.toLowerCase() : el.type; - if (type === 'checkbox' || type === 'radio') { - unimportantEls.push(el); - } - else { - returnEls.push(el); - } - } - - var unimportantElsToAdd = limit - returnEls.length; - if (unimportantElsToAdd > 0) { - returnEls = returnEls.concat(unimportantEls.slice(0, unimportantElsToAdd)); - } - - return returnEls; - // END MODIFICATION - } - - // focus the element and optionally restore its original value - function focusElement(el, setVal) { - if (setVal) { - var initialValue = el.value; - el.focus(); - - if (el.value !== initialValue) { - el.value = initialValue; - } - } else { - el.focus(); - } - } - - return JSON.stringify(getPageDetails(document, 'oneshotUUID')); - } - - function fill(document, fillScript, undefined) { - var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; - - var markTheFilling = true, - animateTheFilling = true; - - // Check if URL is not secure when the original saved one was - function urlNotSecure(savedURL) { - var passwordInputs = null; - if (!savedURL) { - return false; - } - - return 0 === savedURL.indexOf('https://') && 'http:' === document.location.protocol && (passwordInputs = document.querySelectorAll('input[type=password]'), - 0 < passwordInputs.length && (confirmResult = confirm('Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'), - 0 == confirmResult)) ? true : false; - } - - function doFill(fillScript) { - var fillScriptOps, - theOpIds = [], - fillScriptProperties = fillScript.properties, - operationDelayMs = 1, - doOperation, - operationsToDo = []; - - fillScriptProperties && - fillScriptProperties.delay_between_operations && - (operationDelayMs = fillScriptProperties.delay_between_operations); - - if (urlNotSecure(fillScript.savedURL)) { - return; - } - - doOperation = function (ops, theOperation) { - var op = ops[0]; - if (void 0 === op) { - theOperation(); - } else { - // should we delay? - if ('delay' === op.operation || 'delay' === op[0]) { - operationDelayMs = op.parameters ? op.parameters[0] : op[1]; - } else { - if (op = normalizeOp(op)) { - for (var opIndex = 0; opIndex < op.length; opIndex++) { - -1 === operationsToDo.indexOf(op[opIndex]) && operationsToDo.push(op[opIndex]); - } - } - theOpIds = theOpIds.concat(operationsToDo.map(function (operationToDo) { - return operationToDo && operationToDo.hasOwnProperty('opid') ? operationToDo.opid : null; - })); - } - setTimeout(function () { - doOperation(ops.slice(1), theOperation); - }, operationDelayMs); - } - }; - - if (fillScriptOps = fillScript.options) { - fillScriptOps.hasOwnProperty('animate') && (animateTheFilling = fillScriptOps.animate), - fillScriptOps.hasOwnProperty('markFilling') && (markTheFilling = fillScriptOps.markFilling); - } - - // don't mark a password filling - fillScript.itemType && 'fillPassword' === fillScript.itemType && (markTheFilling = false); - - if (!fillScript.hasOwnProperty('script')) { - return; - } - - // custom fill script - - fillScriptOps = fillScript.script; - doOperation(fillScriptOps, function () { - // Done now - // Do we have anything to autosubmit? - if (fillScript.hasOwnProperty('autosubmit') && 'function' == typeof autosubmit) { - fillScript.itemType && 'fillLogin' !== fillScript.itemType || (0 < operationsToDo.length ? setTimeout(function () { - autosubmit(fillScript.autosubmit, fillScriptProperties.allow_clicky_autosubmit, operationsToDo); - }, AUTOSUBMIT_DELAY) : DEBUG_AUTOSUBMIT && console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ', operationsToDo)) - } - - // handle protectedGlobalPage - if ('object' == typeof protectedGlobalPage) { - protectedGlobalPage.b('fillItemResults', { - documentUUID: documentUUID, - fillContextIdentifier: fillScript.fillContextIdentifier, - usedOpids: theOpIds - }, function () { - fillingItemType = null; - }) - } - }); - } - - // fill for reference - var thisFill = { - fill_by_opid: doFillByOpId, - fill_by_query: doFillByQuery, - click_on_opid: doClickByOpId, - click_on_query: doClickByQuery, - touch_all_fields: touchAllFields, - simple_set_value_by_query: doSimpleSetByQuery, - focus_by_opid: doFocusByOpId, - delay: null - }; - - // normalize the op versus the reference - function normalizeOp(op) { - var thisOperation; - if (op.hasOwnProperty('operation') && op.hasOwnProperty('parameters')) { - thisOperation = op.operation, op = op.parameters; - } else { - if ('[object Array]' === Object.prototype.toString.call(op)) { - thisOperation = op[0], - op = op.splice(1); - } else { - return null; - } - } - return thisFill.hasOwnProperty(thisOperation) ? thisFill[thisOperation].apply(this, op) : null; - } - - // do a fill by opid operation - function doFillByOpId(opId, op) { - var el = getElementByOpId(opId); - return el ? (fillTheElement(el, op), [el]) : null; - } - - // do a fill by query operation - function doFillByQuery(query, op) { - var elements = selectAllFromDoc(query); - return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) { - fillTheElement(el, op); - return el; - }, this); - } - - // do a simple set value by query - function doSimpleSetByQuery(query, valueToSet) { - var elements = selectAllFromDoc(query), - arr = []; - Array.prototype.forEach.call(Array.prototype.slice.call(elements), function (el) { - el.disabled || el.a || el.readOnly || void 0 === el.value || (el.value = valueToSet, arr.push(el)); - }); - return arr; - } - - // focus by opid - function doFocusByOpId(opId) { - var el = getElementByOpId(opId) - if (el) { - 'function' === typeof el.click && el.click(), - 'function' === typeof el.focus && doFocusElement(el, true); - } - - return null; - } - - // do a click by opid operation - function doClickByOpId(opId) { - var el = getElementByOpId(opId); - return el ? clickElement(el) ? [el] : null : null; - } - - // do a click by query operation - function doClickByQuery(query) { - query = selectAllFromDoc(query); - return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) { - clickElement(el); - 'function' === typeof el.click && el.click(); - 'function' === typeof el.focus && doFocusElement(el, true); - return [el]; - }, this); - } - - var checkRadioTrueOps = { - 'true': true, - y: true, - 1: true, - yes: true, - '✓': true - }, - styleTimeout = 200; - - // fill an element - function fillTheElement(el, op) { - var shouldCheck; - if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) { - switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true), - el.type ? el.type.toLowerCase() : null) { - case 'checkbox': - shouldCheck = op && 1 <= op.length && checkRadioTrueOps.hasOwnProperty(op.toLowerCase()) && true === checkRadioTrueOps[op.toLowerCase()]; - el.checked === shouldCheck || doAllFillOperations(el, function (theEl) { - theEl.checked = shouldCheck; - }); - break; - case 'radio': - true === checkRadioTrueOps[op.toLowerCase()] && el.click(); - break; - default: - el.value == op || doAllFillOperations(el, function (theEl) { - // START MODIFICATION - if (!theEl.type && theEl.tagName.toLowerCase() === 'span') { - theEl.innerText = op; - return; - } - // END MODIFICATION - theEl.value = op; - }); - } - } - } - - // do all the full operations needed - function doAllFillOperations(el, afterValSetFunc) { - setValueForElement(el); - afterValSetFunc(el); - setValueForElementByEvent(el); - - // START MODIFICATION - if (canSeeElementToStyle(el)) { - el.classList.add('com-bitwarden-browser-animated-fill'); - setTimeout(function () { - if (el) { - el.classList.remove('com-bitwarden-browser-animated-fill'); - } - }, styleTimeout); - } - // END MODIFICATION - } - - document.elementForOPID = getElementByOpId; - - // normalize the event based on API support - function normalizeEvent(el, eventName) { - var ev; - if ('KeyboardEvent' in window) { - ev = new window.KeyboardEvent(eventName, { - bubbles: true, - cancelable: false, - }); - } - else { - ev = el.ownerDocument.createEvent('Events'); - ev.initEvent(eventName, true, false); - ev.charCode = 0; - ev.keyCode = 0; - ev.which = 0; - ev.srcElement = el; - ev.target = el; - } - - return ev; - } - - // set value of the given element - function setValueForElement(el) { - var valueToSet = el.value; - clickElement(el); - doFocusElement(el, false); - el.dispatchEvent(normalizeEvent(el, 'keydown')); - el.dispatchEvent(normalizeEvent(el, 'keypress')); - el.dispatchEvent(normalizeEvent(el, 'keyup')); - el.value !== valueToSet && (el.value = valueToSet); - } - - // set value of the given element by using events - function setValueForElementByEvent(el) { - var valueToSet = el.value, - ev1 = el.ownerDocument.createEvent('HTMLEvents'), - ev2 = el.ownerDocument.createEvent('HTMLEvents'); - - el.dispatchEvent(normalizeEvent(el, 'keydown')); - el.dispatchEvent(normalizeEvent(el, 'keypress')); - el.dispatchEvent(normalizeEvent(el, 'keyup')); - ev2.initEvent('input', true, true); - el.dispatchEvent(ev2); - ev1.initEvent('change', true, true); - el.dispatchEvent(ev1); - el.blur(); - el.value !== valueToSet && (el.value = valueToSet); - } - - // click on an element - function clickElement(el) { - if (!el || el && 'function' !== typeof el.click) { - return false; - } - el.click(); - return true; - } - - // get all fields we care about - function getAllFields() { - var r = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i'); - return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) { - return el.value && r.test(el.value); - }, this); - } - - // touch all the fields - function touchAllFields() { - getAllFields().forEach(function (el) { - setValueForElement(el); - el.click && el.click(); - setValueForElementByEvent(el); - }); - } - - // can we see the element to apply some styling? - function canSeeElementToStyle(el) { - var currentEl; - if (currentEl = animateTheFilling) { - a: { - currentEl = el; - for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) { - theStyle = owner.getComputedStyle ? owner.getComputedStyle(currentEl, null) : currentEl.style; - if (!theStyle) { - currentEl = true; - break a; - } - if ('none' === theStyle.display || 'hidden' == theStyle.visibility) { - currentEl = false; - break a; - } - currentEl = currentEl.parentNode; - } - currentEl = currentEl === document; - } - } - // START MODIFICATION - if (el && !el.type && el.tagName.toLowerCase() === 'span') { - return true; - } - // END MODIFICATION - return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false; - } - - // find the element for this operation - function getElementByOpId(theOpId) { - var theElement; - if (void 0 === theOpId || null === theOpId) { - return null; - } - try { - // START MODIFICATION - var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, ' + - 'span[data-bwautofill]')); - // END MODIFICATION - var filteredElements = elements.filter(function (o) { - return o.opid == theOpId; - }); - if (0 < filteredElements.length) { - theElement = filteredElements[0], - 1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId); - } else { - var elIndex = parseInt(theOpId.split('__')[1], 10); - isNaN(elIndex) || (theElement = elements[elIndex]); - } - } catch (e) { - console.error('An unexpected error occurred: ' + e); - } finally { - return theElement; - } - } - - // helper for doc.querySelectorAll - function selectAllFromDoc(theSelector) { - var d = document, elements = []; - try { - elements = d.querySelectorAll(theSelector); - } catch (e) { } - return elements; - } - - // focus an element and optionally re-set its value after focusing - function doFocusElement(el, setValue) { - if (setValue) { - var existingValue = el.value; - el.focus(); - el.value !== existingValue && (el.value = existingValue); - } else { - el.focus(); - } - } - - doFill(fillScript); - - return JSON.stringify({ - success: true - }); - } - - /* - End 1Password Extension - */ - - chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { - if (msg.command === 'collectPageDetails') { - var pageDetails = collect(document); - var pageDetailsObj = JSON.parse(pageDetails); - chrome.runtime.sendMessage({ - command: 'collectPageDetailsResponse', - tab: msg.tab, - details: pageDetailsObj, - sender: msg.sender - }); - sendResponse(); - return true; - } - else if (msg.command === 'fillForm') { - fill(document, msg.fillScript); - sendResponse(); - return true; - } - }); + /* + 1Password Extension + + Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. + Copyright (c) 2014 AgileBits. All rights reserved. + + ================================================================================ + + Copyright (c) 2014 AgileBits Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + /* + MODIFICATIONS FROM ORIGINAL + + 1. Populate isFirefox + 2. Remove isChrome and isSafari since they are not used. + 3. Unminify and format to meet Mozilla review requirements. + 4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned. + 5. Remove fakeTested prop. + 6. Rename com.agilebits.* stuff to com.bitwarden.* + 7. Remove "some useful globals" on window + 8. Add ability to autofill span[data-bwautofill] elements + */ + + function collect(document, undefined) { + // START MODIFICATION + var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; + // END MODIFICATION + + document.elementsByOPID = {}; + document.addEventListener('input', function (inputevent) { + inputevent.a !== false && + inputevent.target.tagName.toLowerCase() === 'input' && + (inputevent.target.dataset['com.bitwarden.browser.userEdited'] = 'yes'); + }, true); + + function getPageDetails(theDoc, oneShotId) { + // start helpers + + // get the value of a dom element's attribute + function getElementAttrValue(el, attrName) { + var attrVal = el[attrName]; + if ('string' == typeof attrVal) { + return attrVal; + } + attrVal = el.getAttribute(attrName); + return 'string' == typeof attrVal ? attrVal : null; + } + + // has the element been fake tested? + function checkIfFakeTested(field, el) { + if (-1 === ['text', 'password'].indexOf(el.type.toLowerCase()) || + !(passwordRegEx.test(field.value) || + passwordRegEx.test(field.htmlID) || passwordRegEx.test(field.htmlName) || + passwordRegEx.test(field.placeholder) || passwordRegEx.test(field['label-tag']) || + passwordRegEx.test(field['label-data']) || passwordRegEx.test(field['label-aria']))) { + return false; + } + + if (!field.visible) { + return true; + } + + if ('password' == el.type.toLowerCase()) { + return false; + } + + var elType = el.type; + focusElement(el, true); + return elType !== el.type; + } + + // get the value of a dom element + function getElementValue(el) { + switch (toLowerString(el.type)) { + case 'checkbox': + return el.checked ? '✓' : ''; + + case 'hidden': + el = el.value; + if (!el || 'number' != typeof el.length) { + return ''; + } + 254 < el.length && (el = el.substr(0, 254) + '...SNIPPED'); + return el; + + default: + // START MODIFICATION + if (!el.type && el.tagName.toLowerCase() === 'span') { + return el.innerText; + } + // END MODIFICATION + return el.value; + } + } + + // get all the options for a "select" element + function getSelectElementOptions(el) { + if (!el.options) { + return null; + } + + var options = Array.prototype.slice.call(el.options).map(function (option) { + var optionText = option.text ? + toLowerString(option.text).replace(/\\s/gm, '').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/gm, '') : + null; + + return [optionText ? optionText : null, option.value]; + }) + + return { + options: options + }; + } + + // get the top label + function getLabelTop(el) { + var parent; + for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName);) { + el = el.parentElement || el.parentNode; + } + + if (!el || void 0 === el) { + return null; + } + + parent = el.parentElement || el.parentNode; + if ('tr' != parent.tagName.toLowerCase()) { + return null; + } + + parent = parent.previousElementSibling; + if (!parent || 'tr' != (parent.tagName + '').toLowerCase() || + parent.cells && el.cellIndex >= parent.cells.length) { + return null; + } + + el = parent.cells[el.cellIndex]; + var elText = el.textContent || el.innerText; + return elText = cleanText(elText); + } + + // get all the tags for a given label + function getLabelTag(el) { + var docLabel, + theLabels = []; + + if (el.labels && el.labels.length && 0 < el.labels.length) { + theLabels = Array.prototype.slice.call(el.labels); + } else { + if (el.id) { + theLabels = theLabels.concat(Array.prototype.slice.call( + queryDoc(theDoc, 'label[for=' + JSON.stringify(el.id) + ']'))); + } + + if (el.name) { + docLabel = queryDoc(theDoc, 'label[for=' + JSON.stringify(el.name) + ']'); + + for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) { + if (-1 === theLabels.indexOf(docLabel[labelIndex])) { + theLabels.push(docLabel[labelIndex]) + } + } + } + + for (var theEl = el; theEl && theEl != theDoc; theEl = theEl.parentNode) { + if ('label' === toLowerString(theEl.tagName) && -1 === theLabels.indexOf(theEl)) { + theLabels.push(theEl); + } + } + } + + if (0 === theLabels.length) { + theEl = el.parentNode; + if ('dd' === theEl.tagName.toLowerCase() && null !== theEl.previousElementSibling + && 'dt' === theEl.previousElementSibling.tagName.toLowerCase()) { + theLabels.push(theEl.previousElementSibling); + } + } + + if (0 > theLabels.length) { + return null; + } + + return theLabels.map(function (l) { + return (l.textContent || l.innerText) + .replace(/^\\s+/, '').replace(/\\s+$/, '').replace('\\n', '').replace(/\\s{2,}/, ' '); + }).join(''); + } + + // add property and value to the object if there is a value + function addProp(obj, prop, val, d) { + if (0 !== d && d === val || null === val || void 0 === val) { + return; + } + + obj[prop] = val; + } + + // lowercase helper + function toLowerString(s) { + return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase(); + } + + // query the document helper + function queryDoc(doc, query) { + var els = []; + try { + els = doc.querySelectorAll(query); + } catch (e) { } + return els; + } + + // end helpers + + var theView = theDoc.defaultView ? theDoc.defaultView : window, + passwordRegEx = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i'); + + // get all the docs + var theForms = Array.prototype.slice.call(queryDoc(theDoc, 'form')).map(function (formEl, elIndex) { + var op = {}, + formOpId = '__form__' + elIndex; + + formEl.opid = formOpId; + op.opid = formOpId; + addProp(op, 'htmlName', getElementAttrValue(formEl, 'name')); + addProp(op, 'htmlID', getElementAttrValue(formEl, 'id')); + formOpId = getElementAttrValue(formEl, 'action'); + formOpId = new URL(formOpId, window.location.href); + addProp(op, 'htmlAction', formOpId ? formOpId.href : null); + addProp(op, 'htmlMethod', getElementAttrValue(formEl, 'method')); + + return op; + }); + + // get all the form fields + var theFields = Array.prototype.slice.call(getFormElements(theDoc, 50)).map(function (el, elIndex) { + var field = {}, + opId = '__' + elIndex, + elMaxLen = -1 == el.maxLength ? 999 : el.maxLength; + + if (!elMaxLen || 'number' === typeof elMaxLen && isNaN(elMaxLen)) { + elMaxLen = 999; + } + + theDoc.elementsByOPID[opId] = el; + el.opid = opId; + field.opid = opId; + field.elementNumber = elIndex; + addProp(field, 'maxLength', Math.min(elMaxLen, 999), 999); + field.visible = isElementVisible(el); + field.viewable = isElementViewable(el); + addProp(field, 'htmlID', getElementAttrValue(el, 'id')); + addProp(field, 'htmlName', getElementAttrValue(el, 'name')); + addProp(field, 'htmlClass', getElementAttrValue(el, 'class')); + addProp(field, 'tabindex', getElementAttrValue(el, 'tabindex')); + addProp(field, 'title', getElementAttrValue(el, 'title')); + + // START MODIFICATION + addProp(field, 'userEdited', !!el.dataset['com.browser.browser.userEdited']); + + var elTagName = el.tagName.toLowerCase(); + addProp(field, 'tagName', elTagName); + + if (elTagName === 'span') { + return field; + } + // END MODIFICATION + + if ('hidden' != toLowerString(el.type)) { + addProp(field, 'label-tag', getLabelTag(el)); + addProp(field, 'label-data', getElementAttrValue(el, 'data-label')); + addProp(field, 'label-aria', getElementAttrValue(el, 'aria-label')); + addProp(field, 'label-top', getLabelTop(el)); + var labelArr = []; + for (var sib = el; sib && sib.nextSibling;) { + sib = sib.nextSibling; + if (isKnownTag(sib)) { + break; + } + checkNodeType(labelArr, sib); + } + addProp(field, 'label-right', labelArr.join('')); + labelArr = []; + shiftForLeftLabel(el, labelArr); + labelArr = labelArr.reverse().join(''); + addProp(field, 'label-left', labelArr); + addProp(field, 'placeholder', getElementAttrValue(el, 'placeholder')); + } + + addProp(field, 'rel', getElementAttrValue(el, 'rel')); + addProp(field, 'type', toLowerString(getElementAttrValue(el, 'type'))); + addProp(field, 'value', getElementValue(el)); + addProp(field, 'checked', el.checked, false); + addProp(field, 'autoCompleteType', el.getAttribute('x-autocompletetype') || el.getAttribute('autocompletetype') || el.getAttribute('autocomplete'), 'off'); + addProp(field, 'disabled', el.disabled); + addProp(field, 'readonly', el.b || el.readOnly); + addProp(field, 'selectInfo', getSelectElementOptions(el)); + addProp(field, 'aria-hidden', 'true' == el.getAttribute('aria-hidden'), false); + addProp(field, 'aria-disabled', 'true' == el.getAttribute('aria-disabled'), false); + addProp(field, 'aria-haspopup', 'true' == el.getAttribute('aria-haspopup'), false); + addProp(field, 'data-unmasked', el.dataset.unmasked); + addProp(field, 'data-stripe', getElementAttrValue(el, 'data-stripe')); + addProp(field, 'onepasswordFieldType', el.dataset.onepasswordFieldType || el.type); + addProp(field, 'onepasswordDesignation', el.dataset.onepasswordDesignation); + addProp(field, 'onepasswordSignInUrl', el.dataset.onepasswordSignInUrl); + addProp(field, 'onepasswordSectionTitle', el.dataset.onepasswordSectionTitle); + addProp(field, 'onepasswordSectionFieldKind', el.dataset.onepasswordSectionFieldKind); + addProp(field, 'onepasswordSectionFieldTitle', el.dataset.onepasswordSectionFieldTitle); + addProp(field, 'onepasswordSectionFieldValue', el.dataset.onepasswordSectionFieldValue); + + if (el.form) { + field.form = getElementAttrValue(el.form, 'opid'); + } + + // START MODIFICATION + //addProp(field, 'fakeTested', checkIfFakeTested(field, el), false); + // END MODIFICATION + + return field; + }); + + // test form fields + theFields.filter(function (f) { + return f.fakeTested; + }).forEach(function (f) { + var el = theDoc.elementsByOPID[f.opid]; + el.getBoundingClientRect(); + + var originalValue = el.value; + // click it + !el || el && 'function' !== typeof el.click || el.click(); + focusElement(el, false); + + el.dispatchEvent(doEventOnElement(el, 'keydown')); + el.dispatchEvent(doEventOnElement(el, 'keypress')); + el.dispatchEvent(doEventOnElement(el, 'keyup')); + + el.value !== originalValue && (el.value = originalValue); + + el.click && el.click(); + f.postFakeTestVisible = isElementVisible(el); + f.postFakeTestViewable = isElementViewable(el); + f.postFakeTestType = el.type; + + var elValue = el.value; + + var event1 = el.ownerDocument.createEvent('HTMLEvents'), + event2 = el.ownerDocument.createEvent('HTMLEvents'); + el.dispatchEvent(doEventOnElement(el, 'keydown')); + el.dispatchEvent(doEventOnElement(el, 'keypress')); + el.dispatchEvent(doEventOnElement(el, 'keyup')); + event2.initEvent('input', true, true); + el.dispatchEvent(event2); + event1.initEvent('change', true, true); + el.dispatchEvent(event1); + + el.blur(); + el.value !== elValue && (el.value = elValue); + }); + + // build out the page details object. this is the final result + var pageDetails = { + documentUUID: oneShotId, + title: theDoc.title, + url: theView.location.href, + documentUrl: theDoc.location.href, + tabUrl: theView.location.href, + forms: function (forms) { + var formObj = {}; + forms.forEach(function (f) { + formObj[f.opid] = f; + }); + return formObj; + }(theForms), + fields: theFields, + collectedTimestamp: new Date().getTime() + }; + + // get proper page title. maybe they are using the special meta tag? + var theTitle = document.querySelector('[data-onepassword-title]') + if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) { + pageDetails.displayTitle = theTitle.dataset.onepasswordTitle; + } + + return pageDetails; + } + + document.elementForOPID = getElementForOPID; + + function doEventOnElement(kedol, fonor) { + var quebo; + isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'), + quebo.initEvent(fonor, true, false), quebo.charCode = 0, quebo.keyCode = 0, quebo.which = 0, + quebo.srcElement = kedol, quebo.target = kedol); + return quebo; + } + + // clean up the text + function cleanText(s) { + var sVal = null; + s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null); + return sVal; + } + + // check the node type and adjust the array accordingly + function checkNodeType(arr, el) { + var theText = ''; + 3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText); + (theText = cleanText(theText)) && arr.push(theText); + } + + function isKnownTag(el) { + if (el && void 0 !== el) { + var tags = 'select option input form textarea button table iframe body head script'.split(' '); + + if (el) { + var elTag = el ? (el.tagName || '').toLowerCase() : ''; + return tags.constructor == Array ? 0 <= tags.indexOf(elTag) : elTag === tags; + } + else { + return false; + } + } + else { + return true; + } + } + + function shiftForLeftLabel(el, arr, steps) { + var sib; + for (steps || (steps = 0); el && el.previousSibling;) { + el = el.previousSibling; + if (isKnownTag(el)) { + return; + } + + checkNodeType(arr, el); + } + if (el && 0 === arr.length) { + for (sib = null; !sib;) { + el = el.parentElement || el.parentNode; + if (!el) { + return; + } + for (sib = el.previousSibling; sib && !isKnownTag(sib) && sib.lastChild;) { + sib = sib.lastChild; + } + } + + // base case and recurse + isKnownTag(sib) || (checkNodeType(arr, sib), 0 === arr.length && shiftForLeftLabel(sib, arr, steps + 1)); + } + } + + // is a dom element visible on screen? + function isElementVisible(el) { + var theEl = el; + el = (el = el.ownerDocument) ? el.defaultView : {}; + + // walk the dom tree + for (var elStyle; theEl && theEl !== document;) { + elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style; + if (!elStyle) { + return true; + } + + if ('none' === elStyle.display || 'hidden' == elStyle.visibility) { + return false; + } + + // walk up + theEl = theEl.parentNode; + } + + return theEl === document; + } + + // is a dom element "viewable" on screen? + function isElementViewable(el) { + var theDoc = el.ownerDocument.documentElement, + rect = el.getBoundingClientRect(), + docScrollWidth = theDoc.scrollWidth, + docScrollHeight = theDoc.scrollHeight, + leftOffset = rect.left - theDoc.clientLeft, + topOffset = rect.top - theDoc.clientTop, + theRect; + + if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) { + return false; + } + + var rects = el.getClientRects(); + if (0 === rects.length) { + return false; + } + + for (var i = 0; i < rects.length; i++) { + if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) { + return false; + } + } + + if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > docScrollHeight) { + return false; + } + + // walk the tree + for (var pointEl = el.ownerDocument.elementFromPoint(leftOffset + (rect.right > window.innerWidth ? (window.innerWidth - leftOffset) / 2 : rect.width / 2), topOffset + (rect.bottom > window.innerHeight ? (window.innerHeight - topOffset) / 2 : rect.height / 2)); pointEl && pointEl !== el && pointEl !== document;) { + if (pointEl.tagName && 'string' === typeof pointEl.tagName && 'label' === pointEl.tagName.toLowerCase() + && el.labels && 0 < el.labels.length) { + return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl); + } + + // walk up + pointEl = pointEl.parentNode; + } + + return pointEl === el; + } + + function getElementForOPID(opId) { + var theEl; + if (void 0 === opId || null === opId) { + return null; + } + + try { + var formEls = Array.prototype.slice.call(getFormElements(document)); + var filteredFormEls = formEls.filter(function (el) { + return el.opid == opId; + }); + + if (0 < filteredFormEls.length) { + theEl = filteredFormEls[0], 1 < filteredFormEls.length && console.warn('More than one element found with opid ' + opId); + } else { + var theIndex = parseInt(opId.split('__')[1], 10); + isNaN(theIndex) || (theEl = formEls[theIndex]); + } + } catch (e) { + console.error('An unexpected error occurred: ' + e); + } finally { + return theEl; + } + } + + // get all the form elements that we care about + function getFormElements(theDoc, limit) { + // START MODIFICATION + var els = []; + try { + var elsList = theDoc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="reset"])' + + ':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, ' + + 'span[data-bwautofill]'); + els = Array.prototype.slice.call(elsList); + } catch (e) { } + + if (!limit || els.length <= limit) { + return els; + } + + // non-checkboxes/radios have higher priority + var returnEls = []; + var unimportantEls = []; + for (var i = 0; i < els.length; i++) { + if (returnEls.length >= limit) { + break; + } + + var el = els[i]; + var type = el.type ? el.type.toLowerCase() : el.type; + if (type === 'checkbox' || type === 'radio') { + unimportantEls.push(el); + } + else { + returnEls.push(el); + } + } + + var unimportantElsToAdd = limit - returnEls.length; + if (unimportantElsToAdd > 0) { + returnEls = returnEls.concat(unimportantEls.slice(0, unimportantElsToAdd)); + } + + return returnEls; + // END MODIFICATION + } + + // focus the element and optionally restore its original value + function focusElement(el, setVal) { + if (setVal) { + var initialValue = el.value; + el.focus(); + + if (el.value !== initialValue) { + el.value = initialValue; + } + } else { + el.focus(); + } + } + + return JSON.stringify(getPageDetails(document, 'oneshotUUID')); + } + + function fill(document, fillScript, undefined) { + var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; + + var markTheFilling = true, + animateTheFilling = true; + + // Check if URL is not secure when the original saved one was + function urlNotSecure(savedURL) { + var passwordInputs = null; + if (!savedURL) { + return false; + } + + return 0 === savedURL.indexOf('https://') && 'http:' === document.location.protocol && (passwordInputs = document.querySelectorAll('input[type=password]'), + 0 < passwordInputs.length && (confirmResult = confirm('Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'), + 0 == confirmResult)) ? true : false; + } + + function doFill(fillScript) { + var fillScriptOps, + theOpIds = [], + fillScriptProperties = fillScript.properties, + operationDelayMs = 1, + doOperation, + operationsToDo = []; + + fillScriptProperties && + fillScriptProperties.delay_between_operations && + (operationDelayMs = fillScriptProperties.delay_between_operations); + + if (urlNotSecure(fillScript.savedURL)) { + return; + } + + doOperation = function (ops, theOperation) { + var op = ops[0]; + if (void 0 === op) { + theOperation(); + } else { + // should we delay? + if ('delay' === op.operation || 'delay' === op[0]) { + operationDelayMs = op.parameters ? op.parameters[0] : op[1]; + } else { + if (op = normalizeOp(op)) { + for (var opIndex = 0; opIndex < op.length; opIndex++) { + -1 === operationsToDo.indexOf(op[opIndex]) && operationsToDo.push(op[opIndex]); + } + } + theOpIds = theOpIds.concat(operationsToDo.map(function (operationToDo) { + return operationToDo && operationToDo.hasOwnProperty('opid') ? operationToDo.opid : null; + })); + } + setTimeout(function () { + doOperation(ops.slice(1), theOperation); + }, operationDelayMs); + } + }; + + if (fillScriptOps = fillScript.options) { + fillScriptOps.hasOwnProperty('animate') && (animateTheFilling = fillScriptOps.animate), + fillScriptOps.hasOwnProperty('markFilling') && (markTheFilling = fillScriptOps.markFilling); + } + + // don't mark a password filling + fillScript.itemType && 'fillPassword' === fillScript.itemType && (markTheFilling = false); + + if (!fillScript.hasOwnProperty('script')) { + return; + } + + // custom fill script + + fillScriptOps = fillScript.script; + doOperation(fillScriptOps, function () { + // Done now + // Do we have anything to autosubmit? + if (fillScript.hasOwnProperty('autosubmit') && 'function' == typeof autosubmit) { + fillScript.itemType && 'fillLogin' !== fillScript.itemType || (0 < operationsToDo.length ? setTimeout(function () { + autosubmit(fillScript.autosubmit, fillScriptProperties.allow_clicky_autosubmit, operationsToDo); + }, AUTOSUBMIT_DELAY) : DEBUG_AUTOSUBMIT && console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ', operationsToDo)) + } + + // handle protectedGlobalPage + if ('object' == typeof protectedGlobalPage) { + protectedGlobalPage.b('fillItemResults', { + documentUUID: documentUUID, + fillContextIdentifier: fillScript.fillContextIdentifier, + usedOpids: theOpIds + }, function () { + fillingItemType = null; + }) + } + }); + } + + // fill for reference + var thisFill = { + fill_by_opid: doFillByOpId, + fill_by_query: doFillByQuery, + click_on_opid: doClickByOpId, + click_on_query: doClickByQuery, + touch_all_fields: touchAllFields, + simple_set_value_by_query: doSimpleSetByQuery, + focus_by_opid: doFocusByOpId, + delay: null + }; + + // normalize the op versus the reference + function normalizeOp(op) { + var thisOperation; + if (op.hasOwnProperty('operation') && op.hasOwnProperty('parameters')) { + thisOperation = op.operation, op = op.parameters; + } else { + if ('[object Array]' === Object.prototype.toString.call(op)) { + thisOperation = op[0], + op = op.splice(1); + } else { + return null; + } + } + return thisFill.hasOwnProperty(thisOperation) ? thisFill[thisOperation].apply(this, op) : null; + } + + // do a fill by opid operation + function doFillByOpId(opId, op) { + var el = getElementByOpId(opId); + return el ? (fillTheElement(el, op), [el]) : null; + } + + // do a fill by query operation + function doFillByQuery(query, op) { + var elements = selectAllFromDoc(query); + return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) { + fillTheElement(el, op); + return el; + }, this); + } + + // do a simple set value by query + function doSimpleSetByQuery(query, valueToSet) { + var elements = selectAllFromDoc(query), + arr = []; + Array.prototype.forEach.call(Array.prototype.slice.call(elements), function (el) { + el.disabled || el.a || el.readOnly || void 0 === el.value || (el.value = valueToSet, arr.push(el)); + }); + return arr; + } + + // focus by opid + function doFocusByOpId(opId) { + var el = getElementByOpId(opId) + if (el) { + 'function' === typeof el.click && el.click(), + 'function' === typeof el.focus && doFocusElement(el, true); + } + + return null; + } + + // do a click by opid operation + function doClickByOpId(opId) { + var el = getElementByOpId(opId); + return el ? clickElement(el) ? [el] : null : null; + } + + // do a click by query operation + function doClickByQuery(query) { + query = selectAllFromDoc(query); + return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) { + clickElement(el); + 'function' === typeof el.click && el.click(); + 'function' === typeof el.focus && doFocusElement(el, true); + return [el]; + }, this); + } + + var checkRadioTrueOps = { + 'true': true, + y: true, + 1: true, + yes: true, + '✓': true + }, + styleTimeout = 200; + + // fill an element + function fillTheElement(el, op) { + var shouldCheck; + if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) { + switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true), + el.type ? el.type.toLowerCase() : null) { + case 'checkbox': + shouldCheck = op && 1 <= op.length && checkRadioTrueOps.hasOwnProperty(op.toLowerCase()) && true === checkRadioTrueOps[op.toLowerCase()]; + el.checked === shouldCheck || doAllFillOperations(el, function (theEl) { + theEl.checked = shouldCheck; + }); + break; + case 'radio': + true === checkRadioTrueOps[op.toLowerCase()] && el.click(); + break; + default: + el.value == op || doAllFillOperations(el, function (theEl) { + // START MODIFICATION + if (!theEl.type && theEl.tagName.toLowerCase() === 'span') { + theEl.innerText = op; + return; + } + // END MODIFICATION + theEl.value = op; + }); + } + } + } + + // do all the full operations needed + function doAllFillOperations(el, afterValSetFunc) { + setValueForElement(el); + afterValSetFunc(el); + setValueForElementByEvent(el); + + // START MODIFICATION + if (canSeeElementToStyle(el)) { + el.classList.add('com-bitwarden-browser-animated-fill'); + setTimeout(function () { + if (el) { + el.classList.remove('com-bitwarden-browser-animated-fill'); + } + }, styleTimeout); + } + // END MODIFICATION + } + + document.elementForOPID = getElementByOpId; + + // normalize the event based on API support + function normalizeEvent(el, eventName) { + var ev; + if ('KeyboardEvent' in window) { + ev = new window.KeyboardEvent(eventName, { + bubbles: true, + cancelable: false, + }); + } + else { + ev = el.ownerDocument.createEvent('Events'); + ev.initEvent(eventName, true, false); + ev.charCode = 0; + ev.keyCode = 0; + ev.which = 0; + ev.srcElement = el; + ev.target = el; + } + + return ev; + } + + // set value of the given element + function setValueForElement(el) { + var valueToSet = el.value; + clickElement(el); + doFocusElement(el, false); + el.dispatchEvent(normalizeEvent(el, 'keydown')); + el.dispatchEvent(normalizeEvent(el, 'keypress')); + el.dispatchEvent(normalizeEvent(el, 'keyup')); + el.value !== valueToSet && (el.value = valueToSet); + } + + // set value of the given element by using events + function setValueForElementByEvent(el) { + var valueToSet = el.value, + ev1 = el.ownerDocument.createEvent('HTMLEvents'), + ev2 = el.ownerDocument.createEvent('HTMLEvents'); + + el.dispatchEvent(normalizeEvent(el, 'keydown')); + el.dispatchEvent(normalizeEvent(el, 'keypress')); + el.dispatchEvent(normalizeEvent(el, 'keyup')); + ev2.initEvent('input', true, true); + el.dispatchEvent(ev2); + ev1.initEvent('change', true, true); + el.dispatchEvent(ev1); + el.blur(); + el.value !== valueToSet && (el.value = valueToSet); + } + + // click on an element + function clickElement(el) { + if (!el || el && 'function' !== typeof el.click) { + return false; + } + el.click(); + return true; + } + + // get all fields we care about + function getAllFields() { + var r = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i'); + return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) { + return el.value && r.test(el.value); + }, this); + } + + // touch all the fields + function touchAllFields() { + getAllFields().forEach(function (el) { + setValueForElement(el); + el.click && el.click(); + setValueForElementByEvent(el); + }); + } + + // can we see the element to apply some styling? + function canSeeElementToStyle(el) { + var currentEl; + if (currentEl = animateTheFilling) { + a: { + currentEl = el; + for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) { + theStyle = owner.getComputedStyle ? owner.getComputedStyle(currentEl, null) : currentEl.style; + if (!theStyle) { + currentEl = true; + break a; + } + if ('none' === theStyle.display || 'hidden' == theStyle.visibility) { + currentEl = false; + break a; + } + currentEl = currentEl.parentNode; + } + currentEl = currentEl === document; + } + } + // START MODIFICATION + if (el && !el.type && el.tagName.toLowerCase() === 'span') { + return true; + } + // END MODIFICATION + return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false; + } + + // find the element for this operation + function getElementByOpId(theOpId) { + var theElement; + if (void 0 === theOpId || null === theOpId) { + return null; + } + try { + // START MODIFICATION + var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, ' + + 'span[data-bwautofill]')); + // END MODIFICATION + var filteredElements = elements.filter(function (o) { + return o.opid == theOpId; + }); + if (0 < filteredElements.length) { + theElement = filteredElements[0], + 1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId); + } else { + var elIndex = parseInt(theOpId.split('__')[1], 10); + isNaN(elIndex) || (theElement = elements[elIndex]); + } + } catch (e) { + console.error('An unexpected error occurred: ' + e); + } finally { + return theElement; + } + } + + // helper for doc.querySelectorAll + function selectAllFromDoc(theSelector) { + var d = document, elements = []; + try { + elements = d.querySelectorAll(theSelector); + } catch (e) { } + return elements; + } + + // focus an element and optionally re-set its value after focusing + function doFocusElement(el, setValue) { + if (setValue) { + var existingValue = el.value; + el.focus(); + el.value !== existingValue && (el.value = existingValue); + } else { + el.focus(); + } + } + + doFill(fillScript); + + return JSON.stringify({ + success: true + }); + } + + /* + End 1Password Extension + */ + + chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { + if (msg.command === 'collectPageDetails') { + var pageDetails = collect(document); + var pageDetailsObj = JSON.parse(pageDetails); + chrome.runtime.sendMessage({ + command: 'collectPageDetailsResponse', + tab: msg.tab, + details: pageDetailsObj, + sender: msg.sender + }); + sendResponse(); + return true; + } + else if (msg.command === 'fillForm') { + fill(document, msg.fillScript); + sendResponse(); + return true; + } + }); })(); diff --git a/src/content/autofiller.ts b/src/content/autofiller.ts index 5e73c4a5c5..a4a66b647a 100644 --- a/src/content/autofiller.ts +++ b/src/content/autofiller.ts @@ -1,43 +1,43 @@ -document.addEventListener('DOMContentLoaded', event => { - let pageHref: string = null; - let filledThisHref = false; - let delayFillTimeout: number; +document.addEventListener("DOMContentLoaded", (event) => { + let pageHref: string = null; + let filledThisHref = false; + let delayFillTimeout: number; - const enabledKey = 'enableAutoFillOnPageLoad'; - chrome.storage.local.get(enabledKey, (obj: any) => { - if (obj != null && obj[enabledKey] === true) { - setInterval(() => doFillIfNeeded(), 500); - } - }); - chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { - if (msg.command === 'fillForm' && pageHref === msg.url) { - filledThisHref = true; - } - }); - - function doFillIfNeeded(force: boolean = false) { - if (force || pageHref !== window.location.href) { - if (!force) { - // Some websites are slow and rendering all page content. Try to fill again later - // if we haven't already. - filledThisHref = false; - if (delayFillTimeout != null) { - window.clearTimeout(delayFillTimeout); - } - delayFillTimeout = window.setTimeout(() => { - if (!filledThisHref) { - doFillIfNeeded(true); - } - }, 1500); - } - - pageHref = window.location.href; - const msg: any = { - command: 'bgCollectPageDetails', - sender: 'autofiller', - }; - - chrome.runtime.sendMessage(msg); - } + const enabledKey = "enableAutoFillOnPageLoad"; + chrome.storage.local.get(enabledKey, (obj: any) => { + if (obj != null && obj[enabledKey] === true) { + setInterval(() => doFillIfNeeded(), 500); } + }); + chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { + if (msg.command === "fillForm" && pageHref === msg.url) { + filledThisHref = true; + } + }); + + function doFillIfNeeded(force: boolean = false) { + if (force || pageHref !== window.location.href) { + if (!force) { + // Some websites are slow and rendering all page content. Try to fill again later + // if we haven't already. + filledThisHref = false; + if (delayFillTimeout != null) { + window.clearTimeout(delayFillTimeout); + } + delayFillTimeout = window.setTimeout(() => { + if (!filledThisHref) { + doFillIfNeeded(true); + } + }, 1500); + } + + pageHref = window.location.href; + const msg: any = { + command: "bgCollectPageDetails", + sender: "autofiller", + }; + + chrome.runtime.sendMessage(msg); + } + } }); diff --git a/src/content/contextMenuHandler.ts b/src/content/contextMenuHandler.ts index 5d6b1c0a0b..b6d0159df0 100644 --- a/src/content/contextMenuHandler.ts +++ b/src/content/contextMenuHandler.ts @@ -1,66 +1,66 @@ -const inputTags = ['input', 'textarea', 'select']; -const labelTags = ['label', 'span']; -const attributes = ['id', 'name', 'label-aria', 'placeholder']; -const invalidElement = chrome.i18n.getMessage('copyCustomFieldNameInvalidElement'); -const noUniqueIdentifier = chrome.i18n.getMessage('copyCustomFieldNameNotUnique'); +const inputTags = ["input", "textarea", "select"]; +const labelTags = ["label", "span"]; +const attributes = ["id", "name", "label-aria", "placeholder"]; +const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement"); +const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique"); let clickedEl: HTMLElement = null; // Find the best attribute to be used as the Name for an element in a custom field. function getClickedElementIdentifier() { - if (clickedEl == null) { - return invalidElement; - } + if (clickedEl == null) { + return invalidElement; + } - const clickedTag = clickedEl.nodeName.toLowerCase(); - let inputEl = null; + const clickedTag = clickedEl.nodeName.toLowerCase(); + let inputEl = null; - // Try to identify the input element (which may not be the clicked element) - if (labelTags.includes(clickedTag)) { - let inputId = null; - if (clickedTag === 'label') { - inputId = clickedEl.getAttribute('for'); - } else { - inputId = clickedEl.closest('label')?.getAttribute('for'); - } - - inputEl = document.getElementById(inputId); + // Try to identify the input element (which may not be the clicked element) + if (labelTags.includes(clickedTag)) { + let inputId = null; + if (clickedTag === "label") { + inputId = clickedEl.getAttribute("for"); } else { - inputEl = clickedEl; + inputId = clickedEl.closest("label")?.getAttribute("for"); } - if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) { - return invalidElement; - } + inputEl = document.getElementById(inputId); + } else { + inputEl = clickedEl; + } - for (const attr of attributes) { - const attributeValue = inputEl.getAttribute(attr); - const selector = '[' + attr + '="' + attributeValue + '"]'; - if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) { - return attributeValue; - } + if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) { + return invalidElement; + } + + for (const attr of attributes) { + const attributeValue = inputEl.getAttribute(attr); + const selector = "[" + attr + '="' + attributeValue + '"]'; + if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) { + return attributeValue; } - return noUniqueIdentifier; + } + return noUniqueIdentifier; } 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. // Remember it for use later. -document.addEventListener('contextmenu', event => { - clickedEl = event.target as HTMLElement; +document.addEventListener("contextmenu", (event) => { + clickedEl = event.target as HTMLElement; }); // Runs when the 'Copy Custom Field Name' context menu item is actually clicked. -chrome.runtime.onMessage.addListener(event => { - if (event.command === 'getClickedElement') { - const identifier = getClickedElementIdentifier(); - chrome.runtime.sendMessage({ - command: 'getClickedElementResponse', - sender: 'contextMenuHandler', - identifier: identifier, - }); - } +chrome.runtime.onMessage.addListener((event) => { + if (event.command === "getClickedElement") { + const identifier = getClickedElementIdentifier(); + chrome.runtime.sendMessage({ + command: "getClickedElementResponse", + sender: "contextMenuHandler", + identifier: identifier, + }); + } }); diff --git a/src/content/message_handler.ts b/src/content/message_handler.ts index e10834a3d6..ada2baac73 100644 --- a/src/content/message_handler.ts +++ b/src/content/message_handler.ts @@ -1,30 +1,37 @@ -window.addEventListener('message', event => { - if (event.source !== window) - return; +window.addEventListener( + "message", + (event) => { + if (event.source !== window) return; - if (event.data.command && (event.data.command === 'authResult')) { - chrome.runtime.sendMessage({ - command: event.data.command, - code: event.data.code, - state: event.data.state, - referrer: event.source.location.hostname, - }); + if (event.data.command && event.data.command === "authResult") { + chrome.runtime.sendMessage({ + command: event.data.command, + code: event.data.code, + state: event.data.state, + referrer: event.source.location.hostname, + }); } - if (event.data.command && (event.data.command === 'webAuthnResult')) { - chrome.runtime.sendMessage({ - command: event.data.command, - data: event.data.data, - remember: event.data.remember, - referrer: event.source.location.hostname, - }); + if (event.data.command && event.data.command === "webAuthnResult") { + chrome.runtime.sendMessage({ + command: event.data.command, + data: event.data.data, + remember: event.data.remember, + referrer: event.source.location.hostname, + }); } -}, false); + }, + false +); -const forwardCommands = ['promptForLogin', 'addToLockedVaultPendingNotifications', 'unlockCompleted']; +const forwardCommands = [ + "promptForLogin", + "addToLockedVaultPendingNotifications", + "unlockCompleted", +]; -chrome.runtime.onMessage.addListener(event => { - if (forwardCommands.includes(event.command)) { - chrome.runtime.sendMessage(event); - } +chrome.runtime.onMessage.addListener((event) => { + if (forwardCommands.includes(event.command)) { + chrome.runtime.sendMessage(event); + } }); diff --git a/src/content/notificationBar.ts b/src/content/notificationBar.ts index 84964f6f62..141021d6f9 100644 --- a/src/content/notificationBar.ts +++ b/src/content/notificationBar.ts @@ -1,540 +1,593 @@ -import AddLoginRuntimeMessage from 'src/background/models/addLoginRuntimeMessage'; -import ChangePasswordRuntimeMessage from 'src/background/models/changePasswordRuntimeMessage'; +import AddLoginRuntimeMessage from "src/background/models/addLoginRuntimeMessage"; +import ChangePasswordRuntimeMessage from "src/background/models/changePasswordRuntimeMessage"; -document.addEventListener('DOMContentLoaded', event => { - if (window.location.hostname.indexOf('vault.bitwarden.com') > -1) { +document.addEventListener("DOMContentLoaded", (event) => { + if (window.location.hostname.indexOf("vault.bitwarden.com") > -1) { + return; + } + + const pageDetails: any[] = []; + const formData: any[] = []; + let barType: string = null; + let pageHref: string = null; + let observer: MutationObserver = null; + const observeIgnoredElements = new Set([ + "a", + "i", + "b", + "strong", + "span", + "code", + "br", + "img", + "small", + "em", + "hr", + ]); + let domObservationCollectTimeout: number = null; + let collectIfNeededTimeout: number = null; + let observeDomTimeout: number = null; + const inIframe = isInIframe(); + const cancelButtonNames = new Set(["cancel", "close", "back"]); + const logInButtonNames = new Set([ + "log in", + "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 disabledChangedPasswordNotification = false; + + chrome.storage.local.get("neverDomains", (ndObj: any) => { + const domains = ndObj.neverDomains; + if (domains != null && domains.hasOwnProperty(window.location.hostname)) { + return; + } + + chrome.storage.local.get("disableAddLoginNotification", (disAddObj: any) => { + disabledAddLoginNotification = + disAddObj != null && disAddObj.disableAddLoginNotification === true; + chrome.storage.local.get("disableChangedPasswordNotification", (disChangedObj: any) => { + disabledChangedPasswordNotification = + disChangedObj != null && disChangedObj.disableChangedPasswordNotification === true; + if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) { + collectIfNeededWithTimeout(); + } + }); + }); + }); + + chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { + processMessages(msg, sendResponse); + }); + + function processMessages(msg: any, sendResponse: Function) { + if (msg.command === "openNotificationBar") { + if (inIframe) { return; + } + closeExistingAndOpenBar(msg.data.type, msg.data.typeData); + sendResponse(); + return true; + } else if (msg.command === "closeNotificationBar") { + if (inIframe) { + return; + } + closeBar(true); + sendResponse(); + return true; + } else if (msg.command === "adjustNotificationBar") { + if (inIframe) { + return; + } + adjustBar(msg.data); + sendResponse(); + return true; + } else if (msg.command === "notificationBarPageDetails") { + pageDetails.push(msg.data.details); + watchForms(msg.data.forms); + sendResponse(); + return true; } + } - const pageDetails: any[] = []; - const formData: any[] = []; - let barType: string = null; - let pageHref: string = null; - let observer: MutationObserver = null; - const observeIgnoredElements = new Set(['a', 'i', 'b', 'strong', 'span', 'code', 'br', 'img', 'small', 'em', 'hr']); - let domObservationCollectTimeout: number = null; - let collectIfNeededTimeout: number = null; - let observeDomTimeout: number = null; - const inIframe = isInIframe(); - const cancelButtonNames = new Set(['cancel', 'close', 'back']); - const logInButtonNames = new Set(['log in', '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 disabledChangedPasswordNotification = false; + function isInIframe() { + try { + return window.self !== window.top; + } catch { + return true; + } + } - chrome.storage.local.get('neverDomains', (ndObj: any) => { - const domains = ndObj.neverDomains; - if (domains != null && domains.hasOwnProperty(window.location.hostname)) { - return; + function observeDom() { + const bodies = document.querySelectorAll("body"); + if (bodies && bodies.length > 0) { + observer = new MutationObserver((mutations) => { + if (mutations == null || mutations.length === 0 || pageHref !== window.location.href) { + return; } - chrome.storage.local.get('disableAddLoginNotification', (disAddObj: any) => { - disabledAddLoginNotification = disAddObj != null && disAddObj.disableAddLoginNotification === true; - chrome.storage.local.get('disableChangedPasswordNotification', (disChangedObj: any) => { - disabledChangedPasswordNotification = disChangedObj != null && - disChangedObj.disableChangedPasswordNotification === true; - if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) { - collectIfNeededWithTimeout(); - } - }); - }); + let doCollect = false; + for (let i = 0; i < mutations.length; i++) { + const mutation = mutations[i]; + if (mutation.addedNodes == null || mutation.addedNodes.length === 0) { + continue; + } + + for (let j = 0; j < mutation.addedNodes.length; j++) { + const addedNode: any = mutation.addedNodes[j]; + if (addedNode == null) { + continue; + } + + const tagName = addedNode.tagName != null ? addedNode.tagName.toLowerCase() : null; + if ( + tagName != null && + tagName === "form" && + (addedNode.dataset == null || !addedNode.dataset.bitwardenWatching) + ) { + doCollect = true; + break; + } + + if ( + (tagName != null && observeIgnoredElements.has(tagName)) || + addedNode.querySelectorAll == null + ) { + continue; + } + + const forms = addedNode.querySelectorAll("form:not([data-bitwarden-watching])"); + if (forms != null && forms.length > 0) { + doCollect = true; + break; + } + } + + if (doCollect) { + break; + } + } + + if (doCollect) { + if (domObservationCollectTimeout != null) { + window.clearTimeout(domObservationCollectTimeout); + } + + domObservationCollectTimeout = window.setTimeout(collect, 1000); + } + }); + + observer.observe(bodies[0], { childList: true, subtree: true }); + } + } + + function collectIfNeededWithTimeout() { + if (collectIfNeededTimeout != null) { + window.clearTimeout(collectIfNeededTimeout); + } + collectIfNeededTimeout = window.setTimeout(collectIfNeeded, 1000); + } + + function collectIfNeeded() { + if (pageHref !== window.location.href) { + pageHref = window.location.href; + if (observer) { + observer.disconnect(); + observer = null; + } + + collect(); + + if (observeDomTimeout != null) { + window.clearTimeout(observeDomTimeout); + } + observeDomTimeout = window.setTimeout(observeDom, 1000); + } + + if (collectIfNeededTimeout != null) { + window.clearTimeout(collectIfNeededTimeout); + } + collectIfNeededTimeout = window.setTimeout(collectIfNeeded, 1000); + } + + function collect() { + sendPlatformMessage({ + command: "bgCollectPageDetails", + sender: "notificationBar", }); + } - chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { - processMessages(msg, sendResponse); + function watchForms(forms: any[]) { + if (forms == null || forms.length === 0) { + return; + } + + forms.forEach((f: any) => { + const formId: string = f.form != null ? f.form.htmlID : null; + let formEl: HTMLFormElement = null; + if (formId != null && formId !== "") { + formEl = document.getElementById(formId) as HTMLFormElement; + } + + if (formEl == null) { + const index = parseInt(f.form.opid.split("__")[2], null); + formEl = document.getElementsByTagName("form")[index]; + } + + if (formEl != null && formEl.dataset.bitwardenWatching !== "1") { + const formDataObj: any = { + data: f, + formEl: formEl, + usernameEl: null, + passwordEl: null, + passwordEls: null, + }; + locateFields(formDataObj); + formData.push(formDataObj); + listen(formEl); + formEl.dataset.bitwardenWatching = "1"; + } }); + } - function processMessages(msg: any, sendResponse: Function) { - if (msg.command === 'openNotificationBar') { - if (inIframe) { - return; - } - closeExistingAndOpenBar(msg.data.type, msg.data.typeData); - sendResponse(); - return true; - } else if (msg.command === 'closeNotificationBar') { - if (inIframe) { - return; - } - closeBar(true); - sendResponse(); - return true; - } else if (msg.command === 'adjustNotificationBar') { - if (inIframe) { - return; - } - adjustBar(msg.data); - sendResponse(); - return true; - } else if (msg.command === 'notificationBarPageDetails') { - pageDetails.push(msg.data.details); - watchForms(msg.data.forms); - sendResponse(); - return true; - } + function listen(form: HTMLFormElement) { + form.removeEventListener("submit", formSubmitted, false); + form.addEventListener("submit", formSubmitted, false); + const submitButton = getSubmitButton(form, logInButtonNames); + if (submitButton != null) { + submitButton.removeEventListener("click", formSubmitted, false); + submitButton.addEventListener("click", formSubmitted, false); } + } - function isInIframe() { - try { - return window.self !== window.top; - } catch { - return true; - } - } - - function observeDom() { - const bodies = document.querySelectorAll('body'); - if (bodies && bodies.length > 0) { - observer = new MutationObserver(mutations => { - if (mutations == null || mutations.length === 0 || pageHref !== window.location.href) { - return; - } - - let doCollect = false; - for (let i = 0; i < mutations.length; i++) { - const mutation = mutations[i]; - if (mutation.addedNodes == null || mutation.addedNodes.length === 0) { - continue; - } - - for (let j = 0; j < mutation.addedNodes.length; j++) { - const addedNode: any = mutation.addedNodes[j]; - if (addedNode == null) { - continue; - } - - const tagName = addedNode.tagName != null ? addedNode.tagName.toLowerCase() : null; - if (tagName != null && tagName === 'form' && - (addedNode.dataset == null || !addedNode.dataset.bitwardenWatching)) { - doCollect = true; - break; - } - - if ((tagName != null && observeIgnoredElements.has(tagName)) || - addedNode.querySelectorAll == null) { - continue; - } - - const forms = addedNode.querySelectorAll('form:not([data-bitwarden-watching])'); - if (forms != null && forms.length > 0) { - doCollect = true; - break; - } - } - - if (doCollect) { - break; - } - } - - if (doCollect) { - if (domObservationCollectTimeout != null) { - window.clearTimeout(domObservationCollectTimeout); - } - - domObservationCollectTimeout = window.setTimeout(collect, 1000); - } - }); - - observer.observe(bodies[0], { childList: true, subtree: true }); - } - } - - function collectIfNeededWithTimeout() { - if (collectIfNeededTimeout != null) { - window.clearTimeout(collectIfNeededTimeout); - } - collectIfNeededTimeout = window.setTimeout(collectIfNeeded, 1000); - } - - function collectIfNeeded() { - if (pageHref !== window.location.href) { - pageHref = window.location.href; - if (observer) { - observer.disconnect(); - observer = null; - } - - collect(); - - if (observeDomTimeout != null) { - window.clearTimeout(observeDomTimeout); - } - observeDomTimeout = window.setTimeout(observeDom, 1000); - } - - if (collectIfNeededTimeout != null) { - window.clearTimeout(collectIfNeededTimeout); - } - collectIfNeededTimeout = window.setTimeout(collectIfNeeded, 1000); - } - - function collect() { - sendPlatformMessage({ - command: 'bgCollectPageDetails', - sender: 'notificationBar', - }); - } - - function watchForms(forms: any[]) { - if (forms == null || forms.length === 0) { - return; - } - - forms.forEach((f: any) => { - const formId: string = f.form != null ? f.form.htmlID : null; - let formEl: HTMLFormElement = null; - if (formId != null && formId !== '') { - formEl = document.getElementById(formId) as HTMLFormElement; - } - - if (formEl == null) { - const index = parseInt(f.form.opid.split('__')[2], null); - formEl = document.getElementsByTagName('form')[index]; - } - - if (formEl != null && formEl.dataset.bitwardenWatching !== '1') { - const formDataObj: any = { - data: f, - formEl: formEl, - usernameEl: null, - passwordEl: null, - passwordEls: null, - }; - locateFields(formDataObj); - formData.push(formDataObj); - listen(formEl); - formEl.dataset.bitwardenWatching = '1'; - } - }); - } - - function listen(form: HTMLFormElement) { - form.removeEventListener('submit', formSubmitted, false); - form.addEventListener('submit', formSubmitted, false); - const submitButton = getSubmitButton(form, logInButtonNames); - if (submitButton != null) { - submitButton.removeEventListener('click', formSubmitted, false); - submitButton.addEventListener('click', formSubmitted, false); - } - } - - function locateFields(formDataObj: any) { - const inputs = Array.from(document.getElementsByTagName('input')); - formDataObj.usernameEl = locateField(formDataObj.formEl, formDataObj.data.username, inputs); - if (formDataObj.usernameEl != null && formDataObj.data.password != null) { - formDataObj.passwordEl = locatePassword(formDataObj.formEl, formDataObj.data.password, inputs, true); - } else if (formDataObj.data.passwords != null) { - formDataObj.passwordEls = []; - formDataObj.data.passwords.forEach((pData: any) => { - const el = locatePassword(formDataObj.formEl, pData, inputs, false); - if (el != null) { - formDataObj.passwordEls.push(el); - } - }); - if (formDataObj.passwordEls.length === 0) { - formDataObj.passwordEls = null; - } - } - } - - function locatePassword(form: HTMLFormElement, passwordData: any, inputs: HTMLInputElement[], - doLastFallback: boolean) { - let el = locateField(form, passwordData, inputs); - if (el != null && el.type !== 'password') { - el = null; - } - if (doLastFallback && el == null) { - el = form.querySelector('input[type="password"]'); - } - return el; - } - - function locateField(form: HTMLFormElement, fieldData: any, inputs: HTMLInputElement[]) { - if (fieldData == null) { - return; - } - let el: HTMLInputElement = null; - if (fieldData.htmlID != null && fieldData.htmlID !== '') { - try { - el = form.querySelector('#' + fieldData.htmlID); - } catch { - // Ignore error, we perform fallbacks below. - } - } - if (el == null && fieldData.htmlName != null && fieldData.htmlName !== '') { - el = form.querySelector('input[name="' + fieldData.htmlName + '"]'); - } - if (el == null && fieldData.elementNumber != null) { - el = inputs[fieldData.elementNumber]; - } - return el; - } - - function formSubmitted(e: Event) { - let form: HTMLFormElement = null; - if (e.type === 'click') { - form = (e.target as HTMLElement).closest('form'); - if (form == null) { - const parentModal = (e.target as HTMLElement).closest('div.modal'); - if (parentModal != null) { - const modalForms = parentModal.querySelectorAll('form'); - if (modalForms.length === 1) { - form = modalForms[0]; - } - } - } - } else { - form = e.target as HTMLFormElement; - } - - if (form == null || form.dataset.bitwardenProcessed === '1') { - return; - } - - for (let i = 0; i < formData.length; i++) { - if (formData[i].formEl !== form) { - continue; - } - const disabledBoth = disabledChangedPasswordNotification && disabledAddLoginNotification; - if (!disabledBoth && formData[i].usernameEl != null && formData[i].passwordEl != null) { - const login: AddLoginRuntimeMessage = { - username: formData[i].usernameEl.value, - password: formData[i].passwordEl.value, - url: document.URL, - }; - - if (login.username != null && login.username !== '' && - login.password != null && login.password !== '') { - processedForm(form); - sendPlatformMessage({ - command: 'bgAddLogin', - login: login, - }); - break; - } - } - if (!disabledChangedPasswordNotification && formData[i].passwordEls != null) { - const passwords: string[] = formData[i].passwordEls - .filter((el: HTMLInputElement) => el.value != null && el.value !== '') - .map((el: HTMLInputElement) => el.value); - - let curPass: string = null; - let newPass: string = null; - let newPassOnly = false; - if (formData[i].passwordEls.length === 3 && passwords.length === 3) { - newPass = passwords[1]; - if (passwords[0] !== newPass && newPass === passwords[2]) { - curPass = passwords[0]; - } else if (newPass !== passwords[2] && passwords[0] === newPass) { - curPass = passwords[2]; - } - } else if (formData[i].passwordEls.length === 2 && passwords.length === 2) { - if (passwords[0] === passwords[1]) { - newPassOnly = true; - newPass = passwords[0]; - curPass = null; - } else { - const buttonText = getButtonText(getSubmitButton(form, changePasswordButtonNames)); - const matches = Array.from(changePasswordButtonContainsNames) - .filter(n => buttonText.indexOf(n) > -1); - if (matches.length > 0) { - curPass = passwords[0]; - newPass = passwords[1]; - } - } - } - - if (newPass != null && curPass != null || (newPassOnly && newPass != null)) { - processedForm(form); - - const changePasswordRuntimeMessage: ChangePasswordRuntimeMessage = { - newPassword: newPass, - currentPassword: curPass, - url: document.URL, - }; - sendPlatformMessage({ - command: 'bgChangedPassword', - data: changePasswordRuntimeMessage, - }); - break; - } - } - } - } - - function getSubmitButton(wrappingEl: HTMLElement, buttonNames: Set) { - if (wrappingEl == null) { - return null; - } - - const wrappingElIsForm = wrappingEl.tagName.toLowerCase() === 'form'; - - let submitButton = wrappingEl.querySelector('input[type="submit"], input[type="image"], ' + - 'button[type="submit"]') as HTMLElement; - if (submitButton == null && wrappingElIsForm) { - submitButton = wrappingEl.querySelector('button:not([type])'); - if (submitButton != null) { - const buttonText = getButtonText(submitButton); - if (buttonText != null && cancelButtonNames.has(buttonText.trim().toLowerCase())) { - submitButton = null; - } - } - } - if (submitButton == null) { - const possibleSubmitButtons = Array.from(wrappingEl.querySelectorAll('a, span, button[type="button"], ' + - 'input[type="button"], button:not([type])')) as HTMLElement[]; - let typelessButton: HTMLElement = null; - possibleSubmitButtons.forEach(button => { - if (submitButton != null || button == null || button.tagName == null) { - return; - } - const buttonText = getButtonText(button); - if (buttonText != null) { - if (typelessButton != null && button.tagName.toLowerCase() === 'button' && - button.getAttribute('type') == null && - !cancelButtonNames.has(buttonText.trim().toLowerCase())) { - typelessButton = button; - } else if (buttonNames.has(buttonText.trim().toLowerCase())) { - submitButton = button; - } - } - }); - if (submitButton == null && typelessButton != null) { - submitButton = typelessButton; - } - } - if (submitButton == null && wrappingElIsForm) { - // Maybe it's in a modal? - const parentModal = wrappingEl.closest('div.modal') as HTMLElement; - if (parentModal != null) { - const modalForms = parentModal.querySelectorAll('form'); - if (modalForms.length === 1) { - submitButton = getSubmitButton(parentModal, buttonNames); - } - } - } - return submitButton; - } - - function getButtonText(button: HTMLElement) { - let buttonText: string = null; - if (button.tagName.toLowerCase() === 'input') { - buttonText = (button as HTMLInputElement).value; - } else { - buttonText = button.innerText; - } - return buttonText; - } - - function processedForm(form: HTMLFormElement) { - form.dataset.bitwardenProcessed = '1'; - window.setTimeout(() => { - form.dataset.bitwardenProcessed = '0'; - }, 500); - } - - function closeExistingAndOpenBar(type: string, typeData: any) { - let barPage = 'notification/bar.html'; - switch (type) { - case 'add': - barPage = barPage + '?add=1&isVaultLocked=' + typeData.isVaultLocked; - break; - case 'change': - barPage = barPage + '?change=1&isVaultLocked=' + typeData.isVaultLocked; - break; - default: - break; - } - - const frame = document.getElementById('bit-notification-bar-iframe') as HTMLIFrameElement; - if (frame != null && frame.src.indexOf(barPage) >= 0) { - return; - } - - closeBar(false); - openBar(type, barPage); - } - - function openBar(type: string, barPage: string) { - barType = type; - - if (document.body == null) { - return; - } - - const barPageUrl: string = chrome.extension.getURL(barPage); - - const iframe = document.createElement('iframe'); - iframe.style.cssText = 'height: 42px; width: 100%; border: 0; min-height: initial;'; - iframe.id = 'bit-notification-bar-iframe'; - iframe.src = barPageUrl; - - const frameDiv = document.createElement('div'); - frameDiv.setAttribute('aria-live', 'polite'); - frameDiv.id = 'bit-notification-bar'; - frameDiv.style.cssText = 'height: 42px; width: 100%; top: 0; left: 0; padding: 0; position: fixed; ' + - 'z-index: 2147483647; visibility: visible;'; - frameDiv.appendChild(iframe); - document.body.appendChild(frameDiv); - - (iframe.contentWindow.location as any) = barPageUrl; - - const spacer = document.createElement('div'); - spacer.id = 'bit-notification-bar-spacer'; - spacer.style.cssText = 'height: 42px;'; - document.body.insertBefore(spacer, document.body.firstChild); - } - - function closeBar(explicitClose: boolean) { - const barEl = document.getElementById('bit-notification-bar'); - if (barEl != null) { - barEl.parentElement.removeChild(barEl); - } - - const spacerEl = document.getElementById('bit-notification-bar-spacer'); - if (spacerEl) { - spacerEl.parentElement.removeChild(spacerEl); - } - - if (!explicitClose) { - return; - } - - switch (barType) { - case 'add': - sendPlatformMessage({ - command: 'bgAddClose', - }); - break; - case 'change': - sendPlatformMessage({ - command: 'bgChangeClose', - }); - break; - default: - break; - } - } - - function adjustBar(data: any) { - if (data != null && data.height !== 42) { - const newHeight = data.height + 'px'; - doHeightAdjustment('bit-notification-bar-iframe', newHeight); - doHeightAdjustment('bit-notification-bar', newHeight); - doHeightAdjustment('bit-notification-bar-spacer', newHeight); - } - } - - function doHeightAdjustment(elId: string, heightStyle: string) { - const el = document.getElementById(elId); + function locateFields(formDataObj: any) { + const inputs = Array.from(document.getElementsByTagName("input")); + formDataObj.usernameEl = locateField(formDataObj.formEl, formDataObj.data.username, inputs); + if (formDataObj.usernameEl != null && formDataObj.data.password != null) { + formDataObj.passwordEl = locatePassword( + formDataObj.formEl, + formDataObj.data.password, + inputs, + true + ); + } else if (formDataObj.data.passwords != null) { + formDataObj.passwordEls = []; + formDataObj.data.passwords.forEach((pData: any) => { + const el = locatePassword(formDataObj.formEl, pData, inputs, false); if (el != null) { - el.style.height = heightStyle; + formDataObj.passwordEls.push(el); } + }); + if (formDataObj.passwordEls.length === 0) { + formDataObj.passwordEls = null; + } + } + } + + function locatePassword( + form: HTMLFormElement, + passwordData: any, + inputs: HTMLInputElement[], + doLastFallback: boolean + ) { + let el = locateField(form, passwordData, inputs); + if (el != null && el.type !== "password") { + el = null; + } + if (doLastFallback && el == null) { + el = form.querySelector('input[type="password"]'); + } + return el; + } + + function locateField(form: HTMLFormElement, fieldData: any, inputs: HTMLInputElement[]) { + if (fieldData == null) { + return; + } + let el: HTMLInputElement = null; + if (fieldData.htmlID != null && fieldData.htmlID !== "") { + try { + el = form.querySelector("#" + fieldData.htmlID); + } catch { + // Ignore error, we perform fallbacks below. + } + } + if (el == null && fieldData.htmlName != null && fieldData.htmlName !== "") { + el = form.querySelector('input[name="' + fieldData.htmlName + '"]'); + } + if (el == null && fieldData.elementNumber != null) { + el = inputs[fieldData.elementNumber]; + } + return el; + } + + function formSubmitted(e: Event) { + let form: HTMLFormElement = null; + if (e.type === "click") { + form = (e.target as HTMLElement).closest("form"); + if (form == null) { + const parentModal = (e.target as HTMLElement).closest("div.modal"); + if (parentModal != null) { + const modalForms = parentModal.querySelectorAll("form"); + if (modalForms.length === 1) { + form = modalForms[0]; + } + } + } + } else { + form = e.target as HTMLFormElement; } - function sendPlatformMessage(msg: any) { - chrome.runtime.sendMessage(msg); + if (form == null || form.dataset.bitwardenProcessed === "1") { + return; } + + for (let i = 0; i < formData.length; i++) { + if (formData[i].formEl !== form) { + continue; + } + const disabledBoth = disabledChangedPasswordNotification && disabledAddLoginNotification; + if (!disabledBoth && formData[i].usernameEl != null && formData[i].passwordEl != null) { + const login: AddLoginRuntimeMessage = { + username: formData[i].usernameEl.value, + password: formData[i].passwordEl.value, + url: document.URL, + }; + + if ( + login.username != null && + login.username !== "" && + login.password != null && + login.password !== "" + ) { + processedForm(form); + sendPlatformMessage({ + command: "bgAddLogin", + login: login, + }); + break; + } + } + if (!disabledChangedPasswordNotification && formData[i].passwordEls != null) { + const passwords: string[] = formData[i].passwordEls + .filter((el: HTMLInputElement) => el.value != null && el.value !== "") + .map((el: HTMLInputElement) => el.value); + + let curPass: string = null; + let newPass: string = null; + let newPassOnly = false; + if (formData[i].passwordEls.length === 3 && passwords.length === 3) { + newPass = passwords[1]; + if (passwords[0] !== newPass && newPass === passwords[2]) { + curPass = passwords[0]; + } else if (newPass !== passwords[2] && passwords[0] === newPass) { + curPass = passwords[2]; + } + } else if (formData[i].passwordEls.length === 2 && passwords.length === 2) { + if (passwords[0] === passwords[1]) { + newPassOnly = true; + newPass = passwords[0]; + curPass = null; + } else { + const buttonText = getButtonText(getSubmitButton(form, changePasswordButtonNames)); + const matches = Array.from(changePasswordButtonContainsNames).filter( + (n) => buttonText.indexOf(n) > -1 + ); + if (matches.length > 0) { + curPass = passwords[0]; + newPass = passwords[1]; + } + } + } + + if ((newPass != null && curPass != null) || (newPassOnly && newPass != null)) { + processedForm(form); + + const changePasswordRuntimeMessage: ChangePasswordRuntimeMessage = { + newPassword: newPass, + currentPassword: curPass, + url: document.URL, + }; + sendPlatformMessage({ + command: "bgChangedPassword", + data: changePasswordRuntimeMessage, + }); + break; + } + } + } + } + + function getSubmitButton(wrappingEl: HTMLElement, buttonNames: Set) { + if (wrappingEl == null) { + return null; + } + + const wrappingElIsForm = wrappingEl.tagName.toLowerCase() === "form"; + + let submitButton = wrappingEl.querySelector( + 'input[type="submit"], input[type="image"], ' + 'button[type="submit"]' + ) as HTMLElement; + if (submitButton == null && wrappingElIsForm) { + submitButton = wrappingEl.querySelector("button:not([type])"); + if (submitButton != null) { + const buttonText = getButtonText(submitButton); + if (buttonText != null && cancelButtonNames.has(buttonText.trim().toLowerCase())) { + submitButton = null; + } + } + } + if (submitButton == null) { + const possibleSubmitButtons = Array.from( + wrappingEl.querySelectorAll( + 'a, span, button[type="button"], ' + 'input[type="button"], button:not([type])' + ) + ) as HTMLElement[]; + let typelessButton: HTMLElement = null; + possibleSubmitButtons.forEach((button) => { + if (submitButton != null || button == null || button.tagName == null) { + return; + } + const buttonText = getButtonText(button); + if (buttonText != null) { + if ( + typelessButton != null && + button.tagName.toLowerCase() === "button" && + button.getAttribute("type") == null && + !cancelButtonNames.has(buttonText.trim().toLowerCase()) + ) { + typelessButton = button; + } else if (buttonNames.has(buttonText.trim().toLowerCase())) { + submitButton = button; + } + } + }); + if (submitButton == null && typelessButton != null) { + submitButton = typelessButton; + } + } + if (submitButton == null && wrappingElIsForm) { + // Maybe it's in a modal? + const parentModal = wrappingEl.closest("div.modal") as HTMLElement; + if (parentModal != null) { + const modalForms = parentModal.querySelectorAll("form"); + if (modalForms.length === 1) { + submitButton = getSubmitButton(parentModal, buttonNames); + } + } + } + return submitButton; + } + + function getButtonText(button: HTMLElement) { + let buttonText: string = null; + if (button.tagName.toLowerCase() === "input") { + buttonText = (button as HTMLInputElement).value; + } else { + buttonText = button.innerText; + } + return buttonText; + } + + function processedForm(form: HTMLFormElement) { + form.dataset.bitwardenProcessed = "1"; + window.setTimeout(() => { + form.dataset.bitwardenProcessed = "0"; + }, 500); + } + + function closeExistingAndOpenBar(type: string, typeData: any) { + let barPage = "notification/bar.html"; + switch (type) { + case "add": + barPage = barPage + "?add=1&isVaultLocked=" + typeData.isVaultLocked; + break; + case "change": + barPage = barPage + "?change=1&isVaultLocked=" + typeData.isVaultLocked; + break; + default: + break; + } + + const frame = document.getElementById("bit-notification-bar-iframe") as HTMLIFrameElement; + if (frame != null && frame.src.indexOf(barPage) >= 0) { + return; + } + + closeBar(false); + openBar(type, barPage); + } + + function openBar(type: string, barPage: string) { + barType = type; + + if (document.body == null) { + return; + } + + const barPageUrl: string = chrome.extension.getURL(barPage); + + const iframe = document.createElement("iframe"); + iframe.style.cssText = "height: 42px; width: 100%; border: 0; min-height: initial;"; + iframe.id = "bit-notification-bar-iframe"; + iframe.src = barPageUrl; + + const frameDiv = document.createElement("div"); + frameDiv.setAttribute("aria-live", "polite"); + frameDiv.id = "bit-notification-bar"; + frameDiv.style.cssText = + "height: 42px; width: 100%; top: 0; left: 0; padding: 0; position: fixed; " + + "z-index: 2147483647; visibility: visible;"; + frameDiv.appendChild(iframe); + document.body.appendChild(frameDiv); + + (iframe.contentWindow.location as any) = barPageUrl; + + const spacer = document.createElement("div"); + spacer.id = "bit-notification-bar-spacer"; + spacer.style.cssText = "height: 42px;"; + document.body.insertBefore(spacer, document.body.firstChild); + } + + function closeBar(explicitClose: boolean) { + const barEl = document.getElementById("bit-notification-bar"); + if (barEl != null) { + barEl.parentElement.removeChild(barEl); + } + + const spacerEl = document.getElementById("bit-notification-bar-spacer"); + if (spacerEl) { + spacerEl.parentElement.removeChild(spacerEl); + } + + if (!explicitClose) { + return; + } + + switch (barType) { + case "add": + sendPlatformMessage({ + command: "bgAddClose", + }); + break; + case "change": + sendPlatformMessage({ + command: "bgChangeClose", + }); + break; + default: + break; + } + } + + function adjustBar(data: any) { + if (data != null && data.height !== 42) { + const newHeight = data.height + "px"; + doHeightAdjustment("bit-notification-bar-iframe", newHeight); + doHeightAdjustment("bit-notification-bar", newHeight); + doHeightAdjustment("bit-notification-bar-spacer", newHeight); + } + } + + function doHeightAdjustment(elId: string, heightStyle: string) { + const el = document.getElementById(elId); + if (el != null) { + el.style.height = heightStyle; + } + } + + function sendPlatformMessage(msg: any) { + chrome.runtime.sendMessage(msg); + } }); diff --git a/src/content/shortcuts.ts b/src/content/shortcuts.ts index 8aa72deccb..4832644082 100644 --- a/src/content/shortcuts.ts +++ b/src/content/shortcuts.ts @@ -1,50 +1,52 @@ -import * as Mousetrap from 'mousetrap'; +import * as Mousetrap from "mousetrap"; -document.addEventListener('DOMContentLoaded', event => { - const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1; - const isVivaldi = !isSafari && navigator.userAgent.indexOf(' Vivaldi/') !== -1; +document.addEventListener("DOMContentLoaded", (event) => { + const isSafari = + typeof safari !== "undefined" && + navigator.userAgent.indexOf(" Safari/") !== -1 && + navigator.userAgent.indexOf("Chrome") === -1; + const isVivaldi = !isSafari && navigator.userAgent.indexOf(" Vivaldi/") !== -1; - if (!isSafari && !isVivaldi) { - return; - } + if (!isSafari && !isVivaldi) { + return; + } - if (isSafari && (window as any).__bitwardenFrameId == null) { - (window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999)); - } + if (isSafari && (window as any).__bitwardenFrameId == null) { + (window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999)); + } - Mousetrap.prototype.stopCallback = () => { - return false; - }; + Mousetrap.prototype.stopCallback = () => { + return false; + }; - let autofillCommand = ['mod+shift+l']; - if (isSafari) { - autofillCommand = ['mod+\\', 'mod+8', 'mod+shift+p']; - } - Mousetrap.bind(autofillCommand, () => { - sendMessage('autofill_login'); + let autofillCommand = ["mod+shift+l"]; + if (isSafari) { + autofillCommand = ["mod+\\", "mod+8", "mod+shift+p"]; + } + Mousetrap.bind(autofillCommand, () => { + sendMessage("autofill_login"); + }); + + if (isSafari) { + Mousetrap.bind("mod+shift+y", () => { + sendMessage("open_popup"); }); - if (isSafari) { - Mousetrap.bind('mod+shift+y', () => { - sendMessage('open_popup'); - }); + Mousetrap.bind("mod+shift+s", () => { + sendMessage("lock_vault"); + }); + } else { + Mousetrap.bind("mod+shift+9", () => { + sendMessage("generate_password"); + }); + } - Mousetrap.bind('mod+shift+s', () => { - sendMessage('lock_vault'); - }); - } else { - Mousetrap.bind('mod+shift+9', () => { - sendMessage('generate_password'); - }); - } + function sendMessage(shortcut: string) { + const msg: any = { + command: "keyboardShortcutTriggered", + shortcut: shortcut, + }; - function sendMessage(shortcut: string) { - const msg: any = { - command: 'keyboardShortcutTriggered', - shortcut: shortcut, - }; - - chrome.runtime.sendMessage(msg); - } + chrome.runtime.sendMessage(msg); + } }); diff --git a/src/manifest.json b/src/manifest.json index b5e0b0a89a..62a741d131 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -23,47 +23,25 @@ "content/notificationBar.js", "content/contextMenuHandler.js" ], - "matches": [ - "http://*/*", - "https://*/*", - "file:///*" - ], + "matches": ["http://*/*", "https://*/*", "file:///*"], "run_at": "document_start" }, { "all_frames": false, - "js": [ - "content/shortcuts.js" - ], - "matches": [ - "http://*/*", - "https://*/*", - "file:///*" - ], + "js": ["content/shortcuts.js"], + "matches": ["http://*/*", "https://*/*", "file:///*"], "run_at": "document_start" }, { "all_frames": false, - "js": [ - "content/message_handler.js" - ], - "matches": [ - "http://*/*", - "https://*/*", - "file:///*" - ], + "js": ["content/message_handler.js"], + "matches": ["http://*/*", "https://*/*", "file:///*"], "run_at": "document_start" }, { "all_frames": true, - "css": [ - "content/autofill.css" - ], - "matches": [ - "http://*/*", - "https://*/*", - "file:///*" - ], + "css": ["content/autofill.css"], + "matches": ["http://*/*", "https://*/*", "file:///*"], "run_at": "document_end" } ], @@ -92,9 +70,7 @@ "webRequest", "webRequestBlocking" ], - "optional_permissions": [ - "nativeMessaging" - ], + "optional_permissions": ["nativeMessaging"], "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "commands": { "_execute_browser_action": { @@ -144,4 +120,4 @@ "default_panel": "popup/index.html?uilocation=sidebar", "default_icon": "images/icon19.png" } -} \ No newline at end of file +} diff --git a/src/models/autofillField.ts b/src/models/autofillField.ts index 236fe81577..fa08602a32 100644 --- a/src/models/autofillField.ts +++ b/src/models/autofillField.ts @@ -1,25 +1,25 @@ export default class AutofillField { - opid: string; - elementNumber: number; - visible: boolean; - viewable: boolean; - htmlID: string; - htmlName: string; - htmlClass: string; - 'label-left': string; - 'label-right': string; - 'label-top': string; - 'label-tag': string; - 'label-aria': string; - placeholder: string; - type: string; - value: string; - disabled: boolean; - readonly: boolean; - onePasswordFieldType: string; - form: string; - autoCompleteType: string; - selectInfo: any; - maxLength: number; - tagName: string; + opid: string; + elementNumber: number; + visible: boolean; + viewable: boolean; + htmlID: string; + htmlName: string; + htmlClass: string; + "label-left": string; + "label-right": string; + "label-top": string; + "label-tag": string; + "label-aria": string; + placeholder: string; + type: string; + value: string; + disabled: boolean; + readonly: boolean; + onePasswordFieldType: string; + form: string; + autoCompleteType: string; + selectInfo: any; + maxLength: number; + tagName: string; } diff --git a/src/models/autofillForm.ts b/src/models/autofillForm.ts index 2d7fc4800b..625d886f21 100644 --- a/src/models/autofillForm.ts +++ b/src/models/autofillForm.ts @@ -1,7 +1,7 @@ export default class AutofillForm { - opid: string; - htmlName: string; - htmlID: string; - htmlAction: string; - htmlMethod: string; + opid: string; + htmlName: string; + htmlID: string; + htmlAction: string; + htmlMethod: string; } diff --git a/src/models/autofillPageDetails.ts b/src/models/autofillPageDetails.ts index 70f922bbe6..1c91386930 100644 --- a/src/models/autofillPageDetails.ts +++ b/src/models/autofillPageDetails.ts @@ -1,13 +1,13 @@ -import AutofillField from './autofillField'; -import AutofillForm from './autofillForm'; +import AutofillField from "./autofillField"; +import AutofillForm from "./autofillForm"; export default class AutofillPageDetails { - documentUUID: string; - title: string; - url: string; - documentUrl: string; - tabUrl: string; - forms: { [id: string]: AutofillForm; }; - fields: AutofillField[]; - collectedTimestamp: number; + documentUUID: string; + title: string; + url: string; + documentUrl: string; + tabUrl: string; + forms: { [id: string]: AutofillForm }; + fields: AutofillField[]; + collectedTimestamp: number; } diff --git a/src/models/autofillScript.ts b/src/models/autofillScript.ts index 875e620ea8..509c1da87b 100644 --- a/src/models/autofillScript.ts +++ b/src/models/autofillScript.ts @@ -1,12 +1,12 @@ export default class AutofillScript { - script: string[][] = []; - documentUUID: any = {}; - properties: any = {}; - options: any = {}; - metadata: any = {}; - autosubmit: any = null; + script: string[][] = []; + documentUUID: any = {}; + properties: any = {}; + options: any = {}; + metadata: any = {}; + autosubmit: any = null; - constructor(documentUUID: string) { - this.documentUUID = documentUUID; - } + constructor(documentUUID: string) { + this.documentUUID = documentUUID; + } } diff --git a/src/notification/bar.html b/src/notification/bar.html index a03c566f19..101b555df0 100644 --- a/src/notification/bar.html +++ b/src/notification/bar.html @@ -1,41 +1,39 @@  - - + Bitwarden - + - +
- -
-
- -
+ +
+
+ +
- + + +
+ +
+

+ +

+ diff --git a/src/popup/accounts/lock.component.ts b/src/popup/accounts/lock.component.ts index e074f8abdd..84c8b9fa68 100644 --- a/src/popup/accounts/lock.component.ts +++ b/src/popup/accounts/lock.component.ts @@ -1,87 +1,107 @@ -import { - Component, - NgZone, -} from '@angular/core'; -import { Router } from '@angular/router'; -import Swal from 'sweetalert2'; +import { Component, NgZone } 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 { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { UserService } from "jslib-common/abstractions/user.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({ - selector: 'app-lock', - templateUrl: 'lock.component.html', + selector: "app-lock", + templateUrl: "lock.component.html", }) export class LockComponent extends BaseLockComponent { - private isInitialLockScreen: boolean; + private isInitialLockScreen: boolean; - constructor(router: Router, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, messagingService: MessagingService, - userService: UserService, cryptoService: CryptoService, - storageService: StorageService, vaultTimeoutService: VaultTimeoutService, - environmentService: EnvironmentService, 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; - } + constructor( + router: Router, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + messagingService: MessagingService, + userService: UserService, + cryptoService: CryptoService, + storageService: StorageService, + vaultTimeoutService: VaultTimeoutService, + environmentService: EnvironmentService, + 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; + } - async ngOnInit() { - await super.ngOnInit(); - const disableAutoBiometricsPrompt = await this.storageService.get( - ConstantsService.disableAutoBiometricsPromptKey) ?? true; + async ngOnInit() { + await super.ngOnInit(); + const disableAutoBiometricsPrompt = + (await this.storageService.get(ConstantsService.disableAutoBiometricsPromptKey)) ?? + true; - window.setTimeout(async () => { - document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus(); - if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) { - if (await this.vaultTimeoutService.isLocked()) { - await this.unlockBiometric(); - } - } - }, 100); - } - - async unlockBiometric(): Promise { - if (!this.biometricLock) { - return; + window.setTimeout(async () => { + document.getElementById(this.pinLock ? "pin" : "masterPassword").focus(); + if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) { + if (await this.vaultTimeoutService.isLocked()) { + await this.unlockBiometric(); } + } + }, 100); + } - const div = document.createElement('div'); - div.innerHTML = `
${this.i18nService.t('awaitDesktop')}
`; - - Swal.fire({ - heightAuto: false, - buttonsStyling: false, - html: div, - showCancelButton: true, - cancelButtonText: this.i18nService.t('cancel'), - showConfirmButton: false, - }); - - const success = await super.unlockBiometric(); - - // Avoid closing the error dialogs - if (success) { - Swal.close(); - } - - return success; + async unlockBiometric(): Promise { + if (!this.biometricLock) { + return; } + + const div = document.createElement("div"); + div.innerHTML = `
${this.i18nService.t("awaitDesktop")}
`; + + Swal.fire({ + heightAuto: false, + buttonsStyling: false, + html: div, + showCancelButton: true, + cancelButtonText: this.i18nService.t("cancel"), + showConfirmButton: false, + }); + + const success = await super.unlockBiometric(); + + // Avoid closing the error dialogs + if (success) { + Swal.close(); + } + + return success; + } } diff --git a/src/popup/accounts/login.component.html b/src/popup/accounts/login.component.html index 68628ac3e7..ee3381a26c 100644 --- a/src/popup/accounts/login.component.html +++ b/src/popup/accounts/login.component.html @@ -1,47 +1,71 @@
-
-
- {{'cancel' | i18n}} +
+ +

+ {{ "appName" | i18n }} +

+
+ +
+
+ +
+
+
+ +
-

- {{'appName' | i18n}} -

-
- +
-
- -
-
-
- - -
-
-
- - -
-
- -
-
-
- -
-
+
+
-

- {{'getMasterPasswordHint' | i18n}} -

- +
+ +

+ {{ "getMasterPasswordHint" | i18n }} +

+
diff --git a/src/popup/accounts/login.component.ts b/src/popup/accounts/login.component.ts index e6d1dcef24..74983ea410 100644 --- a/src/popup/accounts/login.component.ts +++ b/src/popup/accounts/login.component.ts @@ -1,42 +1,58 @@ -import { - Component, - NgZone, -} from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, NgZone } from "@angular/core"; +import { Router } from "@angular/router"; -import { AuthService } from 'jslib-common/abstractions/auth.service'; -import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.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({ - selector: 'app-login', - templateUrl: 'login.component.html', + selector: "app-login", + templateUrl: "login.component.html", }) export class LoginComponent extends BaseLoginComponent { - constructor(authService: AuthService, router: Router, - protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, - protected stateService: StateService, protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService, - syncService: SyncService, logService: LogService, ngZone: NgZone) { - super(authService, router, platformUtilsService, i18nService, stateService, environmentService, - passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone); - super.onSuccessfulLogin = async () => { - await syncService.fullSync(true); - }; - super.successRoute = '/tabs/vault'; - } + constructor( + authService: AuthService, + router: Router, + protected platformUtilsService: PlatformUtilsService, + protected i18nService: I18nService, + protected stateService: StateService, + protected environmentService: EnvironmentService, + protected passwordGenerationService: PasswordGenerationService, + protected cryptoFunctionService: CryptoFunctionService, + storageService: StorageService, + syncService: SyncService, + logService: LogService, + ngZone: NgZone + ) { + super( + authService, + router, + platformUtilsService, + i18nService, + stateService, + environmentService, + passwordGenerationService, + cryptoFunctionService, + storageService, + logService, + ngZone + ); + super.onSuccessfulLogin = async () => { + await syncService.fullSync(true); + }; + super.successRoute = "/tabs/vault"; + } - settings() { - this.router.navigate(['environment']); - } + settings() { + this.router.navigate(["environment"]); + } } diff --git a/src/popup/accounts/register.component.html b/src/popup/accounts/register.component.html index 0ab0090f57..ae2b4562ee 100644 --- a/src/popup/accounts/register.component.html +++ b/src/popup/accounts/register.component.html @@ -1,100 +1,160 @@
-
-
- {{'cancel' | i18n}} +
+ +

+ {{ "createAccount" | i18n }} +

+
+ +
+
+ +
+
+
+ +
-

- {{'createAccount' | i18n}} -

-
- +
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+ +
-
- -
-
-
- - -
-
-
-
- - -
-
- -
-
-
-
-
-
-
- +
+ +
-
-
-
-
- - -
-
- -
-
-
- - -
-
- +
+ +
+
+
+
+
+ +
-
-
-
-
- - -
-
-
- +
+
+
diff --git a/src/popup/accounts/register.component.ts b/src/popup/accounts/register.component.ts index 4428b5fd9d..7bc3b12d44 100644 --- a/src/popup/accounts/register.component.ts +++ b/src/popup/accounts/register.component.ts @@ -1,29 +1,46 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { AuthService } from 'jslib-common/abstractions/auth.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.service"; -import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component'; -import { LogService } from 'jslib-common/abstractions/log.service'; +import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component"; +import { LogService } from "jslib-common/abstractions/log.service"; @Component({ - selector: 'app-register', - templateUrl: 'register.component.html', + selector: "app-register", + templateUrl: "register.component.html", }) export class RegisterComponent extends BaseRegisterComponent { - constructor(authService: AuthService, router: Router, - i18nService: I18nService, cryptoService: CryptoService, - apiService: ApiService, stateService: StateService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService, - logService: LogService) { - super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService, - passwordGenerationService, environmentService, logService); - } + constructor( + authService: AuthService, + router: Router, + i18nService: I18nService, + cryptoService: CryptoService, + apiService: ApiService, + stateService: StateService, + platformUtilsService: PlatformUtilsService, + passwordGenerationService: PasswordGenerationService, + environmentService: EnvironmentService, + logService: LogService + ) { + super( + authService, + router, + i18nService, + cryptoService, + apiService, + stateService, + platformUtilsService, + passwordGenerationService, + environmentService, + logService + ); + } } diff --git a/src/popup/accounts/remove-password.component.html b/src/popup/accounts/remove-password.component.html index dcbaac8ade..8c3a1a72c8 100644 --- a/src/popup/accounts/remove-password.component.html +++ b/src/popup/accounts/remove-password.component.html @@ -1,29 +1,49 @@
-
-
- {{'removeMasterPassword' | i18n}} -
-
+
+
+ {{ "removeMasterPassword" | i18n }} +
+
-
-
-
-

{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}

-
-
- -
-
- -
-
+
+
+
+

{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}

+
+
+ +
+
+ +
+
diff --git a/src/popup/accounts/remove-password.component.ts b/src/popup/accounts/remove-password.component.ts index c83269494a..cdb75058ab 100644 --- a/src/popup/accounts/remove-password.component.ts +++ b/src/popup/accounts/remove-password.component.ts @@ -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({ - selector: 'app-remove-password', - templateUrl: 'remove-password.component.html', + selector: "app-remove-password", + templateUrl: "remove-password.component.html", }) -export class RemovePasswordComponent extends BaseRemovePasswordComponent { -} +export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/src/popup/accounts/set-password.component.html b/src/popup/accounts/set-password.component.html index 3452708e12..b892806734 100644 --- a/src/popup/accounts/set-password.component.html +++ b/src/popup/accounts/set-password.component.html @@ -1,100 +1,150 @@
-
- -

- {{'setMasterPassword' | i18n}} -

-
- -
-
- -
- -
-
-
- {{'ssoCompleteRegistration' | i18n}} - - {{'resetPasswordAutoEnrollInviteWarning' | i18n}} - - - +
+ +

+ {{ "setMasterPassword" | i18n }} +

+
+ +
+
+ +
+ +
+
+
+ {{ "ssoCompleteRegistration" | i18n }} + + {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} + + + +
+
+
+
+
+
+ + +
+
+ +
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- +
+
+
- + +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ + +
+
+ +
+
+ diff --git a/src/popup/accounts/set-password.component.ts b/src/popup/accounts/set-password.component.ts index 143bb864c8..adac7cfae9 100644 --- a/src/popup/accounts/set-password.component.ts +++ b/src/popup/accounts/set-password.component.ts @@ -1,65 +1,79 @@ -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { ActivatedRoute, Router } from "@angular/router"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { - SetPasswordComponent as BaseSetPasswordComponent, -} from 'jslib-angular/components/set-password.component'; +import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component"; @Component({ - selector: 'app-set-password', - templateUrl: 'set-password.component.html', + selector: "app-set-password", + templateUrl: "set-password.component.html", }) export class SetPasswordComponent extends BaseSetPasswordComponent { - constructor(apiService: ApiService, i18nService: I18nService, - cryptoService: CryptoService, messagingService: MessagingService, - userService: UserService, passwordGenerationService: PasswordGenerationService, - platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router, - syncService: SyncService, route: ActivatedRoute) { - super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, - platformUtilsService, policyService, router, apiService, syncService, route); - } + constructor( + apiService: ApiService, + i18nService: I18nService, + cryptoService: CryptoService, + messagingService: MessagingService, + userService: UserService, + 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() { - return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; - } + get masterPasswordScoreWidth() { + return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; + } - get masterPasswordScoreColor() { - switch (this.masterPasswordScore) { - case 4: - return 'success'; - case 3: - return 'primary'; - case 2: - return 'warning'; - default: - return 'danger'; - } + get masterPasswordScoreColor() { + switch (this.masterPasswordScore) { + case 4: + return "success"; + case 3: + return "primary"; + case 2: + return "warning"; + default: + return "danger"; } + } - get masterPasswordScoreText() { - switch (this.masterPasswordScore) { - case 4: - return this.i18nService.t('strong'); - case 3: - return this.i18nService.t('good'); - case 2: - return this.i18nService.t('weak'); - default: - return this.masterPasswordScore != null ? this.i18nService.t('weak') : null; - } + get masterPasswordScoreText() { + switch (this.masterPasswordScore) { + case 4: + return this.i18nService.t("strong"); + case 3: + return this.i18nService.t("good"); + case 2: + return this.i18nService.t("weak"); + default: + return this.masterPasswordScore != null ? this.i18nService.t("weak") : null; } + } } diff --git a/src/popup/accounts/sso.component.ts b/src/popup/accounts/sso.component.ts index e5ad186d26..241f45f4d4 100644 --- a/src/popup/accounts/sso.component.ts +++ b/src/popup/accounts/sso.component.ts @@ -1,55 +1,73 @@ -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { ActivatedRoute, Router } from "@angular/router"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { AuthService } from 'jslib-common/abstractions/auth.service'; -import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; -import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component'; -import { BrowserApi } from '../../browser/browserApi'; +import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component"; +import { BrowserApi } from "../../browser/browserApi"; @Component({ - selector: 'app-sso', - templateUrl: 'sso.component.html', + selector: "app-sso", + templateUrl: "sso.component.html", }) export class SsoComponent extends BaseSsoComponent { - constructor(authService: AuthService, router: Router, - i18nService: I18nService, route: ActivatedRoute, - storageService: StorageService, stateService: StateService, - platformUtilsService: PlatformUtilsService, 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); + constructor( + authService: AuthService, + router: Router, + i18nService: I18nService, + route: ActivatedRoute, + storageService: StorageService, + stateService: StateService, + platformUtilsService: PlatformUtilsService, + 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.clientId = 'browser'; + this.redirectUri = url + "/sso-connector.html"; + this.clientId = "browser"; - super.onSuccessfulLogin = async () => { - await syncService.fullSync(true); - if (await this.vaultTimeoutService.isLocked()) { - // If the vault is unlocked then this will clear keys from memory, which we don't want to do - BrowserApi.reloadOpenWindows(); - } + super.onSuccessfulLogin = async () => { + await syncService.fullSync(true); + if (await this.vaultTimeoutService.isLocked()) { + // If the vault is unlocked then this will clear keys from memory, which we don't want to do + BrowserApi.reloadOpenWindows(); + } - const thisWindow = window.open('', '_self'); - thisWindow.close(); - }; - } + const thisWindow = window.open("", "_self"); + thisWindow.close(); + }; + } } diff --git a/src/popup/accounts/two-factor-options.component.html b/src/popup/accounts/two-factor-options.component.html index 07ca6d1921..139be18ac8 100644 --- a/src/popup/accounts/two-factor-options.component.html +++ b/src/popup/accounts/two-factor-options.component.html @@ -1,23 +1,29 @@
- -

- {{'twoStepOptions' | i18n}} -

-
+ +

+ {{ "twoStepOptions" | i18n }} +

+
-
-
- - -
+
+
+ +
+
diff --git a/src/popup/accounts/two-factor-options.component.ts b/src/popup/accounts/two-factor-options.component.ts index 5e43015c23..3c05ec0e43 100644 --- a/src/popup/accounts/two-factor-options.component.ts +++ b/src/popup/accounts/two-factor-options.component.ts @@ -1,27 +1,29 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; -import { AuthService } from 'jslib-common/abstractions/auth.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { - TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent, -} from 'jslib-angular/components/two-factor-options.component'; +import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component"; @Component({ - selector: 'app-two-factor-options', - templateUrl: 'two-factor-options.component.html', + selector: "app-two-factor-options", + templateUrl: "two-factor-options.component.html", }) export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { - constructor(authService: AuthService, router: Router, - i18nService: I18nService, platformUtilsService: PlatformUtilsService) { - super(authService, router, i18nService, platformUtilsService, window); - } + constructor( + authService: AuthService, + router: Router, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService + ) { + super(authService, router, i18nService, platformUtilsService, window); + } - choose(p: any) { - super.choose(p); - this.authService.selectedTwoFactorProviderType = p.type; - this.router.navigate(['2fa']); - } + choose(p: any) { + super.choose(p); + this.authService.selectedTwoFactorProviderType = p.type; + this.router.navigate(["2fa"]); + } } diff --git a/src/popup/accounts/two-factor.component.html b/src/popup/accounts/two-factor.component.html index 94d7b1caf7..820043293b 100644 --- a/src/popup/accounts/two-factor.component.html +++ b/src/popup/accounts/two-factor.component.html @@ -1,106 +1,141 @@
-
-
- {{'back' | i18n}} +
+ +

+ {{ title }} +

+
+ +
+
+ + +
+ + {{ "enterVerificationCodeApp" | i18n }} + + + {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }} + +
+
+
+
+ + +
+
+ + +
-

- {{title}} -

-
- +
+ + +
+

{{ "insertYubiKey" | i18n }}

+ +
+
+
+
+ + +
+
+ + +
-
- - -
- - {{'enterVerificationCodeApp' | i18n}} - - - {{'enterVerificationCodeEmail' | i18n : twoFactorEmail}} - -
-
-
-
- - -
-
- - -
-
-
-
- -
-

{{'insertYubiKey' | i18n}}

- -
-
-
-
- - -
-
- - -
-
-
-
- -
-
-
-
- - -
-
-
-
- -
-

{{'webAuthnNewTab' | i18n}}

- -
-
- -
-
-
-
- - -
-
-
-
-
-

{{'noTwoStepProviders' | i18n}}

-

{{'noTwoStepProviders2' | i18n}}

+
+ + +
+
+
+
+ + +
-
-

- -

-

- -

+
+ + +
+

{{ "webAuthnNewTab" | i18n }}

+ +
+
+ +
+
+
+
+ + +
- +
+
+
+

{{ "noTwoStepProviders" | i18n }}

+

{{ "noTwoStepProviders2" | i18n }}

+
+
+

+ +

+

+ +

+
+ diff --git a/src/popup/accounts/two-factor.component.ts b/src/popup/accounts/two-factor.component.ts index ed7d91494d..231fcba701 100644 --- a/src/popup/accounts/two-factor.component.ts +++ b/src/popup/accounts/two-factor.component.ts @@ -1,116 +1,140 @@ -import { Component } from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; -import { first } from 'rxjs/operators'; +import { Component } from "@angular/core"; +import { ActivatedRoute, 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 { AuthService } from 'jslib-common/abstractions/auth.service'; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.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({ - selector: 'app-two-factor', - templateUrl: 'two-factor.component.html', + selector: "app-two-factor", + templateUrl: "two-factor.component.html", }) export class TwoFactorComponent extends BaseTwoFactorComponent { - showNewWindowMessage = false; + showNewWindowMessage = false; - constructor(authService: AuthService, router: Router, - i18nService: I18nService, apiService: ApiService, - platformUtilsService: PlatformUtilsService, private syncService: SyncService, - environmentService: EnvironmentService, 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); + constructor( + authService: AuthService, + router: Router, + i18nService: I18nService, + apiService: ApiService, + platformUtilsService: PlatformUtilsService, + private syncService: SyncService, + environmentService: EnvironmentService, + 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 = () => { + return syncService.fullSync(true); + }; + super.successRoute = "/tabs/vault"; + this.webAuthnNewTab = + this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); + } + + async ngOnInit() { + if (this.route.snapshot.paramMap.has("webAuthnResponse")) { + // WebAuthn fallback response + this.selectedProviderType = TwoFactorProviderType.WebAuthn; + this.token = this.route.snapshot.paramMap.get("webAuthnResponse"); + super.onSuccessfulLogin = async () => { + this.syncService.fullSync(true); + this.messagingService.send("reloadPopup"); + window.close(); + }; + this.remember = this.route.snapshot.paramMap.get("remember") === "true"; + await this.doSubmit(); + return; + } + + await super.ngOnInit(); + if (this.selectedProviderType == null) { + return; + } + + // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width + // than usual to avoid cutting off the dialog. + if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) { + document.body.classList.add("linux-webauthn"); + } + + if ( + this.selectedProviderType === TwoFactorProviderType.Email && + this.popupUtilsService.inPopup(window) + ) { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("popup2faCloseMessage"), + null, + this.i18nService.t("yes"), + this.i18nService.t("no") + ); + if (confirmed) { + this.popupUtilsService.popOut(window); + } + } + + this.route.queryParams.pipe(first()).subscribe(async (qParams) => { + if (qParams.sso === "true") { super.onSuccessfulLogin = () => { - return syncService.fullSync(true); + BrowserApi.reloadOpenWindows(); + const thisWindow = window.open("", "_self"); + thisWindow.close(); + return this.syncService.fullSync(true); }; - super.successRoute = '/tabs/vault'; - this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); + } + }); + } + + async ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + + if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) { + document.body.classList.remove("linux-webauthn"); } + super.ngOnDestroy(); + } - async ngOnInit() { - if (this.route.snapshot.paramMap.has('webAuthnResponse')) { - // WebAuthn fallback response - this.selectedProviderType = TwoFactorProviderType.WebAuthn; - this.token = this.route.snapshot.paramMap.get('webAuthnResponse'); - super.onSuccessfulLogin = async () => { - this.syncService.fullSync(true); - this.messagingService.send('reloadPopup'); - window.close(); - }; - this.remember = this.route.snapshot.paramMap.get('remember') === 'true'; - await this.doSubmit(); - return; - } + anotherMethod() { + this.router.navigate(["2fa-options"]); + } - await super.ngOnInit(); - if (this.selectedProviderType == null) { - return; - } - - // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width - // than usual to avoid cutting off the dialog. - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) { - document.body.classList.add('linux-webauthn'); - } - - if (this.selectedProviderType === TwoFactorProviderType.Email && - this.popupUtilsService.inPopup(window)) { - const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'), - null, this.i18nService.t('yes'), this.i18nService.t('no')); - if (confirmed) { - this.popupUtilsService.popOut(window); - } - } - - this.route.queryParams.pipe(first()).subscribe(async qParams => { - if (qParams.sso === 'true') { - super.onSuccessfulLogin = () => { - BrowserApi.reloadOpenWindows(); - const thisWindow = window.open('', '_self'); - thisWindow.close(); - return this.syncService.fullSync(true); - }; - } - }); - } - - async ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) { - document.body.classList.remove('linux-webauthn'); - } - super.ngOnDestroy(); - } - - anotherMethod() { - this.router.navigate(['2fa-options']); - } - - async isLinux() { - return (await BrowserApi.getPlatformInfo()).os === 'linux'; - } + async isLinux() { + return (await BrowserApi.getPlatformInfo()).os === "linux"; + } } diff --git a/src/popup/accounts/update-temp-password.component.html b/src/popup/accounts/update-temp-password.component.html index 45785088a9..9b55c4f3ab 100644 --- a/src/popup/accounts/update-temp-password.component.html +++ b/src/popup/accounts/update-temp-password.component.html @@ -1,86 +1,130 @@
-
-
- {{'logOut' | i18n}} +
+ +

+ {{ "updateMasterPassword" | i18n }} +

+
+ +
+
+ + + {{ "updateMasterPasswordWarning" | i18n }} + + + +
+
+
+
+
+ + +
+
+ +
+
+
+
+
-

- {{'updateMasterPassword' | i18n}} -

-
-
+
+
+
+
+
+ + +
+
+ +
-
- - - {{'updateMasterPasswordWarning' | i18n}} - - - -
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
+
+
+
+
+
+ +
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
- +
+ +
+
diff --git a/src/popup/accounts/update-temp-password.component.ts b/src/popup/accounts/update-temp-password.component.ts index b473be0aae..5496c6940e 100644 --- a/src/popup/accounts/update-temp-password.component.ts +++ b/src/popup/accounts/update-temp-password.component.ts @@ -1,65 +1,82 @@ -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SyncService } from "jslib-common/abstractions/sync.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 { - Color: string; - Text: string; - Width: number; + Color: string; + Text: string; + Width: number; } @Component({ - selector: 'app-update-temp-password', - templateUrl: 'update-temp-password.component.html', + selector: "app-update-temp-password", + templateUrl: "update-temp-password.component.html", }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { - get masterPasswordScoreStyle(): MasterPasswordScore { - const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; - switch (this.masterPasswordScore) { - case 4: - return { - Color: 'bg-success', - Text: 'strong', - Width: scoreWidth, - }; - case 3: - return { - Color: 'bg-primary', - Text: 'good', - Width: scoreWidth, - }; - case 2: - return { - Color: 'bg-warning', - Text: 'weak', - Width: scoreWidth, - }; - default: - return { - Color: 'bg-danger', - Text: 'weak', - Width: scoreWidth, - }; - } + get masterPasswordScoreStyle(): MasterPasswordScore { + const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; + switch (this.masterPasswordScore) { + case 4: + return { + Color: "bg-success", + Text: "strong", + Width: scoreWidth, + }; + case 3: + return { + Color: "bg-primary", + Text: "good", + Width: scoreWidth, + }; + case 2: + return { + Color: "bg-warning", + Text: "weak", + Width: scoreWidth, + }; + default: + return { + Color: "bg-danger", + Text: "weak", + Width: scoreWidth, + }; } + } - constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationService, policyService: PolicyService, - cryptoService: CryptoService, userService: UserService, - messagingService: MessagingService, apiService: ApiService, - syncService: SyncService, logService: LogService) { - super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, - userService, messagingService, apiService, syncService, logService); - } + constructor( + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + passwordGenerationService: PasswordGenerationService, + policyService: PolicyService, + cryptoService: CryptoService, + userService: UserService, + messagingService: MessagingService, + apiService: ApiService, + syncService: SyncService, + logService: LogService + ) { + super( + i18nService, + platformUtilsService, + passwordGenerationService, + policyService, + cryptoService, + userService, + messagingService, + apiService, + syncService, + logService + ); + } } diff --git a/src/popup/app-routing.animations.ts b/src/popup/app-routing.animations.ts index e2362bec02..0d358f4c0a 100644 --- a/src/popup/app-routing.animations.ts +++ b/src/popup/app-routing.animations.ts @@ -1,199 +1,213 @@ -import { - animate, - group, - query, - style, - transition, - trigger, -} from '@angular/animations'; +import { animate, group, query, style, transition, trigger } from "@angular/animations"; -import { BrowserApi } from '../browser/browserApi'; +import { BrowserApi } from "../browser/browserApi"; -const queryShown = query(':enter, :leave', [ - style({ position: 'fixed', width: '100%', height: '100%' }), -], { optional: true }); +const queryShown = query( + ":enter, :leave", + [style({ position: "fixed", width: "100%", height: "100%" })], + { + optional: true, + } +); // ref: https://github.com/angular/angular/issues/15477 -const queryChildRoute = query('router-outlet ~ *', [ - style({}), - animate(1, style({})), -], { optional: true }); +const queryChildRoute = query("router-outlet ~ *", [style({}), 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) { - 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 queryTranslate( + direction: string, + axis: string, + from: number, + 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) { - return queryTranslate(direction, 'X', from, to, zIndex); +export function queryTranslateX( + 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) { - return queryTranslate(direction, 'Y', from, to, zIndex); +export function queryTranslateY( + direction: string, + from: number, + to: number, + zIndex: number = 1000 +) { + return queryTranslate(direction, "Y", from, to, zIndex); } const inSlideLeft = [ - queryShown, - group([ - queryTranslateX('enter', 100, 0), - queryTranslateX('leave', 0, -100), - queryChildRoute, - ]), + queryShown, + group([queryTranslateX("enter", 100, 0), queryTranslateX("leave", 0, -100), queryChildRoute]), ]; const outSlideRight = [ - queryShown, - group([ - queryTranslateX('enter', -100, 0), - queryTranslateX('leave', 0, 100), - ]), + queryShown, + group([queryTranslateX("enter", -100, 0), queryTranslateX("leave", 0, 100)]), ]; const inSlideUp = [ - queryShown, - group([ - queryTranslateY('enter', 100, 0, 1010), - queryTranslateY('leave', 0, 0), - queryChildRoute, - ]), + queryShown, + group([queryTranslateY("enter", 100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]), ]; const outSlideDown = [ - queryShown, - group([ - queryTranslateY('enter', 0, 0), - queryTranslateY('leave', 0, 100, 1010), - ]), + queryShown, + group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, 100, 1010)]), ]; const inSlideDown = [ - queryShown, - group([ - queryTranslateY('enter', -100, 0, 1010), - queryTranslateY('leave', 0, 0), - queryChildRoute, - ]), + queryShown, + group([queryTranslateY("enter", -100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]), ]; const outSlideUp = [ - queryShown, - group([ - queryTranslateY('enter', 0, 0), - queryTranslateY('leave', 0, -100, 1010), - ]), + queryShown, + group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, -100, 1010)]), ]; export function tabsToCiphers(fromState: string, toState: string) { - if (fromState == null || toState === null || toState.indexOf('ciphers_') === -1) { - return false; - } - return (fromState.indexOf('ciphers_') === 0 && fromState.indexOf('ciphers_direction=b') === -1) || - fromState === 'tabs'; + if (fromState == null || toState === null || toState.indexOf("ciphers_") === -1) { + return false; + } + return ( + (fromState.indexOf("ciphers_") === 0 && fromState.indexOf("ciphers_direction=b") === -1) || + fromState === "tabs" + ); } export function ciphersToTabs(fromState: string, toState: string) { - if (fromState == null || toState === null || fromState.indexOf('ciphers_') === -1) { - return false; - } - return toState.indexOf('ciphers_direction=b') === 0 || toState === 'tabs'; + if (fromState == null || toState === null || fromState.indexOf("ciphers_") === -1) { + return false; + } + return toState.indexOf("ciphers_direction=b") === 0 || toState === "tabs"; } export function ciphersToView(fromState: string, toState: string) { - if (fromState == null || toState === null) { - return false; - } - return fromState.indexOf('ciphers_') === 0 && - (toState === 'view-cipher' || toState === 'add-cipher' || toState === 'clone-cipher'); + if (fromState == null || toState === null) { + return false; + } + return ( + fromState.indexOf("ciphers_") === 0 && + (toState === "view-cipher" || toState === "add-cipher" || toState === "clone-cipher") + ); } export function viewToCiphers(fromState: string, toState: string) { - if (fromState == null || toState === null) { - return false; - } - return (fromState === 'view-cipher' || fromState === 'add-cipher' || fromState === 'clone-cipher') && - toState.indexOf('ciphers_') === 0; + if (fromState == null || toState === null) { + return false; + } + return ( + (fromState === "view-cipher" || fromState === "add-cipher" || fromState === "clone-cipher") && + toState.indexOf("ciphers_") === 0 + ); } -export const routerTransition = trigger('routerTransition', [ - transition('void => home', inSlideLeft), - transition('void => tabs', inSlideLeft), +export const routerTransition = trigger("routerTransition", [ + transition("void => home", 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 => hint', inSlideUp), - transition('login => tabs, login => 2fa', inSlideLeft), + transition("login => home", outSlideDown), + transition("login => hint", inSlideUp), + 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 => 2fa-options', inSlideUp), - transition('2fa-options => 2fa', outSlideDown), - transition('2fa => tabs', inSlideLeft), + transition("2fa => login", outSlideRight), + transition("2fa => 2fa-options", inSlideUp), + transition("2fa-options => 2fa", outSlideDown), + transition("2fa => tabs", inSlideLeft), - transition(tabsToCiphers, inSlideLeft), - transition(ciphersToTabs, outSlideRight), + transition(tabsToCiphers, inSlideLeft), + transition(ciphersToTabs, outSlideRight), - transition(ciphersToView, inSlideUp), - transition(viewToCiphers, outSlideDown), + transition(ciphersToView, inSlideUp), + transition(viewToCiphers, outSlideDown), - transition('tabs => view-cipher', inSlideUp), - transition('view-cipher => tabs', outSlideDown), + transition("tabs => view-cipher", inSlideUp), + transition("view-cipher => tabs", outSlideDown), - 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("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('view-cipher => clone-cipher', inSlideUp), - transition('clone-cipher => view-cipher, clone-cipher => tabs', outSlideDown), + transition("view-cipher => clone-cipher", inSlideUp), + transition("clone-cipher => view-cipher, clone-cipher => tabs", outSlideDown), - transition('view-cipher => share-cipher', inSlideUp), - transition('share-cipher => view-cipher', outSlideDown), + transition("view-cipher => share-cipher", inSlideUp), + transition("share-cipher => view-cipher", outSlideDown), - transition('tabs => add-cipher', inSlideUp), - transition('add-cipher => tabs', outSlideDown), + transition("tabs => add-cipher", inSlideUp), + transition("add-cipher => tabs", outSlideDown), - transition('generator => generator-history, tabs => generator-history', inSlideLeft), - transition('generator-history => generator, generator-history => tabs', outSlideRight), + transition("generator => generator-history, tabs => generator-history", inSlideLeft), + transition("generator-history => generator, generator-history => tabs", outSlideRight), - transition('add-cipher => generator, edit-cipher => generator, clone-cipher => generator', inSlideUp), - transition('generator => add-cipher, generator => edit-cipher, generator => clone-cipher', outSlideDown), + transition( + "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('attachments => edit-cipher, collections => edit-cipher', outSlideRight), + transition("edit-cipher => attachments, edit-cipher => collections", inSlideLeft), + transition("attachments => edit-cipher, collections => edit-cipher", outSlideRight), - transition('clone-cipher => attachments, clone-cipher => collections', inSlideLeft), - transition('attachments => clone-cipher, collections => clone-cipher', outSlideRight), + transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft), + transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight), - transition('tabs => export', inSlideLeft), - transition('export => tabs', outSlideRight), + transition("tabs => export", inSlideLeft), + transition("export => tabs", outSlideRight), - transition('tabs => folders', inSlideLeft), - transition('folders => tabs', outSlideRight), + transition("tabs => folders", inSlideLeft), + transition("folders => tabs", outSlideRight), - transition('folders => edit-folder, folders => add-folder', inSlideUp), - transition('edit-folder => folders, add-folder => folders', outSlideDown), + transition("folders => edit-folder, folders => add-folder", inSlideUp), + transition("edit-folder => folders, add-folder => folders", outSlideDown), - transition('tabs => sync', inSlideLeft), - transition('sync => tabs', outSlideRight), + transition("tabs => sync", inSlideLeft), + transition("sync => tabs", outSlideRight), - transition('tabs => options', inSlideLeft), - transition('options => tabs', outSlideRight), + transition("tabs => options", inSlideLeft), + transition("options => tabs", outSlideRight), - transition('tabs => premium', inSlideLeft), - transition('premium => tabs', outSlideRight), + transition("tabs => premium", inSlideLeft), + transition("premium => tabs", outSlideRight), - transition('tabs => lock', inSlideDown), + transition("tabs => lock", inSlideDown), - transition('tabs => send-type', inSlideLeft), - transition('send-type => tabs', outSlideRight), + transition("tabs => send-type", inSlideLeft), + transition("send-type => tabs", outSlideRight), - transition('tabs => add-send, send-type => add-send', inSlideUp), - transition('add-send => tabs, add-send => send-type', outSlideDown), + transition("tabs => add-send, send-type => add-send", inSlideUp), + transition("add-send => tabs, add-send => send-type", outSlideDown), - transition('tabs => edit-send, send-type => edit-send', inSlideUp), - transition('edit-send => tabs, edit-send => send-type', outSlideDown), + transition("tabs => edit-send, send-type => edit-send", inSlideUp), + transition("edit-send => tabs, edit-send => send-type", outSlideDown), ]); diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index e9b8c797f9..9c7acd60ed 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -1,355 +1,352 @@ -import { Injectable, NgModule } from '@angular/core'; -import { - ActivatedRouteSnapshot, - RouteReuseStrategy, - RouterModule, - Routes, -} from '@angular/router'; +import { Injectable, NgModule } from "@angular/core"; +import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router"; -import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; -import { LockGuardService } from 'jslib-angular/services/lock-guard.service'; +import { AuthGuardService } from "jslib-angular/services/auth-guard.service"; +import { LockGuardService } from "jslib-angular/services/lock-guard.service"; -import { DebounceNavigationService } from './services/debounceNavigationService'; -import { LaunchGuardService } from './services/launch-guard.service'; +import { DebounceNavigationService } from "./services/debounceNavigationService"; +import { LaunchGuardService } from "./services/launch-guard.service"; -import { EnvironmentComponent } from './accounts/environment.component'; -import { HintComponent } from './accounts/hint.component'; -import { HomeComponent } from './accounts/home.component'; -import { LockComponent } from './accounts/lock.component'; -import { LoginComponent } from './accounts/login.component'; -import { RegisterComponent } from './accounts/register.component'; -import { RemovePasswordComponent } from './accounts/remove-password.component'; -import { SetPasswordComponent } from './accounts/set-password.component'; -import { SsoComponent } from './accounts/sso.component'; -import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; -import { TwoFactorComponent } from './accounts/two-factor.component'; -import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component'; +import { EnvironmentComponent } from "./accounts/environment.component"; +import { HintComponent } from "./accounts/hint.component"; +import { HomeComponent } from "./accounts/home.component"; +import { LockComponent } from "./accounts/lock.component"; +import { LoginComponent } from "./accounts/login.component"; +import { RegisterComponent } from "./accounts/register.component"; +import { RemovePasswordComponent } from "./accounts/remove-password.component"; +import { SetPasswordComponent } from "./accounts/set-password.component"; +import { SsoComponent } from "./accounts/sso.component"; +import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component"; +import { TwoFactorComponent } from "./accounts/two-factor.component"; +import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component"; -import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; -import { PasswordGeneratorComponent } from './generator/password-generator.component'; +import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component"; +import { PasswordGeneratorComponent } from "./generator/password-generator.component"; -import { PrivateModeComponent } from './private-mode.component'; -import { TabsComponent } from './tabs.component'; +import { PrivateModeComponent } from "./private-mode.component"; +import { TabsComponent } from "./tabs.component"; -import { ExcludedDomainsComponent } from './settings/excluded-domains.component'; -import { ExportComponent } from './settings/export.component'; -import { FolderAddEditComponent } from './settings/folder-add-edit.component'; -import { FoldersComponent } from './settings/folders.component'; -import { OptionsComponent } from './settings/options.component'; -import { PremiumComponent } from './settings/premium.component'; -import { SettingsComponent } from './settings/settings.component'; -import { SyncComponent } from './settings/sync.component'; +import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"; +import { ExportComponent } from "./settings/export.component"; +import { FolderAddEditComponent } from "./settings/folder-add-edit.component"; +import { FoldersComponent } from "./settings/folders.component"; +import { OptionsComponent } from "./settings/options.component"; +import { PremiumComponent } from "./settings/premium.component"; +import { SettingsComponent } from "./settings/settings.component"; +import { SyncComponent } from "./settings/sync.component"; -import { AddEditComponent } from './vault/add-edit.component'; -import { AttachmentsComponent } from './vault/attachments.component'; -import { CiphersComponent } from './vault/ciphers.component'; -import { CollectionsComponent } from './vault/collections.component'; -import { CurrentTabComponent } from './vault/current-tab.component'; -import { GroupingsComponent } from './vault/groupings.component'; -import { PasswordHistoryComponent } from './vault/password-history.component'; -import { ShareComponent } from './vault/share.component'; -import { ViewComponent } from './vault/view.component'; +import { AddEditComponent } from "./vault/add-edit.component"; +import { AttachmentsComponent } from "./vault/attachments.component"; +import { CiphersComponent } from "./vault/ciphers.component"; +import { CollectionsComponent } from "./vault/collections.component"; +import { CurrentTabComponent } from "./vault/current-tab.component"; +import { GroupingsComponent } from "./vault/groupings.component"; +import { PasswordHistoryComponent } from "./vault/password-history.component"; +import { ShareComponent } from "./vault/share.component"; +import { ViewComponent } from "./vault/view.component"; -import { SendAddEditComponent } from './send/send-add-edit.component'; -import { SendGroupingsComponent } from './send/send-groupings.component'; -import { SendTypeComponent } from './send/send-type.component'; +import { SendAddEditComponent } from "./send/send-add-edit.component"; +import { SendGroupingsComponent } from "./send/send-groupings.component"; +import { SendTypeComponent } from "./send/send-type.component"; const routes: Routes = [ - { - path: '', - redirectTo: 'home', - pathMatch: 'full', - }, - { - path: 'vault', - redirectTo: '/tabs/vault', - pathMatch: 'full', - }, - { - path: 'home', - component: HomeComponent, - canActivate: [LaunchGuardService], - data: { state: 'home' }, - }, - { - path: 'login', - component: LoginComponent, - canActivate: [LaunchGuardService], - data: { state: 'login' }, - }, - { - path: 'lock', - component: LockComponent, - canActivate: [LockGuardService], - data: { state: 'lock' }, - }, - { - path: '2fa', - component: TwoFactorComponent, - canActivate: [LaunchGuardService], - data: { state: '2fa' }, - }, - { - path: '2fa-options', - component: TwoFactorOptionsComponent, - canActivate: [LaunchGuardService], - data: { state: '2fa-options' }, - }, - { - path: 'sso', - component: SsoComponent, - canActivate: [LaunchGuardService], - data: { state: 'sso' }, - }, - { - path: 'set-password', - component: SetPasswordComponent, - data: { state: 'set-password' }, - }, - { - path: 'remove-password', - component: RemovePasswordComponent, + { + path: "", + redirectTo: "home", + pathMatch: "full", + }, + { + path: "vault", + redirectTo: "/tabs/vault", + pathMatch: "full", + }, + { + path: "home", + component: HomeComponent, + canActivate: [LaunchGuardService], + data: { state: "home" }, + }, + { + path: "login", + component: LoginComponent, + canActivate: [LaunchGuardService], + data: { state: "login" }, + }, + { + path: "lock", + component: LockComponent, + canActivate: [LockGuardService], + data: { state: "lock" }, + }, + { + path: "2fa", + component: TwoFactorComponent, + canActivate: [LaunchGuardService], + data: { state: "2fa" }, + }, + { + path: "2fa-options", + component: TwoFactorOptionsComponent, + canActivate: [LaunchGuardService], + data: { state: "2fa-options" }, + }, + { + path: "sso", + component: SsoComponent, + canActivate: [LaunchGuardService], + data: { state: "sso" }, + }, + { + path: "set-password", + component: SetPasswordComponent, + data: { state: "set-password" }, + }, + { + path: "remove-password", + component: RemovePasswordComponent, + canActivate: [AuthGuardService], + data: { state: "remove-password" }, + }, + { + path: "register", + component: RegisterComponent, + canActivate: [LaunchGuardService], + data: { state: "register" }, + }, + { + path: "hint", + component: HintComponent, + canActivate: [LaunchGuardService], + data: { state: "hint" }, + }, + { + path: "environment", + component: EnvironmentComponent, + canActivate: [LaunchGuardService], + data: { state: "environment" }, + }, + { + path: "ciphers", + component: CiphersComponent, + canActivate: [AuthGuardService], + data: { state: "ciphers" }, + }, + { + path: "view-cipher", + component: ViewComponent, + canActivate: [AuthGuardService], + data: { state: "view-cipher" }, + }, + { + path: "cipher-password-history", + component: PasswordHistoryComponent, + canActivate: [AuthGuardService], + data: { state: "cipher-password-history" }, + }, + { + path: "add-cipher", + component: AddEditComponent, + canActivate: [AuthGuardService, DebounceNavigationService], + data: { state: "add-cipher" }, + runGuardsAndResolvers: "always", + }, + { + path: "edit-cipher", + component: AddEditComponent, + canActivate: [AuthGuardService, DebounceNavigationService], + data: { state: "edit-cipher" }, + runGuardsAndResolvers: "always", + }, + { + path: "share-cipher", + component: ShareComponent, + canActivate: [AuthGuardService], + data: { state: "share-cipher" }, + }, + { + path: "collections", + component: CollectionsComponent, + canActivate: [AuthGuardService], + data: { state: "collections" }, + }, + { + path: "attachments", + component: AttachmentsComponent, + canActivate: [AuthGuardService], + data: { state: "attachments" }, + }, + { + path: "generator", + component: PasswordGeneratorComponent, + canActivate: [AuthGuardService], + data: { state: "generator" }, + }, + { + path: "generator-history", + component: PasswordGeneratorHistoryComponent, + canActivate: [AuthGuardService], + data: { state: "generator-history" }, + }, + { + path: "export", + component: ExportComponent, + canActivate: [AuthGuardService], + data: { state: "export" }, + }, + { + path: "folders", + component: FoldersComponent, + canActivate: [AuthGuardService], + data: { state: "folders" }, + }, + { + path: "add-folder", + component: FolderAddEditComponent, + canActivate: [AuthGuardService], + data: { state: "add-folder" }, + }, + { + path: "edit-folder", + component: FolderAddEditComponent, + canActivate: [AuthGuardService], + data: { state: "edit-folder" }, + }, + { + path: "sync", + component: SyncComponent, + canActivate: [AuthGuardService], + data: { state: "sync" }, + }, + { + path: "excluded-domains", + component: ExcludedDomainsComponent, + canActivate: [AuthGuardService], + data: { state: "excluded-domains" }, + }, + { + path: "premium", + component: PremiumComponent, + canActivate: [AuthGuardService], + data: { state: "premium" }, + }, + { + path: "options", + component: OptionsComponent, + canActivate: [AuthGuardService], + data: { state: "options" }, + }, + { + path: "private-mode", + component: PrivateModeComponent, + data: { state: "private-mode" }, + }, + { + path: "clone-cipher", + component: AddEditComponent, + canActivate: [AuthGuardService], + data: { state: "clone-cipher" }, + }, + { + path: "send-type", + component: SendTypeComponent, + canActivate: [AuthGuardService], + data: { state: "send-type" }, + }, + { + path: "add-send", + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: "add-send" }, + }, + { + path: "edit-send", + component: SendAddEditComponent, + canActivate: [AuthGuardService], + data: { state: "edit-send" }, + }, + { + path: "update-temp-password", + component: UpdateTempPasswordComponent, + canActivate: [AuthGuardService], + data: { state: "update-temp-password" }, + }, + { + path: "tabs", + component: TabsComponent, + data: { state: "tabs" }, + children: [ + { + path: "", + redirectTo: "/tabs/vault", + pathMatch: "full", + }, + { + path: "current", + component: CurrentTabComponent, canActivate: [AuthGuardService], - data: { state: 'remove-password' }, - }, - { - path: 'register', - component: RegisterComponent, - canActivate: [LaunchGuardService], - data: { state: 'register' }, - }, - { - path: 'hint', - component: HintComponent, - canActivate: [LaunchGuardService], - data: { state: 'hint' }, - }, - { - path: 'environment', - component: EnvironmentComponent, - canActivate: [LaunchGuardService], - data: { state: 'environment' }, - }, - { - path: 'ciphers', - component: CiphersComponent, + data: { state: "tabs_current" }, + runGuardsAndResolvers: "always", + }, + { + path: "vault", + component: GroupingsComponent, canActivate: [AuthGuardService], - data: { state: 'ciphers' }, - }, - { - path: 'view-cipher', - component: ViewComponent, - canActivate: [AuthGuardService], - data: { state: 'view-cipher' }, - }, - { - path: 'cipher-password-history', - component: PasswordHistoryComponent, - canActivate: [AuthGuardService], - data: { state: 'cipher-password-history' }, - }, - { - path: 'add-cipher', - component: AddEditComponent, - canActivate: [AuthGuardService, DebounceNavigationService], - data: { state: 'add-cipher' }, - runGuardsAndResolvers: 'always', - }, - { - path: 'edit-cipher', - component: AddEditComponent, - canActivate: [AuthGuardService, DebounceNavigationService], - data: { state: 'edit-cipher' }, - runGuardsAndResolvers: 'always', - }, - { - path: 'share-cipher', - component: ShareComponent, - canActivate: [AuthGuardService], - data: { state: 'share-cipher' }, - }, - { - path: 'collections', - component: CollectionsComponent, - canActivate: [AuthGuardService], - data: { state: 'collections' }, - }, - { - path: 'attachments', - component: AttachmentsComponent, - canActivate: [AuthGuardService], - data: { state: 'attachments' }, - }, - { - path: 'generator', + data: { state: "tabs_vault" }, + }, + { + path: "generator", component: PasswordGeneratorComponent, canActivate: [AuthGuardService], - data: { state: 'generator' }, - }, - { - path: 'generator-history', - component: PasswordGeneratorHistoryComponent, + data: { state: "tabs_generator" }, + }, + { + path: "settings", + component: SettingsComponent, canActivate: [AuthGuardService], - data: { state: 'generator-history' }, - }, - { - path: 'export', - component: ExportComponent, + data: { state: "tabs_settings" }, + }, + { + path: "send", + component: SendGroupingsComponent, canActivate: [AuthGuardService], - data: { state: 'export' }, - }, - { - path: 'folders', - component: FoldersComponent, - canActivate: [AuthGuardService], - data: { state: 'folders' }, - }, - { - path: 'add-folder', - component: FolderAddEditComponent, - canActivate: [AuthGuardService], - data: { state: 'add-folder' }, - }, - { - path: 'edit-folder', - component: FolderAddEditComponent, - canActivate: [AuthGuardService], - data: { state: 'edit-folder' }, - }, - { - path: 'sync', - component: SyncComponent, - canActivate: [AuthGuardService], - data: { state: 'sync' }, - }, - { - path: 'excluded-domains', - component: ExcludedDomainsComponent, - canActivate: [AuthGuardService], - data: { state: 'excluded-domains' }, - }, - { - path: 'premium', - component: PremiumComponent, - canActivate: [AuthGuardService], - data: { state: 'premium' }, - }, - { - path: 'options', - component: OptionsComponent, - canActivate: [AuthGuardService], - data: { state: 'options' }, - }, - { - path: 'private-mode', - component: PrivateModeComponent, - data: { state: 'private-mode' }, - }, - { - path: 'clone-cipher', - component: AddEditComponent, - canActivate: [AuthGuardService], - data: { state: 'clone-cipher' }, - }, - { - path: 'send-type', - component: SendTypeComponent, - canActivate: [AuthGuardService], - data: { state: 'send-type' }, - }, - { - path: 'add-send', - component: SendAddEditComponent, - canActivate: [AuthGuardService], - data: { state: 'add-send' }, - }, - { - path: 'edit-send', - component: SendAddEditComponent, - canActivate: [AuthGuardService], - data: { state: 'edit-send' }, - }, - { - path: 'update-temp-password', - component: UpdateTempPasswordComponent, - canActivate: [AuthGuardService], - data: { state: 'update-temp-password' }, - }, - { - path: 'tabs', - component: TabsComponent, - data: { state: 'tabs' }, - children: [ - { - path: '', - redirectTo: '/tabs/vault', - pathMatch: 'full', - }, - { - path: 'current', - component: CurrentTabComponent, - canActivate: [AuthGuardService], - data: { state: 'tabs_current' }, - runGuardsAndResolvers: 'always', - }, - { - path: 'vault', - component: GroupingsComponent, - canActivate: [AuthGuardService], - data: { state: 'tabs_vault' }, - }, - { - path: 'generator', - component: PasswordGeneratorComponent, - canActivate: [AuthGuardService], - data: { state: 'tabs_generator' }, - }, - { - path: 'settings', - component: SettingsComponent, - canActivate: [AuthGuardService], - data: { state: 'tabs_settings' }, - }, - { - path: 'send', - component: SendGroupingsComponent, - canActivate: [AuthGuardService], - data: { state: 'tabs_send' }, - }, - ], - }, + data: { state: "tabs_send" }, + }, + ], + }, ]; @Injectable() export class NoRouteReuseStrategy implements RouteReuseStrategy { - shouldDetach(route: ActivatedRouteSnapshot) { - return false; - } + shouldDetach(route: ActivatedRouteSnapshot) { + return false; + } - store(route: ActivatedRouteSnapshot, handle: {}) { /* Nothing */ } + store(route: ActivatedRouteSnapshot, handle: {}) { + /* Nothing */ + } - shouldAttach(route: ActivatedRouteSnapshot) { - return false; - } + shouldAttach(route: ActivatedRouteSnapshot) { + return false; + } - retrieve(route: ActivatedRouteSnapshot): any { - return null; - } + retrieve(route: ActivatedRouteSnapshot): any { + return null; + } - shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) { - return false; - } + shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) { + return false; + } } @NgModule({ - imports: [RouterModule.forRoot(routes, { - useHash: true, - onSameUrlNavigation: 'reload', - /*enableTracing: true,*/ - })], - exports: [RouterModule], - providers: [ - { provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy }, - ], + imports: [ + RouterModule.forRoot(routes, { + useHash: true, + onSameUrlNavigation: "reload", + /*enableTracing: true,*/ + }), + ], + exports: [RouterModule], + providers: [{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy }], }) -export class AppRoutingModule { } +export class AppRoutingModule {} diff --git a/src/popup/app.component.ts b/src/popup/app.component.ts index bd3bb1fda2..635b88504a 100644 --- a/src/popup/app.component.ts +++ b/src/popup/app.component.ts @@ -1,243 +1,254 @@ -import { - ChangeDetectorRef, - Component, - NgZone, - OnInit, - SecurityContext, -} 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 { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } 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 { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { AuthService } from "jslib-common/abstractions/auth.service"; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "jslib-common/abstractions/state.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({ - selector: 'app-root', - styles: [], - animations: [routerTransition], - template: ` -
- -
`, + selector: "app-root", + styles: [], + animations: [routerTransition], + template: `
+ +
`, }) export class AppComponent implements OnInit { + private lastActivity: number = null; - private lastActivity: number = null; + constructor( + private toastrService: ToastrService, + private storageService: StorageService, + private broadcasterService: BroadcasterService, + private authService: AuthService, + private i18nService: I18nService, + private router: Router, + private stateService: StateService, + private messagingService: MessagingService, + private changeDetectorRef: ChangeDetectorRef, + private ngZone: NgZone, + private sanitizer: DomSanitizer, + private platformUtilsService: PlatformUtilsService, + private keyConnectoService: KeyConnectorService + ) {} - constructor(private toastrService: ToastrService, private storageService: StorageService, - private broadcasterService: BroadcasterService, private authService: AuthService, - private i18nService: I18nService, private router: Router, - private stateService: StateService, private messagingService: MessagingService, - private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, - private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService, - private keyConnectoService: KeyConnectorService) { } + ngOnInit() { + if (BrowserApi.getBackgroundPage() == null) { + return; + } - ngOnInit() { - if (BrowserApi.getBackgroundPage() == null) { - return; - } + this.ngZone.runOutsideAngular(() => { + window.onmousemove = () => this.recordActivity(); + window.onmousedown = () => this.recordActivity(); + window.ontouchstart = () => this.recordActivity(); + window.onclick = () => this.recordActivity(); + window.onscroll = () => this.recordActivity(); + window.onkeypress = () => this.recordActivity(); + }); - this.ngZone.runOutsideAngular(() => { - window.onmousemove = () => this.recordActivity(); - window.onmousedown = () => this.recordActivity(); - window.ontouchstart = () => this.recordActivity(); - window.onclick = () => this.recordActivity(); - window.onscroll = () => this.recordActivity(); - window.onkeypress = () => this.recordActivity(); + (window as any).bitwardenPopupMainMessageListener = async ( + msg: any, + sender: any, + sendResponse: any + ) => { + if (msg.command === "doneLoggingOut") { + this.ngZone.run(async () => { + this.authService.logOut(() => { + if (msg.expired) { + this.showToast({ + type: "warning", + title: this.i18nService.t("loggedOut"), + text: this.i18nService.t("loginExpired"), + }); + } + this.router.navigate(["home"]); + this.stateService.purge(); + }); + this.changeDetectorRef.detectChanges(); }); - - (window as any).bitwardenPopupMainMessageListener = async (msg: any, sender: any, sendResponse: any) => { - if (msg.command === 'doneLoggingOut') { - this.ngZone.run(async () => { - this.authService.logOut(() => { - if (msg.expired) { - this.showToast({ - type: 'warning', - title: this.i18nService.t('loggedOut'), - text: this.i18nService.t('loginExpired'), - }); - } - this.router.navigate(['home']); - this.stateService.purge(); - }); - this.changeDetectorRef.detectChanges(); - }); - } else if (msg.command === 'authBlocked') { - this.ngZone.run(() => { - this.router.navigate(['home']); - }); - } else if (msg.command === 'locked') { - this.stateService.purge(); - this.ngZone.run(() => { - this.router.navigate(['lock']); - }); - } else if (msg.command === 'showDialog') { - await this.showDialog(msg); - } else if (msg.command === 'showToast') { - this.ngZone.run(() => { - this.showToast(msg); - }); - } else if (msg.command === 'reloadProcess') { - const windowReload = this.platformUtilsService.isSafari() || - this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera(); - if (windowReload) { - // Wait to make sure background has reloaded first. - window.setTimeout(() => BrowserApi.reloadExtension(window), 2000); - } - } else if (msg.command === 'reloadPopup') { - this.ngZone.run(() => { - this.router.navigate(['/']); - }); - } else if (msg.command === 'convertAccountToKeyConnector') { - this.ngZone.run(async () => { - await this.keyConnectoService.setConvertAccountRequired(true); - this.router.navigate(['/remove-password']); - }); - } else { - msg.webExtSender = sender; - this.broadcasterService.send(msg); - } - }; - - BrowserApi.messageListener('app.component', (window as any).bitwardenPopupMainMessageListener); - - this.router.events.subscribe(event => { - if (event instanceof NavigationEnd) { - const url = event.urlAfterRedirects || event.url || ''; - if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null && - (window as any).previousPopupUrl.startsWith('/tabs/')) { - this.stateService.remove('GroupingsComponent'); - this.stateService.remove('GroupingsComponentScope'); - this.stateService.remove('CiphersComponent'); - this.stateService.remove('SendGroupingsComponent'); - this.stateService.remove('SendGroupingsComponentScope'); - this.stateService.remove('SendTypeComponent'); - } - if (url.startsWith('/tabs/')) { - this.stateService.remove('addEditCipherInfo'); - } - (window as any).previousPopupUrl = url; - - // Clear route direction after animation (400ms) - if ((window as any).routeDirection != null) { - window.setTimeout(() => { - (window as any).routeDirection = null; - }, 400); - } - } + } else if (msg.command === "authBlocked") { + this.ngZone.run(() => { + this.router.navigate(["home"]); }); - } - - getState(outlet: RouterOutlet) { - if (outlet.activatedRouteData.state === 'ciphers') { - const routeDirection = (window as any).routeDirection != null ? (window as any).routeDirection : ''; - return 'ciphers_direction=' + routeDirection + '_' + - (outlet.activatedRoute.queryParams as any).value.folderId + '_' + - (outlet.activatedRoute.queryParams as any).value.collectionId; - } else { - return outlet.activatedRouteData.state; - } - } - - private async recordActivity() { - const now = (new Date()).getTime(); - if (this.lastActivity != null && now - this.lastActivity < 250) { - return; - } - - this.lastActivity = now; - this.storageService.save(ConstantsService.lastActiveKey, now); - } - - private showToast(msg: any) { - let message = ''; - - const options: Partial = {}; - - if (typeof (msg.text) === 'string') { - message = msg.text; - } else if (msg.text.length === 1) { - message = msg.text[0]; - } else { - msg.text.forEach((t: string) => - message += ('

' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '

')); - options.enableHtml = true; - } - if (msg.options != null) { - if (msg.options.trustedHtml === true) { - options.enableHtml = true; - } - if (msg.options.timeout != null && msg.options.timeout > 0) { - options.timeOut = msg.options.timeout; - } - } - - this.toastrService.show(message, msg.title, options, 'toast-' + msg.type); - } - - private async showDialog(msg: any) { - let iconClasses: string = null; - const type = msg.type; - if (type != null) { - // If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed. - switch (type) { - case 'success': - iconClasses = 'fa-check text-success'; - break; - case 'warning': - iconClasses = 'fa-warning text-warning'; - break; - case 'error': - iconClasses = 'fa-bolt text-danger'; - break; - case 'info': - iconClasses = 'fa-info-circle text-info'; - break; - default: - break; - } - } - - const cancelText = msg.cancelText; - const confirmText = msg.confirmText; - const confirmed = await Swal.fire({ - heightAuto: false, - buttonsStyling: false, - icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml. - iconHtml: iconClasses != null ? `` : undefined, - text: msg.text, - html: msg.html, - titleText: msg.title, - showCancelButton: (cancelText != null), - cancelButtonText: cancelText, - showConfirmButton: true, - confirmButtonText: confirmText == null ? this.i18nService.t('ok') : confirmText, - timer: 300000, + } else if (msg.command === "locked") { + this.stateService.purge(); + this.ngZone.run(() => { + this.router.navigate(["lock"]); }); - - this.messagingService.send('showDialogResolve', { - dialogId: msg.dialogId, - confirmed: confirmed.value, + } else if (msg.command === "showDialog") { + await this.showDialog(msg); + } else if (msg.command === "showToast") { + this.ngZone.run(() => { + this.showToast(msg); }); + } else if (msg.command === "reloadProcess") { + const windowReload = + this.platformUtilsService.isSafari() || + this.platformUtilsService.isFirefox() || + this.platformUtilsService.isOpera(); + if (windowReload) { + // Wait to make sure background has reloaded first. + window.setTimeout(() => BrowserApi.reloadExtension(window), 2000); + } + } else if (msg.command === "reloadPopup") { + this.ngZone.run(() => { + this.router.navigate(["/"]); + }); + } else if (msg.command === "convertAccountToKeyConnector") { + this.ngZone.run(async () => { + await this.keyConnectoService.setConvertAccountRequired(true); + this.router.navigate(["/remove-password"]); + }); + } else { + msg.webExtSender = sender; + this.broadcasterService.send(msg); + } + }; + + BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener); + + this.router.events.subscribe((event) => { + if (event instanceof NavigationEnd) { + const url = event.urlAfterRedirects || event.url || ""; + if ( + url.startsWith("/tabs/") && + (window as any).previousPopupUrl != null && + (window as any).previousPopupUrl.startsWith("/tabs/") + ) { + this.stateService.remove("GroupingsComponent"); + this.stateService.remove("GroupingsComponentScope"); + this.stateService.remove("CiphersComponent"); + this.stateService.remove("SendGroupingsComponent"); + this.stateService.remove("SendGroupingsComponentScope"); + this.stateService.remove("SendTypeComponent"); + } + if (url.startsWith("/tabs/")) { + this.stateService.remove("addEditCipherInfo"); + } + (window as any).previousPopupUrl = url; + + // Clear route direction after animation (400ms) + if ((window as any).routeDirection != null) { + window.setTimeout(() => { + (window as any).routeDirection = null; + }, 400); + } + } + }); + } + + getState(outlet: RouterOutlet) { + if (outlet.activatedRouteData.state === "ciphers") { + const routeDirection = + (window as any).routeDirection != null ? (window as any).routeDirection : ""; + return ( + "ciphers_direction=" + + routeDirection + + "_" + + (outlet.activatedRoute.queryParams as any).value.folderId + + "_" + + (outlet.activatedRoute.queryParams as any).value.collectionId + ); + } else { + return outlet.activatedRouteData.state; } + } + + private async recordActivity() { + const now = new Date().getTime(); + if (this.lastActivity != null && now - this.lastActivity < 250) { + return; + } + + this.lastActivity = now; + this.storageService.save(ConstantsService.lastActiveKey, now); + } + + private showToast(msg: any) { + let message = ""; + + const options: Partial = {}; + + if (typeof msg.text === "string") { + message = msg.text; + } else if (msg.text.length === 1) { + message = msg.text[0]; + } else { + msg.text.forEach( + (t: string) => + (message += "

" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "

") + ); + options.enableHtml = true; + } + if (msg.options != null) { + if (msg.options.trustedHtml === true) { + options.enableHtml = true; + } + if (msg.options.timeout != null && msg.options.timeout > 0) { + options.timeOut = msg.options.timeout; + } + } + + this.toastrService.show(message, msg.title, options, "toast-" + msg.type); + } + + private async showDialog(msg: any) { + let iconClasses: string = null; + const type = msg.type; + if (type != null) { + // If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed. + switch (type) { + case "success": + iconClasses = "fa-check text-success"; + break; + case "warning": + iconClasses = "fa-warning text-warning"; + break; + case "error": + iconClasses = "fa-bolt text-danger"; + break; + case "info": + iconClasses = "fa-info-circle text-info"; + break; + default: + break; + } + } + + const cancelText = msg.cancelText; + const confirmText = msg.confirmText; + const confirmed = await Swal.fire({ + heightAuto: false, + buttonsStyling: false, + icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml. + iconHtml: + iconClasses != null ? `` : undefined, + text: msg.text, + html: msg.html, + titleText: msg.title, + showCancelButton: cancelText != null, + cancelButtonText: cancelText, + showConfirmButton: true, + confirmButtonText: confirmText == null ? this.i18nService.t("ok") : confirmText, + timer: 300000, + }); + + this.messagingService.send("showDialogResolve", { + dialogId: msg.dialogId, + confirmed: confirmed.value, + }); + } } diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index 8b7f1258d7..9d86c7a029 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -1,272 +1,265 @@ -import { A11yModule } from '@angular/cdk/a11y'; -import { DragDropModule } from '@angular/cdk/drag-drop'; -import { ScrollingModule } from '@angular/cdk/scrolling'; +import { A11yModule } from "@angular/cdk/a11y"; +import { DragDropModule } from "@angular/cdk/drag-drop"; +import { ScrollingModule } from "@angular/cdk/scrolling"; -import { AppRoutingModule } from './app-routing.module'; -import { ServicesModule } from './services/services.module'; +import { AppRoutingModule } from "./app-routing.module"; +import { ServicesModule } from "./services/services.module"; -import { NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { BrowserModule } from "@angular/platform-browser"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { EnvironmentComponent } from './accounts/environment.component'; -import { HintComponent } from './accounts/hint.component'; -import { HomeComponent } from './accounts/home.component'; -import { LockComponent } from './accounts/lock.component'; -import { LoginComponent } from './accounts/login.component'; -import { RegisterComponent } from './accounts/register.component'; -import { RemovePasswordComponent } from './accounts/remove-password.component'; -import { SetPasswordComponent } from './accounts/set-password.component'; -import { SsoComponent } from './accounts/sso.component'; -import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; -import { TwoFactorComponent } from './accounts/two-factor.component'; -import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component'; +import { EnvironmentComponent } from "./accounts/environment.component"; +import { HintComponent } from "./accounts/hint.component"; +import { HomeComponent } from "./accounts/home.component"; +import { LockComponent } from "./accounts/lock.component"; +import { LoginComponent } from "./accounts/login.component"; +import { RegisterComponent } from "./accounts/register.component"; +import { RemovePasswordComponent } from "./accounts/remove-password.component"; +import { SetPasswordComponent } from "./accounts/set-password.component"; +import { SsoComponent } from "./accounts/sso.component"; +import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component"; +import { TwoFactorComponent } from "./accounts/two-factor.component"; +import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component"; -import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; -import { PasswordGeneratorComponent } from './generator/password-generator.component'; +import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component"; +import { PasswordGeneratorComponent } from "./generator/password-generator.component"; -import { AppComponent } from './app.component'; -import { PrivateModeComponent } from './private-mode.component'; -import { TabsComponent } from './tabs.component'; +import { AppComponent } from "./app.component"; +import { PrivateModeComponent } from "./private-mode.component"; +import { TabsComponent } from "./tabs.component"; -import { ExcludedDomainsComponent } from './settings/excluded-domains.component'; -import { ExportComponent } from './settings/export.component'; -import { FolderAddEditComponent } from './settings/folder-add-edit.component'; -import { FoldersComponent } from './settings/folders.component'; -import { OptionsComponent } from './settings/options.component'; -import { PremiumComponent } from './settings/premium.component'; -import { SettingsComponent } from './settings/settings.component'; -import { SyncComponent } from './settings/sync.component'; -import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component'; +import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"; +import { ExportComponent } from "./settings/export.component"; +import { FolderAddEditComponent } from "./settings/folder-add-edit.component"; +import { FoldersComponent } from "./settings/folders.component"; +import { OptionsComponent } from "./settings/options.component"; +import { PremiumComponent } from "./settings/premium.component"; +import { SettingsComponent } from "./settings/settings.component"; +import { SyncComponent } from "./settings/sync.component"; +import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component"; -import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component'; -import { AddEditComponent } from './vault/add-edit.component'; -import { AttachmentsComponent } from './vault/attachments.component'; -import { CiphersComponent } from './vault/ciphers.component'; -import { CollectionsComponent } from './vault/collections.component'; -import { CurrentTabComponent } from './vault/current-tab.component'; -import { GroupingsComponent } from './vault/groupings.component'; -import { PasswordHistoryComponent } from './vault/password-history.component'; -import { ShareComponent } from './vault/share.component'; -import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component'; -import { ViewComponent } from './vault/view.component'; +import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component"; +import { AddEditComponent } from "./vault/add-edit.component"; +import { AttachmentsComponent } from "./vault/attachments.component"; +import { CiphersComponent } from "./vault/ciphers.component"; +import { CollectionsComponent } from "./vault/collections.component"; +import { CurrentTabComponent } from "./vault/current-tab.component"; +import { GroupingsComponent } from "./vault/groupings.component"; +import { PasswordHistoryComponent } from "./vault/password-history.component"; +import { ShareComponent } from "./vault/share.component"; +import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component"; +import { ViewComponent } from "./vault/view.component"; -import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component'; -import { SendAddEditComponent } from './send/send-add-edit.component'; -import { SendGroupingsComponent } from './send/send-groupings.component'; -import { SendTypeComponent } from './send/send-type.component'; +import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component"; +import { SendAddEditComponent } from "./send/send-add-edit.component"; +import { SendGroupingsComponent } from "./send/send-groupings.component"; +import { SendTypeComponent } from "./send/send-type.component"; -import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive'; -import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive'; -import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive'; -import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive'; -import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive'; -import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive'; -import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive'; -import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive'; -import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive'; -import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive'; -import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive'; -import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive'; +import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive"; +import { ApiActionDirective } from "jslib-angular/directives/api-action.directive"; +import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive"; +import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive"; +import { BoxRowDirective } from "jslib-angular/directives/box-row.directive"; +import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive"; +import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive"; +import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive"; +import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive"; +import { StopClickDirective } from "jslib-angular/directives/stop-click.directive"; +import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive"; +import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive"; -import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe'; -import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe'; -import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe'; +import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe"; +import { I18nPipe } from "jslib-angular/pipes/i18n.pipe"; +import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe"; -import { ActionButtonsComponent } from './components/action-buttons.component'; -import { CipherRowComponent } from './components/cipher-row.component'; -import { PasswordRepromptComponent } from './components/password-reprompt.component'; -import { PopOutComponent } from './components/pop-out.component'; -import { SendListComponent } from './components/send-list.component'; -import { SetPinComponent } from './components/set-pin.component'; -import { VerifyMasterPasswordComponent } from './components/verify-master-password.component'; +import { ActionButtonsComponent } from "./components/action-buttons.component"; +import { CipherRowComponent } from "./components/cipher-row.component"; +import { PasswordRepromptComponent } from "./components/password-reprompt.component"; +import { PopOutComponent } from "./components/pop-out.component"; +import { SendListComponent } from "./components/send-list.component"; +import { SetPinComponent } from "./components/set-pin.component"; +import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component"; -import { CalloutComponent } from 'jslib-angular/components/callout.component'; -import { IconComponent } from 'jslib-angular/components/icon.component'; -import { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; +import { CalloutComponent } from "jslib-angular/components/callout.component"; +import { IconComponent } from "jslib-angular/components/icon.component"; +import { BitwardenToastModule } from "jslib-angular/components/toastr.component"; -import { - CurrencyPipe, - DatePipe, - registerLocaleData, -} from '@angular/common'; -import localeAz from '@angular/common/locales/az'; -import localeBe from '@angular/common/locales/be'; -import localeBg from '@angular/common/locales/bg'; -import localeBn from '@angular/common/locales/bn'; -import localeCa from '@angular/common/locales/ca'; -import localeCs from '@angular/common/locales/cs'; -import localeDa from '@angular/common/locales/da'; -import localeDe from '@angular/common/locales/de'; -import localeEl from '@angular/common/locales/el'; -import localeEnGb from '@angular/common/locales/en-GB'; -import localeEnIn from '@angular/common/locales/en-IN'; -import localeEs from '@angular/common/locales/es'; -import localeEt from '@angular/common/locales/et'; -import localeFa from '@angular/common/locales/fa'; -import localeFi from '@angular/common/locales/fi'; -import localeFr from '@angular/common/locales/fr'; -import localeHe from '@angular/common/locales/he'; -import localeHr from '@angular/common/locales/hr'; -import localeHu from '@angular/common/locales/hu'; -import localeId from '@angular/common/locales/id'; -import localeIt from '@angular/common/locales/it'; -import localeJa from '@angular/common/locales/ja'; -import localeKn from '@angular/common/locales/kn'; -import localeKo from '@angular/common/locales/ko'; -import localeLv from '@angular/common/locales/lv'; -import localeMl from '@angular/common/locales/ml'; -import localeNb from '@angular/common/locales/nb'; -import localeNl from '@angular/common/locales/nl'; -import localePl from '@angular/common/locales/pl'; -import localePtBr from '@angular/common/locales/pt'; -import localePtPt from '@angular/common/locales/pt-PT'; -import localeRo from '@angular/common/locales/ro'; -import localeRu from '@angular/common/locales/ru'; -import localeSk from '@angular/common/locales/sk'; -import localeSr from '@angular/common/locales/sr'; -import localeSv from '@angular/common/locales/sv'; -import localeTh from '@angular/common/locales/th'; -import localeTr from '@angular/common/locales/tr'; -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'; +import { CurrencyPipe, DatePipe, registerLocaleData } from "@angular/common"; +import localeAz from "@angular/common/locales/az"; +import localeBe from "@angular/common/locales/be"; +import localeBg from "@angular/common/locales/bg"; +import localeBn from "@angular/common/locales/bn"; +import localeCa from "@angular/common/locales/ca"; +import localeCs from "@angular/common/locales/cs"; +import localeDa from "@angular/common/locales/da"; +import localeDe from "@angular/common/locales/de"; +import localeEl from "@angular/common/locales/el"; +import localeEnGb from "@angular/common/locales/en-GB"; +import localeEnIn from "@angular/common/locales/en-IN"; +import localeEs from "@angular/common/locales/es"; +import localeEt from "@angular/common/locales/et"; +import localeFa from "@angular/common/locales/fa"; +import localeFi from "@angular/common/locales/fi"; +import localeFr from "@angular/common/locales/fr"; +import localeHe from "@angular/common/locales/he"; +import localeHr from "@angular/common/locales/hr"; +import localeHu from "@angular/common/locales/hu"; +import localeId from "@angular/common/locales/id"; +import localeIt from "@angular/common/locales/it"; +import localeJa from "@angular/common/locales/ja"; +import localeKn from "@angular/common/locales/kn"; +import localeKo from "@angular/common/locales/ko"; +import localeLv from "@angular/common/locales/lv"; +import localeMl from "@angular/common/locales/ml"; +import localeNb from "@angular/common/locales/nb"; +import localeNl from "@angular/common/locales/nl"; +import localePl from "@angular/common/locales/pl"; +import localePtBr from "@angular/common/locales/pt"; +import localePtPt from "@angular/common/locales/pt-PT"; +import localeRo from "@angular/common/locales/ro"; +import localeRu from "@angular/common/locales/ru"; +import localeSk from "@angular/common/locales/sk"; +import localeSr from "@angular/common/locales/sr"; +import localeSv from "@angular/common/locales/sv"; +import localeTh from "@angular/common/locales/th"; +import localeTr from "@angular/common/locales/tr"; +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(localeBe, 'be'); -registerLocaleData(localeBg, 'bg'); -registerLocaleData(localeBn, 'bn'); -registerLocaleData(localeCa, 'ca'); -registerLocaleData(localeCs, 'cs'); -registerLocaleData(localeDa, 'da'); -registerLocaleData(localeDe, 'de'); -registerLocaleData(localeEl, 'el'); -registerLocaleData(localeEnGb, 'en-GB'); -registerLocaleData(localeEnIn, 'en-IN'); -registerLocaleData(localeEs, 'es'); -registerLocaleData(localeEt, 'et'); -registerLocaleData(localeFa, 'fa'); -registerLocaleData(localeFi, 'fi'); -registerLocaleData(localeFr, 'fr'); -registerLocaleData(localeHe, 'he'); -registerLocaleData(localeHr, 'hr'); -registerLocaleData(localeHu, 'hu'); -registerLocaleData(localeId, 'id'); -registerLocaleData(localeIt, 'it'); -registerLocaleData(localeJa, 'ja'); -registerLocaleData(localeKo, 'ko'); -registerLocaleData(localeKn, 'kn'); -registerLocaleData(localeLv, 'lv'); -registerLocaleData(localeMl, 'ml'); -registerLocaleData(localeNb, 'nb'); -registerLocaleData(localeNl, 'nl'); -registerLocaleData(localePl, 'pl'); -registerLocaleData(localePtBr, 'pt-BR'); -registerLocaleData(localePtPt, 'pt-PT'); -registerLocaleData(localeRo, 'ro'); -registerLocaleData(localeRu, 'ru'); -registerLocaleData(localeSk, 'sk'); -registerLocaleData(localeSr, 'sr'); -registerLocaleData(localeSv, 'sv'); -registerLocaleData(localeTh, 'th'); -registerLocaleData(localeTr, 'tr'); -registerLocaleData(localeUk, 'uk'); -registerLocaleData(localeVi, 'vi'); -registerLocaleData(localeZhCn, 'zh-CN'); -registerLocaleData(localeZhTw, 'zh-TW'); +registerLocaleData(localeAz, "az"); +registerLocaleData(localeBe, "be"); +registerLocaleData(localeBg, "bg"); +registerLocaleData(localeBn, "bn"); +registerLocaleData(localeCa, "ca"); +registerLocaleData(localeCs, "cs"); +registerLocaleData(localeDa, "da"); +registerLocaleData(localeDe, "de"); +registerLocaleData(localeEl, "el"); +registerLocaleData(localeEnGb, "en-GB"); +registerLocaleData(localeEnIn, "en-IN"); +registerLocaleData(localeEs, "es"); +registerLocaleData(localeEt, "et"); +registerLocaleData(localeFa, "fa"); +registerLocaleData(localeFi, "fi"); +registerLocaleData(localeFr, "fr"); +registerLocaleData(localeHe, "he"); +registerLocaleData(localeHr, "hr"); +registerLocaleData(localeHu, "hu"); +registerLocaleData(localeId, "id"); +registerLocaleData(localeIt, "it"); +registerLocaleData(localeJa, "ja"); +registerLocaleData(localeKo, "ko"); +registerLocaleData(localeKn, "kn"); +registerLocaleData(localeLv, "lv"); +registerLocaleData(localeMl, "ml"); +registerLocaleData(localeNb, "nb"); +registerLocaleData(localeNl, "nl"); +registerLocaleData(localePl, "pl"); +registerLocaleData(localePtBr, "pt-BR"); +registerLocaleData(localePtPt, "pt-PT"); +registerLocaleData(localeRo, "ro"); +registerLocaleData(localeRu, "ru"); +registerLocaleData(localeSk, "sk"); +registerLocaleData(localeSr, "sr"); +registerLocaleData(localeSv, "sv"); +registerLocaleData(localeTh, "th"); +registerLocaleData(localeTr, "tr"); +registerLocaleData(localeUk, "uk"); +registerLocaleData(localeVi, "vi"); +registerLocaleData(localeZhCn, "zh-CN"); +registerLocaleData(localeZhTw, "zh-TW"); @NgModule({ - imports: [ - A11yModule, - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - DragDropModule, - FormsModule, - ReactiveFormsModule, - ScrollingModule, - ServicesModule, - BitwardenToastModule.forRoot({ - maxOpened: 2, - autoDismiss: true, - closeButton: true, - positionClass: 'toast-bottom-full-width', - }), - ], - declarations: [ - A11yTitleDirective, - ActionButtonsComponent, - AddEditComponent, - AddEditCustomFieldsComponent, - ApiActionDirective, - AppComponent, - AttachmentsComponent, - AutofocusDirective, - BlurClickDirective, - BoxRowDirective, - CalloutComponent, - CipherListVirtualScroll, - CipherRowComponent, - CiphersComponent, - CollectionsComponent, - ColorPasswordPipe, - CurrentTabComponent, - EnvironmentComponent, - ExcludedDomainsComponent, - ExportComponent, - FallbackSrcDirective, - FolderAddEditComponent, - FoldersComponent, - GroupingsComponent, - HintComponent, - HomeComponent, - I18nPipe, - IconComponent, - InputVerbatimDirective, - LockComponent, - LoginComponent, - OptionsComponent, - PasswordGeneratorComponent, - PasswordGeneratorHistoryComponent, - PasswordHistoryComponent, - PasswordRepromptComponent, - PopOutComponent, - PremiumComponent, - PrivateModeComponent, - RegisterComponent, - SearchCiphersPipe, - SelectCopyDirective, - SendAddEditComponent, - SendEffluxDatesComponent, - SendGroupingsComponent, - SendListComponent, - SendTypeComponent, - SetPasswordComponent, - SetPinComponent, - SettingsComponent, - ShareComponent, - SsoComponent, - StopClickDirective, - StopPropDirective, - SyncComponent, - TabsComponent, - TrueFalseValueDirective, - TwoFactorComponent, - TwoFactorOptionsComponent, - UpdateTempPasswordComponent, - VaultTimeoutInputComponent, - VerifyMasterPasswordComponent, - ViewComponent, - ViewCustomFieldsComponent, - RemovePasswordComponent, - ], - entryComponents: [], - providers: [ - CurrencyPipe, - DatePipe, - ], - bootstrap: [AppComponent], + imports: [ + A11yModule, + AppRoutingModule, + BrowserAnimationsModule, + BrowserModule, + DragDropModule, + FormsModule, + ReactiveFormsModule, + ScrollingModule, + ServicesModule, + BitwardenToastModule.forRoot({ + maxOpened: 2, + autoDismiss: true, + closeButton: true, + positionClass: "toast-bottom-full-width", + }), + ], + declarations: [ + A11yTitleDirective, + ActionButtonsComponent, + AddEditComponent, + AddEditCustomFieldsComponent, + ApiActionDirective, + AppComponent, + AttachmentsComponent, + AutofocusDirective, + BlurClickDirective, + BoxRowDirective, + CalloutComponent, + CipherListVirtualScroll, + CipherRowComponent, + CiphersComponent, + CollectionsComponent, + ColorPasswordPipe, + CurrentTabComponent, + EnvironmentComponent, + ExcludedDomainsComponent, + ExportComponent, + FallbackSrcDirective, + FolderAddEditComponent, + FoldersComponent, + GroupingsComponent, + HintComponent, + HomeComponent, + I18nPipe, + IconComponent, + InputVerbatimDirective, + LockComponent, + LoginComponent, + OptionsComponent, + PasswordGeneratorComponent, + PasswordGeneratorHistoryComponent, + PasswordHistoryComponent, + PasswordRepromptComponent, + PopOutComponent, + PremiumComponent, + PrivateModeComponent, + RegisterComponent, + SearchCiphersPipe, + SelectCopyDirective, + SendAddEditComponent, + SendEffluxDatesComponent, + SendGroupingsComponent, + SendListComponent, + SendTypeComponent, + SetPasswordComponent, + SetPinComponent, + SettingsComponent, + ShareComponent, + SsoComponent, + StopClickDirective, + StopPropDirective, + SyncComponent, + TabsComponent, + TrueFalseValueDirective, + TwoFactorComponent, + TwoFactorOptionsComponent, + UpdateTempPasswordComponent, + VaultTimeoutInputComponent, + VerifyMasterPasswordComponent, + ViewComponent, + ViewCustomFieldsComponent, + RemovePasswordComponent, + ], + entryComponents: [], + providers: [CurrencyPipe, DatePipe], + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/src/popup/components/action-buttons.component.html b/src/popup/components/action-buttons.component.html index a3caa2c933..20ebd35080 100644 --- a/src/popup/components/action-buttons.component.html +++ b/src/popup/components/action-buttons.component.html @@ -1,41 +1,87 @@ - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - - - + + + diff --git a/src/popup/components/action-buttons.component.ts b/src/popup/components/action-buttons.component.ts index b7cc088ddf..1f073d1079 100644 --- a/src/popup/components/action-buttons.component.ts +++ b/src/popup/components/action-buttons.component.ts @@ -1,82 +1,88 @@ -import { - Component, - EventEmitter, - Input, - Output, -} from '@angular/core'; +import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; -import { CipherType } from 'jslib-common/enums/cipherType'; -import { EventType } from 'jslib-common/enums/eventType'; +import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; +import { CipherType } from "jslib-common/enums/cipherType"; +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 { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { TotpService } from 'jslib-common/abstractions/totp.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { EventService } from "jslib-common/abstractions/event.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { TotpService } from "jslib-common/abstractions/totp.service"; +import { UserService } from "jslib-common/abstractions/user.service"; @Component({ - selector: 'app-action-buttons', - templateUrl: 'action-buttons.component.html', + selector: "app-action-buttons", + templateUrl: "action-buttons.component.html", }) export class ActionButtonsComponent { - @Output() onView = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Input() cipher: CipherView; - @Input() showView = false; + @Output() onView = new EventEmitter(); + @Output() launchEvent = new EventEmitter(); + @Input() cipher: CipherView; + @Input() showView = false; - cipherType = CipherType; - userHasPremiumAccess = false; + cipherType = CipherType; + userHasPremiumAccess = false; - constructor(private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private eventService: EventService, - private totpService: TotpService, private userService: UserService, - private passwordRepromptService: PasswordRepromptService) { } + constructor( + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private eventService: EventService, + private totpService: TotpService, + private userService: UserService, + private passwordRepromptService: PasswordRepromptService + ) {} - async ngOnInit() { - this.userHasPremiumAccess = await this.userService.canAccessPremium(); + async ngOnInit() { + this.userHasPremiumAccess = await this.userService.canAccessPremium(); + } + + launchCipher() { + this.launchEvent.emit(this.cipher); + } + + async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { + if ( + this.cipher.reprompt !== CipherRepromptType.None && + this.passwordRepromptService.protectedFields().includes(aType) && + !(await this.passwordRepromptService.showPasswordPrompt()) + ) { + return; } - launchCipher() { - this.launchEvent.emit(this.cipher); + if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) { + return; + } else if (value === cipher.login.totp) { + value = await this.totpService.getCode(value); } - async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { - if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) && - !await this.passwordRepromptService.showPasswordPrompt()) { - return; - } - - if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) { - return; - } else if (value === cipher.login.totp) { - value = await this.totpService.getCode(value); - } - - if (!cipher.viewPassword) { - return; - } - - this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast('info', null, - this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey))); - - if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') { - this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id); - } else if (typeI18nKey === 'securityCode') { - this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); - } + if (!cipher.viewPassword) { + return; } - displayTotpCopyButton(cipher: CipherView) { - return (cipher?.login?.hasTotp ?? false) && - (cipher.organizationUseTotp || this.userHasPremiumAccess); - } + this.platformUtilsService.copyToClipboard(value, { window: window }); + this.platformUtilsService.showToast( + "info", + null, + this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)) + ); - view() { - this.onView.emit(this.cipher); + if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") { + this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id); + } else if (typeI18nKey === "securityCode") { + this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); } + } + + displayTotpCopyButton(cipher: CipherView) { + return ( + (cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess) + ); + } + + view() { + this.onView.emit(this.cipher); + } } diff --git a/src/popup/components/cipher-row.component.html b/src/popup/components/cipher-row.component.html index 1772566db9..5953c31d88 100644 --- a/src/popup/components/cipher-row.component.html +++ b/src/popup/components/cipher-row.component.html @@ -1,23 +1,38 @@ - diff --git a/src/popup/components/cipher-row.component.ts b/src/popup/components/cipher-row.component.ts index f5e4a31b83..484ccdea2d 100644 --- a/src/popup/components/cipher-row.component.ts +++ b/src/popup/components/cipher-row.component.ts @@ -1,33 +1,28 @@ -import { - Component, - EventEmitter, - Input, - Output, -} from '@angular/core'; +import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { CipherView } from 'jslib-common/models/view/cipherView'; +import { CipherView } from "jslib-common/models/view/cipherView"; @Component({ - selector: 'app-cipher-row', - templateUrl: 'cipher-row.component.html', + selector: "app-cipher-row", + templateUrl: "cipher-row.component.html", }) export class CipherRowComponent { - @Output() onSelected = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Output() onView = new EventEmitter(); - @Input() cipher: CipherView; - @Input() showView = false; - @Input() title: string; + @Output() onSelected = new EventEmitter(); + @Output() launchEvent = new EventEmitter(); + @Output() onView = new EventEmitter(); + @Input() cipher: CipherView; + @Input() showView = false; + @Input() title: string; - selectCipher(c: CipherView) { - this.onSelected.emit(c); - } + selectCipher(c: CipherView) { + this.onSelected.emit(c); + } - launchCipher(c: CipherView) { - this.launchEvent.emit(c); - } + launchCipher(c: CipherView) { + this.launchEvent.emit(c); + } - viewCipher(c: CipherView) { - this.onView.emit(c); - } + viewCipher(c: CipherView) { + this.onView.emit(c); + } } diff --git a/src/popup/components/password-reprompt.component.html b/src/popup/components/password-reprompt.component.html index da74320257..faa383af39 100644 --- a/src/popup/components/password-reprompt.component.html +++ b/src/popup/components/password-reprompt.component.html @@ -1,38 +1,56 @@ +
+ +
+
+ + +
diff --git a/src/popup/components/password-reprompt.component.ts b/src/popup/components/password-reprompt.component.ts index b3204f9422..442a0ab5ee 100644 --- a/src/popup/components/password-reprompt.component.ts +++ b/src/popup/components/password-reprompt.component.ts @@ -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({ - templateUrl: 'password-reprompt.component.html', + templateUrl: "password-reprompt.component.html", }) export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} diff --git a/src/popup/components/pop-out.component.html b/src/popup/components/pop-out.component.html index 2e8d5581c2..fa289ca752 100644 --- a/src/popup/components/pop-out.component.html +++ b/src/popup/components/pop-out.component.html @@ -1,5 +1,5 @@ - + diff --git a/src/popup/components/pop-out.component.ts b/src/popup/components/pop-out.component.ts index d4716685e5..ee1929ce39 100644 --- a/src/popup/components/pop-out.component.ts +++ b/src/popup/components/pop-out.component.ts @@ -1,32 +1,30 @@ -import { - Component, - Input, - OnInit, -} from '@angular/core'; +import { 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({ - selector: 'app-pop-out', - templateUrl: 'pop-out.component.html', + selector: "app-pop-out", + templateUrl: "pop-out.component.html", }) export class PopOutComponent implements OnInit { - @Input() show = true; + @Input() show = true; - constructor(private platformUtilsService: PlatformUtilsService, - private popupUtilsService: PopupUtilsService) { } + constructor( + private platformUtilsService: PlatformUtilsService, + private popupUtilsService: PopupUtilsService + ) {} - ngOnInit() { - if (this.show) { - if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) { - this.show = false; - } - } + ngOnInit() { + if (this.show) { + if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) { + this.show = false; + } } + } - expand() { - this.popupUtilsService.popOut(window); - } + expand() { + this.popupUtilsService.popOut(window); + } } diff --git a/src/popup/components/send-list.component.html b/src/popup/components/send-list.component.html index cbedd7fbe9..09c35990cc 100644 --- a/src/popup/components/send-list.component.html +++ b/src/popup/components/send-list.component.html @@ -1,50 +1,90 @@ - diff --git a/src/popup/components/send-list.component.ts b/src/popup/components/send-list.component.ts index 8e8d6b6f5f..c727de8c76 100644 --- a/src/popup/components/send-list.component.ts +++ b/src/popup/components/send-list.component.ts @@ -1,42 +1,37 @@ -import { - Component, - EventEmitter, - Input, - Output, -} from '@angular/core'; +import { 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({ - selector: 'app-send-list', - templateUrl: 'send-list.component.html', + selector: "app-send-list", + templateUrl: "send-list.component.html", }) export class SendListComponent { - @Input() sends: SendView[]; - @Input() title: string; - @Input() disabledByPolicy = false; - @Output() onSelected = new EventEmitter(); - @Output() onCopySendLink = new EventEmitter(); - @Output() onRemovePassword = new EventEmitter(); - @Output() onDeleteSend = new EventEmitter(); + @Input() sends: SendView[]; + @Input() title: string; + @Input() disabledByPolicy = false; + @Output() onSelected = new EventEmitter(); + @Output() onCopySendLink = new EventEmitter(); + @Output() onRemovePassword = new EventEmitter(); + @Output() onDeleteSend = new EventEmitter(); - sendType = SendType; + sendType = SendType; - selectSend(s: SendView) { - this.onSelected.emit(s); - } + selectSend(s: SendView) { + this.onSelected.emit(s); + } - copySendLink(s: SendView) { - this.onCopySendLink.emit(s); - } + copySendLink(s: SendView) { + this.onCopySendLink.emit(s); + } - removePassword(s: SendView) { - this.onRemovePassword.emit(s); - } + removePassword(s: SendView) { + this.onRemovePassword.emit(s); + } - delete(s: SendView) { - this.onDeleteSend.emit(s); - } + delete(s: SendView) { + this.onDeleteSend.emit(s); + } } diff --git a/src/popup/components/set-pin.component.html b/src/popup/components/set-pin.component.html index 60340a1d68..c08a2a48e8 100644 --- a/src/popup/components/set-pin.component.html +++ b/src/popup/components/set-pin.component.html @@ -1,44 +1,65 @@ + + +
+ +
+ + + + diff --git a/src/popup/components/set-pin.component.ts b/src/popup/components/set-pin.component.ts index 938a32873f..4497f7e774 100644 --- a/src/popup/components/set-pin.component.ts +++ b/src/popup/components/set-pin.component.ts @@ -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({ - templateUrl: 'set-pin.component.html', + templateUrl: "set-pin.component.html", }) export class SetPinComponent extends BaseSetPinComponent {} diff --git a/src/popup/components/verify-master-password.component.html b/src/popup/components/verify-master-password.component.html index bf85c29920..a2745663f4 100644 --- a/src/popup/components/verify-master-password.component.html +++ b/src/popup/components/verify-master-password.component.html @@ -1,25 +1,46 @@ -
- - -
+
+ + +
-
- - - - - {{'codeSent' | i18n}} - -
+
+ + + + + {{ "codeSent" | i18n }} + +
-
- - -
+
+ + +
diff --git a/src/popup/components/verify-master-password.component.ts b/src/popup/components/verify-master-password.component.ts index d51e5dc614..56b36505c5 100644 --- a/src/popup/components/verify-master-password.component.ts +++ b/src/popup/components/verify-master-password.component.ts @@ -1,31 +1,23 @@ -import { - animate, - style, - transition, - trigger, -} from '@angular/animations'; -import { Component } from '@angular/core'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { animate, style, 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({ - selector: 'app-verify-master-password', - templateUrl: 'verify-master-password.component.html', - providers: [ - { - provide: NG_VALUE_ACCESSOR, - multi: true, - useExisting: VerifyMasterPasswordComponent, - }, - ], - animations: [ - trigger('sent', [ - transition(':enter', [ - style({ opacity: 0 }), - animate('100ms', style({ opacity: 1 })), - ]), - ]), - ], + selector: "app-verify-master-password", + templateUrl: "verify-master-password.component.html", + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: VerifyMasterPasswordComponent, + }, + ], + animations: [ + trigger("sent", [ + transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), + ]), + ], }) -export class VerifyMasterPasswordComponent extends BaseComponent { } +export class VerifyMasterPasswordComponent extends BaseComponent {} diff --git a/src/popup/generator/password-generator-history.component.html b/src/popup/generator/password-generator-history.component.html index 67c6f61869..72b0a72854 100644 --- a/src/popup/generator/password-generator-history.component.html +++ b/src/popup/generator/password-generator-history.component.html @@ -1,40 +1,48 @@
-
- -
-

- {{'passwordHistory' | i18n}} -

-
- -
+
+ +
+

+ {{ "passwordHistory" | i18n }} +

+
+ +
-
-
-
-
-
-
- {{h.date | date:'medium'}} -
-
-
- -
-
+
+
+
+
+
+
+ {{ h.date | date: "medium" }} +
+
+ +
+
-
-

{{'noPasswordsInList' | i18n}}

-
+
+
+

{{ "noPasswordsInList" | i18n }}

+
diff --git a/src/popup/generator/password-generator-history.component.ts b/src/popup/generator/password-generator-history.component.ts index fa8a21d0a7..9093ec51d2 100644 --- a/src/popup/generator/password-generator-history.component.ts +++ b/src/popup/generator/password-generator-history.component.ts @@ -1,25 +1,27 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { - PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent, -} from 'jslib-angular/components/password-generator-history.component'; +import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component"; @Component({ - selector: 'app-password-generator-history', - templateUrl: 'password-generator-history.component.html', + selector: "app-password-generator-history", + templateUrl: "password-generator-history.component.html", }) export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { - constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, - i18nService: I18nService, private location: Location) { - super(passwordGenerationService, platformUtilsService, i18nService, window); - } + constructor( + passwordGenerationService: PasswordGenerationService, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + private location: Location + ) { + super(passwordGenerationService, platformUtilsService, i18nService, window); + } - close() { - this.location.back(); - } + close() { + this.location.back(); + } } diff --git a/src/popup/generator/password-generator.component.html b/src/popup/generator/password-generator.component.html index 0c7e555f04..fc846e5ec6 100644 --- a/src/popup/generator/password-generator.component.html +++ b/src/popup/generator/password-generator.component.html @@ -1,128 +1,229 @@
-
- - -
-

- {{'passGen' | i18n}} -

-
- -
+
+ + +
+

+ {{ "passGen" | i18n }} +

+
+ +
- - {{'passwordGeneratorPolicyInEffect' | i18n}} - -
-
+ + {{ "passwordGeneratorPolicyInEffect" | i18n }} + +
+
+
+
+
+ +
-
-
- - -
+
+
+ -
- +
+

+ {{ "options" | i18n }} +

+
+
+ +
+ +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
-

- {{'options' | i18n}} -

-
-
- -
- - -
-
+
+
+ +
+
+ + +
+
+ + +
+
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -
-
-
- - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
+ diff --git a/src/popup/generator/password-generator.component.ts b/src/popup/generator/password-generator.component.ts index d2007d33eb..266e7aadc4 100644 --- a/src/popup/generator/password-generator.component.ts +++ b/src/popup/generator/password-generator.component.ts @@ -1,46 +1,48 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.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 { - PasswordGeneratorComponent as BasePasswordGeneratorComponent, -} from 'jslib-angular/components/password-generator.component'; +import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component"; @Component({ - selector: 'app-password-generator', - templateUrl: 'password-generator.component.html', + selector: "app-password-generator", + templateUrl: "password-generator.component.html", }) export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent { - private cipherState: CipherView; + private cipherState: CipherView; - constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, - i18nService: I18nService, private stateService: StateService, - private location: Location) { - super(passwordGenerationService, platformUtilsService, i18nService, window); - } + constructor( + passwordGenerationService: PasswordGenerationService, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + private stateService: StateService, + private location: Location + ) { + super(passwordGenerationService, platformUtilsService, i18nService, window); + } - async ngOnInit() { - await super.ngOnInit(); - const addEditCipherInfo = await this.stateService.get('addEditCipherInfo'); - if (addEditCipherInfo != null) { - this.cipherState = addEditCipherInfo.cipher; - } - this.showSelect = this.cipherState != null; + async ngOnInit() { + await super.ngOnInit(); + const addEditCipherInfo = await this.stateService.get("addEditCipherInfo"); + if (addEditCipherInfo != null) { + this.cipherState = addEditCipherInfo.cipher; } + this.showSelect = this.cipherState != null; + } - select() { - super.select(); - this.cipherState.login.password = this.password; - this.close(); - } + select() { + super.select(); + this.cipherState.login.password = this.password; + this.close(); + } - close() { - this.location.back(); - } + close() { + this.location.back(); + } } diff --git a/src/popup/index.html b/src/popup/index.html index 9a174c1dcc..aa4d8727fd 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -1,14 +1,14 @@ - - - + + + Bitwarden - - - + + + -
+
- + diff --git a/src/popup/main.ts b/src/popup/main.ts index ca81ae7ffe..2cc9c0ac34 100644 --- a/src/popup/main.ts +++ b/src/popup/main.ts @@ -1,17 +1,17 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from "@angular/core"; +import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; // 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') { - enableProdMode(); +if (process.env.ENV === "production") { + enableProdMode(); } function init() { - platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); + platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); } init(); diff --git a/src/popup/polyfills.ts b/src/popup/polyfills.ts index 373bed9c57..80e089e478 100644 --- a/src/popup/polyfills.ts +++ b/src/popup/polyfills.ts @@ -1,6 +1,6 @@ /* tslint:disable */ -import 'core-js/stable'; -import 'date-input-polyfill'; -import 'web-animations-js'; -import 'zone.js/dist/zone'; +import "core-js/stable"; +import "date-input-polyfill"; +import "web-animations-js"; +import "zone.js/dist/zone"; /* tslint:enable */ diff --git a/src/popup/private-mode.component.html b/src/popup/private-mode.component.html index f01efc966a..a1758be4f4 100644 --- a/src/popup/private-mode.component.html +++ b/src/popup/private-mode.component.html @@ -1,6 +1,6 @@
-

{{privateModeMessage}}

- +

{{ privateModeMessage }}

+
diff --git a/src/popup/private-mode.component.ts b/src/popup/private-mode.component.ts index 4fd4bc7915..71fd5864d0 100644 --- a/src/popup/private-mode.component.ts +++ b/src/popup/private-mode.component.ts @@ -1,24 +1,23 @@ -import { BrowserApi } from '../browser/browserApi'; +import { BrowserApi } from "../browser/browserApi"; -import { - Component, - OnInit, -} from '@angular/core'; +import { Component, OnInit } from "@angular/core"; @Component({ - selector: 'app-private-mode', - templateUrl: 'private-mode.component.html', + selector: "app-private-mode", + templateUrl: "private-mode.component.html", }) export class PrivateModeComponent implements OnInit { - privateModeMessage: string; - learnMoreMessage: string; + privateModeMessage: string; + learnMoreMessage: string; - ngOnInit() { - this.privateModeMessage = chrome.i18n.getMessage('privateModeMessage'); - this.learnMoreMessage = chrome.i18n.getMessage('learnMore'); - } + ngOnInit() { + this.privateModeMessage = chrome.i18n.getMessage("privateModeMessage"); + this.learnMoreMessage = chrome.i18n.getMessage("learnMore"); + } - learnMore() { - BrowserApi.createNewTab('https://help.bitwarden.com/article/extension-wont-load-in-private-mode/'); - } + learnMore() { + BrowserApi.createNewTab( + "https://help.bitwarden.com/article/extension-wont-load-in-private-mode/" + ); + } } diff --git a/src/popup/scss/base.scss b/src/popup/scss/base.scss index 42abe64c09..92c2d920b3 100644 --- a/src/popup/scss/base.scss +++ b/src/popup/scss/base.scss @@ -1,438 +1,461 @@ @import "variables.scss"; * { - box-sizing: border-box; - padding: 0; - margin: 0; + box-sizing: border-box; + padding: 0; + margin: 0; } -html, body { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - line-height: $line-height-base; - -webkit-font-smoothing: antialiased; +html, +body { + font-family: $font-family-sans-serif; + font-size: $font-size-base; + line-height: $line-height-base; + -webkit-font-smoothing: antialiased; } body { + width: 375px !important; + height: 600px !important; + overflow: hidden; + color: $text-color; + background-color: $background-color; + + @include themify($themes) { + color: themed("textColor"); + background-color: themed("backgroundColor"); + } + + &.body-sm { width: 375px !important; - height: 600px !important; - overflow: hidden; - color: $text-color; - background-color: $background-color; + height: 500px !important; + } - @include themify($themes) { - color: themed('textColor'); - background-color: themed('backgroundColor'); - } + &.body-xs { + width: 375px !important; + height: 300px !important; + } - &.body-sm { - width: 375px !important; - height: 500px !important; - } - - &.body-xs { - width: 375px !important; - height: 300px !important; - } - - &.body-full { - width: 100% !important; - height: 100% !important; - } + &.body-full { + width: 100% !important; + height: 100% !important; + } } -h1, h2, h3, h4, h5, h6 { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - font-weight: normal; +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: $font-family-sans-serif; + font-size: $font-size-base; + font-weight: normal; } p { - margin-bottom: 10px; + margin-bottom: 10px; } -ul, ol { - margin-bottom: 10px; +ul, +ol { + margin-bottom: 10px; } img { - border: none; + border: none; } a { - text-decoration: none; + text-decoration: none; + @include themify($themes) { + color: themed("primaryColor"); + } + + &:hover, + &:focus { @include themify($themes) { - color: themed('primaryColor'); - } - - &:hover, &:focus { - @include themify($themes) { - color: darken(themed('primaryColor'), 6%); - } + color: darken(themed("primaryColor"), 6%); } + } } -input, select, textarea { - @include themify($themes) { - color: themed('textColor'); - background-color: themed('inputBackgroundColor'); - } +input, +select, +textarea { + @include themify($themes) { + color: themed("textColor"); + background-color: themed("inputBackgroundColor"); + } } -input, select, textarea, button { - font-size: $font-size-base; - font-family: $font-family-sans-serif; +input, +select, +textarea, +button { + font-size: $font-size-base; + font-family: $font-family-sans-serif; } button { - white-space: nowrap; - cursor: pointer; + white-space: nowrap; + cursor: pointer; } textarea { - resize: vertical; + resize: vertical; } main { - height: 100%; + height: 100%; } -content::-webkit-scrollbar, cdk-virtual-scroll-viewport::-webkit-scrollbar { - width: 10px; - height: 10px; +content::-webkit-scrollbar, +cdk-virtual-scroll-viewport::-webkit-scrollbar { + width: 10px; + height: 10px; } content::-webkit-scrollbar-track { - background-color: transparent; + background-color: transparent; } cdk-virtual-scroll-viewport::-webkit-scrollbar-track { - @include themify($themes) { - background-color: themed('backgroundColor'); - } + @include themify($themes) { + background-color: themed("backgroundColor"); + } } -content::-webkit-scrollbar-thumb, cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb { - border-radius: 10px; - margin-right: 1px; +content::-webkit-scrollbar-thumb, +cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb { + border-radius: 10px; + margin-right: 1px; + @include themify($themes) { + background-color: themed("scrollbarColor"); + } + + &:hover { @include themify($themes) { - background-color: themed('scrollbarColor'); - } - - &:hover { - @include themify($themes) { - background-color: themed('scrollbarHoverColor'); - } + background-color: themed("scrollbarHoverColor"); } + } } header { - min-height: 44px; - max-height: 44px; + min-height: 44px; + max-height: 44px; + display: flex; + border-bottom: 1px solid #000000; + + @include themify($themes) { + color: themed("headerColor"); + background-color: themed("headerBackgroundColor"); + border-bottom-color: themed("headerBorderColor"); + } + + .left, + .right { + flex: 1; display: flex; - border-bottom: 1px solid #000000; + min-width: -webkit-min-content; /* Workaround to Chrome bug */ + .header-icon { + margin-right: 5px; + } + } + + .right { + justify-content: flex-end; + } + + .center { + display: flex; + align-items: center; + text-align: center; + min-width: 0; + } + + app-pop-out > button, + div > button, + div > a { + border: none; + padding: 0 10px; + text-decoration: none; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; @include themify($themes) { - color: themed('headerColor'); - background-color: themed('headerBackgroundColor'); - border-bottom-color: themed('headerBorderColor'); + color: themed("headerColor"); + background-color: themed("headerBackgroundColor"); } - .left, .right { - flex: 1; - display: flex; - min-width: -webkit-min-content; /* Workaround to Chrome bug */ - .header-icon { - margin-right: 5px; - } + &:hover, + &:focus { + @include themify($themes) { + background-color: themed("headerBackgroundHoverColor"); + color: themed("headerColor"); + } } - .right { - justify-content: flex-end; + &:focus { + text-decoration: underline; } - .center { - display: flex; - align-items: center; - text-align: center; - min-width: 0; + &[disabled] { + opacity: 0.65; + cursor: default !important; } - app-pop-out > button, div > button, div > a { - border: none; - padding: 0 10px; - text-decoration: none; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; + i + span { + margin-left: 5px; + } + } + + app-pop-out { + display: flex; + } + + .title { + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .search { + padding: 7px 10px; + width: 100%; + text-align: left; + position: relative; + display: flex; + + .fa { + position: absolute; + top: 15px; + left: 20px; + + @include themify($themes) { + color: themed("headerInputPlaceholderColor"); + } + } + + input { + width: 100%; + margin: 0; + border: none; + padding: 5px 10px 5px 30px; + border-radius: $border-radius; + + @include themify($themes) { + background-color: themed("headerInputBackgroundColor"); + color: themed("headerInputColor"); + } + + &:focus { + border-radius: $border-radius; + outline: none; @include themify($themes) { - color: themed('headerColor'); - background-color: themed('headerBackgroundColor'); + background-color: themed("headerInputBackgroundFocusColor"); } + } - &:hover, &:focus { - @include themify($themes) { - background-color: themed('headerBackgroundHoverColor'); - color: themed('headerColor'); - } - } - - &:focus { - text-decoration: underline; - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - } - - i + span { - margin-left: 5px; + &::-webkit-input-placeholder { + @include themify($themes) { + color: themed("headerInputPlaceholderColor"); } + } } + } - app-pop-out { - display: flex; + .left + .search { + padding-left: 0; + + .fa { + left: 10px; } + } - .title { - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .search { - padding: 7px 10px; - width: 100%; - text-align: left; - position: relative; - display: flex; - - .fa { - position: absolute; - top: 15px; - left: 20px; - - @include themify($themes) { - color: themed('headerInputPlaceholderColor'); - } - } - - input { - width: 100%; - margin: 0; - border: none; - padding: 5px 10px 5px 30px; - border-radius: $border-radius; - - @include themify($themes) { - background-color: themed('headerInputBackgroundColor'); - color: themed('headerInputColor'); - } - - &:focus { - border-radius: $border-radius; - outline: none; - - @include themify($themes) { - background-color: themed('headerInputBackgroundFocusColor'); - } - } - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed('headerInputPlaceholderColor'); - } - } - } - } - - .left + .search { - padding-left: 0; - - .fa { - left: 10px; - } - } - - .search + .right { - margin-left: -10px; - } + .search + .right { + margin-left: -10px; + } } .content { - padding: 15px; + padding: 15px; } .tabs { - width: 100%; - height: 55px; - border-top: 1px solid #000000; - position: absolute; - bottom: 0; - left: 0; - right: 0; - overflow: hidden; + width: 100%; + height: 55px; + border-top: 1px solid #000000; + position: absolute; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; - @include themify($themes) { - background-color: themed('tabBackgroundColor'); - border-top-color: themed('borderColor'); - } + @include themify($themes) { + background-color: themed("tabBackgroundColor"); + border-top-color: themed("borderColor"); + } - ul { - display: flex; - list-style: none; - padding: 0; - margin: 0; + ul { + display: flex; + list-style: none; + padding: 0; + margin: 0; - li { - flex: 1; - display: inline-block; - padding: 0; - margin: 0; + li { + flex: 1; + display: inline-block; + padding: 0; + margin: 0; - a { - text-align: center; - display: block; - padding: 7px 0; - text-decoration: none; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + a { + text-align: center; + display: block; + padding: 7px 0; + text-decoration: none; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; - @include themify($themes) { - color: themed('mutedColor'); - } - - &:hover, &:focus { - @include themify($themes) { - background-color: themed('tabBackgroundHoverColor'); - } - } - - i { - display: block; - margin-bottom: 2px; - text-align: center; - } - } - - &.active { - a { - @include themify($themes) { - color: themed('primaryColor'); - } - } - } + @include themify($themes) { + color: themed("mutedColor"); } + + &:hover, + &:focus { + @include themify($themes) { + background-color: themed("tabBackgroundHoverColor"); + } + } + + i { + display: block; + margin-bottom: 2px; + text-align: center; + } + } + + &.active { + a { + @include themify($themes) { + color: themed("primaryColor"); + } + } + } } + } } app-root { - position: absolute; - width: 100%; - height: 100%; - z-index: 980; - @include themify($themes) { - background-color: themed('backgroundColor'); - } + position: absolute; + width: 100%; + height: 100%; + z-index: 980; + @include themify($themes) { + background-color: themed("backgroundColor"); + } } @media only screen and (min-width: 601px) { - app-login header { - padding: 0 calc((100% - 500px) / 2); - } + app-login header { + padding: 0 calc((100% - 500px) / 2); + } - app-login content { - padding: 0 calc((100% - 500px) / 2); - } + app-login content { + padding: 0 calc((100% - 500px) / 2); + } - app-two-factor header { - padding: 0 calc((100% - 500px) / 2); - } + app-two-factor header { + padding: 0 calc((100% - 500px) / 2); + } - app-two-factor content { - padding: 0 calc((100% - 500px) / 2); - } + app-two-factor content { + padding: 0 calc((100% - 500px) / 2); + } - app-lock header { - padding: 0 calc((100% - 500px) / 2); - } + app-lock header { + padding: 0 calc((100% - 500px) / 2); + } - app-lock content { - padding: 0 calc((100% - 500px) / 2); - } + app-lock content { + padding: 0 calc((100% - 500px) / 2); + } } content { - position: absolute; - top: 44px; - bottom: 0; - left: 0; - right: 0; - overflow-y: auto; - overflow-x: hidden; + position: absolute; + top: 44px; + bottom: 0; + left: 0; + right: 0; + overflow-y: auto; + overflow-x: hidden; - @include themify($themes) { - background-color: themed('backgroundColor'); - } - - &.no-header { - top: 0; - } - - &.flex { - display: flex; - flex-flow: column; - height: calc(100% - 44px); - - &.tab-page { - height: calc(100% - 99px); - } + @include themify($themes) { + background-color: themed("backgroundColor"); + } + + &.no-header { + top: 0; + } + + &.flex { + display: flex; + flex-flow: column; + height: calc(100% - 44px); + + &.tab-page { + height: calc(100% - 99px); } + } } .tab-page { - content { - bottom: 55px; - } + content { + bottom: 55px; + } } -.center-content, .no-items, .full-loading-spinner { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - flex-direction: column; - flex-grow: 1; +.center-content, +.no-items, +.full-loading-spinner { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + flex-direction: column; + flex-grow: 1; } -.no-items, .full-loading-spinner { - text-align: center; +.no-items, +.full-loading-spinner { + text-align: center; - .fa { - margin-bottom: 10px; + .fa { + margin-bottom: 10px; - @include themify($themes) { - color: themed('disabledIconColor'); - } + @include themify($themes) { + color: themed("disabledIconColor"); } + } } // cdk-virtual-scroll .cdk-virtual-scroll-viewport { - width: 100%; - height: 100%; - overflow-y: auto; - overflow-x: hidden; + width: 100%; + height: 100%; + overflow-y: auto; + overflow-x: hidden; } .cdk-virtual-scroll-content-wrapper { - width: 100%; + width: 100%; } diff --git a/src/popup/scss/box.scss b/src/popup/scss/box.scss index e65e32491d..2697b93928 100644 --- a/src/popup/scss/box.scss +++ b/src/popup/scss/box.scss @@ -1,646 +1,669 @@ @import "variables.scss"; .box { - position: relative; - width: 100%; - margin: 10px 0; + position: relative; + width: 100%; + margin: 10px 0; - &.first { - margin-top: 0; - } + &.first { + margin-top: 0; + } - .box-header { - margin: 0 10px 5px 10px; - text-transform: uppercase; - display: flex; - - @include themify($themes) { - color: themed('headingColor'); - } - } - - .box-header-expandable { - padding: 0 10px; - margin-bottom: 5px; - text-transform: uppercase; - display: flex; - width: 100%; - box-sizing: border-box; - - @include themify($themes) { - color: themed('headingColor'); - } - - &:hover, &:focus, &.active { - @include themify($themes) { - background-color: themed('boxBackgroundHoverColor'); - } - } - - .icon { - display: flex; - align-items: center; - margin-left: 5px; - - @include themify($themes) { - color: themed('headingColor'); - } - } - } - - .box-content { - border-top: 1px solid #000000; - border-bottom: 1px solid #000000; - - @include themify($themes) { - background-color: themed('boxBackgroundColor'); - border-color: themed('borderColor'); - } - - &.box-content-padded { - padding: 10px 15px; - } - - &.condensed .box-content-row, .box-content-row.condensed { - padding-top: 5px; - padding-bottom: 5px; - } - - &.no-hover .box-content-row, .box-content-row.no-hover { - &:hover, &:focus { - background-color: initial !important; - } - } - } - - .box-footer { - margin: 5px 10px; - font-size: $font-size-small; - - @include themify($themes) { - color: themed('mutedColor'); - } - } - - &.list { - .box-content { - .box-content-row { - padding: 3px 10px; - text-decoration: none; - - @include themify($themes) { - color: themed('textColor'); - } - - &.padded { - padding-top: 10px; - padding-bottom: 10px; - } - - &:hover, &:focus, &.active { - @include themify($themes) { - background-color: themed('listItemBackgroundHoverColor'); - } - } - - &:focus { - border-left: 5px solid #000000; - padding-left: 5px; - - @include themify($themes) { - border-left-color: themed('mutedColor'); - } - } - - .action-buttons { - .row-btn { - padding-left: 5px; - padding-right: 5px; - } - } - - .text:not(.no-ellipsis), .detail { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .row-main { - display: flex; - min-width: 0; - - .row-main-content { - min-width: 0; - } - } - } - - &.single-line { - .box-content-row { - display: flex; - padding-top: 10px; - padding-bottom: 10px; - } - } - } - } - - &.only-list { - margin-bottom: 0; - - .box-content { - border-bottom: none; - } - } - - &.full-list { - margin: 0; - - .box-content { - border: none; - } - } -} - -.stacked-boxes { + .box-header { + margin: 0 10px 5px 10px; + text-transform: uppercase; display: flex; - flex-direction: column; -} - -.box-section-divider { - border-top: 1px solid #000000; - padding-top: 10px; @include themify($themes) { - border-color: themed('borderColor'); + color: themed("headingColor"); } -} + } -.box-content-row { - display: block; - padding: 10px 15px; - position: relative; - z-index: 1; + .box-header-expandable { + padding: 0 10px; + margin-bottom: 5px; + text-transform: uppercase; + display: flex; + width: 100%; + box-sizing: border-box; - &:before { - content: ""; - position: absolute; - right: 0; - bottom: 0; - height: 1px; - width: calc(100% - 10px); - border-bottom: 1px solid #000000; - - @include themify($themes) { - border-bottom-color: themed('boxBorderColor'); - } + @include themify($themes) { + color: themed("headingColor"); } - &:last-child { - &:before { - border: none; - height: 0; - } - } - - &.last:last-child:before { - border-bottom: 1px solid #000000; - @include themify($themes) { - border-bottom-color: themed('boxBorderColor'); - } - } - - &:after { - content: ""; - display: table; - clear: both; - } - - &:hover, &:focus, &.active { - @include themify($themes) { - background-color: themed('boxBackgroundHoverColor'); - } - } - - &.pre { - white-space: pre; - overflow-x: auto; - } - - &.pre-wrap { - white-space: pre-wrap; - overflow-x: auto; - } - - .row-label, label { - font-size: $font-size-small; - display: block; - width: 100%; - margin-bottom: 5px; - - @include themify($themes) { - color: themed('mutedColor'); - } - - .sub-label { - margin-left: 10px; - } - } - - .flex-label { - font-size: $font-size-small; - display: flex; - flex-grow: 1; - margin-bottom: 5px; - - @include themify($themes) { - color: themed('mutedColor'); - } - - > a { - flex-grow: 0; - } - } - - .text, .detail { - display: block; - - @include themify($themes) { - color: themed('textColor'); - } - } - - .detail { - font-size: $font-size-small; - - @include themify($themes) { - color: themed('mutedColor'); - } - } - - .img-right { - float: right; - margin-left: 10px; - } - - .row-main { - flex-grow: 1; - min-width: 0; - } - - &.box-content-row-flex, .box-content-row-flex, &.box-content-row-checkbox, - &.box-content-row-input, &.box-content-row-slider, &.box-content-row-multi { - display: flex; - align-items: center; - word-break: break-all; - - &.box-content-row-word-break { - word-break: normal; - } - } - - &.box-content-row-multi { - width: 100%; - - input:not([type="checkbox"]) { - width: 100%; - } - - input + label.sr-only + select { - margin-top: 5px; - } - - > a, - > button { - padding: 8px 8px 8px 4px; - margin: 0; - - @include themify($themes) { - color: themed('dangerColor'); - } - } - } - - - &.box-content-row-multi, &.box-content-row-newmulti { - padding-left: 10px; - } - - &.box-content-row-newmulti { - @include themify($themes) { - color: themed('primaryColor'); - } - } - - &.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider { - label, .row-label { - font-size: $font-size-base; - display: block; - width: initial; - margin-bottom: 0; - - @include themify($themes) { - color: themed('textColor'); - } - } - - > span { - @include themify($themes) { - color: themed('mutedColor'); - } - } - - > input { - margin: 0 0 0 auto; - padding: 0; - } - - > * { - margin-right: 15px; - - &:last-child { - margin-right: 0; - } - } - } - - &.box-content-row-checkbox-left { - justify-content: flex-start; - - > input { - margin: 0 15px 0 0; - } - } - - &.box-content-row-input { - label { - white-space: nowrap; - } - - input { - text-align: right; - - &[type="number"] { - max-width: 50px; - } - } - } - - &.box-content-row-slider { - input[type="range"] { - height: 10px; - } - - input[type="number"] { - width: 45px; - } - - label { - white-space: nowrap; - } - } - - input:not([type="checkbox"]):not([type="radio"]), textarea { - border: none; - width: 100%; - background-color: transparent !important; - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed('inputPlaceholderColor'); - } - } - - &:focus { - outline: none; - } - } - - select { - width: 100%; - border: 1px solid #000000; - border-radius: $border-radius; - - @include themify($themes) { - border-color: themed('inputBorderColor'); - } - } - - .action-buttons { - display: flex; - margin-left: 5px; - - .row-btn { - cursor: pointer; - padding: 10px 8px; - background: none; - border: none; - - @include themify($themes) { - color: themed('boxRowButtonColor'); - } - - &:hover, &:focus { - @include themify($themes) { - color: themed('boxRowButtonHoverColor'); - } - } - - &.disabled { - @include themify($themes) { - color: themed('disabledIconColor'); - opacity: themed('disabledBoxOpacity'); - } - - &:hover { - @include themify($themes) { - color: themed('disabledIconColor'); - opacity: themed('disabledBoxOpacity'); - } - } - cursor: default !important; - } - } - - &.no-pad .row-btn { - padding-top: 0; - padding-bottom: 0; - } - } - - &:not(.box-draggable-row) { - .action-buttons .row-btn:last-child { - padding-right: 2px !important; - } - } - - &.box-draggable-row { - &.box-content-row-checkbox { - input[type="checkbox"] + .drag-handle { - margin-left: 10px; - } - } - } - - .drag-handle { - cursor: move; - padding: 10px 2px 10px 8px; - user-select: none; - - @include themify($themes) { - color: themed('mutedColor'); - } - } - - &.cdk-drag-preview { - position: relative; - display: flex; - align-items: center; - opacity: 0.8; - - @include themify($themes) { - background-color: themed('boxBackgroundColor'); - } - } - - select.field-type { - margin: 5px 0 0 25px; - width: calc(100% - 25px); - } - - .row-sub-icon, .row-sub-label + i.fa { - @include themify($themes) { - color: themed('disabledIconColor'); - } - } - - .row-sub-label { - margin: 0 15px; - white-space: nowrap; - - @include themify($themes) { - color: themed('mutedColor'); - } + &:hover, + &:focus, + &.active { + @include themify($themes) { + background-color: themed("boxBackgroundHoverColor"); + } } .icon { - display: flex; - justify-content: center; - align-items: center; - min-width: 34px; - margin-left: -5px; + display: flex; + align-items: center; + margin-left: 5px; + + @include themify($themes) { + color: themed("headingColor"); + } + } + } + + .box-content { + border-top: 1px solid #000000; + border-bottom: 1px solid #000000; + + @include themify($themes) { + background-color: themed("boxBackgroundColor"); + border-color: themed("borderColor"); + } + + &.box-content-padded { + padding: 10px 15px; + } + + &.condensed .box-content-row, + .box-content-row.condensed { + padding-top: 5px; + padding-bottom: 5px; + } + + &.no-hover .box-content-row, + .box-content-row.no-hover { + &:hover, + &:focus { + background-color: initial !important; + } + } + } + + .box-footer { + margin: 5px 10px; + font-size: $font-size-small; + + @include themify($themes) { + color: themed("mutedColor"); + } + } + + &.list { + .box-content { + .box-content-row { + padding: 3px 10px; + text-decoration: none; @include themify($themes) { - color: themed('mutedColor'); + color: themed("textColor"); } - &.icon-small { - min-width: 25px; + &.padded { + padding-top: 10px; + padding-bottom: 10px; } - img { - border-radius: $border-radius; - max-height: 20px; - max-width: 20px; + &:hover, + &:focus, + &.active { + @include themify($themes) { + background-color: themed("listItemBackgroundHoverColor"); + } } + + &:focus { + border-left: 5px solid #000000; + padding-left: 5px; + + @include themify($themes) { + border-left-color: themed("mutedColor"); + } + } + + .action-buttons { + .row-btn { + padding-left: 5px; + padding-right: 5px; + } + } + + .text:not(.no-ellipsis), + .detail { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .row-main { + display: flex; + min-width: 0; + + .row-main-content { + min-width: 0; + } + } + } + + &.single-line { + .box-content-row { + display: flex; + padding-top: 10px; + padding-bottom: 10px; + } + } } + } - &.totp { - .totp-code { - font-family: $font-family-monospace; - font-size: 1.1em; - } + &.only-list { + margin-bottom: 0; - .totp-countdown { - margin: 3px 3px 0 0; - display: block; - user-select: none; - - .totp-sec { - font-size: 0.85em; - position: absolute; - line-height: 32px; - width: 32px; - text-align: center; - } - - svg { - width: 32px; - height: 32px; - transform: rotate(-90deg); - } - - .totp-circle { - fill: none; - - @include themify($themes) { - stroke: themed('totpStrokeColor'); - } - - &.inner { - stroke-width: 3; - stroke-dasharray: 78.6; - stroke-dashoffset: 0; - } - - &.outer { - stroke-width: 2; - stroke-dasharray: 88; - stroke-dashoffset: 0; - } - } - } - - &.low { - .totp-sec, .totp-code { - @include themify($themes) { - color: themed('dangerColor'); - } - } - - .totp-circle { - @include themify($themes) { - stroke: themed('dangerColor'); - } - } - } + .box-content { + border-bottom: none; } + } - .progress { - display: flex; - height: 5px; - overflow: hidden; - margin: 5px -15px -10px; + &.full-list { + margin: 0; - .progress-bar { - display: flex; - flex-direction: column; - justify-content: center; - white-space: nowrap; - background-color: $brand-primary; - } - } - - .radio-group { - display: flex; - justify-content: flex-start; - align-items: center; - margin-bottom: 5px; - - input { - flex-grow: 0; - } - - label { - margin: 0 0 0 5px; - flex-grow: 1; - font-size: $font-size-base; - display: block; - width: 100%; - - @include themify($themes) { - color: themed('textColor'); - } - } + .box-content { + border: none; } + } +} + +.stacked-boxes { + display: flex; + flex-direction: column; +} + +.box-section-divider { + border-top: 1px solid #000000; + padding-top: 10px; + + @include themify($themes) { + border-color: themed("borderColor"); + } +} + +.box-content-row { + display: block; + padding: 10px 15px; + position: relative; + z-index: 1; + + &:before { + content: ""; + position: absolute; + right: 0; + bottom: 0; + height: 1px; + width: calc(100% - 10px); + border-bottom: 1px solid #000000; + + @include themify($themes) { + border-bottom-color: themed("boxBorderColor"); + } + } + + &:last-child { + &:before { + border: none; + height: 0; + } + } + + &.last:last-child:before { + border-bottom: 1px solid #000000; + @include themify($themes) { + border-bottom-color: themed("boxBorderColor"); + } + } + + &:after { + content: ""; + display: table; + clear: both; + } + + &:hover, + &:focus, + &.active { + @include themify($themes) { + background-color: themed("boxBackgroundHoverColor"); + } + } + + &.pre { + white-space: pre; + overflow-x: auto; + } + + &.pre-wrap { + white-space: pre-wrap; + overflow-x: auto; + } + + .row-label, + label { + font-size: $font-size-small; + display: block; + width: 100%; + margin-bottom: 5px; + + @include themify($themes) { + color: themed("mutedColor"); + } + + .sub-label { + margin-left: 10px; + } + } + + .flex-label { + font-size: $font-size-small; + display: flex; + flex-grow: 1; + margin-bottom: 5px; + + @include themify($themes) { + color: themed("mutedColor"); + } + + > a { + flex-grow: 0; + } + } + + .text, + .detail { + display: block; + + @include themify($themes) { + color: themed("textColor"); + } + } + + .detail { + font-size: $font-size-small; + + @include themify($themes) { + color: themed("mutedColor"); + } + } + + .img-right { + float: right; + margin-left: 10px; + } + + .row-main { + flex-grow: 1; + min-width: 0; + } + + &.box-content-row-flex, + .box-content-row-flex, + &.box-content-row-checkbox, + &.box-content-row-input, + &.box-content-row-slider, + &.box-content-row-multi { + display: flex; + align-items: center; + word-break: break-all; + + &.box-content-row-word-break { + word-break: normal; + } + } + + &.box-content-row-multi { + width: 100%; + + input:not([type="checkbox"]) { + width: 100%; + } + + input + label.sr-only + select { + margin-top: 5px; + } + + > a, + > button { + padding: 8px 8px 8px 4px; + margin: 0; + + @include themify($themes) { + color: themed("dangerColor"); + } + } + } + + &.box-content-row-multi, + &.box-content-row-newmulti { + padding-left: 10px; + } + + &.box-content-row-newmulti { + @include themify($themes) { + color: themed("primaryColor"); + } + } + + &.box-content-row-checkbox, + &.box-content-row-input, + &.box-content-row-slider { + label, + .row-label { + font-size: $font-size-base; + display: block; + width: initial; + margin-bottom: 0; + + @include themify($themes) { + color: themed("textColor"); + } + } + + > span { + @include themify($themes) { + color: themed("mutedColor"); + } + } + + > input { + margin: 0 0 0 auto; + padding: 0; + } + + > * { + margin-right: 15px; + + &:last-child { + margin-right: 0; + } + } + } + + &.box-content-row-checkbox-left { + justify-content: flex-start; + + > input { + margin: 0 15px 0 0; + } + } + + &.box-content-row-input { + label { + white-space: nowrap; + } + + input { + text-align: right; + + &[type="number"] { + max-width: 50px; + } + } + } + + &.box-content-row-slider { + input[type="range"] { + height: 10px; + } + + input[type="number"] { + width: 45px; + } + + label { + white-space: nowrap; + } + } + + input:not([type="checkbox"]):not([type="radio"]), + textarea { + border: none; + width: 100%; + background-color: transparent !important; + + &::-webkit-input-placeholder { + @include themify($themes) { + color: themed("inputPlaceholderColor"); + } + } + + &:focus { + outline: none; + } + } + + select { + width: 100%; + border: 1px solid #000000; + border-radius: $border-radius; + + @include themify($themes) { + border-color: themed("inputBorderColor"); + } + } + + .action-buttons { + display: flex; + margin-left: 5px; + + .row-btn { + cursor: pointer; + padding: 10px 8px; + background: none; + border: none; + + @include themify($themes) { + color: themed("boxRowButtonColor"); + } + + &:hover, + &:focus { + @include themify($themes) { + color: themed("boxRowButtonHoverColor"); + } + } + + &.disabled { + @include themify($themes) { + color: themed("disabledIconColor"); + opacity: themed("disabledBoxOpacity"); + } + + &:hover { + @include themify($themes) { + color: themed("disabledIconColor"); + opacity: themed("disabledBoxOpacity"); + } + } + cursor: default !important; + } + } + + &.no-pad .row-btn { + padding-top: 0; + padding-bottom: 0; + } + } + + &:not(.box-draggable-row) { + .action-buttons .row-btn:last-child { + padding-right: 2px !important; + } + } + + &.box-draggable-row { + &.box-content-row-checkbox { + input[type="checkbox"] + .drag-handle { + margin-left: 10px; + } + } + } + + .drag-handle { + cursor: move; + padding: 10px 2px 10px 8px; + user-select: none; + + @include themify($themes) { + color: themed("mutedColor"); + } + } + + &.cdk-drag-preview { + position: relative; + display: flex; + align-items: center; + opacity: 0.8; + + @include themify($themes) { + background-color: themed("boxBackgroundColor"); + } + } + + select.field-type { + margin: 5px 0 0 25px; + width: calc(100% - 25px); + } + + .row-sub-icon, + .row-sub-label + i.fa { + @include themify($themes) { + color: themed("disabledIconColor"); + } + } + + .row-sub-label { + margin: 0 15px; + white-space: nowrap; + + @include themify($themes) { + color: themed("mutedColor"); + } + } + + .icon { + display: flex; + justify-content: center; + align-items: center; + min-width: 34px; + margin-left: -5px; + + @include themify($themes) { + color: themed("mutedColor"); + } + + &.icon-small { + min-width: 25px; + } + + img { + border-radius: $border-radius; + max-height: 20px; + max-width: 20px; + } + } + + &.totp { + .totp-code { + font-family: $font-family-monospace; + font-size: 1.1em; + } + + .totp-countdown { + margin: 3px 3px 0 0; + display: block; + user-select: none; + + .totp-sec { + font-size: 0.85em; + position: absolute; + line-height: 32px; + width: 32px; + text-align: center; + } + + svg { + width: 32px; + height: 32px; + transform: rotate(-90deg); + } + + .totp-circle { + fill: none; + + @include themify($themes) { + stroke: themed("totpStrokeColor"); + } + + &.inner { + stroke-width: 3; + stroke-dasharray: 78.6; + stroke-dashoffset: 0; + } + + &.outer { + stroke-width: 2; + stroke-dasharray: 88; + stroke-dashoffset: 0; + } + } + } + + &.low { + .totp-sec, + .totp-code { + @include themify($themes) { + color: themed("dangerColor"); + } + } + + .totp-circle { + @include themify($themes) { + stroke: themed("dangerColor"); + } + } + } + } + + .progress { + display: flex; + height: 5px; + overflow: hidden; + margin: 5px -15px -10px; + + .progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + white-space: nowrap; + background-color: $brand-primary; + } + } + + .radio-group { + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + + input { + flex-grow: 0; + } + + label { + margin: 0 0 0 5px; + flex-grow: 1; + font-size: $font-size-base; + display: block; + width: 100%; + + @include themify($themes) { + color: themed("textColor"); + } + } + } } diff --git a/src/popup/scss/buttons.scss b/src/popup/scss/buttons.scss index e1c3b375cd..f4eea2a1cc 100644 --- a/src/popup/scss/buttons.scss +++ b/src/popup/scss/buttons.scss @@ -1,101 +1,101 @@ @import "variables.scss"; .btn { - border-radius: $border-radius; - padding: 7px 15px; - border: 1px solid #000000; - font-size: $font-size-base; - white-space: nowrap; - text-align: center; + border-radius: $border-radius; + padding: 7px 15px; + border: 1px solid #000000; + font-size: $font-size-base; + white-space: nowrap; + text-align: center; + cursor: pointer; + + @include themify($themes) { + background-color: themed("buttonBackgroundColor"); + border-color: themed("buttonBorderColor"); + color: themed("buttonColor"); + } + + &.primary { + @include themify($themes) { + color: themed("buttonPrimaryColor"); + } + } + + &.danger { + @include themify($themes) { + color: themed("buttonDangerColor"); + } + } + + &:hover:not([disabled]) { cursor: pointer; @include themify($themes) { - background-color: themed('buttonBackgroundColor'); - border-color: themed('buttonBorderColor'); - color: themed('buttonColor'); + background-color: darken(themed("buttonBackgroundColor"), 1.5%); + border-color: darken(themed("buttonBorderColor"), 17%); + color: darken(themed("buttonColor"), 10%); } &.primary { - @include themify($themes) { - color: themed('buttonPrimaryColor'); - } + @include themify($themes) { + color: darken(themed("buttonPrimaryColor"), 6%); + } } &.danger { - @include themify($themes) { - color: themed('buttonDangerColor'); - } + @include themify($themes) { + color: darken(themed("buttonDangerColor"), 6%); + } } + } - &:hover:not([disabled]) { - cursor: pointer; + &:focus:not([disabled]) { + cursor: pointer; + outline: 0; - @include themify($themes) { - background-color: darken(themed('buttonBackgroundColor'), 1.5%); - border-color: darken(themed('buttonBorderColor'), 17%); - color: darken(themed('buttonColor'), 10%); - } - - &.primary { - @include themify($themes) { - color: darken(themed('buttonPrimaryColor'), 6%); - } - } - - &.danger { - @include themify($themes) { - color: darken(themed('buttonDangerColor'), 6%); - } - } + @include themify($themes) { + background-color: darken(themed("buttonBackgroundColor"), 6%); + border-color: darken(themed("buttonBorderColor"), 25%); } + } - &:focus:not([disabled]) { - cursor: pointer; - outline: 0; + &[disabled] { + opacity: 0.65; + cursor: default !important; + } - @include themify($themes) { - background-color: darken(themed('buttonBackgroundColor'), 6%); - border-color: darken(themed('buttonBorderColor'), 25%); - } - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - } - - &.block { - display: block; - width: 100%; - } - - &.link, - &.neutral { - border: none !important; - background: none !important; - - &:focus { - text-decoration: underline; - } + &.block { + display: block; + width: 100%; + } + + &.link, + &.neutral { + border: none !important; + background: none !important; + + &:focus { + text-decoration: underline; } + } } .action-buttons { - .btn { - &:focus { - outline: auto; - } + .btn { + &:focus { + outline: auto; } + } } button.box-content-row { - display: block; - width: 100%; - text-align: left; + display: block; + width: 100%; + text-align: left; } button { - border: none; - background: transparent; - color: inherit; -} \ No newline at end of file + border: none; + background: transparent; + color: inherit; +} diff --git a/src/popup/scss/environment.scss b/src/popup/scss/environment.scss index c3b5378ee2..4b70c95ca7 100644 --- a/src/popup/scss/environment.scss +++ b/src/popup/scss/environment.scss @@ -1,39 +1,39 @@ @import "variables.scss"; html.browser_safari { - body { - height: 360px !important; + body { + height: 360px !important; - &.body-xs { - height: 300px !important; - } - - &.body-full { - height: 100% !important; - } + &.body-xs { + height: 300px !important; } - header { - .search .fa { - left: 20px; - } + &.body-full { + height: 100% !important; + } + } - .left + .search .fa { - left: 10px; - } + header { + .search .fa { + left: 20px; } - app-root { - border-width: 1px; - border-style: solid; - border-color: #000000; + .left + .search .fa { + left: 10px; } + } - &.theme_light app-root { - border-color: #777777; - } + app-root { + border-width: 1px; + border-style: solid; + border-color: #000000; + } - &.theme_nord app-root { - border-color: #2e3440; - } + &.theme_light app-root { + border-color: #777777; + } + + &.theme_nord app-root { + border-color: #2e3440; + } } diff --git a/src/popup/scss/grid.scss b/src/popup/scss/grid.scss index b053bf8ad7..8cdb29bb52 100644 --- a/src/popup/scss/grid.scss +++ b/src/popup/scss/grid.scss @@ -1,11 +1,11 @@ .row { - display: flex; - margin: 0 -15px; - width: 100%; + display: flex; + margin: 0 -15px; + width: 100%; } .col { - flex-basis: 0; - flex-grow: 1; - padding: 0 15px; + flex-basis: 0; + flex-grow: 1; + padding: 0 15px; } diff --git a/src/popup/scss/misc.scss b/src/popup/scss/misc.scss index a5080a9eea..a6bc19970b 100644 --- a/src/popup/scss/misc.scss +++ b/src/popup/scss/misc.scss @@ -1,375 +1,378 @@ @import "variables.scss"; -small, .small { - font-size: $font-size-small; +small, +.small { + font-size: $font-size-small; } .bg-primary { - @include themify($themes) { - background-color: themed('primaryColor') !important; - } + @include themify($themes) { + background-color: themed("primaryColor") !important; + } } .bg-success { - @include themify($themes) { - background-color: themed('successColor') !important; - } + @include themify($themes) { + background-color: themed("successColor") !important; + } } .bg-danger { - @include themify($themes) { - background-color: themed('dangerColor') !important; - } + @include themify($themes) { + background-color: themed("dangerColor") !important; + } } .bg-info { - @include themify($themes) { - background-color: themed('infoColor') !important; - } + @include themify($themes) { + background-color: themed("infoColor") !important; + } } .bg-warning { - @include themify($themes) { - background-color: themed('warningColor') !important; - } + @include themify($themes) { + background-color: themed("warningColor") !important; + } } .text-primary { - @include themify($themes) { - color: themed('primaryColor') !important; - } + @include themify($themes) { + color: themed("primaryColor") !important; + } } .text-success { - @include themify($themes) { - color: themed('successColor') !important; - } + @include themify($themes) { + color: themed("successColor") !important; + } } .text-muted { - @include themify($themes) { - color: themed('mutedColor') !important; - } + @include themify($themes) { + color: themed("mutedColor") !important; + } } .text-default { - @include themify($themes) { - color: themed('textColor') !important; - } + @include themify($themes) { + color: themed("textColor") !important; + } } .text-danger { - @include themify($themes) { - color: themed('dangerColor') !important; - } + @include themify($themes) { + color: themed("dangerColor") !important; + } } .text-info { - @include themify($themes) { - color: themed('infoColor') !important; - } + @include themify($themes) { + color: themed("infoColor") !important; + } } .text-warning { - @include themify($themes) { - color: themed('warningColor') !important; - } + @include themify($themes) { + color: themed("warningColor") !important; + } } .text-center { - text-align: center; + text-align: center; } .font-weight-semibold { - font-weight: 600; + font-weight: 600; } p.lead { - font-size: $font-size-large; - margin-bottom: 20px; - font-weight: normal; + font-size: $font-size-large; + margin-bottom: 20px; + font-weight: normal; } .flex-right { - margin-left: auto; + margin-left: auto; } .flex-bottom { - margin-top: auto; + margin-top: auto; } .no-margin { - margin: 0 !important; + margin: 0 !important; } .no-vmargin { - margin-top: 0 !important; - margin-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; } .no-vpad { - padding-top: 0 !important; - padding-bottom: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; } .display-block { - display: block !important; + display: block !important; } .monospaced { - font-family: $font-family-monospace; + font-family: $font-family-monospace; } .show-whitespace { - white-space: pre-wrap; + white-space: pre-wrap; } .img-responsive { - display: block; - max-width: 100%; - height: auto; + display: block; + max-width: 100%; + height: auto; } .img-rounded { - border-radius: 6px; + border-radius: 6px; } .sr-only { - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - border: 0 !important; + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + border: 0 !important; } .password-wrapper { - overflow-wrap: break-word; - white-space: pre-wrap; - min-width: 0; + overflow-wrap: break-word; + white-space: pre-wrap; + min-width: 0; } .password-number { - @include themify($themes) { - color: themed('passwordNumberColor'); - } + @include themify($themes) { + color: themed("passwordNumberColor"); + } } .password-special { - @include themify($themes) { - color: themed('passwordSpecialColor'); - } + @include themify($themes) { + color: themed("passwordSpecialColor"); + } } #duo-frame { - background: url('../images/loading.svg') 0 0 no-repeat; - width: 100%; - height: 470px; - margin-bottom: -10px; + background: url("../images/loading.svg") 0 0 no-repeat; + width: 100%; + height: 470px; + margin-bottom: -10px; - iframe { - width: 100%; - height: 100%; - border: none; - } + iframe { + width: 100%; + height: 100%; + border: none; + } } #web-authn-frame { - background: url('../images/loading.svg') 0 0 no-repeat; - width: 100%; - height: 310px; - margin-bottom: -10px; + background: url("../images/loading.svg") 0 0 no-repeat; + width: 100%; + height: 310px; + margin-bottom: -10px; - iframe { - width: 100%; - height: 100%; - border: none; - } + iframe { + width: 100%; + height: 100%; + border: none; + } } #hcaptcha_iframe { - width: 100%; - border: none; - transition: height 0.25s linear; + width: 100%; + border: none; + transition: height 0.25s linear; } body.linux-webauthn { - width: 485px !important; - #web-authn-frame { - iframe { - width: 375px; - margin: 0 55px; - } + width: 485px !important; + #web-authn-frame { + iframe { + width: 375px; + margin: 0 55px; } + } } app-root > #loading { - display: flex; - text-align: center; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - color: $text-muted; + display: flex; + text-align: center; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + color: $text-muted; - @include themify($themes) { - color: themed('mutedColor'); - } + @include themify($themes) { + color: themed("mutedColor"); + } } app-vault-icon { - display: flex; + display: flex; } .logo-image { - margin: 0 auto; - width: 142px; - height: 21px; - background-size: 142px 21px; - background-repeat: no-repeat; - @include themify($themes) { - background-image: url('../images/logo-' + themed('logoSuffix') + '@2x.png'); - } - @media (min-width: 219px) { - width: 189px; - height: 28px; - background-size: 189px 28px; - } - @media (min-width: 314px) { - width: 284px; - height: 43px; - background-size: 284px 43px; - } + margin: 0 auto; + width: 142px; + height: 21px; + background-size: 142px 21px; + background-repeat: no-repeat; + @include themify($themes) { + background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png"); + } + @media (min-width: 219px) { + width: 189px; + height: 28px; + background-size: 189px 28px; + } + @media (min-width: 314px) { + width: 284px; + height: 43px; + background-size: 284px 43px; + } } [hidden] { - display: none !important; + display: none !important; } .draggable { - cursor: move; + cursor: move; } .callout { - padding: 10px; - margin: 10px; - border: 1px solid #000000; - border-left-width: 5px; - border-radius: 3px; + padding: 10px; + margin: 10px; + border: 1px solid #000000; + border-left-width: 5px; + border-radius: 3px; + @include themify($themes) { + border-color: themed("calloutBorderColor"); + background-color: themed("calloutBackgroundColor"); + } + + .callout-heading { + margin-top: 0; + } + + h3.callout-heading { + font-weight: bold; + text-transform: uppercase; + } + + &.callout-primary { @include themify($themes) { - border-color: themed('calloutBorderColor'); - background-color: themed('calloutBackgroundColor'); + border-left-color: themed("primaryColor"); } .callout-heading { - margin-top: 0; + @include themify($themes) { + color: themed("primaryColor"); + } + } + } + + &.callout-info { + @include themify($themes) { + border-left-color: themed("infoColor"); } - h3.callout-heading { - font-weight: bold; - text-transform: uppercase; + .callout-heading { + @include themify($themes) { + color: themed("infoColor"); + } + } + } + + &.callout-danger { + @include themify($themes) { + border-left-color: themed("dangerColor"); } - &.callout-primary { - @include themify($themes) { - border-left-color: themed('primaryColor'); - } + .callout-heading { + @include themify($themes) { + color: themed("dangerColor"); + } + } + } - .callout-heading { - @include themify($themes) { - color: themed('primaryColor'); - } - } + &.callout-success { + @include themify($themes) { + border-left-color: themed("successColor"); } - &.callout-info { - @include themify($themes) { - border-left-color: themed('infoColor'); - } + .callout-heading { + @include themify($themes) { + color: themed("successColor"); + } + } + } - .callout-heading { - @include themify($themes) { - color: themed('infoColor'); - } - } + &.callout-warning { + @include themify($themes) { + border-left-color: themed("warningColor"); } - &.callout-danger { - @include themify($themes) { - border-left-color: themed('dangerColor'); - } - - .callout-heading { - @include themify($themes) { - color: themed('dangerColor'); - } - } + .callout-heading { + @include themify($themes) { + color: themed("warningColor"); + } } + } - &.callout-success { - @include themify($themes) { - border-left-color: themed('successColor'); - } - - .callout-heading { - @include themify($themes) { - color: themed('successColor'); - } - } + &.clickable { + &:hover, + &:focus, + &.active { + @include themify($themes) { + background-color: themed("boxBackgroundHoverColor"); + } } + } - &.callout-warning { - @include themify($themes) { - border-left-color: themed('warningColor'); - } - - .callout-heading { - @include themify($themes) { - color: themed('warningColor'); - } - } - } - - &.clickable { - &:hover, &:focus, &.active { - @include themify($themes) { - background-color: themed('boxBackgroundHoverColor'); - } - } - } - - .enforced-policy-options ul { - padding-left: 30px; - margin: 0; - } + .enforced-policy-options ul { + padding-left: 30px; + margin: 0; + } } input[type="password"]::-ms-reveal { - display: none; + display: none; } .flex { - display: flex; + display: flex; - &.flex-grow { - > * { - flex: 1; - } + &.flex-grow { + > * { + flex: 1; } + } } // Workaround for slow performance on external monitors on Chrome + MacOS // See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 @keyframes redraw { - 0% { - opacity: 0.99; - } - 100% { - opacity: 1; - } + 0% { + opacity: 0.99; + } + 100% { + opacity: 1; + } } html.force_redraw { - animation: redraw 1s linear infinite; + animation: redraw 1s linear infinite; } diff --git a/src/popup/scss/modal.scss b/src/popup/scss/modal.scss index d7ae2fa80c..85c75ee766 100644 --- a/src/popup/scss/modal.scss +++ b/src/popup/scss/modal.scss @@ -7,7 +7,13 @@ $border-radius-lg: $border-radius; // 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: 1050 !default; @@ -15,19 +21,19 @@ $zindex-modal: 1050 !default; // Padding applied to the modal body $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-title-line-height: $line-height-base !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-box-shadow-xs: none; $modal-content-box-shadow-sm-up: none; $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-footer-border-color: $modal-header-border-color !default; $modal-header-border-width: $modal-content-border-width !default; @@ -38,7 +44,7 @@ $modal-lg: 800px !default; $modal-md: 500px !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-weight: bold !default; @@ -48,45 +54,44 @@ $close-text-shadow: 0 1px 0 $white !default; // ref: https://github.com/twbs/bootstrap/blob/v4-dev/scss/mixins/_breakpoints.scss @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { - $min: breakpoint-min($name, $breakpoints); + $min: breakpoint-min($name, $breakpoints); - @if $min { - @media (min-width: $min) { - @content; - } - } - @else { - @content; + @if $min { + @media (min-width: $min) { + @content; } + } @else { + @content; + } } @function breakpoint-min($name, $breakpoints: $grid-breakpoints) { - $min: map-get($breakpoints, $name); - @return if($min != 0, $min, null); + $min: map-get($breakpoints, $name); + @return if($min != 0, $min, null); } // Custom Added CSS animations @keyframes modalshow { - 0% { - opacity: 0; - transform: translate(0, -25%); - } + 0% { + opacity: 0; + transform: translate(0, -25%); + } - 100% { - opacity: 1; - transform: translate(0, 0); - } + 100% { + opacity: 1; + transform: translate(0, 0); + } } @keyframes backdropshow { - 0% { - opacity: 0; - } + 0% { + opacity: 0; + } - 100% { - opacity: $modal-backdrop-opacity; - } + 100% { + opacity: $modal-backdrop-opacity; + } } // ref: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_modal.scss @@ -96,234 +101,234 @@ $close-text-shadow: 0 1px 0 $white !default; // .modal-dialog - positioning shell for the actual modal // .modal-content - actual modal w/ bg and corners and stuff - // Kill the scroll on the body .modal-open { - overflow: hidden; + overflow: hidden; } // Container that the modal scrolls within .modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: $zindex-modal; - //display: none; - overflow: hidden; - // Prevent Chrome on Windows from adding a focus outline. For details, see - // https://github.com/twbs/bootstrap/pull/10951. - outline: 0; - // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a - // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342 - // See also https://github.com/twbs/bootstrap/issues/17695 - .modal-open & { - overflow-x: hidden; - overflow-y: auto; - } + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal; + //display: none; + overflow: hidden; + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a + // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342 + // See also https://github.com/twbs/bootstrap/issues/17695 + .modal-open & { + overflow-x: hidden; + overflow-y: auto; + } } // Shell div to position the modal with bottom padding .modal-dialog { - position: relative; - width: auto; - margin: $modal-dialog-margin; - // allow clicks to pass through for custom click handling to close modal - pointer-events: none; - // When fading in the modal, animate it to slide down - .modal.fade & { - //@include transition($modal-transition); - //transform: translate(0, -25%); - animation: modalshow 0.3s ease-in; - } - //.modal.show & { - // transform: translate(0, 0); - //} - transform: translate(0, 0); + position: relative; + width: auto; + margin: $modal-dialog-margin; + // allow clicks to pass through for custom click handling to close modal + pointer-events: none; + // When fading in the modal, animate it to slide down + .modal.fade & { + //@include transition($modal-transition); + //transform: translate(0, -25%); + animation: modalshow 0.3s ease-in; + } + //.modal.show & { + // transform: translate(0, 0); + //} + transform: translate(0, 0); } .modal-dialog-centered { - display: flex; - align-items: center; - min-height: calc(100% - (#{$modal-dialog-margin} * 2)); + display: flex; + align-items: center; + min-height: calc(100% - (#{$modal-dialog-margin} * 2)); } // Actual modal .modal-content { - position: relative; - display: flex; - flex-direction: column; - width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog` - // counteract the pointer-events: none; in the .modal-dialog - pointer-events: auto; - //background-color: $modal-content-bg; - background-clip: padding-box; - border: $modal-content-border-width solid $modal-content-border-color; - //@include border-radius($border-radius-lg); - //@include box-shadow($modal-content-box-shadow-xs); - border-radius: $border-radius-lg; - box-shadow: $modal-content-box-shadow-xs; - // Remove focus outline from opened modal - outline: 0; + position: relative; + display: flex; + flex-direction: column; + width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog` + // counteract the pointer-events: none; in the .modal-dialog + pointer-events: auto; + //background-color: $modal-content-bg; + background-clip: padding-box; + border: $modal-content-border-width solid $modal-content-border-color; + //@include border-radius($border-radius-lg); + //@include box-shadow($modal-content-box-shadow-xs); + border-radius: $border-radius-lg; + box-shadow: $modal-content-box-shadow-xs; + // Remove focus outline from opened modal + outline: 0; - @include themify($themes) { - background-color: themed('backgroundColorAlt'); - } + @include themify($themes) { + background-color: themed("backgroundColorAlt"); + } } // Modal background .modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: $zindex-modal-backdrop; - background-color: $modal-backdrop-bg; - // Fade for backdrop - &.fade { - //opacity: 0; - animation: backdropshow 0.1s ease-in; - } - //&.show { - // opacity: $modal-backdrop-opacity; - //} - opacity: $modal-backdrop-opacity; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal-backdrop; + background-color: $modal-backdrop-bg; + // Fade for backdrop + &.fade { + //opacity: 0; + animation: backdropshow 0.1s ease-in; + } + //&.show { + // opacity: $modal-backdrop-opacity; + //} + opacity: $modal-backdrop-opacity; } // Modal header // Top section of the modal w/ title and dismiss .modal-header { - display: flex; - align-items: flex-start; // so the close btn always stays on the upper right corner - justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends + display: flex; + align-items: flex-start; // so the close btn always stays on the upper right corner + justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends + padding: $modal-header-padding $modal-inner-padding; + border-bottom: $modal-header-border-width solid $modal-header-border-color; + //@include border-top-radius($border-radius-lg); + + @include themify($themes) { + border-bottom-color: themed("borderColor"); + } + + .close { padding: $modal-header-padding $modal-inner-padding; - border-bottom: $modal-header-border-width solid $modal-header-border-color; - //@include border-top-radius($border-radius-lg); + // auto on the left force icon to the right even when there is no .modal-title + margin: (-$modal-header-padding) (-$modal-inner-padding) (-$modal-header-padding) auto; + } - @include themify($themes) { - border-bottom-color: themed('borderColor'); - } - - .close { - padding: $modal-header-padding $modal-inner-padding; - // auto on the left force icon to the right even when there is no .modal-title - margin: (-$modal-header-padding) (-$modal-inner-padding) (-$modal-header-padding) auto; - } - - h5 { - font-size: $font-size-base; - font-weight: bold; - display: flex; - align-items: center; - - .fa { - margin-right: 5px; - } + h5 { + font-size: $font-size-base; + font-weight: bold; + display: flex; + align-items: center; + + .fa { + margin-right: 5px; } + } } // Title text within header .modal-title { - margin-bottom: 0; - line-height: $modal-title-line-height; + margin-bottom: 0; + line-height: $modal-title-line-height; } // Modal body // Where all modal content resides (sibling of .modal-header and .modal-footer) .modal-body { - position: relative; - // Enable `flex-grow: 1` so that the body take up as much space as possible - // when should there be a fixed height on `.modal-dialog`. - flex: 1 1 auto; - padding: $modal-inner-padding; + position: relative; + // Enable `flex-grow: 1` so that the body take up as much space as possible + // when should there be a fixed height on `.modal-dialog`. + flex: 1 1 auto; + padding: $modal-inner-padding; } // Footer (for actions) .modal-footer { + display: flex; + align-items: center; // vertically center + //justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items + padding: $modal-inner-padding; + border-top: $modal-footer-border-width solid $modal-footer-border-color; + + @include themify($themes) { + border-top-color: themed("borderColor"); + } + + // Easily place margin between footer elements + button { + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + + .right { + margin-left: auto; display: flex; - align-items: center; // vertically center - //justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items - padding: $modal-inner-padding; - border-top: $modal-footer-border-width solid $modal-footer-border-color; - - @include themify($themes) { - border-top-color: themed('borderColor'); - } - - // Easily place margin between footer elements - button { - margin-right: 10px; - - &:last-child { - margin-right: 0; - } - } - - .right { - margin-left: auto; - display: flex; - } + } } // Measure scrollbar width for padding body during modal show/hide .modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; } // Scale up the modal @include media-breakpoint-up(sm) { - // Automatically set modal's width for larger viewports - .modal-dialog { - max-width: $modal-md; - margin: $modal-dialog-margin-y-sm-up auto; - } + // Automatically set modal's width for larger viewports + .modal-dialog { + max-width: $modal-md; + margin: $modal-dialog-margin-y-sm-up auto; + } - .modal-dialog-centered { - min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2)); - } + .modal-dialog-centered { + min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2)); + } - .modal-content { - //@include box-shadow($modal-content-box-shadow-sm-up); - box-shadow: $modal-content-box-shadow-sm-up; - } + .modal-content { + //@include box-shadow($modal-content-box-shadow-sm-up); + box-shadow: $modal-content-box-shadow-sm-up; + } - .modal-sm { - max-width: $modal-sm; - } + .modal-sm { + max-width: $modal-sm; + } } @include media-breakpoint-up(lg) { - .modal-lg { - max-width: $modal-lg; - } + .modal-lg { + max-width: $modal-lg; + } } // ref: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_close.scss .close { - float: right; - font-size: $close-font-size; - font-weight: $close-font-weight; - line-height: 1; - color: $close-color; - text-shadow: $close-text-shadow; - opacity: .5; + float: right; + font-size: $close-font-size; + font-weight: $close-font-weight; + line-height: 1; + color: $close-color; + text-shadow: $close-text-shadow; + opacity: 0.5; - &:hover, &:focus { - color: $close-color; - text-decoration: none; - opacity: .75; - } - // Opinionated: add "hand" cursor to non-disabled .close elements - &:not(:disabled):not(.disabled) { - cursor: pointer; - } + &:hover, + &:focus { + color: $close-color; + text-decoration: none; + opacity: 0.75; + } + // Opinionated: add "hand" cursor to non-disabled .close elements + &:not(:disabled):not(.disabled) { + cursor: pointer; + } } // Additional properties for button version @@ -333,19 +338,19 @@ $close-text-shadow: 0 1px 0 $white !default; // stylelint-disable property-no-vendor-prefix, selector-no-qualifying-type button.close { - padding: 0; - background-color: transparent; - border: 0; - -webkit-appearance: none; + padding: 0; + background-color: transparent; + border: 0; + -webkit-appearance: none; } // stylelint-enable // box .modal-content .box { - margin-top: 20px; + margin-top: 20px; - &:first-child { - margin-top: 0; - } + &:first-child { + margin-top: 0; + } } diff --git a/src/popup/scss/pages.scss b/src/popup/scss/pages.scss index 57b422bb39..4134f9b560 100644 --- a/src/popup/scss/pages.scss +++ b/src/popup/scss/pages.scss @@ -1,95 +1,97 @@ @import "variables.scss"; app-sync { - content { - .btn { - margin-bottom: 10px; - } + content { + .btn { + margin-bottom: 10px; } + } } app-password-generator .password-block { - font-size: $font-size-large; - font-family: $font-family-monospace; - margin: 20px; + font-size: $font-size-large; + font-family: $font-family-monospace; + margin: 20px; - .password-wrapper { - text-align: center; - } + .password-wrapper { + text-align: center; + } } app-home { - position: fixed; - height: 100%; - width: 100%; + position: fixed; + height: 100%; + width: 100%; - .center-content { - margin-top: -50px; - height: calc(100% + 50px); + .center-content { + margin-top: -50px; + height: calc(100% + 50px); + } + + img { + width: 284px; + margin: 0 auto; + } + + p.lead { + margin: 30px 0; + } + + .btn + .btn { + margin-top: 10px; + } + + a.settings-icon { + position: absolute; + top: 10px; + left: 10px; + + @include themify($themes) { + color: themed("mutedColor"); } - img { - width: 284px; - margin: 0 auto; + &:not(:hover):not(:focus) { + span { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } + } + + &:hover, + &:focus { + text-decoration: none; + + @include themify($themes) { + color: themed("primaryColor"); + } + } + } +} + +body.body-sm, +body.body-xs { + app-home { + .center-content { + margin-top: 0; + height: 100%; } p.lead { - margin: 30px 0; - } - - .btn + .btn { - margin-top: 10px; - } - - a.settings-icon { - position: absolute; - top: 10px; - left: 10px; - - @include themify($themes) { - color: themed('mutedColor'); - } - - &:not(:hover):not(:focus) { - span { - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; - } - } - - &:hover, &:focus { - text-decoration: none; - - @include themify($themes) { - color: themed('primaryColor'); - } - } - } -} - -body.body-sm, body.body-xs { - app-home { - .center-content { - margin-top: 0; - height: 100%; - } - - p.lead { - margin: 15px 0; - } + margin: 15px 0; } + } } body.body-full { - app-home { - .center-content { - margin-top: -80px; - height: calc(100% + 80px); - } + app-home { + .center-content { + margin-top: -80px; + height: calc(100% + 80px); } + } } diff --git a/src/popup/scss/plugins.scss b/src/popup/scss/plugins.scss index 9062e4cbdb..3825735205 100644 --- a/src/popup/scss/plugins.scss +++ b/src/popup/scss/plugins.scss @@ -1,6 +1,6 @@ $fa-font-path: "~font-awesome/fonts"; @import "~font-awesome/scss/font-awesome.scss"; -@import '~ngx-toastr/toastr'; +@import "~ngx-toastr/toastr"; @import "~sweetalert2/src/sweetalert2.scss"; @import "variables.scss"; @@ -9,204 +9,206 @@ $fa-font-path: "~font-awesome/fonts"; // Toaster .toast-container { + .toast-close-button { + font-size: 18px; + margin-right: 4px; + } + + .ngx-toastr { + align-items: center; + background-image: none !important; + border-radius: $border-radius; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); + display: flex; + padding: 15px; + .toast-close-button { - font-size: 18px; - margin-right: 4px; + position: absolute; + right: 5px; + top: 0; } - .ngx-toastr { - align-items: center; - background-image: none !important; - border-radius: $border-radius; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); - display: flex; - padding: 15px; - - .toast-close-button { - position: absolute; - right: 5px; - top: 0; - } - - &:hover { - box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); - } - - .icon i::before { - float: left; - font-style: normal; - font-family: FontAwesome; - font-size: 25px; - line-height: 20px; - padding-right: 15px; - } - - .toast-message { - p { - margin-bottom: 0.5rem; - - &:last-child { - margin-bottom: 0; - } - } - } - - &.toast-danger, &.toast-error { - @include themify($themes) { - background-color: themed('dangerColor'); - } - - .icon i::before { - content: "\f0e7"; - } - } - - &.toast-warning { - @include themify($themes) { - background-color: themed('warningColor'); - } - - .icon i::before { - content: "\f071"; - } - } - - &.toast-info { - @include themify($themes) { - background-color: themed('infoColor'); - } - - .icon i:before { - content: "\f05a"; - } - } - - &.toast-success { - @include themify($themes) { - background-color: themed('successColor'); - } - - .icon i:before { - content: "\f00C"; - } - } + &:hover { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); } + + .icon i::before { + float: left; + font-style: normal; + font-family: FontAwesome; + font-size: 25px; + line-height: 20px; + padding-right: 15px; + } + + .toast-message { + p { + margin-bottom: 0.5rem; + + &:last-child { + margin-bottom: 0; + } + } + } + + &.toast-danger, + &.toast-error { + @include themify($themes) { + background-color: themed("dangerColor"); + } + + .icon i::before { + content: "\f0e7"; + } + } + + &.toast-warning { + @include themify($themes) { + background-color: themed("warningColor"); + } + + .icon i::before { + content: "\f071"; + } + } + + &.toast-info { + @include themify($themes) { + background-color: themed("infoColor"); + } + + .icon i:before { + content: "\f05a"; + } + } + + &.toast-success { + @include themify($themes) { + background-color: themed("successColor"); + } + + .icon i:before { + content: "\f00C"; + } + } + } } // SweetAlert .swal2-popup { - padding: 15px; - border-radius: $border-radius; - width: 34em; + padding: 15px; + border-radius: $border-radius; + width: 34em; + + @include themify($themes) { + background-color: themed("backgroundColorAlt"); + color: themed("textColor"); + } + + .swal2-icon { + margin: 0 auto; + width: auto; + height: auto; + border: none; + } + + .swal2-content { + margin: 0; + font-size: $font-size-base; + @include themify($themes) { + color: themed("textColor"); + } + + label.checkbox { + margin-top: 10px; + display: flex; + text-align: left; + align-items: top; + + input { + margin: 3px 5px 0 1px; + } + } + + .swal2-input, + .swal2-textarea { + border: 1px solid #000000; + border-radius: $border-radius; + margin-bottom: 0; + box-shadow: none; + // Inherit theme font-size + font-size: inherit; + + // Sweetalert 1 did not have box-shadow + &:focus { + box-shadow: none; + } + @include themify($themes) { + border-color: themed("inputBorderColor"); + color: themed("textColor"); + background-color: themed("inputBackgroundColor"); + } + &::-webkit-input-placeholder { + @include themify($themes) { + color: themed("inputPlaceholderColor"); + } + } + } + } + + i.swal-custom-icon { + display: block; + margin: 0 auto; + font-size: 35px; + } + + .swal2-title { + padding: 10px 0 15px; + margin: 0; + font-size: $font-size-large; @include themify($themes) { - background-color: themed('backgroundColorAlt'); - color: themed('textColor'); + color: themed("textColor"); } + } - .swal2-icon { - margin: 0 auto; - width: auto; - height: auto; - border: none; + .swal2-text { + text-align: left; // sweetalert1 behaviour + font-size: $font-size-base; + + @include themify($themes) { + color: themed("textColor"); } + } - .swal2-content { - margin: 0; - font-size: $font-size-base; - @include themify($themes) { - color: themed('textColor'); - } + > .swal2-text:first-child { + margin-top: 20px; + } - label.checkbox { - margin-top: 10px; - display: flex; - text-align: left; - align-items: top; + .swal2-actions { + margin: 20px auto 0; + justify-content: flex-start; + flex-direction: row-reverse; - input { - margin: 3px 5px 0 1px; - } - } + button { + margin-left: 10px; + @extend .btn; - .swal2-input, .swal2-textarea { - border: 1px solid #000000; - border-radius: $border-radius; - margin-bottom: 0; - box-shadow: none; - // Inherit theme font-size - font-size: inherit; - - // Sweetalert 1 did not have box-shadow - &:focus { - box-shadow: none; - } - @include themify($themes) { - border-color: themed('inputBorderColor'); - color: themed('textColor'); - background-color: themed('inputBackgroundColor'); - } - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed('inputPlaceholderColor'); - } - } - } + &.swal2-confirm { + @extend .btn, .primary; + font-weight: bold; + } } + } - i.swal-custom-icon { - display: block; - margin: 0 auto; - font-size: 35px; - } - - .swal2-title { - padding: 10px 0 15px; - margin: 0; - font-size: $font-size-large; - - @include themify($themes) { - color: themed('textColor'); - } - } - - .swal2-text { - text-align: left; // sweetalert1 behaviour - font-size: $font-size-base; - - @include themify($themes) { - color: themed('textColor'); - } - } - - > .swal2-text:first-child { - margin-top: 20px; - } - - .swal2-actions { - margin: 20px auto 0; - justify-content: flex-start; - flex-direction: row-reverse; - - button { - margin-left: 10px; - @extend .btn; - - &.swal2-confirm { - @extend .btn, .primary; - font-weight: bold; - } - } - } - - .swal2-validation-message { - margin-top: 20px; - } + .swal2-validation-message { + margin-top: 20px; + } } date-input-polyfill { - &[data-open="true"] { - z-index: 10000 !important; - } + &[data-open="true"] { + z-index: 10000 !important; + } } diff --git a/src/popup/scss/variables.scss b/src/popup/scss/variables.scss index 825aeeff91..915e190c43 100644 --- a/src/popup/scss/variables.scss +++ b/src/popup/scss/variables.scss @@ -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-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace; +$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; $font-size-small: 12px; @@ -18,12 +18,12 @@ $gray: #555; $gray-light: #777; $text-muted: $gray-light; -$brand-primary: #175DDC; +$brand-primary: #175ddc; $brand-danger: #dd4b39; $brand-success: #00a65a; $brand-info: #555555; $brand-warning: #bf7e16; -$brand-primary-accent: #1252A3; +$brand-primary-accent: #1252a3; $background-color: #f0f0f0; @@ -37,236 +37,241 @@ $button-color: lighten($text-color, 40%); $button-color-primary: darken($brand-primary, 8%); $button-color-danger: darken($brand-danger, 10%); -$solarizedDarkBase03: #002b36; -$solarizedDarkBase02: #073642; -$solarizedDarkBase01: #586e75; -$solarizedDarkBase00: #657b83; -$solarizedDarkBase0: #839496; -$solarizedDarkBase1: #93a1a1; -$solarizedDarkBase2: #eee8d5; -$solarizedDarkBase3: #fdf6e3; -$solarizedDarkYellow: #b58900; -$solarizedDarkOrange: #cb4b16; -$solarizedDarkRed: #dc322f; -$solarizedDarkMagenta: #d33682; -$solarizedDarkViolet: #6c71c4; -$solarizedDarkBlue: #268bd2; -$solarizedDarkCyan: #2aa198; -$solarizedDarkGreen: #859900; +$solarizedDarkBase03: #002b36; +$solarizedDarkBase02: #073642; +$solarizedDarkBase01: #586e75; +$solarizedDarkBase00: #657b83; +$solarizedDarkBase0: #839496; +$solarizedDarkBase1: #93a1a1; +$solarizedDarkBase2: #eee8d5; +$solarizedDarkBase3: #fdf6e3; +$solarizedDarkYellow: #b58900; +$solarizedDarkOrange: #cb4b16; +$solarizedDarkRed: #dc322f; +$solarizedDarkMagenta: #d33682; +$solarizedDarkViolet: #6c71c4; +$solarizedDarkBlue: #268bd2; +$solarizedDarkCyan: #2aa198; +$solarizedDarkGreen: #859900; $themes: ( - light: ( - textColor: $text-color, - borderColor: $border-color-dark, - backgroundColor: $background-color, - backgroundColorAlt: #ffffff, - scrollbarColor: rgba(100,100,100,.2), - scrollbarHoverColor: rgba(100,100,100,.4), - boxBackgroundColor: $box-background-color, - boxBackgroundHoverColor: $box-background-hover-color, - boxBorderColor: $box-border-color, - tabBackgroundColor: #ffffff, - tabBackgroundHoverColor: $list-item-hover, - headerColor: #ffffff, - headerBackgroundColor: $brand-primary, - headerBackgroundHoverColor: rgba(255, 255, 255, 0.1), - headerBorderColor: $brand-primary, - headerInputBackgroundColor: darken($brand-primary, 8%), - headerInputBackgroundFocusColor: darken($brand-primary, 10%), - headerInputColor: #ffffff, - headerInputPlaceholderColor: lighten($brand-primary, 35%), - listItemBackgroundHoverColor: $list-item-hover, - disabledIconColor: $list-icon-color, - disabledBoxOpacity: $disabled-box-opacity, - headingColor: $gray-light, - labelColor: $gray-light, - mutedColor: $text-muted, - totpStrokeColor: $brand-primary, - boxRowButtonColor: $brand-primary, - boxRowButtonHoverColor: darken($brand-primary, 10%), - inputBorderColor: darken($border-color-dark, 7%), - inputBackgroundColor: #ffffff, - inputPlaceholderColor: lighten($gray-light, 35%), - buttonBackgroundColor: $button-background-color, - buttonBorderColor: $button-border-color, - buttonColor: $button-color, - buttonPrimaryColor: $button-color-primary, - buttonDangerColor: $button-color-danger, - primaryColor: $brand-primary, - primaryAccentColor: $brand-primary-accent, - dangerColor: $brand-danger, - successColor: $brand-success, - infoColor: $brand-info, - warningColor: $brand-warning, - logoSuffix: 'dark', - passwordNumberColor: #007fde, - passwordSpecialColor: #c40800, - calloutBorderColor: $border-color-dark, - calloutBackgroundColor: $box-background-color, - ), - dark: ( - textColor: #ffffff, - borderColor: #111111, - backgroundColor: #222222, - backgroundColorAlt: #3d3d3d, - scrollbarColor: #4d4d4d, - scrollbarHoverColor: #5f5f5f, - boxBackgroundColor: #363636, - boxBackgroundHoverColor: #3f3f3f, - boxBorderColor: #2f2f2f, - tabBackgroundColor: #363636, - tabBackgroundHoverColor: #3f3f3f, - headerColor: #ffffff, - headerBackgroundColor: #363636, - headerBackgroundHoverColor: #3f3f3f, - headerBorderColor: #111111, - headerInputBackgroundColor: #222222, - headerInputBackgroundFocusColor: #1d1d1d, - headerInputColor: #ffffff, - headerInputPlaceholderColor: #707070, - listItemBackgroundHoverColor: #3c3c3c, - disabledIconColor: #cacaca, - disabledBoxOpacity: 0.5, - headingColor: #a3a3a3, - labelColor: #a3a3a3, - mutedColor: #a3a3a3, - totpStrokeColor: #cacaca, - boxRowButtonColor: #cacaca, - boxRowButtonHoverColor: #ffffff, - inputBorderColor: #222222, - inputBackgroundColor: #363636, - inputPlaceholderColor: #707070, - buttonBackgroundColor: #363636, - buttonBorderColor: #1f1f1f, - buttonColor: #e0e0e0, - buttonPrimaryColor: #46ace7, - buttonDangerColor: #ff3e24, - primaryColor: #52bdfb, - primaryAccentColor: #3ea1da, - dangerColor: #ff3e24, - successColor: $brand-success, - infoColor: $brand-info, - warningColor: $brand-warning, - logoSuffix: 'white', - passwordNumberColor: #52bdfb, - passwordSpecialColor: #ff7c70, - calloutBorderColor: #111111, - calloutBackgroundColor: #3d3d3d, - ), - nord: ( - textColor: $nord5, - borderColor: $nord0, - backgroundColor: $nord1, - backgroundColorAlt: $nord2, - scrollbarColor: $nord4, - scrollbarHoverColor: $nord6, - boxBackgroundColor: $nord2, - boxBackgroundHoverColor: $nord3, - boxBorderColor: $nord1, - tabBackgroundColor: $nord1, - tabBackgroundHoverColor: $nord2, - headerColor: $nord5, - headerBackgroundColor: $nord1, - headerBackgroundHoverColor: $nord2, - headerBorderColor: $nord0, - headerInputBackgroundColor: $nord6, - headerInputBackgroundFocusColor: $nord5, - headerInputColor: $nord2, - headerInputPlaceholderColor: $nord3, - listItemBackgroundHoverColor: $nord3, - disabledIconColor: $nord4, - disabledBoxOpacity: 0.5, - headingColor: $nord4, - labelColor: $nord4, - mutedColor: $nord4, - totpStrokeColor: $nord4, - boxRowButtonColor: $nord4, - boxRowButtonHoverColor: $nord6, - inputBorderColor: $nord0, - inputBackgroundColor: $nord2, - inputPlaceholderColor: lighten($nord3, 20%), - buttonBackgroundColor: $nord3, - buttonBorderColor: $nord0, - buttonColor: $nord5, - buttonPrimaryColor: $nord8, - buttonDangerColor: $nord11, - primaryColor: $nord9, - primaryAccentColor: $nord8, - dangerColor: $nord11, - successColor: $nord14, - infoColor: $nord9, - warningColor: $nord12, - logoSuffix: 'white', - passwordNumberColor: $nord8, - passwordSpecialColor: $nord12, - calloutBorderColor: $nord0, - calloutBackgroundColor: $nord2, - ), - solarizedDark: ( - textColor: $solarizedDarkBase2, - borderColor: $solarizedDarkBase03, - backgroundColor: $solarizedDarkBase03, - backgroundColorAlt: $solarizedDarkBase02, - scrollbarColor: $solarizedDarkBase0, - scrollbarHoverColor: $solarizedDarkBase2, - boxBackgroundColor: $solarizedDarkBase03, - boxBackgroundHoverColor: $solarizedDarkBase02, - boxBorderColor: $solarizedDarkBase02, - tabBackgroundColor: $solarizedDarkBase02, - tabBackgroundHoverColor: $solarizedDarkBase01, - headerColor: $solarizedDarkBase1, - headerBackgroundColor: $solarizedDarkBase02, - headerBackgroundHoverColor: $solarizedDarkBase01, - headerBorderColor: $solarizedDarkBase03, - headerInputBackgroundColor: $solarizedDarkBase2, - headerInputBackgroundFocusColor: $solarizedDarkBase1, - headerInputColor: $solarizedDarkBase01, - headerInputPlaceholderColor: $solarizedDarkBase00, - listItemBackgroundHoverColor: $solarizedDarkBase02, - disabledIconColor: $solarizedDarkBase0, - disabledBoxOpacity: 0.5, - headingColor: $solarizedDarkBase0, - labelColor: $solarizedDarkBase0, - mutedColor: $solarizedDarkBase0, - totpStrokeColor: $solarizedDarkBase0, - boxRowButtonColor: $solarizedDarkBase0, - boxRowButtonHoverColor: $solarizedDarkBase2, - inputBorderColor: $solarizedDarkBase03, - inputBackgroundColor: $solarizedDarkBase01, - inputPlaceholderColor: lighten($solarizedDarkBase00, 20%), - buttonBackgroundColor: $solarizedDarkBase00, - buttonBorderColor: $solarizedDarkBase03, - buttonColor: $solarizedDarkBase1, - buttonPrimaryColor: $solarizedDarkCyan, - buttonDangerColor: $solarizedDarkRed, - primaryColor: $solarizedDarkGreen, - primaryAccentColor: $solarizedDarkCyan, - dangerColor: $solarizedDarkRed, - successColor: $solarizedDarkGreen, - infoColor: $solarizedDarkGreen, - warningColor: $solarizedDarkYellow, - logoSuffix: 'white', - passwordNumberColor: $solarizedDarkCyan, - passwordSpecialColor: $solarizedDarkYellow, - calloutBorderColor: $solarizedDarkBase03, - calloutBackgroundColor: $solarizedDarkBase01, - ), + light: ( + textColor: $text-color, + borderColor: $border-color-dark, + backgroundColor: $background-color, + backgroundColorAlt: #ffffff, + scrollbarColor: rgba(100, 100, 100, 0.2), + scrollbarHoverColor: rgba(100, 100, 100, 0.4), + boxBackgroundColor: $box-background-color, + boxBackgroundHoverColor: $box-background-hover-color, + boxBorderColor: $box-border-color, + tabBackgroundColor: #ffffff, + tabBackgroundHoverColor: $list-item-hover, + headerColor: #ffffff, + headerBackgroundColor: $brand-primary, + headerBackgroundHoverColor: rgba(255, 255, 255, 0.1), + headerBorderColor: $brand-primary, + headerInputBackgroundColor: darken($brand-primary, 8%), + headerInputBackgroundFocusColor: darken($brand-primary, 10%), + headerInputColor: #ffffff, + headerInputPlaceholderColor: lighten($brand-primary, 35%), + listItemBackgroundHoverColor: $list-item-hover, + disabledIconColor: $list-icon-color, + disabledBoxOpacity: $disabled-box-opacity, + headingColor: $gray-light, + labelColor: $gray-light, + mutedColor: $text-muted, + totpStrokeColor: $brand-primary, + boxRowButtonColor: $brand-primary, + boxRowButtonHoverColor: darken($brand-primary, 10%), + inputBorderColor: darken($border-color-dark, 7%), + inputBackgroundColor: #ffffff, + inputPlaceholderColor: lighten($gray-light, 35%), + buttonBackgroundColor: $button-background-color, + buttonBorderColor: $button-border-color, + buttonColor: $button-color, + buttonPrimaryColor: $button-color-primary, + buttonDangerColor: $button-color-danger, + primaryColor: $brand-primary, + primaryAccentColor: $brand-primary-accent, + dangerColor: $brand-danger, + successColor: $brand-success, + infoColor: $brand-info, + warningColor: $brand-warning, + logoSuffix: "dark", + passwordNumberColor: #007fde, + passwordSpecialColor: #c40800, + calloutBorderColor: $border-color-dark, + calloutBackgroundColor: $box-background-color, + ), + dark: ( + textColor: #ffffff, + borderColor: #111111, + backgroundColor: #222222, + backgroundColorAlt: #3d3d3d, + scrollbarColor: #4d4d4d, + scrollbarHoverColor: #5f5f5f, + boxBackgroundColor: #363636, + boxBackgroundHoverColor: #3f3f3f, + boxBorderColor: #2f2f2f, + tabBackgroundColor: #363636, + tabBackgroundHoverColor: #3f3f3f, + headerColor: #ffffff, + headerBackgroundColor: #363636, + headerBackgroundHoverColor: #3f3f3f, + headerBorderColor: #111111, + headerInputBackgroundColor: #222222, + headerInputBackgroundFocusColor: #1d1d1d, + headerInputColor: #ffffff, + headerInputPlaceholderColor: #707070, + listItemBackgroundHoverColor: #3c3c3c, + disabledIconColor: #cacaca, + disabledBoxOpacity: 0.5, + headingColor: #a3a3a3, + labelColor: #a3a3a3, + mutedColor: #a3a3a3, + totpStrokeColor: #cacaca, + boxRowButtonColor: #cacaca, + boxRowButtonHoverColor: #ffffff, + inputBorderColor: #222222, + inputBackgroundColor: #363636, + inputPlaceholderColor: #707070, + buttonBackgroundColor: #363636, + buttonBorderColor: #1f1f1f, + buttonColor: #e0e0e0, + buttonPrimaryColor: #46ace7, + buttonDangerColor: #ff3e24, + primaryColor: #52bdfb, + primaryAccentColor: #3ea1da, + dangerColor: #ff3e24, + successColor: $brand-success, + infoColor: $brand-info, + warningColor: $brand-warning, + logoSuffix: "white", + passwordNumberColor: #52bdfb, + passwordSpecialColor: #ff7c70, + calloutBorderColor: #111111, + calloutBackgroundColor: #3d3d3d, + ), + nord: ( + textColor: $nord5, + borderColor: $nord0, + backgroundColor: $nord1, + backgroundColorAlt: $nord2, + scrollbarColor: $nord4, + scrollbarHoverColor: $nord6, + boxBackgroundColor: $nord2, + boxBackgroundHoverColor: $nord3, + boxBorderColor: $nord1, + tabBackgroundColor: $nord1, + tabBackgroundHoverColor: $nord2, + headerColor: $nord5, + headerBackgroundColor: $nord1, + headerBackgroundHoverColor: $nord2, + headerBorderColor: $nord0, + headerInputBackgroundColor: $nord6, + headerInputBackgroundFocusColor: $nord5, + headerInputColor: $nord2, + headerInputPlaceholderColor: $nord3, + listItemBackgroundHoverColor: $nord3, + disabledIconColor: $nord4, + disabledBoxOpacity: 0.5, + headingColor: $nord4, + labelColor: $nord4, + mutedColor: $nord4, + totpStrokeColor: $nord4, + boxRowButtonColor: $nord4, + boxRowButtonHoverColor: $nord6, + inputBorderColor: $nord0, + inputBackgroundColor: $nord2, + inputPlaceholderColor: lighten($nord3, 20%), + buttonBackgroundColor: $nord3, + buttonBorderColor: $nord0, + buttonColor: $nord5, + buttonPrimaryColor: $nord8, + buttonDangerColor: $nord11, + primaryColor: $nord9, + primaryAccentColor: $nord8, + dangerColor: $nord11, + successColor: $nord14, + infoColor: $nord9, + warningColor: $nord12, + logoSuffix: "white", + passwordNumberColor: $nord8, + passwordSpecialColor: $nord12, + calloutBorderColor: $nord0, + calloutBackgroundColor: $nord2, + ), + solarizedDark: ( + textColor: $solarizedDarkBase2, + borderColor: $solarizedDarkBase03, + backgroundColor: $solarizedDarkBase03, + backgroundColorAlt: $solarizedDarkBase02, + scrollbarColor: $solarizedDarkBase0, + scrollbarHoverColor: $solarizedDarkBase2, + boxBackgroundColor: $solarizedDarkBase03, + boxBackgroundHoverColor: $solarizedDarkBase02, + boxBorderColor: $solarizedDarkBase02, + tabBackgroundColor: $solarizedDarkBase02, + tabBackgroundHoverColor: $solarizedDarkBase01, + headerColor: $solarizedDarkBase1, + headerBackgroundColor: $solarizedDarkBase02, + headerBackgroundHoverColor: $solarizedDarkBase01, + headerBorderColor: $solarizedDarkBase03, + headerInputBackgroundColor: $solarizedDarkBase2, + headerInputBackgroundFocusColor: $solarizedDarkBase1, + headerInputColor: $solarizedDarkBase01, + headerInputPlaceholderColor: $solarizedDarkBase00, + listItemBackgroundHoverColor: $solarizedDarkBase02, + disabledIconColor: $solarizedDarkBase0, + disabledBoxOpacity: 0.5, + headingColor: $solarizedDarkBase0, + labelColor: $solarizedDarkBase0, + mutedColor: $solarizedDarkBase0, + totpStrokeColor: $solarizedDarkBase0, + boxRowButtonColor: $solarizedDarkBase0, + boxRowButtonHoverColor: $solarizedDarkBase2, + inputBorderColor: $solarizedDarkBase03, + inputBackgroundColor: $solarizedDarkBase01, + inputPlaceholderColor: lighten($solarizedDarkBase00, 20%), + buttonBackgroundColor: $solarizedDarkBase00, + buttonBorderColor: $solarizedDarkBase03, + buttonColor: $solarizedDarkBase1, + buttonPrimaryColor: $solarizedDarkCyan, + buttonDangerColor: $solarizedDarkRed, + primaryColor: $solarizedDarkGreen, + primaryAccentColor: $solarizedDarkCyan, + dangerColor: $solarizedDarkRed, + successColor: $solarizedDarkGreen, + infoColor: $solarizedDarkGreen, + warningColor: $solarizedDarkYellow, + logoSuffix: "white", + passwordNumberColor: $solarizedDarkCyan, + passwordSpecialColor: $solarizedDarkYellow, + calloutBorderColor: $solarizedDarkBase03, + calloutBackgroundColor: $solarizedDarkBase01, + ), ); @mixin themify($themes: $themes) { - @each $theme, $map in $themes { - html.theme_#{$theme} & { - $theme-map: () !global; - @each $key, $submap in $map { - $value: map-get(map-get($themes, $theme), '#{$key}'); - $theme-map: map-merge($theme-map, ($key: $value)) !global; - } - @content; - $theme-map: null !global; - } + @each $theme, $map in $themes { + html.theme_#{$theme} & { + $theme-map: () !global; + @each $key, $submap in $map { + $value: map-get(map-get($themes, $theme), "#{$key}"); + $theme-map: map-merge( + $theme-map, + ( + $key: $value, + ) + ) !global; + } + @content; + $theme-map: null !global; } + } } @function themed($key) { - @return map-get($theme-map, $key); + @return map-get($theme-map, $key); } diff --git a/src/popup/send/efflux-dates.component.html b/src/popup/send/efflux-dates.component.html index b7b35f3a46..81fcf8f838 100644 --- a/src/popup/send/efflux-dates.component.html +++ b/src/popup/send/efflux-dates.component.html @@ -1,129 +1,209 @@ -
-
- -
- - -
-
- -
-
-
- - -
+
+
+ +
+ +
- -
-
- -
- - -
-
- -
-
-
-
- - -
- -
-
- + - - - -
- - -
-
- -
- - -
-
- - - -
-
- - - -
- - -
-
- -
- - -
-
- - - -
-
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + +
+ +
+
+ +
+ + + +
+ + +
+
+ +
+ + +
+
+ + + +
+
+ + + +
+ + +
+
+ +
+ + +
+
+ + + +
+
- diff --git a/src/popup/send/efflux-dates.component.ts b/src/popup/send/efflux-dates.component.ts index 21b2ed8aad..86a33f5764 100644 --- a/src/popup/send/efflux-dates.component.ts +++ b/src/popup/send/efflux-dates.component.ts @@ -1,30 +1,28 @@ -import { DatePipe } from '@angular/common'; +import { DatePipe } from "@angular/common"; -import { - Component, - EventEmitter, - Input, - Output, -} from '@angular/core'; +import { 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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { I18nService } from "jslib-common/abstractions/i18n.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({ - selector: 'app-send-efflux-dates', - templateUrl: 'efflux-dates.component.html', - viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], + selector: "app-send-efflux-dates", + templateUrl: "efflux-dates.component.html", + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], }) export class EffluxDatesComponent extends BaseEffluxDatesComponent { - @Input() readonly inPopout: boolean; - @Output() popOutWindow = new EventEmitter(); + @Input() readonly inPopout: boolean; + @Output() popOutWindow = new EventEmitter(); - constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, - protected datePipe: DatePipe) { - super(i18nService, platformUtilsService, datePipe); - } + constructor( + protected i18nService: I18nService, + protected platformUtilsService: PlatformUtilsService, + protected datePipe: DatePipe + ) { + super(i18nService, platformUtilsService, datePipe); + } } diff --git a/src/popup/send/send-add-edit.component.html b/src/popup/send/send-add-edit.component.html index 90e59193ae..09d2705caf 100644 --- a/src/popup/send/send-add-edit.component.html +++ b/src/popup/send/send-add-edit.component.html @@ -1,222 +1,319 @@
-
-
- +
+
+ +
+

+ {{ title }} +

+
+ +
+
+ + + + {{ "sendDisabledWarning" | i18n }} + + + {{ "sendOptionsPolicyInEffect" | i18n }} + + + +
{{ "sendLinuxChromiumFileWarning" | i18n }}
+
{{ "sendFirefoxFileWarning" | i18n }}
+
{{ "sendSafariFileWarning" | i18n }}
+
+ +
+
+
+ +
-

- {{title}} -

-
- +
+ +
+ +
+
+
+ +
+ + +
-
- - - - {{'sendDisabledWarning' | i18n}} - - - {{'sendOptionsPolicyInEffect' | i18n}} - - - -
{{'sendLinuxChromiumFileWarning' | i18n}}
-
{{'sendFirefoxFileWarning' | i18n}}
-
{{'sendSafariFileWarning' | i18n}}
-
- -
-
-
- - -
-
- +
+
+ +
+
+
+ +
{{ send.file.fileName }} ({{ send.file.sizeName }})
- -
-
-
- -
- - -
-
-
+
+ +
- -
-
-
- -
{{send.file.fileName}} ({{send.file.sizeName}})
-
-
- - -
-
- +
+ +
+ +
+
+
+ +
- -
-
-
- - -
-
- -
-
- - -
-
+
+ +
+
+ +
- -
-

- {{'share' | i18n}} -

-
- -
- - -
-
+
+
+ +
+

+ {{ "share" | i18n }} +

+
+ +
+ +
- -
-

- -

+
+
+ +
+

+ +

+
+
+ + + +
+
+
+ + +
-
- - - -
-
-
- - -
-
- -
- -
-
-
- - -
-
-
- -
-
-
-
- - - -
-
- -
-
-
- -
- -
-
-
- - -
-
- -
- -
-
-
- - -
-
-
- -
-
-
- - -
-
-
+ - -
-
- -
+
+ +
+
+
+ + +
- +
+ +
+
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ +
+
+ diff --git a/src/popup/send/send-add-edit.component.ts b/src/popup/send/send-add-edit.component.ts index def91594b0..fcfa434c29 100644 --- a/src/popup/send/send-add-edit.component.ts +++ b/src/popup/send/send-add-edit.component.ts @@ -1,128 +1,149 @@ -import { - DatePipe, - Location, -} from '@angular/common'; +import { DatePipe, Location } from "@angular/common"; -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SendService } from 'jslib-common/abstractions/send.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SendService } from "jslib-common/abstractions/send.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({ - selector: 'app-send-add-edit', - templateUrl: 'send-add-edit.component.html', + selector: "app-send-add-edit", + templateUrl: "send-add-edit.component.html", }) export class SendAddEditComponent extends BaseAddEditComponent { - // Options header - showOptions = false; - // File visibility - isFirefox = false; - inPopout = false; - inSidebar = false; - isLinux = false; - isUnsupportedMac = false; + // Options header + showOptions = false; + // File visibility + isFirefox = false; + inPopout = false; + inSidebar = false; + isLinux = false; + isUnsupportedMac = false; - constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, - userService: UserService, messagingService: MessagingService, policyService: PolicyService, - 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); + constructor( + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + userService: UserService, + messagingService: MessagingService, + policyService: PolicyService, + 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 { + return !(this.editMode || this.showFilePopoutMessage); + } + + get showFilePopoutMessage(): boolean { + return ( + !this.editMode && + (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning) + ); + } + + get showFirefoxFileWarning(): boolean { + return this.isFirefox && !(this.inSidebar || this.inPopout); + } + + get showSafariFileWarning(): boolean { + return this.isSafari && !this.inPopout; + } + + // Only show this for Chromium based browsers in Linux and Mac > Big Sur + get showChromiumFileWarning(): boolean { + return ( + (this.isLinux || this.isUnsupportedMac) && + !this.isFirefox && + !(this.inSidebar || this.inPopout) + ); + } + + popOutWindow() { + this.popupUtilsService.popOut(window); + } + + async ngOnInit() { + // File visilibity + this.isFirefox = this.platformUtilsService.isFirefox(); + this.inPopout = this.popupUtilsService.inPopout(window); + this.inSidebar = this.popupUtilsService.inSidebar(window); + this.isLinux = window?.navigator?.userAgent.indexOf("Linux") !== -1; + this.isUnsupportedMac = + this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes("Mac OS X 11"); + + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (params.sendId) { + this.sendId = params.sendId; + } + if (params.type) { + const type = parseInt(params.type, null); + this.type = type; + } + await this.load(); + }); + + window.setTimeout(() => { + if (!this.editMode) { + document.getElementById("name").focus(); + } + }, 200); + } + + async submit(): Promise { + if (await super.submit()) { + this.cancel(); + return true; } - get showFileSelector(): boolean { - return !(this.editMode || this.showFilePopoutMessage); + return false; + } + + async delete(): Promise { + if (await super.delete()) { + this.cancel(); + return true; } - get showFilePopoutMessage(): boolean { - return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning); - } - - get showFirefoxFileWarning(): boolean { - return this.isFirefox && !(this.inSidebar || this.inPopout); - } - - get showSafariFileWarning(): boolean { - return this.isSafari && !this.inPopout; - } - - // Only show this for Chromium based browsers in Linux and Mac > Big Sur - get showChromiumFileWarning(): boolean { - return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout); - } - - popOutWindow() { - this.popupUtilsService.popOut(window); - } - - async ngOnInit() { - // File visilibity - this.isFirefox = this.platformUtilsService.isFirefox(); - this.inPopout = this.popupUtilsService.inPopout(window); - this.inSidebar = this.popupUtilsService.inSidebar(window); - this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1; - this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11'); - - this.route.queryParams.pipe(first()).subscribe(async params => { - if (params.sendId) { - this.sendId = params.sendId; - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - await this.load(); - }); - - window.setTimeout(() => { - if (!this.editMode) { - document.getElementById('name').focus(); - } - }, 200); - } - - async submit(): Promise { - if (await super.submit()) { - this.cancel(); - return true; - } - - return false; - } - - async delete(): Promise { - if (await super.delete()) { - this.cancel(); - return true; - } - - return false; - } - - cancel() { - // If true, the window was pop'd out on the add-send page. location.back will not work - if ((window as any).previousPopupUrl.startsWith('/add-send')) { - this.router.navigate(['tabs/send']); - } else { - this.location.back(); - } + return false; + } + + cancel() { + // If true, the window was pop'd out on the add-send page. location.back will not work + if ((window as any).previousPopupUrl.startsWith("/add-send")) { + this.router.navigate(["tabs/send"]); + } else { + this.location.back(); } + } } diff --git a/src/popup/send/send-groupings.component.html b/src/popup/send/send-groupings.component.html index 9b9c7d4131..d5e20422a4 100644 --- a/src/popup/send/send-groupings.component.html +++ b/src/popup/send/send-groupings.component.html @@ -1,79 +1,122 @@
-
- -
-

{{'send' | i18n}}

- -
- -
+
+ +
+

{{ "send" | i18n }}

+ +
+ +
- - - {{'sendDisabledWarning' | i18n}} - -
- - - -

{{'noItemsInList' | i18n}}

- -
+ + + {{ "sendDisabledWarning" | i18n }} + +
+ + + +

{{ "noItemsInList" | i18n }}

+ +
+
+ +
+

+ {{ "types" | i18n }} +

+
+ + +
- -
-

- {{'types' | i18n}} -

-
- - -
-
-
-

- {{'allSends' | i18n}} -
{{sends.length}}
-

-
- -
-
-
- -
-

{{'noItemsInList' | i18n}}

-
-
-
- - -
-
-
+
+

+ {{ "allSends" | i18n }} +
{{ sends.length }}
+

+
+ +
+
+
+ +
+

{{ "noItemsInList" | i18n }}

+
+
+
+ + +
+
+
diff --git a/src/popup/send/send-groupings.component.ts b/src/popup/send/send-groupings.component.ts index 77befed92d..323ff2ed90 100644 --- a/src/popup/send/send-groupings.component.ts +++ b/src/popup/send/send-groupings.component.ts @@ -1,188 +1,205 @@ -import { - ChangeDetectorRef, - Component, - NgZone, -} from '@angular/core'; +import { ChangeDetectorRef, Component, NgZone } from "@angular/core"; -import { - Router, -} from '@angular/router'; +import { 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 { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SearchService } from 'jslib-common/abstractions/search.service'; -import { SendService } from 'jslib-common/abstractions/send.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SearchService } from "jslib-common/abstractions/search.service"; +import { SendService } from "jslib-common/abstractions/send.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { SyncService } from "jslib-common/abstractions/sync.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 ScopeStateId = ComponentId + 'Scope'; +const ComponentId = "SendComponent"; +const ScopeStateId = ComponentId + "Scope"; @Component({ - selector: 'app-send-groupings', - templateUrl: 'send-groupings.component.html', + selector: "app-send-groupings", + templateUrl: "send-groupings.component.html", }) export class SendGroupingsComponent extends BaseSendComponent { - // Header - showLeftHeader = true; - // Send Type Calculations - typeCounts = new Map(); - // State Handling - state: any; - scopeState: any; - private loadedTimeout: number; + // Header + showLeftHeader = true; + // Send Type Calculations + typeCounts = new Map(); + // State Handling + state: any; + scopeState: any; + private loadedTimeout: number; - constructor(sendService: SendService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, - policyService: PolicyService, userService: UserService, 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 () => { - this.calculateTypeCounts(); - this.selectAll(); - }; + constructor( + sendService: SendService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + environmentService: EnvironmentService, + ngZone: NgZone, + policyService: PolicyService, + userService: UserService, + 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 () => { + this.calculateTypeCounts(); + this.selectAll(); + }; + } + + async ngOnInit() { + // Determine Header details + this.showLeftHeader = !( + this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() + ); + // Clear state of Send Type Component + this.stateService.remove("SendTypeComponent"); + // Let super class finish + await super.ngOnInit(); + // Handle State Restore if necessary + const restoredScopeState = await this.restoreState(); + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText != null) { + this.searchText = this.state.searchText; } - async ngOnInit() { - // Determine Header details - this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()); - // Clear state of Send Type Component - this.stateService.remove('SendTypeComponent'); - // Let super class finish - await super.ngOnInit(); - // Handle State Restore if necessary - const restoredScopeState = await this.restoreState(); - this.state = (await this.stateService.get(ComponentId)) || {}; - if (this.state.searchText != null) { - this.searchText = this.state.searchText; + if (!this.syncService.syncInProgress) { + this.load(); + } else { + this.loadedTimeout = window.setTimeout(() => { + if (!this.loaded) { + this.load(); + } + }, 5000); + } + + if (!this.syncService.syncInProgress || restoredScopeState) { + window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); + } + + // Load all sends if sync completed in background + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + window.setTimeout(() => { + this.load(); + }, 500); + break; + default: + break; } - if (!this.syncService.syncInProgress) { - this.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - this.load(); - } - }, 5000); - } + this.changeDetectorRef.detectChanges(); + }); + }); + } - if (!this.syncService.syncInProgress || restoredScopeState) { - window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); - } + ngOnDestroy() { + // Remove timeout + if (this.loadedTimeout != null) { + window.clearTimeout(this.loadedTimeout); + } + // Save state + this.saveState(); + // Unsubscribe + this.broadcasterService.unsubscribe(ComponentId); + } - // Load all sends if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'syncCompleted': - window.setTimeout(() => { - this.load(); - }, 500); - break; - default: - break; - } + async selectType(type: SendType) { + this.router.navigate(["/send-type"], { queryParams: { type: type } }); + } - this.changeDetectorRef.detectChanges(); - }); - }); + async selectSend(s: SendView) { + this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } }); + } + + async addSend() { + if (this.disableSend) { + return; + } + this.router.navigate(["/add-send"]); + } + + async removePassword(s: SendView): Promise { + if (this.disableSend) { + return; + } + super.removePassword(s); + } + + showSearching() { + return ( + this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)) + ); + } + + private calculateTypeCounts() { + // Create type counts + const typeCounts = new Map(); + this.sends.forEach((s) => { + if (typeCounts.has(s.type)) { + typeCounts.set(s.type, typeCounts.get(s.type) + 1); + } else { + typeCounts.set(s.type, 1); + } + }); + this.typeCounts = typeCounts; + } + + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); + + this.scopeState = { + sends: this.sends, + typeCounts: this.typeCounts, + }; + await this.stateService.save(ScopeStateId, this.scopeState); + } + + private async restoreState(): Promise { + this.scopeState = await this.stateService.get(ScopeStateId); + if (this.scopeState == null) { + return false; } - ngOnDestroy() { - // Remove timeout - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - // Save state - this.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); + if (this.scopeState.sends != null) { + this.sends = this.scopeState.sends; + } + if (this.scopeState.typeCounts != null) { + this.typeCounts = this.scopeState.typeCounts; } - async selectType(type: SendType) { - this.router.navigate(['/send-type'], { queryParams: { type: type } }); - } - - async selectSend(s: SendView) { - this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; - } - this.router.navigate(['/add-send']); - } - - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; - } - super.removePassword(s); - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)); - } - - private calculateTypeCounts() { - // Create type counts - const typeCounts = new Map(); - this.sends.forEach(s => { - if (typeCounts.has(s.type)) { - typeCounts.set(s.type, typeCounts.get(s.type) + 1); - } else { - typeCounts.set(s.type, 1); - } - }); - this.typeCounts = typeCounts; - } - - private async saveState() { - this.state = { - scrollY: this.popupUtils.getContentScrollY(window), - searchText: this.searchText, - }; - await this.stateService.save(ComponentId, this.state); - - this.scopeState = { - sends: this.sends, - typeCounts: this.typeCounts, - }; - await this.stateService.save(ScopeStateId, this.scopeState); - } - - private async restoreState(): Promise { - this.scopeState = await this.stateService.get(ScopeStateId); - if (this.scopeState == null) { - return false; - } - - if (this.scopeState.sends != null) { - this.sends = this.scopeState.sends; - } - if (this.scopeState.typeCounts != null) { - this.typeCounts = this.scopeState.typeCounts; - } - - return true; - } + return true; + } } diff --git a/src/popup/send/send-type.component.html b/src/popup/send/send-type.component.html index e25f7f1b64..d6653c41df 100644 --- a/src/popup/send/send-type.component.html +++ b/src/popup/send/send-type.component.html @@ -1,45 +1,69 @@
-
- -
-

{{'send' | i18n}}

- -
- -
+
+ +
+

{{ "send" | i18n }}

+ +
+ +
- - - {{'sendDisabledWarning' | i18n}} - -
- - -

{{'noItemsInList' | i18n}}

- -
-
-
-

- {{groupingTitle}} - {{filteredSends.length}} -

-
- - -
+ + + {{ "sendDisabledWarning" | i18n }} + +
+ + +

{{ "noItemsInList" | i18n }}

+ +
+
+
+

+ {{ groupingTitle }} + {{ filteredSends.length }} +

+
+ +
+
diff --git a/src/popup/send/send-type.component.ts b/src/popup/send/send-type.component.ts index ba7f5ccee1..bd9fd4e14c 100644 --- a/src/popup/send/send-type.component.ts +++ b/src/popup/send/send-type.component.ts @@ -1,157 +1,172 @@ -import { - ChangeDetectorRef, - Component, - NgZone, -} from '@angular/core'; +import { ChangeDetectorRef, Component, NgZone } from "@angular/core"; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { Location } from '@angular/common'; +import { Location } from "@angular/common"; -import { SendView } from 'jslib-common/models/view/sendView'; +import { SendView } from "jslib-common/models/view/sendView"; -import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component'; +import { SendComponent as BaseSendComponent } from "jslib-angular/components/send/send.component"; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SearchService } from 'jslib-common/abstractions/search.service'; -import { SendService } from 'jslib-common/abstractions/send.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SearchService } from "jslib-common/abstractions/search.service"; +import { SendService } from "jslib-common/abstractions/send.service"; +import { StateService } from "jslib-common/abstractions/state.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 = 'SendTypeComponent'; +const ComponentId = "SendTypeComponent"; @Component({ - selector: 'app-send-type', - templateUrl: 'send-type.component.html', + selector: "app-send-type", + templateUrl: "send-type.component.html", }) export class SendTypeComponent extends BaseSendComponent { - groupingTitle: string; - // State Handling - state: any; - private refreshTimeout: number; - private applySavedState = true; + groupingTitle: string; + // State Handling + state: any; + private refreshTimeout: number; + private applySavedState = true; - constructor(sendService: SendService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, - policyService: PolicyService, userService: UserService, searchService: SearchService, - private popupUtils: PopupUtilsService, private stateService: StateService, - private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService, private router: Router, logService: LogService) { - super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, - policyService, userService, logService); - super.onSuccessfulLoad = async () => { - this.selectType(this.type); - }; - this.applySavedState = (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith('/send-type'); - } + constructor( + sendService: SendService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + environmentService: EnvironmentService, + ngZone: NgZone, + policyService: PolicyService, + userService: UserService, + searchService: SearchService, + private popupUtils: PopupUtilsService, + private stateService: StateService, + private route: ActivatedRoute, + private location: Location, + private changeDetectorRef: ChangeDetectorRef, + private broadcasterService: BroadcasterService, + private router: Router, + logService: LogService + ) { + super( + sendService, + i18nService, + platformUtilsService, + environmentService, + ngZone, + searchService, + policyService, + userService, + logService + ); + super.onSuccessfulLoad = async () => { + this.selectType(this.type); + }; + this.applySavedState = + (window as any).previousPopupUrl != null && + !(window as any).previousPopupUrl.startsWith("/send-type"); + } - async ngOnInit() { - // Let super class finish - await super.ngOnInit(); - this.route.queryParams.pipe(first()).subscribe(async params => { - if (this.applySavedState) { - this.state = (await this.stateService.get(ComponentId)) || {}; - if (this.state.searchText != null) { - this.searchText = this.state.searchText; - } - } - - if (params.type != null) { - this.type = parseInt(params.type, null); - switch (this.type) { - case SendType.Text: - this.groupingTitle = this.i18nService.t('sendTypeText'); - break; - case SendType.File: - this.groupingTitle = this.i18nService.t('sendTypeFile'); - break; - default: - break; - } - await this.load(s => s.type === this.type); - } - - // Restore state and remove reference - if (this.applySavedState && this.state != null) { - window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); - } - this.stateService.remove(ComponentId); - }); - - // Refresh Send list if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'syncCompleted': - if (message.successfully) { - this.refreshTimeout = window.setTimeout(() => { - this.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // Remove timeout - if (this.refreshTimeout != null) { - window.clearTimeout(this.refreshTimeout); + async ngOnInit() { + // Let super class finish + await super.ngOnInit(); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (this.applySavedState) { + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText != null) { + this.searchText = this.state.searchText; } - // Save state - this.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); - } + } - async selectSend(s: SendView) { - this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; + if (params.type != null) { + this.type = parseInt(params.type, null); + switch (this.type) { + case SendType.Text: + this.groupingTitle = this.i18nService.t("sendTypeText"); + break; + case SendType.File: + this.groupingTitle = this.i18nService.t("sendTypeFile"); + break; + default: + break; } - this.router.navigate(['/add-send'], { queryParams: { type: this.type } }); - } + await this.load((s) => s.type === this.type); + } - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; + // Restore state and remove reference + if (this.applySavedState && this.state != null) { + window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); + } + this.stateService.remove(ComponentId); + }); + + // Refresh Send list if sync completed in background + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + if (message.successfully) { + this.refreshTimeout = window.setTimeout(() => { + this.refresh(); + }, 500); + } + break; + default: + break; } - super.removePassword(s); - } - back() { - (window as any).routeDirection = 'b'; - this.location.back(); - } + this.changeDetectorRef.detectChanges(); + }); + }); + } - private async saveState() { - this.state = { - scrollY: this.popupUtils.getContentScrollY(window), - searchText: this.searchText, - }; - await this.stateService.save(ComponentId, this.state); + ngOnDestroy() { + // Remove timeout + if (this.refreshTimeout != null) { + window.clearTimeout(this.refreshTimeout); } + // Save state + this.saveState(); + // Unsubscribe + this.broadcasterService.unsubscribe(ComponentId); + } + + async selectSend(s: SendView) { + this.router.navigate(["/edit-send"], { queryParams: { sendId: s.id } }); + } + + async addSend() { + if (this.disableSend) { + return; + } + this.router.navigate(["/add-send"], { queryParams: { type: this.type } }); + } + + async removePassword(s: SendView): Promise { + if (this.disableSend) { + return; + } + super.removePassword(s); + } + + back() { + (window as any).routeDirection = "b"; + this.location.back(); + } + + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); + } } diff --git a/src/popup/services/debounceNavigationService.ts b/src/popup/services/debounceNavigationService.ts index b5f40ffb7d..574767b9b8 100644 --- a/src/popup/services/debounceNavigationService.ts +++ b/src/popup/services/debounceNavigationService.ts @@ -1,53 +1,50 @@ -import { - Injectable, - OnDestroy -} from '@angular/core'; -import { - CanActivate, - NavigationEnd, - NavigationStart, - Router, -} from '@angular/router'; +import { Injectable, OnDestroy } from "@angular/core"; +import { CanActivate, NavigationEnd, NavigationStart, Router } from "@angular/router"; -import { Subscription } from 'rxjs'; -import { - filter, - pairwise, -} from 'rxjs/operators'; +import { Subscription } from "rxjs"; +import { filter, pairwise } from "rxjs/operators"; @Injectable() export class DebounceNavigationService implements CanActivate, OnDestroy { - navigationStartSub: Subscription; - navigationSuccessSub: Subscription; + navigationStartSub: Subscription; + navigationSuccessSub: Subscription; - private lastNavigation: NavigationStart; - private thisNavigation: NavigationStart; - private lastNavigationSuccessId: number; + private lastNavigation: NavigationStart; + private thisNavigation: NavigationStart; + private lastNavigationSuccessId: number; - constructor(private router: Router) { - this.navigationStartSub = this.router.events - .pipe(filter(event => event instanceof NavigationStart), pairwise()) - .subscribe((events: [NavigationStart, NavigationStart]) => [this.lastNavigation, this.thisNavigation] = events); + constructor(private router: Router) { + this.navigationStartSub = this.router.events + .pipe( + filter((event) => event instanceof NavigationStart), + pairwise() + ) + .subscribe( + (events: [NavigationStart, NavigationStart]) => + ([this.lastNavigation, this.thisNavigation] = events) + ); - this.navigationSuccessSub = this.router.events - .pipe(filter(event => event instanceof NavigationEnd)) - .subscribe((event: NavigationEnd) => this.lastNavigationSuccessId = event.id); + this.navigationSuccessSub = this.router.events + .pipe(filter((event) => event instanceof NavigationEnd)) + .subscribe((event: NavigationEnd) => (this.lastNavigationSuccessId = event.id)); + } + + async canActivate() { + return !( + this.thisNavigation?.navigationTrigger === "hashchange" && + this.lastNavigation.navigationTrigger === "popstate" && + this.lastNavigationSuccessId === this.lastNavigation.id && + this.lastNavigation.url === this.thisNavigation?.url + ); + } + + ngOnDestroy() { + if (this.navigationStartSub != null) { + this.navigationStartSub.unsubscribe(); } - async canActivate() { - return !(this.thisNavigation?.navigationTrigger === 'hashchange' && - this.lastNavigation.navigationTrigger === 'popstate' && - this.lastNavigationSuccessId === this.lastNavigation.id && - this.lastNavigation.url === this.thisNavigation?.url); - } - - ngOnDestroy() { - if (this.navigationStartSub != null) { - this.navigationStartSub.unsubscribe(); - } - - if (this.navigationSuccessSub != null) { - this.navigationSuccessSub.unsubscribe(); - } + if (this.navigationSuccessSub != null) { + this.navigationSuccessSub.unsubscribe(); } + } } diff --git a/src/popup/services/launch-guard.service.ts b/src/popup/services/launch-guard.service.ts index 5574ac2db9..9ef631efbe 100644 --- a/src/popup/services/launch-guard.service.ts +++ b/src/popup/services/launch-guard.service.ts @@ -1,22 +1,19 @@ -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { Injectable } from '@angular/core'; -import { - CanActivate, - Router, -} from '@angular/router'; +import { Injectable } from "@angular/core"; +import { CanActivate, Router } from "@angular/router"; -import { UnauthGuardService } from 'jslib-angular/services/unauth-guard.service'; +import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service"; @Injectable() export class LaunchGuardService implements CanActivate { - constructor(private router: Router, private unauthGuardService: UnauthGuardService) { } + constructor(private router: Router, private unauthGuardService: UnauthGuardService) {} - async canActivate() { - if (BrowserApi.getBackgroundPage() == null) { - this.router.navigate(['private-mode']); - return false; - } - return await this.unauthGuardService.canActivate(); + async canActivate() { + if (BrowserApi.getBackgroundPage() == null) { + this.router.navigate(["private-mode"]); + return false; } + return await this.unauthGuardService.canActivate(); + } } diff --git a/src/popup/services/lock-guard.service.ts b/src/popup/services/lock-guard.service.ts index 03306d0548..0862c313c7 100644 --- a/src/popup/services/lock-guard.service.ts +++ b/src/popup/services/lock-guard.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; -import { LockGuardService as BaseLockGuardService } from 'jslib-angular/services/lock-guard.service'; +import { LockGuardService as BaseLockGuardService } from "jslib-angular/services/lock-guard.service"; @Injectable() export class LockGuardService extends BaseLockGuardService { - protected homepage = 'tabs/current'; + protected homepage = "tabs/current"; } diff --git a/src/popup/services/password-reprompt.service.ts b/src/popup/services/password-reprompt.service.ts index d59026e0aa..33b26bfeb8 100644 --- a/src/popup/services/password-reprompt.service.ts +++ b/src/popup/services/password-reprompt.service.ts @@ -1,11 +1,10 @@ +import { Injectable } from "@angular/core"; -import { Injectable } from '@angular/core'; +import { PasswordRepromptService as BasePasswordRepromptService } from "jslib-angular/services/passwordReprompt.service"; -import { PasswordRepromptService as BasePasswordRepromptService } from 'jslib-angular/services/passwordReprompt.service'; - -import { PasswordRepromptComponent } from '../components/password-reprompt.component'; +import { PasswordRepromptComponent } from "../components/password-reprompt.component"; @Injectable() export class PasswordRepromptService extends BasePasswordRepromptService { - component = PasswordRepromptComponent; + component = PasswordRepromptComponent; } diff --git a/src/popup/services/popup-search.service.ts b/src/popup/services/popup-search.service.ts index 5a427c596a..ef4e07dc49 100644 --- a/src/popup/services/popup-search.service.ts +++ b/src/popup/services/popup-search.service.ts @@ -1,24 +1,28 @@ -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; -import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; -import { SearchService } from 'jslib-common/services/search.service'; +import { ConsoleLogService } from "jslib-common/services/consoleLog.service"; +import { SearchService } from "jslib-common/services/search.service"; export class PopupSearchService extends SearchService { - constructor(private mainSearchService: SearchService, cipherService: CipherService, - consoleLogService: ConsoleLogService, i18nService: I18nService) { - super(cipherService, consoleLogService, i18nService); - } + constructor( + private mainSearchService: SearchService, + cipherService: CipherService, + consoleLogService: ConsoleLogService, + i18nService: I18nService + ) { + super(cipherService, consoleLogService, i18nService); + } - clearIndex() { - throw new Error('Not available.'); - } + clearIndex() { + throw new Error("Not available."); + } - indexCiphers(): Promise { - throw new Error('Not available.'); - } + indexCiphers(): Promise { + throw new Error("Not available."); + } - getIndexForSearch() { - return this.mainSearchService.getIndexForSearch(); - } + getIndexForSearch() { + return this.mainSearchService.getIndexForSearch(); + } } diff --git a/src/popup/services/popup-utils.service.ts b/src/popup/services/popup-utils.service.ts index 8bfdb36bc4..64c880ce9a 100644 --- a/src/popup/services/popup-utils.service.ts +++ b/src/popup/services/popup-utils.service.ts @@ -1,76 +1,81 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; @Injectable() export class PopupUtilsService { - constructor(private platformUtilsService: PlatformUtilsService) { } + constructor(private platformUtilsService: PlatformUtilsService) {} - inSidebar(win: Window): boolean { - return win.location.search !== '' && win.location.search.indexOf('uilocation=sidebar') > -1; + inSidebar(win: Window): boolean { + return win.location.search !== "" && win.location.search.indexOf("uilocation=sidebar") > -1; + } + + inTab(win: Window): boolean { + return win.location.search !== "" && win.location.search.indexOf("uilocation=tab") > -1; + } + + inPopout(win: Window): boolean { + return win.location.search !== "" && win.location.search.indexOf("uilocation=popout") > -1; + } + + inPopup(win: Window): boolean { + return ( + win.location.search === "" || + win.location.search.indexOf("uilocation=") === -1 || + win.location.search.indexOf("uilocation=popup") > -1 + ); + } + + getContentScrollY(win: Window, scrollingContainer: string = "content"): number { + const content = win.document.getElementsByTagName(scrollingContainer)[0]; + return content.scrollTop; + } + + setContentScrollY(win: Window, scrollY: number, scrollingContainer: string = "content"): void { + if (scrollY != null) { + const content = win.document.getElementsByTagName(scrollingContainer)[0]; + content.scrollTop = scrollY; + } + } + + popOut(win: Window, href: string = null): void { + if (href === null) { + href = win.location.href; } - inTab(win: Window): boolean { - return win.location.search !== '' && win.location.search.indexOf('uilocation=tab') > -1; - } - - inPopout(win: Window): boolean { - return win.location.search !== '' && win.location.search.indexOf('uilocation=popout') > -1; - } - - inPopup(win: Window): boolean { - return win.location.search === '' || win.location.search.indexOf('uilocation=') === -1 || - win.location.search.indexOf('uilocation=popup') > -1; - } - - getContentScrollY(win: Window, scrollingContainer: string = 'content'): number { - const content = win.document.getElementsByTagName(scrollingContainer)[0]; - return content.scrollTop; - } - - setContentScrollY(win: Window, scrollY: number, scrollingContainer: string = 'content'): void { - if (scrollY != null) { - const content = win.document.getElementsByTagName(scrollingContainer)[0]; - content.scrollTop = scrollY; - } - } - - popOut(win: Window, href: string = null): void { - - if (href === null) { - href = win.location.href; - } - - if ((typeof chrome !== 'undefined') && chrome.windows && chrome.windows.create) { - if (href.indexOf('?uilocation=') > -1) { - href = href.replace('uilocation=popup', 'uilocation=popout') - .replace('uilocation=tab', 'uilocation=popout') - .replace('uilocation=sidebar', 'uilocation=popout'); - } else { - const hrefParts = href.split('#'); - href = hrefParts[0] + '?uilocation=popout' + (hrefParts.length > 0 ? '#' + hrefParts[1] : ''); - } - - const bodyRect = document.querySelector('body').getBoundingClientRect(); - chrome.windows.create({ - url: href, - type: 'popup', - width: Math.round(bodyRect.width ? bodyRect.width + 60 : 375), - height: Math.round(bodyRect.height || 600), - }); - - if (this.inPopup(win)) { - BrowserApi.closePopup(win); - } - } else if ((typeof chrome !== 'undefined') && chrome.tabs && chrome.tabs.create) { - href = href.replace('uilocation=popup', 'uilocation=tab') - .replace('uilocation=popout', 'uilocation=tab') - .replace('uilocation=sidebar', 'uilocation=tab'); - chrome.tabs.create({ - url: href, - }); - } + if (typeof chrome !== "undefined" && chrome.windows && chrome.windows.create) { + if (href.indexOf("?uilocation=") > -1) { + href = href + .replace("uilocation=popup", "uilocation=popout") + .replace("uilocation=tab", "uilocation=popout") + .replace("uilocation=sidebar", "uilocation=popout"); + } else { + const hrefParts = href.split("#"); + href = + hrefParts[0] + "?uilocation=popout" + (hrefParts.length > 0 ? "#" + hrefParts[1] : ""); + } + + const bodyRect = document.querySelector("body").getBoundingClientRect(); + chrome.windows.create({ + url: href, + type: "popup", + width: Math.round(bodyRect.width ? bodyRect.width + 60 : 375), + height: Math.round(bodyRect.height || 600), + }); + + if (this.inPopup(win)) { + BrowserApi.closePopup(win); + } + } else if (typeof chrome !== "undefined" && chrome.tabs && chrome.tabs.create) { + href = href + .replace("uilocation=popup", "uilocation=tab") + .replace("uilocation=popout", "uilocation=tab") + .replace("uilocation=sidebar", "uilocation=tab"); + chrome.tabs.create({ + url: href, + }); } + } } diff --git a/src/popup/services/services.module.ts b/src/popup/services/services.module.ts index bccb4816fa..d3a710cda4 100644 --- a/src/popup/services/services.module.ts +++ b/src/popup/services/services.module.ts @@ -1,225 +1,276 @@ -import { - APP_INITIALIZER, - LOCALE_ID, - NgModule, -} from '@angular/core'; +import { APP_INITIALIZER, LOCALE_ID, NgModule } from "@angular/core"; -import { DebounceNavigationService } from './debounceNavigationService'; -import { LaunchGuardService } from './launch-guard.service'; -import { LockGuardService } from './lock-guard.service'; -import { PasswordRepromptService } from './password-reprompt.service'; -import { UnauthGuardService } from './unauth-guard.service'; +import { DebounceNavigationService } from "./debounceNavigationService"; +import { LaunchGuardService } from "./launch-guard.service"; +import { LockGuardService } from "./lock-guard.service"; +import { PasswordRepromptService } from "./password-reprompt.service"; +import { UnauthGuardService } from "./unauth-guard.service"; -import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module'; -import { LockGuardService as BaseLockGuardService } from 'jslib-angular/services/lock-guard.service'; -import { UnauthGuardService as BaseUnauthGuardService } from 'jslib-angular/services/unauth-guard.service'; +import { JslibServicesModule } from "jslib-angular/services/jslib-services.module"; +import { LockGuardService as BaseLockGuardService } from "jslib-angular/services/lock-guard.service"; +import { UnauthGuardService as BaseUnauthGuardService } from "jslib-angular/services/unauth-guard.service"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { AppIdService } from 'jslib-common/abstractions/appId.service'; -import { AuditService } from 'jslib-common/abstractions/audit.service'; -import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { EventService } from 'jslib-common/abstractions/event.service'; -import { ExportService } from 'jslib-common/abstractions/export.service'; -import { FileUploadService } from 'jslib-common/abstractions/fileUpload.service'; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; -import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; -import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; -import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service'; -import { SendService } from 'jslib-common/abstractions/send.service'; -import { SettingsService } from 'jslib-common/abstractions/settings.service'; -import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { TokenService } from 'jslib-common/abstractions/token.service'; -import { TotpService } from 'jslib-common/abstractions/totp.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { AppIdService } from "jslib-common/abstractions/appId.service"; +import { AuditService } from "jslib-common/abstractions/audit.service"; +import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { EventService } from "jslib-common/abstractions/event.service"; +import { ExportService } from "jslib-common/abstractions/export.service"; +import { FileUploadService } from "jslib-common/abstractions/fileUpload.service"; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; +import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { NotificationsService } from "jslib-common/abstractions/notifications.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SearchService as SearchServiceAbstraction } from "jslib-common/abstractions/search.service"; +import { SendService } from "jslib-common/abstractions/send.service"; +import { SettingsService } from "jslib-common/abstractions/settings.service"; +import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; +import { TokenService } from "jslib-common/abstractions/token.service"; +import { TotpService } from "jslib-common/abstractions/totp.service"; +import { UserService } from "jslib-common/abstractions/user.service"; +import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; -import { AutofillService } from '../../services/abstractions/autofill.service'; -import BrowserMessagingService from '../../services/browserMessaging.service'; +import { AutofillService } from "../../services/abstractions/autofill.service"; +import BrowserMessagingService from "../../services/browserMessaging.service"; -import { AuthService } from 'jslib-common/services/auth.service'; -import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; -import { SearchService } from 'jslib-common/services/search.service'; -import { StateService } from 'jslib-common/services/state.service'; +import { AuthService } from "jslib-common/services/auth.service"; +import { ConsoleLogService } from "jslib-common/services/consoleLog.service"; +import { ConstantsService } from "jslib-common/services/constants.service"; +import { SearchService } from "jslib-common/services/search.service"; +import { StateService } from "jslib-common/services/state.service"; -import { PopupSearchService } from './popup-search.service'; -import { PopupUtilsService } from './popup-utils.service'; +import { PopupSearchService } from "./popup-search.service"; +import { PopupUtilsService } from "./popup-utils.service"; -import { ThemeType } from 'jslib-common/enums/themeType'; +import { ThemeType } from "jslib-common/enums/themeType"; function getBgService(service: string) { - return (): T => { - const page = BrowserApi.getBackgroundPage(); - return page ? page.bitwardenMain[service] as T : null; - }; + return (): T => { + const page = BrowserApi.getBackgroundPage(); + return page ? (page.bitwardenMain[service] as T) : null; + }; } const isPrivateMode = BrowserApi.getBackgroundPage() == null; -export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, - storageService: StorageService, popupUtilsService: PopupUtilsService, stateService: StateServiceAbstraction, - logService: LogServiceAbstraction): Function { - return async () => { - if (!popupUtilsService.inPopup(window)) { - window.document.body.classList.add('body-full'); - } else if (window.screen.availHeight < 600) { - window.document.body.classList.add('body-xs'); - } else if (window.screen.availHeight <= 800) { - window.document.body.classList.add('body-sm'); +export function initFactory( + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + storageService: StorageService, + popupUtilsService: PopupUtilsService, + stateService: StateServiceAbstraction, + logService: LogServiceAbstraction +): Function { + return async () => { + if (!popupUtilsService.inPopup(window)) { + window.document.body.classList.add("body-full"); + } else if (window.screen.availHeight < 600) { + window.document.body.classList.add("body-xs"); + } else if (window.screen.availHeight <= 800) { + window.document.body.classList.add("body-sm"); + } + + if (!isPrivateMode) { + await stateService.save( + ConstantsService.disableFaviconKey, + await storageService.get(ConstantsService.disableFaviconKey) + ); + + await stateService.save( + ConstantsService.disableBadgeCounterKey, + await storageService.get(ConstantsService.disableBadgeCounterKey) + ); + + const htmlEl = window.document.documentElement; + const theme = await platformUtilsService.getEffectiveTheme(); + htmlEl.classList.add("theme_" + theme); + platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => { + const bwTheme = await storageService.get(ConstantsService.themeKey); + if (bwTheme == null || bwTheme === ThemeType.System) { + htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark); + htmlEl.classList.add("theme_" + sysTheme); } + }); + htmlEl.classList.add("locale_" + i18nService.translationLocale); - if (!isPrivateMode) { - await stateService.save(ConstantsService.disableFaviconKey, - await storageService.get(ConstantsService.disableFaviconKey)); - - await stateService.save(ConstantsService.disableBadgeCounterKey, - await storageService.get(ConstantsService.disableBadgeCounterKey)); - - const htmlEl = window.document.documentElement; - const theme = await platformUtilsService.getEffectiveTheme(); - htmlEl.classList.add('theme_' + theme); - platformUtilsService.onDefaultSystemThemeChange(async sysTheme => { - const bwTheme = await storageService.get(ConstantsService.themeKey); - if (bwTheme == null || bwTheme === ThemeType.System) { - htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark); - htmlEl.classList.add('theme_' + sysTheme); - } - }); - htmlEl.classList.add('locale_' + i18nService.translationLocale); - - // Workaround for slow performance on external monitors on Chrome + MacOS - // See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 - if (platformUtilsService.isChrome() && - navigator.platform.indexOf('Mac') > -1 && - popupUtilsService.inPopup(window) && - (window.screenLeft < 0 || - window.screenTop < 0 || - window.screenLeft > window.screen.width || - window.screenTop > window.screen.height)) { - htmlEl.classList.add('force_redraw'); - logService.info('Force redraw is on'); - } - } - }; + // Workaround for slow performance on external monitors on Chrome + MacOS + // See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 + if ( + platformUtilsService.isChrome() && + navigator.platform.indexOf("Mac") > -1 && + popupUtilsService.inPopup(window) && + (window.screenLeft < 0 || + window.screenTop < 0 || + window.screenLeft > window.screen.width || + window.screenTop > window.screen.height) + ) { + htmlEl.classList.add("force_redraw"); + logService.info("Force redraw is on"); + } + } + }; } @NgModule({ - imports: [ - JslibServicesModule, - ], - declarations: [], - providers: [ - { - provide: LOCALE_ID, - useFactory: () => isPrivateMode ? null : getBgService('i18nService')().translationLocale, - deps: [], - }, - { - provide: APP_INITIALIZER, - useFactory: initFactory, - deps: [ - PlatformUtilsService, - I18nService, - StorageService, - PopupUtilsService, - StateServiceAbstraction, - LogServiceAbstraction, - ], - multi: true, - }, - LaunchGuardService, - { provide: BaseLockGuardService, useClass: LockGuardService }, - { provide: BaseUnauthGuardService, useClass: UnauthGuardService }, - DebounceNavigationService, + imports: [JslibServicesModule], + declarations: [], + providers: [ + { + provide: LOCALE_ID, + useFactory: () => + isPrivateMode ? null : getBgService("i18nService")().translationLocale, + deps: [], + }, + { + provide: APP_INITIALIZER, + useFactory: initFactory, + deps: [ + PlatformUtilsService, + I18nService, + StorageService, PopupUtilsService, - { provide: MessagingService, useClass: BrowserMessagingService }, - { provide: AuthServiceAbstraction, useFactory: getBgService('authService'), deps: [] }, - { provide: StateServiceAbstraction, useClass: StateService }, - { - provide: SearchServiceAbstraction, - useFactory: (cipherService: CipherService, logService: ConsoleLogService, i18nService: I18nService) => { - return isPrivateMode ? null : new PopupSearchService(getBgService('searchService')(), - cipherService, logService, i18nService); - }, - deps: [ - CipherService, - LogServiceAbstraction, - I18nService, - ], - }, - { provide: AuditService, useFactory: getBgService('auditService'), deps: [] }, - { provide: FileUploadService, useFactory: getBgService('fileUploadService'), deps: [] }, - { provide: CipherService, useFactory: getBgService('cipherService'), deps: [] }, - { - provide: CryptoFunctionService, - useFactory: getBgService('cryptoFunctionService'), - deps: [], - }, - { provide: FolderService, useFactory: getBgService('folderService'), deps: [] }, - { provide: CollectionService, useFactory: getBgService('collectionService'), deps: [] }, - { provide: LogServiceAbstraction, useFactory: getBgService('logService'), deps: [] }, - { provide: EnvironmentService, useFactory: getBgService('environmentService'), deps: [] }, - { provide: TotpService, useFactory: getBgService('totpService'), deps: [] }, - { provide: TokenService, useFactory: getBgService('tokenService'), deps: [] }, - { provide: I18nService, useFactory: getBgService('i18nService'), deps: [] }, - { provide: CryptoService, useFactory: getBgService('cryptoService'), deps: [] }, - { provide: EventService, useFactory: getBgService('eventService'), deps: [] }, - { provide: PolicyService, useFactory: getBgService('policyService'), deps: [] }, - { - provide: PlatformUtilsService, - useFactory: getBgService('platformUtilsService'), - deps: [], - }, - { - provide: PasswordGenerationService, - useFactory: getBgService('passwordGenerationService'), - deps: [], - }, - { provide: ApiService, useFactory: getBgService('apiService'), deps: [] }, - { provide: SyncService, useFactory: getBgService('syncService'), deps: [] }, - { provide: UserService, useFactory: getBgService('userService'), deps: [] }, - { provide: SettingsService, useFactory: getBgService('settingsService'), deps: [] }, - { provide: StorageService, useFactory: getBgService('storageService'), deps: [] }, - { provide: AppIdService, useFactory: getBgService('appIdService'), deps: [] }, - { provide: AutofillService, useFactory: getBgService('autofillService'), deps: [] }, - { provide: ExportService, useFactory: getBgService('exportService'), deps: [] }, - { provide: SendService, useFactory: getBgService('sendService'), deps: [] }, - { provide: KeyConnectorService, useFactory: getBgService('keyConnectorService'), deps: [] }, - { - provide: UserVerificationService, - useFactory: getBgService('userVerificationService'), - deps: [], - }, - { - provide: VaultTimeoutService, - useFactory: getBgService('vaultTimeoutService'), - deps: [], - }, - { - provide: NotificationsService, - useFactory: getBgService('notificationsService'), - deps: [], - }, - { provide: LogServiceAbstraction, useFactory: getBgService('logService'), deps: [] }, - { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, - ], + StateServiceAbstraction, + LogServiceAbstraction, + ], + multi: true, + }, + LaunchGuardService, + { provide: BaseLockGuardService, useClass: LockGuardService }, + { provide: BaseUnauthGuardService, useClass: UnauthGuardService }, + DebounceNavigationService, + PopupUtilsService, + { provide: MessagingService, useClass: BrowserMessagingService }, + { + provide: AuthServiceAbstraction, + useFactory: getBgService("authService"), + deps: [], + }, + { provide: StateServiceAbstraction, useClass: StateService }, + { + provide: SearchServiceAbstraction, + useFactory: ( + cipherService: CipherService, + logService: ConsoleLogService, + i18nService: I18nService + ) => { + return isPrivateMode + ? null + : new PopupSearchService( + getBgService("searchService")(), + cipherService, + logService, + i18nService + ); + }, + deps: [CipherService, LogServiceAbstraction, I18nService], + }, + { provide: AuditService, useFactory: getBgService("auditService"), deps: [] }, + { + provide: FileUploadService, + useFactory: getBgService("fileUploadService"), + deps: [], + }, + { provide: CipherService, useFactory: getBgService("cipherService"), deps: [] }, + { + provide: CryptoFunctionService, + useFactory: getBgService("cryptoFunctionService"), + deps: [], + }, + { provide: FolderService, useFactory: getBgService("folderService"), deps: [] }, + { + provide: CollectionService, + useFactory: getBgService("collectionService"), + deps: [], + }, + { + provide: LogServiceAbstraction, + useFactory: getBgService("logService"), + deps: [], + }, + { + provide: EnvironmentService, + useFactory: getBgService("environmentService"), + deps: [], + }, + { provide: TotpService, useFactory: getBgService("totpService"), deps: [] }, + { provide: TokenService, useFactory: getBgService("tokenService"), deps: [] }, + { provide: I18nService, useFactory: getBgService("i18nService"), deps: [] }, + { provide: CryptoService, useFactory: getBgService("cryptoService"), deps: [] }, + { provide: EventService, useFactory: getBgService("eventService"), deps: [] }, + { provide: PolicyService, useFactory: getBgService("policyService"), deps: [] }, + { + provide: PlatformUtilsService, + useFactory: getBgService("platformUtilsService"), + deps: [], + }, + { + provide: PasswordGenerationService, + useFactory: getBgService("passwordGenerationService"), + deps: [], + }, + { provide: ApiService, useFactory: getBgService("apiService"), deps: [] }, + { provide: SyncService, useFactory: getBgService("syncService"), deps: [] }, + { provide: UserService, useFactory: getBgService("userService"), deps: [] }, + { + provide: SettingsService, + useFactory: getBgService("settingsService"), + deps: [], + }, + { + provide: StorageService, + useFactory: getBgService("storageService"), + deps: [], + }, + { provide: AppIdService, useFactory: getBgService("appIdService"), deps: [] }, + { + provide: AutofillService, + useFactory: getBgService("autofillService"), + deps: [], + }, + { provide: ExportService, useFactory: getBgService("exportService"), deps: [] }, + { provide: SendService, useFactory: getBgService("sendService"), deps: [] }, + { + provide: KeyConnectorService, + useFactory: getBgService("keyConnectorService"), + deps: [], + }, + { + provide: UserVerificationService, + useFactory: getBgService("userVerificationService"), + deps: [], + }, + { + provide: VaultTimeoutService, + useFactory: getBgService("vaultTimeoutService"), + deps: [], + }, + { + provide: NotificationsService, + useFactory: getBgService("notificationsService"), + deps: [], + }, + { + provide: LogServiceAbstraction, + useFactory: getBgService("logService"), + deps: [], + }, + { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, + ], }) -export class ServicesModule { -} +export class ServicesModule {} diff --git a/src/popup/services/unauth-guard.service.ts b/src/popup/services/unauth-guard.service.ts index e74857bb8c..a85ab6c88c 100644 --- a/src/popup/services/unauth-guard.service.ts +++ b/src/popup/services/unauth-guard.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; -import { UnauthGuardService as BaseUnauthGuardService } from 'jslib-angular/services/unauth-guard.service'; +import { UnauthGuardService as BaseUnauthGuardService } from "jslib-angular/services/unauth-guard.service"; @Injectable() export class UnauthGuardService extends BaseUnauthGuardService { - protected homepage = 'tabs/current'; + protected homepage = "tabs/current"; } diff --git a/src/popup/settings/excluded-domains.component.html b/src/popup/settings/excluded-domains.component.html index fd5b217f49..66d3193def 100644 --- a/src/popup/settings/excluded-domains.component.html +++ b/src/popup/settings/excluded-domains.component.html @@ -1,53 +1,85 @@
-
- -

- {{'excludedDomains' | i18n}} -

-
- -
-
- -
-
- -
- -
- - - - -
-
- -
-
-
- +
+ +

+ {{ "excludedDomains" | i18n }} +

+
+ +
+
+ +
+
+ +
+ +
+ + + +
- - - \ No newline at end of file +
+
+ +
+ +
+
+ diff --git a/src/popup/settings/excluded-domains.component.ts b/src/popup/settings/excluded-domains.component.ts index 8c4270e433..6f856842a9 100644 --- a/src/popup/settings/excluded-domains.component.ts +++ b/src/popup/settings/excluded-domains.component.ts @@ -1,115 +1,119 @@ -import { - Component, - NgZone, - OnDestroy, - OnInit, -} from '@angular/core'; +import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from '@angular/router'; +import { Router } from "@angular/router"; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { ConstantsService } from "jslib-common/services/constants.service"; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { Utils } from 'jslib-common/misc/utils'; +import { Utils } from "jslib-common/misc/utils"; interface ExcludedDomain { - uri: string; - showCurrentUris: boolean; + uri: string; + showCurrentUris: boolean; } -const BroadcasterSubscriptionId = 'excludedDomains'; +const BroadcasterSubscriptionId = "excludedDomains"; @Component({ - selector: 'app-excluded-domains', - templateUrl: 'excluded-domains.component.html', + selector: "app-excluded-domains", + templateUrl: "excluded-domains.component.html", }) export class ExcludedDomainsComponent implements OnInit, OnDestroy { - excludedDomains: ExcludedDomain[] = []; - currentUris: string[]; - loadCurrentUrisTimeout: number; + excludedDomains: ExcludedDomain[] = []; + currentUris: string[]; + loadCurrentUrisTimeout: number; - constructor(private storageService: StorageService, - private i18nService: I18nService, private router: Router, - private broadcasterService: BroadcasterService, private ngZone: NgZone, - private platformUtilsService: PlatformUtilsService) { + constructor( + private storageService: StorageService, + private i18nService: I18nService, + private router: Router, + private broadcasterService: BroadcasterService, + private ngZone: NgZone, + private platformUtilsService: PlatformUtilsService + ) {} + + async ngOnInit() { + const savedDomains = await this.storageService.get(ConstantsService.neverDomainsKey); + if (savedDomains) { + for (const uri of Object.keys(savedDomains)) { + this.excludedDomains.push({ uri: uri, showCurrentUris: false }); + } } - async ngOnInit() { - const savedDomains = await this.storageService.get(ConstantsService.neverDomainsKey); - if (savedDomains) { - for (const uri of Object.keys(savedDomains)) { - this.excludedDomains.push({ uri: uri, showCurrentUris: false }); + await this.loadCurrentUris(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "tabChanged": + case "windowChanged": + if (this.loadCurrentUrisTimeout != null) { + window.clearTimeout(this.loadCurrentUrisTimeout); } + this.loadCurrentUrisTimeout = window.setTimeout( + async () => await this.loadCurrentUris(), + 500 + ); + break; + default: + break; } + }); + }); + } - await this.loadCurrentUris(); + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'tabChanged': - case 'windowChanged': - if (this.loadCurrentUrisTimeout != null) { - window.clearTimeout(this.loadCurrentUrisTimeout); - } - this.loadCurrentUrisTimeout = window.setTimeout(async () => await this.loadCurrentUris(), 500); - break; - default: - break; - } - }); - }); - } + async addUri() { + this.excludedDomains.push({ uri: "", showCurrentUris: false }); + } - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } + async removeUri(i: number) { + this.excludedDomains.splice(i, 1); + } - async addUri() { - this.excludedDomains.push({ uri: '', showCurrentUris: false }); - } - - async removeUri(i: number) { - this.excludedDomains.splice(i, 1); - } - - async submit() { - const savedDomains: { [name: string]: null } = {}; - for (const domain of this.excludedDomains) { - if (domain.uri && domain.uri !== '') { - const validDomain = Utils.getHostname(domain.uri); - if (!validDomain) { - this.platformUtilsService.showToast('error', null, - this.i18nService.t('excludedDomainsInvalidDomain', domain.uri)); - return; - } - savedDomains[validDomain] = null; - } + async submit() { + const savedDomains: { [name: string]: null } = {}; + for (const domain of this.excludedDomains) { + if (domain.uri && domain.uri !== "") { + const validDomain = Utils.getHostname(domain.uri); + if (!validDomain) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("excludedDomainsInvalidDomain", domain.uri) + ); + return; } - await this.storageService.save(ConstantsService.neverDomainsKey, savedDomains); - this.router.navigate(['/tabs/settings']); + savedDomains[validDomain] = null; + } } + await this.storageService.save(ConstantsService.neverDomainsKey, savedDomains); + this.router.navigate(["/tabs/settings"]); + } - trackByFunction(index: number, item: any) { - return index; - } + trackByFunction(index: number, item: any) { + return index; + } - toggleUriInput(domain: ExcludedDomain) { - domain.showCurrentUris = !domain.showCurrentUris; - } + toggleUriInput(domain: ExcludedDomain) { + domain.showCurrentUris = !domain.showCurrentUris; + } - async loadCurrentUris() { - const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' }); - if (tabs) { - const uriSet = new Set(tabs.map(tab => Utils.getHostname(tab.url))); - uriSet.delete(null); - this.currentUris = Array.from(uriSet); - } + async loadCurrentUris() { + const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); + if (tabs) { + const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); + uriSet.delete(null); + this.currentUris = Array.from(uriSet); } + } } diff --git a/src/popup/settings/export.component.html b/src/popup/settings/export.component.html index 5e912107ef..d78082281a 100644 --- a/src/popup/settings/export.component.html +++ b/src/popup/settings/export.component.html @@ -1,37 +1,39 @@
-
- -

- {{'exportVault' | i18n}} -

-
- -
-
- - - {{'personalVaultExportPolicyInEffect' | i18n}} - +
+ +

+ {{ "exportVault" | i18n }} +

+
+ +
+
+ + + {{ "personalVaultExportPolicyInEffect" | i18n }} + -
-
-
- - -
- - -
- +
+
+
+ +
- + + +
+ +
+ diff --git a/src/popup/settings/export.component.ts b/src/popup/settings/export.component.ts index b60986a5b5..90ef6f9fff 100644 --- a/src/popup/settings/export.component.ts +++ b/src/popup/settings/export.component.ts @@ -1,33 +1,51 @@ -import { Component } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; +import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; +import { Router } from "@angular/router"; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { EventService } from 'jslib-common/abstractions/event.service'; -import { ExportService } from 'jslib-common/abstractions/export.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service'; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { EventService } from "jslib-common/abstractions/event.service"; +import { ExportService } from "jslib-common/abstractions/export.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; -import { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component'; +import { ExportComponent as BaseExportComponent } from "jslib-angular/components/export.component"; @Component({ - selector: 'app-export', - templateUrl: 'export.component.html', + selector: "app-export", + templateUrl: "export.component.html", }) export class ExportComponent extends BaseExportComponent { - constructor(cryptoService: CryptoService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, exportService: ExportService, - eventService: EventService, policyService: PolicyService, private router: Router, - logService: LogService, userVerificationService: UserVerificationService, fb: FormBuilder) { - super(cryptoService, i18nService, platformUtilsService, exportService, eventService, policyService, window, - logService, userVerificationService, fb); - } + constructor( + cryptoService: CryptoService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + exportService: ExportService, + eventService: EventService, + policyService: PolicyService, + private router: Router, + logService: LogService, + userVerificationService: UserVerificationService, + fb: FormBuilder + ) { + super( + cryptoService, + i18nService, + platformUtilsService, + exportService, + eventService, + policyService, + window, + logService, + userVerificationService, + fb + ); + } - protected saved() { - super.saved(); - this.router.navigate(['/tabs/settings']); - } + protected saved() { + super.saved(); + this.router.navigate(["/tabs/settings"]); + } } diff --git a/src/popup/settings/folder-add-edit.component.html b/src/popup/settings/folder-add-edit.component.html index 23d61026d4..86d61724c9 100644 --- a/src/popup/settings/folder-add-edit.component.html +++ b/src/popup/settings/folder-add-edit.component.html @@ -1,40 +1,53 @@
-
-
- {{'cancel' | i18n}} +
+ +

+ {{ title }} +

+
+ +
+
+ +
+
+
+ +
-

- {{title}} -

-
- -
-
- -
-
-
- - -
+
+
+
+
+ -
-
-
+ {{ "deleteFolder" | i18n }} +
+ +
+
+
diff --git a/src/popup/settings/folder-add-edit.component.ts b/src/popup/settings/folder-add-edit.component.ts index 41be9f21d5..91b79148f7 100644 --- a/src/popup/settings/folder-add-edit.component.ts +++ b/src/popup/settings/folder-add-edit.component.ts @@ -1,54 +1,54 @@ -import { Component } from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { - FolderAddEditComponent as BaseFolderAddEditComponent, -} from 'jslib-angular/components/folder-add-edit.component'; +import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component"; @Component({ - selector: 'app-folder-add-edit', - templateUrl: 'folder-add-edit.component.html', + selector: "app-folder-add-edit", + templateUrl: "folder-add-edit.component.html", }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { - constructor(folderService: FolderService, i18nService: I18nService, - platformUtilsService: PlatformUtilsService, private router: Router, - private route: ActivatedRoute, logService: LogService) { - super(folderService, i18nService, platformUtilsService, logService); + constructor( + folderService: FolderService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + private router: Router, + private route: ActivatedRoute, + logService: LogService + ) { + super(folderService, i18nService, platformUtilsService, logService); + } + + async ngOnInit() { + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (params.folderId) { + this.folderId = params.folderId; + } + await this.init(); + }); + } + + async submit(): Promise { + if (await super.submit()) { + this.router.navigate(["/folders"]); + return true; } - async ngOnInit() { - this.route.queryParams.pipe(first()).subscribe(async params => { - if (params.folderId) { - this.folderId = params.folderId; - } - await this.init(); - }); - } + return false; + } - async submit(): Promise { - if (await super.submit()) { - this.router.navigate(['/folders']); - return true; - } - - return false; - } - - async delete(): Promise { - const confirmed = await super.delete(); - if (confirmed) { - this.router.navigate(['/folders']); - } - return confirmed; + async delete(): Promise { + const confirmed = await super.delete(); + if (confirmed) { + this.router.navigate(["/folders"]); } + return confirmed; + } } diff --git a/src/popup/settings/folders.component.html b/src/popup/settings/folders.component.html index a05a22b60d..787d2fa6bd 100644 --- a/src/popup/settings/folders.component.html +++ b/src/popup/settings/folders.component.html @@ -1,27 +1,39 @@
- -

- {{'folders' | i18n}} -

-
- -
+ +

+ {{ "folders" | i18n }} +

+
+ +
-
-
- -
-
-
-

{{'noFolders' | i18n}}

+
+
+
+
+
+

{{ "noFolders" | i18n }}

+
diff --git a/src/popup/settings/folders.component.ts b/src/popup/settings/folders.component.ts index 29bc3e7322..596a8438b4 100644 --- a/src/popup/settings/folders.component.ts +++ b/src/popup/settings/folders.component.ts @@ -1,35 +1,32 @@ -import { - Component, - OnInit, -} from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; -import { FolderView } from 'jslib-common/models/view/folderView'; +import { FolderView } from "jslib-common/models/view/folderView"; -import { FolderService } from 'jslib-common/abstractions/folder.service'; +import { FolderService } from "jslib-common/abstractions/folder.service"; @Component({ - selector: 'app-folders', - templateUrl: 'folders.component.html', + selector: "app-folders", + templateUrl: "folders.component.html", }) export class FoldersComponent implements OnInit { - folders: FolderView[]; + folders: FolderView[]; - constructor(private folderService: FolderService, private router: Router) { } + constructor(private folderService: FolderService, private router: Router) {} - async ngOnInit() { - this.folders = await this.folderService.getAllDecrypted(); - // Remove "No Folder" - if (this.folders.length > 0) { - this.folders = this.folders.slice(0, this.folders.length - 1); - } + async ngOnInit() { + this.folders = await this.folderService.getAllDecrypted(); + // Remove "No Folder" + if (this.folders.length > 0) { + this.folders = this.folders.slice(0, this.folders.length - 1); } + } - folderSelected(folder: FolderView) { - this.router.navigate(['/edit-folder'], { queryParams: { folderId: folder.id } }); - } + folderSelected(folder: FolderView) { + this.router.navigate(["/edit-folder"], { queryParams: { folderId: folder.id } }); + } - addFolder() { - this.router.navigate(['/add-folder']); - } + addFolder() { + this.router.navigate(["/add-folder"]); + } } diff --git a/src/popup/settings/options.component.html b/src/popup/settings/options.component.html index 7c1e5db852..fc139e1fca 100644 --- a/src/popup/settings/options.component.html +++ b/src/popup/settings/options.component.html @@ -1,183 +1,258 @@
- -

- {{'options' | i18n}} -

-
+ +

+ {{ "options" | i18n }} +

+
+
+

+ +

+
+
-

- -

+
+
+ + +
+
+
- -
-
-
- - -
-
- +
+
+
+ +
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
- -
-

- -

+
+
- -
-
-
- - -
-
- +
+
+
+ +
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
- -
-

- -

+
+
- -
-
-
- - -
-
- +
+
+
+ +
-
-
-
- - -
-
- +
+ +
+
+
+
+ +
- +
+ +
+
+
+
+ + +
+
+ +
+ +
+

+ +

+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+

+ +

+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
diff --git a/src/popup/settings/options.component.ts b/src/popup/settings/options.component.ts index 8ff25e0c27..3a6e3924f5 100644 --- a/src/popup/settings/options.component.ts +++ b/src/popup/settings/options.component.ts @@ -1,170 +1,211 @@ -import { - Component, - OnInit, -} from '@angular/core'; +import { Component, OnInit } from "@angular/core"; -import { ThemeType } from 'jslib-common/enums/themeType'; -import { UriMatchType } from 'jslib-common/enums/uriMatchType'; +import { ThemeType } from "jslib-common/enums/themeType"; +import { UriMatchType } from "jslib-common/enums/uriMatchType"; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { TotpService } from 'jslib-common/abstractions/totp.service'; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { TotpService } from "jslib-common/abstractions/totp.service"; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { ConstantsService } from "jslib-common/services/constants.service"; @Component({ - selector: 'app-options', - templateUrl: 'options.component.html', + selector: "app-options", + templateUrl: "options.component.html", }) export class OptionsComponent implements OnInit { - disableFavicon = false; - disableBadgeCounter = false; - enableAutoFillOnPageLoad = false; - autoFillOnPageLoadDefault = false; - autoFillOnPageLoadOptions: any[]; - disableAutoTotpCopy = false; - disableContextMenuItem = false; - disableAddLoginNotification = false; - disableChangedPasswordNotification = false; - dontShowCards = false; - dontShowIdentities = false; - showClearClipboard = true; - theme: string; - themeOptions: any[]; - defaultUriMatch = UriMatchType.Domain; - uriMatchOptions: any[]; - clearClipboard: number; - clearClipboardOptions: any[]; - showGeneral: boolean = true; - showAutofill: boolean = true; - showDisplay: boolean = true; + disableFavicon = false; + disableBadgeCounter = false; + enableAutoFillOnPageLoad = false; + autoFillOnPageLoadDefault = false; + autoFillOnPageLoadOptions: any[]; + disableAutoTotpCopy = false; + disableContextMenuItem = false; + disableAddLoginNotification = false; + disableChangedPasswordNotification = false; + dontShowCards = false; + dontShowIdentities = false; + showClearClipboard = true; + theme: string; + themeOptions: any[]; + defaultUriMatch = UriMatchType.Domain; + uriMatchOptions: any[]; + clearClipboard: number; + clearClipboardOptions: any[]; + showGeneral: boolean = true; + showAutofill: boolean = true; + showDisplay: boolean = true; - constructor(private messagingService: MessagingService, private storageService: StorageService, - private stateService: StateService, private totpService: TotpService, i18nService: I18nService) { - this.themeOptions = [ - { name: i18nService.t('default'), value: null }, - { name: i18nService.t('light'), value: ThemeType.Light }, - { name: i18nService.t('dark'), value: ThemeType.Dark }, - { name: 'Nord', value: ThemeType.Nord }, - { name: i18nService.t('solarizedDark'), value: ThemeType.SolarizedDark }, - ]; - this.uriMatchOptions = [ - { name: i18nService.t('baseDomain'), value: UriMatchType.Domain }, - { name: i18nService.t('host'), value: UriMatchType.Host }, - { name: i18nService.t('startsWith'), value: UriMatchType.StartsWith }, - { name: i18nService.t('regEx'), value: UriMatchType.RegularExpression }, - { name: i18nService.t('exact'), value: UriMatchType.Exact }, - { name: i18nService.t('never'), value: UriMatchType.Never }, - ]; - this.clearClipboardOptions = [ - { name: i18nService.t('never'), value: null }, - { name: i18nService.t('tenSeconds'), value: 10 }, - { name: i18nService.t('twentySeconds'), value: 20 }, - { name: i18nService.t('thirtySeconds'), value: 30 }, - { name: i18nService.t('oneMinute'), value: 60 }, - { name: i18nService.t('twoMinutes'), value: 120 }, - { name: i18nService.t('fiveMinutes'), value: 300 }, - ]; - this.autoFillOnPageLoadOptions = [ - { name: i18nService.t('autoFillOnPageLoadYes'), value: true }, - { name: i18nService.t('autoFillOnPageLoadNo'), value: false }, - ]; - } + constructor( + private messagingService: MessagingService, + private storageService: StorageService, + private stateService: StateService, + private totpService: TotpService, + i18nService: I18nService + ) { + this.themeOptions = [ + { name: i18nService.t("default"), value: null }, + { name: i18nService.t("light"), value: ThemeType.Light }, + { name: i18nService.t("dark"), value: ThemeType.Dark }, + { name: "Nord", value: ThemeType.Nord }, + { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, + ]; + this.uriMatchOptions = [ + { name: i18nService.t("baseDomain"), value: UriMatchType.Domain }, + { name: i18nService.t("host"), value: UriMatchType.Host }, + { name: i18nService.t("startsWith"), value: UriMatchType.StartsWith }, + { name: i18nService.t("regEx"), value: UriMatchType.RegularExpression }, + { name: i18nService.t("exact"), value: UriMatchType.Exact }, + { name: i18nService.t("never"), value: UriMatchType.Never }, + ]; + this.clearClipboardOptions = [ + { name: i18nService.t("never"), value: null }, + { name: i18nService.t("tenSeconds"), value: 10 }, + { name: i18nService.t("twentySeconds"), value: 20 }, + { name: i18nService.t("thirtySeconds"), value: 30 }, + { name: i18nService.t("oneMinute"), value: 60 }, + { name: i18nService.t("twoMinutes"), value: 120 }, + { name: i18nService.t("fiveMinutes"), value: 300 }, + ]; + this.autoFillOnPageLoadOptions = [ + { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, + { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, + ]; + } - async ngOnInit() { - this.enableAutoFillOnPageLoad = await this.storageService.get( - ConstantsService.enableAutoFillOnPageLoadKey); + async ngOnInit() { + this.enableAutoFillOnPageLoad = await this.storageService.get( + ConstantsService.enableAutoFillOnPageLoadKey + ); - this.autoFillOnPageLoadDefault = await this.storageService.get( - ConstantsService.autoFillOnPageLoadDefaultKey) ?? true; + this.autoFillOnPageLoadDefault = + (await this.storageService.get(ConstantsService.autoFillOnPageLoadDefaultKey)) ?? + true; - this.disableAddLoginNotification = await this.storageService.get( - ConstantsService.disableAddLoginNotificationKey); + this.disableAddLoginNotification = await this.storageService.get( + ConstantsService.disableAddLoginNotificationKey + ); - this.disableChangedPasswordNotification = await this.storageService.get( - ConstantsService.disableChangedPasswordNotificationKey); + this.disableChangedPasswordNotification = await this.storageService.get( + ConstantsService.disableChangedPasswordNotificationKey + ); - this.disableContextMenuItem = await this.storageService.get( - ConstantsService.disableContextMenuItemKey); + this.disableContextMenuItem = await this.storageService.get( + ConstantsService.disableContextMenuItemKey + ); - this.dontShowCards = await this.storageService.get(ConstantsService.dontShowCardsCurrentTab); - this.dontShowIdentities = await this.storageService.get(ConstantsService.dontShowIdentitiesCurrentTab); + this.dontShowCards = await this.storageService.get( + ConstantsService.dontShowCardsCurrentTab + ); + this.dontShowIdentities = await this.storageService.get( + ConstantsService.dontShowIdentitiesCurrentTab + ); - this.disableAutoTotpCopy = !(await this.totpService.isAutoCopyEnabled()); + this.disableAutoTotpCopy = !(await this.totpService.isAutoCopyEnabled()); - this.disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); + this.disableFavicon = await this.storageService.get( + ConstantsService.disableFaviconKey + ); - this.disableBadgeCounter = await this.storageService.get(ConstantsService.disableBadgeCounterKey); + this.disableBadgeCounter = await this.storageService.get( + ConstantsService.disableBadgeCounterKey + ); - this.theme = await this.storageService.get(ConstantsService.themeKey); + this.theme = await this.storageService.get(ConstantsService.themeKey); - const defaultUriMatch = await this.storageService.get(ConstantsService.defaultUriMatch); - this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch; + const defaultUriMatch = await this.storageService.get( + ConstantsService.defaultUriMatch + ); + this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch; - this.clearClipboard = await this.storageService.get(ConstantsService.clearClipboardKey); - } + this.clearClipboard = await this.storageService.get(ConstantsService.clearClipboardKey); + } - async updateAddLoginNotification() { - await this.storageService.save(ConstantsService.disableAddLoginNotificationKey, - this.disableAddLoginNotification); - } + async updateAddLoginNotification() { + await this.storageService.save( + ConstantsService.disableAddLoginNotificationKey, + this.disableAddLoginNotification + ); + } - async updateChangedPasswordNotification() { - await this.storageService.save(ConstantsService.disableChangedPasswordNotificationKey, - this.disableChangedPasswordNotification); - } + async updateChangedPasswordNotification() { + await this.storageService.save( + ConstantsService.disableChangedPasswordNotificationKey, + this.disableChangedPasswordNotification + ); + } - async updateDisableContextMenuItem() { - await this.storageService.save(ConstantsService.disableContextMenuItemKey, - this.disableContextMenuItem); - this.messagingService.send('bgUpdateContextMenu'); - } + async updateDisableContextMenuItem() { + await this.storageService.save( + ConstantsService.disableContextMenuItemKey, + this.disableContextMenuItem + ); + this.messagingService.send("bgUpdateContextMenu"); + } - async updateAutoTotpCopy() { - await this.storageService.save(ConstantsService.disableAutoTotpCopyKey, this.disableAutoTotpCopy); - } + async updateAutoTotpCopy() { + await this.storageService.save( + ConstantsService.disableAutoTotpCopyKey, + this.disableAutoTotpCopy + ); + } - async updateAutoFillOnPageLoad() { - await this.storageService.save(ConstantsService.enableAutoFillOnPageLoadKey, this.enableAutoFillOnPageLoad); - } + async updateAutoFillOnPageLoad() { + await this.storageService.save( + ConstantsService.enableAutoFillOnPageLoadKey, + this.enableAutoFillOnPageLoad + ); + } - async updateAutoFillOnPageLoadDefault() { - await this.storageService.save(ConstantsService.autoFillOnPageLoadDefaultKey, this.autoFillOnPageLoadDefault); - } + async updateAutoFillOnPageLoadDefault() { + await this.storageService.save( + ConstantsService.autoFillOnPageLoadDefaultKey, + this.autoFillOnPageLoadDefault + ); + } - async updateDisableFavicon() { - await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicon); - await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicon); - } + async updateDisableFavicon() { + await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicon); + await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicon); + } - async updateDisableBadgeCounter() { - await this.storageService.save(ConstantsService.disableBadgeCounterKey, this.disableBadgeCounter); - await this.stateService.save(ConstantsService.disableBadgeCounterKey, this.disableBadgeCounter); - this.messagingService.send('bgUpdateContextMenu'); - } + async updateDisableBadgeCounter() { + await this.storageService.save( + ConstantsService.disableBadgeCounterKey, + this.disableBadgeCounter + ); + await this.stateService.save(ConstantsService.disableBadgeCounterKey, this.disableBadgeCounter); + this.messagingService.send("bgUpdateContextMenu"); + } - async updateShowCards() { - await this.storageService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards); - await this.stateService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards); - } + async updateShowCards() { + await this.storageService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards); + await this.stateService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards); + } - async updateShowIdentities() { - await this.storageService.save(ConstantsService.dontShowIdentitiesCurrentTab, this.dontShowIdentities); - await this.stateService.save(ConstantsService.dontShowIdentitiesCurrentTab, this.dontShowIdentities); - } + async updateShowIdentities() { + await this.storageService.save( + ConstantsService.dontShowIdentitiesCurrentTab, + this.dontShowIdentities + ); + await this.stateService.save( + ConstantsService.dontShowIdentitiesCurrentTab, + this.dontShowIdentities + ); + } - async saveTheme() { - await this.storageService.save(ConstantsService.themeKey, this.theme); - window.setTimeout(() => window.location.reload(), 200); - } + async saveTheme() { + await this.storageService.save(ConstantsService.themeKey, this.theme); + window.setTimeout(() => window.location.reload(), 200); + } - async saveDefaultUriMatch() { - await this.storageService.save(ConstantsService.defaultUriMatch, this.defaultUriMatch); - } + async saveDefaultUriMatch() { + await this.storageService.save(ConstantsService.defaultUriMatch, this.defaultUriMatch); + } - async saveClearClipboard() { - await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard); - } + async saveClearClipboard() { + await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard); + } } diff --git a/src/popup/settings/premium.component.html b/src/popup/settings/premium.component.html index 3f82c6abd8..111aa439c4 100644 --- a/src/popup/settings/premium.component.html +++ b/src/popup/settings/premium.component.html @@ -1,62 +1,73 @@
- -

- {{'premiumMembership' | i18n}} -

-
+ +

+ {{ "premiumMembership" | i18n }} +

+
-
- -

{{'premiumNotCurrentMember' | i18n}}

-

{{'premiumSignUpAndGet' | i18n}}

-
    -
  • - - {{'ppremiumSignUpStorage' | i18n}} -
  • -
  • - - {{'ppremiumSignUpTwoStep' | i18n}} -
  • -
  • - - {{'ppremiumSignUpReports' | i18n}} -
  • -
  • - - {{'ppremiumSignUpTotp' | i18n}} -
  • -
  • - - {{'ppremiumSignUpSupport' | i18n}} -
  • -
  • - - {{'ppremiumSignUpFuture' | i18n}} -
  • -
-

{{priceString}}

- - -
- -

{{'premiumCurrentMember' | i18n}}

-

{{'premiumCurrentMemberThanks' | i18n}}

- -
-
+
+ +

{{ "premiumNotCurrentMember" | i18n }}

+

{{ "premiumSignUpAndGet" | i18n }}

+
    +
  • + + {{ "ppremiumSignUpStorage" | i18n }} +
  • +
  • + + {{ "ppremiumSignUpTwoStep" | i18n }} +
  • +
  • + + {{ "ppremiumSignUpReports" | i18n }} +
  • +
  • + + {{ "ppremiumSignUpTotp" | i18n }} +
  • +
  • + + {{ "ppremiumSignUpSupport" | i18n }} +
  • +
  • + + {{ "ppremiumSignUpFuture" | i18n }} +
  • +
+

{{ priceString }}

+ + +
+ +

{{ "premiumCurrentMember" | i18n }}

+

{{ "premiumCurrentMemberThanks" | i18n }}

+ +
+
diff --git a/src/popup/settings/premium.component.ts b/src/popup/settings/premium.component.ts index 3388798a16..db032c959c 100644 --- a/src/popup/settings/premium.component.ts +++ b/src/popup/settings/premium.component.ts @@ -1,31 +1,36 @@ -import { CurrencyPipe } from '@angular/common'; -import { Component } from '@angular/core'; +import { CurrencyPipe } from "@angular/common"; +import { Component } from "@angular/core"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component'; +import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component"; @Component({ - selector: 'app-premium', - templateUrl: 'premium.component.html', + selector: "app-premium", + templateUrl: "premium.component.html", }) export class PremiumComponent extends BasePremiumComponent { - priceString: string; + priceString: string; - constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, - apiService: ApiService, userService: UserService, - private currencyPipe: CurrencyPipe, logService: LogService) { - super(i18nService, platformUtilsService, apiService, userService, logService); + constructor( + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + apiService: ApiService, + userService: UserService, + private currencyPipe: CurrencyPipe, + logService: LogService + ) { + super(i18nService, platformUtilsService, apiService, userService, logService); - // Support old price string. Can be removed in future once all translations are properly updated. - const thePrice = this.currencyPipe.transform(this.price, '$'); - this.priceString = i18nService.t('premiumPrice', thePrice); - if (this.priceString.indexOf('%price%') > -1) { - this.priceString = this.priceString.replace('%price%', thePrice); - } + // Support old price string. Can be removed in future once all translations are properly updated. + const thePrice = this.currencyPipe.transform(this.price, "$"); + this.priceString = i18nService.t("premiumPrice", thePrice); + if (this.priceString.indexOf("%price%") > -1) { + this.priceString = this.priceString.replace("%price%", thePrice); } + } } diff --git a/src/popup/settings/settings.component.html b/src/popup/settings/settings.component.html index 1ca91e9ef7..d18a51019c 100644 --- a/src/popup/settings/settings.component.html +++ b/src/popup/settings/settings.component.html @@ -1,140 +1,225 @@
-
- -
-

- {{'settings' | i18n}} -

-
+
+ +
+

+ {{ "settings" | i18n }} +

+
-
-

{{'manage' | i18n}}

- +
+

{{ "manage" | i18n }}

+ -
-

{{'security' | i18n}}

-
- -
- - -
-
- - -
-
- - -
-
- - -
- - -
+
+
+

{{ "security" | i18n }}

+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
-
-

{{'account' | i18n}}

-
- -
-
- {{'premiumMembership' | i18n}} -
- -
- - - +
+
+

{{ "account" | i18n }}

+
+ +
+
+ +
+ {{ "premiumMembership" | i18n }}
+ +
+ + +
-
-

{{'tools' | i18n}}

-
- - - -
+
+
+

{{ "tools" | i18n }}

+
+ + +
-
-

{{'other' | i18n}}

-
- -
{{'options' | i18n}}
- -
- - - - -
- +
+
+

{{ "other" | i18n }}

+
+ +
{{ "options" | i18n }}
+ +
+ + + +
+ +
diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index aa0c6dcc83..6a6108db09 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -1,368 +1,416 @@ -import { - Component, - ElementRef, - OnInit, - ViewChild, -} from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { Router } from '@angular/router'; -import Swal from 'sweetalert2/src/sweetalert2.js'; +import { Component, ElementRef, OnInit, ViewChild } from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { Router } from "@angular/router"; +import Swal from "sweetalert2/src/sweetalert2.js"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { DeviceType } from 'jslib-common/enums/deviceType'; +import { DeviceType } from "jslib-common/enums/deviceType"; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { ConstantsService } from "jslib-common/services/constants.service"; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -import { PopupUtilsService } from '../services/popup-utils.service'; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { EnvironmentService } from "jslib-common/abstractions/environment.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { UserService } from "jslib-common/abstractions/user.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; +import { PopupUtilsService } from "../services/popup-utils.service"; -import { ModalService } from 'jslib-angular/services/modal.service'; +import { ModalService } from "jslib-angular/services/modal.service"; -import { SetPinComponent } from '../components/set-pin.component'; +import { SetPinComponent } from "../components/set-pin.component"; const RateUrls = { - [DeviceType.ChromeExtension]: - 'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews', - [DeviceType.FirefoxExtension]: - 'https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews', - [DeviceType.OperaExtension]: - 'https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container', - [DeviceType.EdgeExtension]: - 'https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh', - [DeviceType.VivaldiExtension]: - 'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews', - [DeviceType.SafariExtension]: - 'https://apps.apple.com/app/bitwarden/id1352778147', + [DeviceType.ChromeExtension]: + "https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", + [DeviceType.FirefoxExtension]: + "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews", + [DeviceType.OperaExtension]: + "https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container", + [DeviceType.EdgeExtension]: + "https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh", + [DeviceType.VivaldiExtension]: + "https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", + [DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147", }; @Component({ - selector: 'app-settings', - templateUrl: 'settings.component.html', + selector: "app-settings", + templateUrl: "settings.component.html", }) export class SettingsComponent implements OnInit { - @ViewChild('vaultTimeoutActionSelect', { read: ElementRef, static: true }) vaultTimeoutActionSelectRef: ElementRef; - vaultTimeouts: any[]; - vaultTimeoutActions: any[]; - vaultTimeoutAction: string; - pin: boolean = null; - supportsBiometric: boolean; - biometric: boolean = false; - disableAutoBiometricsPrompt = true; - previousVaultTimeout: number = null; - showChangeMasterPass = true; + @ViewChild("vaultTimeoutActionSelect", { read: ElementRef, static: true }) + vaultTimeoutActionSelectRef: ElementRef; + vaultTimeouts: any[]; + vaultTimeoutActions: any[]; + vaultTimeoutAction: string; + pin: boolean = null; + supportsBiometric: boolean; + biometric: boolean = false; + disableAutoBiometricsPrompt = true; + previousVaultTimeout: number = null; + showChangeMasterPass = true; - vaultTimeout: FormControl = new FormControl(null); + vaultTimeout: FormControl = new FormControl(null); - constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService, - public messagingService: MessagingService, private router: Router, - private environmentService: EnvironmentService, private cryptoService: CryptoService, - private userService: UserService, private popupUtilsService: PopupUtilsService, - private modalService: ModalService, - private keyConnectorService: KeyConnectorService) { + constructor( + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private vaultTimeoutService: VaultTimeoutService, + private storageService: StorageService, + public messagingService: MessagingService, + private router: Router, + private environmentService: EnvironmentService, + private cryptoService: CryptoService, + private userService: UserService, + private popupUtilsService: PopupUtilsService, + private modalService: ModalService, + private keyConnectorService: KeyConnectorService + ) {} + + async ngOnInit() { + const showOnLocked = + !this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari(); + + this.vaultTimeouts = [ + { name: this.i18nService.t("immediately"), value: 0 }, + { name: this.i18nService.t("oneMinute"), value: 1 }, + { name: this.i18nService.t("fiveMinutes"), value: 5 }, + { name: this.i18nService.t("fifteenMinutes"), value: 15 }, + { name: this.i18nService.t("thirtyMinutes"), value: 30 }, + { name: this.i18nService.t("oneHour"), value: 60 }, + { name: this.i18nService.t("fourHours"), value: 240 }, + // { name: i18nService.t('onIdle'), value: -4 }, + // { name: i18nService.t('onSleep'), value: -3 }, + ]; + + if (showOnLocked) { + this.vaultTimeouts.push({ name: this.i18nService.t("onLocked"), value: -2 }); } - async ngOnInit() { - const showOnLocked = !this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari(); + this.vaultTimeouts.push({ name: this.i18nService.t("onRestart"), value: -1 }); + this.vaultTimeouts.push({ name: this.i18nService.t("never"), value: null }); - this.vaultTimeouts = [ - { name: this.i18nService.t('immediately'), value: 0 }, - { name: this.i18nService.t('oneMinute'), value: 1 }, - { name: this.i18nService.t('fiveMinutes'), value: 5 }, - { name: this.i18nService.t('fifteenMinutes'), value: 15 }, - { name: this.i18nService.t('thirtyMinutes'), value: 30 }, - { name: this.i18nService.t('oneHour'), value: 60 }, - { name: this.i18nService.t('fourHours'), value: 240 }, - // { name: i18nService.t('onIdle'), value: -4 }, - // { name: i18nService.t('onSleep'), value: -3 }, - ]; + this.vaultTimeoutActions = [ + { name: this.i18nService.t("lock"), value: "lock" }, + { name: this.i18nService.t("logOut"), value: "logOut" }, + ]; - if (showOnLocked) { - this.vaultTimeouts.push({ name: this.i18nService.t('onLocked'), value: -2 }); - } + let timeout = await this.vaultTimeoutService.getVaultTimeout(); + if (timeout != null) { + if (timeout === -2 && !showOnLocked) { + timeout = -1; + } + this.vaultTimeout.setValue(timeout); + } + this.previousVaultTimeout = this.vaultTimeout.value; + this.vaultTimeout.valueChanges.subscribe((value) => { + this.saveVaultTimeout(value); + }); - this.vaultTimeouts.push({ name: this.i18nService.t('onRestart'), value: -1 }); - this.vaultTimeouts.push({ name: this.i18nService.t('never'), value: null }); + const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); + this.vaultTimeoutAction = action == null ? "lock" : action; - this.vaultTimeoutActions = [ - { name: this.i18nService.t('lock'), value: 'lock' }, - { name: this.i18nService.t('logOut'), value: 'logOut' }, - ]; + const pinSet = await this.vaultTimeoutService.isPinLockSet(); + this.pin = pinSet[0] || pinSet[1]; - let timeout = await this.vaultTimeoutService.getVaultTimeout(); - if (timeout != null) { - if (timeout === -2 && !showOnLocked) { - timeout = -1; - } - this.vaultTimeout.setValue(timeout); - } - this.previousVaultTimeout = this.vaultTimeout.value; - this.vaultTimeout.valueChanges.subscribe(value => { - this.saveVaultTimeout(value); + this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); + this.biometric = await this.vaultTimeoutService.isBiometricLockSet(); + this.disableAutoBiometricsPrompt = + (await this.storageService.get(ConstantsService.disableAutoBiometricsPromptKey)) ?? + true; + this.showChangeMasterPass = !(await this.keyConnectorService.getUsesKeyConnector()); + } + + async saveVaultTimeout(newValue: number) { + if (newValue == null) { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("neverLockWarning"), + null, + this.i18nService.t("yes"), + this.i18nService.t("cancel"), + "warning" + ); + if (!confirmed) { + this.vaultTimeout.setValue(this.previousVaultTimeout); + return; + } + } + + if (!this.vaultTimeout.valid) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("vaultTimeoutToLarge")); + return; + } + + this.previousVaultTimeout = this.vaultTimeout.value; + + await this.vaultTimeoutService.setVaultTimeoutOptions( + this.vaultTimeout.value, + this.vaultTimeoutAction + ); + if (this.previousVaultTimeout == null) { + this.messagingService.send("bgReseedStorage"); + } + } + + async saveVaultTimeoutAction(newValue: string) { + if (newValue === "logOut") { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("vaultTimeoutLogOutConfirmation"), + this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"), + this.i18nService.t("yes"), + this.i18nService.t("cancel"), + "warning" + ); + if (!confirmed) { + this.vaultTimeoutActions.forEach((option: any, i) => { + if (option.value === this.vaultTimeoutAction) { + this.vaultTimeoutActionSelectRef.nativeElement.value = + i + ": " + this.vaultTimeoutAction; + } }); - - const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); - this.vaultTimeoutAction = action == null ? 'lock' : action; - - const pinSet = await this.vaultTimeoutService.isPinLockSet(); - this.pin = pinSet[0] || pinSet[1]; - - this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); - this.biometric = await this.vaultTimeoutService.isBiometricLockSet(); - this.disableAutoBiometricsPrompt = await this.storageService.get( - ConstantsService.disableAutoBiometricsPromptKey) ?? true; - this.showChangeMasterPass = !await this.keyConnectorService.getUsesKeyConnector(); + return; + } } - async saveVaultTimeout(newValue: number) { - if (newValue == null) { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('neverLockWarning'), null, - this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning'); - if (!confirmed) { - this.vaultTimeout.setValue(this.previousVaultTimeout); - return; + if (!this.vaultTimeout.valid) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("vaultTimeoutToLarge")); + return; + } + + this.vaultTimeoutAction = newValue; + await this.vaultTimeoutService.setVaultTimeoutOptions( + this.vaultTimeout.value, + this.vaultTimeoutAction + ); + } + + async updatePin() { + if (this.pin) { + const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true }); + + if (ref == null) { + this.pin = false; + return; + } + + this.pin = await ref.onClosedPromise(); + } else { + await this.cryptoService.clearPinProtectedKey(); + await this.vaultTimeoutService.clear(); + } + } + + async updateBiometric() { + if (this.biometric && this.supportsBiometric) { + let granted; + try { + granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] }); + } catch (e) { + // tslint:disable-next-line + console.error(e); + + if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) { + await this.platformUtilsService.showDialog( + this.i18nService.t("nativeMessaginPermissionSidebarDesc"), + this.i18nService.t("nativeMessaginPermissionSidebarTitle"), + this.i18nService.t("ok"), + null + ); + this.biometric = false; + return; + } + } + + if (!granted) { + await this.platformUtilsService.showDialog( + this.i18nService.t("nativeMessaginPermissionErrorDesc"), + this.i18nService.t("nativeMessaginPermissionErrorTitle"), + this.i18nService.t("ok"), + null + ); + this.biometric = false; + return; + } + + const submitted = Swal.fire({ + heightAuto: false, + buttonsStyling: false, + titleText: this.i18nService.t("awaitDesktop"), + text: this.i18nService.t("awaitDesktopDesc"), + icon: "info", + iconHtml: '', + showCancelButton: true, + cancelButtonText: this.i18nService.t("cancel"), + showConfirmButton: false, + allowOutsideClick: false, + }); + + await this.storageService.save(ConstantsService.biometricAwaitingAcceptance, true); + await this.cryptoService.toggleKey(); + + await Promise.race([ + submitted.then((result) => { + if (result.dismiss === Swal.DismissReason.cancel) { + this.biometric = false; + this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); + } + }), + this.platformUtilsService + .authenticateBiometric() + .then((result) => { + this.biometric = result; + + Swal.close(); + if (this.biometric === false) { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorEnableBiometricTitle"), + this.i18nService.t("errorEnableBiometricDesc") + ); } - } - - if (!this.vaultTimeout.valid) { - this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutToLarge')); - return; - } - - this.previousVaultTimeout = this.vaultTimeout.value; - - await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction); - if (this.previousVaultTimeout == null) { - this.messagingService.send('bgReseedStorage'); - } + }) + .catch((e) => { + // Handle connection errors + this.biometric = false; + }), + ]); + } else { + await this.storageService.remove(ConstantsService.biometricUnlockKey); + this.vaultTimeoutService.biometricLocked = false; } + } - async saveVaultTimeoutAction(newValue: string) { - if (newValue === 'logOut') { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('vaultTimeoutLogOutConfirmation'), - this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'), - this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning'); - if (!confirmed) { - this.vaultTimeoutActions.forEach((option: any, i) => { - if (option.value === this.vaultTimeoutAction) { - this.vaultTimeoutActionSelectRef.nativeElement.value = i + ': ' + this.vaultTimeoutAction; - } - }); - return; - } - } + async updateAutoBiometricsPrompt() { + await this.storageService.save( + ConstantsService.disableAutoBiometricsPromptKey, + this.disableAutoBiometricsPrompt + ); + } - if (!this.vaultTimeout.valid) { - this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutToLarge')); - return; - } + async lock() { + await this.vaultTimeoutService.lock(true); + } - this.vaultTimeoutAction = newValue; - await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction); + async logOut() { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("logOutConfirmation"), + this.i18nService.t("logOut"), + this.i18nService.t("yes"), + this.i18nService.t("cancel") + ); + if (confirmed) { + this.messagingService.send("logout"); } + } - async updatePin() { - if (this.pin) { - const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true }); - - if (ref == null) { - this.pin = false; - return; - } - - this.pin = await ref.onClosedPromise(); - } else { - await this.cryptoService.clearPinProtectedKey(); - await this.vaultTimeoutService.clear(); - } + async changePassword() { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("changeMasterPasswordConfirmation"), + this.i18nService.t("changeMasterPassword"), + this.i18nService.t("yes"), + this.i18nService.t("cancel") + ); + if (confirmed) { + BrowserApi.createNewTab("https://help.bitwarden.com/article/change-your-master-password/"); } + } - async updateBiometric() { - if (this.biometric && this.supportsBiometric) { - - let granted; - try { - granted = await BrowserApi.requestPermission({ permissions: ['nativeMessaging'] }); - } catch (e) { - // tslint:disable-next-line - console.error(e); - - if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) { - await this.platformUtilsService.showDialog( - this.i18nService.t('nativeMessaginPermissionSidebarDesc'), this.i18nService.t('nativeMessaginPermissionSidebarTitle'), - this.i18nService.t('ok'), null); - this.biometric = false; - return; - } - } - - if (!granted) { - await this.platformUtilsService.showDialog( - this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'), - this.i18nService.t('ok'), null); - this.biometric = false; - return; - } - - const submitted = Swal.fire({ - heightAuto: false, - buttonsStyling: false, - titleText: this.i18nService.t('awaitDesktop'), - text: this.i18nService.t('awaitDesktopDesc'), - icon: 'info', - iconHtml: '', - showCancelButton: true, - cancelButtonText: this.i18nService.t('cancel'), - showConfirmButton: false, - allowOutsideClick: false, - }); - - await this.storageService.save(ConstantsService.biometricAwaitingAcceptance, true); - await this.cryptoService.toggleKey(); - - await Promise.race([ - submitted.then(result => { - if (result.dismiss === Swal.DismissReason.cancel) { - this.biometric = false; - this.storageService.remove(ConstantsService.biometricAwaitingAcceptance); - } - }), - this.platformUtilsService.authenticateBiometric().then(result => { - this.biometric = result; - - Swal.close(); - if (this.biometric === false) { - this.platformUtilsService.showToast('error', this.i18nService.t('errorEnableBiometricTitle'), this.i18nService.t('errorEnableBiometricDesc')); - } - }).catch(e => { - // Handle connection errors - this.biometric = false; - }), - ]); - } else { - await this.storageService.remove(ConstantsService.biometricUnlockKey); - this.vaultTimeoutService.biometricLocked = false; - } + async twoStep() { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("twoStepLoginConfirmation"), + this.i18nService.t("twoStepLogin"), + this.i18nService.t("yes"), + this.i18nService.t("cancel") + ); + if (confirmed) { + BrowserApi.createNewTab("https://help.bitwarden.com/article/setup-two-step-login/"); } + } - async updateAutoBiometricsPrompt() { - await this.storageService.save(ConstantsService.disableAutoBiometricsPromptKey, this.disableAutoBiometricsPrompt); + async share() { + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t("learnOrgConfirmation"), + this.i18nService.t("learnOrg"), + this.i18nService.t("yes"), + this.i18nService.t("cancel") + ); + if (confirmed) { + BrowserApi.createNewTab("https://help.bitwarden.com/article/what-is-an-organization/"); } + } - async lock() { - await this.vaultTimeoutService.lock(true); + async webVault() { + const url = this.environmentService.getWebVaultUrl(); + BrowserApi.createNewTab(url); + } + + import() { + BrowserApi.createNewTab("https://help.bitwarden.com/article/import-data/"); + } + + export() { + this.router.navigate(["/export"]); + } + + help() { + BrowserApi.createNewTab("https://help.bitwarden.com/"); + } + + about() { + const year = new Date().getFullYear(); + const versionText = document.createTextNode( + this.i18nService.t("version") + ": " + BrowserApi.getApplicationVersion() + ); + const div = document.createElement("div"); + div.innerHTML = + `

+

Bitwarden
© Bitwarden Inc. 2015-` + + year + + `

`; + div.appendChild(versionText); + + Swal.fire({ + heightAuto: false, + buttonsStyling: false, + html: div, + showConfirmButton: false, + showCancelButton: true, + cancelButtonText: this.i18nService.t("close"), + }); + } + + async fingerprint() { + const fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId()); + const p = document.createElement("p"); + p.innerText = this.i18nService.t("yourAccountsFingerprint") + ":"; + const p2 = document.createElement("p"); + p2.innerText = fingerprint.join("-"); + const div = document.createElement("div"); + div.appendChild(p); + div.appendChild(p2); + + const result = await Swal.fire({ + heightAuto: false, + buttonsStyling: false, + html: div, + showCancelButton: true, + cancelButtonText: this.i18nService.t("close"), + showConfirmButton: true, + confirmButtonText: this.i18nService.t("learnMore"), + }); + + if (result.value) { + this.platformUtilsService.launchUri("https://help.bitwarden.com/article/fingerprint-phrase/"); } + } - async logOut() { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'), - this.i18nService.t('yes'), this.i18nService.t('cancel')); - if (confirmed) { - this.messagingService.send('logout'); - } - } - - async changePassword() { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('changeMasterPasswordConfirmation'), this.i18nService.t('changeMasterPassword'), - this.i18nService.t('yes'), this.i18nService.t('cancel')); - if (confirmed) { - BrowserApi.createNewTab('https://help.bitwarden.com/article/change-your-master-password/'); - } - } - - async twoStep() { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('twoStepLoginConfirmation'), this.i18nService.t('twoStepLogin'), - this.i18nService.t('yes'), this.i18nService.t('cancel')); - if (confirmed) { - BrowserApi.createNewTab('https://help.bitwarden.com/article/setup-two-step-login/'); - } - } - - async share() { - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t('learnOrgConfirmation'), this.i18nService.t('learnOrg'), - this.i18nService.t('yes'), this.i18nService.t('cancel')); - if (confirmed) { - BrowserApi.createNewTab('https://help.bitwarden.com/article/what-is-an-organization/'); - } - } - - async webVault() { - const url = this.environmentService.getWebVaultUrl(); - BrowserApi.createNewTab(url); - } - - import() { - BrowserApi.createNewTab('https://help.bitwarden.com/article/import-data/'); - } - - export() { - this.router.navigate(['/export']); - } - - help() { - BrowserApi.createNewTab('https://help.bitwarden.com/'); - } - - about() { - const year = (new Date()).getFullYear(); - const versionText = document.createTextNode( - this.i18nService.t('version') + ': ' + BrowserApi.getApplicationVersion()); - const div = document.createElement('div'); - div.innerHTML = `

-

Bitwarden
© Bitwarden Inc. 2015-` + year + `

`; - div.appendChild(versionText); - - Swal.fire({ - heightAuto: false, - buttonsStyling: false, - html: div, - showConfirmButton: false, - showCancelButton: true, - cancelButtonText: this.i18nService.t('close'), - }); - } - - async fingerprint() { - const fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId()); - const p = document.createElement('p'); - p.innerText = this.i18nService.t('yourAccountsFingerprint') + ':'; - const p2 = document.createElement('p'); - p2.innerText = fingerprint.join('-'); - const div = document.createElement('div'); - div.appendChild(p); - div.appendChild(p2); - - const result = await Swal.fire({ - heightAuto: false, - buttonsStyling: false, - html: div, - showCancelButton: true, - cancelButtonText: this.i18nService.t('close'), - showConfirmButton: true, - confirmButtonText: this.i18nService.t('learnMore'), - }); - - if (result.value) { - this.platformUtilsService.launchUri('https://help.bitwarden.com/article/fingerprint-phrase/'); - } - } - - rate() { - const deviceType = this.platformUtilsService.getDevice(); - BrowserApi.createNewTab((RateUrls as any)[deviceType]); - } + rate() { + const deviceType = this.platformUtilsService.getDevice(); + BrowserApi.createNewTab((RateUrls as any)[deviceType]); + } } diff --git a/src/popup/settings/sync.component.html b/src/popup/settings/sync.component.html index 2ec7c82548..c76e1f4c7d 100644 --- a/src/popup/settings/sync.component.html +++ b/src/popup/settings/sync.component.html @@ -1,22 +1,28 @@
- -

- {{'sync' | i18n}} -

-
+ +

+ {{ "sync" | i18n }} +

+
-
- -

{{'lastSync' | i18n}} {{lastSync}}

-
+
+ +

{{ "lastSync" | i18n }} {{ lastSync }}

+
diff --git a/src/popup/settings/sync.component.ts b/src/popup/settings/sync.component.ts index 9ec6b5fffd..c0f9ae7959 100644 --- a/src/popup/settings/sync.component.ts +++ b/src/popup/settings/sync.component.ts @@ -1,45 +1,44 @@ -import { - Component, - OnInit, -} from '@angular/core'; +import { Component, OnInit } from "@angular/core"; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; @Component({ - selector: 'app-sync', - templateUrl: 'sync.component.html', + selector: "app-sync", + templateUrl: "sync.component.html", }) export class SyncComponent implements OnInit { - lastSync = '--'; - syncPromise: Promise; + lastSync = "--"; + syncPromise: Promise; - constructor(private syncService: SyncService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService) { - } + constructor( + private syncService: SyncService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService + ) {} - async ngOnInit() { - await this.setLastSync(); - } + async ngOnInit() { + await this.setLastSync(); + } - async sync() { - this.syncPromise = this.syncService.fullSync(true); - const success = await this.syncPromise; - if (success) { - await this.setLastSync(); - this.platformUtilsService.showToast('success', null, this.i18nService.t('syncingComplete')); - } else { - this.platformUtilsService.showToast('error', null, this.i18nService.t('syncingFailed')); - } + async sync() { + this.syncPromise = this.syncService.fullSync(true); + const success = await this.syncPromise; + if (success) { + await this.setLastSync(); + this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingComplete")); + } else { + this.platformUtilsService.showToast("error", null, this.i18nService.t("syncingFailed")); } + } - async setLastSync() { - const last = await this.syncService.getLastSync(); - if (last != null) { - this.lastSync = last.toLocaleDateString() + ' ' + last.toLocaleTimeString(); - } else { - this.lastSync = this.i18nService.t('never'); - } + async setLastSync() { + const last = await this.syncService.getLastSync(); + if (last != null) { + this.lastSync = last.toLocaleDateString() + " " + last.toLocaleTimeString(); + } else { + this.lastSync = this.i18nService.t("never"); } + } } diff --git a/src/popup/settings/vault-timeout-input.component.html b/src/popup/settings/vault-timeout-input.component.html index 760dbd5d1e..68b5665073 100644 --- a/src/popup/settings/vault-timeout-input.component.html +++ b/src/popup/settings/vault-timeout-input.component.html @@ -1,29 +1,48 @@ - {{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}} + {{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
-
- - -
-
-
-
-
- - -
-
-
-
- - -
-
+
+ + +
+
+
+
+
+ +
+
+
+
+ + +
+
+
diff --git a/src/popup/settings/vault-timeout-input.component.ts b/src/popup/settings/vault-timeout-input.component.ts index 47335e51c4..8eb308b4c0 100644 --- a/src/popup/settings/vault-timeout-input.component.ts +++ b/src/popup/settings/vault-timeout-input.component.ts @@ -1,28 +1,22 @@ -import { Component } from '@angular/core'; -import { - NG_VALIDATORS, - NG_VALUE_ACCESSOR, -} from '@angular/forms'; +import { Component } from "@angular/core"; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms"; -import { - VaultTimeoutInputComponent as VaultTimeoutInputComponentBase -} from 'jslib-angular/components/settings/vault-timeout-input.component'; +import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component"; @Component({ - selector: 'app-vault-timeout-input', - templateUrl: 'vault-timeout-input.component.html', - providers: [ - { - provide: NG_VALUE_ACCESSOR, - multi: true, - useExisting: VaultTimeoutInputComponent, - }, - { - provide: NG_VALIDATORS, - multi: true, - useExisting: VaultTimeoutInputComponent, - }, - ], + selector: "app-vault-timeout-input", + templateUrl: "vault-timeout-input.component.html", + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: VaultTimeoutInputComponent, + }, + { + provide: NG_VALIDATORS, + multi: true, + useExisting: VaultTimeoutInputComponent, + }, + ], }) -export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase { -} +export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/src/popup/tabs.component.html b/src/popup/tabs.component.html index 645bd2359e..75c6e75b91 100644 --- a/src/popup/tabs.component.html +++ b/src/popup/tabs.component.html @@ -1,32 +1,32 @@ diff --git a/src/popup/tabs.component.ts b/src/popup/tabs.component.ts index a94eb1a18e..07392096ae 100644 --- a/src/popup/tabs.component.ts +++ b/src/popup/tabs.component.ts @@ -1,20 +1,17 @@ -import { - Component, - OnInit, -} from '@angular/core'; +import { Component, OnInit } from "@angular/core"; -import { PopupUtilsService } from './services/popup-utils.service'; +import { PopupUtilsService } from "./services/popup-utils.service"; @Component({ - selector: 'app-tabs', - templateUrl: 'tabs.component.html', + selector: "app-tabs", + templateUrl: "tabs.component.html", }) export class TabsComponent implements OnInit { - showCurrentTab: boolean = true; + showCurrentTab: boolean = true; - constructor(private popupUtilsService: PopupUtilsService) { } + constructor(private popupUtilsService: PopupUtilsService) {} - ngOnInit() { - this.showCurrentTab = !this.popupUtilsService.inPopout(window); - } + ngOnInit() { + this.showCurrentTab = !this.popupUtilsService.inPopout(window); + } } diff --git a/src/popup/vault/add-edit-custom-fields.component.html b/src/popup/vault/add-edit-custom-fields.component.html index fe36ab0a4b..e6a9c6c9c4 100644 --- a/src/popup/vault/add-edit-custom-fields.component.html +++ b/src/popup/vault/add-edit-custom-fields.component.html @@ -1,61 +1,121 @@
-

- {{'customFields' | i18n}} -

-
- -
-
- - - -
- - - - - - - -
- - -
- -
-
- -
-
+

+ {{ "customFields" | i18n }} +

+
+ +
+
+ + + +
+ + + + + + +
- -
- - - + + +
+
+
+ +
+
+ +
+ + + +
+
diff --git a/src/popup/vault/add-edit-custom-fields.component.ts b/src/popup/vault/add-edit-custom-fields.component.ts index ff0d983476..b35848cd68 100644 --- a/src/popup/vault/add-edit-custom-fields.component.ts +++ b/src/popup/vault/add-edit-custom-fields.component.ts @@ -1,18 +1,16 @@ -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; -import { - AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent -} from 'jslib-angular/components/add-edit-custom-fields.component'; +import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "jslib-angular/components/add-edit-custom-fields.component"; -import { EventService } from 'jslib-common/abstractions/event.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; +import { EventService } from "jslib-common/abstractions/event.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; @Component({ - selector: 'app-vault-add-edit-custom-fields', - templateUrl: 'add-edit-custom-fields.component.html', + selector: "app-vault-add-edit-custom-fields", + templateUrl: "add-edit-custom-fields.component.html", }) export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { - constructor(i18nService: I18nService, eventService: EventService) { - super(i18nService, eventService); - } + constructor(i18nService: I18nService, eventService: EventService) { + super(i18nService, eventService); + } } diff --git a/src/popup/vault/add-edit.component.html b/src/popup/vault/add-edit.component.html index 971413f80e..e14db0f32e 100644 --- a/src/popup/vault/add-edit.component.html +++ b/src/popup/vault/add-edit.component.html @@ -1,375 +1,646 @@
-
-
- +
+
+ +
+

+ {{ title }} +

+
+ +
+
+ + + {{ "personalOwnershipPolicyInEffect" | i18n }} + +
+

+ {{ "itemInformation" | i18n }} +

+
+
+ +
-

- {{title}} -

-
- + + +
+
+
+ + +
+
+ +
+
+ + +
+
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+ -
- - - - {{'personalOwnershipPolicyInEffect' | i18n}} - -
-

- {{'itemInformation' | i18n}} -

-
-
- - -
-
- - -
- -
-
- - -
-
-
- - -
-
- - - -
-
-
- - -
-
- -
-
- - -
-
-
- - -
-
- -
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
+
+ + + + + +
-
-
-
- -
- -
- - - - - - -
-
- - -
-
-
- +
+ +
+
+ + +
+
+
+
+
+ +
-
-
-
- - -
-
+
+
+
+
+
+ +
-
-
-
- - -
-
- - -
-
- - -
- - -
+
+ +
-
-

- -

-
-
- -
-
+
+ +
- - -
-

- {{'ownership' | i18n}} -

-
-
- - -
-
+ + +
+
+
+

+ +

+
+
+
-
-

- {{'collections' | i18n}} -

-
-
- {{'noCollectionsInList' | i18n}} -
-
-
-
- - -
-
+
+
+ + +
+

+ {{ "ownership" | i18n }} +

+
+
+ +
-
-
- -
+
+
+
+

+ {{ "collections" | i18n }} +

+
+
+ {{ "noCollectionsInList" | i18n }}
- +
+
+
+ + +
+
+
+
+
+ +
+
+ diff --git a/src/popup/vault/add-edit.component.ts b/src/popup/vault/add-edit.component.ts index 336de6ae28..841ba1dfcd 100644 --- a/src/popup/vault/add-edit.component.ts +++ b/src/popup/vault/add-edit.component.ts @@ -1,194 +1,229 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { AuditService } from 'jslib-common/abstractions/audit.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { EventService } from 'jslib-common/abstractions/event.service'; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { AuditService } from "jslib-common/abstractions/audit.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { EventService } from "jslib-common/abstractions/event.service"; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { ConstantsService } from "jslib-common/services/constants.service"; -import { PopupUtilsService } from '../services/popup-utils.service'; +import { PopupUtilsService } from "../services/popup-utils.service"; -import { LoginUriView } from 'jslib-common/models/view/loginUriView'; +import { LoginUriView } from "jslib-common/models/view/loginUriView"; -import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component'; +import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/add-edit.component"; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherType } from "jslib-common/enums/cipherType"; @Component({ - selector: 'app-vault-add-edit', - templateUrl: 'add-edit.component.html', + selector: "app-vault-add-edit", + templateUrl: "add-edit.component.html", }) export class AddEditComponent extends BaseAddEditComponent { - currentUris: string[]; - showAttachments = true; - openAttachmentsInPopup: boolean; - showAutoFillOnPageLoadOptions: boolean; + currentUris: string[]; + showAttachments = true; + openAttachmentsInPopup: boolean; + showAutoFillOnPageLoadOptions: boolean; - constructor(cipherService: CipherService, folderService: FolderService, - i18nService: I18nService, platformUtilsService: PlatformUtilsService, - auditService: AuditService, stateService: StateService, - userService: UserService, collectionService: CollectionService, - messagingService: MessagingService, private route: ActivatedRoute, - private router: Router, private location: Location, - eventService: EventService, policyService: PolicyService, - private popupUtilsService: PopupUtilsService, private storageService: StorageService, - logService: LogService, passwordRepromptService: PasswordRepromptService) { - super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService, - userService, collectionService, messagingService, eventService, policyService, passwordRepromptService, - logService); - } + constructor( + cipherService: CipherService, + folderService: FolderService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + auditService: AuditService, + stateService: StateService, + userService: UserService, + collectionService: CollectionService, + messagingService: MessagingService, + private route: ActivatedRoute, + private router: Router, + private location: Location, + eventService: EventService, + policyService: PolicyService, + private popupUtilsService: PopupUtilsService, + private storageService: StorageService, + logService: LogService, + passwordRepromptService: PasswordRepromptService + ) { + super( + cipherService, + folderService, + i18nService, + platformUtilsService, + auditService, + stateService, + userService, + collectionService, + messagingService, + eventService, + policyService, + passwordRepromptService, + logService + ); + } - async ngOnInit() { - await super.ngOnInit(); + async ngOnInit() { + await super.ngOnInit(); - this.route.queryParams.pipe(first()).subscribe(async params => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - if (params.folderId) { - this.folderId = params.folderId; - } - if (params.collectionId) { - const collection = this.writeableCollections.find(c => c.id === params.collectionId); - if (collection != null) { - this.collectionIds = [collection.id]; - this.organizationId = collection.organizationId; - } - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - this.editMode = !params.cipherId; - - if (params.cloneMode != null) { - this.cloneMode = params.cloneMode === 'true'; - } - await this.load(); - - if (!this.editMode || this.cloneMode) { - if (!this.popupUtilsService.inPopout(window) && params.name && - (this.cipher.name == null || this.cipher.name === '')) { - this.cipher.name = params.name; - } - if (!this.popupUtilsService.inPopout(window) && params.uri && - (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === '')) { - this.cipher.login.uris[0].uri = params.uri; - } - } - - this.openAttachmentsInPopup = this.popupUtilsService.inPopup(window); - }); - - if (!this.editMode) { - const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' }); - this.currentUris = tabs == null ? null : - tabs.filter(tab => tab.url != null && tab.url !== '').map(tab => tab.url); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (params.cipherId) { + this.cipherId = params.cipherId; + } + if (params.folderId) { + this.folderId = params.folderId; + } + if (params.collectionId) { + const collection = this.writeableCollections.find((c) => c.id === params.collectionId); + if (collection != null) { + this.collectionIds = [collection.id]; + this.organizationId = collection.organizationId; } + } + if (params.type) { + const type = parseInt(params.type, null); + this.type = type; + } + this.editMode = !params.cipherId; - window.setTimeout(() => { - if (!this.editMode) { - if (this.cipher.name != null && this.cipher.name !== '') { - document.getElementById('loginUsername').focus(); - } else { - document.getElementById('name').focus(); - } - } - }, 200); - } + if (params.cloneMode != null) { + this.cloneMode = params.cloneMode === "true"; + } + await this.load(); - async load() { - await super.load(); - this.showAutoFillOnPageLoadOptions = this.cipher.type === CipherType.Login && - await this.storageService.get(ConstantsService.enableAutoFillOnPageLoadKey); - } - - async submit(): Promise { - if (await super.submit()) { - if (this.cloneMode) { - this.router.navigate(['/tabs/vault']); - } else { - this.location.back(); - } - return true; + if (!this.editMode || this.cloneMode) { + if ( + !this.popupUtilsService.inPopout(window) && + params.name && + (this.cipher.name == null || this.cipher.name === "") + ) { + this.cipher.name = params.name; } + if ( + !this.popupUtilsService.inPopout(window) && + params.uri && + (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") + ) { + this.cipher.login.uris[0].uri = params.uri; + } + } - return false; + this.openAttachmentsInPopup = this.popupUtilsService.inPopup(window); + }); + + if (!this.editMode) { + const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); + this.currentUris = + tabs == null + ? null + : tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url); } - attachments() { - super.attachments(); - - if (this.openAttachmentsInPopup) { - const destinationUrl = this.router.createUrlTree(['/attachments'], { queryParams: { cipherId: this.cipher.id } }).toString(); - const currentBaseUrl = window.location.href.replace(this.router.url, ''); - this.popupUtilsService.popOut(window, currentBaseUrl + destinationUrl); + window.setTimeout(() => { + if (!this.editMode) { + if (this.cipher.name != null && this.cipher.name !== "") { + document.getElementById("loginUsername").focus(); } else { - this.router.navigate(['/attachments'], { queryParams: { cipherId: this.cipher.id } }); + document.getElementById("name").focus(); } - } + } + }, 200); + } + async load() { + await super.load(); + this.showAutoFillOnPageLoadOptions = + this.cipher.type === CipherType.Login && + (await this.storageService.get(ConstantsService.enableAutoFillOnPageLoadKey)); + } - editCollections() { - super.editCollections(); - if (this.cipher.organizationId != null) { - this.router.navigate(['/collections'], { queryParams: { cipherId: this.cipher.id } }); - } - } - - cancel() { - super.cancel(); + async submit(): Promise { + if (await super.submit()) { + if (this.cloneMode) { + this.router.navigate(["/tabs/vault"]); + } else { this.location.back(); + } + return true; } - async generatePassword(): Promise { - const confirmed = await super.generatePassword(); - if (confirmed) { - this.stateService.save('addEditCipherInfo', { - cipher: this.cipher, - collectionIds: this.collections == null ? [] : - this.collections.filter(c => (c as any).checked).map(c => c.id), - }); - this.router.navigate(['generator']); - } - return confirmed; - } + return false; + } - async delete(): Promise { - const confirmed = await super.delete(); - if (confirmed) { - this.router.navigate(['/tabs/vault']); - } - return confirmed; - } + attachments() { + super.attachments(); - toggleUriInput(uri: LoginUriView) { - const u = (uri as any); - u.showCurrentUris = !u.showCurrentUris; + if (this.openAttachmentsInPopup) { + const destinationUrl = this.router + .createUrlTree(["/attachments"], { queryParams: { cipherId: this.cipher.id } }) + .toString(); + const currentBaseUrl = window.location.href.replace(this.router.url, ""); + this.popupUtilsService.popOut(window, currentBaseUrl + destinationUrl); + } else { + this.router.navigate(["/attachments"], { queryParams: { cipherId: this.cipher.id } }); } + } - allowOwnershipOptions(): boolean { - return (!this.editMode || this.cloneMode) && this.ownershipOptions - && (this.ownershipOptions.length > 1 || !this.allowPersonal); + editCollections() { + super.editCollections(); + if (this.cipher.organizationId != null) { + this.router.navigate(["/collections"], { queryParams: { cipherId: this.cipher.id } }); } + } + + cancel() { + super.cancel(); + this.location.back(); + } + + async generatePassword(): Promise { + const confirmed = await super.generatePassword(); + if (confirmed) { + this.stateService.save("addEditCipherInfo", { + cipher: this.cipher, + collectionIds: + this.collections == null + ? [] + : this.collections.filter((c) => (c as any).checked).map((c) => c.id), + }); + this.router.navigate(["generator"]); + } + return confirmed; + } + + async delete(): Promise { + const confirmed = await super.delete(); + if (confirmed) { + this.router.navigate(["/tabs/vault"]); + } + return confirmed; + } + + toggleUriInput(uri: LoginUriView) { + const u = uri as any; + u.showCurrentUris = !u.showCurrentUris; + } + + allowOwnershipOptions(): boolean { + return ( + (!this.editMode || this.cloneMode) && + this.ownershipOptions && + (this.ownershipOptions.length > 1 || !this.allowPersonal) + ); + } } diff --git a/src/popup/vault/attachments.component.html b/src/popup/vault/attachments.component.html index 8d709453c7..136e711917 100644 --- a/src/popup/vault/attachments.component.html +++ b/src/popup/vault/attachments.component.html @@ -1,57 +1,73 @@
-
-
- - + +
+

+ {{ "attachments" | i18n }} +

+
+ +
+
+ +
+
+
+
+ {{ a.fileName }} +
+ {{ a.sizeName }} +
+ +
-

- {{'attachments' | i18n}} -

-
- +
+
+
+

+ {{ "newAttachment" | i18n }} +

+
+
+ +
- - -
-
-
-
- {{a.fileName}} -
- {{a.sizeName}} -
- -
-
-
-
-
-

- {{'newAttachment' | i18n}} -

-
-
- - -
-
- -
-
+
+ +
+ diff --git a/src/popup/vault/attachments.component.ts b/src/popup/vault/attachments.component.ts index 9ef9d0547a..5bd11dda7a 100644 --- a/src/popup/vault/attachments.component.ts +++ b/src/popup/vault/attachments.component.ts @@ -1,48 +1,63 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component'; +import { AttachmentsComponent as BaseAttachmentsComponent } from "jslib-angular/components/attachments.component"; @Component({ - selector: 'app-vault-attachments', - templateUrl: 'attachments.component.html', + selector: "app-vault-attachments", + templateUrl: "attachments.component.html", }) export class AttachmentsComponent extends BaseAttachmentsComponent { - openedAttachmentsInPopup: boolean; + openedAttachmentsInPopup: boolean; - constructor(cipherService: CipherService, i18nService: I18nService, - cryptoService: CryptoService, userService: UserService, - platformUtilsService: PlatformUtilsService, apiService: ApiService, private location: Location, - private route: ActivatedRoute, logService: LogService) { - super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService, window, - logService); - } + constructor( + cipherService: CipherService, + i18nService: I18nService, + cryptoService: CryptoService, + userService: UserService, + platformUtilsService: PlatformUtilsService, + apiService: ApiService, + private location: Location, + private route: ActivatedRoute, + logService: LogService + ) { + super( + cipherService, + i18nService, + cryptoService, + userService, + platformUtilsService, + apiService, + window, + logService + ); + } - async ngOnInit() { - this.route.queryParams.pipe(first()).subscribe(async params => { - this.cipherId = params.cipherId; - await this.init(); - }); + async ngOnInit() { + this.route.queryParams.pipe(first()).subscribe(async (params) => { + this.cipherId = params.cipherId; + await this.init(); + }); - this.openedAttachmentsInPopup = history.length === 1; - } + this.openedAttachmentsInPopup = history.length === 1; + } - back() { - this.location.back(); - } + back() { + this.location.back(); + } - close() { - window.close(); - } + close() { + window.close(); + } } diff --git a/src/popup/vault/ciphers.component.html b/src/popup/vault/ciphers.component.html index d59462c09a..71e4742ae0 100644 --- a/src/popup/vault/ciphers.component.html +++ b/src/popup/vault/ciphers.component.html @@ -1,81 +1,112 @@
-
- -
-

{{'myVault' | i18n}}

- -
- -
+
+ +
+

{{ "myVault" | i18n }}

+ +
+ +
- - -
-

- {{'folders' | i18n}} -

-
- + + +
+

+ {{ "folders" | i18n }} +

+
+ +
+
+
+

+ {{ "collections" | i18n }} +

+
+ +
+
+
+ +
+ + +

{{ "noItemsInList" | i18n }}

+ +
+
+ +
+

+ {{ groupingTitle }} + {{ isSearching() ? ciphers.length : ciphers.length }} +

+
+
-
-

- {{'collections' | i18n}} -

-
- -
-
- - -
- - -

{{'noItemsInList' | i18n}}

- -
-
- -
-

- {{groupingTitle}} - {{isSearching() ? ciphers.length : ciphers.length}} -

-
- -
-
-
-
+
+
+
diff --git a/src/popup/vault/ciphers.component.ts b/src/popup/vault/ciphers.component.ts index 587780bb1f..93e6ca38b3 100644 --- a/src/popup/vault/ciphers.component.ts +++ b/src/popup/vault/ciphers.component.ts @@ -1,234 +1,246 @@ -import { Location } from '@angular/common'; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, -} from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Location } from "@angular/common"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { SearchService } from 'jslib-common/abstractions/search.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SearchService } from "jslib-common/abstractions/search.service"; +import { StateService } from "jslib-common/abstractions/state.service"; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherType } from "jslib-common/enums/cipherType"; -import { CipherView } from 'jslib-common/models/view/cipherView'; -import { CollectionView } from 'jslib-common/models/view/collectionView'; -import { FolderView } from 'jslib-common/models/view/folderView'; +import { CipherView } from "jslib-common/models/view/cipherView"; +import { CollectionView } from "jslib-common/models/view/collectionView"; +import { FolderView } from "jslib-common/models/view/folderView"; -import { TreeNode } from 'jslib-common/models/domain/treeNode'; +import { TreeNode } from "jslib-common/models/domain/treeNode"; -import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component'; +import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component"; -import { PopupUtilsService } from '../services/popup-utils.service'; +import { PopupUtilsService } from "../services/popup-utils.service"; -const ComponentId = 'CiphersComponent'; +const ComponentId = "CiphersComponent"; @Component({ - selector: 'app-vault-ciphers', - templateUrl: 'ciphers.component.html', + selector: "app-vault-ciphers", + templateUrl: "ciphers.component.html", }) export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy { - groupingTitle: string; - state: any; - folderId: string = null; - collectionId: string = null; - type: CipherType = null; - nestedFolders: TreeNode[]; - nestedCollections: TreeNode[]; - searchTypeSearch = false; + groupingTitle: string; + state: any; + folderId: string = null; + collectionId: string = null; + type: CipherType = null; + nestedFolders: TreeNode[]; + nestedCollections: TreeNode[]; + searchTypeSearch = false; - private selectedTimeout: number; - private preventSelected = false; - private applySavedState = true; - private scrollingContainer = 'cdk-virtual-scroll-viewport'; + private selectedTimeout: number; + private preventSelected = false; + private applySavedState = true; + private scrollingContainer = "cdk-virtual-scroll-viewport"; - constructor(searchService: SearchService, private route: ActivatedRoute, - private router: Router, private location: Location, - private ngZone: NgZone, private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, private stateService: StateService, - private popupUtils: PopupUtilsService, private i18nService: I18nService, - private folderService: FolderService, private collectionService: CollectionService, - private platformUtilsService: PlatformUtilsService, private cipherService: CipherService) { - super(searchService); - this.applySavedState = (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith('/ciphers'); - } + constructor( + searchService: SearchService, + private route: ActivatedRoute, + private router: Router, + private location: Location, + private ngZone: NgZone, + private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef, + private stateService: StateService, + private popupUtils: PopupUtilsService, + private i18nService: I18nService, + private folderService: FolderService, + private collectionService: CollectionService, + private platformUtilsService: PlatformUtilsService, + private cipherService: CipherService + ) { + super(searchService); + this.applySavedState = + (window as any).previousPopupUrl != null && + !(window as any).previousPopupUrl.startsWith("/ciphers"); + } - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.route.queryParams.pipe(first()).subscribe(async params => { - if (this.applySavedState) { - this.state = (await this.stateService.get(ComponentId)) || {}; - if (this.state.searchText) { - this.searchText = this.state.searchText; - } + async ngOnInit() { + this.searchTypeSearch = !this.platformUtilsService.isSafari(); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (this.applySavedState) { + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText) { + this.searchText = this.state.searchText; + } + } + + if (params.deleted) { + this.groupingTitle = this.i18nService.t("trash"); + this.searchPlaceholder = this.i18nService.t("searchTrash"); + await this.load(null, true); + } else if (params.type) { + this.searchPlaceholder = this.i18nService.t("searchType"); + this.type = parseInt(params.type, null); + switch (this.type) { + case CipherType.Login: + this.groupingTitle = this.i18nService.t("logins"); + break; + case CipherType.Card: + this.groupingTitle = this.i18nService.t("cards"); + break; + case CipherType.Identity: + this.groupingTitle = this.i18nService.t("identities"); + break; + case CipherType.SecureNote: + this.groupingTitle = this.i18nService.t("secureNotes"); + break; + default: + break; + } + await this.load((c) => c.type === this.type); + } else if (params.folderId) { + this.folderId = params.folderId === "none" ? null : params.folderId; + this.searchPlaceholder = this.i18nService.t("searchFolder"); + if (this.folderId != null) { + const folderNode = await this.folderService.getNested(this.folderId); + if (folderNode != null && folderNode.node != null) { + this.groupingTitle = folderNode.node.name; + this.nestedFolders = + folderNode.children != null && folderNode.children.length > 0 + ? folderNode.children + : null; + } + } else { + this.groupingTitle = this.i18nService.t("noneFolder"); + } + await this.load((c) => c.folderId === this.folderId); + } else if (params.collectionId) { + this.collectionId = params.collectionId; + this.searchPlaceholder = this.i18nService.t("searchCollection"); + const collectionNode = await this.collectionService.getNested(this.collectionId); + if (collectionNode != null && collectionNode.node != null) { + this.groupingTitle = collectionNode.node.name; + this.nestedCollections = + collectionNode.children != null && collectionNode.children.length > 0 + ? collectionNode.children + : null; + } + await this.load( + (c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1 + ); + } else { + this.groupingTitle = this.i18nService.t("allItems"); + await this.load(); + } + + if (this.applySavedState && this.state != null) { + window.setTimeout( + () => + this.popupUtils.setContentScrollY(window, this.state.scrollY, this.scrollingContainer), + 0 + ); + } + this.stateService.remove(ComponentId); + }); + + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + if (message.successfully) { + window.setTimeout(() => { + this.refresh(); + }, 500); } - - if (params.deleted) { - this.groupingTitle = this.i18nService.t('trash'); - this.searchPlaceholder = this.i18nService.t('searchTrash'); - await this.load(null, true); - } else if (params.type) { - this.searchPlaceholder = this.i18nService.t('searchType'); - this.type = parseInt(params.type, null); - switch (this.type) { - case CipherType.Login: - this.groupingTitle = this.i18nService.t('logins'); - break; - case CipherType.Card: - this.groupingTitle = this.i18nService.t('cards'); - break; - case CipherType.Identity: - this.groupingTitle = this.i18nService.t('identities'); - break; - case CipherType.SecureNote: - this.groupingTitle = this.i18nService.t('secureNotes'); - break; - default: - break; - } - await this.load(c => c.type === this.type); - } else if (params.folderId) { - this.folderId = params.folderId === 'none' ? null : params.folderId; - this.searchPlaceholder = this.i18nService.t('searchFolder'); - if (this.folderId != null) { - const folderNode = await this.folderService.getNested(this.folderId); - if (folderNode != null && folderNode.node != null) { - this.groupingTitle = folderNode.node.name; - this.nestedFolders = folderNode.children != null && folderNode.children.length > 0 ? - folderNode.children : null; - } - } else { - this.groupingTitle = this.i18nService.t('noneFolder'); - } - await this.load(c => c.folderId === this.folderId); - } else if (params.collectionId) { - this.collectionId = params.collectionId; - this.searchPlaceholder = this.i18nService.t('searchCollection'); - const collectionNode = await this.collectionService.getNested(this.collectionId); - if (collectionNode != null && collectionNode.node != null) { - this.groupingTitle = collectionNode.node.name; - this.nestedCollections = collectionNode.children != null && collectionNode.children.length > 0 ? - collectionNode.children : null; - } - await this.load(c => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1); - } else { - this.groupingTitle = this.i18nService.t('allItems'); - await this.load(); - } - - if (this.applySavedState && this.state != null) { - window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY, - this.scrollingContainer), 0); - } - this.stateService.remove(ComponentId); - }); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'syncCompleted': - if (message.successfully) { - window.setTimeout(() => { - this.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - this.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - } - - selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - super.selectCipher(cipher); - this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } }); - } - this.preventSelected = false; - }, 200); - } - - selectFolder(folder: FolderView) { - if (folder.id != null) { - this.router.navigate(['/ciphers'], { queryParams: { folderId: folder.id } }); - } - } - - selectCollection(collection: CollectionView) { - this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } }); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; + break; + default: + break; } - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - BrowserApi.createNewTab(cipher.login.launchUri); - if (this.popupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } + this.changeDetectorRef.detectChanges(); + }); + }); + } + + ngOnDestroy() { + this.saveState(); + this.broadcasterService.unsubscribe(ComponentId); + } + + selectCipher(cipher: CipherView) { + this.selectedTimeout = window.setTimeout(() => { + if (!this.preventSelected) { + super.selectCipher(cipher); + this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); + } + this.preventSelected = false; + }, 200); + } + + selectFolder(folder: FolderView) { + if (folder.id != null) { + this.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id } }); + } + } + + selectCollection(collection: CollectionView) { + this.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } }); + } + + async launchCipher(cipher: CipherView) { + if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { + return; } - addCipher() { - if (this.deleted) { - return false; - } - super.addCipher(); - this.router.navigate(['/add-cipher'], { - queryParams: { - folderId: this.folderId, - type: this.type, - collectionId: this.collectionId, - }, - }); + if (this.selectedTimeout != null) { + window.clearTimeout(this.selectedTimeout); } + this.preventSelected = true; + await this.cipherService.updateLastLaunchedDate(cipher.id); + BrowserApi.createNewTab(cipher.login.launchUri); + if (this.popupUtils.inPopup(window)) { + BrowserApi.closePopup(window); + } + } - back() { - (window as any).routeDirection = 'b'; - this.location.back(); + addCipher() { + if (this.deleted) { + return false; } + super.addCipher(); + this.router.navigate(["/add-cipher"], { + queryParams: { + folderId: this.folderId, + type: this.type, + collectionId: this.collectionId, + }, + }); + } - showGroupings() { - return !this.isSearching() && - ((this.nestedFolders && this.nestedFolders.length) || - (this.nestedCollections && this.nestedCollections.length)); - } + back() { + (window as any).routeDirection = "b"; + this.location.back(); + } - private async saveState() { - this.state = { - scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer), - searchText: this.searchText, - }; - await this.stateService.save(ComponentId, this.state); - } + showGroupings() { + return ( + !this.isSearching() && + ((this.nestedFolders && this.nestedFolders.length) || + (this.nestedCollections && this.nestedCollections.length)) + ); + } + + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); + } } diff --git a/src/popup/vault/collections.component.html b/src/popup/vault/collections.component.html index e96c3a5b67..51f1a8880f 100644 --- a/src/popup/vault/collections.component.html +++ b/src/popup/vault/collections.component.html @@ -1,36 +1,43 @@
-
-
- +
+
+ +
+

+ {{ "collections" | i18n }} +

+
+ +
+
+ +
+
+
+ {{ "noCollectionsInList" | i18n }}
-

- {{'collections' | i18n}} -

-
- +
+
+
+ +
-
- -
-
-
- {{'noCollectionsInList' | i18n}} -
-
-
-
- - -
-
-
-
+
+
+
diff --git a/src/popup/vault/collections.component.ts b/src/popup/vault/collections.component.ts index fa1fa5372c..d31e492bef 100644 --- a/src/popup/vault/collections.component.ts +++ b/src/popup/vault/collections.component.ts @@ -1,39 +1,44 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { first } from 'rxjs/operators'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { first } from "rxjs/operators"; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { CollectionsComponent as BaseCollectionsComponent } from 'jslib-angular/components/collections.component'; +import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component"; @Component({ - selector: 'app-vault-collections', - templateUrl: 'collections.component.html', + selector: "app-vault-collections", + templateUrl: "collections.component.html", }) export class CollectionsComponent extends BaseCollectionsComponent { - constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService, - i18nService: I18nService, cipherService: CipherService, - private route: ActivatedRoute, private location: Location, - logService: LogService) { - super(collectionService, platformUtilsService, i18nService, cipherService, logService); - } + constructor( + collectionService: CollectionService, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + cipherService: CipherService, + private route: ActivatedRoute, + private location: Location, + logService: LogService + ) { + super(collectionService, platformUtilsService, i18nService, cipherService, logService); + } - async ngOnInit() { - this.onSavedCollections.subscribe(() => { - this.back(); - }); - this.route.queryParams.pipe(first()).subscribe(async params => { - this.cipherId = params.cipherId; - await this.load(); - }); - } + async ngOnInit() { + this.onSavedCollections.subscribe(() => { + this.back(); + }); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + this.cipherId = params.cipherId; + await this.load(); + }); + } - back() { - this.location.back(); - } + back() { + this.location.back(); + } } diff --git a/src/popup/vault/current-tab.component.html b/src/popup/vault/current-tab.component.html index d2a9ae9fc3..7f3d961b96 100644 --- a/src/popup/vault/current-tab.component.html +++ b/src/popup/vault/current-tab.component.html @@ -1,64 +1,94 @@
-

{{'currentTab' | i18n}}

-
- - -
- -
- -
+

{{ "currentTab" | i18n }}

+
+ + +
+ +
+ +
-
- +
+ +
+ +
+

+ {{ "typeLogins" | i18n }} + {{ loginCiphers.length }} +

+
+ + +
+

{{ "autoFillInfo" | i18n }}

+ +
+
- -
-

- {{'typeLogins' | i18n}} - {{loginCiphers.length}} -

-
- - -
-

{{'autoFillInfo' | i18n}}

- -
-
-
-
-

- {{'cards' | i18n}} - {{cardCiphers.length}} -

-
- -
-
-
-

- {{'identities' | i18n}} - {{identityCiphers.length}} -

-
- -
-
-
+
+

+ {{ "cards" | i18n }} + {{ cardCiphers.length }} +

+
+ +
+
+
+

+ {{ "identities" | i18n }} + {{ identityCiphers.length }} +

+
+ +
+
+
diff --git a/src/popup/vault/current-tab.component.ts b/src/popup/vault/current-tab.component.ts index b02bbe184d..afe8e6c0ee 100644 --- a/src/popup/vault/current-tab.component.ts +++ b/src/popup/vault/current-tab.component.ts @@ -1,244 +1,255 @@ -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, -} from '@angular/core'; -import { Router } from '@angular/router'; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; +import { CipherType } from "jslib-common/enums/cipherType"; -import { CipherView } from 'jslib-common/models/view/cipherView'; +import { CipherView } from "jslib-common/models/view/cipherView"; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { SearchService } from 'jslib-common/abstractions/search.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SearchService } from "jslib-common/abstractions/search.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { SyncService } from "jslib-common/abstractions/sync.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 { PopupUtilsService } from '../services/popup-utils.service'; +import { PopupUtilsService } from "../services/popup-utils.service"; -import { Utils } from 'jslib-common/misc/utils'; +import { Utils } from "jslib-common/misc/utils"; -const BroadcasterSubscriptionId = 'CurrentTabComponent'; +const BroadcasterSubscriptionId = "CurrentTabComponent"; @Component({ - selector: 'app-current-tab', - templateUrl: 'current-tab.component.html', + selector: "app-current-tab", + templateUrl: "current-tab.component.html", }) export class CurrentTabComponent implements OnInit, OnDestroy { - pageDetails: any[] = []; - cardCiphers: CipherView[]; - identityCiphers: CipherView[]; - loginCiphers: CipherView[]; - url: string; - hostname: string; - searchText: string; - inSidebar = false; - searchTypeSearch = false; - loaded = false; + pageDetails: any[] = []; + cardCiphers: CipherView[]; + identityCiphers: CipherView[]; + loginCiphers: CipherView[]; + url: string; + hostname: string; + searchText: string; + inSidebar = false; + searchTypeSearch = false; + loaded = false; - private totpCode: string; - private totpTimeout: number; - private loadedTimeout: number; - private searchTimeout: number; + private totpCode: string; + private totpTimeout: number; + private loadedTimeout: number; + private searchTimeout: number; - constructor(private platformUtilsService: PlatformUtilsService, private cipherService: CipherService, - private popupUtilsService: PopupUtilsService, private autofillService: AutofillService, - private i18nService: I18nService, private router: Router, - private ngZone: NgZone, private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService, - private searchService: SearchService, private storageService: StorageService, - private passwordRepromptService: PasswordRepromptService) { + constructor( + private platformUtilsService: PlatformUtilsService, + private cipherService: CipherService, + private popupUtilsService: PopupUtilsService, + private autofillService: AutofillService, + private i18nService: I18nService, + private router: Router, + private ngZone: NgZone, + private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef, + private syncService: SyncService, + private searchService: SearchService, + private storageService: StorageService, + private passwordRepromptService: PasswordRepromptService + ) {} + + async ngOnInit() { + this.searchTypeSearch = !this.platformUtilsService.isSafari(); + this.inSidebar = this.popupUtilsService.inSidebar(window); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + if (this.loaded) { + window.setTimeout(() => { + this.load(); + }, 500); + } + break; + case "collectPageDetailsResponse": + if (message.sender === BroadcasterSubscriptionId) { + this.pageDetails.push({ + frameId: message.webExtSender.frameId, + tab: message.tab, + details: message.details, + }); + } + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }); + }); + + if (!this.syncService.syncInProgress) { + await this.load(); + } else { + this.loadedTimeout = window.setTimeout(async () => { + if (!this.loaded) { + await this.load(); + } + }, 5000); } - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.inSidebar = this.popupUtilsService.inSidebar(window); + window.setTimeout(() => { + document.getElementById("search").focus(); + }, 100); + } - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'syncCompleted': - if (this.loaded) { - window.setTimeout(() => { - this.load(); - }, 500); - } - break; - case 'collectPageDetailsResponse': - if (message.sender === BroadcasterSubscriptionId) { - this.pageDetails.push({ - frameId: message.webExtSender.frameId, - tab: message.tab, - details: message.details, - }); - } - break; - default: - break; - } + ngOnDestroy() { + window.clearTimeout(this.loadedTimeout); + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } - this.changeDetectorRef.detectChanges(); - }); - }); + async refresh() { + await this.load(); + } - if (!this.syncService.syncInProgress) { - await this.load(); + addCipher() { + this.router.navigate(["/add-cipher"], { queryParams: { name: this.hostname, uri: this.url } }); + } + + viewCipher(cipher: CipherView) { + this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); + } + + async fillCipher(cipher: CipherView) { + if ( + cipher.reprompt !== CipherRepromptType.None && + !(await this.passwordRepromptService.showPasswordPrompt()) + ) { + return; + } + + this.totpCode = null; + if (this.totpTimeout != null) { + window.clearTimeout(this.totpTimeout); + } + + if (this.pageDetails == null || this.pageDetails.length === 0) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); + return; + } + + try { + this.totpCode = await this.autofillService.doAutoFill({ + cipher: cipher, + pageDetails: this.pageDetails, + doc: window.document, + fillNewPassword: true, + }); + if (this.totpCode != null) { + this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); + } + if (this.popupUtilsService.inPopup(window)) { + if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) { + BrowserApi.closePopup(window); } else { - this.loadedTimeout = window.setTimeout(async () => { - if (!this.loaded) { - await this.load(); - } - }, 5000); + // Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard + setTimeout(() => BrowserApi.closePopup(window), 50); } + } + } catch { + this.ngZone.run(() => { + this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); + this.changeDetectorRef.detectChanges(); + }); + } + } - window.setTimeout(() => { - document.getElementById('search').focus(); - }, 100); + searchVault() { + if (this.searchTimeout != null) { + clearTimeout(this.searchTimeout); + } + if (!this.searchService.isSearchable(this.searchText)) { + return; + } + this.searchTimeout = window.setTimeout(async () => { + this.router.navigate(["/tabs/vault"], { queryParams: { searchText: this.searchText } }); + }, 200); + } + + closeOnEsc(e: KeyboardEvent) { + // If input not empty, use browser default behavior of clearing input instead + if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { + BrowserApi.closePopup(window); + } + } + + private async load() { + const tab = await BrowserApi.getTabFromCurrentWindow(); + if (tab != null) { + this.url = tab.url; + } else { + this.loginCiphers = []; + this.loaded = true; + return; } - ngOnDestroy() { - window.clearTimeout(this.loadedTimeout); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + this.hostname = Utils.getHostname(this.url); + this.pageDetails = []; + BrowserApi.tabSendMessage(tab, { + command: "collectPageDetails", + tab: tab, + sender: BroadcasterSubscriptionId, + }); + + const otherTypes: CipherType[] = []; + const dontShowCards = await this.storageService.get( + ConstantsService.dontShowCardsCurrentTab + ); + const dontShowIdentities = await this.storageService.get( + ConstantsService.dontShowIdentitiesCurrentTab + ); + if (!dontShowCards) { + otherTypes.push(CipherType.Card); + } + if (!dontShowIdentities) { + otherTypes.push(CipherType.Identity); } - async refresh() { - await this.load(); - } + const ciphers = await this.cipherService.getAllDecryptedForUrl( + this.url, + otherTypes.length > 0 ? otherTypes : null + ); - addCipher() { - this.router.navigate(['/add-cipher'], { queryParams: { name: this.hostname, uri: this.url } }); - } + this.loginCiphers = []; + this.cardCiphers = []; + this.identityCiphers = []; - viewCipher(cipher: CipherView) { - this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } }); - } + ciphers.forEach((c) => { + switch (c.type) { + case CipherType.Login: + this.loginCiphers.push(c); + break; + case CipherType.Card: + this.cardCiphers.push(c); + break; + case CipherType.Identity: + this.identityCiphers.push(c); + break; + default: + break; + } + }); - async fillCipher(cipher: CipherView) { - if (cipher.reprompt !== CipherRepromptType.None && !await this.passwordRepromptService.showPasswordPrompt()) { - return; - } - - this.totpCode = null; - if (this.totpTimeout != null) { - window.clearTimeout(this.totpTimeout); - } - - if (this.pageDetails == null || this.pageDetails.length === 0) { - this.platformUtilsService.showToast('error', null, this.i18nService.t('autofillError')); - return; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - cipher: cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - if (this.popupUtilsService.inPopup(window)) { - if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) { - BrowserApi.closePopup(window); - } else { - // Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard - setTimeout(() => BrowserApi.closePopup(window), 50); - } - } - } catch { - this.ngZone.run(() => { - this.platformUtilsService.showToast('error', null, this.i18nService.t('autofillError')); - this.changeDetectorRef.detectChanges(); - }); - } - } - - searchVault() { - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - if (!this.searchService.isSearchable(this.searchText)) { - return; - } - this.searchTimeout = window.setTimeout(async () => { - this.router.navigate(['/tabs/vault'], { queryParams: { searchText: this.searchText } }); - }, 200); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) { - BrowserApi.closePopup(window); - } - } - - private async load() { - const tab = await BrowserApi.getTabFromCurrentWindow(); - if (tab != null) { - this.url = tab.url; - } else { - this.loginCiphers = []; - this.loaded = true; - return; - } - - this.hostname = Utils.getHostname(this.url); - this.pageDetails = []; - BrowserApi.tabSendMessage(tab, { - command: 'collectPageDetails', - tab: tab, - sender: BroadcasterSubscriptionId, - }); - - const otherTypes: CipherType[] = []; - const dontShowCards = await this.storageService.get(ConstantsService.dontShowCardsCurrentTab); - const dontShowIdentities = await this.storageService.get( - ConstantsService.dontShowIdentitiesCurrentTab); - if (!dontShowCards) { - otherTypes.push(CipherType.Card); - } - if (!dontShowIdentities) { - otherTypes.push(CipherType.Identity); - } - - const ciphers = await this.cipherService.getAllDecryptedForUrl(this.url, - otherTypes.length > 0 ? otherTypes : null); - - this.loginCiphers = []; - this.cardCiphers = []; - this.identityCiphers = []; - - ciphers.forEach(c => { - switch (c.type) { - case CipherType.Login: - this.loginCiphers.push(c); - break; - case CipherType.Card: - this.cardCiphers.push(c); - break; - case CipherType.Identity: - this.identityCiphers.push(c); - break; - default: - break; - } - }); - - this.loginCiphers = this.loginCiphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); - this.loaded = true; - } + this.loginCiphers = this.loginCiphers.sort((a, b) => + this.cipherService.sortCiphersByLastUsedThenName(a, b) + ); + this.loaded = true; + } } diff --git a/src/popup/vault/groupings.component.html b/src/popup/vault/groupings.component.html index 4f74ec7d02..83f1be4670 100644 --- a/src/popup/vault/groupings.component.html +++ b/src/popup/vault/groupings.component.html @@ -1,160 +1,230 @@
-
- -
-

{{'myVault' | i18n}}

- -
- -
+
+ +
+

{{ "myVault" | i18n }}

+ +
+ +
-
- - - -

{{'noItemsInList' | i18n}}

- -
+
+ + + +

{{ "noItemsInList" | i18n }}

+ +
+
+ +
+

+ {{ "favorites" | i18n }} + {{ favoriteCiphers.length }} +

+
+ +
- -
-

- {{'favorites' | i18n}} - {{favoriteCiphers.length}} -

-
- +
+

+ {{ "types" | i18n }} + 4 +

+
+ + + + +
+
+
+

+ {{ "folders" | i18n }} + {{ folderCount }} +

+
+ +
+
+
+

+ {{ "collections" | i18n }} + {{ nestedCollections.length }} +

+
+ +
+
+
+

+ {{ "noneFolder" | i18n }} +
{{ noFolderCiphers.length }}
+

+
+ +
+
+
+

+ {{ "trash" | i18n }} + {{ deletedCount }} +

+
+ +
+
+ + +
+

{{ "noItemsInList" | i18n }}

+
+ +
+
+
-
-

- {{'types' | i18n}} - 4 -

-
- - - - -
-
-
-

- {{'folders' | i18n}} - {{folderCount}} -

-
- -
-
-
-

- {{'collections' | i18n}} - {{nestedCollections.length}} -

-
- -
-
-
-

- {{'noneFolder' | i18n}} -
{{noFolderCiphers.length}}
-

-
- -
-
-
-

- {{'trash' | i18n}} - {{deletedCount}} -

-
- -
-
- - -
-

{{'noItemsInList' | i18n}}

-
- -
-
- -
-
-
-
+
+
+
diff --git a/src/popup/vault/groupings.component.ts b/src/popup/vault/groupings.component.ts index 2780786cc3..584537b25d 100644 --- a/src/popup/vault/groupings.component.ts +++ b/src/popup/vault/groupings.component.ts @@ -1,361 +1,376 @@ -import { Location } from '@angular/common'; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, -} from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Location } from "@angular/common"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { BrowserApi } from '../../browser/browserApi'; +import { BrowserApi } from "../../browser/browserApi"; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherType } from "jslib-common/enums/cipherType"; -import { CipherView } from 'jslib-common/models/view/cipherView'; -import { CollectionView } from 'jslib-common/models/view/collectionView'; -import { FolderView } from 'jslib-common/models/view/folderView'; +import { CipherView } from "jslib-common/models/view/cipherView"; +import { CollectionView } from "jslib-common/models/view/collectionView"; +import { FolderView } from "jslib-common/models/view/folderView"; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { SearchService } from 'jslib-common/abstractions/search.service'; -import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { FolderService } from "jslib-common/abstractions/folder.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SearchService } from "jslib-common/abstractions/search.service"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component'; +import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component"; -import { PopupUtilsService } from '../services/popup-utils.service'; +import { PopupUtilsService } from "../services/popup-utils.service"; -const ComponentId = 'GroupingsComponent'; -const ScopeStateId = ComponentId + 'Scope'; +const ComponentId = "GroupingsComponent"; +const ScopeStateId = ComponentId + "Scope"; @Component({ - selector: 'app-vault-groupings', - templateUrl: 'groupings.component.html', + selector: "app-vault-groupings", + templateUrl: "groupings.component.html", }) export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy { + get showNoFolderCiphers(): boolean { + return ( + this.noFolderCiphers != null && + this.noFolderCiphers.length < this.noFolderListSize && + this.collections.length === 0 + ); + } - get showNoFolderCiphers(): boolean { - return this.noFolderCiphers != null && this.noFolderCiphers.length < this.noFolderListSize && - this.collections.length === 0; - } + get folderCount(): number { + return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1); + } + ciphers: CipherView[]; + favoriteCiphers: CipherView[]; + noFolderCiphers: CipherView[]; + folderCounts = new Map(); + collectionCounts = new Map(); + typeCounts = new Map(); + searchText: string; + state: any; + scopeState: any; + showLeftHeader = true; + searchPending = false; + searchTypeSearch = false; + deletedCount = 0; - get folderCount(): number { - return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1); - } - ciphers: CipherView[]; - favoriteCiphers: CipherView[]; - noFolderCiphers: CipherView[]; - folderCounts = new Map(); - collectionCounts = new Map(); - typeCounts = new Map(); - searchText: string; - state: any; - scopeState: any; - showLeftHeader = true; - searchPending = false; - searchTypeSearch = false; - deletedCount = 0; + private loadedTimeout: number; + private selectedTimeout: number; + private preventSelected = false; + private noFolderListSize = 100; + private searchTimeout: any = null; + private hasSearched = false; + private hasLoadedAllCiphers = false; + private allCiphers: CipherView[] = null; - private loadedTimeout: number; - private selectedTimeout: number; - private preventSelected = false; - private noFolderListSize = 100; - private searchTimeout: any = null; - private hasSearched = false; - private hasLoadedAllCiphers = false; - private allCiphers: CipherView[] = null; + constructor( + collectionService: CollectionService, + folderService: FolderService, + storageService: StorageService, + userService: UserService, + private cipherService: CipherService, + private router: Router, + private ngZone: NgZone, + private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef, + private route: ActivatedRoute, + private stateService: StateService, + private popupUtils: PopupUtilsService, + private syncService: SyncService, + private platformUtilsService: PlatformUtilsService, + private searchService: SearchService, + private location: Location + ) { + super(collectionService, folderService, storageService, userService); + this.noFolderListSize = 100; + } - constructor(collectionService: CollectionService, folderService: FolderService, - storageService: StorageService, userService: UserService, - private cipherService: CipherService, private router: Router, - private ngZone: NgZone, private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, private route: ActivatedRoute, - private stateService: StateService, private popupUtils: PopupUtilsService, - private syncService: SyncService, private platformUtilsService: PlatformUtilsService, - private searchService: SearchService, private location: Location) { - super(collectionService, folderService, storageService, userService); - this.noFolderListSize = 100; - } + async ngOnInit() { + this.searchTypeSearch = !this.platformUtilsService.isSafari(); + this.showLeftHeader = !( + this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() + ); + this.stateService.remove("CiphersComponent"); - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()); - this.stateService.remove('CiphersComponent'); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'syncCompleted': - window.setTimeout(() => { - this.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - const restoredScopeState = await this.restoreState(); - this.route.queryParams.pipe(first()).subscribe(async params => { - this.state = (await this.stateService.get(ComponentId)) || {}; - if (this.state.searchText) { - this.searchText = this.state.searchText; - } else if (params.searchText) { - this.searchText = params.searchText; - this.location.replaceState('vault'); - } - - if (!this.syncService.syncInProgress) { - this.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - this.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); - } - }); - } - - ngOnDestroy() { - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); + this.broadcasterService.subscribe(ComponentId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + window.setTimeout(() => { + this.load(); + }, 500); + break; + default: + break; } - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.saveState(); - this.broadcasterService.unsubscribe(ComponentId); + + this.changeDetectorRef.detectChanges(); + }); + }); + + const restoredScopeState = await this.restoreState(); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + this.state = (await this.stateService.get(ComponentId)) || {}; + if (this.state.searchText) { + this.searchText = this.state.searchText; + } else if (params.searchText) { + this.searchText = params.searchText; + this.location.replaceState("vault"); + } + + if (!this.syncService.syncInProgress) { + this.load(); + } else { + this.loadedTimeout = window.setTimeout(() => { + if (!this.loaded) { + this.load(); + } + }, 5000); + } + + if (!this.syncService.syncInProgress || restoredScopeState) { + window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0); + } + }); + } + + ngOnDestroy() { + if (this.loadedTimeout != null) { + window.clearTimeout(this.loadedTimeout); + } + if (this.selectedTimeout != null) { + window.clearTimeout(this.selectedTimeout); + } + this.saveState(); + this.broadcasterService.unsubscribe(ComponentId); + } + + async load() { + await super.load(false); + await this.loadCiphers(); + if (this.showNoFolderCiphers && this.nestedFolders.length > 0) { + // Remove "No Folder" from folder listing + this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1); } - async load() { - await super.load(false); + super.loaded = true; + } + + async loadCiphers() { + this.allCiphers = await this.cipherService.getAllDecrypted(); + if (!this.hasLoadedAllCiphers) { + this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText); + } + this.deletedCount = this.allCiphers.filter((c) => c.isDeleted).length; + await this.search(null); + let favoriteCiphers: CipherView[] = null; + let noFolderCiphers: CipherView[] = null; + const folderCounts = new Map(); + const collectionCounts = new Map(); + const typeCounts = new Map(); + + this.ciphers.forEach((c) => { + if (c.isDeleted) { + return; + } + if (c.favorite) { + if (favoriteCiphers == null) { + favoriteCiphers = []; + } + favoriteCiphers.push(c); + } + + if (c.folderId == null) { + if (noFolderCiphers == null) { + noFolderCiphers = []; + } + noFolderCiphers.push(c); + } + + if (typeCounts.has(c.type)) { + typeCounts.set(c.type, typeCounts.get(c.type) + 1); + } else { + typeCounts.set(c.type, 1); + } + + if (folderCounts.has(c.folderId)) { + folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1); + } else { + folderCounts.set(c.folderId, 1); + } + + if (c.collectionIds != null) { + c.collectionIds.forEach((colId) => { + if (collectionCounts.has(colId)) { + collectionCounts.set(colId, collectionCounts.get(colId) + 1); + } else { + collectionCounts.set(colId, 1); + } + }); + } + }); + + this.favoriteCiphers = favoriteCiphers; + this.noFolderCiphers = noFolderCiphers; + this.typeCounts = typeCounts; + this.folderCounts = folderCounts; + this.collectionCounts = collectionCounts; + } + + async search(timeout: number = null) { + this.searchPending = false; + if (this.searchTimeout != null) { + clearTimeout(this.searchTimeout); + } + const filterDeleted = (c: CipherView) => !c.isDeleted; + if (timeout == null) { + this.hasSearched = this.searchService.isSearchable(this.searchText); + this.ciphers = await this.searchService.searchCiphers( + this.searchText, + filterDeleted, + this.allCiphers + ); + return; + } + this.searchPending = true; + this.searchTimeout = setTimeout(async () => { + this.hasSearched = this.searchService.isSearchable(this.searchText); + if (!this.hasLoadedAllCiphers && !this.hasSearched) { await this.loadCiphers(); - if (this.showNoFolderCiphers && this.nestedFolders.length > 0) { - // Remove "No Folder" from folder listing - this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1); - } + } else { + this.ciphers = await this.searchService.searchCiphers( + this.searchText, + filterDeleted, + this.allCiphers + ); + } + this.searchPending = false; + }, timeout); + } - super.loaded = true; + async selectType(type: CipherType) { + super.selectType(type); + this.router.navigate(["/ciphers"], { queryParams: { type: type } }); + } + + async selectFolder(folder: FolderView) { + super.selectFolder(folder); + this.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id || "none" } }); + } + + async selectCollection(collection: CollectionView) { + super.selectCollection(collection); + this.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } }); + } + + async selectTrash() { + super.selectTrash(); + this.router.navigate(["/ciphers"], { queryParams: { deleted: true } }); + } + + async selectCipher(cipher: CipherView) { + this.selectedTimeout = window.setTimeout(() => { + if (!this.preventSelected) { + this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); + } + this.preventSelected = false; + }, 200); + } + + async launchCipher(cipher: CipherView) { + if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { + return; } - async loadCiphers() { - this.allCiphers = await this.cipherService.getAllDecrypted(); - if (!this.hasLoadedAllCiphers) { - this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText); - } - this.deletedCount = this.allCiphers.filter(c => c.isDeleted).length; - await this.search(null); - let favoriteCiphers: CipherView[] = null; - let noFolderCiphers: CipherView[] = null; - const folderCounts = new Map(); - const collectionCounts = new Map(); - const typeCounts = new Map(); + if (this.selectedTimeout != null) { + window.clearTimeout(this.selectedTimeout); + } + this.preventSelected = true; + await this.cipherService.updateLastLaunchedDate(cipher.id); + BrowserApi.createNewTab(cipher.login.launchUri); + if (this.popupUtils.inPopup(window)) { + BrowserApi.closePopup(window); + } + } - this.ciphers.forEach(c => { - if (c.isDeleted) { - return; - } - if (c.favorite) { - if (favoriteCiphers == null) { - favoriteCiphers = []; - } - favoriteCiphers.push(c); - } + async addCipher() { + this.router.navigate(["/add-cipher"]); + } - if (c.folderId == null) { - if (noFolderCiphers == null) { - noFolderCiphers = []; - } - noFolderCiphers.push(c); - } + showSearching() { + return ( + this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)) + ); + } - if (typeCounts.has(c.type)) { - typeCounts.set(c.type, typeCounts.get(c.type) + 1); - } else { - typeCounts.set(c.type, 1); - } + closeOnEsc(e: KeyboardEvent) { + // If input not empty, use browser default behavior of clearing input instead + if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { + BrowserApi.closePopup(window); + } + } - if (folderCounts.has(c.folderId)) { - folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1); - } else { - folderCounts.set(c.folderId, 1); - } + private async saveState() { + this.state = { + scrollY: this.popupUtils.getContentScrollY(window), + searchText: this.searchText, + }; + await this.stateService.save(ComponentId, this.state); - if (c.collectionIds != null) { - c.collectionIds.forEach(colId => { - if (collectionCounts.has(colId)) { - collectionCounts.set(colId, collectionCounts.get(colId) + 1); - } else { - collectionCounts.set(colId, 1); - } - }); - } - }); + this.scopeState = { + favoriteCiphers: this.favoriteCiphers, + noFolderCiphers: this.noFolderCiphers, + ciphers: this.ciphers, + collectionCounts: this.collectionCounts, + folderCounts: this.folderCounts, + typeCounts: this.typeCounts, + folders: this.folders, + collections: this.collections, + deletedCount: this.deletedCount, + }; + await this.stateService.save(ScopeStateId, this.scopeState); + } - this.favoriteCiphers = favoriteCiphers; - this.noFolderCiphers = noFolderCiphers; - this.typeCounts = typeCounts; - this.folderCounts = folderCounts; - this.collectionCounts = collectionCounts; + private async restoreState(): Promise { + this.scopeState = await this.stateService.get(ScopeStateId); + if (this.scopeState == null) { + return false; } - async search(timeout: number = null) { - this.searchPending = false; - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - const filterDeleted = (c: CipherView) => !c.isDeleted; - if (timeout == null) { - this.hasSearched = this.searchService.isSearchable(this.searchText); - this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers); - return; - } - this.searchPending = true; - this.searchTimeout = setTimeout(async () => { - this.hasSearched = this.searchService.isSearchable(this.searchText); - if (!this.hasLoadedAllCiphers && !this.hasSearched) { - await this.loadCiphers(); - } else { - this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers); - } - this.searchPending = false; - }, timeout); + if (this.scopeState.favoriteCiphers != null) { + this.favoriteCiphers = this.scopeState.favoriteCiphers; + } + if (this.scopeState.noFolderCiphers != null) { + this.noFolderCiphers = this.scopeState.noFolderCiphers; + } + if (this.scopeState.ciphers != null) { + this.ciphers = this.scopeState.ciphers; + } + if (this.scopeState.collectionCounts != null) { + this.collectionCounts = this.scopeState.collectionCounts; + } + if (this.scopeState.folderCounts != null) { + this.folderCounts = this.scopeState.folderCounts; + } + if (this.scopeState.typeCounts != null) { + this.typeCounts = this.scopeState.typeCounts; + } + if (this.scopeState.folders != null) { + this.folders = this.scopeState.folders; + } + if (this.scopeState.collections != null) { + this.collections = this.scopeState.collections; + } + if (this.scopeState.deletedCiphers != null) { + this.deletedCount = this.scopeState.deletedCount; } - async selectType(type: CipherType) { - super.selectType(type); - this.router.navigate(['/ciphers'], { queryParams: { type: type } }); - } - - async selectFolder(folder: FolderView) { - super.selectFolder(folder); - this.router.navigate(['/ciphers'], { queryParams: { folderId: folder.id || 'none' } }); - } - - async selectCollection(collection: CollectionView) { - super.selectCollection(collection); - this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } }); - } - - async selectTrash() { - super.selectTrash(); - this.router.navigate(['/ciphers'], { queryParams: { deleted: true } }); - } - - async selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } }); - } - this.preventSelected = false; - }, 200); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - BrowserApi.createNewTab(cipher.login.launchUri); - if (this.popupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - async addCipher() { - this.router.navigate(['/add-cipher']); - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText)); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) { - BrowserApi.closePopup(window); - } - } - - private async saveState() { - this.state = { - scrollY: this.popupUtils.getContentScrollY(window), - searchText: this.searchText, - }; - await this.stateService.save(ComponentId, this.state); - - this.scopeState = { - favoriteCiphers: this.favoriteCiphers, - noFolderCiphers: this.noFolderCiphers, - ciphers: this.ciphers, - collectionCounts: this.collectionCounts, - folderCounts: this.folderCounts, - typeCounts: this.typeCounts, - folders: this.folders, - collections: this.collections, - deletedCount: this.deletedCount, - }; - await this.stateService.save(ScopeStateId, this.scopeState); - } - - private async restoreState(): Promise { - this.scopeState = await this.stateService.get(ScopeStateId); - if (this.scopeState == null) { - return false; - } - - if (this.scopeState.favoriteCiphers != null) { - this.favoriteCiphers = this.scopeState.favoriteCiphers; - } - if (this.scopeState.noFolderCiphers != null) { - this.noFolderCiphers = this.scopeState.noFolderCiphers; - } - if (this.scopeState.ciphers != null) { - this.ciphers = this.scopeState.ciphers; - } - if (this.scopeState.collectionCounts != null) { - this.collectionCounts = this.scopeState.collectionCounts; - } - if (this.scopeState.folderCounts != null) { - this.folderCounts = this.scopeState.folderCounts; - } - if (this.scopeState.typeCounts != null) { - this.typeCounts = this.scopeState.typeCounts; - } - if (this.scopeState.folders != null) { - this.folders = this.scopeState.folders; - } - if (this.scopeState.collections != null) { - this.collections = this.scopeState.collections; - } - if (this.scopeState.deletedCiphers != null) { - this.deletedCount = this.scopeState.deletedCount; - } - - return true; - } + return true; + } } diff --git a/src/popup/vault/password-history.component.html b/src/popup/vault/password-history.component.html index 25da87a609..003d7bced2 100644 --- a/src/popup/vault/password-history.component.html +++ b/src/popup/vault/password-history.component.html @@ -1,34 +1,39 @@
-
- -
-

- {{'passwordHistory' | i18n}} -

-
+
+ +
+

+ {{ "passwordHistory" | i18n }} +

+
-
-
-
-
-
- - {{h.password}} - - {{h.lastUsedDate | date:'medium'}} -
-
-
- -
-
+
+
+
+
+
+ + {{ h.password }} + + {{ h.lastUsedDate | date: "medium" }} +
+
+ +
+
-
-

{{'noPasswordsInList' | i18n}}

-
+
+
+

{{ "noPasswordsInList" | i18n }}

+
diff --git a/src/popup/vault/password-history.component.ts b/src/popup/vault/password-history.component.ts index 85da89a11f..6c850ca2ea 100644 --- a/src/popup/vault/password-history.component.ts +++ b/src/popup/vault/password-history.component.ts @@ -1,40 +1,42 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { - PasswordHistoryComponent as BasePasswordHistoryComponent, -} from 'jslib-angular/components/password-history.component'; +import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "jslib-angular/components/password-history.component"; @Component({ - selector: 'app-password-history', - templateUrl: 'password-history.component.html', + selector: "app-password-history", + templateUrl: "password-history.component.html", }) export class PasswordHistoryComponent extends BasePasswordHistoryComponent { - constructor(cipherService: CipherService, platformUtilsService: PlatformUtilsService, - i18nService: I18nService, private location: Location, - private route: ActivatedRoute) { - super(cipherService, platformUtilsService, i18nService, window); - } + constructor( + cipherService: CipherService, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + private location: Location, + private route: ActivatedRoute + ) { + super(cipherService, platformUtilsService, i18nService, window); + } - async ngOnInit() { - this.route.queryParams.pipe(first()).subscribe(async params => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } else { - this.close(); - } - await this.init(); - }); - } + async ngOnInit() { + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (params.cipherId) { + this.cipherId = params.cipherId; + } else { + this.close(); + } + await this.init(); + }); + } - close() { - this.location.back(); - } + close() { + this.location.back(); + } } diff --git a/src/popup/vault/share.component.html b/src/popup/vault/share.component.html index a16dd91bc3..0324761802 100644 --- a/src/popup/vault/share.component.html +++ b/src/popup/vault/share.component.html @@ -1,56 +1,71 @@
-
-
- +
+
+ +
+

+ {{ "moveToOrganization" | i18n }} +

+
+ +
+
+ +
+
+
+ {{ "noOrganizationsList" | i18n }}
-

- {{'moveToOrganization' | i18n}} -

-
- +
+
+
+ +
-
- -
-
-
- {{'noOrganizationsList' | i18n}} -
-
-
-
- - -
-
- +
+ +
+
+

+ {{ "collections" | i18n }} +

+
+
+ {{ "noCollectionsInList" | i18n }}
-
-

- {{'collections' | i18n}} -

-
-
- {{'noCollectionsInList' | i18n}} -
-
-
-
- - -
-
+
+
+
+ +
- +
+
+ diff --git a/src/popup/vault/share.component.ts b/src/popup/vault/share.component.ts index c32804d563..29b30ea4b9 100644 --- a/src/popup/vault/share.component.ts +++ b/src/popup/vault/share.component.ts @@ -1,53 +1,65 @@ -import { Location } from '@angular/common'; -import { Component } from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Location } from "@angular/common"; +import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CollectionService } from 'jslib-common/abstractions/collection.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CollectionService } from "jslib-common/abstractions/collection.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component'; +import { ShareComponent as BaseShareComponent } from "jslib-angular/components/share.component"; @Component({ - selector: 'app-vault-share', - templateUrl: 'share.component.html', + selector: "app-vault-share", + templateUrl: "share.component.html", }) export class ShareComponent extends BaseShareComponent { - constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService, - i18nService: I18nService, userService: UserService, - cipherService: CipherService, private route: ActivatedRoute, - private router: Router, logService: LogService) { - super(collectionService, platformUtilsService, i18nService, userService, cipherService, - logService); - } + constructor( + collectionService: CollectionService, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + userService: UserService, + cipherService: CipherService, + private route: ActivatedRoute, + private router: Router, + logService: LogService + ) { + super( + collectionService, + platformUtilsService, + i18nService, + userService, + cipherService, + logService + ); + } - async ngOnInit() { - this.onSharedCipher.subscribe(() => { - this.router.navigate(['view-cipher', { cipherId: this.cipherId }]); - }); - this.route.queryParams.pipe(first()).subscribe(async params => { - this.cipherId = params.cipherId; - await this.load(); - }); - } + async ngOnInit() { + this.onSharedCipher.subscribe(() => { + this.router.navigate(["view-cipher", { cipherId: this.cipherId }]); + }); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + this.cipherId = params.cipherId; + await this.load(); + }); + } - async submit(): Promise { - const success = await super.submit(); - if (success) { - this.cancel(); - } - return success; + async submit(): Promise { + const success = await super.submit(); + if (success) { + this.cancel(); } + return success; + } - cancel() { - this.router.navigate(['/view-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } }); - } + cancel() { + this.router.navigate(["/view-cipher"], { + replaceUrl: true, + queryParams: { cipherId: this.cipher.id }, + }); + } } diff --git a/src/popup/vault/view-custom-fields.component.html b/src/popup/vault/view-custom-fields.component.html index e62e8d6bc5..731486afaa 100644 --- a/src/popup/vault/view-custom-fields.component.html +++ b/src/popup/vault/view-custom-fields.component.html @@ -1,45 +1,69 @@ -

- {{'customFields' | i18n}} -

-
-
-
- {{field.name}} -
- {{field.value || ' '}} -
-
- {{field.value}} - {{field.maskedValue}} -
-
- - - {{field.value}} -
-
-
- - {{'linkedValue' | i18n}} -
- {{cipher.linkedFieldI18nKey(field.linkedId) | i18n}} -
-
-
- - -
+

+ {{ "customFields" | i18n }} +

+
+
+
+ {{ field.name }} +
+ {{ field.value || " " }}
+
+ {{ + field.value + }} + {{ field.maskedValue }} +
+
+ + + {{ field.value }} +
+
+
+ + {{ "linkedValue" | i18n }} +
+ {{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }} +
+
+
+ + +
+
diff --git a/src/popup/vault/view-custom-fields.component.ts b/src/popup/vault/view-custom-fields.component.ts index 916a1df2eb..385dc13f26 100644 --- a/src/popup/vault/view-custom-fields.component.ts +++ b/src/popup/vault/view-custom-fields.component.ts @@ -1,19 +1,15 @@ -import { - Component, -} from '@angular/core'; +import { Component } from "@angular/core"; -import { EventService } from 'jslib-common/abstractions/event.service'; +import { EventService } from "jslib-common/abstractions/event.service"; -import { - ViewCustomFieldsComponent as BaseViewCustomFieldsComponent -} from 'jslib-angular/components/view-custom-fields.component'; +import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "jslib-angular/components/view-custom-fields.component"; @Component({ - selector: 'app-vault-view-custom-fields', - templateUrl: 'view-custom-fields.component.html', + selector: "app-vault-view-custom-fields", + templateUrl: "view-custom-fields.component.html", }) export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { - constructor(eventService: EventService) { - super(eventService); - } + constructor(eventService: EventService) { + super(eventService); + } } diff --git a/src/popup/vault/view.component.html b/src/popup/vault/view.component.html index d0d9699539..f54fa7ce96 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -1,316 +1,508 @@
-
- -
-

- {{'viewItem' | i18n}} -

-
- -
+
+ +
+

+ {{ "viewItem" | i18n }} +

+
+ +
-
-

- {{'itemInformation' | i18n}} -

-
-
- - -
- -
-
-
- - -
-
- -
-
-
-
- {{'password' | i18n}} -
- {{cipher.login.maskedPassword}}
-
-
-
- - - -
-
-
-
- {{'verificationCodeTotp' | i18n}} - {{totpCodeFormatted}} -
- - {{totpSec}} - - - - - - - -
- -
-
-
- -
-
- {{'cardholderName' | i18n}} - {{cipher.card.cardholderName}} -
-
-
- {{'number' | i18n}} - {{cipher.card.maskedNumber}} - {{cipher.card.number}} -
-
- - -
-
-
- {{'brand' | i18n}} - {{cipher.card.brand}} -
-
- {{'expiration' | i18n}} - {{cipher.card.expiration}} -
-
-
- {{'securityCode' | i18n}} - {{cipher.card.maskedCode}} - {{cipher.card.code}} -
-
- - -
-
-
- -
-
- {{'identityName' | i18n}} - {{cipher.identity.fullName}} -
-
- {{'username' | i18n}} - {{cipher.identity.username}} -
-
- {{'company' | i18n}} - {{cipher.identity.company}} -
-
- {{'ssn' | i18n}} - {{cipher.identity.ssn}} -
-
- {{'passportNumber' | i18n}} - {{cipher.identity.passportNumber}} -
-
- {{'licenseNumber' | i18n}} - {{cipher.identity.licenseNumber}} -
-
- {{'email' | i18n}} - {{cipher.identity.email}} -
-
- {{'phone' | i18n}} - {{cipher.identity.phone}} -
-
- {{'address' | i18n}} -
{{cipher.identity.address1}}
-
{{cipher.identity.address2}}
-
{{cipher.identity.address3}}
-
{{cipher.identity.fullAddressPart2}}
-
{{cipher.identity.country}}
-
-
+
+

+ {{ "itemInformation" | i18n }} +

+
+
+ + +
+ +
+
+
+ + +
+
+ +
-
-
-
-
-
- - - - - -
-
- - -
+
+
+ {{ "password" | i18n }} +
+ {{ cipher.login.maskedPassword }}
+
+
+
+ + + +
-
-
-

- -

-
-
- -
+
+
+ {{ "verificationCodeTotp" | i18n }} + {{ totpCodeFormatted }} +
+ + {{ totpSec }} + + + + + + + +
+ +
-
-
- -
-
-

- {{'attachments' | i18n}} -

-
- +
+ +
+
+ {{ "cardholderName" | i18n }} + {{ cipher.card.cardholderName }}
-
-
-
- - - - - - +
-
-
- + +
+
+ {{ "identityName" | i18n }} + {{ cipher.identity.fullName }} +
+
+ {{ "username" | i18n }} + {{ cipher.identity.username }} +
+
+ {{ "company" | i18n }} + {{ cipher.identity.company }} +
+
+ {{ "ssn" | i18n }} + {{ cipher.identity.ssn }} +
+
+ {{ "passportNumber" | i18n }} + {{ cipher.identity.passportNumber }} +
+
+ {{ "licenseNumber" | i18n }} + {{ cipher.identity.licenseNumber }} +
+
+ {{ "email" | i18n }} + {{ cipher.identity.email }} +
+
+ {{ "phone" | i18n }} + {{ cipher.identity.phone }} +
+
+ {{ "address" | i18n }} +
{{ cipher.identity.address1 }}
+
{{ cipher.identity.address2 }}
+
{{ cipher.identity.address3 }}
+
{{ cipher.identity.fullAddressPart2 }}
+
{{ cipher.identity.country }}
+
+
+
+
+
+
+
+ + + + + +
+
+ + +
+
+
+
+
+

+ +

+
+
+ +
+
+
+
+ +
+
+

+ {{ "attachments" | i18n }} +

+
+ +
+
+
+
+ + + + + + +
+
+
+ +
diff --git a/src/popup/vault/view.component.ts b/src/popup/vault/view.component.ts index fe8bfe8e57..254aac1bf3 100644 --- a/src/popup/vault/view.component.ts +++ b/src/popup/vault/view.component.ts @@ -1,269 +1,294 @@ -import { Location } from '@angular/common'; -import { - ChangeDetectorRef, - Component, - NgZone, -} from '@angular/core'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; +import { Location } from "@angular/common"; +import { ChangeDetectorRef, Component, NgZone } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; -import { first } from 'rxjs/operators'; +import { first } from "rxjs/operators"; -import { ApiService } from 'jslib-common/abstractions/api.service'; -import { AuditService } from 'jslib-common/abstractions/audit.service'; -import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; -import { CipherService } from 'jslib-common/abstractions/cipher.service'; -import { CryptoService } from 'jslib-common/abstractions/crypto.service'; -import { EventService } from 'jslib-common/abstractions/event.service'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { LogService } from 'jslib-common/abstractions/log.service'; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { TokenService } from 'jslib-common/abstractions/token.service'; -import { TotpService } from 'jslib-common/abstractions/totp.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { ApiService } from "jslib-common/abstractions/api.service"; +import { AuditService } from "jslib-common/abstractions/audit.service"; +import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { EventService } from "jslib-common/abstractions/event.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { TokenService } from "jslib-common/abstractions/token.service"; +import { TotpService } from "jslib-common/abstractions/totp.service"; +import { UserService } from "jslib-common/abstractions/user.service"; -import { Cipher } from 'jslib-common/models/domain/cipher'; -import { LoginUriView } from 'jslib-common/models/view/loginUriView'; +import { Cipher } from "jslib-common/models/domain/cipher"; +import { LoginUriView } from "jslib-common/models/view/loginUriView"; -import { CipherType } from 'jslib-common/enums/cipherType'; +import { CipherType } from "jslib-common/enums/cipherType"; -import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component'; -import { BrowserApi } from '../../browser/browserApi'; -import { AutofillService } from '../../services/abstractions/autofill.service'; -import { PopupUtilsService } from '../services/popup-utils.service'; +import { ViewComponent as BaseViewComponent } from "jslib-angular/components/view.component"; +import { BrowserApi } from "../../browser/browserApi"; +import { AutofillService } from "../../services/abstractions/autofill.service"; +import { PopupUtilsService } from "../services/popup-utils.service"; -const BroadcasterSubscriptionId = 'ChildViewComponent'; +const BroadcasterSubscriptionId = "ChildViewComponent"; @Component({ - selector: 'app-vault-view', - templateUrl: 'view.component.html', + selector: "app-vault-view", + templateUrl: "view.component.html", }) export class ViewComponent extends BaseViewComponent { - showAttachments = true; - pageDetails: any[] = []; - tab: any; - loadPageDetailsTimeout: number; - inPopout = false; - cipherType = CipherType; + showAttachments = true; + pageDetails: any[] = []; + tab: any; + loadPageDetailsTimeout: number; + inPopout = false; + cipherType = CipherType; - constructor(cipherService: CipherService, totpService: TotpService, - tokenService: TokenService, i18nService: I18nService, - cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, - auditService: AuditService, private route: ActivatedRoute, - private router: Router, private location: Location, - broadcasterService: BroadcasterService, ngZone: NgZone, - changeDetectorRef: ChangeDetectorRef, userService: UserService, - eventService: EventService, private autofillService: AutofillService, - private messagingService: MessagingService, private popupUtilsService: PopupUtilsService, - apiService: ApiService, passwordRepromptService: PasswordRepromptService, - logService: LogService) { - super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, - auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService, - apiService, passwordRepromptService, logService); - } + constructor( + cipherService: CipherService, + totpService: TotpService, + tokenService: TokenService, + i18nService: I18nService, + cryptoService: CryptoService, + platformUtilsService: PlatformUtilsService, + auditService: AuditService, + private route: ActivatedRoute, + private router: Router, + private location: Location, + broadcasterService: BroadcasterService, + ngZone: NgZone, + changeDetectorRef: ChangeDetectorRef, + userService: UserService, + eventService: EventService, + private autofillService: AutofillService, + private messagingService: MessagingService, + private popupUtilsService: PopupUtilsService, + apiService: ApiService, + passwordRepromptService: PasswordRepromptService, + logService: LogService + ) { + super( + cipherService, + totpService, + tokenService, + i18nService, + cryptoService, + platformUtilsService, + auditService, + window, + broadcasterService, + ngZone, + changeDetectorRef, + userService, + eventService, + apiService, + passwordRepromptService, + logService + ); + } - ngOnInit() { - this.inPopout = this.popupUtilsService.inPopout(window); - this.route.queryParams.pipe(first()).subscribe(async params => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } else { - this.close(); + ngOnInit() { + this.inPopout = this.popupUtilsService.inPopout(window); + this.route.queryParams.pipe(first()).subscribe(async (params) => { + if (params.cipherId) { + this.cipherId = params.cipherId; + } else { + this.close(); + } + + await this.load(); + }); + + super.ngOnInit(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case "collectPageDetailsResponse": + if (message.sender === BroadcasterSubscriptionId) { + this.pageDetails.push({ + frameId: message.webExtSender.frameId, + tab: message.tab, + details: message.details, + }); } - - await this.load(); - }); - - super.ngOnInit(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - this.ngZone.run(async () => { - switch (message.command) { - case 'collectPageDetailsResponse': - if (message.sender === BroadcasterSubscriptionId) { - this.pageDetails.push({ - frameId: message.webExtSender.frameId, - tab: message.tab, - details: message.details, - }); - } - break; - case 'tabChanged': - case 'windowChanged': - if (this.loadPageDetailsTimeout != null) { - window.clearTimeout(this.loadPageDetailsTimeout); - } - this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500); - break; - default: - break; - } - }); - }); - } - - ngOnDestroy() { - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async load() { - await super.load(); - await this.loadPageDetails(); - } - - async edit() { - if (this.cipher.isDeleted) { - return false; - } - if (!await super.edit()) { - return false; - } - - this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } }); - return true; - } - - async clone() { - if (this.cipher.isDeleted) { - return false; - } - - if (!await super.clone()) { - return false; - } - - this.router.navigate(['/clone-cipher'], { - queryParams: { - cloneMode: true, - cipherId: this.cipher.id, - }, - }); - return true; - } - - async share() { - if (!await super.share()) { - return false; - } - - if (this.cipher.organizationId == null) { - this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } }); - } - return true; - } - - async fillCipher() { - const didAutofill = await this.doAutofill(); - if (didAutofill) { - this.platformUtilsService.showToast('success', null, - this.i18nService.t('autoFillSuccess')); - } - } - - async fillCipherAndSave() { - const didAutofill = await this.doAutofill(); - - if (didAutofill) { - if (this.tab == null) { - throw new Error('No tab found.'); - } - - if (this.cipher.login.uris == null) { - this.cipher.login.uris = []; - } else { - if (this.cipher.login.uris.some(uri => uri.uri === this.tab.url)) { - this.platformUtilsService.showToast('success', null, - this.i18nService.t('autoFillSuccessAndSavedUri')); - return; - } - } - - const loginUri = new LoginUriView(); - loginUri.uri = this.tab.url; - this.cipher.login.uris.push(loginUri); - - try { - const cipher: Cipher = await this.cipherService.encrypt(this.cipher); - await this.cipherService.saveWithServer(cipher); - this.platformUtilsService.showToast('success', null, - this.i18nService.t('autoFillSuccessAndSavedUri')); - this.messagingService.send('editedCipher'); - } catch { - this.platformUtilsService.showToast('error', null, - this.i18nService.t('unexpectedError')); + break; + case "tabChanged": + case "windowChanged": + if (this.loadPageDetailsTimeout != null) { + window.clearTimeout(this.loadPageDetailsTimeout); } + this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500); + break; + default: + break; } + }); + }); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + + async load() { + await super.load(); + await this.loadPageDetails(); + } + + async edit() { + if (this.cipher.isDeleted) { + return false; + } + if (!(await super.edit())) { + return false; } - async restore() { - if (!this.cipher.isDeleted) { - return false; - } - if (await super.restore()) { - this.close(); - return true; - } - return false; + this.router.navigate(["/edit-cipher"], { queryParams: { cipherId: this.cipher.id } }); + return true; + } + + async clone() { + if (this.cipher.isDeleted) { + return false; } - async delete() { - if (await super.delete()) { - this.close(); - return true; - } - return false; + if (!(await super.clone())) { + return false; } - close() { - this.location.back(); + this.router.navigate(["/clone-cipher"], { + queryParams: { + cloneMode: true, + cipherId: this.cipher.id, + }, + }); + return true; + } + + async share() { + if (!(await super.share())) { + return false; } - private async loadPageDetails() { - this.pageDetails = []; - this.tab = await BrowserApi.getTabFromCurrentWindow(); - if (this.tab == null) { - return; + if (this.cipher.organizationId == null) { + this.router.navigate(["/share-cipher"], { + replaceUrl: true, + queryParams: { cipherId: this.cipher.id }, + }); + } + return true; + } + + async fillCipher() { + const didAutofill = await this.doAutofill(); + if (didAutofill) { + this.platformUtilsService.showToast("success", null, this.i18nService.t("autoFillSuccess")); + } + } + + async fillCipherAndSave() { + const didAutofill = await this.doAutofill(); + + if (didAutofill) { + if (this.tab == null) { + throw new Error("No tab found."); + } + + if (this.cipher.login.uris == null) { + this.cipher.login.uris = []; + } else { + if (this.cipher.login.uris.some((uri) => uri.uri === this.tab.url)) { + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("autoFillSuccessAndSavedUri") + ); + return; } - BrowserApi.tabSendMessage(this.tab, { - command: 'collectPageDetails', - tab: this.tab, - sender: BroadcasterSubscriptionId, - }); + } + + const loginUri = new LoginUriView(); + loginUri.uri = this.tab.url; + this.cipher.login.uris.push(loginUri); + + try { + const cipher: Cipher = await this.cipherService.encrypt(this.cipher); + await this.cipherService.saveWithServer(cipher); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("autoFillSuccessAndSavedUri") + ); + this.messagingService.send("editedCipher"); + } catch { + this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + } + } + } + + async restore() { + if (!this.cipher.isDeleted) { + return false; + } + if (await super.restore()) { + this.close(); + return true; + } + return false; + } + + async delete() { + if (await super.delete()) { + this.close(); + return true; + } + return false; + } + + close() { + this.location.back(); + } + + private async loadPageDetails() { + this.pageDetails = []; + this.tab = await BrowserApi.getTabFromCurrentWindow(); + if (this.tab == null) { + return; + } + BrowserApi.tabSendMessage(this.tab, { + command: "collectPageDetails", + tab: this.tab, + sender: BroadcasterSubscriptionId, + }); + } + + private async doAutofill() { + if (!(await this.promptPassword())) { + return false; } - private async doAutofill() { - if (!await this.promptPassword()) { - return false; - } - - if (this.pageDetails == null || this.pageDetails.length === 0) { - this.platformUtilsService.showToast('error', null, - this.i18nService.t('autofillError')); - return false; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - cipher: this.cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - } catch { - this.platformUtilsService.showToast('error', null, - this.i18nService.t('autofillError')); - this.changeDetectorRef.detectChanges(); - return false; - } - - return true; + if (this.pageDetails == null || this.pageDetails.length === 0) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); + return false; } + + try { + this.totpCode = await this.autofillService.doAutoFill({ + cipher: this.cipher, + pageDetails: this.pageDetails, + doc: window.document, + fillNewPassword: true, + }); + if (this.totpCode != null) { + this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); + } + } catch { + this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); + this.changeDetectorRef.detectChanges(); + return false; + } + + return true; + } } diff --git a/src/services/abstractions/autofill.service.ts b/src/services/abstractions/autofill.service.ts index f0490b41a2..68fb1b8dba 100644 --- a/src/services/abstractions/autofill.service.ts +++ b/src/services/abstractions/autofill.service.ts @@ -1,7 +1,7 @@ -import AutofillPageDetails from '../../models/autofillPageDetails'; +import AutofillPageDetails from "../../models/autofillPageDetails"; export abstract class AutofillService { - getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[]; - doAutoFill: (options: any) => Promise; - doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise; + getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => any[]; + doAutoFill: (options: any) => Promise; + doAutoFillActiveTab: (pageDetails: any, fromCommand: boolean) => Promise; } diff --git a/src/services/autofill.service.ts b/src/services/autofill.service.ts index a5c27ab808..32f820bd06 100644 --- a/src/services/autofill.service.ts +++ b/src/services/autofill.service.ts @@ -1,3 +1,4 @@ +/* tslint:disable */ import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { EventService } from 'jslib-common/abstractions/event.service'; import { LogService } from 'jslib-common/abstractions/log.service'; @@ -128,7 +129,7 @@ var IsoProvinces: { [id: string]: string; } = { alberta: 'AB', 'british columbia': 'BC', manitoba: 'MB', 'new brunswick': 'NB', 'newfoundland and labrador': 'NL', 'nova scotia': 'NS', ontario: 'ON', 'prince edward island': 'PE', quebec: 'QC', saskatchewan: 'SK', }; -/* tslint:enable */ +// /* tslint:enable */ export default class AutofillService implements AutofillServiceInterface { diff --git a/src/services/browserCrypto.service.ts b/src/services/browserCrypto.service.ts index 995847d941..22fe131c0b 100644 --- a/src/services/browserCrypto.service.ts +++ b/src/services/browserCrypto.service.ts @@ -1,14 +1,13 @@ -import { KeySuffixOptions } from 'jslib-common/abstractions/storage.service'; -import { CryptoService } from 'jslib-common/services/crypto.service'; +import { KeySuffixOptions } from "jslib-common/abstractions/storage.service"; +import { CryptoService } from "jslib-common/services/crypto.service"; export class BrowserCryptoService extends CryptoService { - protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { - if (keySuffix === 'biometric') { - await this.platformUtilService.authenticateBiometric(); - return (await this.getKey())?.keyB64; - } - - return await super.retrieveKeyFromStorage(keySuffix); + protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { + if (keySuffix === "biometric") { + await this.platformUtilService.authenticateBiometric(); + return (await this.getKey())?.keyB64; } + return await super.retrieveKeyFromStorage(keySuffix); + } } diff --git a/src/services/browserMessaging.service.ts b/src/services/browserMessaging.service.ts index b4c579d6e9..f59ac88774 100644 --- a/src/services/browserMessaging.service.ts +++ b/src/services/browserMessaging.service.ts @@ -1,8 +1,8 @@ -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; export default class BrowserMessagingService implements MessagingService { - send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - chrome.runtime.sendMessage(message); - } + send(subscriber: string, arg: any = {}) { + const message = Object.assign({}, { command: subscriber }, arg); + chrome.runtime.sendMessage(message); + } } diff --git a/src/services/browserPlatformUtils.service.spec.ts b/src/services/browserPlatformUtils.service.spec.ts index 0d7a7a2666..07dd1201d3 100644 --- a/src/services/browserPlatformUtils.service.spec.ts +++ b/src/services/browserPlatformUtils.service.spec.ts @@ -1,101 +1,106 @@ -import BrowserPlatformUtilsService from './browserPlatformUtils.service'; +import BrowserPlatformUtilsService from "./browserPlatformUtils.service"; -import { DeviceType } from 'jslib-common/enums/deviceType'; +import { DeviceType } from "jslib-common/enums/deviceType"; const platformUtilsFactory = () => new BrowserPlatformUtilsService(null, null, null, null); -describe('Browser Utils Service', () => { - describe('getBrowser', () => { - const originalUserAgent = navigator.userAgent; - const originalSafariAppExtension = (window as any).safariAppExtension; - const originalOpr = (window as any).opr; +describe("Browser Utils Service", () => { + describe("getBrowser", () => { + const originalUserAgent = navigator.userAgent; + const originalSafariAppExtension = (window as any).safariAppExtension; + const originalOpr = (window as any).opr; - // Reset the userAgent. - afterAll(() => { - Object.defineProperty(navigator, 'userAgent', { - value: originalUserAgent, - }); - Object.defineProperty(window, 'safari', { - value: originalSafariAppExtension, - }); - Object.defineProperty(window, 'opr', { - value: originalOpr, - }); - }); - - it('should detect chrome', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension); - }); - - it('should detect firefox', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0', - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); - }); - - it('should detect opera', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)', - }); - - Object.defineProperty(window, 'opr', { - configurable: true, - value: {}, - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension); - }); - - it('should detect edge', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43', - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension); - }); - - it('should detect safari', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8', - }); - - Object.defineProperty(window, 'safariAppExtension', { - configurable: true, - value: true, - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension); - - Object.defineProperty(window, 'safariAppExtension', { - configurable: true, - value: false, - }); - }); - - it('should detect vivaldi', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40', - }); - - const browserPlatformUtilsService = platformUtilsFactory(); - expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension); - }); + // Reset the userAgent. + afterAll(() => { + Object.defineProperty(navigator, "userAgent", { + value: originalUserAgent, + }); + Object.defineProperty(window, "safari", { + value: originalSafariAppExtension, + }); + Object.defineProperty(window, "opr", { + value: originalOpr, + }); }); + + it("should detect chrome", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension); + }); + + it("should detect firefox", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0", + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); + }); + + it("should detect opera", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)", + }); + + Object.defineProperty(window, "opr", { + configurable: true, + value: {}, + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension); + }); + + it("should detect edge", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43", + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension); + }); + + it("should detect safari", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8", + }); + + Object.defineProperty(window, "safariAppExtension", { + configurable: true, + value: true, + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension); + + Object.defineProperty(window, "safariAppExtension", { + configurable: true, + value: false, + }); + }); + + it("should detect vivaldi", () => { + Object.defineProperty(navigator, "userAgent", { + configurable: true, + value: + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40", + }); + + const browserPlatformUtilsService = platformUtilsFactory(); + expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension); + }); + }); }); diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 8a3a2d0cd3..31e8d32180 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -1,342 +1,377 @@ -import { BrowserApi } from '../browser/browserApi'; -import { SafariApp } from '../browser/safariApp'; +import { BrowserApi } from "../browser/browserApi"; +import { SafariApp } from "../browser/safariApp"; -import { DeviceType } from 'jslib-common/enums/deviceType'; -import { ThemeType } from 'jslib-common/enums/themeType'; +import { DeviceType } from "jslib-common/enums/deviceType"; +import { ThemeType } from "jslib-common/enums/themeType"; -import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.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"; const DialogPromiseExpiration = 600000; // 10 minutes export default class BrowserPlatformUtilsService implements PlatformUtilsService { - identityClientId: string = 'browser'; + identityClientId: string = "browser"; - private showDialogResolves = new Map void, date: Date }>(); - private passwordDialogResolves = new Map Promise, date: Date }>(); - private deviceCache: DeviceType = null; - private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)'); + private showDialogResolves = new Map void; date: Date }>(); + private passwordDialogResolves = new Map< + number, + { tryResolve: (canceled: boolean, password: string) => Promise; date: Date } + >(); + private deviceCache: DeviceType = null; + private prefersColorSchemeDark = window.matchMedia("(prefers-color-scheme: dark)"); - constructor(private messagingService: MessagingService, private storageService: StorageService, - private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, - private biometricCallback: () => Promise) { } + constructor( + private messagingService: MessagingService, + private storageService: StorageService, + private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, + private biometricCallback: () => Promise + ) {} - getDevice(): DeviceType { - if (this.deviceCache) { - return this.deviceCache; + getDevice(): DeviceType { + if (this.deviceCache) { + return this.deviceCache; + } + + if ( + navigator.userAgent.indexOf(" Firefox/") !== -1 || + navigator.userAgent.indexOf(" Gecko/") !== -1 + ) { + this.deviceCache = DeviceType.FirefoxExtension; + } else if ( + (!!(window as any).opr && !!opr.addons) || + !!(window as any).opera || + navigator.userAgent.indexOf(" OPR/") >= 0 + ) { + this.deviceCache = DeviceType.OperaExtension; + } else if (navigator.userAgent.indexOf(" Edg/") !== -1) { + this.deviceCache = DeviceType.EdgeExtension; + } else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) { + this.deviceCache = DeviceType.VivaldiExtension; + } else if ((window as any).chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) { + this.deviceCache = DeviceType.ChromeExtension; + } else if (navigator.userAgent.indexOf(" Safari/") !== -1) { + this.deviceCache = DeviceType.SafariExtension; + } + + return this.deviceCache; + } + + getDeviceString(): string { + const device = DeviceType[this.getDevice()].toLowerCase(); + return device.replace("extension", ""); + } + + isFirefox(): boolean { + return this.getDevice() === DeviceType.FirefoxExtension; + } + + isChrome(): boolean { + return this.getDevice() === DeviceType.ChromeExtension; + } + + isEdge(): boolean { + return this.getDevice() === DeviceType.EdgeExtension; + } + + isOpera(): boolean { + return this.getDevice() === DeviceType.OperaExtension; + } + + isVivaldi(): boolean { + return this.getDevice() === DeviceType.VivaldiExtension; + } + + isSafari(): boolean { + return this.getDevice() === DeviceType.SafariExtension; + } + + isIE(): boolean { + return false; + } + + isMacAppStore(): boolean { + return false; + } + + async isViewOpen(): Promise { + if (await BrowserApi.isPopupOpen()) { + return true; + } + + if (this.isSafari()) { + return false; + } + + const sidebarView = this.sidebarViewName(); + const sidebarOpen = + sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0; + if (sidebarOpen) { + return true; + } + + const tabOpen = chrome.extension.getViews({ type: "tab" }).length > 0; + return tabOpen; + } + + lockTimeout(): number { + return null; + } + + launchUri(uri: string, options?: any): void { + BrowserApi.createNewTab(uri, options && options.extensionPage === true); + } + + saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { + BrowserApi.downloadFile(win, blobData, blobOptions, fileName); + } + + getApplicationVersion(): Promise { + return Promise.resolve(BrowserApi.getApplicationVersion()); + } + + supportsWebAuthn(win: Window): boolean { + return typeof PublicKeyCredential !== "undefined"; + } + + supportsDuo(): boolean { + return true; + } + + showToast( + type: "error" | "success" | "warning" | "info", + title: string, + text: string | string[], + options?: any + ): void { + this.messagingService.send("showToast", { + text: text, + title: title, + type: type, + options: options, + }); + } + + showDialog( + body: string, + title?: string, + confirmText?: string, + cancelText?: string, + type?: string, + bodyIsHtml: boolean = false + ) { + const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + this.messagingService.send("showDialog", { + text: bodyIsHtml ? null : body, + html: bodyIsHtml ? body : null, + title: title, + confirmText: confirmText, + cancelText: cancelText, + type: type, + dialogId: dialogId, + }); + return new Promise((resolve) => { + this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() }); + }); + } + + isDev(): boolean { + return process.env.ENV === "development"; + } + + isSelfHost(): boolean { + return false; + } + + copyToClipboard(text: string, options?: any): void { + let win = window; + let doc = window.document; + if (options && (options.window || options.win)) { + win = options.window || options.win; + doc = win.document; + } else if (options && options.doc) { + doc = options.doc; + } + const clearing = options ? !!options.clearing : false; + const clearMs: number = options && options.clearMs ? options.clearMs : null; + + if (this.isSafari()) { + SafariApp.sendMessageToApp("copyToClipboard", text).then(() => { + if (!clearing && this.clipboardWriteCallback != null) { + this.clipboardWriteCallback(text, clearMs); } - - if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) { - this.deviceCache = DeviceType.FirefoxExtension; - } else if ((!!(window as any).opr && !!opr.addons) || !!(window as any).opera || - navigator.userAgent.indexOf(' OPR/') >= 0) { - this.deviceCache = DeviceType.OperaExtension; - } else if (navigator.userAgent.indexOf(' Edg/') !== -1) { - this.deviceCache = DeviceType.EdgeExtension; - } else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) { - this.deviceCache = DeviceType.VivaldiExtension; - } else if ((window as any).chrome && navigator.userAgent.indexOf(' Chrome/') !== -1) { - this.deviceCache = DeviceType.ChromeExtension; - } else if (navigator.userAgent.indexOf(' Safari/') !== -1) { - this.deviceCache = DeviceType.SafariExtension; + }); + } else if ( + this.isFirefox() && + (win as any).navigator.clipboard && + (win as any).navigator.clipboard.writeText + ) { + (win as any).navigator.clipboard.writeText(text).then(() => { + if (!clearing && this.clipboardWriteCallback != null) { + this.clipboardWriteCallback(text, clearMs); } + }); + } else if ((win as any).clipboardData && (win as any).clipboardData.setData) { + // IE specific code path to prevent textarea being shown while dialog is visible. + (win as any).clipboardData.setData("Text", text); + if (!clearing && this.clipboardWriteCallback != null) { + this.clipboardWriteCallback(text, clearMs); + } + } else if (doc.queryCommandSupported && doc.queryCommandSupported("copy")) { + if (this.isChrome() && text === "") { + text = "\u0000"; + } - return this.deviceCache; - } + const textarea = doc.createElement("textarea"); + textarea.textContent = text == null || text === "" ? " " : text; + // Prevent scrolling to bottom of page in MS Edge. + textarea.style.position = "fixed"; + doc.body.appendChild(textarea); + textarea.select(); - getDeviceString(): string { - const device = DeviceType[this.getDevice()].toLowerCase(); - return device.replace('extension', ''); - } - - isFirefox(): boolean { - return this.getDevice() === DeviceType.FirefoxExtension; - } - - isChrome(): boolean { - return this.getDevice() === DeviceType.ChromeExtension; - } - - isEdge(): boolean { - return this.getDevice() === DeviceType.EdgeExtension; - } - - isOpera(): boolean { - return this.getDevice() === DeviceType.OperaExtension; - } - - isVivaldi(): boolean { - return this.getDevice() === DeviceType.VivaldiExtension; - } - - isSafari(): boolean { - return this.getDevice() === DeviceType.SafariExtension; - } - - isIE(): boolean { - return false; - } - - isMacAppStore(): boolean { - return false; - } - - async isViewOpen(): Promise { - if (await BrowserApi.isPopupOpen()) { - return true; + try { + // Security exception may be thrown by some browsers. + if (doc.execCommand("copy") && !clearing && this.clipboardWriteCallback != null) { + this.clipboardWriteCallback(text, clearMs); } + } catch (e) { + // tslint:disable-next-line + console.warn("Copy to clipboard failed.", e); + } finally { + doc.body.removeChild(textarea); + } + } + } - if (this.isSafari()) { - return false; + async readFromClipboard(options?: any): Promise { + let win = window; + let doc = window.document; + if (options && (options.window || options.win)) { + win = options.window || options.win; + doc = win.document; + } else if (options && options.doc) { + doc = options.doc; + } + + if (this.isSafari()) { + return await SafariApp.sendMessageToApp("readFromClipboard"); + } else if ( + this.isFirefox() && + (win as any).navigator.clipboard && + (win as any).navigator.clipboard.readText + ) { + return await (win as any).navigator.clipboard.readText(); + } else if (doc.queryCommandSupported && doc.queryCommandSupported("paste")) { + const textarea = doc.createElement("textarea"); + // Prevent scrolling to bottom of page in MS Edge. + textarea.style.position = "fixed"; + doc.body.appendChild(textarea); + textarea.focus(); + try { + // Security exception may be thrown by some browsers. + if (doc.execCommand("paste")) { + return textarea.value; } + } catch (e) { + // tslint:disable-next-line + console.warn("Read from clipboard failed.", e); + } finally { + doc.body.removeChild(textarea); + } + } + return null; + } - const sidebarView = this.sidebarViewName(); - const sidebarOpen = sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0; - if (sidebarOpen) { - return true; - } - - const tabOpen = chrome.extension.getViews({ type: 'tab' }).length > 0; - return tabOpen; + resolveDialogPromise(dialogId: number, confirmed: boolean) { + if (this.showDialogResolves.has(dialogId)) { + const resolveObj = this.showDialogResolves.get(dialogId); + resolveObj.resolve(confirmed); + this.showDialogResolves.delete(dialogId); } - lockTimeout(): number { - return null; + // Clean up old promises + this.showDialogResolves.forEach((val, key) => { + const age = new Date().getTime() - val.date.getTime(); + if (age > DialogPromiseExpiration) { + this.showDialogResolves.delete(key); + } + }); + } + + async resolvePasswordDialogPromise( + dialogId: number, + canceled: boolean, + password: string + ): Promise { + let result = false; + if (this.passwordDialogResolves.has(dialogId)) { + const resolveObj = this.passwordDialogResolves.get(dialogId); + if (await resolveObj.tryResolve(canceled, password)) { + this.passwordDialogResolves.delete(dialogId); + result = true; + } } - launchUri(uri: string, options?: any): void { - BrowserApi.createNewTab(uri, options && options.extensionPage === true); + // Clean up old promises + this.passwordDialogResolves.forEach((val, key) => { + const age = new Date().getTime() - val.date.getTime(); + if (age > DialogPromiseExpiration) { + this.passwordDialogResolves.delete(key); + } + }); + + return result; + } + + async supportsBiometric() { + const platformInfo = await BrowserApi.getPlatformInfo(); + if (platformInfo.os === "android") { + return false; } - saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { - BrowserApi.downloadFile(win, blobData, blobOptions, fileName); + if (this.isFirefox()) { + return parseInt((await browser.runtime.getBrowserInfo()).version.split(".")[0], 10) >= 87; } - getApplicationVersion(): Promise { - return Promise.resolve(BrowserApi.getApplicationVersion()); + return true; + } + + authenticateBiometric() { + return this.biometricCallback(); + } + + sidebarViewName(): string { + if ((window as any).chrome.sidebarAction && this.isFirefox()) { + return "sidebar"; + } else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) { + return "sidebar_panel"; } - supportsWebAuthn(win: Window): boolean { - return (typeof(PublicKeyCredential) !== 'undefined'); - } - - supportsDuo(): boolean { - return true; - } - - showToast(type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], - options?: any): void { - this.messagingService.send('showToast', { - text: text, - title: title, - type: type, - options: options, - }); - } - - showDialog(body: string, title?: string, confirmText?: string, cancelText?: string, type?: string, - bodyIsHtml: boolean = false) { - const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - this.messagingService.send('showDialog', { - text: bodyIsHtml ? null : body, - html: bodyIsHtml ? body : null, - title: title, - confirmText: confirmText, - cancelText: cancelText, - type: type, - dialogId: dialogId, - }); - return new Promise(resolve => { - this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() }); - }); - } - - isDev(): boolean { - return process.env.ENV === 'development'; - } - - isSelfHost(): boolean { - return false; - } - - copyToClipboard(text: string, options?: any): void { - let win = window; - let doc = window.document; - if (options && (options.window || options.win)) { - win = options.window || options.win; - doc = win.document; - } else if (options && options.doc) { - doc = options.doc; - } - const clearing = options ? !!options.clearing : false; - const clearMs: number = options && options.clearMs ? options.clearMs : null; - - if (this.isSafari()) { - SafariApp.sendMessageToApp('copyToClipboard', text).then(() => { - if (!clearing && this.clipboardWriteCallback != null) { - this.clipboardWriteCallback(text, clearMs); - } - }); - } else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.writeText) { - (win as any).navigator.clipboard.writeText(text).then(() => { - if (!clearing && this.clipboardWriteCallback != null) { - this.clipboardWriteCallback(text, clearMs); - } - }); - } else if ((win as any).clipboardData && (win as any).clipboardData.setData) { - // IE specific code path to prevent textarea being shown while dialog is visible. - (win as any).clipboardData.setData('Text', text); - if (!clearing && this.clipboardWriteCallback != null) { - this.clipboardWriteCallback(text, clearMs); - } - } else if (doc.queryCommandSupported && doc.queryCommandSupported('copy')) { - if (this.isChrome() && text === '') { - text = '\u0000'; - } - - const textarea = doc.createElement('textarea'); - textarea.textContent = text == null || text === '' ? ' ' : text; - // Prevent scrolling to bottom of page in MS Edge. - textarea.style.position = 'fixed'; - doc.body.appendChild(textarea); - textarea.select(); - - try { - // Security exception may be thrown by some browsers. - if (doc.execCommand('copy') && !clearing && this.clipboardWriteCallback != null) { - this.clipboardWriteCallback(text, clearMs); - } - } catch (e) { - // tslint:disable-next-line - console.warn('Copy to clipboard failed.', e); - } finally { - doc.body.removeChild(textarea); - } - } - } - - async readFromClipboard(options?: any): Promise { - let win = window; - let doc = window.document; - if (options && (options.window || options.win)) { - win = options.window || options.win; - doc = win.document; - } else if (options && options.doc) { - doc = options.doc; - } - - if (this.isSafari()) { - return await SafariApp.sendMessageToApp('readFromClipboard'); - } else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.readText) { - return await (win as any).navigator.clipboard.readText(); - } else if (doc.queryCommandSupported && doc.queryCommandSupported('paste')) { - const textarea = doc.createElement('textarea'); - // Prevent scrolling to bottom of page in MS Edge. - textarea.style.position = 'fixed'; - doc.body.appendChild(textarea); - textarea.focus(); - try { - // Security exception may be thrown by some browsers. - if (doc.execCommand('paste')) { - return textarea.value; - } - } catch (e) { - // tslint:disable-next-line - console.warn('Read from clipboard failed.', e); - } finally { - doc.body.removeChild(textarea); - } - } - return null; - } - - resolveDialogPromise(dialogId: number, confirmed: boolean) { - if (this.showDialogResolves.has(dialogId)) { - const resolveObj = this.showDialogResolves.get(dialogId); - resolveObj.resolve(confirmed); - this.showDialogResolves.delete(dialogId); - } - - // Clean up old promises - this.showDialogResolves.forEach((val, key) => { - const age = new Date().getTime() - val.date.getTime(); - if (age > DialogPromiseExpiration) { - this.showDialogResolves.delete(key); - } - }); - } - - async resolvePasswordDialogPromise(dialogId: number, canceled: boolean, password: string): Promise { - let result = false; - if (this.passwordDialogResolves.has(dialogId)) { - const resolveObj = this.passwordDialogResolves.get(dialogId); - if (await resolveObj.tryResolve(canceled, password)) { - this.passwordDialogResolves.delete(dialogId); - result = true; - } - } - - // Clean up old promises - this.passwordDialogResolves.forEach((val, key) => { - const age = new Date().getTime() - val.date.getTime(); - if (age > DialogPromiseExpiration) { - this.passwordDialogResolves.delete(key); - } - }); - - return result; - } - - async supportsBiometric() { - const platformInfo = await BrowserApi.getPlatformInfo(); - if (platformInfo.os === 'android') { - return false; - } - - if (this.isFirefox()) { - return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87; - } - - return true; - } - - authenticateBiometric() { - return this.biometricCallback(); - } - - sidebarViewName(): string { - if ((window as any).chrome.sidebarAction && this.isFirefox()) { - return 'sidebar'; - } else if (this.isOpera() && (typeof opr !== 'undefined') && opr.sidebarAction) { - return 'sidebar_panel'; - } - - return null; - } - - supportsSecureStorage(): boolean { - return false; - } - - getDefaultSystemTheme(): Promise { - return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light); - } - - onDefaultSystemThemeChange(callback: ((theme: ThemeType.Light | ThemeType.Dark) => unknown)) { - this.prefersColorSchemeDark.addEventListener('change', ({ matches }) => { - callback(matches ? ThemeType.Dark : ThemeType.Light); - }); - } - - async getEffectiveTheme() { - const theme = await this.storageService.get(ConstantsService.themeKey); - if (theme == null || theme === ThemeType.System) { - return this.getDefaultSystemTheme(); - } else { - return theme; - } + return null; + } + + supportsSecureStorage(): boolean { + return false; + } + + getDefaultSystemTheme(): Promise { + return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light); + } + + onDefaultSystemThemeChange(callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown) { + this.prefersColorSchemeDark.addEventListener("change", ({ matches }) => { + callback(matches ? ThemeType.Dark : ThemeType.Light); + }); + } + + async getEffectiveTheme() { + const theme = await this.storageService.get(ConstantsService.themeKey); + if (theme == null || theme === ThemeType.System) { + return this.getDefaultSystemTheme(); + } else { + return theme; } + } } diff --git a/src/services/browserStorage.service.ts b/src/services/browserStorage.service.ts index 0f417ae223..455d541959 100644 --- a/src/services/browserStorage.service.ts +++ b/src/services/browserStorage.service.ts @@ -1,55 +1,55 @@ -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { StorageService } from "jslib-common/abstractions/storage.service"; export default class BrowserStorageService implements StorageService { - private chromeStorageApi: any; + private chromeStorageApi: any; - constructor() { - this.chromeStorageApi = chrome.storage.local; - } + constructor() { + this.chromeStorageApi = chrome.storage.local; + } - async get(key: string): Promise { - return new Promise(resolve => { - this.chromeStorageApi.get(key, (obj: any) => { - if (obj != null && obj[key] != null) { - resolve(obj[key] as T); - return; - } - resolve(null); - }); - }); - } - - async has(key: string): Promise { - return await this.get(key) != null; - } - - async save(key: string, obj: any): Promise { - if (obj == null) { - // Fix safari not liking null in set - return new Promise(resolve => { - this.chromeStorageApi.remove(key, () => { - resolve(); - }); - }); + async get(key: string): Promise { + return new Promise((resolve) => { + this.chromeStorageApi.get(key, (obj: any) => { + if (obj != null && obj[key] != null) { + resolve(obj[key] as T); + return; } + resolve(null); + }); + }); + } - if (obj instanceof Set) { - obj = Array.from(obj); - } + async has(key: string): Promise { + return (await this.get(key)) != null; + } - const keyedObj = { [key]: obj }; - return new Promise(resolve => { - this.chromeStorageApi.set(keyedObj, () => { - resolve(); - }); + async save(key: string, obj: any): Promise { + if (obj == null) { + // Fix safari not liking null in set + return new Promise((resolve) => { + this.chromeStorageApi.remove(key, () => { + resolve(); }); + }); } - async remove(key: string): Promise { - return new Promise(resolve => { - this.chromeStorageApi.remove(key, () => { - resolve(); - }); - }); + if (obj instanceof Set) { + obj = Array.from(obj); } + + const keyedObj = { [key]: obj }; + return new Promise((resolve) => { + this.chromeStorageApi.set(keyedObj, () => { + resolve(); + }); + }); + } + + async remove(key: string): Promise { + return new Promise((resolve) => { + this.chromeStorageApi.remove(key, () => { + resolve(); + }); + }); + } } diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts index 8bb867f570..d2d9cfb2e8 100644 --- a/src/services/i18n.service.ts +++ b/src/services/i18n.service.ts @@ -1,45 +1,85 @@ -import { I18nService as BaseI18nService } from 'jslib-common/services/i18n.service'; +import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service"; export default class I18nService extends BaseI18nService { - constructor(systemLanguage: string) { - super(systemLanguage, null, async (formattedLocale: string) => { - // Deprecated - const file = await fetch(this.localesDirectory + formattedLocale + '/messages.json'); - return await file.json(); - }); + constructor(systemLanguage: string) { + super(systemLanguage, null, async (formattedLocale: string) => { + // Deprecated + const file = await fetch(this.localesDirectory + formattedLocale + "/messages.json"); + return await file.json(); + }); - // Please leave 'en' where it is, as it's our fallback language in case no translation can be found - this.supportedTranslationLocales = [ - 'en', 'az', 'be', 'bg', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en-GB', 'en-IN', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hr', 'hu', - 'id', 'it', 'ja', 'kn', 'ko', 'lv', 'ml', 'nb', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sv', 'th', 'tr', 'uk', - 'vi', 'zh-CN', 'zh-TW', - ]; + // Please leave 'en' where it is, as it's our fallback language in case no translation can be found + this.supportedTranslationLocales = [ + "en", + "az", + "be", + "bg", + "bn", + "ca", + "cs", + "da", + "de", + "el", + "en-GB", + "en-IN", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hr", + "hu", + "id", + "it", + "ja", + "kn", + "ko", + "lv", + "ml", + "nb", + "nl", + "pl", + "pt-BR", + "pt-PT", + "ro", + "ru", + "sk", + "sr", + "sv", + "th", + "tr", + "uk", + "vi", + "zh-CN", + "zh-TW", + ]; + } + + t(id: string, p1?: string, p2?: string, p3?: string): string { + return this.translate(id, p1, p2, p3); + } + + translate(id: string, p1?: string, p2?: string, p3?: string): string { + if (this.localesDirectory == null) { + const placeholders: string[] = []; + if (p1 != null) { + placeholders.push(p1); + } + if (p2 != null) { + placeholders.push(p2); + } + if (p3 != null) { + placeholders.push(p3); + } + + if (placeholders.length) { + return chrome.i18n.getMessage(id, placeholders); + } else { + return chrome.i18n.getMessage(id); + } } - t(id: string, p1?: string, p2?: string, p3?: string): string { - return this.translate(id, p1, p2, p3); - } - - translate(id: string, p1?: string, p2?: string, p3?: string): string { - if (this.localesDirectory == null) { - const placeholders: string[] = []; - if (p1 != null) { - placeholders.push(p1); - } - if (p2 != null) { - placeholders.push(p2); - } - if (p3 != null) { - placeholders.push(p3); - } - - if (placeholders.length) { - return chrome.i18n.getMessage(id, placeholders); - } else { - return chrome.i18n.getMessage(id); - } - } - - return super.translate(id, p1, p2, p3); - } + return super.translate(id, p1, p2, p3); + } } diff --git a/src/services/vaultTimeout.service.ts b/src/services/vaultTimeout.service.ts index cbdb0a6f50..af1dc47109 100644 --- a/src/services/vaultTimeout.service.ts +++ b/src/services/vaultTimeout.service.ts @@ -1,29 +1,28 @@ -import { VaultTimeoutService as BaseVaultTimeoutService } from 'jslib-common/services/vaultTimeout.service'; -import { SafariApp } from '../browser/safariApp'; +import { VaultTimeoutService as BaseVaultTimeoutService } from "jslib-common/services/vaultTimeout.service"; +import { SafariApp } from "../browser/safariApp"; export default class VaultTimeoutService extends BaseVaultTimeoutService { + startCheck() { + this.checkVaultTimeout(); + if (this.platformUtilsService.isSafari()) { + this.checkSafari(); + } else { + setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds + } + } - startCheck() { + // This is a work-around to safari adding an arbitary delay to setTimeout and + // setIntervals. It works by calling the native extension which sleeps for 10s, + // efficiently replicating setInterval. + async checkSafari() { + while (true) { + try { + await SafariApp.sendMessageToApp("sleep"); this.checkVaultTimeout(); - if (this.platformUtilsService.isSafari()) { - this.checkSafari(); - } else { - setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds - } - } - - // This is a work-around to safari adding an arbitary delay to setTimeout and - // setIntervals. It works by calling the native extension which sleeps for 10s, - // efficiently replicating setInterval. - async checkSafari() { - while (true) { - try { - await SafariApp.sendMessageToApp('sleep'); - this.checkVaultTimeout(); - } catch (e) { - // tslint:disable-next-line - console.log('Exception Safari VaultTimeout', e); - } - } + } catch (e) { + // tslint:disable-next-line + console.log("Exception Safari VaultTimeout", e); + } } + } } diff --git a/tsconfig.json b/tsconfig.json index 73fc376634..230ec632db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,18 +10,12 @@ "sourceMap": true, "baseUrl": ".", "paths": { - "jslib-common/*": [ - "jslib/common/src/*" - ], - "jslib-angular/*": [ - "jslib/angular/src/*" - ] + "jslib-common/*": ["jslib/common/src/*"], + "jslib-angular/*": ["jslib/angular/src/*"] } }, "angularCompilerOptions": { "preserveWhitespaces": true }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/tslint.json b/tslint.json index f23a829a46..52ae50308c 100644 --- a/tslint.json +++ b/tslint.json @@ -1,17 +1,17 @@ { "extends": "tslint:recommended", "rules": { - "align": [ true, "statements", "members" ], + "align": [true, "statements", "members"], "ban-types": { "options": [ - [ "Object", "Avoid using the `Object` type. Did you mean `object`?" ], - [ "Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?" ], - [ "Number", "Avoid using the `Number` type. Did you mean `number`?" ], - [ "String", "Avoid using the `String` type. Did you mean `string`?" ], - [ "Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?" ] + ["Object", "Avoid using the `Object` type. Did you mean `object`?"], + ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], + ["Number", "Avoid using the `Number` type. Did you mean `number`?"], + ["String", "Avoid using the `String` type. Did you mean `string`?"], + ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"] ] }, - "member-access": [ true, "no-public" ], + "member-access": [true, "no-public"], "member-ordering": [ true, { @@ -34,11 +34,10 @@ ] } ], - "no-empty": [ true ], + "no-empty": [true], "object-literal-sort-keys": false, - "object-literal-shorthand": [ true, "never" ], + "object-literal-shorthand": [true, "never"], "prefer-for-of": false, - "quotemark": [ true, "single" ], "whitespace": [ true, "check-branch", @@ -51,7 +50,7 @@ ], "max-classes-per-file": false, "ordered-imports": true, - "arrow-parens": [ true ], + "arrow-parens": [true], "trailing-comma": [ true, { diff --git a/webpack.config.js b/webpack.config.js index 04127562a1..3c311a2743 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,181 +1,184 @@ -const path = require('path'); -const webpack = require('webpack'); -const { CleanWebpackPlugin } = require('clean-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const { AngularWebpackPlugin } = require('@ngtools/webpack'); +const path = require("path"); +const webpack = require("webpack"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const { AngularWebpackPlugin } = require("@ngtools/webpack"); if (process.env.NODE_ENV == null) { - process.env.NODE_ENV = 'development'; + process.env.NODE_ENV = "development"; } -const ENV = process.env.ENV = process.env.NODE_ENV; +const ENV = (process.env.ENV = process.env.NODE_ENV); const moduleRules = [ - { - test: /\.ts$/, - enforce: 'pre', - loader: 'tslint-loader', + { + test: /\.ts$/, + enforce: "pre", + loader: "tslint-loader", + }, + { + test: /\.(html)$/, + loader: "html-loader", + }, + { + test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + exclude: /loading.svg/, + generator: { + filename: "popup/fonts/[name].[ext]", }, - { - test: /\.(html)$/, - loader: 'html-loader', + type: "asset/resource", + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + exclude: /.*(fontawesome-webfont|glyphicons-halflings-regular)\.svg/, + generator: { + filename: "popup/images/[name].[ext]", }, - { - test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, - exclude: /loading.svg/, - generator: { - filename: 'popup/fonts/[name].[ext]', - }, - type: 'asset/resource', - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - exclude: /.*(fontawesome-webfont|glyphicons-halflings-regular)\.svg/, - generator: { - filename: 'popup/images/[name].[ext]', - }, - type: 'asset/resource', - }, - { - test: /\.scss$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - }, - 'css-loader', - 'sass-loader', - ], - }, - // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 - { - test: /[\/\\]@angular[\/\\].+\.js$/, - parser: { system: true }, - }, - { - test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, - loader: '@ngtools/webpack', - } + type: "asset/resource", + }, + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + "css-loader", + "sass-loader", + ], + }, + // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 + { + test: /[\/\\]@angular[\/\\].+\.js$/, + parser: { system: true }, + }, + { + test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, + loader: "@ngtools/webpack", + }, ]; const plugins = [ - new HtmlWebpackPlugin({ - template: './src/popup/index.html', - filename: 'popup/index.html', - chunks: ['popup/polyfills', 'popup/vendor-angular', 'popup/vendor', 'popup/main'], - }), - new HtmlWebpackPlugin({ - template: './src/background.html', - filename: 'background.html', - chunks: ['vendor', 'background'], - }), - new HtmlWebpackPlugin({ - template: './src/notification/bar.html', - filename: 'notification/bar.html', - chunks: ['notification/bar'], - }), - new CopyWebpackPlugin({ - patterns: [ - './src/manifest.json', - { from: './src/_locales', to: '_locales' }, - { from: './src/images', to: 'images' }, - { from: './src/popup/images', to: 'popup/images' }, - { from: './src/content/autofill.css', to: 'content' }, - ] - }), - new webpack.SourceMapDevToolPlugin({ - include: ['popup/main.js', 'background.js'], - }), - new MiniCssExtractPlugin({ - filename: '[name].css', - chunkFilename: 'chunk-[id].css', - }), - new webpack.DefinePlugin({ - 'process.env': { - 'ENV': JSON.stringify(ENV) - } - }), - new AngularWebpackPlugin({ - tsConfigPath: 'tsconfig.json', - entryModule: 'src/popup/app.module#AppModule', - sourceMap: true, - }), - new CleanWebpackPlugin({ - cleanAfterEveryBuildPatterns: ['!popup/fonts/**/*'], - }), - new webpack.ProvidePlugin({ - process: 'process/browser', - }), + new HtmlWebpackPlugin({ + template: "./src/popup/index.html", + filename: "popup/index.html", + chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], + }), + new HtmlWebpackPlugin({ + template: "./src/background.html", + filename: "background.html", + chunks: ["vendor", "background"], + }), + new HtmlWebpackPlugin({ + template: "./src/notification/bar.html", + filename: "notification/bar.html", + chunks: ["notification/bar"], + }), + new CopyWebpackPlugin({ + patterns: [ + "./src/manifest.json", + { from: "./src/_locales", to: "_locales" }, + { from: "./src/images", to: "images" }, + { from: "./src/popup/images", to: "popup/images" }, + { from: "./src/content/autofill.css", to: "content" }, + ], + }), + new webpack.SourceMapDevToolPlugin({ + include: ["popup/main.js", "background.js"], + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "chunk-[id].css", + }), + new webpack.DefinePlugin({ + "process.env": { + ENV: JSON.stringify(ENV), + }, + }), + new AngularWebpackPlugin({ + tsConfigPath: "tsconfig.json", + entryModule: "src/popup/app.module#AppModule", + sourceMap: true, + }), + new CleanWebpackPlugin({ + cleanAfterEveryBuildPatterns: ["!popup/fonts/**/*"], + }), + new webpack.ProvidePlugin({ + process: "process/browser", + }), ]; - const config = { - mode: ENV, - devtool: false, - entry: { - 'popup/polyfills': './src/popup/polyfills.ts', - 'popup/main': './src/popup/main.ts', - 'background': './src/background.ts', - 'content/autofill': './src/content/autofill.js', - 'content/autofiller': './src/content/autofiller.ts', - 'content/notificationBar': './src/content/notificationBar.ts', - 'content/contextMenuHandler': './src/content/contextMenuHandler.ts', - 'content/shortcuts': './src/content/shortcuts.ts', - 'content/message_handler': './src/content/message_handler.ts', - 'notification/bar': './src/notification/bar.js', - }, - optimization: { - minimize: false, - splitChunks: { - cacheGroups: { - commons: { - test(module, chunks) { - return module.resource != null && - module.resource.includes(`${path.sep}node_modules${path.sep}`) && - !module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`); - }, - name: 'popup/vendor', - chunks: (chunk) => { - return chunk.name === 'popup/main'; - }, - }, - angular: { - test(module, chunks) { - return module.resource != null && - module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`); - }, - name: 'popup/vendor-angular', - chunks: (chunk) => { - return chunk.name === 'popup/main'; - }, - }, - commons2: { - test: /[\\/]node_modules[\\/]/, - name: 'vendor', - chunks: (chunk) => { - return chunk.name === 'background'; - }, - }, - }, + mode: ENV, + devtool: false, + entry: { + "popup/polyfills": "./src/popup/polyfills.ts", + "popup/main": "./src/popup/main.ts", + background: "./src/background.ts", + "content/autofill": "./src/content/autofill.js", + "content/autofiller": "./src/content/autofiller.ts", + "content/notificationBar": "./src/content/notificationBar.ts", + "content/contextMenuHandler": "./src/content/contextMenuHandler.ts", + "content/shortcuts": "./src/content/shortcuts.ts", + "content/message_handler": "./src/content/message_handler.ts", + "notification/bar": "./src/notification/bar.js", + }, + optimization: { + minimize: false, + splitChunks: { + cacheGroups: { + commons: { + test(module, chunks) { + return ( + module.resource != null && + module.resource.includes(`${path.sep}node_modules${path.sep}`) && + !module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`) + ); + }, + name: "popup/vendor", + chunks: (chunk) => { + return chunk.name === "popup/main"; + }, }, - }, - resolve: { - extensions: ['.ts', '.js'], - symlinks: false, - modules: [path.resolve('node_modules')], - fallback: { - 'assert': false, - 'buffer': require.resolve('buffer/'), - 'util': require.resolve('util/'), - 'url': require.resolve('url/'), + angular: { + test(module, chunks) { + return ( + module.resource != null && + module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`) + ); + }, + name: "popup/vendor-angular", + chunks: (chunk) => { + return chunk.name === "popup/main"; + }, }, + commons2: { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + chunks: (chunk) => { + return chunk.name === "background"; + }, + }, + }, }, - output: { - filename: '[name].js', - path: path.resolve(__dirname, 'build'), + }, + resolve: { + extensions: [".ts", ".js"], + symlinks: false, + modules: [path.resolve("node_modules")], + fallback: { + assert: false, + buffer: require.resolve("buffer/"), + util: require.resolve("util/"), + url: require.resolve("url/"), }, - module: { rules: moduleRules }, - plugins: plugins, + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + }, + module: { rules: moduleRules }, + plugins: plugins, }; module.exports = config;