From cc6aa2440da1a6038b462c3581284f56dd2968e8 Mon Sep 17 00:00:00 2001 From: Erayd Date: Mon, 16 Apr 2018 10:58:51 +1200 Subject: [PATCH] Barebones implementation of 'list' action (#2) --- src/background.src.js | 44 +++++++-- src/global.css | 6 ++ src/manifest.json | 9 +- src/package.json | 5 +- src/popup.css | 28 ++++++ src/popup.html | 4 +- src/popup.src.js | 201 ++++++++++++++++++++++++++++++++++++++++-- src/yarn.lock | 16 +++- 8 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 src/global.css create mode 100644 src/popup.css 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"