diff --git a/.travis.yml b/.travis.yml
index 737bc2a6..a31d5514 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,8 +26,6 @@ jobs:
- nvm install node
- pip install tox
before_script:
- - jwm &
- - sleep 10
- npm install
- npm run package
script: tox
diff --git a/docs/css/extra.css b/docs/css/extra.css
index 1d113bdd..7ce898cc 100644
--- a/docs/css/extra.css
+++ b/docs/css/extra.css
@@ -8,7 +8,7 @@ h1 {
a.button-link {
display: block;
- margin: 0px auto;
+ margin: .5em auto;
padding: 0.5em;
width: 12em;
color: white;
@@ -17,3 +17,14 @@ a.button-link {
border-radius: 4px;
text-align: center;
}
+
+div.right {
+ float: right;
+ clear: right;
+ width: 40%;
+ padding-left: 2em;
+}
+
+.rst-content .admonition-title::before {
+ display: none; !important
+}
diff --git a/docs/faqs.md b/docs/faqs.md
new file mode 100644
index 00000000..6e2496a7
--- /dev/null
+++ b/docs/faqs.md
@@ -0,0 +1,53 @@
+## What’s the difference between Lockbox and the Firefox password manager?
+
+Firefox password manager is the built-in feature that saves and autofills website login information. You can protect these logins with a master password.
+
+Lockbox is a stand-alone password manager extension that you can secure with a Firefox Account for newer encryption than what is offered with password manager.
+
+The alpha version of Lockbox lets you create, store and manage entries (a site’s username and password) and copy and paste login information. We realize managing passwords this way may feel very manual, but we plan to add features like autofill and password generation in future releases. We are also working to build cloud backup, create a mobile app, and support multiple browsers.
+
+## Can I use Lockbox and the Firefox password manager at the same time?
+
+No. When you install Lockbox, Firefox automatically disables the password manager. If you disable or delete Lockbox, Firefox re-enables password manager on the browser’s next restart.
+
+## If I disable or delete Lockbox, will login information from my entries transfer into the password manager?
+
+No. But login information you previously added to password manager will still be available.
+
+## Does Lockbox import my information from password manager?
+
+Not in the current alpha version.
+
+## What security technology does Lockbox use?
+
+When you protect Lockbox with a Firefox Account, Lockbox uses [AES256-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) encryption, a tamper-resistent block cipher technology, to protect your data. Lockbox also uses [HMAC SHA-256](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) to “hash” searchable data for additional security.
+
+## How do I disable or delete Lockbox?
+
+1. Click the menu button ![menu](https://user-images.githubusercontent.com/49511/33676293-a3470a0c-da72-11e7-9f93-2f054bc16cb9.png)
+ and choose Add-ons ![extensions](https://user-images.githubusercontent.com/49511/33676294-a35f8b5e-da72-11e7-8bfa-186708b20aab.png)
+2. ![disable](https://user-images.githubusercontent.com/49511/33676295-a3732b32-da72-11e7-9920-43c8b6d25134.png) or ![remove](https://user-images.githubusercontent.com/49511/33676296-a38aa708-da72-11e7-9c15-7960d17422b7.png) Lockbox
+
+## If I delete Lockbox, what happens to the entries I’ve saved?
+
+The alpha version of Lockbox doesn’t offer backup or synchronization. You’ll need to re-add login information to Lockbox after installing it again.
+
+## What if I forget my Firefox Account password?
+
+Firefox Accounts do not offer password recovery functionality. If you added a Firefox Account to Lockbox and forget your password, you’ll need to reset your Firefox Account. Note that you’ll lose all saved Lockbox entries.
+
+## Will Lockbox work with other password managers?
+
+The alpha version of Lockbox hasn’t been tested widely with other password managers. We recommend disabling or deleting other password managers from Firefox before installing Lockbox.
+
+## Do Lockbox entries sync to other computers with Lockbox installed?
+
+Yes, if you secure Lockbox with a Firefox Account.
+
+## Can I try Lockbox if I don’t have a Mozilla.com email address?
+
+Sure. To get started, click the Install Lockbox button on the Introduction page. Note that this is an alpha version. Features and functionality will change as we continue developing Lockbox.
+
+## If I already have a Firefox Account, can I create a separate Firefox Account to use only with Lockbox?
+
+You can create a new account using a different email address than the one you use for your existing Firefox Account. Note that the new Firefox Account won’t sync bookmarks, history and open tabs unless you use it to sign into the browser.
diff --git a/docs/images/tour-01.welcome.png b/docs/images/tour-01.welcome.png
new file mode 100644
index 00000000..5c1bc28d
Binary files /dev/null and b/docs/images/tour-01.welcome.png differ
diff --git a/docs/images/tour-02.create-entry.png b/docs/images/tour-02.create-entry.png
new file mode 100644
index 00000000..1af1a580
Binary files /dev/null and b/docs/images/tour-02.create-entry.png differ
diff --git a/docs/images/tour-03.doorhanger-search.png b/docs/images/tour-03.doorhanger-search.png
new file mode 100644
index 00000000..b970619d
Binary files /dev/null and b/docs/images/tour-03.doorhanger-search.png differ
diff --git a/docs/images/tour-04.signup-fxa.png b/docs/images/tour-04.signup-fxa.png
new file mode 100644
index 00000000..19bca2d5
Binary files /dev/null and b/docs/images/tour-04.signup-fxa.png differ
diff --git a/docs/index.md b/docs/index.md
index 7f3a3dde..6b48ab73 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,14 +1,45 @@
-# Lockbox Extension
+# Lockbox for desktop
-*This is just one component of the Lockbox product. Please see the
-[Lockbox website][org-website] for more documentation and context.*
+!!! right "Install the extension"
+ [Install Lockbox][install-link]{: .button-link }
+ Have questions about how Lockbox works? [Check out the FAQs][faq-link]
-Click below to install the Lockbox extension:
+!!! right "Contribute"
+ You can also contribute by:
+
+ - Developing code
+ - Reporting bugs
+
+ [Learn how to get started][contribute-link]
-[Install][install-link]{: .button-link }
+The Lockbox extension is a simple, stand-alone password manager that works
+with Firefox for desktop. It’s the first of several planned experiments
+designed to help us test and improve password management and online
+security.
-**Note: This is a rapidly evolving prototype that will change. Any data stored
-is not guaranteed to be retained in future updates.**
+Install it and sign in with your Firefox Account to encrypt your data with
+tamper-resistant block cipher technology. Then [share feedback
+here](feedback-link).
+
+## Get Started
+
+1. Install Lockbox, and it will automatically disable Firefox’s password manager.
+ ![install lockbox](./images/tour-01.welcome.png)
+
+2. Create an entry with a website name, URL, username, and password.
+ ![create an entry](./images/tour-02.create-entry.png)
+
+3. Search or browse in the toolbar menu or on the full tab to find the password you need.
+ ![search from doorhanger](./images/tour-03.doorhanger-search.png)
+
+4. Sign up or sign in with a Firefox Account to encrypt your entries.
+ ![sinup for fxa](./images/tour-04.signup-fxa.png)
+
+_This is just one component of the Lockbox product. Please see the [Lockbox
+website][website-link] for more documentation and context._
[install-link]: https://testpilot.firefox.com/files/lockbox@mozilla.com/latest
-[org-website]: https://mozilla-lockbox.github.io/
+[faq-link]: /faqs/
+[contribute-link]: /contributing/
+[website-link]: https://mozilla-lockbox.github.io/
+[feedback-link]: https://qsurvey.mozilla.com/s3/Lockbox-Input
diff --git a/docs/metrics.md b/docs/metrics.md
index 813551a0..513cd2de 100644
--- a/docs/metrics.md
+++ b/docs/metrics.md
@@ -144,9 +144,11 @@ All events are currently implemented under the **category: lockboxV0**. The `ext
9. `feedbackClick` fires when the user clicks the "Send Feedback" button. **objects**: manage
-10. `resetRequested` fires when the user clicks the "Reset" button in the Lockbox settings. **objects**: settings
+10. `faqClick` fires when the user clicks the "FAQ" button. **objects**: manage
-11. `resetCompleted` fires when the user completes a reset of their Lockbox data in the Lockbox settings. **objects**: settings
+11. `resetRequested` fires when the user clicks the "Reset" button in the Lockbox settings. **objects**: settings
+
+12. `resetCompleted` fires when the user completes a reset of their Lockbox data in the Lockbox settings. **objects**: settings
## List of Planned Events
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 4d1cde84..d9dc94ad 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,28 @@
# Lockbox Release Notes
+## 0.1.4-alpha
+
+_Date: 2017-12-11_
+
+### What's New
+
+- Access your saved Lockbox entries from a doorhanger experience ([#338](https://github.com/mozilla-lockbox/lockbox-extension/pull/362))
+- Secure your Lockbox with a Firefox Account ([#362](https://github.com/mozilla-lockbox/lockbox-extension/pull/362))
+- See the visual design and polish come together for the entire experience ([#351](https://github.com/mozilla-lockbox/lockbox-extension/pull/351))
+- Get help and instructions when you first get started ([#392](https://github.com/mozilla-lockbox/lockbox-extension/issues/392))
+- Get additional support from the updated [Lockbox website](https://mozilla-lockbox.github.io/lockbox-extension/), including the FAQ ([#345](https://github.com/mozilla-lockbox/lockbox-extension/issues/345))
+
+
+### What's Fixed
+
+### Known Issues
+
+* **Any existing Lockbox entries from previous versions have been removed.** Previous versions were storing and encrypting data differently than we are now. In order to add our new security features your old data can no longer be read/accessed and you'll see an empty state after you upgrade.
+* Once you link a Firefox Account to Lockbox, you cannot unlink it from that account.
+* Once you link a Firefox Account to Lockbox, signing in with a different account can render Lockbox unusable until you quit and restart Firefox.
+* Once you link a Firefox Account to Lockbox, resetting your Firefox Account password through "forgot your password" will render all your logins inaccessible; the only recourse is to reset Lockbox and start over.
+* Firefox's default prompt to save logins is only disabled on new installs of this extension; updating Lockbox will not change your current Firefox preferences.
+
## 0.1.3-alpha
_Date: 2017-11-29_
diff --git a/mkdocs.yml b/mkdocs.yml
index 4847fa4f..e5de7878 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,14 +8,12 @@ extra_css:
markdown_extensions:
- attr_list
+- admonition
pages:
- 'Introduction': 'index.md'
-- 'Installing': 'install.md'
-- 'User Guide': 'user-guide.md'
- 'Release Notes': 'release-notes.md'
-- 'Contributing': 'contributing.md'
-- 'Code of Conduct': 'code_of_conduct.md'
-- 'API Guide': 'api.md'
-- 'Metrics Guide': 'metrics.md'
-- 'Releases': 'releases.md'
+- 'FAQs': 'faqs.md'
+- 'Contribute': 'contributing.md'
+- 'Source Code': 'install.md'
+- 'Metrics': 'metrics.md'
diff --git a/package-lock.json b/package-lock.json
index e6c04c8f..ba39ec07 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "lockbox",
- "version": "0.1.3-alpha",
+ "version": "0.1.4-alpha",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -9771,7 +9771,7 @@
}
},
"lockbox-datastore": {
- "version": "git+https://github.com/mozilla-lockbox/lockbox-datastore.git#dff86556265e63ae50a05f9cec2333e671f4f843",
+ "version": "git+https://github.com/linuxwolf/lockbox-datastore.git#c7c72a134800c52c98b1dbd91f889b6cf7dbf2be",
"requires": {
"dexie": "1.5.1",
"fake-indexeddb": "2.0.3",
diff --git a/package.json b/package.json
index 779b7174..91f9eebd 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,9 @@
"title": "Lockbox",
"name": "lockbox",
"id": "lockbox@mozilla.com",
- "version": "0.1.3-alpha",
+ "version": "0.1.4-alpha",
"main": "dist/bootstrap.js",
- "description": "A Lockbox extension for Firefox",
+ "description": "The simple way to store, retrieve and manage website login info",
"author": "Lockbox Team ",
"engines": {
"firefox": ">=57"
@@ -55,7 +55,7 @@
"fluent-langneg": "^0.1.0",
"fluent-react": "^0.4.1",
"intl-pluralrules": "^0.1.0",
- "lockbox-datastore": "git+https://github.com/mozilla-lockbox/lockbox-datastore.git",
+ "lockbox-datastore": "git+https://github.com/linuxwolf/lockbox-datastore.git#binkey",
"node-jose": "^0.10.0",
"prop-types": "^15.6.0",
"react": "^16.1.1",
diff --git a/requirements/tests.txt b/requirements/tests.txt
index 9c10623d..4985a72e 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -1,5 +1,6 @@
+fxapom==1.10.1
PyPOM==1.2.0
-pytest==3.2.2
+pytest==3.3.0
pytest-selenium==1.11.0
pytest-xdist==1.18.2
-selenium==3.6.0
+selenium==3.8.0
diff --git a/src/bootstrap.js b/src/bootstrap.js
index 02cb3219..fa785480 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* global ADDON_INSTALL, ADDON_UNINSTALL */
+/* global ADDON_INSTALL, ADDON_UPGRADE, ADDON_UNINSTALL */
/* eslint-disable no-unused-vars */
const { utils: Cu } = Components;
@@ -18,8 +18,35 @@ const ORIGINAL_REMEMBER_SIGNONS_PREF =
// category name. In order to do this, every time we update the events in any
// way, we must also give them a unique category name. If you're updating the
// events, please increment the version number here by 1.
-const TELEMETRY_CATEGORY = "lockboxv0";
+const TELEMETRY_CATEGORY = "lockboxv1";
+class EventDispatcher {
+ constructor() {
+ this.pendingEvents = [];
+ }
+
+ record(event) {
+ if (this.port) {
+ this.port.postMessage(event);
+ return true;
+ }
+
+ this.pendingEvents.push(event);
+ return false;
+ }
+
+ connect(port) {
+ this.port = port;
+
+ const events = this.pendingEvents;
+ this.pendingEvents = [];
+ for (let evt of events) {
+ this.port.postMessage(evt);
+ }
+ }
+}
+
+const dispatcher = new EventDispatcher();
function startup({webExtension}, reason) {
try {
Services.telemetry.registerEvents(TELEMETRY_CATEGORY, {
@@ -34,52 +61,49 @@ function startup({webExtension}, reason) {
},
"displayView": {
methods: ["render"],
- objects: ["firstrun", "manage", "popupUnlock"],
- extra_keys: ["fxauid"],
- },
- "fxaSignIn": {
- methods: ["render"],
- objects: ["signInPage"],
- },
- "confirmPW": {
- methods: ["click"],
- objects: ["confirmPWPage"],
- },
- "setupDone": {
- methods: ["click"],
- objects: ["setupDoneButton"],
+ objects: ["firstrun", "popupUnlock", "manage", "doorhanger"],
extra_keys: ["fxauid"],
},
"itemAdding": {
methods: ["itemAdding"],
- objects: ["addItemForm"],
+ objects: ["manage"],
extra_keys: ["fxauid"],
},
"itemUpdating": {
methods: ["itemUpdating"],
- objects: ["updatingItemForm"],
+ objects: ["manage"],
extra_keys: ["fxauid"],
},
"itemDeleting": {
methods: ["itemDeleting"],
- objects: ["updatingItemForm"],
+ objects: ["manage"],
extra_keys: ["fxauid"],
},
+ "itemAdded": {
+ methods: ["itemAdded"],
+ objects: ["manage"],
+ extra_keys: ["itemid", "fxauid"],
+ },
+ "itemUpdated": {
+ methods: ["itemUpdated"],
+ objects: ["manage"],
+ extra_keys: ["itemid", "fxauid"],
+ },
+ "itemDeleted": {
+ methods: ["itemDeleted"],
+ objects: ["manage"],
+ extra_keys: ["itemid", "fxauid"],
+ },
"itemSelected": {
methods: ["itemSelected"],
- objects: ["itemList"],
+ objects: ["manage", "doorhanger"],
extra_keys: ["fxauid"],
},
"addClick": {
methods: ["addClick"],
- objects: ["addButton"],
+ objects: ["manage"],
extra_keys: ["fxauid"],
},
- "itemAdded": {
- methods: ["itemAdded"],
- objects: ["addItemForm"],
- extra_keys: ["itemid", "fxauid"],
- },
"datastore": {
methods: ["added", "updated", "deleted"],
objects: ["datastore"],
@@ -90,9 +114,14 @@ function startup({webExtension}, reason) {
objects: ["manage"],
extra_keys: ["fxauid"],
},
+ "faq": {
+ methods: ["faqClick"],
+ objects: ["manage"],
+ extra_keys: ["fxauid"],
+ },
"itemCopied": {
methods: ["usernameCopied", "passwordCopied"],
- objects: ["itemDetails"],
+ objects: ["manage", "doorhanger"],
extra_keys: ["fxauid"],
},
"resetRequested": {
@@ -105,6 +134,24 @@ function startup({webExtension}, reason) {
objects: ["settings"],
extra_keys: ["fxauid"],
},
+ "setupGuest": {
+ methods: ["click"],
+ objects: ["welcomeGuest"],
+ },
+ "fxaStart": {
+ methods: ["click"],
+ objects: ["welcomeSignin", "manageAcctCreate", "manageAcctSignin", "unlockSignin"],
+ },
+ "fxaAuth": {
+ methods: ["fxaUpgrade", "fxaSignin", "fxaSignout"],
+ objects: ["accounts"],
+ extra_keys: ["fxauid"],
+ },
+ "fxaFail": {
+ methods: ["fxaFailed"],
+ objects: ["accounts"],
+ extra_keys: ["message"],
+ },
});
} catch (e) {
if (e.message === "Attempt to register event that is already registered.") {
@@ -128,6 +175,10 @@ function startup({webExtension}, reason) {
respond({});
}
});
+
+ browser.runtime.onConnect.addListener((port) => {
+ dispatcher.connect(port);
+ });
});
}
@@ -143,6 +194,14 @@ function install(data, reason) {
Services.prefs.getBoolPref(REMEMBER_SIGNONS_PREF)
);
Services.prefs.setBoolPref(REMEMBER_SIGNONS_PREF, false);
+
+ dispatcher.record({ type: "extension_installed" });
+ } else if (reason === ADDON_UPGRADE) {
+ dispatcher.record({
+ type: "extension_upgraded",
+ version: data.newVersion,
+ oldVersion: data.oldVersion,
+ });
}
}
@@ -165,3 +224,5 @@ startup;
shutdown;
install;
uninstall;
+dispatcher;
+EventDispatcher;
diff --git a/src/webextension/background/accounts/configs.json b/src/webextension/background/accounts/configs.json
new file mode 100644
index 00000000..c3683544
--- /dev/null
+++ b/src/webextension/background/accounts/configs.json
@@ -0,0 +1,32 @@
+{
+ "production": {
+ "client_id": "1b024772203a0849",
+ "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
+ "authorization_endpoint": "https://oauth.accounts.firefox.com/v1/authorization",
+ "token_endpoint": "https://oauth.accounts.firefox.com/v1/token",
+ "userinfo_endpoint": "https://profile.accounts.firefox.com/v1/profile",
+ "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
+ "pkce": true,
+ "app_keys": true
+ },
+ "dev-latest": {
+ "client_id": "f69b2d16e724b432",
+ "client_secret": "32052e102892dd07b1885c38887849d8283d1407350ee009b6377b8f542a272c",
+ "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
+ "authorization_endpoint": "https://oauth-latest.dev.lcip.org/v1/authorization",
+ "token_endpoint": "https://oauth-latest.dev.lcip.org/v1/token",
+ "userinfo_endpoint": "https://latest.dev.lcip.org/profile/v1/profile",
+ "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
+ "pkce": false
+ },
+ "scoped-keys": {
+ "client_id": "37fdfa37698f251a",
+ "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
+ "authorization_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/authorization",
+ "token_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/token",
+ "userinfo_endpoint": "https://latest-keys.dev.lcip.org/profile/v1/profile",
+ "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
+ "pkce": true,
+ "app_keys": true
+ }
+}
diff --git a/src/webextension/background/accounts/index.js b/src/webextension/background/accounts/index.js
new file mode 100644
index 00000000..d9c15ae0
--- /dev/null
+++ b/src/webextension/background/accounts/index.js
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const DEFAULT_CONFIG = "production";
+
+import jose from "node-jose";
+
+import configs from "./configs.json";
+
+async function generateAuthzURL(config, props) {
+ const queryParams = new URLSearchParams();
+ queryParams.set("response_type", "code");
+ queryParams.set("client_id", config.client_id);
+ queryParams.set("redirect_uri", config.redirect_uri);
+ queryParams.set("access_type", "offline");
+ queryParams.set("scope", config.scopes.join(" "));
+ if (config.action) {
+ queryParams.set("action", config.action);
+ }
+
+ const state = props.state = jose.util.base64url.encode(jose.util.randomBytes(16));
+ queryParams.set("state", state);
+ if (config.pkce) {
+ props.pkce = jose.util.base64url.encode(jose.util.randomBytes(32));
+ let challenge = new TextEncoder().encode(props.pkce);
+ challenge = await jose.JWA.digest("SHA-256", challenge);
+ challenge = jose.util.base64url.encode(challenge);
+ queryParams.set("code_challenge", challenge);
+ queryParams.set("code_challenge_method", "S256");
+ }
+ if (config.app_keys) {
+ const keystore = jose.JWK.createKeyStore();
+ props.appKey = await keystore.generate("EC", "P-256");
+ const keysJWK = jose.util.base64url.encode(JSON.stringify(props.appKey));
+ queryParams.set("keys_jwk", keysJWK);
+ }
+ return `${config.authorization_endpoint}?${queryParams}`;
+}
+
+function processAuthzResponse(url, props) {
+ const queryParams = url.searchParams;
+ if (queryParams.get("state") !== props.state) {
+ throw new Error("invalid oauth state");
+ }
+ const code = queryParams.get("code");
+ if (!code) {
+ throw new Error("invalid oauth authorization code");
+ }
+ return code;
+}
+
+async function fetchFromEndPoint(name, url, request) {
+ const response = await fetch(url, request);
+ let body;
+ try {
+ body = await response.json();
+ } catch (err) {
+ body = {};
+ }
+ if (!response.ok) {
+ const error = new Error(`failed ${name} request: ${body.message || response.statusText}`);
+ error.errno = body.errno;
+ throw error;
+ }
+ return body;
+}
+
+export const GUEST = "guest";
+export const UNAUTHENTICATED = "unauthenticated";
+export const AUTHENTICATED = "authenticated";
+
+export const APP_KEY_NAME = "https://identity.mozilla.com/apps/lockbox";
+
+export class Account {
+ constructor({config = DEFAULT_CONFIG, info}) {
+ // TODO: verify configuration (when there is one)
+ this.config = config;
+ this.info = info || undefined;
+ }
+
+ toJSON() {
+ const { config } = this;
+ let { info } = this;
+ if (info) {
+ // only exporta specific whitelist of values
+ info = {
+ uid: info.uid,
+ access_token: info.access_token || undefined,
+ expires_at: info.expires_at || undefined,
+ id_token: info.id_token || undefined,
+ };
+ }
+ return {
+ config,
+ info,
+ };
+ }
+
+ get mode() {
+ const info = this.info;
+ if (!info || !info.uid) {
+ return GUEST;
+ }
+ if (!info.refresh_token) {
+ return UNAUTHENTICATED;
+ }
+ return AUTHENTICATED;
+ }
+
+ get signedIn() { return this.mode === AUTHENTICATED; }
+
+ get uid() { return (this.info && this.info.uid) || undefined; }
+ get email() { return (this.info && this.info.email) || undefined; }
+ get keys() { return (this.info && this.info.keys) || new Map(); }
+
+ async signIn(action = "signin") {
+ let cfg = configs[this.config];
+
+ const props = {};
+ let url, request;
+
+ // request authorization
+ cfg = {
+ ...cfg,
+ action,
+ };
+ url = await generateAuthzURL(cfg, props);
+ const authzRsp = await browser.identity.launchWebAuthFlow({
+ url,
+ interactive: true,
+ });
+ const authzCode = processAuthzResponse(new URL(authzRsp), props);
+
+ // exchange token
+ const tokenParams = {
+ grant_type: "authorization_code",
+ code: authzCode,
+ client_id: cfg.client_id,
+ };
+ if (cfg.pkce) {
+ tokenParams.code_verifier = props.pkce;
+ } else {
+ tokenParams.client_secret = cfg.client_secret;
+ }
+ url = cfg.token_endpoint;
+ request = {
+ method: "post",
+ headers: {
+ "content-type": "application/json",
+ },
+ cache: "no-cache",
+ body: JSON.stringify(tokenParams),
+ };
+ const oauthInfo = await fetchFromEndPoint("token", url, request);
+ // console.log(`oauth info == ${JSON.stringify(oauthInfo)}`);
+
+ const keys = new Map();
+ if (oauthInfo.keys_jwe) {
+ let bundle = await jose.JWE.createDecrypt(props.appKey).decrypt(oauthInfo.keys_jwe);
+ bundle = JSON.parse(new TextDecoder().decode(bundle.payload));
+ const pending = Object.keys(bundle).map(async (name) => {
+ let key = bundle[name];
+ key = await jose.JWK.asKey(key);
+ keys.set(name, key);
+ });
+ await Promise.all(pending);
+ }
+
+ // retrieve user info
+ url = cfg.userinfo_endpoint;
+ request = {
+ method: "get",
+ headers: {
+ authorization: `Bearer ${oauthInfo.access_token}`,
+ },
+ cache: "no-cache",
+ };
+ const userInfo = await fetchFromEndPoint("userinfo", url, request);
+
+ this.info = {
+ uid: userInfo.uid,
+ email: userInfo.email,
+ access_token: oauthInfo.access_token,
+ expires_at: (Date.now / 1000) + oauthInfo.expires_in,
+ refresh_token: oauthInfo.refresh_token,
+ id_token: oauthInfo.id_token,
+ keys,
+ };
+ return this;
+ }
+
+ async signOut() {
+ // TODO: implement a complete signout/forget
+ // TODO: something server side?
+ this.info = undefined;
+ return this;
+ }
+
+ details() {
+ return {
+ mode: this.mode,
+ uid: this.uid,
+ email: this.email,
+ };
+ }
+}
+
+
+let account;
+export default function getAccount() {
+ if (!account) {
+ account = new Account({});
+ }
+ return account;
+}
+
+export async function loadAccount(storage) {
+ const stored = await storage.get("account");
+ if (stored && stored.account) {
+ account = new Account(stored.account);
+ }
+ return getAccount();
+}
+
+export async function saveAccount(storage) {
+ const account = getAccount().toJSON();
+ await storage.set({ account });
+}
+
+export function setAccount(config, info) {
+ account = config ? new Account({config, info}) : undefined;
+}
+
+export async function openAccount(storage) {
+ let account;
+
+ try {
+ // attempt to load account (FxA) data
+ account = await loadAccount(storage);
+ // eslint-disable-next-line no-console
+ console.log(`loaded account for (${account.mode.toString()}) '${account.uid || ""}'`);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(`loading account failed (fallback to empty GUEST): ${err.message}`);
+ account = getAccount();
+ }
+
+ return account;
+}
diff --git a/src/webextension/background/authorization/index.js b/src/webextension/background/authorization/index.js
deleted file mode 100644
index 33f9c1c7..00000000
--- a/src/webextension/background/authorization/index.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const DEFAULT_CONFIG = "dev-latest";
-
-import UUID from "uuid";
-
-export class Authorization {
- constructor({config = DEFAULT_CONFIG, info}) {
- // TODO: verify configuration (when there is one)
- this.config = config;
- this.info = info || undefined;
- }
-
- toJSON() {
- let { config, info } = this;
- if (info) {
- info = { ...info };
- delete info.email;
- }
- return {
- config,
- info,
- };
- }
-
- get signedIn() { return this.info !== undefined; }
- get verified() { return (this.info && this.info.verified) || false; }
-
- get uid() { return (this.info && this.info.uid) || undefined; }
- get email() { return (this.info && this.info.email) || undefined; }
-
- async signIn(interactive = true) {
- let uid = UUID();
- this.info = {
- uid,
- };
- return this.info;
- }
-
- async signOut() {
- // TODO: something server side?
- this.info = undefined;
- }
-
- async verify(password) {
- if (!this.signedIn) {
- throw new Error("not signed in");
- }
-
- // TODO: do something real!
- this.info.verified = true;
-
- return password;
- }
-}
-
-let authorization;
-export default function getAuthorization() {
- if (!authorization) {
- authorization = new Authorization({});
- }
- return authorization;
-}
-
-export async function loadAuthorization(storage) {
- let stored = await storage.get("authz");
- if (stored && stored.authz) {
- authorization = new Authorization(stored.authz);
- }
- return getAuthorization();
-}
-
-export async function saveAuthorization(storage) {
- let authz = getAuthorization().toJSON();
- await storage.set({ authz });
-}
-
-export function setAuthorization(config, info) {
- authorization = config ? new Authorization({config, info}) : undefined;
-}
diff --git a/src/webextension/background/browser-action.js b/src/webextension/background/browser-action.js
index ecca9abc..fca9bac2 100644
--- a/src/webextension/background/browser-action.js
+++ b/src/webextension/background/browser-action.js
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { openView } from "./views";
+import getAccount, * as accounts from "./accounts";
import * as telemetry from "./telemetry";
let listener;
@@ -37,25 +38,33 @@ function uninstallListener() {
listener = null;
}
-export default async function updateBrowserAction(ds) {
+function installEntriesAction() {
+ return installPopup("list/popup/index.html");
+}
+
+export default async function updateBrowserAction({account = getAccount(), datastore}) {
// clear listener
// XXXX: be more efficient with this?
uninstallListener();
uninstallPopup();
- const iconpath = ds.locked ? "icons/lb_locked.svg" : "icons/lb_unlocked.svg";
+ const iconpath = datastore.locked ? "icons/lb_locked.svg" : "icons/lb_unlocked.svg";
browser.browserAction.setIcon({ path: iconpath });
- if (!ds.initialized) {
+ if (!datastore.initialized) {
// setup first-run popup
return installListener("firstrun");
- } else if (ds.locked) {
+ }
+ if (datastore.locked) {
+ if (account.mode === accounts.GUEST) {
+ // unlock on user's behalf ...
+ // XXXX: is this a bad idea or terrible idea?
+ await datastore.unlock();
+ return installEntriesAction();
+ }
// setup unlock popup
- return installPopup("popup/unlock/index.html");
+ return installPopup("unlock/index.html");
}
- if (process.env.ENABLE_DOORHANGER) {
- return installPopup("list/popup/index.html");
- }
- return installListener("manage");
+ return installEntriesAction();
}
diff --git a/src/webextension/background/datastore.js b/src/webextension/background/datastore.js
index dec559a2..087a5910 100644
--- a/src/webextension/background/datastore.js
+++ b/src/webextension/background/datastore.js
@@ -20,9 +20,10 @@ async function recordMetric(method, itemid, fields) {
telemetry.recordEvent(method, "datastore", extra);
}
-export default async function openDataStore() {
+export default async function openDataStore(cfg = {}) {
if (!datastore) {
datastore = await DataStore.open({
+ ...cfg,
recordMetric,
});
}
diff --git a/src/webextension/background/index.js b/src/webextension/background/index.js
index ab972b32..06157690 100644
--- a/src/webextension/background/index.js
+++ b/src/webextension/background/index.js
@@ -3,24 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import openDataStore from "./datastore";
-import { loadAuthorization } from "./authorization";
+import { openAccount, GUEST } from "./accounts";
import initializeMessagePorts from "./message-ports";
import updateBrowserAction from "./browser-action";
-// XXX: For now, initialize the datastore on startup and then hook up the
-// button. Eventually, we'll have UX to create new datastores (and persist
-// existing ones).
-openDataStore().then(async (ds) => {
- try {
- // attempt to load authorization (FxA) data
- let authz = await loadAuthorization(browser.storage.local);
- // eslint-disable-next-line no-console
- console.log(`loaded authorization for '${authz.uid || ""}'`);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(`loading failed: ${err.message}`);
+openAccount(browser.storage.local).then(async (account) => {
+ let datastore = await openDataStore({ salt: account.uid });
+ if (datastore.initialized && account.mode === GUEST) {
+ await datastore.unlock();
}
initializeMessagePorts();
- await updateBrowserAction(ds);
+ await updateBrowserAction({account, datastore});
});
diff --git a/src/webextension/background/message-ports.js b/src/webextension/background/message-ports.js
index e48e083e..680cc526 100644
--- a/src/webextension/background/message-ports.js
+++ b/src/webextension/background/message-ports.js
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import openDataStore from "./datastore";
-import getAuthorization, { saveAuthorization } from "./authorization/index";
+import getAccount, * as accounts from "./accounts";
import updateBrowserAction from "./browser-action";
import * as telemetry from "./telemetry";
import { openView, closeView } from "./views";
@@ -19,7 +19,28 @@ function broadcast(message, excludedSender) {
}
}
+let addonPort;
+
export default function initializeMessagePorts() {
+ // setup port to receive messages from bootstrapped addon
+ addonPort = browser.runtime.connect({ name: "webext-to-legacy" });
+ addonPort.onMessage.addListener(async (message) => {
+ switch (message.type) {
+ case "extension_installed":
+ openView("firstrun");
+ break;
+ case "extension_upgraded":
+ openDataStore().then(async (datastore) => {
+ if (!datastore.initialized) {
+ openView("firstrun");
+ }
+ });
+ break;
+ default:
+ break;
+ }
+ });
+
browser.runtime.onConnect.addListener((port) => {
ports.add(port);
port.onDisconnect.addListener(() => ports.delete(port));
@@ -27,46 +48,100 @@ export default function initializeMessagePorts() {
browser.runtime.onMessage.addListener(async (message, sender) => {
switch (message.type) {
+ case "get_account_details":
+ return {account: getAccount().details()};
case "open_view":
return openView(message.name).then(() => ({}));
case "close_view":
return closeView(message.name).then(() => ({}));
- case "signin":
- return getAuthorization().signIn(message.interactive);
case "initialize":
- return openDataStore().then(async (ds) => {
- await ds.initialize({
- password: message.password,
- });
- await saveAuthorization(browser.storage.local);
- await updateBrowserAction(ds);
+ return openDataStore().then(async (datastore) => {
+ await datastore.initialize();
+ // TODO: be more implicit on saving account info
+ await accounts.saveAccount(browser.storage.local);
+ await updateBrowserAction({datastore});
+ if (message.view) {
+ openView(message.view);
+ }
+
+ return {};
+ });
+ case "upgrade_account":
+ return openDataStore().then(async (datastore) => {
+ const account = await getAccount().signIn(message.action);
+ const appKey = account.keys.get("https://identity.mozilla.com/apps/lockbox");
+ const salt = account.uid;
+
+ try {
+ if (datastore.initialized && datastore.locked) {
+ await datastore.unlock();
+ }
+ await datastore.initialize({ appKey, salt, rebase: true });
+ // FIXME: be more implicit on saving account info
+ await accounts.saveAccount(browser.storage.local);
+ await updateBrowserAction({ account, datastore });
+ telemetry.recordEvent("fxaUpgrade", "accounts");
+ } catch (err) {
+ telemetry.recordEvent("fxaFailed", "accounts", err.message);
+ throw err;
+ }
+
+ broadcast({ type: "account_details_updated", account: account.details() });
+ if (message.view) {
+ openView(message.view);
+ }
+
return {};
});
case "reset":
- return openDataStore().then(async (ds) => {
+ return openDataStore().then(async (datastore) => {
+ const account = getAccount();
+
await closeView();
- await ds.reset();
+ await datastore.reset();
+ await account.signOut();
// TODO: put other reset calls here
- await updateBrowserAction(ds);
- await openView("firstrun");
+ await updateBrowserAction({datastore});
+ broadcast({type: "account_details_updated", account: account.details()});
+ openView("firstrun");
return {};
});
- case "unlock":
- return openDataStore().then(async (ds) => {
- await ds.unlock(message.password);
- await updateBrowserAction(ds);
+ case "signin":
+ return openDataStore().then(async (datastore) => {
+ const account = getAccount();
+ let appKey;
+ try {
+ if (account.mode === accounts.UNAUTHENTICATED) {
+ await account.signIn();
+ appKey = account.keys.get(accounts.APP_KEY_NAME);
+ }
+ await datastore.unlock(appKey);
+ await updateBrowserAction({ datastore });
+ telemetry.recordEvent("fxaSignin", "accounts");
+ } catch (err) {
+ telemetry.recordEvent("fxaFailed", "accounts", err.message);
+ throw err;
+ }
+
+ broadcast({ type: "account_details_updated", account: account.details() });
+ if (message.view) {
+ openView(message.view);
+ }
+
return {};
});
- case "lock":
- return openDataStore().then(async (ds) => {
- await ds.lock();
- await updateBrowserAction(ds);
+ case "signout":
+ return openDataStore().then(async (datastore) => {
+ // TODO: perform (light) signout from FxA
+ await datastore.lock();
+ await updateBrowserAction({datastore});
+
return {};
});
@@ -75,7 +150,6 @@ export default function initializeMessagePorts() {
return {items: Array.from((await ds.list()).values(),
makeItemSummary)};
});
-
case "add_item":
return openDataStore().then(async (ds) => {
const item = await ds.add(message.item);
diff --git a/src/webextension/background/telemetry.js b/src/webextension/background/telemetry.js
index 77220a47..1bad78ad 100644
--- a/src/webextension/background/telemetry.js
+++ b/src/webextension/background/telemetry.js
@@ -2,10 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import getAuthorization from "./authorization/index";
+import getAccount from "./accounts";
export async function recordEvent(method, object, extra) {
- const fxauid = getAuthorization().uid;
+ const fxauid = getAccount().uid;
if (fxauid) {
extra = {...(extra || {}), fxauid};
}
diff --git a/src/webextension/firstrun/components/app.css b/src/webextension/firstrun/components/app.css
index b93dab3a..aba15e97 100644
--- a/src/webextension/firstrun/components/app.css
+++ b/src/webextension/firstrun/components/app.css
@@ -13,7 +13,7 @@ body {
}
.firstrun {
- width: 459px;
+ width: 612px;
margin: 3em auto;
display: flex;
flex-flow: column nowrap;
diff --git a/src/webextension/firstrun/components/app.js b/src/webextension/firstrun/components/app.js
index 70c2f61c..d36ce434 100644
--- a/src/webextension/firstrun/components/app.js
+++ b/src/webextension/firstrun/components/app.js
@@ -6,14 +6,12 @@ import { Localized } from "fluent-react";
import React from "react";
import DocumentTitle from "react-document-title";
-import Welcome from "./welcome";
-import MasterPasswordSetup from "./master-password-setup";
+import Intro from "./intro";
+import StartUsing from "./using";
import styles from "./app.css";
export default function App() {
- // Eventually, we'll have a feedback button up top here, and maybe some other
- // stuff.
const imgSrc = browser.extension.getURL("/images/nessie_v2.svg");
return (
@@ -21,8 +19,8 @@ export default function App() {
-
-
+
+
diff --git a/src/webextension/firstrun/components/welcome.css b/src/webextension/firstrun/components/intro.css
similarity index 51%
rename from src/webextension/firstrun/components/welcome.css
rename to src/webextension/firstrun/components/intro.css
index 8b660562..cca2ed8d 100644
--- a/src/webextension/firstrun/components/welcome.css
+++ b/src/webextension/firstrun/components/intro.css
@@ -2,18 +2,35 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-.welcome {
+.intro {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
}
-.welcome > h1,
-.welcome > p {
+.intro > h1,
+.intro > h2,
+.intro > p {
text-align: center;
+ line-height: 1.5;
+ letter-spacing: 0.2px;
}
-.welcome > h1 {
- font-size: 2em;
- font-weight: normal;
+
+.intro > h1 {
+ font-size: 33px;
+ font-weight: 300;
+ margin: 0;
+}
+
+.intro > h2 {
+ font-size: 17px;
+ font-weight: 300;
+ font-style: italic;
+ margin: 0px;
+ text-transform: lowercase;
+}
+
+.intro > p {
+ font-size: 15px;
}
diff --git a/src/webextension/firstrun/components/intro.js b/src/webextension/firstrun/components/intro.js
new file mode 100644
index 00000000..00571d9e
--- /dev/null
+++ b/src/webextension/firstrun/components/intro.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import React from "react";
+
+import styles from "./intro.css";
+
+export default function Intro() {
+ return (
+
+
+
wELCOMe
+
+
+
mORe wELCOMe
+
+
+ );
+}
diff --git a/src/webextension/firstrun/components/master-password-setup.css b/src/webextension/firstrun/components/master-password-setup.css
deleted file mode 100644
index 35b9e889..00000000
--- a/src/webextension/firstrun/components/master-password-setup.css
+++ /dev/null
@@ -1,33 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-.master-password-setup {
- display: flex;
- flex-flow: column nowrap;
- justify-content: center;
- align-content: stretch;
- margin: 1em auto;
- width: 306px;
-}
-
-.master-password-setup > * {
- margin: 0.5em 0;
-}
-
-.master-password-setup > h3 {
- font-size: 1em;
- width: auto;
- align-self: center;
-}
-
-.master-password-setup > .error {
- color: red;
- text-align: center;
- font-weight: bold;
-}
-
-.master-password-setup > button {
- text-align: center;
- height: 3em;
-}
diff --git a/src/webextension/firstrun/components/master-password-setup.js b/src/webextension/firstrun/components/master-password-setup.js
deleted file mode 100644
index 4227210f..00000000
--- a/src/webextension/firstrun/components/master-password-setup.js
+++ /dev/null
@@ -1,107 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Localized } from "fluent-react";
-import React from "react";
-
-import Button from "../../widgets/button";
-import LabelText from "../../widgets/label-text";
-import PasswordInput from "../../widgets/password-input";
-import * as telemetry from "../../telemetry";
-
-import styles from "./master-password-setup.css";
-
-export default class MasterPasswordSetup extends React.Component {
- constructor() {
- super();
- this.state = {
- password: "",
- confirmPassword: "",
- };
- }
-
- componentDidMount() {
- this._firstField.focus();
- }
-
- handleChange(event) {
- this.setState({
- [event.target.name]: event.target.value,
- error: undefined,
- });
- }
-
- async handleSubmit() {
- let { password, confirmPassword } = this.state;
- if (password !== confirmPassword) {
- // TODO: Localize this!
- this.setState({
- error: "Passwords do not match",
- });
- } else {
- try {
- await browser.runtime.sendMessage({
- type: "signin",
- interactive: false,
- });
- await browser.runtime.sendMessage({
- type: "initialize",
- password,
- });
-
- telemetry.recordEvent("lockbox", "click", "setupDoneButton");
-
- await browser.runtime.sendMessage({
- type: "open_view",
- name: "manage",
- });
- await browser.runtime.sendMessage({
- type: "close_view",
- name: "firstrun",
- });
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(`initialize failed: ${err.message}`);
- // TODO: Localize this!
- this.setState({
- error: "Could not initialize!",
- });
- }
- }
- }
-
- render() {
- const { error = "\u00a0" } = this.state;
- const controlledProps = (name) => {
- return {name, value: this.state[name],
- onChange: (e) => this.handleChange(e)};
- };
-
- return (
-
- );
- }
-}
diff --git a/src/webextension/firstrun/components/using.css b/src/webextension/firstrun/components/using.css
new file mode 100644
index 00000000..d3e02e2a
--- /dev/null
+++ b/src/webextension/firstrun/components/using.css
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.using {
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+}
+
+.using h1,
+.using h2,
+.using p {
+ text-align: center;
+}
+
+.using > .actions {
+ margin: 0;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: stretch;
+}
+
+.using > .actions > h2 {
+ margin: 1em 0 .5em;
+ font-size: 22px;
+ font-weight: 300;
+ line-height: 1.5;
+ letter-spacing: 0.2px;
+}
+
+.using > .actions > button {
+ margin: 0;
+ font-size: 15px;
+}
diff --git a/src/webextension/firstrun/components/using.js b/src/webextension/firstrun/components/using.js
new file mode 100644
index 00000000..78ba0fd8
--- /dev/null
+++ b/src/webextension/firstrun/components/using.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import React from "react";
+
+import Button from "../../widgets/button";
+import * as telemetry from "../../telemetry";
+
+import styles from "./using.css";
+
+export default function StartUsing() {
+ const doGuest = async () => {
+ telemetry.recordEvent("click", "welcomeGuest");
+ browser.runtime.sendMessage({
+ type: "initialize",
+ view: "manage",
+ });
+ browser.runtime.sendMessage({
+ type: "close_view",
+ name: "firstrun",
+ });
+ };
+ const doReturning = async () => {
+ telemetry.recordEvent("click", "welcomeSignin");
+ browser.runtime.sendMessage({
+ type: "upgrade_account",
+ view: "manage",
+ });
+ browser.runtime.sendMessage({
+ type: "close_view",
+ name: "firstrun",
+ });
+ };
+
+ return (
+
+
+
+
gUESt
+
+
+
+
+
+
rETURNINg
+
+
+
+
+
+
+ );
+}
diff --git a/src/webextension/firstrun/components/welcome.js b/src/webextension/firstrun/components/welcome.js
deleted file mode 100644
index 816512cc..00000000
--- a/src/webextension/firstrun/components/welcome.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Localized } from "fluent-react";
-import React from "react";
-
-import styles from "./welcome.css";
-
-export default function Welcome() {
- return (
-
-
-
wELCOMe
-
-
-
Cras justo odio, dapibus ac facilisis in, egestas eget quam. Sed
- posuere consectetur est at lobortis. Lorem ipsum dolor sit amet,
- consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur
- adipiscing elit. Etiam porta sem malesuada magna mollis euismod. Cras
- mattis consectetur purus sit amet fermentum.
-
-
-
- Lorem ipsum dolor sit amet, consectetur.
- Mauris, aliquam vel pellentesque et, mattis bibendum tellus. Fusce
- sodales, tellus a auctor accumsan, diam risus pharetra orci, at lacinia
- libero eros ut erat. Fusce ex neque, pharetra id rhoncus in,
- pellentesque quis urna.
-
-
-
-
Curabitur blandit tempus porttitor. Nulla vitae elit libero, a
- pharetra augue. Vestibulum id ligula porta felis euismod semper.
- Maecenas sed diam eget risus varius blandit sit amet non magna.
- Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis
- vestibulum. Maecenas sed diam eget risus varius blandit sit amet
- non magna.
diff --git a/src/webextension/list/components/item-list.css b/src/webextension/list/components/item-list.css
index f08bb288..09ed7d1f 100644
--- a/src/webextension/list/components/item-list.css
+++ b/src/webextension/list/components/item-list.css
@@ -3,15 +3,25 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.empty {
- white-space: pre-wrap;
- margin: 3em 1em;
- font-style: italic;
text-align: center;
- color: #626262;
+ white-space: pre-wrap;
+ margin: 1em 1em;
+ padding: 20px;
+ overflow: hidden;
+ overflow-wrap: break-word;
+ color: #737373;
+ font-size: 15px;
+ line-height: 1.5;
+ letter-spacing: 0.2px;
+}
+
+.item {
+ border-bottom: solid 0.5px #d7d7db;
+ border-right: solid 0.5px #d7d7db;
}
.item:nth-child(2n+1) {
- background-color: #f1f1f1;
+ background-color: #ededf0;
}
.item:nth-child(2n) {
diff --git a/src/webextension/list/components/item-summary.css b/src/webextension/list/components/item-summary.css
index 38b5fe5f..b59c9ec5 100644
--- a/src/webextension/list/components/item-summary.css
+++ b/src/webextension/list/components/item-summary.css
@@ -5,15 +5,38 @@
.item-summary {
border-bottom: 1px solid #d7d7db;
padding: .5em;
+ font-size: 15px;
+}
+
+.item-summary::before {
+ background-image: url(/icons/arrowhead-right-16.svg);
+ background-repeat: no-repeat;
+ background-size: 16px 16px;
+ content: "";
+ float: right;
+ height: 16px;
+ width: 16px;
+ margin: 14px 0;
+}
+
+[data-selected] .item-summary::before {
+ display: none;
}
.title, .subtitle {
+ line-height: 1.5;
+ letter-spacing: 0.2px;
+ color: #0c0c0d;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
+.title {
+ font-weight: bold;
+}
+
.subtitle {
- font-size: 80%;
- opacity: 0.75;
+ font-size: 13px;
+ font-weight: 400;
}
diff --git a/src/webextension/list/containers/item-filter.js b/src/webextension/list/containers/item-filter.js
index 5cdb3293..33986bba 100644
--- a/src/webextension/list/containers/item-filter.js
+++ b/src/webextension/list/containers/item-filter.js
@@ -20,6 +20,7 @@ function ItemFilter(props) {
export default connect(
(state) => ({
value: state.filter,
+ disabled: state.cache.items.length === 0,
}),
(dispatch) => ({
onChange: (value) => { dispatch(filterItems(value)); },
diff --git a/src/webextension/list/manage/components/account-linked.css b/src/webextension/list/manage/components/account-linked.css
new file mode 100644
index 00000000..5b0c36f9
--- /dev/null
+++ b/src/webextension/list/manage/components/account-linked.css
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.linked {
+ margin: 2em auto;
+ padding: 0;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+}
+
+.linked > h2,
+.linked > p {
+ text-align: center;
+ letter-spacing: 0.2px;
+ line-height: 1.5;
+}
+
+.linked > h2 {
+ margin: 0 0 .5em;
+ padding: 0;
+ font-size: 17px;
+ font-weight: 600;
+ line-height: 1.32;
+ color: #058b00;
+}
+
+.linked > p {
+ margin-top: 0;
+ font-size: 15px;
+ white-space: pre-line;
+}
diff --git a/src/webextension/list/manage/components/account-linked.js b/src/webextension/list/manage/components/account-linked.js
new file mode 100644
index 00000000..96ce5fa9
--- /dev/null
+++ b/src/webextension/list/manage/components/account-linked.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import React from "react";
+
+import styles from "./account-linked.css";
+
+export default function AccountLinked() {
+ return (
+
+
+
aCCOUNt lINKEd
+
+
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
+
+
+ );
+}
diff --git a/src/webextension/popup/unlock/components/app.js b/src/webextension/list/manage/components/account-summary-label.css
similarity index 57%
rename from src/webextension/popup/unlock/components/app.js
rename to src/webextension/list/manage/components/account-summary-label.css
index 1ab717eb..d25cc583 100644
--- a/src/webextension/popup/unlock/components/app.js
+++ b/src/webextension/list/manage/components/account-summary-label.css
@@ -2,12 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import React from "react";
-
-import UnlockPrompt from "./unlock-prompt";
-
-import "./app.css";
-
-export default function App() {
- return ;
+.account-summary {
+ font-size: 15px;
}
diff --git a/src/webextension/list/manage/components/account-summary-label.js b/src/webextension/list/manage/components/account-summary-label.js
new file mode 100644
index 00000000..16bfd9a4
--- /dev/null
+++ b/src/webextension/list/manage/components/account-summary-label.js
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import PropTypes from "prop-types";
+import React from "react";
+
+import styles from "./account-summary-label.css";
+
+export default function AccountSummaryLabel({email}) {
+ if (!email) {
+ return null;
+ }
+
+ return (
+ {email}
+ );
+}
+
+AccountSummaryLabel.propTypes = {
+ email: PropTypes.string,
+};
diff --git a/src/webextension/list/manage/components/app.css b/src/webextension/list/manage/components/app.css
index b3f70992..3d729607 100644
--- a/src/webextension/list/manage/components/app.css
+++ b/src/webextension/list/manage/components/app.css
@@ -22,30 +22,20 @@ body > main,
.app-main {
display: grid;
height: 100%;
- grid-template-columns: 1fr 5fr;
+ grid-template-columns: 1fr 3fr;
grid-template-rows: auto 1fr;
}
-.side-toolbar,
-.main-toolbar {
+.navigation {
background-color: #ededf0;
- border-bottom: 1px solid #c8c8c8;
-}
-
-.side-toolbar {
- padding-inline-end: 0;
-}
-
-.main-toolbar {
- padding-inline-start: 0;
+ grid-column-start: span 2;
}
.app-main > aside {
grid-column: 1;
- min-width: 0;
+ min-width: 150px;
min-height: 0;
- background-color: #dfdfe3;
- border-right: 1px solid #b1b1b3;
+ background-color: rgba(215, 215, 219, 0.75);
display: grid;
grid-template-rows: auto 1fr auto;
}
@@ -53,4 +43,16 @@ body > main,
.app-main > article {
grid-column: 2;
background-color: #f9f9fa;
+ border-top: 1px solid #d1d1d2;
+}
+
+.filter {
+ border: 1px solid #d1d1d2;
+ border-inline-start: 0px;
+ border-radius: 0;
+}
+
+.filter:focus-within,
+.filter:focus-within:hover {
+ box-shadow: none;
}
diff --git a/src/webextension/list/manage/components/app.js b/src/webextension/list/manage/components/app.js
index 0f13f674..c381d838 100644
--- a/src/webextension/list/manage/components/app.js
+++ b/src/webextension/list/manage/components/app.js
@@ -6,14 +6,15 @@ import { Localized } from "fluent-react";
import React from "react";
import DocumentTitle from "react-document-title";
+import AccountSummary from "../containers/account-summary";
import AddItem from "../containers/add-item";
import AllItems from "../containers/all-items";
import CurrentSelection from "../containers/current-selection";
import GoHome from "../containers/go-home";
import ItemFilter from "../../containers/item-filter";
-import ItemCount from "../containers/item-count";
import ModalRoot from "../containers/modals";
-import SendFeedback from "../components/send-feedback";
+import SendFeedback from "../containers/send-feedback";
+import OpenFAQ from "../containers/open-faq";
import Toolbar, { ToolbarSpace } from "../../../widgets/toolbar";
import styles from "./app.css";
@@ -24,18 +25,16 @@ export default function App() {
-
-
-
+
-
-
-
+
+
+
diff --git a/src/webextension/list/manage/components/homepage.css b/src/webextension/list/manage/components/homepage.css
index 8e95856c..ee564f94 100644
--- a/src/webextension/list/manage/components/homepage.css
+++ b/src/webextension/list/manage/components/homepage.css
@@ -3,34 +3,28 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.homepage {
- align-items: center;
display: flex;
flex-flow: column nowrap;
- justify-content: center;
- margin: 3em auto;
- width: 701px;
+ align-items: center;
+ margin: 3em;
}
.homepage > h1,
+.homepage > h2,
.homepage > p {
color: #0c0c0d;
letter-spacing: 0.2px;
- line-height: 1.5;
+ line-height: 1.5;
+ text-align: center;
}
.homepage h1 {
- font-size: 33px;
+ font-size: 17px;
font-weight: 300;
- margin: auto;
- text-align: center;
-}
-
-.homepage p {
- font-size: 15px;
- text-align: left;
- white-space: pre-line;
+ font-style: italic;
+ text-transform: lowercase;
}
.homepage > img {
- width: 300px;
+ max-width: 279px;
}
diff --git a/src/webextension/list/manage/components/homepage.js b/src/webextension/list/manage/components/homepage.js
index 8918983d..7bac56f1 100644
--- a/src/webextension/list/manage/components/homepage.js
+++ b/src/webextension/list/manage/components/homepage.js
@@ -3,34 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { Localized } from "fluent-react";
-import PropTypes from "prop-types";
import React from "react";
+import AccountDetails from "../containers/account-details";
+
import styles from "./homepage.css";
-export default function Homepage({count}) {
+export default function Homepage() {
const imgSrc = browser.extension.getURL("/images/nessie_v2.svg");
- let title;
- if (count === 0) {
- title = "welcOMe to lOcKboX";
- } else {
- title = "YoU have X enTrieS in YoUr lOcKboX";
- }
-
return (
-
-
{title}
-
-
-
{"yOu'Ve suCCessfuLLY iNSTalled..."}
+
+
tHe sIMPLe wAy tO sTORE...
+
);
}
-
-Homepage.propTypes = {
- count: PropTypes.number.isRequired,
-};
diff --git a/src/webextension/list/manage/components/intro-page.css b/src/webextension/list/manage/components/intro-page.css
new file mode 100644
index 00000000..1d7eb66a
--- /dev/null
+++ b/src/webextension/list/manage/components/intro-page.css
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.intro-images {
+ display: grid;
+ grid-auto-flow: column;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-template-rows: auto auto auto;
+ grid-column-gap: 4em;
+ margin: 4em auto;
+ padding: 0 4em;
+ max-width: 800px;
+}
+
+.intro-image {
+ display: contents;
+ text-align: center;
+}
+
+.intro-image > h1,
+.intro-image > p {
+ overflow: hidden;
+}
+
+.intro-image > img {
+ width: 100%;
+ border: 1px solid rgba(12, 12, 13, 0.3);
+ border-radius: 5px;
+ box-shadow: 0 0 10px rgba(12, 12, 13, 0.3);
+}
+
+.intro-image > h1 {
+ font-size: 17px;
+ margin-bottom: 0;
+}
+
+.intro-image > p {
+ font-size: 15px;
+}
diff --git a/src/webextension/list/manage/components/intro-page.js b/src/webextension/list/manage/components/intro-page.js
new file mode 100644
index 00000000..0d45f04e
--- /dev/null
+++ b/src/webextension/list/manage/components/intro-page.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import PropTypes from "prop-types";
+import React from "react";
+
+import AccountDetails from "../containers/account-details";
+
+import styles from "./intro-page.css";
+
+function IntroImage({src, title, children}) {
+ return (
+
+
+
{title}
+
{children}
+
+ );
+}
+
+IntroImage.propTypes = {
+ src: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ children: PropTypes.node,
+};
+
+export default function IntroPage() {
+ return (
+
+
+
+
+ sAVe uSERNAMe aNd pASSWORd...
+
+
+
+
+ cLICk tHe lOCKBOx iCOn...
+
+
+
+
+ cOPy an eNTRY's iNFo...
+
+
+
+
+
+ );
+}
diff --git a/src/webextension/list/manage/components/item-details.css b/src/webextension/list/manage/components/item-details.css
index 879f2e98..6d6b269e 100644
--- a/src/webextension/list/manage/components/item-details.css
+++ b/src/webextension/list/manage/components/item-details.css
@@ -3,7 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.item-details {
- margin: 2em;
+ padding: 2em;
+ background-color: #f9f9fa;
}
.item-details > h1 {
@@ -12,7 +13,6 @@
}
.buttons {
- border-top: 1px solid #d6d6d6;
margin-top: 1em;
padding: 1em 0;
}
diff --git a/src/webextension/list/manage/components/item-details.js b/src/webextension/list/manage/components/item-details.js
index da8c609f..fcab13f3 100644
--- a/src/webextension/list/manage/components/item-details.js
+++ b/src/webextension/list/manage/components/item-details.js
@@ -15,19 +15,19 @@ import styles from "./item-details.css";
// Note: ItemDetails doesn't directly interact with items from the Lockbox
// datastore. For that, please consult <../containers/current-item.js>.
-export default function ItemDetails({fields, onEdit, onDelete}) {
+export default function ItemDetails({fields, onCopy, onEdit, onDelete}) {
return (
iTEm dETAILs
-
+
-
+
@@ -36,6 +36,7 @@ export default function ItemDetails({fields, onEdit, onDelete}) {
ItemDetails.propTypes = {
...ItemFields.propTypes,
+ onCopy: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};
diff --git a/src/webextension/list/manage/components/link-account.css b/src/webextension/list/manage/components/link-account.css
new file mode 100644
index 00000000..6a04cef5
--- /dev/null
+++ b/src/webextension/list/manage/components/link-account.css
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.link {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 2em auto;
+ padding: 0;
+ max-width: 500px;
+}
+
+.link > h2,
+.link > p {
+ text-align: center;
+ letter-spacing: 0.2px;
+ line-height: 1.5;
+}
+
+.link > h2 {
+ margin: 0 0 .5em;
+ padding: 0;
+ font-size: 17px;
+ font-weight: 600;
+ line-height: 1.32;
+}
+
+.link > p {
+ font-size: 15px;
+ margin: 0 0 0.5em;
+}
+
+.link > menu {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 0;
+ padding: 0;
+}
+
+.link > menu > button {
+ margin: 0.5em;
+}
diff --git a/src/webextension/list/manage/components/link-account.js b/src/webextension/list/manage/components/link-account.js
new file mode 100644
index 00000000..b0ac0a70
--- /dev/null
+++ b/src/webextension/list/manage/components/link-account.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import PropTypes from "prop-types";
+import React from "react";
+
+import Button from "../../../widgets/button";
+
+import styles from "./link-account.css";
+
+export default function LinkAccount({ onCreate, onSignin }) {
+ return (
+
+
+
uPGRADe
+
+
+
uPGRADe yOUr lOCKBOx
+
+
+
+ );
+}
+LinkAccount.propTypes = {
+ onCreate: PropTypes.func.isRequired,
+ onSignin: PropTypes.func.isRequired,
+};
diff --git a/src/webextension/list/manage/components/send-feedback.js b/src/webextension/list/manage/components/send-feedback.js
deleted file mode 100644
index d17e6944..00000000
--- a/src/webextension/list/manage/components/send-feedback.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Localized } from "fluent-react";
-import React from "react";
-
-import Button from "../../../widgets/button";
-import * as telemetry from "../../../telemetry";
-
-const FEEDBACK_URL = "https://qsurvey.mozilla.com/s3/Lockbox-Input";
-
-export default function SendFeedback() {
- const doClick = () => {
- telemetry.recordEvent("feedbackClick", "manage");
- window.open(FEEDBACK_URL, "_blank");
- };
-
- return (
-
-
-
- );
-}
-
-SendFeedback.propTypes = {};
diff --git a/src/webextension/list/manage/containers/account-details.js b/src/webextension/list/manage/containers/account-details.js
new file mode 100644
index 00000000..9a3c3e76
--- /dev/null
+++ b/src/webextension/list/manage/containers/account-details.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import React from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import * as telemetry from "../../../telemetry";
+import LinkAccount from "../components/link-account";
+import AccountLinked from "../components/account-linked";
+
+function linkAction(action) {
+ const obj = (action === "signup") ?
+ "manageAcctCreate" :
+ "manageAcctSignin";
+ return async () => {
+ telemetry.recordEvent("click", obj);
+ browser.runtime.sendMessage({
+ type: "upgrade_account",
+ action,
+ });
+ };
+}
+
+const ConnectedLinkAccount = connect(
+ (state) => ({
+ ...state,
+ onCreate: linkAction("signup"),
+ onSignin: linkAction("signin"),
+ })
+)(LinkAccount);
+
+function AccountDetails({mode}) {
+ let inner = null;
+
+ if (mode === "guest") {
+ inner = ;
+ } else if (mode === "authenticated") {
+ inner = ;
+ }
+
+ return
{inner}
;
+}
+
+AccountDetails.propTypes = {
+ mode: PropTypes.string.isRequired,
+};
+
+export default connect(
+ (state) => ({
+ mode: state.account.mode,
+ })
+)(AccountDetails);
diff --git a/src/webextension/list/manage/containers/account-summary.js b/src/webextension/list/manage/containers/account-summary.js
new file mode 100644
index 00000000..de329146
--- /dev/null
+++ b/src/webextension/list/manage/containers/account-summary.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { connect } from "react-redux";
+
+import AccountSummaryLabel from "../components/account-summary-label";
+
+export default connect(
+ (state) => {
+ if (state.account.mode === "authenticated") {
+ return {
+ email: state.account.email,
+ };
+ }
+ return {};
+ }
+)(AccountSummaryLabel);
diff --git a/src/webextension/list/manage/containers/add-item.js b/src/webextension/list/manage/containers/add-item.js
index 4877c9ad..4196ed51 100644
--- a/src/webextension/list/manage/containers/add-item.js
+++ b/src/webextension/list/manage/containers/add-item.js
@@ -9,17 +9,13 @@ import { connect } from "react-redux";
import Button from "../../../widgets/button";
import { startNewItem } from "../../actions";
-import * as telemetry from "../../../telemetry";
-
-function AddItem({dispatch}) {
- const doClick = () => {
- telemetry.recordEvent("addClick", "addButton");
- dispatch(startNewItem());
- };
+import { NEW_ITEM_ID } from "../../common";
+function AddItem({disabled, onAddItem}) {
return (
-
@@ -27,7 +23,15 @@ function AddItem({dispatch}) {
}
AddItem.propTypes = {
- dispatch: PropTypes.func.isRequired,
+ disabled: PropTypes.bool.isRequired,
+ onAddItem: PropTypes.func.isRequired,
};
-export default connect()(AddItem);
+export default connect(
+ (state) => ({
+ disabled: state.list.selectedItemId === NEW_ITEM_ID,
+ }),
+ (dispatch) => ({
+ onAddItem: () => { dispatch(startNewItem()); },
+ })
+)(AddItem);
diff --git a/src/webextension/list/manage/containers/current-selection.js b/src/webextension/list/manage/containers/current-selection.js
index 0e0f10fb..3c765e9a 100644
--- a/src/webextension/list/manage/containers/current-selection.js
+++ b/src/webextension/list/manage/containers/current-selection.js
@@ -9,11 +9,12 @@ import { connect } from "react-redux";
import { flattenItem, unflattenItem } from "../../common";
import {
addItem, updateItem, requestRemoveItem, editCurrentItem, requestCancelEditing,
- editorChanged,
+ editorChanged, copiedField,
} from "../../actions";
import EditItemDetails from "../components/edit-item-details";
import ItemDetails from "../components/item-details";
import Homepage from "../components/homepage";
+import IntroPage from "../components/intro-page";
const ConnectedEditItemDetails = connect(
(state, ownProps) => ({
@@ -45,6 +46,7 @@ const ConnectedItemDetails = connect(
fields: flattenItem(ownProps.item),
}),
(dispatch, ownProps) => ({
+ onCopy: (field) => { dispatch(copiedField(field)); },
onEdit: () => { dispatch(editCurrentItem()); },
onDelete: () => { dispatch(requestRemoveItem(ownProps.item.id)); },
})
@@ -59,8 +61,10 @@ function CurrentSelection({editing, item, hideHome, numItems}) {
} else if (hideHome) {
// Don't show anything since we're still loading the item details.
inner = null;
+ } else if (numItems !== 0) {
+ inner = ;
} else {
- inner = ;
+ inner = ;
}
return
{inner}
;
}
diff --git a/src/webextension/list/manage/containers/go-home.js b/src/webextension/list/manage/containers/go-home.js
index d475a007..c2c0e2a5 100644
--- a/src/webextension/list/manage/containers/go-home.js
+++ b/src/webextension/list/manage/containers/go-home.js
@@ -13,7 +13,7 @@ import { requestSelectItem } from "../../actions";
function GoHome({onClick}) {
return (
-
+
hOMe
diff --git a/src/webextension/list/manage/containers/item-count.js b/src/webextension/list/manage/containers/item-count.js
deleted file mode 100644
index c16c33b5..00000000
--- a/src/webextension/list/manage/containers/item-count.js
+++ /dev/null
@@ -1,28 +0,0 @@
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Localized } from "fluent-react";
-import PropTypes from "prop-types";
-import React from "react";
-import { connect } from "react-redux";
-
-import styles from "./item-count.css";
-
-function ItemCount({count}) {
- return (
-
- ## iTEMs
-
- );
-}
-
-ItemCount.propTypes = {
- count: PropTypes.number.isRequired,
-};
-
-export default connect(
- (state) => ({
- count: state.cache.items.length,
- })
-)(ItemCount);
diff --git a/src/webextension/popup/unlock/components/app.css b/src/webextension/list/manage/containers/open-faq.css
similarity index 56%
rename from src/webextension/popup/unlock/components/app.css
rename to src/webextension/list/manage/containers/open-faq.css
index efbc44fc..4c9f3cae 100644
--- a/src/webextension/popup/unlock/components/app.css
+++ b/src/webextension/list/manage/containers/open-faq.css
@@ -2,13 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-body {
- padding: 0;
- margin: 0;
- width: 250px;
- color: #0c0c0d;
- background-color: #f9f9fa;
- font: caption;
- font-size: 13px;
- -moz-user-select: none;
+.faq {
+ font-size: 15px;
}
diff --git a/src/webextension/list/manage/containers/open-faq.js b/src/webextension/list/manage/containers/open-faq.js
new file mode 100644
index 00000000..21bfc2e3
--- /dev/null
+++ b/src/webextension/list/manage/containers/open-faq.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import PropTypes from "prop-types";
+import React from "react";
+import { connect } from "react-redux";
+
+import { ExternalLink } from "../../../widgets/link";
+import { openFAQ } from "../../actions";
+
+import styles from "./open-faq.css";
+
+function OpenFAQ({onOpenFAQ}) {
+ return (
+
+
+ fAq
+
+
+ );
+}
+
+OpenFAQ.propTypes = {
+ onOpenFAQ: PropTypes.func.isRequired,
+};
+
+export default connect(
+ undefined,
+ (dispatch) => ({
+ onOpenFAQ: () => { dispatch(openFAQ()); },
+ })
+)(OpenFAQ);
diff --git a/src/webextension/list/manage/containers/item-count.css b/src/webextension/list/manage/containers/send-feedback.css
similarity index 51%
rename from src/webextension/list/manage/containers/item-count.css
rename to src/webextension/list/manage/containers/send-feedback.css
index 54d82943..2ff9e646 100644
--- a/src/webextension/list/manage/containers/item-count.css
+++ b/src/webextension/list/manage/containers/send-feedback.css
@@ -1,8 +1,7 @@
- /* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-.item-count {
- font-size: 17px;
- white-space: nowrap;
+.send-feedback {
+ font-size: 15px;
}
diff --git a/src/webextension/list/manage/containers/send-feedback.js b/src/webextension/list/manage/containers/send-feedback.js
new file mode 100644
index 00000000..70263c84
--- /dev/null
+++ b/src/webextension/list/manage/containers/send-feedback.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import PropTypes from "prop-types";
+import React from "react";
+import { connect } from "react-redux";
+
+import { ExternalLink } from "../../../widgets/link";
+import { sendFeedback } from "../../actions";
+
+import styles from "./send-feedback.css";
+
+function SendFeedback({onSendFeedback}) {
+ return (
+
+
+ fEEDBACk
+
+
+ );
+}
+
+SendFeedback.propTypes = {
+ onSendFeedback: PropTypes.func.isRequired,
+};
+
+export default connect(
+ undefined,
+ (dispatch) => ({
+ onSendFeedback: () => { dispatch(sendFeedback()); },
+ })
+)(SendFeedback);
diff --git a/src/webextension/list/manage/index.js b/src/webextension/list/manage/index.js
index 6b0ce700..d798ee1b 100644
--- a/src/webextension/list/manage/index.js
+++ b/src/webextension/list/manage/index.js
@@ -10,12 +10,16 @@ import thunk from "redux-thunk";
import AppLocalizationProvider from "../../l10n";
import App from "./components/app";
-import { listItems } from "../actions";
+import { getAccountDetails, listItems } from "../actions";
import reducer from "./reducers";
import initializeMessagePorts from "../message-ports";
import * as telemetry from "../../telemetry";
+import telemetryLogger from "./telemetry";
-const store = createStore(reducer, undefined, applyMiddleware(thunk));
+const store = createStore(reducer, undefined, applyMiddleware(
+ thunk, telemetryLogger
+));
+store.dispatch(getAccountDetails());
store.dispatch(listItems());
initializeMessagePorts(store);
@@ -23,7 +27,7 @@ telemetry.recordEvent("render", "manage");
ReactDOM.render(
-
diff --git a/src/webextension/list/manage/reducers.js b/src/webextension/list/manage/reducers.js
index bd1d3392..c1c8f87f 100644
--- a/src/webextension/list/manage/reducers.js
+++ b/src/webextension/list/manage/reducers.js
@@ -5,7 +5,7 @@
import { combineReducers } from "redux";
import * as actions from "../actions";
-import { cacheReducer, listReducer } from "../reducers";
+import { accountReducer, cacheReducer, listReducer } from "../reducers";
export function editorReducer(state = {
editing: false, changed: false, hideHome: false,
@@ -45,6 +45,7 @@ export function modalReducer(state = {id: null, props: null}, action) {
}
export default combineReducers({
+ account: accountReducer,
cache: cacheReducer,
list: listReducer,
editor: editorReducer,
diff --git a/src/webextension/list/manage/telemetry.js b/src/webextension/list/manage/telemetry.js
new file mode 100644
index 00000000..d38904bf
--- /dev/null
+++ b/src/webextension/list/manage/telemetry.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import * as actions from "../actions";
+import * as telemetry from "../../telemetry";
+
+export default (store) => (next) => (action) => {
+ try {
+ switch (action.type) {
+ case actions.ADD_ITEM_STARTING:
+ telemetry.recordEvent("itemAdding", "manage");
+ break;
+ case actions.UPDATE_ITEM_STARTING:
+ telemetry.recordEvent("itemUpdating", "manage");
+ break;
+ case actions.REMOVE_ITEM_STARTING:
+ telemetry.recordEvent("itemDeleting", "manage");
+ break;
+ case actions.ADD_ITEM_COMPLETED:
+ if (action.interactive && action.item) {
+ telemetry.recordEvent("itemAdded", "manage",
+ {itemid: action.item.id});
+ }
+ break;
+ case actions.UPDATE_ITEM_COMPLETED:
+ if (action.interactive && action.item) {
+ telemetry.recordEvent("itemUpdated", "manage",
+ {itemid: action.item.id});
+ }
+ break;
+ case actions.REMOVE_ITEM_COMPLETED:
+ if (action.interactive && action.item) {
+ telemetry.recordEvent("itemDeleted", "manage",
+ {itemid: action.item.id});
+ }
+ break;
+ case actions.SELECT_ITEM_COMPLETED:
+ if (action.item) {
+ telemetry.recordEvent("itemSelected", "manage",
+ {itemid: action.item.id});
+ }
+ break;
+ case actions.COPIED_FIELD:
+ telemetry.recordEvent(`${action.field}Copied`, "manage");
+ break;
+ case actions.START_NEW_ITEM:
+ telemetry.recordEvent("addClick", "manage");
+ break;
+ case actions.SEND_FEEDBACK:
+ telemetry.recordEvent("feedbackClick", "manage");
+ break;
+ case actions.OPEN_FAQ:
+ telemetry.recordEvent("faqClick", "manage");
+ break;
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error("Unable to record telemetry event", e);
+ }
+
+ return next(action);
+};
diff --git a/src/webextension/list/message-ports.js b/src/webextension/list/message-ports.js
index f1696eea..e04b1ba5 100644
--- a/src/webextension/list/message-ports.js
+++ b/src/webextension/list/message-ports.js
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { addedItem, updatedItem, removedItem } from "./actions";
+import { accountDetailsUpdated, addedItem, updatedItem, removedItem } from "./actions";
let messagePort;
@@ -12,6 +12,9 @@ export default function initializeMessagePorts(store) {
messagePort = browser.runtime.connect();
messagePort.onMessage.addListener((message) => {
switch (message.type) {
+ case "account_details_updated":
+ store.dispatch(accountDetailsUpdated(message.account));
+ break;
case "added_item":
store.dispatch(addedItem(message.item));
break;
diff --git a/src/webextension/list/popup/components/item-details-panel.js b/src/webextension/list/popup/components/item-details-panel.js
index 63156b73..74ab7eb5 100644
--- a/src/webextension/list/popup/components/item-details-panel.js
+++ b/src/webextension/list/popup/components/item-details-panel.js
@@ -11,17 +11,17 @@ import { ItemFields } from "../../components/item-fields";
import styles from "./item-details-panel.css";
-export default function ItemDetailsPanel({fields, onBack}) {
+export default function ItemDetailsPanel({fields, onCopy, onBack}) {
return (
- { onBack(); }}>
+
eNTRy dETAILs
-
+
);
@@ -29,5 +29,6 @@ export default function ItemDetailsPanel({fields, onBack}) {
ItemDetailsPanel.propTypes = {
...ItemFields.propTypes,
+ onCopy: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
};
diff --git a/src/webextension/list/popup/containers/current-selection.js b/src/webextension/list/popup/containers/current-selection.js
index 7fd6a995..15de934b 100644
--- a/src/webextension/list/popup/containers/current-selection.js
+++ b/src/webextension/list/popup/containers/current-selection.js
@@ -7,7 +7,7 @@ import React from "react";
import { connect } from "react-redux";
import { flattenItem } from "../../common";
-import { selectItem } from "../../actions";
+import { selectItem, copiedField } from "../../actions";
import ItemDetailsPanel from "../components/item-details-panel";
import ItemListPanel from "../components/item-list-panel";
@@ -16,6 +16,7 @@ const ConnectedItemDetailsPanel = connect(
fields: flattenItem(ownProps.item),
}),
(dispatch) => ({
+ onCopy: (field) => { dispatch(copiedField(field)); },
onBack: () => { dispatch(selectItem(null)); },
})
)(ItemDetailsPanel);
diff --git a/src/webextension/list/popup/index.js b/src/webextension/list/popup/index.js
index 7decd846..0e1abb68 100644
--- a/src/webextension/list/popup/index.js
+++ b/src/webextension/list/popup/index.js
@@ -13,11 +13,17 @@ import App from "./components/app";
import { listItems } from "../actions";
import reducer from "./reducers";
import initializeMessagePorts from "../message-ports";
+import * as telemetry from "../../telemetry";
+import telemetryLogger from "./telemetry";
-const store = createStore(reducer, undefined, applyMiddleware(thunk));
+const store = createStore(reducer, undefined, applyMiddleware(
+ thunk, telemetryLogger
+));
store.dispatch(listItems());
initializeMessagePorts(store);
+telemetry.recordEvent("render", "doorhanger");
+
ReactDOM.render(
(next) => (action) => {
+ try {
+ switch (action.type) {
+ case actions.SELECT_ITEM_COMPLETED:
+ if (action.item) {
+ telemetry.recordEvent("itemSelected", "doorhanger",
+ {itemid: action.item.id});
+ }
+ break;
+ case actions.COPIED_FIELD:
+ telemetry.recordEvent(`${action.field}Copied`, "doorhanger");
+ break;
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error("Unable to record telemetry event", e);
+ }
+
+ return next(action);
+};
diff --git a/src/webextension/list/reducers.js b/src/webextension/list/reducers.js
index 0487b26c..f078a83b 100644
--- a/src/webextension/list/reducers.js
+++ b/src/webextension/list/reducers.js
@@ -27,6 +27,18 @@ function maybeRemoveCurrentItem(state, action) {
return {};
}
+export function accountReducer(state = { mode: "guest" }, action) {
+ switch (action.type) {
+ case actions.GET_ACCOUNT_DETAILS_COMPLETED:
+ return {
+ ...state,
+ ...action.account,
+ };
+ default:
+ return state;
+ }
+}
+
export function cacheReducer(state = {items: [], currentItem: null}, action) {
switch (action.type) {
case actions.LIST_ITEMS_COMPLETED:
diff --git a/src/webextension/locales/en-US/common.ftl b/src/webextension/locales/en-US/common.ftl
new file mode 100644
index 00000000..2ec6d727
--- /dev/null
+++ b/src/webextension/locales/en-US/common.ftl
@@ -0,0 +1,10 @@
+product-title = Lockbox
+product-tagline = The simple way to store, retrieve and manage website login info
+
+// when Kinto is supported, the above should be changed to:
+// Creating a Firefox account - or adding Lockbox to an existing account - protects your logins
+// with the strongest encryption available and syncs your Lockbox info across accounts.
+
+
+product-action-signin = Sign In
+product-action-prefs = Preferences
diff --git a/src/webextension/locales/en-US/firstrun.ftl b/src/webextension/locales/en-US/firstrun.ftl
index 87a965e3..7e9ae417 100644
--- a/src/webextension/locales/en-US/firstrun.ftl
+++ b/src/webextension/locales/en-US/firstrun.ftl
@@ -1,29 +1,13 @@
document
.title = Welcome to Lockbox
-welcome-title = Welcome to Lockbox (Alpha)
+firstrun-intro-title = Welcome to { product-title }
+firstrun-intro-tagline = { product-tagline }
-welcome-intro =
- You've just installed the Lockbox browser extension!
- This Alpha prototype will give you the ability to create
- new entries, and then later you can view, search, edit, and
- delete those entries.
+firstrun-using-guest-title = New to Lockbox?
-welcome-warning =
- This is a rapidly evolving prototype that will change as Alpha progresses.
- Any data stored here is not guaranteed to be retained in future updates.
+firstrun-using-guest-action = Get Started
-welcome-feedback =
- Please be sure to let us know your thoughts using our feedback
- button within the tool, including any issues you may find, things
- you like, and the things you're looking forward to in the future.
+firstrun-using-returning-title = Returning User?
-master-password-setup-formtitle = Please create a master password below:
-
-master-password-setup-password = Password
-
-master-password-setup-confirm = Confirm Password
-
-master-password-setup-err-mismatch = Passwords to not match
-
-master-password-setup-submit = Continue to Lockbox
+firstrun-using-returning-action = Sign in to your Firefox Account
diff --git a/src/webextension/locales/en-US/list.ftl b/src/webextension/locales/en-US/list.ftl
index 90159ee6..72c3f2ab 100644
--- a/src/webextension/locales/en-US/list.ftl
+++ b/src/webextension/locales/en-US/list.ftl
@@ -3,10 +3,10 @@
document
.title = Lockbox Entries
-item-fields-title = Entry Name
+item-fields-title = Site Name
item-fields-title-input
.placeholder = Primary Bank
-item-fields-origin = Website Address
+item-fields-origin = Site Address
item-fields-origin-input
.placeholder = www.example.com
item-fields-username = Username
@@ -18,66 +18,74 @@ item-fields-password = Password
item-fields-copy-password = Copy
.title = Copy the password to the clipboard
item-fields-notes = Notes
-item-fields-notes-input
- .placeholder = Answers to security questions or other account specifics…
-item-summary-new-title = New Entry
+item-summary-new-title = New entry
item-summary-title =
{ $length ->
- [0] (No Entry Name)
+ [0] (no site name)
*[other] { $title }
}
item-summary-username =
{ $length ->
- [0] (No Username)
+ [0] (no username)
*[other] { $username }
}
item-filter
- .placeholder = Search for an entry
+ .placeholder = Search Lockbox Entries
[[manage]]
-toolbar-item-count =
- { $count ->
- [one] 1 Entry
- *[other] { $count } Entries
- }
-
-toolbar-add-item = New Entry
+toolbar-add-item = New entry
toolbar-go-home = Home
-toolbar-send-feedback = Feedback
+toolbar-send-feedback = Provide Feedback
+toolbar-open-faq = FAQ
all-items-empty =
- When you create an entry, it will be saved in this sidebar.
+ When you add an Entry, it automatically shows up here.
all-items-filtered = No results
-homepage-title =
- { $count ->
- [0] Welcome to Lockbox
- [1] You have { $count } entry in your Lockbox
- *[other] You have { $count } entries in your Lockbox
- }
+intro-page-step-1 =
+ Save username and password info to create a { product-title } entry.
+
+ .title = Add login info to { product-title }
+
+intro-page-step-2 =
+ Click the { product-title } icon to see all the entries you've saved.
+
+ .title = Go straight to your logins
-homepage-greeting =
- You’ve successfully installed the Lockbox browser extension! This Alpha
- prototype gives you the ability to create new entries, and then view, search,
- edit, and delete those entries.
-
- Please be sure to let us know your thoughts using our feedback button above,
- including any issues you may find, things you like, and the things you’re
- looking forward to in the future.
+intro-page-step-3 =
+ Copy an entry's info to sign in right from Firefox.
+
+ .title = Sign in from { product-title }
+
+homepage-title = { product-tagline }
+
+homepage-linkaccount-title = Add Serious Security & Convenience
+homepage-linkaccount-description =
+ Now create a Firefox account – or add { product-title } to an existing
+ account – to protect your logins with the strongest encryption
+ available and sync your { product-title } info across devices.
+
+homepage-linkaccount-action-create = Create Account
+homepage-linkaccount-action-signin = { product-action-signin }
+
+homepage-accountlinked-title = Your logins are locked down tight!
+homepage-accountlinked-description =
+ { product-title } uses the strongest encryption available to
+ protect your logins – even for banking and other critical sites.
item-details-heading-view = Entry Details
-item-details-heading-new = Create a New Entry
-item-details-heading-edit = Edit Entry
+item-details-heading-new = Create New Entry
+item-details-heading-edit = Edit Entry Details
-item-details-edit = Edit Entry
-item-details-delete = Delete Entry
+item-details-edit = Edit
+item-details-delete = Delete
-item-details-save-new = Save Entry
-item-details-save-existing = Save Changes
+item-details-save-new = Create Entry
+item-details-save-existing = Save
item-details-cancel = Cancel
[[popup]]
@@ -86,12 +94,14 @@ manage-lockbox-button = Manage Lockbox
item-details-panel-title = Entry Details
+navigate-panel-backwards = Go back
+
[[dialogs]]
-modal-cancel-editing = This entry has unsaved changes. Are you sure you want to discard them?
+modal-cancel-editing = Unsaved changes exist. Discard them?
.confirmLabel = Discard Changes
.cancelLabel = Go Back
-modal-delete = Are you sure you want to delete this entry?
- .confirmLabel = Delete Entry
+modal-delete = Delete this Entry?
+ .confirmLabel = Delete
.cancelLabel = Cancel
diff --git a/src/webextension/locales/en-US/popup.ftl b/src/webextension/locales/en-US/popup.ftl
deleted file mode 100644
index b0a20dd3..00000000
--- a/src/webextension/locales/en-US/popup.ftl
+++ /dev/null
@@ -1,8 +0,0 @@
-[[unlock]]
-
-unlock-prompt-title = Lockbox
-unlock-prompt-desc = Enter your password
-
-unlock-prompt-err-invalid-pwd = Wrong password
-
-unlock-prompt-submit = Unlock
diff --git a/src/webextension/locales/en-US/unlock.ftl b/src/webextension/locales/en-US/unlock.ftl
new file mode 100644
index 00000000..8c7a8339
--- /dev/null
+++ b/src/webextension/locales/en-US/unlock.ftl
@@ -0,0 +1,5 @@
+unlock-title = { product-title }
+unlock-tagline = { product-tagline }
+
+unlock-action-signin = { product-action-signin }
+unlock-action-prefs = { product-action-prefs }
diff --git a/src/webextension/locales/en-US/widgets.ftl b/src/webextension/locales/en-US/widgets.ftl
index 3f500244..b14c7e09 100644
--- a/src/webextension/locales/en-US/widgets.ftl
+++ b/src/webextension/locales/en-US/widgets.ftl
@@ -3,5 +3,8 @@ filter-input-clear = Clear
password-input-show = Show
password-input-hide = Hide
+panel-back-button
+ .alt = Go Back
+
modal-root
.contentLabel = Modal dialog
diff --git a/src/webextension/manifest.json.tpl b/src/webextension/manifest.json.tpl
index cfafee75..e4579ea5 100644
--- a/src/webextension/manifest.json.tpl
+++ b/src/webextension/manifest.json.tpl
@@ -19,7 +19,8 @@
"default_icon": {
"32": "icons/lb_locked.svg"
},
- "default_title": "Lockbox"
+ "default_title": "Lockbox",
+ "browser_style": false
},
"commands": {
@@ -37,6 +38,7 @@
],
"options_ui": {
- "page": "settings/index.html"
+ "page": "settings/index.html",
+ "browser_style": false
}
}
diff --git a/src/webextension/popup/unlock/components/unlock-prompt.css b/src/webextension/popup/unlock/components/unlock-prompt.css
deleted file mode 100644
index 46089a7f..00000000
--- a/src/webextension/popup/unlock/components/unlock-prompt.css
+++ /dev/null
@@ -1,27 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-.unlock-prompt {
- margin: 1em;
-}
-
-.unlock-prompt > form {
- width: 100%;
- height: 100%;
-
- margin: 0;
- padding: 0;
- display: flex;
- flex-flow: column nowrap;
- justify-content: space-around;
- align-items: center;
-}
-
-.unlock-prompt > form > p {
- flex: 1;
-}
-
-.password-input {
- margin-bottom: 1em;
-}
diff --git a/src/webextension/popup/unlock/components/unlock-prompt.js b/src/webextension/popup/unlock/components/unlock-prompt.js
deleted file mode 100644
index 2d7c8589..00000000
--- a/src/webextension/popup/unlock/components/unlock-prompt.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Localized } from "fluent-react";
-import React from "react";
-
-import Button from "../../../widgets/button";
-import ErrorMessage from "../../../widgets/error-message";
-import PasswordInput from "../../../widgets/password-input";
-
-import styles from "./unlock-prompt.css";
-
-export default class UnlockPrompt extends React.Component {
- constructor() {
- super();
- this.state = {
- error: "",
- password: "",
- };
- }
-
- // This function exists to allow tests to override it; otherwise, our unit
- // tests would break because we navigated away from the test URL!
- _navigate(url) {
- // istanbul ignore next
- window.location.replace(url);
- }
-
- async attempt() {
- const { password } = this.state;
-
- try {
- await browser.runtime.sendMessage({
- type: "unlock",
- password,
- });
-
- if (process.env.ENABLE_DOORHANGER) {
- this._navigate(browser.extension.getURL("/list/popup/index.html"));
- } else {
- await browser.runtime.sendMessage({
- type: "open_view",
- name: "manage",
- });
- window.close();
- }
- } catch (err) {
- this.setState({
- error: "unlock-prompt-err-invalid-pwd",
- });
- }
- }
-
- componentDidMount() {
- this._passwordField.focus();
- }
-
- handlePasswordChange(value) {
- this.setState({
- error: "",
- password: value,
- });
- }
-
- render() {
- const { error } = this.state;
-
- // XXX: It might be worth splitting this up and Redux-ifying this view so
- // that each component is as simple as possible.
- return (
-
-
-
- );
- }
-}
diff --git a/src/webextension/unlock/components/app.css b/src/webextension/unlock/components/app.css
new file mode 100644
index 00000000..5ec13990
--- /dev/null
+++ b/src/webextension/unlock/components/app.css
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+body {
+ padding: 0;
+ margin: 0;
+ width: 344px;
+ color: #0c0c0d;
+ background-color: #f9f9fa;
+ font: caption;
+ font-size: 13px;
+ -moz-user-select: none;
+}
+
+.unlock {
+ margin: 0;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+}
+
+.unlock > img {
+ margin: 2em auto;
+ width: 229px;
+}
+
+.unlock > .content {
+ margin: 2em auto;
+ width: 229px;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+}
+
+.unlock > .content > h1 {
+ font-size: 2em;
+ font-weight: bold;
+ text-align: center;
+ margin: 0 0 .5em;
+}
+
+.unlock > .content > h2 {
+ font-size: 1.5em;
+ font-weight: normal;
+ font-style: italic;
+ text-align: center;
+ text-transform: lowercase;;
+ margin: 0;
+}
+
+.unlock > menu {
+ margin: 0;
+ padding: 0;
+ width: 100vw;
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: stretch;
+}
+
+.unlock > menu > * {
+ width: 50%;
+ flex-grow: 1;
+}
+
+.unlock > menu > * {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.unlock > menu > *:first-child {
+ border-bottom-right-radius: 0;
+}
+
+.unlock > menu > *:last-child {
+ border-bottom-left-radius: 0;
+}
diff --git a/src/webextension/unlock/components/app.js b/src/webextension/unlock/components/app.js
new file mode 100644
index 00000000..5ec9ddec
--- /dev/null
+++ b/src/webextension/unlock/components/app.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Localized } from "fluent-react";
+import React from "react";
+
+import Button from "../../widgets/button";
+import * as telemetry from "../../telemetry";
+
+import styles from "./app.css";
+
+export default function App() {
+ const imgSrc = browser.extension.getURL("/images/nessie_v2.svg");
+
+ const doSignIn = async () => {
+ telemetry.recordEvent("click", "unlockSignin");
+ browser.runtime.sendMessage({
+ type: "signin",
+ view: "manage",
+ });
+ };
+ const doPrefs = async () => {
+ browser.runtime.openOptionsPage();
+ window.close();
+ };
+
+ return (
+
+
+
+
+