Skip to content

Commit 99a58b4

Browse files
authored
Show badge with the number of matching entries (#50)
1 parent 0f8b5a3 commit 99a58b4

File tree

3 files changed

+169
-69
lines changed

3 files changed

+169
-69
lines changed

src/background.js

Lines changed: 165 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"use strict";
33

44
require("chrome-extension-async");
5+
var TldJS = require("tldjs");
56
var sha1 = require("sha1");
67

78
// native application id
@@ -17,8 +18,12 @@ var defaultSettings = {
1718

1819
var authListeners = {};
1920

21+
chrome.browserAction.setBadgeBackgroundColor({
22+
color: "#666"
23+
});
24+
2025
// watch for tab updates
21-
chrome.tabs.onUpdated.addListener(function(tabId, info) {
26+
chrome.tabs.onUpdated.addListener((tabId, info) => {
2227
// ignore non-complete status
2328
if (info.status !== "complete") {
2429
return;
@@ -29,6 +34,9 @@ chrome.tabs.onUpdated.addListener(function(tabId, info) {
2934
chrome.webRequest.onAuthRequired.removeListener(authListeners[tabId]);
3035
delete authListeners[tabId];
3136
}
37+
38+
// show number of matching passwords in a badge
39+
updateMatchingPasswordsCount(tabId);
3240
});
3341

3442
// handle incoming messages
@@ -43,6 +51,79 @@ chrome.runtime.onInstalled.addListener(onExtensionInstalled);
4351

4452
//----------------------------------- Function definitions ----------------------------------//
4553

54+
/**
55+
* Get the deepest available domain component of a path
56+
*
57+
* @since 3.0.0
58+
*
59+
* @param string path Path to parse
60+
* @return string|null Extracted domain
61+
*/
62+
function pathToDomain(path) {
63+
var parts = path.split(/\//).reverse();
64+
for (var key in parts) {
65+
if (parts[key].indexOf("@") >= 0) {
66+
continue;
67+
}
68+
var t = TldJS.parse(parts[key]);
69+
if (t.isValid && t.domain !== null) {
70+
return t.hostname;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
/**
78+
* Set badge text with the number of matching password entries
79+
*
80+
* @since 3.0.0
81+
*
82+
* @param int tabId Tab id
83+
*/
84+
async function updateMatchingPasswordsCount(tabId) {
85+
try {
86+
const settings = await getFullSettings();
87+
var response = await hostAction(settings, "list");
88+
if (response.status != "ok") {
89+
throw new Error(JSON.stringify(response));
90+
}
91+
92+
// Get tab info
93+
let currentDomain = undefined;
94+
try {
95+
const tab = await chrome.tabs.get(tabId);
96+
currentDomain = new URL(tab.url).hostname;
97+
} catch (e) {
98+
throw new Error(`Unable to determine domain of the tab with id ${tabId}`);
99+
}
100+
101+
let matchedPasswordsCount = 0;
102+
for (var storeId in response.data.files) {
103+
for (var key in response.data.files[storeId]) {
104+
const login = response.data.files[storeId][key].replace(/\.gpg$/i, "");
105+
const domain = pathToDomain(storeId + "/" + login);
106+
const inCurrentDomain =
107+
currentDomain === domain || currentDomain.endsWith("." + domain);
108+
const recent = settings.recent[sha1(currentDomain + sha1(storeId + sha1(login)))];
109+
if (recent || inCurrentDomain) {
110+
matchedPasswordsCount++;
111+
}
112+
}
113+
}
114+
115+
if (matchedPasswordsCount) {
116+
// Set badge for the current tab
117+
chrome.browserAction.setBadgeText({
118+
text: "" + matchedPasswordsCount,
119+
tabId: tabId
120+
});
121+
}
122+
} catch (e) {
123+
console.log(e);
124+
}
125+
}
126+
46127
/**
47128
* Copy text to clipboard
48129
*
@@ -89,6 +170,11 @@ function saveRecent(settings, login, remove = false) {
89170

90171
// save to local storage
91172
localStorage.setItem("recent", JSON.stringify(settings.recent));
173+
174+
// a new entry was added to the popup matching list, need to refresh the count
175+
if (!login.inCurrentDomain && login.recent.count === 1) {
176+
updateMatchingPasswordsCount(settings.tab.id);
177+
}
92178
}
93179

94180
/**
@@ -226,6 +312,83 @@ function getLocalSettings() {
226312
return settings;
227313
}
228314

315+
/**
316+
* Get full settings from the extension and host application
317+
*
318+
* @since 3.0.0
319+
*
320+
* @return object Full settings object
321+
*/
322+
async function getFullSettings() {
323+
var settings = getLocalSettings();
324+
var configureSettings = Object.assign(deepCopy(settings), {
325+
defaultStore: {}
326+
});
327+
var response = await hostAction(configureSettings, "configure");
328+
if (response.status != "ok") {
329+
throw new Error(JSON.stringify(response)); // TODO handle host error
330+
}
331+
settings.version = response.version;
332+
if (Object.keys(settings.stores).length > 0) {
333+
// there are user-configured stores present
334+
for (var storeId in settings.stores) {
335+
if (response.data.storeSettings.hasOwnProperty(storeId)) {
336+
var fileSettings = JSON.parse(response.data.storeSettings[storeId]);
337+
if (typeof settings.stores[storeId].settings !== "object") {
338+
settings.stores[storeId].settings = {};
339+
}
340+
var storeSettings = settings.stores[storeId].settings;
341+
for (var settingKey in fileSettings) {
342+
if (!storeSettings.hasOwnProperty(settingKey)) {
343+
storeSettings[settingKey] = fileSettings[settingKey];
344+
}
345+
}
346+
}
347+
}
348+
} else {
349+
// no user-configured stores, so use the default store
350+
settings.stores.default = {
351+
id: "default",
352+
name: "pass",
353+
path: response.data.defaultStore.path
354+
};
355+
var fileSettings = JSON.parse(response.data.defaultStore.settings);
356+
if (typeof settings.stores.default.settings !== "object") {
357+
settings.stores.default.settings = {};
358+
}
359+
var storeSettings = settings.stores.default.settings;
360+
for (var settingKey in fileSettings) {
361+
if (!storeSettings.hasOwnProperty(settingKey)) {
362+
storeSettings[settingKey] = fileSettings[settingKey];
363+
}
364+
}
365+
}
366+
367+
// Fill recent data
368+
for (var storeId in settings.stores) {
369+
var when = localStorage.getItem("recent:" + storeId);
370+
if (when) {
371+
settings.stores[storeId].when = JSON.parse(when);
372+
} else {
373+
settings.stores[storeId].when = 0;
374+
}
375+
}
376+
settings.recent = localStorage.getItem("recent");
377+
if (settings.recent) {
378+
settings.recent = JSON.parse(settings.recent);
379+
} else {
380+
settings.recent = {};
381+
}
382+
383+
// Fill current tab info
384+
try {
385+
settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
386+
settings.host = new URL(settings.tab.url).hostname;
387+
} catch (e) {}
388+
389+
return settings;
390+
}
391+
229392
/**
230393
* Get most relevant setting value
231394
*
@@ -602,73 +765,8 @@ async function receiveMessage(message, sender, sendResponse) {
602765
return;
603766
}
604767

605-
var settings = getLocalSettings();
606768
try {
607-
var configureSettings = Object.assign(deepCopy(settings), {
608-
defaultStore: {}
609-
});
610-
var response = await hostAction(configureSettings, "configure");
611-
if (response.status != "ok") {
612-
throw new Error(JSON.stringify(response)); // TODO handle host error
613-
}
614-
settings.version = response.version;
615-
if (Object.keys(settings.stores).length > 0) {
616-
// there are user-configured stores present
617-
for (var storeId in settings.stores) {
618-
if (response.data.storeSettings.hasOwnProperty(storeId)) {
619-
var fileSettings = JSON.parse(response.data.storeSettings[storeId]);
620-
if (typeof settings.stores[storeId].settings !== "object") {
621-
settings.stores[storeId].settings = {};
622-
}
623-
var storeSettings = settings.stores[storeId].settings;
624-
for (var settingKey in fileSettings) {
625-
if (!storeSettings.hasOwnProperty(settingKey)) {
626-
storeSettings[settingKey] = fileSettings[settingKey];
627-
}
628-
}
629-
}
630-
}
631-
} else {
632-
// no user-configured stores, so use the default store
633-
settings.stores.default = {
634-
id: "default",
635-
name: "pass",
636-
path: response.data.defaultStore.path
637-
};
638-
var fileSettings = JSON.parse(response.data.defaultStore.settings);
639-
if (typeof settings.stores.default.settings !== "object") {
640-
settings.stores.default.settings = {};
641-
}
642-
var storeSettings = settings.stores.default.settings;
643-
for (var settingKey in fileSettings) {
644-
if (!storeSettings.hasOwnProperty(settingKey)) {
645-
storeSettings[settingKey] = fileSettings[settingKey];
646-
}
647-
}
648-
}
649-
650-
// Fill recent data
651-
for (var storeId in settings.stores) {
652-
var when = localStorage.getItem("recent:" + storeId);
653-
if (when) {
654-
settings.stores[storeId].when = JSON.parse(when);
655-
} else {
656-
settings.stores[storeId].when = 0;
657-
}
658-
}
659-
settings.recent = localStorage.getItem("recent");
660-
if (settings.recent) {
661-
settings.recent = JSON.parse(settings.recent);
662-
} else {
663-
settings.recent = {};
664-
}
665-
666-
// Fill current tab info
667-
try {
668-
settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
669-
settings.host = new URL(settings.tab.url).hostname;
670-
} catch (e) {}
671-
769+
const settings = await getFullSettings();
672770
handleMessage(settings, message, sendResponse);
673771
} catch (e) {
674772
// handle error

src/manifest-chromium.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
"open_in_tab": false
2424
},
2525
"permissions": [
26-
"clipboardWrite",
2726
"activeTab",
27+
"tabs",
28+
"clipboardWrite",
2829
"nativeMessaging",
2930
"notifications",
3031
"webRequest",

src/manifest-firefox.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
"open_in_tab": false
2222
},
2323
"permissions": [
24-
"clipboardWrite",
2524
"activeTab",
25+
"tabs",
26+
"clipboardWrite",
2627
"nativeMessaging",
2728
"notifications",
2829
"webRequest",

0 commit comments

Comments
 (0)