diff --git a/src/background.src.js b/src/background.src.js
index c8501d0..63e4967 100644
--- a/src/background.src.js
+++ b/src/background.src.js
@@ -17,7 +17,12 @@ var defaultSettings = {
};
// handle incoming messages
-browser.runtime.onMessage.addListener(receiveMessage);
+browser.runtime.onMessage.addListener(function(message, sender, sendResponse) {
+ receiveMessage(message, sender, sendResponse);
+
+ // allow async responses after this function returns
+ return true;
+});
//----------------------------------- Function definitions ----------------------------------//
/**
@@ -49,7 +54,7 @@ function getLocalSettings() {
* @param function(mixed) sendResponse Callback to send response
* @return void
*/
-function handleMessage(settings, message, sendResponse) {
+async function handleMessage(settings, message, sendResponse) {
// check that action is present
if (typeof message !== "object" || !message.hasOwnProperty("action")) {
sendResponse({ status: "error", message: "Action is missing" });
@@ -66,6 +71,14 @@ function handleMessage(settings, message, sendResponse) {
saveSettings(message.settings);
sendResponse({ status: "ok" });
break;
+ case "listFiles":
+ try {
+ var response = await hostAction(settings, "list");
+ sendResponse(response.data.files);
+ } catch (e) {
+ console.log(e);
+ }
+ break;
default:
sendResponse({
status: "error",
@@ -75,6 +88,28 @@ function handleMessage(settings, message, sendResponse) {
}
}
+/**
+ * Send a request to the host app
+ *
+ * @since 3.0.0
+ *
+ * @param object settings Live settings object
+ * @param string action Action to run
+ * @param params object Additional params to pass to the host app
+ * @return Promise
+ */
+function hostAction(settings, action, params = {}) {
+ var request = {
+ settings: settings,
+ action: action
+ };
+ for (var key in params) {
+ request[key] = params[key];
+ }
+
+ return browser.runtime.sendNativeMessage(appID, request);
+}
+
/**
* Wrap inbound messages to fetch native configuration
*
@@ -105,7 +140,7 @@ async function receiveMessage(message, sender, sendResponse) {
for (var key in settings.stores) {
if (response.data.storeSettings.hasOwnProperty(key)) {
var fileSettings = JSON.parse(response.data.storeSettings[key]);
- if (typeof(settings.stores[key].settings) !== "object") {
+ if (typeof settings.stores[key].settings !== "object") {
settings.stores[key].settings = {};
}
var storeSettings = settings.stores[key].settings;
@@ -130,9 +165,6 @@ async function receiveMessage(message, sender, sendResponse) {
console.log(e);
sendResponse({ status: "error", message: e.toString() });
}
-
- // allow async responses after this function returns
- return true;
}
/**
diff --git a/src/global.css b/src/global.css
new file mode 100644
index 0000000..2c8ef1f
--- /dev/null
+++ b/src/global.css
@@ -0,0 +1,6 @@
+html, body {
+ font-family: sans;
+ font-size: 14px;
+ margin: 0;
+ padding: 0;
+}
diff --git a/src/manifest.json b/src/manifest.json
index 1bbd500..633c98c 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -20,14 +20,7 @@
"default_popup": "popup.html"
},
"permissions": [
- "tabs",
"activeTab",
- "nativeMessaging",
- "notifications",
- "storage",
- "webRequest",
- "webRequestBlocking",
- "http://*/*",
- "https://*/*"
+ "nativeMessaging"
]
}
diff --git a/src/package.json b/src/package.json
index 5c389ef..7e22ddd 100644
--- a/src/package.json
+++ b/src/package.json
@@ -15,7 +15,10 @@
}
],
"dependencies": {
- "chrome-extension-async": "^3.0.0"
+ "chrome-extension-async": "^3.0.0",
+ "fuzzysort": "^1.1.0",
+ "mithril": "^1.1.0",
+ "tldjs": "^2.3.0"
},
"devDependencies": {
"browserify": "^16.2.0",
diff --git a/src/popup.css b/src/popup.css
new file mode 100644
index 0000000..d89d0c1
--- /dev/null
+++ b/src/popup.css
@@ -0,0 +1,28 @@
+html, body {
+ height: 100%;
+ min-width: 260px;
+}
+
+body {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+}
+
+.part {
+ box-sizing: border-box;
+ padding: 4px 4px 0 4px;
+}
+
+.part:last-child {
+ padding-bottom: 4px;
+}
+
+.part.error {
+ color: #F00;
+}
+
+.part.notice {
+ color: #090;
+}
+
diff --git a/src/popup.html b/src/popup.html
index 1eb7a2d..1793316 100644
--- a/src/popup.html
+++ b/src/popup.html
@@ -1,9 +1,11 @@
+
+
- TODO
+ Loading available logins...
diff --git a/src/popup.src.js b/src/popup.src.js
index 0c03518..06c9efa 100644
--- a/src/popup.src.js
+++ b/src/popup.src.js
@@ -2,23 +2,208 @@
"use strict";
require("chrome-extension-async");
+var Mithril = require("mithril");
+var TldJS = require("tldjs");
+var FuzzySort = require("fuzzysort");
+var startTime = Date.now();
var settings = null;
+var error = null;
+var notice = null;
+var logins = [];
+var domainLogins = [];
+
if (typeof browser === "undefined") {
var browser = chrome;
}
-browser.runtime.sendMessage({ action: "getSettings" }).then(
- function(response) {
+// performance debugging function - TODO remove once extension is ready for release
+function checkpoint(activity) {
+ console.log("Elapsed: " + (Date.now() - startTime) + "ms (" + activity + ")");
+}
+
+// wrap with current tab & settings
+checkpoint("start");
+browser.tabs.query({ active: true, currentWindow: true }, async function(tabs) {
+ checkpoint("after tab");
+ try {
+ var response = browser.runtime.sendMessage({ action: "getSettings" });
+ checkpoint("after getSettings");
settings = response;
+ settings.tab = tabs[0];
+ settings.host = new URL(settings.tab.url).hostname;
run();
- },
- function(response) {
- console.log(response); // TODO
+ } catch (e) {
+ console.log(e.toString()); // TODO
}
-);
+});
//----------------------------------- Function definitions ----------------------------------//
-function run() {
- console.log(settings); // TODO
+
+/**
+ * Get the logins which match the provided domain
+ *
+ * @since 3.0.0
+ *
+ * @param string domain Domain to filter against
+ * @return array
+ */
+function getDomainLogins(domain) {
+ var domainLogins = [];
+ var t = TldJS.parse(domain);
+
+ // ignore invalid domains
+ if (!t.isValid || t.domain === null) {
+ return [];
+ }
+
+ // filter against the domain
+ for (var key in logins) {
+ if (logins[key].domain === t.hostname) {
+ domainLogins.push(logins[key]);
+ }
+ }
+
+ // recurse and add matching domains to the list
+ domainLogins = domainLogins.concat(getDomainLogins(t.hostname.replace(/^.+?\./, "")));
+
+ return domainLogins;
+}
+
+/**
+ * Get the deepest available domain component of a path
+ *
+ * @since 3.0.0
+ *
+ * @param string path Path to parse
+ * @return string|null Extracted domain
+ */
+function pathToDomain(path) {
+ var parts = path.split(/\//).reverse();
+ for (var key in parts) {
+ if (parts[key].indexOf("@") >= 0) {
+ continue;
+ }
+ var t = TldJS.parse(parts[key]);
+ if (t.isValid && t.domain !== null) {
+ return t.hostname;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Render the popup contents
+ *
+ * @since 3.0.0
+ *
+ * @return void
+ */
+function render() {
+ var body = document.getElementsByTagName("body")[0];
+ Mithril.mount(body, {
+ view: function() {
+ return [renderError(), renderNotice(), renderList()];
+ }
+ });
+ checkpoint("after render");
+}
+
+/**
+ * Render any error messages
+ *
+ * @since 3.0.0
+ *
+ * @return Vnode
+ */
+function renderError() {
+ return error === null ? null : Mithril("div.part.error", error);
+}
+
+/**
+ * Render any notices
+ *
+ * @since 3.0.0
+ *
+ * @return Vnode
+ */
+function renderNotice() {
+ return notice === null ? null : Mithril("div.part.notice", notice);
+}
+
+/**
+ * Render the list of available logins
+ *
+ * @since 3.0.0
+ *
+ * @return []Vnode
+ */
+function renderList() {
+ if (!logins.length) {
+ showError("There are no matching logins available");
+ return null;
+ }
+
+ var list = [];
+ domainLogins.forEach(function(login) {
+ list.push(
+ Mithril("div.part.login", { title: login.domain }, login.store + ":" + login.login)
+ );
+ });
+ if (!list.length) {
+ showNotice("There are no logins matching " + settings.host + ".");
+ }
+
+ checkpoint("after renderList");
+ return Mithril("div.logins", list);
+}
+
+async function run() {
+ try {
+ // get list of logins
+ var response = await browser.runtime.sendMessage({ action: "listFiles" });
+ checkpoint("after listFiles");
+ for (var store in response) {
+ for (var key in response[store]) {
+ var login = {
+ store: store,
+ login: response[store][key].replace(/\.gpg$/i, "")
+ };
+ login.domain = pathToDomain(login.store + "/" + login.login);
+ logins.push(login);
+ }
+ }
+ checkpoint("after listFiles post-processing");
+
+ domainLogins = getDomainLogins(settings.host);
+
+ render();
+ } catch (e) {
+ showError(e.toString());
+ }
+}
+
+/**
+ * Show an error message
+ *
+ * @since 3.0.0
+ *
+ * @param string message Message text
+ */
+function showError(message) {
+ error = message;
+ Mithril.redraw();
+}
+
+/**
+ * Show an informational message
+ *
+ * @since 3.0.0
+ *
+ * @param string message Message text
+ */
+function showNotice(message) {
+ notice = message;
+ Mithril.redraw();
}
diff --git a/src/yarn.lock b/src/yarn.lock
index ee1e2d0..b4858e8 100644
--- a/src/yarn.lock
+++ b/src/yarn.lock
@@ -400,6 +400,10 @@ function-bind@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+fuzzysort@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.1.tgz#bf128f1a4cc6e6b7188665ac5676de46a3d81768"
+
glob@^7.1.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@@ -572,6 +576,10 @@ minimist@^1.1.0, minimist@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+mithril@^1.1.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/mithril/-/mithril-1.1.6.tgz#bd2cc0de3d3c86076a6a7a30367a601a1bd108f3"
+
mkdirp@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -680,7 +688,7 @@ punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-punycode@^1.3.2:
+punycode@^1.3.2, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -837,6 +845,12 @@ timers-browserify@^1.0.1:
dependencies:
process "~0.11.0"
+tldjs@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-2.3.1.tgz#cf09c3eb5d7403a9e214b7d65f3cf9651c0ab039"
+ dependencies:
+ punycode "^1.4.1"
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"