Barebones implementation of 'list' action (#2)

This commit is contained in:
Erayd
2018-04-16 10:58:51 +12:00
committed by GitHub
parent 0f76a9f4e3
commit cc6aa2440d
8 changed files with 288 additions and 25 deletions

View File

@@ -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;
}
/**

6
src/global.css Normal file
View File

@@ -0,0 +1,6 @@
html, body {
font-family: sans;
font-size: 14px;
margin: 0;
padding: 0;
}

View File

@@ -20,14 +20,7 @@
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab",
"nativeMessaging",
"notifications",
"storage",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*"
"nativeMessaging"
]
}

View File

@@ -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",

28
src/popup.css Normal file
View File

@@ -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;
}

View File

@@ -1,9 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="global.css" />
<link rel="stylesheet" type="text/css" href="popup.css" />
<script src="popup.js"></script>
</head>
<body>
<p>TODO</p>
<div class="part notice">Loading available logins...</div>
</body>
</html>

View File

@@ -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();
}

View File

@@ -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"