Track recently used logins & display first in popup (#9)

* Track recently used logins & display first in popup

Results are sorted by:
 - Most recent store first
 - Within each store, login with highest use count first
 - Within same usage count, most recent first

There is a 60-sec debounce on logins, to avoid excessive incrementing of
the counter when a login is used multiple times in rapid succession
(e.g. for copying user / pass etc.).
This commit is contained in:
Erayd
2018-04-22 00:26:05 +12:00
committed by GitHub
parent 65f6748737
commit af96b0309c
7 changed files with 132 additions and 10 deletions

View File

@@ -301,7 +301,7 @@ function hostAction(settings, action, params = {}) {
*/
async function parseFields(settings, login) {
var response = await hostAction(settings, "fetch", {
store: login.store,
store: login.store.name,
file: login.login + ".gpg"
});
if (response.status != "ok") {

View File

@@ -18,6 +18,8 @@
"chrome-extension-async": "^3.0.0",
"fuzzysort": "^1.1.0",
"mithril": "^1.1.0",
"moment": "^2.22.1",
"sha1": "^1.1.1",
"tldjs": "^2.3.0"
},
"devDependencies": {

View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="21px" version="1.1" viewBox="0 0 20 21" width="20px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#000000" id="Core" opacity="0.9" transform="translate(-464.000000, -254.000000)"><g id="history" transform="translate(464.000000, 254.500000)"><path d="M10.5,0 C7,0 3.9,1.9 2.3,4.8 L0,2.5 L0,9 L6.5,9 L3.7,6.2 C5,3.7 7.5,2 10.5,2 C14.6,2 18,5.4 18,9.5 C18,13.6 14.6,17 10.5,17 C7.2,17 4.5,14.9 3.4,12 L1.3,12 C2.4,16 6.1,19 10.5,19 C15.8,19 20,14.7 20,9.5 C20,4.3 15.7,0 10.5,0 L10.5,0 Z M9,5 L9,10.1 L13.7,12.9 L14.5,11.6 L10.5,9.2 L10.5,5 L9,5 L9,5 Z" id="Shape"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 813 B

View File

@@ -2,6 +2,7 @@ module.exports = Interface;
var m = require("mithril");
var FuzzySort = require("fuzzysort");
var Moment = require("moment");
var SearchInterface = require("./searchinterface");
/**
@@ -102,8 +103,21 @@ function view(ctl, params) {
}
},
[
badges ? m("div.store.badge", result.store) : null,
m("div.name", m.trust(result.display)),
badges ? m("div.store.badge", result.store.name) : null,
m("div.name", [
m.trust(result.display),
result.recent.when > 0
? m("div.recent", {
title:
"Used here " +
result.recent.count +
" time" +
(result.recent.count > 1 ? "s" : "") +
", last " +
Moment(new Date(result.recent.when)).fromNow()
})
: null
]),
m("div.action.copy-password", {
title: "Copy password",
onclick: function(e) {
@@ -148,7 +162,19 @@ function search(s) {
// get candidate list
var candidates = this.logins.map(result => Object.assign(result, { display: result.login }));
if (this.currentDomainOnly) {
candidates = candidates.filter(login => login.inCurrentDomain);
var recent = candidates.filter(login => login.recent.count > 0);
recent.sort(function(a, b) {
if (a.store.when != b.store.when) {
return b.store.when - a.store.when;
}
if (a.recent.count != b.recent.count) {
return b.recent.count - a.recent.count;
}
return b.recent.when - a.recent.when;
});
candidates = recent.concat(
candidates.filter(login => login.inCurrentDomain && !login.recent.count)
);
}
if (s.length) {
@@ -156,7 +182,7 @@ function search(s) {
// fuzzy-search first word & add highlighting
if (fuzzyFirstWord) {
candidates = FuzzySort.go(filter[0], candidates, {
keys: ["login", "store"],
keys: ["login", "store.name"],
allowTypo: false
}).map(result =>
Object.assign(result.obj, {

View File

@@ -95,9 +95,18 @@ body {
}
.part.login > .name {
display: flex;
width: 100%;
}
.part.login > .name > .recent {
background-image: url("icon-history.svg");
background-repeat: no-repeat;
background-size: contain;
margin-left: 4px;
width: 16px;
}
.part.login > .action {
background-position: right 3px top 0;
background-repeat: no-repeat;

View File

@@ -3,6 +3,7 @@
require("chrome-extension-async");
var TldJS = require("tldjs");
var sha1 = require("sha1");
var Interface = require("./interface");
// wrap with current tab & settings
@@ -15,6 +16,20 @@ chrome.tabs.query({ active: true, currentWindow: true }, async function(tabs) {
var settings = response.settings;
settings.tab = tabs[0];
settings.host = new URL(settings.tab.url).hostname;
for (var store in settings.stores) {
var when = localStorage.getItem("recent:" + settings.stores[store].path);
if (when) {
settings.stores[store].when = JSON.parse(when);
} else {
settings.stores[store].when = 0;
}
}
settings.recent = localStorage.getItem("recent");
if (settings.recent) {
settings.recent = JSON.parse(settings.recent);
} else {
settings.recent = {};
}
run(settings);
} catch (e) {
handleError(e);
@@ -79,21 +94,34 @@ async function run(settings) {
var response = await chrome.runtime.sendMessage({ action: "listFiles" });
var logins = [];
var index = 0;
var recent = localStorage.getItem("recent:" + settings.host);
if (recent) {
recent = JSON.parse(recent);
}
for (var store in response) {
for (var key in response[store]) {
// set login fields
var login = {
index: index++,
store: store,
store: settings.stores[store],
login: response[store][key].replace(/\.gpg$/i, ""),
allowFill: true
};
login.domain = pathToDomain(login.store + "/" + login.login);
login.domain = pathToDomain(store + "/" + login.login);
login.inCurrentDomain =
settings.host == login.domain || settings.host.endsWith("." + login.domain);
login.recent =
settings.recent[
sha1(settings.host + sha1(login.store.path + sha1(login.login)))
];
if (!login.recent) {
login.recent = {
when: 0,
count: 0
};
}
// bind handlers
login.doAction = withLogin.bind(login);
login.doAction = withLogin.bind({ settings: settings, login: login });
logins.push(login);
}
@@ -105,6 +133,34 @@ async function run(settings) {
}
}
/**
* Save login to recent list for current domain
*
* @since 3.0.0
*
* @param object settings Settings object
* @param object login Login object
* @param bool remove Remove this item from recent history
* @return void
*/
function saveRecent(settings, login, remove = false) {
var ignoreInterval = 60000; // 60 seconds - don't increment counter twice within this window
// save store timestamp
localStorage.setItem("recent:" + login.store.path, JSON.stringify(Date.now()));
// update login usage count & timestamp
if (Date.now() > login.recent.when + ignoreInterval) {
login.recent.count++;
}
login.recent.when = Date.now();
settings.recent[sha1(settings.host + sha1(login.store.path + sha1(login.login)))] =
login.recent;
// save to local storage
localStorage.setItem("recent", JSON.stringify(settings.recent));
}
/**
* Do a login action
*
@@ -142,10 +198,19 @@ async function withLogin(action) {
}
// hand off action to background script
var response = await chrome.runtime.sendMessage({ action: action, login: this });
var response = await chrome.runtime.sendMessage({
action: action,
login: this.login
});
if (response.status != "ok") {
throw new Error(response.message);
} else {
switch (action) {
case "fill":
case "copyPassword":
case "copyUsername":
saveRecent(this.settings, this.login);
}
window.close();
}
} catch (e) {

View File

@@ -224,6 +224,10 @@ cached-path-relative@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
"charenc@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
chrome-extension-async@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/chrome-extension-async/-/chrome-extension-async-3.2.4.tgz#c5b3e206688ca81c903b5e239ff3f9a73a29c47e"
@@ -303,6 +307,10 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
"crypt@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
crypto-browserify@^3.0.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -606,6 +614,10 @@ module-deps@^6.0.0:
through2 "^2.0.0"
xtend "^4.0.0"
moment@^2.22.1:
version "2.22.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -759,6 +771,13 @@ sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4:
inherits "^2.0.1"
safe-buffer "^5.0.1"
sha1@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848"
dependencies:
charenc ">= 0.0.1"
crypt ">= 0.0.1"
shasum@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f"