├── .babelrc ├── .dir-locals.el ├── .eslintignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .pyup.yml ├── .travis.yml ├── LICENSE ├── README.md ├── dir-list-webpack-plugin.js ├── docs ├── CODEOWNERS ├── README.md ├── SECURITY.md ├── code_of_conduct.md ├── contributing.md ├── css │ └── extra.css ├── developer │ ├── directory-structure.md │ ├── install.md │ ├── releases.md │ ├── test-plan-accessibility.md │ ├── test-plan-telemetry.md │ └── test-plan.md ├── faqs.md ├── images │ ├── tour-01.welcome.png │ ├── tour-02.create-entry.png │ ├── tour-03.doorhanger-search.png │ └── tour-04.signup-fxa.png ├── index.md ├── metrics.md ├── release-notes.md └── user-guide.md ├── jpm-prefs.json ├── json-webpack-plugin.js ├── karma.conf.js ├── mkdocs.yml ├── mocha-webpack.opts ├── package-lock.json ├── package.json ├── requirements ├── flake8.txt └── tests.txt ├── setup.cfg ├── src ├── bootstrap.js ├── icon.png ├── install.rdf.ejs ├── template.ejs └── webextension │ ├── .dir-locals.el │ ├── background │ ├── README.md │ ├── accounts │ │ ├── configs.json │ │ └── index.js │ ├── browser-action.js │ ├── datastore.js │ ├── index.js │ ├── message-ports.js │ ├── telemetry.js │ └── views.js │ ├── common.js │ ├── firstrun │ ├── components │ │ ├── app.css │ │ ├── app.js │ │ ├── intro.css │ │ ├── intro.js │ │ ├── using.css │ │ └── using.js │ └── index.js │ ├── fonts │ └── fira-mono-regular.woff │ ├── icons │ ├── account.svg │ ├── arrowhead-left-16.svg │ ├── chevron-right.svg │ ├── clear.svg │ ├── default-avatar.svg │ ├── external-link.svg │ ├── hide.svg │ ├── lb_locked.svg │ ├── lb_unlocked.svg │ ├── options.svg │ ├── search.svg │ ├── show.svg │ └── signout.svg │ ├── images │ ├── intro-step-1.png │ ├── intro-step-2.png │ ├── intro-step-3.png │ └── nessie_v2.svg │ ├── l10n.js │ ├── list │ ├── README.md │ ├── actions.js │ ├── common.js │ ├── components │ │ ├── item-fields.css │ │ ├── item-fields.js │ │ ├── item-list.css │ │ ├── item-list.js │ │ ├── item-summary.css │ │ └── item-summary.js │ ├── containers │ │ └── item-filter.js │ ├── filter.js │ ├── manage │ │ ├── components │ │ │ ├── account-linked.css │ │ │ ├── account-linked.js │ │ │ ├── account-summary.css │ │ │ ├── account-summary.js │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ ├── edit-item-details.js │ │ │ ├── home-breadcrumbs.js │ │ │ ├── homepage.css │ │ │ ├── homepage.js │ │ │ ├── intro-page.css │ │ │ ├── intro-page.js │ │ │ ├── item-details.css │ │ │ ├── item-details.js │ │ │ ├── item-list-panel.css │ │ │ ├── item-list-panel.js │ │ │ ├── link-account.css │ │ │ └── link-account.js │ │ ├── containers │ │ │ ├── account-details.js │ │ │ ├── add-item.css │ │ │ ├── add-item.js │ │ │ ├── all-items.js │ │ │ ├── current-account-summary.js │ │ │ ├── current-breadcrumbs.js │ │ │ ├── current-selection.js │ │ │ ├── modals.js │ │ │ ├── open-faq.css │ │ │ ├── open-faq.js │ │ │ ├── send-feedback.css │ │ │ └── send-feedback.js │ │ ├── index.js │ │ ├── reducers.js │ │ └── telemetry.js │ ├── message-ports.js │ ├── popup │ │ ├── components │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ ├── item-details-panel.css │ │ │ ├── item-details-panel.js │ │ │ └── item-list-panel.js │ │ ├── containers │ │ │ ├── all-items-panel.js │ │ │ └── current-selection.js │ │ ├── index.js │ │ ├── reducers.js │ │ └── telemetry.js │ └── reducers.js │ ├── locales │ ├── README.md │ ├── en-US │ │ ├── common.ftl │ │ ├── firstrun.ftl │ │ ├── list.ftl │ │ ├── settings.ftl │ │ ├── unlock.ftl │ │ └── widgets.ftl │ ├── fr-FR │ │ ├── common.ftl │ │ ├── firstrun.ftl │ │ ├── list.ftl │ │ ├── settings.ftl │ │ ├── unlock.ftl │ │ └── widgets.ftl │ └── locales.json │ ├── manifest.json.tpl │ ├── settings │ ├── actions.js │ ├── components │ │ ├── app.css │ │ ├── app.js │ │ └── local-reset.js │ ├── containers │ │ └── modals.js │ ├── index.js │ └── reducers.js │ ├── telemetry.js │ ├── unlock │ ├── components │ │ ├── app.css │ │ └── app.js │ └── index.js │ └── widgets │ ├── README.md │ ├── breadcrumbs.css │ ├── breadcrumbs.js │ ├── button.css │ ├── button.js │ ├── copy-to-clipboard-button.css │ ├── copy-to-clipboard-button.js │ ├── dialog-box.css │ ├── dialog-box.js │ ├── field-text.js │ ├── filter-input.js │ ├── input.css │ ├── input.js │ ├── label-text.js │ ├── link.css │ ├── link.js │ ├── modal-root.css │ ├── modal-root.js │ ├── panel.css │ ├── panel.js │ ├── password-input.js │ ├── scrolling-list.css │ ├── scrolling-list.js │ ├── stack.css │ ├── stack.js │ ├── text-area.css │ ├── text-area.js │ ├── toolbar.css │ └── toolbar.js ├── stylelint.config.js ├── test ├── .dir-locals.el ├── .eslintrc.json ├── integration │ ├── README.md │ ├── conftest.py │ ├── pages │ │ ├── __init__.py │ │ ├── base.py │ │ ├── door_hanger.py │ │ ├── home.py │ │ ├── login.py │ │ └── util │ │ │ ├── __init__.py │ │ │ └── util.py │ └── tests │ │ ├── __init__.py │ │ ├── test_accessibility.py │ │ ├── test_account_creation.py │ │ ├── test_doorhanger.py │ │ └── test_guest_mode.py └── unit │ ├── background │ ├── accounts-test.js │ ├── browser-action-test.js │ ├── message-ports-test.js │ ├── telemetry-test.js │ └── views-test.js │ ├── bootstrap-test.js │ ├── chai-focus.js │ ├── common.js │ ├── enzyme.js │ ├── firstrun │ └── components │ │ ├── app-test.js │ │ ├── intro-test.js │ │ └── using-test.js │ ├── index.js │ ├── l10n-test.js │ ├── list │ ├── actions-test.js │ ├── components │ │ ├── item-list-test.js │ │ └── item-summary-test.js │ ├── containers │ │ └── item-filter-test.js │ ├── filter-test.js │ ├── manage │ │ ├── components │ │ │ ├── account-linked-test.js │ │ │ ├── account-summary-test.js │ │ │ ├── app-test.js │ │ │ ├── edit-item-details-test.js │ │ │ ├── home-breadcrumbs-test.js │ │ │ ├── homepage-test.js │ │ │ ├── item-details-test.js │ │ │ ├── item-list-panel-test.js │ │ │ └── link-account-test.js │ │ ├── containers │ │ │ ├── account-details-test.js │ │ │ ├── add-item-test.js │ │ │ ├── all-items-test.js │ │ │ ├── current-account-summary-test.js │ │ │ ├── current-breadcrumbs-test.js │ │ │ ├── current-selection-test.js │ │ │ ├── modals-test.js │ │ │ ├── open-faq-test.js │ │ │ └── send-feedback-test.js │ │ ├── mock-redux-state.js │ │ └── reducers-test.js │ ├── message-ports-test.js │ ├── popup │ │ ├── components │ │ │ ├── app-test.js │ │ │ ├── item-details-panel-test.js │ │ │ └── item-list-panel-test.js │ │ ├── containers │ │ │ ├── all-items-panel-test.js │ │ │ └── current-selection-test.js │ │ └── mock-redux-state.js │ └── reducers-test.js │ ├── mocks │ ├── browser.js │ ├── l10n.js │ └── xpcom.js │ ├── settings │ ├── actions-test.js │ ├── components │ │ ├── app-test.js │ │ └── local-reset-test.js │ ├── containers │ │ └── modals-test.js │ ├── mock-redux-state.js │ └── reducers-test.js │ ├── telemetry-test.js │ ├── unlock │ └── components │ │ └── app-test.js │ └── widgets │ ├── breadcrumbs-test.js │ ├── button-test.js │ ├── copy-to-clipboard-button-test.js │ ├── dialog-box-test.js │ ├── field-text-test.js │ ├── filter-input-test.js │ ├── input-test.js │ ├── label-text-test.js │ ├── link-test.js │ ├── panel-test.js │ ├── password-input-test.js │ ├── scrolling-list-test.js │ ├── stack-test.js │ ├── text-area-test.js │ └── toolbar-test.js ├── tox.ini ├── webpack.config.babel.js └── webpack.config.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | ["@babel/preset-env", { 5 | "targets": {"browsers": ["firefox >= 56"]}, 6 | "useBuiltins": true, 7 | "modules": false 8 | }] 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-syntax-object-rest-spread", 12 | "@babel/plugin-syntax-async-generators" 13 | ], 14 | "env": { 15 | "production": { 16 | "presets": [ 17 | "minify" 18 | ] 19 | }, 20 | "test": { 21 | "plugins": [ 22 | "istanbul", 23 | "rewire" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((markdown-mode 2 | (mode . visual-line))) 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | coverage 3 | dist 4 | node_modules 5 | site 6 | temp 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | let extraPlugins = []; 2 | if (process.env.STRICT_LINT !== "1") { 3 | extraPlugins.push("only-warn"); 4 | } 5 | 6 | module.exports = { 7 | "parserOptions": { 8 | "ecmaVersion": 2017, 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "jsx": true, 12 | "experimentalObjectRestSpread": true, 13 | }, 14 | }, 15 | "extends": [ 16 | "eslint:recommended", 17 | "plugin:mozilla/recommended", 18 | "plugin:react/recommended", 19 | ], 20 | "env": { 21 | "browser": true, 22 | "node": true, 23 | "webextensions": true, 24 | "es6": true, 25 | }, 26 | "plugins": [ 27 | "mozilla", 28 | "react", 29 | ...extraPlugins, 30 | ], 31 | "rules": { 32 | "comma-dangle": ["error", "always-multiline"], 33 | "curly": "error", 34 | "no-console": "warn", 35 | "semi": "error", 36 | "mozilla/no-define-cc-etc": "off", 37 | "mozilla/use-chromeutils-import": "off", 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | 5 | --- 6 | 7 | *Please note: this project is not currently or actively planning to fix non-critical (data loss, security related) bugs. This is an experimental prototype.* 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - OS: [e.g. iOS] 27 | - Browser version [e.g. 62, Nightly 63.a.01) 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | *Please note: your idea may have already been considered and you may find it in an issue by searching this repository. Also, this project is not currently or actively planning to implement new features as it is an experimental prototype.* 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /coverage/ 3 | /dist/ 4 | /docs/api.md 5 | /node_modules/ 6 | /site/ 7 | /temp/ 8 | /*.xpi 9 | /addon.env 10 | .DS_Store 11 | .cache 12 | .tox 13 | *.log 14 | *.pyc 15 | .pytest_cache 16 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # see https://pyup.io/docs/configuration/ for all available options 2 | 3 | schedule: every week 4 | -------------------------------------------------------------------------------- /docs/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # lockbox-extension code owners 2 | # https://help.github.com/articles/about-codeowners/ 3 | 4 | # integration tests and related config are maintained by PI 5 | /requirements/ @mozilla-lockbox/product-integrity 6 | /test/integration/ @mozilla-lockbox/product-integrity 7 | /.pyup.ini @mozilla-lockbox/product-integrity 8 | /setup.cfg @mozilla-lockbox/product-integrity 9 | /tox.ini @mozilla-lockbox/product-integrity 10 | 11 | # test plans are maintained by PI 12 | /docs/developer/test-plan* @mozilla-lockbox/product-integrity 13 | 14 | # metrics docs are maintained by Leif 15 | /docs/metrics.md @irrationalagent 16 | 17 | # unit tests and related config are maintained by engineering 18 | /test/unit/ @mozilla-lockbox/desktop-engineering 19 | /test/ @mozilla-lockbox/desktop-engineering 20 | 21 | # source code and all other docs are maintained by engineering 22 | /src/ @mozilla-lockbox/desktop-engineering 23 | /docs/ @mozilla-lockbox/desktop-engineering 24 | 25 | # release notes are maintained by the product owners 26 | /docs/release-notes.md @mozilla-lockbox/product 27 | 28 | # include Flod for l10n string changes 29 | # @todo uncomment below after initial string freeze 30 | #src/webextension/locales/en-US/*.ftl @flodolo 31 | 32 | # otherwise, everything is to be reviewed by the desktop team 33 | * @mozilla-lockbox/desktop-engineering 34 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Lockbox's extension documentation! 2 | 3 | You can read this documentation [online][online-docs-link] or by browsing the 4 | [`docs` directory on GitHub][repo-docs-link]. 5 | 6 | [online-docs-link]: https://mozilla-lockbox.github.io/lockbox-extension/ 7 | [repo-docs-link]: https://github.com/mozilla-lockbox/lockbox-extension/tree/master/docs 8 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Mozilla Security # 2 | 3 | - Mozilla cares about privacy and security. For more information please see: https://www.mozilla.org/security/ 4 | 5 | - If you believe that you've found a security vulnerability, please report it by sending email to the addresses: security@mozilla.org and lockbox-dev@mozilla.com 6 | -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details please see the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/) and [Developer Etiquette Guidelines](https://bugzilla.mozilla.org/page.cgi?id=etiquette.html). 4 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | h1 { 6 | margin-bottom: 1em; 7 | } 8 | 9 | a.button-link { 10 | display: block; 11 | margin: .5em auto; 12 | padding: 0.5em; 13 | width: 12em; 14 | color: white; 15 | background-color: #717171; 16 | border: 1px solid #717171; 17 | border-radius: 4px; 18 | text-align: center; 19 | } 20 | 21 | div.right { 22 | float: right; 23 | clear: right; 24 | width: 40%; 25 | padding-left: 2em; 26 | } 27 | 28 | .rst-content .admonition-title::before { 29 | display: none !important; 30 | } 31 | -------------------------------------------------------------------------------- /docs/developer/test-plan-telemetry.md: -------------------------------------------------------------------------------- 1 | # Telemetry Test Plan: Lockbox-extension 2 | 3 | Given the importance the Lockbox team places on actionable user telemetry, ensuring confidence in the fidelity of that data is vital. Coverage aspects are broken into two distinct areas: test automation and manual testing. 4 | 5 | ## Unit tests 6 | When a new feature is developed or an additional user action needs to be recorded, the developer implementing the new telemetry event will add a unit test to ensure the action creates a ping. 7 | 8 | ## Manual tests 9 | ### New telemetry events 10 | Once a new telemetry event is added to a feature and that event has a unit test, the telemetry ping will be manually inspected by either Leif Oines, Product Data Scientist or Product Integrity. The goal of this exercise it to ensure the new event and accompanying unit test are capturing the correct user behavior. 11 | 12 | ### Regression testing 13 | A small handful of smoke tests will manually be run prior to each release. Note, the team will rely on the underlying unit tests for comprehensive testing of event verification. 14 | 15 | Product Integrity will investigate options for automating these tests into the integration test suite. 16 | 17 | ## Metrics being gathered 18 | 19 | For a comprehensive list of metrics being gathered review the [Lockbox Telemetry Plan](https://github.com/mozilla-lockbox/lockbox-extension/blob/telemetry_leif/docs/metrics.md#list-of-events-currently-recorded). -------------------------------------------------------------------------------- /docs/images/tour-01.welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-01.welcome.png -------------------------------------------------------------------------------- /docs/images/tour-02.create-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-02.create-entry.png -------------------------------------------------------------------------------- /docs/images/tour-03.doorhanger-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-03.doorhanger-search.png -------------------------------------------------------------------------------- /docs/images/tour-04.signup-fxa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-04.signup-fxa.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Lockbox for desktop 2 | 3 | !!! right "Install the extension" 4 | [Install Lockbox][install-link]{: .button-link } 5 | Have questions about how Lockbox works? [Check out the FAQs][faq-link] 6 | 7 | !!! right "Contribute" 8 | You can also contribute by: 9 | 10 | - Developing code 11 | - Reporting bugs 12 | 13 | [Learn how to get started][contribute-link] 14 | 15 | The Lockbox extension is a simple, stand-alone password manager that works 16 | with Firefox for desktop. It’s the first of several planned experiments 17 | designed to help us test and improve password management and online 18 | security. 19 | 20 | **Lockbox is an experimental prototype and is not currently or actively planning to fix non-critical (data loss, security related) bugs or implement new features.** 21 | 22 | Install it and sign in with your Firefox Account to encrypt your data with 23 | tamper-resistant block cipher technology. Then [share feedback 24 | here][feedback-link]. 25 | 26 | ## Get Started 27 | 28 | 1. Install Lockbox, and it will automatically disable Firefox’s password manager. 29 | ![install lockbox](./images/tour-01.welcome.png) 30 | 31 | 2. Create an entry with a website name, URL, username, and password. 32 | ![create an entry](./images/tour-02.create-entry.png) 33 | 34 | 3. Search or browse in the toolbar menu or on the full tab to find the password you need. 35 | ![search from doorhanger](./images/tour-03.doorhanger-search.png) 36 | 37 | 4. Sign up or sign in with a Firefox Account to encrypt your entries. 38 | ![sinup for fxa](./images/tour-04.signup-fxa.png) 39 | 40 | _This is just one component of the Lockbox product. Please see the [Lockbox 41 | website][website-link] for more documentation and context._ 42 | 43 | [install-link]: https://testpilot.firefox.com/files/lockbox@mozilla.com/latest 44 | [faq-link]: /faqs/ 45 | [contribute-link]: /contributing/ 46 | [website-link]: https://mozilla-lockbox.github.io/ 47 | [feedback-link]: https://qsurvey.mozilla.com/s3/Lockbox-Input 48 | -------------------------------------------------------------------------------- /docs/user-guide.md: -------------------------------------------------------------------------------- 1 | # Lockbox User Guide 2 | 3 | First things first, [install the extension](developer/install.md). 4 | 5 | ## Commands 6 | 7 | * `Ctrl-Shift-L`: open Lockbox editor (item management) 8 | -------------------------------------------------------------------------------- /jpm-prefs.json: -------------------------------------------------------------------------------- 1 | { 2 | "toolkit.telemetry.enabled": true, 3 | "toolkit.telemetry.server": "https://127.0.0.1/telemetry-dummy/", 4 | "xpinstall.signatures.required": false, 5 | "extensions.legacy.enabled": true 6 | } 7 | -------------------------------------------------------------------------------- /json-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const renderObject = require("json-templater/object"); 8 | 9 | module.exports = class JSONWebpackPlugin { 10 | constructor(options) { 11 | this.options = { 12 | template: undefined, 13 | filename: undefined, 14 | data: {}, 15 | ...options, 16 | }; 17 | } 18 | 19 | apply(compiler) { 20 | compiler.plugin("emit", (compilation, callback) => { 21 | if (!this.options.template) { 22 | compilation.errors.push("JSONWebpackPlugin template undefined"); 23 | callback(); 24 | return; 25 | } 26 | if (!this.options.filename) { 27 | compilation.errors.push("JSONWebpackPlugin filename undefined"); 28 | callback(); 29 | return; 30 | } 31 | 32 | const template = path.resolve(compiler.context, this.options.template); 33 | fs.readFile(template, {encoding: "utf8"}, (err, data) => { 34 | if (err) { 35 | compilation.errors.push("JSONWebpackPlugin couldn't read template"); 36 | callback(); 37 | return; 38 | } 39 | 40 | const rendered = renderObject(data, this.options.data); 41 | compilation.assets[this.options.filename] = { 42 | source() { 43 | return rendered; 44 | }, 45 | size() { 46 | return rendered.length; 47 | }, 48 | }; 49 | callback(); 50 | }); 51 | }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function(config) { 6 | config.set({ 7 | browsers: ["FirefoxHeadless"], 8 | customLaunchers: { 9 | FirefoxHeadless: { 10 | base: "Firefox", 11 | flags: ["-headless"], 12 | }, 13 | }, 14 | 15 | basePath: "", 16 | files: [ 17 | "test/unit/index.js", 18 | ], 19 | exclude: [], 20 | preprocessors: { 21 | "test/unit/index.js": ["webpack", "sourcemap"], 22 | }, 23 | 24 | frameworks: ["mocha"], 25 | reporters: ["mocha", "coverage"], 26 | 27 | webpack: require("./webpack.config.test.js"), 28 | webpackMiddleware: { 29 | stats: "errors-only", 30 | }, 31 | 32 | coverageReporter: { 33 | dir: "coverage/", 34 | reporters: [ 35 | { type: "text" }, 36 | { type: "lcov" }, 37 | { type: "html", subdir: "html" }, 38 | ], 39 | }, 40 | 41 | port: 9876, 42 | colors: true, 43 | logLevel: config.LOG_INFO, 44 | autoWatch: true, 45 | singleRun: true, 46 | concurrency: Infinity, 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Lockbox Extension 2 | site_description: Documentation for the Firefox extension 3 | theme: readthedocs 4 | repo_url: https://github.com/mozilla-lockbox/lockbox-extension 5 | 6 | extra_css: 7 | - css/extra.css 8 | 9 | markdown_extensions: 10 | - attr_list 11 | - admonition 12 | 13 | pages: 14 | - 'Introduction': 'index.md' 15 | - 'Release Notes': 'release-notes.md' 16 | - 'FAQs': 'faqs.md' 17 | - 'Contribute': 'contributing.md' 18 | - 'Telemetry and Metrics': 'metrics.md' 19 | - 'Developer Guides': 20 | - 'Build and Install': 'developer/install.md' 21 | - 'Release Instructions': 'developer/releases.md' 22 | - 'Directory Structure': 'developer/directory-structure.md' 23 | - 'Test Plan': 'developer/test-plan.md' 24 | - 'Test Plan - Accessibility': 'developer/test-plan-accessibility.md' 25 | - 'Test Plan - Telemetry': 'developer/test-plan-telemetry.md' 26 | -------------------------------------------------------------------------------- /mocha-webpack.opts: -------------------------------------------------------------------------------- 1 | --colors 2 | --recursive 3 | --require ignore-styles 4 | --require babel-core/register 5 | --require jsdom-global/register 6 | -------------------------------------------------------------------------------- /requirements/flake8.txt: -------------------------------------------------------------------------------- 1 | flake8==3.5.0 2 | flake8-isort==2.5 3 | flake8-docstrings==1.3.0 4 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | fxapom==1.10.2 2 | PyPOM==2.0.0 3 | pytest==3.7.1 4 | pytest-selenium==1.13.0 5 | pytest-xdist==1.22.5 6 | selenium==3.14.0 7 | axe-selenium-python==2.0.4 8 | pytest-axe==1.0.0 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [isort] 2 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/icon.png -------------------------------------------------------------------------------- /src/install.rdf.ejs: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | <% const package = htmlWebpackPlugin.options.package; %> 6 | <%- package.id %> 7 | 2 8 | true 9 | <%- package.title %> (unmaintained) 10 | <%- package.version %> 11 | Mozilla 12 | <%- package.author %> 13 | <%- package.description %> 14 | <%- package.homepage %> 15 | true 16 | true 17 | https://testpilot.firefox.com/files/<%= package.id %>/updates.json 18 | 19 | 20 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 21 | 57.0 22 | 59.0a1 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | <% const path = require("path"); 9 | const dir = path.dirname(htmlWebpackPlugin.options.filename); 10 | for (let css of htmlWebpackPlugin.files.css) { 11 | %> 12 | 13 | <% } for (let js of htmlWebpackPlugin.files.js) { %> 14 | 15 | <% } %> 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src/webextension/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ; Use rjsx-mode instead of js2-mode for files in this directory. 2 | ((js2-mode . ((mode . rjsx)))) 3 | -------------------------------------------------------------------------------- /src/webextension/background/README.md: -------------------------------------------------------------------------------- 1 | # Background Scripts 2 | 3 | All of the logic working with the datastore occurs in the background scripts. Front-end pages communicate with these scripts via message ports. 4 | 5 | ## `get_account_details` 6 | 7 | Returns the JSON for the user's account. Contains the following fields: 8 | 9 | ``` 10 | mode 11 | uid 12 | email 13 | displayName 14 | avatar 15 | ``` 16 | 17 | ## `open_view` 18 | 19 | Open the view named `name` in a new tab. Returns an empty object. 20 | 21 | ## `close_view` 22 | 23 | Close the view named `name` if it's open. Returns an empty object. 24 | 25 | ## `initialize` 26 | 27 | Initialize the datastore and open the view named `view`, if specified. Returns an empty object. 28 | 29 | ## `upgrade_account` 30 | 31 | Upgrade a local account to an FxA-connected account. 32 | 33 | ## `reset` 34 | 35 | Reset the local state and re-open the first run page. Returns an empty object. 36 | 37 | ## `signin` 38 | 39 | Sign in to the user's Firefox Account (i.e. unlock the datastore), and open the view named `view`, if specified. Returns an empty object. 40 | 41 | ## `signout` 42 | 43 | Sign out of the user's Firefox Account (i.e. lock the datastore), and close the management view if open. Returns an empty object. 44 | 45 | ## `list_items` 46 | 47 | List all the items in the datastore. Returns an array of summaries of the items in the `items` field. 48 | 49 | ## `add_item` 50 | 51 | Add an item (in the `item` attribute) to the datastore. Returns the updated item in the `item` field with its `id` field filled out. 52 | 53 | ## `update_item` 54 | 55 | Update an existing item (in the `item` attribute) in the datastore. Returns the updated item in the `item` field. 56 | 57 | ## `remove_item` 58 | 59 | Remove the item with ID `id` from the datastore. Returns an empty object. 60 | 61 | ## `get_item` 62 | 63 | Fetches the item with ID `id` from the datastore. Returns the item object in the `item` field. 64 | 65 | ## `proxy_telemetry_event` 66 | 67 | Record a telemetry event with method `method`, object `object`, and extra `extra`. 68 | -------------------------------------------------------------------------------- /src/webextension/background/accounts/configs.json: -------------------------------------------------------------------------------- 1 | { 2 | "production": { 3 | "client_id": "1b024772203a0849", 4 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/", 5 | "authorization_endpoint": "https://oauth.accounts.firefox.com/v1/authorization", 6 | "token_endpoint": "https://oauth.accounts.firefox.com/v1/token", 7 | "userinfo_endpoint": "https://profile.accounts.firefox.com/v1/profile", 8 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"], 9 | "pkce": true, 10 | "app_keys": true 11 | }, 12 | "dev-latest": { 13 | "client_id": "f69b2d16e724b432", 14 | "client_secret": "32052e102892dd07b1885c38887849d8283d1407350ee009b6377b8f542a272c", 15 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/", 16 | "authorization_endpoint": "https://oauth-latest.dev.lcip.org/v1/authorization", 17 | "token_endpoint": "https://oauth-latest.dev.lcip.org/v1/token", 18 | "userinfo_endpoint": "https://latest.dev.lcip.org/profile/v1/profile", 19 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"], 20 | "pkce": false 21 | }, 22 | "scoped-keys": { 23 | "client_id": "37fdfa37698f251a", 24 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/", 25 | "authorization_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/authorization", 26 | "token_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/token", 27 | "userinfo_endpoint": "https://latest-keys.dev.lcip.org/profile/v1/profile", 28 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"], 29 | "pkce": true, 30 | "app_keys": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/webextension/background/datastore.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import * as DataStore from "lockbox-datastore"; 6 | import * as telemetry from "./telemetry"; 7 | 8 | let datastore; 9 | 10 | async function recordMetric(method, itemid, fields) { 11 | let extra = { 12 | itemid, 13 | }; 14 | if (fields) { 15 | extra = { 16 | ...extra, 17 | fields, 18 | }; 19 | } 20 | telemetry.recordEvent(method, "datastore", extra); 21 | } 22 | 23 | export const DEFAULT_APP_KEY = { 24 | "kty": "oct", 25 | "kid": "L9-eBkDrYHdPdXV_ymuzy_u9n3drkQcSw5pskrNl4pg", 26 | "k": "WsTdZ2tjji2W36JN9vk9s2AYsvp8eYy1pBbKPgcSLL4", 27 | }; 28 | 29 | export function clearDataStore() { 30 | datastore = undefined; 31 | } 32 | 33 | export default async function openDataStore(cfg = {}) { 34 | if (!datastore) { 35 | datastore = await DataStore.open({ 36 | ...cfg, 37 | recordMetric, 38 | }); 39 | } 40 | return datastore; 41 | } 42 | -------------------------------------------------------------------------------- /src/webextension/background/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import openDataStore, { DEFAULT_APP_KEY } from "./datastore"; 6 | import { openAccount, GUEST } from "./accounts"; 7 | import initializeMessagePorts from "./message-ports"; 8 | import updateBrowserAction from "./browser-action"; 9 | 10 | openAccount(browser.storage.local).then(async (account) => { 11 | let datastore = await openDataStore({ salt: account.uid }); 12 | if (datastore.initialized && account.mode === GUEST) { 13 | try { 14 | await datastore.unlock(DEFAULT_APP_KEY); 15 | } catch (err) { 16 | // eslint-disable-next-line no-console 17 | console.error("datastore is in an inconsistent state."); 18 | } 19 | } 20 | 21 | initializeMessagePorts(); 22 | await updateBrowserAction({account, datastore}); 23 | }); 24 | -------------------------------------------------------------------------------- /src/webextension/background/telemetry.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export async function recordEvent(method, object, extra) { 6 | return browser.runtime.sendMessage({ 7 | type: "telemetry_event", method, object, extra, 8 | }); 9 | } 10 | 11 | export async function setScalar(name, value) { 12 | return browser.runtime.sendMessage({ 13 | type: "telemetry_scalar", name, value, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/webextension/common.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export function makeItemSummary(item) { 6 | return { 7 | title: item.title, 8 | id: item.id, 9 | origins: item.origins, 10 | username: item.entry.username, 11 | }; 12 | } 13 | 14 | export function classNames(classNames) { 15 | return classNames.filter((i) => i).join(" "); 16 | } 17 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | color: #0c0c0d; 9 | background-color: #f9f9fa; 10 | font: caption; 11 | font-size: 13px; 12 | -moz-user-select: none; 13 | } 14 | 15 | .firstrun { 16 | width: 612px; 17 | margin: 3em auto; 18 | display: flex; 19 | flex-flow: column nowrap; 20 | align-items: center; 21 | } 22 | 23 | .firstrun > img { 24 | width: 300px; 25 | } 26 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import Intro from "./intro"; 10 | import StartUsing from "./using"; 11 | 12 | import styles from "./app.css"; 13 | 14 | export default function App() { 15 | const imgSrc = browser.extension.getURL("/images/nessie_v2.svg"); 16 | 17 | return ( 18 | 19 | 20 |
21 | 22 | 23 | 24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/intro.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .intro { 6 | display: flex; 7 | flex-flow: column nowrap; 8 | justify-content: center; 9 | align-items: center; 10 | } 11 | 12 | .intro > h1, 13 | .intro > h2, 14 | .intro > h3, 15 | .intro > p { 16 | text-align: center; 17 | line-height: 1.5; 18 | letter-spacing: 0.2px; 19 | } 20 | 21 | .intro > h1 { 22 | font-size: 33px; 23 | font-weight: 300; 24 | margin: 0; 25 | } 26 | 27 | .intro > h2 { 28 | font-size: 17px; 29 | font-weight: 300; 30 | font-style: italic; 31 | margin: 0px; 32 | text-transform: lowercase; 33 | } 34 | 35 | .intro > p { 36 | font-size: 15px; 37 | } 38 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/intro.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | 8 | import styles from "./intro.css"; 9 | 10 | export default function Intro() { 11 | return ( 12 |
13 | 14 |

wELCOMe

15 |
16 | 17 |

mORe wELCOMe

18 |
19 | 20 |

uNMAINTAINEd dISCLAIMEr

21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/using.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .using { 6 | display: flex; 7 | flex-flow: column nowrap; 8 | align-items: center; 9 | } 10 | 11 | .using h1, 12 | .using h2, 13 | .using p { 14 | text-align: center; 15 | } 16 | 17 | .using > .actions { 18 | margin: 0; 19 | display: flex; 20 | flex-flow: column nowrap; 21 | align-items: stretch; 22 | } 23 | 24 | .using > .actions > h2 { 25 | margin: 1em 0 .5em; 26 | font-size: 22px; 27 | font-weight: 300; 28 | line-height: 1.5; 29 | letter-spacing: 0.2px; 30 | } 31 | 32 | .using > .actions > button { 33 | margin: 0; 34 | font-size: 15px; 35 | } 36 | -------------------------------------------------------------------------------- /src/webextension/firstrun/components/using.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | 8 | import Button from "../../widgets/button"; 9 | import * as telemetry from "../../telemetry"; 10 | 11 | import styles from "./using.css"; 12 | 13 | export default function StartUsing() { 14 | const doGuest = async () => { 15 | telemetry.recordEvent("click", "welcomeGuest"); 16 | browser.runtime.sendMessage({ 17 | type: "initialize", 18 | view: "manage", 19 | }); 20 | browser.runtime.sendMessage({ 21 | type: "close_view", 22 | name: "firstrun", 23 | }); 24 | }; 25 | const doReturning = async () => { 26 | telemetry.recordEvent("click", "welcomeSignin"); 27 | browser.runtime.sendMessage({ 28 | type: "upgrade_account", 29 | view: "manage", 30 | }); 31 | browser.runtime.sendMessage({ 32 | type: "close_view", 33 | name: "firstrun", 34 | }); 35 | }; 36 | 37 | return ( 38 |
39 |
40 | 41 |

gUESt

42 |
43 | 44 | 47 | 48 | 49 |

rETURNINg

50 |
51 | 52 | 53 | 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/webextension/firstrun/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | 8 | import AppLocalizationProvider from "../l10n"; 9 | import App from "./components/app"; 10 | import * as telemetry from "../telemetry"; 11 | 12 | telemetry.recordEvent("render", "firstrun"); 13 | 14 | ReactDOM.render( 15 | 17 | 18 | , 19 | document.getElementById("content") 20 | ); 21 | -------------------------------------------------------------------------------- /src/webextension/fonts/fira-mono-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/fonts/fira-mono-regular.woff -------------------------------------------------------------------------------- /src/webextension/icons/account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/icons/arrowhead-left-16.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/webextension/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/icons/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/icons/default-avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/webextension/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/webextension/icons/hide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/webextension/icons/lb_locked.svg: -------------------------------------------------------------------------------- 1 | lockbox-16 2 | -------------------------------------------------------------------------------- /src/webextension/icons/lb_unlocked.svg: -------------------------------------------------------------------------------- 1 | unlockbox-16 2 | -------------------------------------------------------------------------------- /src/webextension/icons/options.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/icons/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/webextension/icons/signout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webextension/images/intro-step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-1.png -------------------------------------------------------------------------------- /src/webextension/images/intro-step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-2.png -------------------------------------------------------------------------------- /src/webextension/images/intro-step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-3.png -------------------------------------------------------------------------------- /src/webextension/list/README.md: -------------------------------------------------------------------------------- 1 | # List Views 2 | 3 | This directory contains the list views for the extension (the doorhanger - in `popup/` - and the full management UI - in `manage/`). Code in other directories is shared between both views. 4 | 5 | ## Redux storage 6 | 7 | The list views are the primary users of Redux in the extension. This section describes how the state is stored at a high level. 8 | 9 | ### Cache reducer (common) 10 | 11 | This reducer contains a local cache of the necessary portions of the Lockbox datastore. In particular, it contains a summary of all the items in the store, plus full details of the currently-selected item. Item summaries have the following fields: 12 | 13 | ``` 14 | title 15 | id 16 | origins 17 | username 18 | ``` 19 | 20 | ### List reducer (common) 21 | 22 | This reducer contains the logic for the actual item list. It stores the ID of the currently-selected item as well as any filter to be applied to the list. 23 | 24 | ### Editor reducer (manager) 25 | 26 | This reducer tracks the state of the editor components. It stores whether the editor is active and if the user has changed any of the fields. `hideHome` is a bit more subtle; it's used when selecting another item when the editor is open, and prevents the home view from appearing while that item is loading. 27 | 28 | ### Modal reducer (manager) 29 | 30 | This reducer contains the state for any modal dialogs to be shown in the manager. It contains the ID of the modal and any props that should be passed to the modal when it's displayed. 31 | -------------------------------------------------------------------------------- /src/webextension/list/common.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // This is a string instead of a symbol since we need to be able to use it as a 6 | // React `key` inside a list. 7 | export const NEW_ITEM_ID = "new-item"; 8 | 9 | export function unflattenItem(item, id) { 10 | return { 11 | id, 12 | title: item.title, 13 | origins: item.origin ? [item.origin] : [], 14 | entry: { 15 | kind: "login", 16 | username: item.username, 17 | password: item.password, 18 | notes: item.notes, 19 | }, 20 | }; 21 | } 22 | 23 | export function flattenItem(item) { 24 | return { 25 | title: item.title, 26 | origin: item.origins[0] || "", 27 | username: item.entry.username, 28 | password: item.entry.password, 29 | notes: item.entry.notes, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/webextension/list/components/item-fields.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .item-fields { 6 | display: grid; 7 | grid-template-columns: minmax(200px, 1fr); 8 | max-width: 360px; 9 | } 10 | 11 | .item-fields > label, 12 | .item-fields > .field { 13 | display: contents; 14 | } 15 | 16 | .item-fields textarea { 17 | resize: none; 18 | } 19 | 20 | .input { 21 | max-width: 242px; 22 | } 23 | 24 | .password { 25 | max-width: 258px; 26 | } 27 | 28 | .notes { 29 | min-height: 60px; 30 | } 31 | 32 | .notes-read-only { 33 | min-height: 73px; 34 | } 35 | 36 | .first-label { 37 | margin-top: 0; 38 | } 39 | 40 | .inline-button { 41 | display: flex; 42 | } 43 | 44 | .inline-button > *:first-child { 45 | flex: 1; 46 | } 47 | -------------------------------------------------------------------------------- /src/webextension/list/components/item-list.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .empty { 6 | text-align: center; 7 | white-space: pre-wrap; 8 | padding: 40px; 9 | overflow: hidden; 10 | overflow-wrap: break-word; 11 | color: #4a4a4f; 12 | font-size: 15px; 13 | line-height: 1.5; 14 | letter-spacing: 0.2px; 15 | } 16 | 17 | .item { 18 | color: #0c0c0d; 19 | border-bottom: 1px solid #d7d7db; 20 | background-color: #ffffff; 21 | } 22 | 23 | .item:first-child { 24 | border-top: 1px solid #d7d7db; 25 | } 26 | 27 | .item .item-summary:hover { 28 | background-color: rgba(10, 132, 255, 0.07); 29 | } 30 | 31 | .item .item-summary:active { 32 | background-color: rgba(10, 132, 255, 0.12); 33 | } 34 | 35 | .item[data-selected] .item-summary { 36 | position: relative; 37 | background-color: rgba(10, 132, 255, 0.12); 38 | box-shadow: 0 -1px 0 rgba(10, 132, 255, 0.4), 0 1px 0 rgba(10, 132, 255, 0.4); 39 | } 40 | 41 | /* Provide borders at the top and bottom of selected items. These are ::before 42 | and ::after pseudoelements so that we can give them their own z-index. This 43 | lets us have other elements (e.g. headers, footers) around the list that are 44 | drawn on top of the list, but to have the selected borders here go on top of 45 | *those*. */ 46 | 47 | .item[data-selected] .item-summary::before, 48 | .item[data-selected] .item-summary::after { 49 | content: ""; 50 | display: block; 51 | position: absolute; 52 | left: 0; 53 | right: 0; 54 | width: 100%; 55 | height: 1px; 56 | background-color: #99bff2; 57 | z-index: 10; 58 | } 59 | 60 | .item[data-selected] .item-summary::before { 61 | top: -1px; 62 | } 63 | 64 | .item[data-selected] .item-summary::after { 65 | bottom: -1px; 66 | } 67 | 68 | .item-list:focus .item[data-selected] .item-summary::before, 69 | .item-list:focus .item[data-selected] .item-summary::after { 70 | background-color: #0a84ff; 71 | } 72 | 73 | .verbose + .verbose { 74 | margin-top: 4px; 75 | border-top: 1px solid #d7d7db; 76 | } 77 | -------------------------------------------------------------------------------- /src/webextension/list/components/item-list.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../../common"; 9 | import ItemSummary from "./item-summary"; 10 | import ScrollingList from "../../widgets/scrolling-list"; 11 | 12 | import styles from "./item-list.css"; 13 | 14 | export default function ItemList({items, itemClassName, verbose, onCopy, 15 | ...props}) { 16 | return ( 17 | 21 | {({id, title, username}) => { 22 | return ( 23 | 25 | ); 26 | }} 27 | 28 | ); 29 | } 30 | 31 | ItemList.propTypes = { 32 | items: PropTypes.arrayOf( 33 | PropTypes.shape({ 34 | id: PropTypes.string.isRequired, 35 | title: PropTypes.string.isRequired, 36 | username: PropTypes.string.isRequired, 37 | }).isRequired 38 | ).isRequired, 39 | itemClassName: PropTypes.string, 40 | verbose: PropTypes.bool, 41 | onCopy: PropTypes.func, 42 | }; 43 | 44 | ItemList.defaultProps = { 45 | itemClassName: "", 46 | verbose: false, 47 | }; 48 | 49 | export function ItemListPlaceholder({children}) { 50 | return ( 51 |
52 | {children} 53 |
54 | ); 55 | } 56 | 57 | ItemListPlaceholder.propTypes = { 58 | children: PropTypes.node, 59 | }; 60 | -------------------------------------------------------------------------------- /src/webextension/list/components/item-summary.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .item-summary-container::before { 6 | content: ""; 7 | float: right; 8 | position: relative; 9 | width: 5px; 10 | height: 9px; 11 | margin: 32px 20px; 12 | background-image: url(/icons/chevron-right.svg); 13 | background-repeat: no-repeat; 14 | background-size: 5px 9px; 15 | z-index: 2; 16 | } 17 | 18 | .item-summary { 19 | padding: 16px 20px; 20 | font-size: 15px; 21 | } 22 | 23 | .title, 24 | .subtitle { 25 | line-height: 1.5; 26 | letter-spacing: 0.2px; 27 | overflow: hidden; 28 | white-space: nowrap; 29 | text-overflow: ellipsis; 30 | } 31 | 32 | .title { 33 | font-weight: bold; 34 | } 35 | 36 | .subtitle { 37 | font-size: 13px; 38 | font-weight: 400; 39 | } 40 | 41 | .copy-buttons { 42 | border-top: 1px solid #d7d7db; 43 | display: flex; 44 | } 45 | 46 | .copy-button { 47 | flex: 1; 48 | height: 40px; 49 | } 50 | 51 | .copy-button + .copy-button { 52 | border-inline-start: 1px solid #d7d7db; 53 | } 54 | 55 | .copy-button-inner { 56 | border-radius: 0; 57 | } 58 | 59 | .copy-button-inner:focus { 60 | box-shadow: inset 0 0 0 2px #0a84ff; 61 | } 62 | 63 | .copy-button-inner:hover { 64 | background-color: #ededf0; 65 | } 66 | 67 | .copy-button-inner:active:hover { 68 | background-color: #d7d7db; 69 | } 70 | -------------------------------------------------------------------------------- /src/webextension/list/containers/item-filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import { filterItems } from "../actions"; 11 | import FilterInput from "../../widgets/filter-input"; 12 | 13 | function ItemFilter({inputRef, ...props}) { 14 | return ( 15 | 18 | 20 | 21 | ); 22 | } 23 | 24 | ItemFilter.propTypes = { 25 | inputRef: PropTypes.func, 26 | }; 27 | 28 | export default connect( 29 | (state) => ({ 30 | value: state.list.filter.query, 31 | disabled: state.cache.items.length === 0, 32 | }), 33 | (dispatch) => ({ 34 | onChange: (value) => { dispatch(filterItems(value)); }, 35 | }) 36 | )(ItemFilter); 37 | -------------------------------------------------------------------------------- /src/webextension/list/filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export function parseFilterString(filter) { 6 | filter = filter.trim(); 7 | if (!filter) { 8 | return []; 9 | } 10 | return filter.split(/\s+/).map((i) => i.toLocaleLowerCase()); 11 | } 12 | 13 | function match(filterElement, value) { 14 | return value.includes(filterElement); 15 | } 16 | 17 | function matchAny(filterElement, values) { 18 | for (let i of values) { 19 | if (match(filterElement, i)) { 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | export function filterItem(filter, item) { 27 | const title = item.title.toLocaleLowerCase(); 28 | const username = item.username.toLocaleLowerCase(); 29 | const origins = item.origins.map((i) => i.toLocaleLowerCase()); 30 | 31 | for (let i of filter) { 32 | if (!match(i, title) && !match(i, username) && !matchAny(i, origins)) { 33 | return false; 34 | } 35 | } 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/account-linked.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .linked { 6 | margin: 2em auto; 7 | padding: 0; 8 | display: flex; 9 | flex-flow: column nowrap; 10 | align-items: center; 11 | } 12 | 13 | .linked > h2, 14 | .linked > p { 15 | text-align: center; 16 | letter-spacing: 0.2px; 17 | line-height: 1.5; 18 | } 19 | 20 | .linked > h2 { 21 | margin: 0 0 .5em; 22 | padding: 0; 23 | font-size: 17px; 24 | font-weight: 600; 25 | line-height: 1.32; 26 | color: #058b00; 27 | } 28 | 29 | .linked > p { 30 | margin-top: 0; 31 | font-size: 15px; 32 | white-space: pre-line; 33 | } 34 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/account-linked.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | 8 | import styles from "./account-linked.css"; 9 | 10 | export default function AccountLinked() { 11 | return ( 12 |
13 | 14 |

aCCOUNt lINKEd

15 |
16 | 17 |

Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | color: #0c0c0d; 9 | background-color: #f9f9fa; 10 | font: caption; 11 | font-size: 13px; 12 | -moz-user-select: none; 13 | overflow: hidden; 14 | } 15 | 16 | html, 17 | body, 18 | body > main, 19 | .app { 20 | height: 100%; 21 | } 22 | 23 | .app-main { 24 | display: grid; 25 | height: 100%; 26 | grid-template-columns: 1fr 3fr; 27 | } 28 | 29 | .navigation { 30 | height: 48px; 31 | padding-inline-start: 4.6em; 32 | } 33 | 34 | .navigation > * + * { 35 | margin-inline-start: 40px; 36 | } 37 | 38 | .aside { 39 | grid-column: 1; 40 | min-width: 150px; 41 | min-height: 0; 42 | display: flex; 43 | flex-direction: column; 44 | border-inline-end: 1px solid #d7d7db; 45 | } 46 | 47 | .app-main > article { 48 | grid-column: 2; 49 | background-color: #f9f9fa; 50 | overflow: auto; 51 | } 52 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import AllItems from "../containers/all-items"; 10 | import CurrentAccountSummary from "../containers/current-account-summary"; 11 | import CurrentBreadcrumbs from "../containers/current-breadcrumbs"; 12 | import CurrentSelection from "../containers/current-selection"; 13 | import ModalRoot from "../containers/modals"; 14 | import OpenFAQ from "../containers/open-faq"; 15 | import Toolbar, { ToolbarSpace } from "../../../widgets/toolbar"; 16 | 17 | import styles from "./app.css"; 18 | 19 | export default class App extends React.Component { 20 | componentDidMount() { 21 | this._filterField.focus(true); 22 | } 23 | 24 | render() { 25 | return ( 26 | 27 | 28 |
29 |
30 | { 32 | this._filterField = element; 33 | }}/> 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | 45 |
46 |
47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/home-breadcrumbs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import Breadcrumbs, { Crumb } from "../../../widgets/breadcrumbs"; 10 | import { NEW_ITEM_ID } from "../../common"; 11 | 12 | export default function HomeBreadcrumbs({itemId, itemTitle, onClickHome}) { 13 | if (itemId) { 14 | const trimmedTitle = itemTitle.trim(); 15 | const titleId = `breadcrumbs-item${itemId === NEW_ITEM_ID ? "-new" : ""}`; 16 | 17 | return ( 18 | 19 | 20 | hOMe 21 | 22 | 23 | 25 | iTEm 26 | 27 | 28 | ); 29 | } 30 | return null; 31 | } 32 | 33 | HomeBreadcrumbs.propTypes = { 34 | itemId: PropTypes.string, 35 | itemTitle: PropTypes.string, 36 | onClickHome: PropTypes.func.isRequired, 37 | }; 38 | 39 | HomeBreadcrumbs.defaultProps = { 40 | itemTitle: "", 41 | }; 42 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/homepage.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .homepage { 6 | display: flex; 7 | flex-flow: column nowrap; 8 | align-items: center; 9 | margin: 3em; 10 | } 11 | 12 | .homepage > h1, 13 | .homepage > h2, 14 | .homepage > h3, 15 | .homepage > p { 16 | color: #0c0c0d; 17 | letter-spacing: 0.2px; 18 | line-height: 1.5; 19 | text-align: center; 20 | } 21 | 22 | .homepage h1 { 23 | font-size: 17px; 24 | font-weight: 300; 25 | font-style: italic; 26 | text-transform: lowercase; 27 | } 28 | 29 | .homepage > img { 30 | max-width: 279px; 31 | } 32 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/homepage.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | 8 | import AccountDetails from "../containers/account-details"; 9 | 10 | import styles from "./homepage.css"; 11 | 12 | export default function Homepage() { 13 | const imgSrc = browser.extension.getURL("/images/nessie_v2.svg"); 14 | 15 | return ( 16 |
17 | 18 | 19 |

tHe sIMPLe wAy tO sTORE...

20 |
21 | 22 |

uNMAINTAINEd dISCLAIMEr

23 |
24 | 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/intro-page.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .intro-images { 6 | display: grid; 7 | grid-auto-flow: column; 8 | grid-template-columns: 1fr 1fr 1fr; 9 | grid-template-rows: auto auto auto; 10 | grid-column-gap: 4em; 11 | margin: 8em auto 4em; 12 | padding: 0 4em; 13 | max-width: 880px; 14 | } 15 | 16 | .intro-image { 17 | display: contents; 18 | text-align: center; 19 | } 20 | 21 | .intro-image > h1, 22 | .intro-image > p { 23 | overflow: hidden; 24 | } 25 | 26 | .intro-image > img { 27 | width: 100%; 28 | border: 1px solid #d7d7db; 29 | border-radius: 5px; 30 | box-shadow: 0 1px 4px rgba(12, 12, 13, 0.1); 31 | } 32 | 33 | .intro-image > h1 { 34 | font-size: 17px; 35 | margin-bottom: 0; 36 | } 37 | 38 | .intro-image > p { 39 | font-size: 15px; 40 | line-height: 1.5; 41 | } 42 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/intro-page.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import AccountDetails from "../containers/account-details"; 10 | 11 | import styles from "./intro-page.css"; 12 | 13 | function IntroImage({src, title, children}) { 14 | return ( 15 |
16 | 17 |

{title}

18 |

{children}

19 |
20 | ); 21 | } 22 | 23 | IntroImage.propTypes = { 24 | src: PropTypes.string.isRequired, 25 | title: PropTypes.string.isRequired, 26 | children: PropTypes.node, 27 | }; 28 | 29 | export default function IntroPage() { 30 | return ( 31 |
32 |
33 | 34 | 36 | sAVe uSERNAMe aNd pASSWORd... 37 | 38 | 39 | 40 | 42 | cLICk tHe lOCKBOx iCOn... 43 | 44 | 45 | 46 | 48 | cOPy an eNTRY's iNFo... 49 | 50 | 51 |
52 | 53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/item-details.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .item-details { 6 | padding: 1em 4.6em 4.6em; 7 | background-color: #f9f9fa; 8 | } 9 | 10 | .item-details > h1 { 11 | font-weight: 300; 12 | font-size: 22px; 13 | margin: 0 0 1.5em; 14 | padding-bottom: 1em; 15 | border-bottom: 1px solid #d7d7db; 16 | } 17 | 18 | .buttons { 19 | margin-top: 1em; 20 | padding: 1em 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/item-details.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import Button from "../../../widgets/button"; 10 | import Toolbar from "../../../widgets/toolbar"; 11 | import { ItemFields } from "../../components/item-fields"; 12 | 13 | import styles from "./item-details.css"; 14 | 15 | // Note: ItemDetails doesn't directly interact with items from the Lockbox 16 | // datastore. For that, please consult <../containers/current-item.js>. 17 | 18 | export default function ItemDetails({fields, onCopy, onEdit, onDelete}) { 19 | return ( 20 |
21 | 22 |

iTEm dETAILs

23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | ); 35 | } 36 | 37 | ItemDetails.propTypes = { 38 | ...ItemFields.propTypes, 39 | onCopy: PropTypes.func.isRequired, 40 | onEdit: PropTypes.func.isRequired, 41 | onDelete: PropTypes.func.isRequired, 42 | }; 43 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/item-list-panel.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .filter-toolbar > * + * { 6 | margin-inline-start: 8px; 7 | } 8 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/item-list-panel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import { PanelHeader, PanelBody, PanelFooter } from "../../../widgets/panel"; 10 | import AddItem from "../containers/add-item"; 11 | import ItemList, { ItemListPlaceholder } from "../../components/item-list"; 12 | import ItemFilter from "../../containers/item-filter"; 13 | import SendFeedback from "../containers/send-feedback"; 14 | 15 | import styles from "./item-list-panel.css"; 16 | 17 | export default function ItemListPanel({className, inputRef, totalItemCount, 18 | ...props}) { 19 | const hasItems = props.items.length !== 0; 20 | let list; 21 | if (!hasItems) { 22 | list = ( 23 | 25 | 26 | wHEn yOu cREATe an eNTRy... 27 | 28 | 29 | ); 30 | } else { 31 | list = ; 32 | } 33 | 34 | return ( 35 | 50 | ); 51 | } 52 | 53 | ItemListPanel.propTypes = { 54 | className: PropTypes.string, 55 | inputRef: PropTypes.func, 56 | totalItemCount: PropTypes.number.isRequired, 57 | ...ItemList.propTypes, 58 | }; 59 | 60 | ItemListPanel.defaultProps = { 61 | className: "", 62 | }; 63 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/link-account.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .link { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | margin: 2em auto; 10 | padding: 0; 11 | max-width: 500px; 12 | } 13 | 14 | .link > h2, 15 | .link > p { 16 | text-align: center; 17 | letter-spacing: 0.2px; 18 | line-height: 1.5; 19 | } 20 | 21 | .link > h2 { 22 | margin: 0 0 .5em; 23 | padding: 0; 24 | font-size: 17px; 25 | font-weight: 600; 26 | line-height: 1.32; 27 | } 28 | 29 | .link > p { 30 | font-size: 15px; 31 | margin: 0 0 0.5em; 32 | } 33 | 34 | .link > menu { 35 | display: flex; 36 | flex-wrap: wrap; 37 | justify-content: center; 38 | margin: 0; 39 | padding: 0; 40 | } 41 | 42 | .link > menu > button { 43 | margin: 0.5em; 44 | } 45 | -------------------------------------------------------------------------------- /src/webextension/list/manage/components/link-account.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import Button from "../../../widgets/button"; 10 | 11 | import styles from "./link-account.css"; 12 | 13 | export default function LinkAccount({ onCreate, onSignin }) { 14 | return ( 15 |
16 | 17 |

uPGRADe

18 |
19 | 20 |

uPGRADe yOUr lOCKBOx

21 |
22 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 |
33 | ); 34 | } 35 | LinkAccount.propTypes = { 36 | onCreate: PropTypes.func.isRequired, 37 | onSignin: PropTypes.func.isRequired, 38 | }; 39 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/account-details.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import PropTypes from "prop-types"; 7 | import { connect } from "react-redux"; 8 | 9 | import * as telemetry from "../../../telemetry"; 10 | import LinkAccount from "../components/link-account"; 11 | import AccountLinked from "../components/account-linked"; 12 | 13 | function linkAction(action) { 14 | const obj = (action === "signup") ? 15 | "manageAcctCreate" : 16 | "manageAcctSignin"; 17 | return async () => { 18 | telemetry.recordEvent("click", obj); 19 | browser.runtime.sendMessage({ 20 | type: "upgrade_account", 21 | action, 22 | }); 23 | }; 24 | } 25 | 26 | const ConnectedLinkAccount = connect( 27 | (state) => ({ 28 | ...state, 29 | onCreate: linkAction("signup"), 30 | onSignin: linkAction("signin"), 31 | }) 32 | )(LinkAccount); 33 | 34 | function AccountDetails({mode}) { 35 | let inner = null; 36 | 37 | if (mode === "guest") { 38 | inner = ; 39 | } else if (mode === "authenticated") { 40 | inner = ; 41 | } 42 | 43 | return
{inner}
; 44 | } 45 | 46 | AccountDetails.propTypes = { 47 | mode: PropTypes.string.isRequired, 48 | }; 49 | 50 | export default connect( 51 | (state) => ({ 52 | mode: state.account.mode, 53 | }) 54 | )(AccountDetails); 55 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/add-item.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .add-item { 6 | min-width: initial; 7 | width: 32px; 8 | } 9 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/add-item.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import Button from "../../../widgets/button"; 11 | import { startNewItem } from "../../actions"; 12 | 13 | import styles from "./add-item.css"; 14 | 15 | function AddItem({disabled, onAddItem}) { 16 | return ( 17 | 18 | 22 | 23 | ); 24 | } 25 | 26 | AddItem.propTypes = { 27 | disabled: PropTypes.bool.isRequired, 28 | onAddItem: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default connect( 32 | (state) => ({ 33 | disabled: state.editor.editing, 34 | }), 35 | (dispatch) => ({ 36 | onAddItem: () => { dispatch(startNewItem()); }, 37 | }) 38 | )(AddItem); 39 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/all-items.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ItemListPanel from "../components/item-list-panel"; 8 | import { requestSelectItem } from "../../actions"; 9 | import { parseFilterString, filterItem } from "../../filter"; 10 | import { NEW_ITEM_ID } from "../../common"; 11 | 12 | const collator = new Intl.Collator(); 13 | 14 | export default connect( 15 | (state, ownProps) => { 16 | const totalItemCount = state.cache.items.length; 17 | const filter = parseFilterString(state.list.filter.query); 18 | const selected = state.list.selectedItemId; 19 | const items = state.cache.items 20 | .filter((i) => filterItem(filter, i)) 21 | .sort((a, b) => collator.compare(a.title, b.title)); 22 | 23 | if (selected === NEW_ITEM_ID) { 24 | items.unshift({id: NEW_ITEM_ID, title: "", username: ""}); 25 | } 26 | return {...ownProps, totalItemCount, items, selected}; 27 | }, 28 | (dispatch) => ({ 29 | onChange: (id) => dispatch(requestSelectItem(id)), 30 | }), 31 | )(ItemListPanel); 32 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/current-account-summary.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import AccountSummary from "../components/account-summary"; 8 | import { openAccountPage, openOptions, signout } from "../../actions"; 9 | 10 | export default connect( 11 | (state) => { 12 | if (state.account.mode === "authenticated") { 13 | return { 14 | displayName: state.account.displayName, 15 | avatar: state.account.avatar, 16 | }; 17 | } 18 | return {}; 19 | }, 20 | (dispatch) => ({ 21 | onClickAccount: () => { dispatch(openAccountPage()); }, 22 | onClickOptions: () => { dispatch(openOptions()); }, 23 | onClickSignout: () => { dispatch(signout()); }, 24 | }), 25 | )(AccountSummary); 26 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/current-breadcrumbs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import HomeBreadcrumbs from "../components/home-breadcrumbs"; 8 | import { requestSelectItem } from "../../actions"; 9 | 10 | export default connect( 11 | (state) => { 12 | const item = state.cache.currentItem; 13 | return { 14 | itemId: state.list.selectedItemId, 15 | itemTitle: item ? item.title : undefined, 16 | }; 17 | }, 18 | (dispatch) => ({ 19 | onClickHome: () => { dispatch(requestSelectItem(null)); }, 20 | }) 21 | )(HomeBreadcrumbs); 22 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/modals.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ModalRoot from "../../../widgets/modal-root"; 8 | import { LocalizedConfirmDialog } from "../../../widgets/dialog-box"; 9 | import { hideModal, removeItem, cancelEditing, selectItem } from 10 | "../../actions"; 11 | 12 | export const DeleteItemModal = connect( 13 | (state) => ({ 14 | l10nId: "modal-delete", 15 | theme: "danger", 16 | }), 17 | (dispatch, {itemId}) => ({ 18 | onConfirm: () => { dispatch(removeItem(itemId)); }, 19 | }) 20 | )(LocalizedConfirmDialog); 21 | 22 | export const CancelEditingModal = connect( 23 | (state) => ({ 24 | l10nId: "modal-cancel-editing", 25 | }), 26 | (dispatch, {nextItemId}) => { 27 | if (nextItemId !== undefined) { 28 | return {onConfirm: () => { dispatch(selectItem(nextItemId)); }}; 29 | } 30 | return {onConfirm: () => { dispatch(cancelEditing()); }}; 31 | } 32 | )(LocalizedConfirmDialog); 33 | 34 | const MODALS = { 35 | "delete": DeleteItemModal, 36 | "cancel-editing": CancelEditingModal, 37 | }; 38 | 39 | export default connect( 40 | (state) => ({ 41 | modals: MODALS, 42 | modalId: state.modal.id, 43 | modalProps: state.modal.props, 44 | }), 45 | (dispatch) => ({ 46 | onClose: () => { dispatch(hideModal()); }, 47 | }) 48 | )(ModalRoot); 49 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/open-faq.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .faq { 6 | font-size: 15px; 7 | } 8 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/open-faq.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import { ExternalLink } from "../../../widgets/link"; 11 | import { openFAQ } from "../../actions"; 12 | 13 | import styles from "./open-faq.css"; 14 | 15 | function OpenFAQ({onOpenFAQ}) { 16 | return ( 17 | 18 | 19 | fAq 20 | 21 | 22 | ); 23 | } 24 | 25 | OpenFAQ.propTypes = { 26 | onOpenFAQ: PropTypes.func.isRequired, 27 | }; 28 | 29 | export default connect( 30 | undefined, 31 | (dispatch) => ({ 32 | onOpenFAQ: () => { dispatch(openFAQ()); }, 33 | }) 34 | )(OpenFAQ); 35 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/send-feedback.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .send-feedback { 6 | font-size: 15px; 7 | } 8 | -------------------------------------------------------------------------------- /src/webextension/list/manage/containers/send-feedback.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import { PanelFooterButton } from "../../../widgets/panel"; 11 | import { sendFeedback } from "../../actions"; 12 | 13 | import styles from "./send-feedback.css"; 14 | 15 | function SendFeedback({onSendFeedback}) { 16 | return ( 17 | 18 | 20 | fEEDBACk 21 | 22 | 23 | ); 24 | } 25 | 26 | SendFeedback.propTypes = { 27 | onSendFeedback: PropTypes.func.isRequired, 28 | }; 29 | 30 | export default connect( 31 | undefined, 32 | (dispatch) => ({ 33 | onSendFeedback: () => { dispatch(sendFeedback()); }, 34 | }) 35 | )(SendFeedback); 36 | -------------------------------------------------------------------------------- /src/webextension/list/manage/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | import { Provider } from "react-redux"; 8 | import { createStore, applyMiddleware } from "redux"; 9 | import thunk from "redux-thunk"; 10 | 11 | import AppLocalizationProvider from "../../l10n"; 12 | import App from "./components/app"; 13 | import { getAccountDetails, listItems } from "../actions"; 14 | import reducer from "./reducers"; 15 | import initializeMessagePorts from "../message-ports"; 16 | import * as telemetry from "../../telemetry"; 17 | import telemetryLogger from "./telemetry"; 18 | 19 | const store = createStore(reducer, undefined, applyMiddleware( 20 | thunk, telemetryLogger 21 | )); 22 | store.dispatch(getAccountDetails()); 23 | store.dispatch(listItems()); 24 | initializeMessagePorts(store); 25 | 26 | telemetry.recordEvent("render", "manage"); 27 | 28 | ReactDOM.render( 29 | 30 | 32 | 33 | 34 | , 35 | document.getElementById("content") 36 | ); 37 | -------------------------------------------------------------------------------- /src/webextension/list/manage/reducers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { combineReducers } from "redux"; 6 | 7 | import * as actions from "../actions"; 8 | import { accountReducer, cacheReducer, listReducer } from "../reducers"; 9 | 10 | export function editorReducer(state = { 11 | editing: false, changed: false, hideHome: false, 12 | }, action) { 13 | switch (action.type) { 14 | case actions.ADD_ITEM_COMPLETED: 15 | case actions.UPDATE_ITEM_COMPLETED: 16 | if (action.interactive) { 17 | return {...state, editing: false, changed: false}; 18 | } 19 | return state; 20 | case actions.SELECT_ITEM_STARTING: 21 | return {...state, editing: false, changed: false, hideHome: state.editing}; 22 | case actions.SELECT_ITEM_COMPLETED: 23 | return {...state, hideHome: false}; 24 | case actions.START_NEW_ITEM: 25 | case actions.EDIT_CURRENT_ITEM: 26 | return {...state, editing: true}; 27 | case actions.EDITOR_CHANGED: 28 | return {...state, changed: true}; 29 | case actions.CANCEL_EDITING: 30 | return {...state, editing: false, changed: false}; 31 | default: 32 | return state; 33 | } 34 | } 35 | 36 | export function modalReducer(state = {id: null, props: null}, action) { 37 | switch (action.type) { 38 | case actions.SHOW_MODAL: 39 | return {...state, id: action.id, props: action.props}; 40 | case actions.HIDE_MODAL: 41 | return {...state, id: null, props: null}; 42 | default: 43 | return state; 44 | } 45 | } 46 | 47 | export default combineReducers({ 48 | account: accountReducer, 49 | cache: cacheReducer, 50 | list: listReducer, 51 | editor: editorReducer, 52 | modal: modalReducer, 53 | }); 54 | -------------------------------------------------------------------------------- /src/webextension/list/message-ports.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { accountDetailsUpdated, addedItem, updatedItem, removedItem } from "./actions"; 6 | 7 | let messagePort; 8 | 9 | export default function initializeMessagePorts(store) { 10 | // Listen for changes to the datastore from other sources and dispatch actions 11 | // to sync those changes with our Redux store. 12 | messagePort = browser.runtime.connect(); 13 | messagePort.onMessage.addListener((message) => { 14 | switch (message.type) { 15 | case "account_details_updated": 16 | store.dispatch(accountDetailsUpdated(message.account)); 17 | break; 18 | case "added_item": 19 | store.dispatch(addedItem(message.item)); 20 | break; 21 | case "updated_item": 22 | store.dispatch(updatedItem(message.item)); 23 | break; 24 | case "removed_item": 25 | store.dispatch(removedItem(message.id)); 26 | break; 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/webextension/list/popup/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | width: 280px; 9 | height: 360px; 10 | color: #0c0c0d; 11 | background-color: #ededf0; 12 | font: caption; 13 | font-size: 13px; 14 | -moz-user-select: none; 15 | } 16 | 17 | body > main, 18 | .app { 19 | height: 100%; 20 | } 21 | -------------------------------------------------------------------------------- /src/webextension/list/popup/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import CurrentSelection from "../containers/current-selection"; 10 | 11 | import styles from "./app.css"; 12 | 13 | export default class App extends React.Component { 14 | componentDidMount() { 15 | this._filterField.focus(true); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | 22 |
23 | { 24 | this._filterField = element; 25 | }}/> 26 |
27 |
28 |
29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/webextension/list/popup/components/item-details-panel.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .panel-body { 6 | padding: 1em; 7 | } 8 | -------------------------------------------------------------------------------- /src/webextension/list/popup/components/item-details-panel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import PropTypes from "prop-types"; 8 | 9 | import Panel, { PanelHeader, PanelBody } from "../../../widgets/panel"; 10 | import { ItemFields } from "../../components/item-fields"; 11 | 12 | import styles from "./item-details-panel.css"; 13 | 14 | export default function ItemDetailsPanel({fields, onCopy, onBack}) { 15 | return ( 16 | 17 | 18 | 19 | eNTRy dETAILs 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | ItemDetailsPanel.propTypes = { 31 | ...ItemFields.propTypes, 32 | onCopy: PropTypes.func.isRequired, 33 | onBack: PropTypes.func.isRequired, 34 | }; 35 | -------------------------------------------------------------------------------- /src/webextension/list/popup/containers/all-items-panel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ItemListPanel from "../components/item-list-panel"; 8 | import { selectItem, copiedField } from "../../actions"; 9 | import { parseFilterString, filterItem } from "../../filter"; 10 | const collator = new Intl.Collator(); 11 | 12 | export default connect( 13 | (state, ownProps) => { 14 | const filter = parseFilterString(state.list.filter.query); 15 | const items = state.cache.items 16 | .filter((i) => filterItem(filter, i)) 17 | .sort((a, b) => collator.compare(a.title, b.title)); 18 | if (items.length || state.list.filter.userEntered) { 19 | return {...ownProps, items, noResultsBanner: false}; 20 | } 21 | // An autogenerated filter that produced no results; show everything, plus 22 | // a banner informing users. 23 | return {...ownProps, items: state.cache.items, noResultsBanner: true}; 24 | }, 25 | (dispatch) => ({ 26 | onClick: (id) => { dispatch(selectItem(id)); }, 27 | onCopy: (field) => { dispatch(copiedField(field)); }, 28 | }), 29 | )(ItemListPanel); 30 | -------------------------------------------------------------------------------- /src/webextension/list/popup/containers/current-selection.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | import { connect } from "react-redux"; 8 | 9 | import { flattenItem } from "../../common"; 10 | import { selectItem, copiedField } from "../../actions"; 11 | import AllItemsPanel from "./all-items-panel"; 12 | import ItemDetailsPanel from "../components/item-details-panel"; 13 | 14 | const ConnectedItemDetailsPanel = connect( 15 | (state, ownProps) => ({ 16 | fields: flattenItem(ownProps.item), 17 | }), 18 | (dispatch) => ({ 19 | onCopy: (field) => { dispatch(copiedField(field)); }, 20 | onBack: () => { dispatch(selectItem(null)); }, 21 | }) 22 | )(ItemDetailsPanel); 23 | 24 | function CurrentSelection({item, inputRef}) { 25 | if (item) { 26 | return ; 27 | } 28 | return ; 29 | } 30 | 31 | CurrentSelection.propTypes = { 32 | item: PropTypes.object, 33 | inputRef: PropTypes.func, 34 | }; 35 | 36 | export default connect( 37 | (state) => ({ 38 | item: state.cache.currentItem, 39 | }) 40 | )(CurrentSelection); 41 | -------------------------------------------------------------------------------- /src/webextension/list/popup/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | import { Provider } from "react-redux"; 8 | import { createStore, applyMiddleware } from "redux"; 9 | import thunk from "redux-thunk"; 10 | 11 | import AppLocalizationProvider from "../../l10n"; 12 | import App from "./components/app"; 13 | import { filterItems, listItems } from "../actions"; 14 | import reducer from "./reducers"; 15 | import initializeMessagePorts from "../message-ports"; 16 | import * as telemetry from "../../telemetry"; 17 | import telemetryLogger from "./telemetry"; 18 | 19 | const store = createStore(reducer, undefined, applyMiddleware( 20 | thunk, telemetryLogger 21 | )); 22 | 23 | // Pre-fill the URL of the current tab. 24 | // XXX: Eventually, we'll probably want to put this in another file. 25 | browser.tabs.query({ active: true, currentWindow: true }).then((result) => { 26 | const validProtocols = ["http:", "https:"]; 27 | const url = new URL(result[0].url); 28 | if (validProtocols.includes(url.protocol)) { 29 | store.dispatch(filterItems(url.hostname, false)); 30 | } 31 | }); 32 | 33 | store.dispatch(listItems()); 34 | initializeMessagePorts(store); 35 | 36 | telemetry.recordEvent("render", "doorhanger"); 37 | 38 | ReactDOM.render( 39 | 40 | 42 | 43 | 44 | , 45 | document.getElementById("content") 46 | ); 47 | -------------------------------------------------------------------------------- /src/webextension/list/popup/reducers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { combineReducers } from "redux"; 6 | 7 | import { cacheReducer, listReducer } from "../reducers"; 8 | 9 | export default combineReducers({ 10 | cache: cacheReducer, 11 | list: listReducer, 12 | }); 13 | -------------------------------------------------------------------------------- /src/webextension/list/popup/telemetry.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import * as actions from "../actions"; 6 | import * as telemetry from "../../telemetry"; 7 | 8 | export default (store) => (next) => (action) => { 9 | try { 10 | switch (action.type) { 11 | case actions.SELECT_ITEM_COMPLETED: 12 | if (action.item) { 13 | telemetry.recordEvent("itemSelected", "doorhanger", 14 | {itemid: action.item.id}); 15 | } 16 | break; 17 | case actions.COPIED_FIELD: 18 | telemetry.recordEvent(`${action.field}Copied`, "doorhanger"); 19 | break; 20 | } 21 | } catch (e) { 22 | // eslint-disable-next-line no-console 23 | console.error("Unable to record telemetry event", e); 24 | } 25 | 26 | return next(action); 27 | }; 28 | -------------------------------------------------------------------------------- /src/webextension/locales/README.md: -------------------------------------------------------------------------------- 1 | # Adding Locales 2 | 3 | To add a locale, just create a new directory with the name of your locale and 4 | add the necessary translations to it. For development builds, this locale will 5 | automatically be included. However, for production builds, you should **add your 6 | locale to locales.json**. This allows you to gradually work on adding 7 | translations to your locale without having to have it production-ready 8 | immediately. 9 | -------------------------------------------------------------------------------- /src/webextension/locales/en-US/common.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | product-title = Lockbox 6 | product-tagline = The simple way to store, retrieve and manage website login info 7 | 8 | product-disclaimer = This is just a prototype and not officially supported. Any data stored or functionality is not guaranteed to remain. 9 | 10 | # when Kinto is supported, the above should be changed to: 11 | # Creating a Firefox account - or adding Lockbox to an existing account - protects your logins 12 | # with the strongest encryption available and syncs your Lockbox info across accounts. 13 | 14 | 15 | product-action-signin = Sign In 16 | product-action-prefs = Preferences 17 | -------------------------------------------------------------------------------- /src/webextension/locales/en-US/firstrun.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | document = 6 | .title = Welcome to Lockbox 7 | 8 | firstrun-intro-title = Welcome to { product-title } 9 | firstrun-intro-tagline = { product-tagline } 10 | 11 | firstrun-using-guest-title = New to Lockbox? 12 | 13 | firstrun-using-guest-action = Get Started 14 | 15 | firstrun-using-returning-title = Returning User? 16 | 17 | firstrun-using-returning-action = Sign in to your Firefox Account 18 | -------------------------------------------------------------------------------- /src/webextension/locales/en-US/settings.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | settings-local-reset-title = Reset Lockbox 6 | settings-local-reset-description = This resets Lockbox to its uninitialized state. Once performed, it cannot be undone. 7 | settings-local-reset-button = 💥💣 Reset 💣💥 8 | 9 | modal-local-reset = Are you sure you wish to reset Lockbox? 10 | .confirmLabel = Yes, reset it 11 | .cancelLabel = No, do not reset 12 | -------------------------------------------------------------------------------- /src/webextension/locales/en-US/unlock.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | document = 6 | .title = Unlock Lockbox 7 | 8 | unlock-title = { product-title } 9 | unlock-tagline = { product-tagline } 10 | 11 | unlock-action-signin = { product-action-signin } 12 | unlock-action-prefs = { product-action-prefs } 13 | -------------------------------------------------------------------------------- /src/webextension/locales/en-US/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copy 6 | copy-to-clipboard-copied = ✔ Copied 7 | 8 | filter-input-clear = 9 | .title = Clear 10 | 11 | modal-root = 12 | .contentLabel = Modal dialog 13 | 14 | password-input-show = 15 | .title = Show 16 | password-input-hide = 17 | .title = Hide 18 | 19 | panel-back-button = 20 | .alt = Go Back 21 | -------------------------------------------------------------------------------- /src/webextension/locales/fr-FR/common.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | product-title = Lockbox 6 | product-tagline = Une façon simple de stocker, retrouver et de gérer les infos de connexion de site web 7 | 8 | # Lors ce que Kinto est supporté, le texte ci-dessus devrait être modifié comme suit: 9 | # Créer un compte Firefox - ou ajouter Lockbox à un compte existant - protège vos identifiants 10 | # avec le chiffrage le plus puissant disponible et synchronisations de vos infos Lockbox à travers vos comptes. 11 | 12 | 13 | product-action-signin = S'identifier 14 | product-action-prefs = Réglages 15 | -------------------------------------------------------------------------------- /src/webextension/locales/fr-FR/firstrun.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | document 6 | .title = Bienvenue dans Lockbox 7 | 8 | firstrun-intro-title = Bienvenue dans { product-title } 9 | firstrun-intro-tagline = { product-tagline } 10 | 11 | firstrun-using-guest-title = Nouveau dans Lockbox? 12 | 13 | firstrun-using-guest-action = Commençons 14 | 15 | firstrun-using-returning-title = Retour utilisateurs ? 16 | 17 | firstrun-using-returning-action = Connectez-vous à votre compte Firefox 18 | -------------------------------------------------------------------------------- /src/webextension/locales/fr-FR/settings.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | settings-local-reset-title = Réinitialiser Lockbox 6 | settings-local-reset-description = Celà va réinitialiser Lockbox à son état initial. Une fois celà fait, il est impossible d'annuler l'action. 7 | settings-local-reset-button = 💥💣 Réinitialisation 💣💥 8 | 9 | modal-local-reset = Êtes-vous sûr de vouloir procéder à une réinitialisation de Lockbox? 10 | .confirmLabel = Oui, le réinitialiser 11 | .cancelLabel = Non, ne pas réinitialiser 12 | -------------------------------------------------------------------------------- /src/webextension/locales/fr-FR/unlock.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | document = 6 | .title = Débloquer Lockbox 7 | 8 | unlock-title = { product-title } 9 | unlock-tagline = { product-tagline } 10 | 11 | unlock-action-signin = { product-action-signin } 12 | unlock-action-prefs = { product-action-prefs } 13 | -------------------------------------------------------------------------------- /src/webextension/locales/fr-FR/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copie 6 | copy-to-clipboard-copied = ✔ Copié 7 | 8 | filter-input-clear = 9 | .title = Éffacer 10 | 11 | modal-root = 12 | .contentLabel = Dialogue modal 13 | 14 | password-input-show = 15 | .title = Afficher 16 | password-input-hide = 17 | .title = Masquer 18 | 19 | panel-back-button = 20 | .alt = Retour arrière 21 | -------------------------------------------------------------------------------- /src/webextension/locales/locales.json: -------------------------------------------------------------------------------- 1 | ["en-US", "fr-FR"] 2 | -------------------------------------------------------------------------------- /src/webextension/manifest.json.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "{{title}}", 4 | "version": "{{version}}", 5 | 6 | "description": "{{description}}", 7 | 8 | "applications": { 9 | "gecko": { 10 | "id": "{{id}}" 11 | } 12 | }, 13 | 14 | "background": { 15 | "scripts": ["background.js"] 16 | }, 17 | 18 | "browser_action": { 19 | "default_icon": { 20 | "32": "icons/lb_locked.svg" 21 | }, 22 | "default_title": "Lockbox", 23 | "browser_style": false 24 | }, 25 | 26 | "commands": { 27 | "_execute_browser_action": { 28 | "suggested_key": { 29 | "default": "Ctrl+Shift+L" 30 | } 31 | } 32 | }, 33 | 34 | "permissions": [ 35 | "identity", 36 | "storage", 37 | "tabs", 38 | "clipboardWrite" 39 | ], 40 | 41 | "options_ui": { 42 | "page": "settings/index.html", 43 | "browser_style": false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/webextension/settings/actions.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import * as telemetry from "../telemetry"; 6 | 7 | export const LOCAL_RESET_STARTING = Symbol("LOCAL_RESET_STARTING"); 8 | export const LOCAL_RESET_COMPLETED = Symbol("LOCAL_RESET_COMPLETED"); 9 | 10 | export const SHOW_MODAL = Symbol("SHOW_MODAL"); 11 | export const HIDE_MODAL = Symbol("HIDE_MODAL"); 12 | 13 | let nextActionId = 0; 14 | 15 | // Reset actions 16 | 17 | export function localReset() { 18 | return async (dispatch) => { 19 | const actionId = nextActionId++; 20 | dispatch(localResetStarting(actionId)); 21 | await browser.runtime.sendMessage({ 22 | type: "reset", 23 | }); 24 | dispatch(localResetCompleted(actionId)); 25 | }; 26 | } 27 | 28 | function localResetStarting(actionId) { 29 | return { 30 | type: LOCAL_RESET_STARTING, 31 | actionId, 32 | }; 33 | } 34 | 35 | function localResetCompleted(actionId) { 36 | telemetry.recordEvent("resetCompleted", "settings"); 37 | return { 38 | type: LOCAL_RESET_COMPLETED, 39 | actionId, 40 | }; 41 | } 42 | 43 | export function requestLocalReset() { 44 | telemetry.recordEvent("resetRequested", "settings"); 45 | return showModal("local-reset"); 46 | } 47 | 48 | // Modal actions 49 | 50 | function showModal(id, props = {}) { 51 | return { 52 | type: SHOW_MODAL, 53 | id, 54 | props, 55 | }; 56 | } 57 | 58 | export function hideModal() { 59 | return { 60 | type: HIDE_MODAL, 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/webextension/settings/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | html { 6 | display: flex; 7 | } 8 | 9 | body { 10 | padding: 0; 11 | margin: 1em; 12 | min-height: 150px; 13 | color: #0c0c0d; 14 | background-color: #f9f9fa; 15 | font: caption; 16 | font-size: 13px; 17 | -moz-user-select: none; 18 | display: flex; 19 | } 20 | -------------------------------------------------------------------------------- /src/webextension/settings/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | 8 | import LocalReset from "./local-reset"; 9 | import ModalRoot from "../containers/modals"; 10 | 11 | import "./app.css"; 12 | 13 | export default function App() { 14 | return ( 15 |
16 | 17 |

nOTe: tHIs eXTENSION Is nOt mAINTAINEd

18 |
19 | 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/webextension/settings/components/local-reset.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import { 11 | requestLocalReset, 12 | } from "../actions"; 13 | import Button from "../../widgets/button"; 14 | 15 | export function LocalReset({onReset}) { 16 | return ( 17 |
18 | 19 |

rESEt lOCKBOx

20 |
21 | 22 |

dO tHe fACTORy rESEt

23 |
24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | LocalReset.propTypes = { 31 | onReset: PropTypes.func.isRequired, 32 | }; 33 | 34 | export default connect( 35 | (state) => ({ 36 | }), 37 | (dispatch) => ({ 38 | onReset: () => { dispatch(requestLocalReset()); }, 39 | }) 40 | )(LocalReset); 41 | -------------------------------------------------------------------------------- /src/webextension/settings/containers/modals.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ModalRoot from "../../widgets/modal-root"; 8 | import { LocalizedConfirmDialog } from "../../widgets/dialog-box"; 9 | import { hideModal, localReset } from "../actions"; 10 | 11 | export const LocalResetModal = connect( 12 | (state) => ({ 13 | l10nId: "modal-local-reset", 14 | theme: "danger", 15 | }), 16 | (dispatch) => ({ 17 | onConfirm: () => { dispatch(localReset()); }, 18 | }) 19 | )(LocalizedConfirmDialog); 20 | 21 | const MODALS = { 22 | "local-reset": LocalResetModal, 23 | }; 24 | 25 | export default connect( 26 | (state) => ({ 27 | modals: MODALS, 28 | modalId: state.modal.id, 29 | modalProps: state.modal.props, 30 | }), 31 | (dispatch) => ({ 32 | onClose: () => { dispatch(hideModal()); }, 33 | }) 34 | )(ModalRoot); 35 | -------------------------------------------------------------------------------- /src/webextension/settings/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | import { Provider } from "react-redux"; 8 | import { createStore, applyMiddleware } from "redux"; 9 | import thunk from "redux-thunk"; 10 | 11 | import AppLocalizationProvider from "../l10n"; 12 | import App from "./components/app"; 13 | import reducer from "./reducers"; 14 | 15 | const store = createStore(reducer, undefined, applyMiddleware(thunk)); 16 | 17 | ReactDOM.render( 18 | 19 | 21 | 22 | 23 | , 24 | document.getElementById("content") 25 | ); 26 | -------------------------------------------------------------------------------- /src/webextension/settings/reducers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // keep in sync with 6 | 7 | import { combineReducers } from "redux"; 8 | 9 | import { 10 | SHOW_MODAL, HIDE_MODAL, 11 | } from "./actions"; 12 | 13 | export function modalReducer(state = {id: null, props: null}, action) { 14 | switch (action.type) { 15 | case SHOW_MODAL: 16 | return {...state, id: action.id, props: action.props}; 17 | case HIDE_MODAL: 18 | return {...state, id: null, props: null}; 19 | default: 20 | return state; 21 | } 22 | } 23 | 24 | const reducer = combineReducers({ 25 | modal: modalReducer, 26 | }); 27 | 28 | export default reducer; 29 | -------------------------------------------------------------------------------- /src/webextension/telemetry.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export async function recordEvent(method, object, extra) { 6 | return browser.runtime.sendMessage({ 7 | type: "proxy_telemetry_event", method, object, extra, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/webextension/unlock/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | width: 280px; 9 | height: 360px; 10 | color: #0c0c0d; 11 | background-color: #ededf0; 12 | font: caption; 13 | font-size: 13px; 14 | -moz-user-select: none; 15 | } 16 | 17 | body > main { 18 | height: 100%; 19 | } 20 | 21 | .lockie { 22 | display: block; 23 | margin: 4em auto 0; 24 | width: 200px; 25 | } 26 | 27 | .unlock-content { 28 | margin: 1em auto 3em; 29 | width: 240px; 30 | display: flex; 31 | flex-flow: column nowrap; 32 | align-items: center; 33 | } 34 | 35 | .unlock-content > h1 { 36 | font-size: 2.5em; 37 | font-weight: 300; 38 | text-align: center; 39 | margin: 0 0 0.25em; 40 | } 41 | 42 | .unlock-content > h2 { 43 | font-size: 1.15em; 44 | font-weight: 300; 45 | font-style: italic; 46 | text-align: center; 47 | text-transform: lowercase; 48 | margin: 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/webextension/unlock/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import Panel, { PanelBody, PanelFooter, PanelFooterButton } 10 | from "../../widgets/panel"; 11 | import * as telemetry from "../../telemetry"; 12 | 13 | import styles from "./app.css"; 14 | 15 | export default function App() { 16 | const imgSrc = browser.extension.getURL("/images/nessie_v2.svg"); 17 | 18 | const doSignIn = async () => { 19 | telemetry.recordEvent("click", "unlockSignin"); 20 | browser.runtime.sendMessage({ 21 | type: "signin", 22 | view: "manage", 23 | }); 24 | }; 25 | const doPrefs = async () => { 26 | browser.runtime.openOptionsPage(); 27 | window.close(); 28 | }; 29 | 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |

lOCKBOx

39 |
40 | 41 |

lOCKBOx tAGLINe

42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | sIGn iN 50 | 51 | 52 | 53 | 54 | pREFs 55 | 56 | 57 | 58 |
59 |
60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/webextension/unlock/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | 8 | import AppLocalizationProvider from "../l10n"; 9 | import App from "./components/app"; 10 | import * as telemetry from "../telemetry"; 11 | 12 | // This is the closest approximation we can get to a toolbar click when the 13 | // popup is registered. 14 | telemetry.recordEvent("render", "popupUnlock"); 15 | 16 | ReactDOM.render( 17 | 19 | 20 | , 21 | document.getElementById("content") 22 | ); 23 | -------------------------------------------------------------------------------- /src/webextension/widgets/README.md: -------------------------------------------------------------------------------- 1 | # Common Widgets 2 | 3 | This directory contains the common widgets used throughout the extension. If you need a general-purpose UI widget, it should probably go in here. 4 | 5 | ## Visuals 6 | 7 | All the widgets here are designed to follow the [Photon Design System][photon], though we occasionally make modifications to better suit our needs. 8 | 9 | ## APIs 10 | 11 | Widgets should be designed as React components (no Redux, since that would add extra requirements to how a view's Redux store is designed), and should generally contain a few common options/methods to allow users of the widget to work with it as needed. 12 | 13 | ### Custom classNames 14 | 15 | Widgets should have a `className` argument in their constructor that allows users to customize the styling of the widget for a particular instance. 16 | 17 | ### focus() method 18 | 19 | Focusable widgets (buttons, form elements, links, etc) should have a `focus()` method that focuses the relevant HTML element. If the widget also contains selectable text, `focus()` should take an optional boolean value (defaulting to `false`) to focus *and* select the text. 20 | 21 | ### Extra props 22 | 23 | Finally, widgets should accept extra props in their constructor and apply them to the most-relevant subcomponent. This allows users to apply things like ARIA labels and other props to the widget. 24 | 25 | [photon]: https://design.firefox.com/photon/welcome.html 26 | -------------------------------------------------------------------------------- /src/webextension/widgets/breadcrumbs.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .breadcrumbs { 6 | font-size: 15px; 7 | } 8 | 9 | .crumb { 10 | font-size: 15px; 11 | } 12 | 13 | .crumb:not(:first-child)::before { 14 | content: ""; 15 | display: inline-block; 16 | width: 5px; 17 | height: 9px; 18 | background-image: url(/icons/chevron-right.svg); 19 | background-repeat: no-repeat; 20 | background-size: 5px 9px; 21 | margin: 0 16px; 22 | } 23 | -------------------------------------------------------------------------------- /src/webextension/widgets/breadcrumbs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | import { Link } from "./link"; 10 | 11 | import styles from "./breadcrumbs.css"; 12 | 13 | export default function Breadcrumbs({className, ...props}) { 14 | return ( 15 |
16 | ); 17 | } 18 | 19 | Breadcrumbs.propTypes = { 20 | className: PropTypes.string, 21 | }; 22 | 23 | Breadcrumbs.defaultProps = { 24 | className: "", 25 | }; 26 | 27 | export function Crumb({className, onClick, ...props}) { 28 | const finalClassName = classNames([styles.crumb, className]); 29 | if (onClick) { 30 | return ( 31 | 32 | ); 33 | } 34 | 35 | return ( 36 | 37 | ); 38 | } 39 | 40 | Crumb.propTypes = { 41 | className: PropTypes.string, 42 | onClick: PropTypes.func, 43 | }; 44 | 45 | Crumb.defaultProps = { 46 | className: "", 47 | }; 48 | -------------------------------------------------------------------------------- /src/webextension/widgets/button.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./button.css"; 11 | 12 | const THEME_CLASS_NAME = { 13 | primary: `${styles.primaryTheme}`, 14 | normal: `${styles.normalTheme}`, 15 | ghost: `${styles.ghostTheme}`, 16 | danger: `${styles.dangerTheme}`, 17 | }; 18 | 19 | const SIZE_CLASS_NAME = { 20 | puffy: `${styles.puffySize}`, 21 | normal: `${styles.normalSize}`, 22 | micro: `${styles.microSize}`, 23 | wide: `${styles.normalSize} ${styles.wideSize}`, 24 | }; 25 | 26 | export default class Button extends React.Component { 27 | static get propTypes() { 28 | return { 29 | theme: PropTypes.oneOf(Object.keys(THEME_CLASS_NAME)), 30 | size: PropTypes.oneOf(Object.keys(SIZE_CLASS_NAME)), 31 | className: PropTypes.string, 32 | }; 33 | } 34 | 35 | static get defaultProps() { 36 | return { 37 | theme: "normal", 38 | size: "normal", 39 | className: "", 40 | }; 41 | } 42 | 43 | focus() { 44 | this.buttonElement.focus(); 45 | } 46 | 47 | render() { 48 | const {className, theme, size, ...props} = this.props; 49 | return ( 50 | 45 | ); 46 | } 47 | } 48 | 49 | // XXX: External links go to a real URL, and we should probably indicate that to 50 | // the user (by Firefox showing the URL in the bottom of the window), even if 51 | // the actual loading of the URL happens in a Redux action. 52 | 53 | export class ExternalLink extends Link { 54 | get baseClassName() { 55 | return classNames([styles.link, styles.external]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/webextension/widgets/modal-root.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .modal { 6 | border-radius: 4px; 7 | box-shadow: 0 4px 16px 0 rgba(12, 12, 13, 0.1); 8 | min-width: 300px; 9 | color: #0c0c0d; 10 | background-color: #f9f9fa; 11 | } 12 | 13 | .overlay { 14 | position: fixed; 15 | top: 0; 16 | bottom: 0; 17 | left: 0; 18 | right: 0; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | background-color: rgba(0, 0, 0, 0.5); 23 | } 24 | -------------------------------------------------------------------------------- /src/webextension/widgets/modal-root.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import Modal from "react-modal"; 9 | 10 | import styles from "./modal-root.css"; 11 | 12 | export default function ModalRoot({modals, modalId, modalProps, onClose}) { 13 | let modal = null; 14 | if (modalId) { 15 | const CurrentModal = modals[modalId]; 16 | modal = ; 17 | } 18 | 19 | return ( 20 | 21 | 23 | {modal} 24 | 25 | 26 | ); 27 | } 28 | 29 | ModalRoot.propTypes = { 30 | modals: PropTypes.objectOf(PropTypes.func).isRequired, 31 | modalId: PropTypes.string, 32 | modalProps: PropTypes.object, 33 | onClose: PropTypes.func.isRequired, 34 | }; 35 | -------------------------------------------------------------------------------- /src/webextension/widgets/scrolling-list.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .scrolling-list { 6 | margin: 0; 7 | padding: 0; 8 | list-style: none; 9 | overflow-x: hidden; 10 | overflow-y: auto; 11 | } 12 | 13 | .scrolling-list:-moz-focusring { 14 | outline-style: none; 15 | } 16 | 17 | .styled-item { 18 | color: #0c0c0d; 19 | background-color: #f9f9fa; 20 | } 21 | 22 | .scrolling-list > .styled-item[data-selected] { 23 | background-color: #b1b1b3; 24 | } 25 | 26 | .scrolling-list:focus > .styled-item[data-selected] { 27 | color: #ffffff; 28 | background-color: #0060df; 29 | } 30 | -------------------------------------------------------------------------------- /src/webextension/widgets/stack.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* XXX: Sadly, I can't come up with a better way to make something that works 6 | like a than by just using XUL's box model... */ 7 | 8 | .stack { 9 | display: -moz-stack; 10 | } 11 | 12 | .stretch { 13 | height: 100%; 14 | width: 100%; 15 | } 16 | 17 | .stack-item { 18 | display: -moz-box; 19 | visibility: hidden; 20 | } 21 | 22 | .stack-item[data-selected] { 23 | visibility: visible; 24 | } 25 | 26 | .stack-item > * { 27 | -moz-box-flex: 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/webextension/widgets/stack.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./stack.css"; 11 | 12 | export default class Stack extends React.Component { 13 | static get propTypes() { 14 | return { 15 | children: PropTypes.node, 16 | selectedIndex: PropTypes.number, 17 | stretch: PropTypes.bool, 18 | }; 19 | } 20 | 21 | static get defaultProps() { 22 | return { 23 | selectedIndex: 0, 24 | stretch: false, 25 | }; 26 | } 27 | 28 | render() { 29 | const {children, selectedIndex, stretch, ...props} = this.props; 30 | return ( 31 |
32 |
33 | {React.Children.map(children, (child, i) => { 34 | const extraProps = {}; 35 | if (i === selectedIndex) { 36 | extraProps["data-selected"] = true; 37 | } 38 | 39 | return ( 40 | 41 | {child} 42 | 43 | ); 44 | })} 45 |
46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/webextension/widgets/text-area.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .text-area { 6 | padding: 8px; 7 | font: caption; 8 | font-size: 13px; 9 | color: #0c0c0d; 10 | background-color: #ffffff; 11 | border: 1px solid rgba(12, 12, 13, 0.3); 12 | border-radius: 2px; 13 | } 14 | 15 | .text-area:hover { 16 | border-color: rgba(12, 12, 13, 0.5); 17 | } 18 | 19 | .text-area:focus { 20 | border-color: #0a84ff; 21 | /* Note: This is adapted from the Photon button spec, since the input fields 22 | spec doesn't define this. */ 23 | box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); 24 | } 25 | -------------------------------------------------------------------------------- /src/webextension/widgets/text-area.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./text-area.css"; 11 | 12 | export default class TextArea extends React.Component { 13 | static get propTypes() { 14 | return { 15 | className: PropTypes.string, 16 | }; 17 | } 18 | 19 | static get defaultProps() { 20 | return { 21 | className: "", 22 | }; 23 | } 24 | 25 | focus(select = false) { 26 | this.textAreaElement.focus(); 27 | if (select) { 28 | this.textAreaElement.setSelectionRange( 29 | 0, this.textAreaElement.value.length 30 | ); 31 | } 32 | } 33 | 34 | render() { 35 | const {className, ...props} = this.props; 36 | return ( 37 |