diff --git a/src/background.js b/src/background.js index 5e87a88..421455e 100644 --- a/src/background.js +++ b/src/background.js @@ -2,6 +2,7 @@ "use strict"; require("chrome-extension-async"); +var TldJS = require("tldjs"); var sha1 = require("sha1"); // native application id @@ -17,8 +18,12 @@ var defaultSettings = { var authListeners = {}; +chrome.browserAction.setBadgeBackgroundColor({ + color: "#666" +}); + // watch for tab updates -chrome.tabs.onUpdated.addListener(function(tabId, info) { +chrome.tabs.onUpdated.addListener((tabId, info) => { // ignore non-complete status if (info.status !== "complete") { return; @@ -29,6 +34,9 @@ chrome.tabs.onUpdated.addListener(function(tabId, info) { chrome.webRequest.onAuthRequired.removeListener(authListeners[tabId]); delete authListeners[tabId]; } + + // show number of matching passwords in a badge + updateMatchingPasswordsCount(tabId); }); // handle incoming messages @@ -43,6 +51,79 @@ chrome.runtime.onInstalled.addListener(onExtensionInstalled); //----------------------------------- Function definitions ----------------------------------// +/** + * 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; +} + +/** + * Set badge text with the number of matching password entries + * + * @since 3.0.0 + * + * @param int tabId Tab id + */ +async function updateMatchingPasswordsCount(tabId) { + try { + const settings = await getFullSettings(); + var response = await hostAction(settings, "list"); + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); + } + + // Get tab info + let currentDomain = undefined; + try { + const tab = await chrome.tabs.get(tabId); + currentDomain = new URL(tab.url).hostname; + } catch (e) { + throw new Error(`Unable to determine domain of the tab with id ${tabId}`); + } + + let matchedPasswordsCount = 0; + for (var storeId in response.data.files) { + for (var key in response.data.files[storeId]) { + const login = response.data.files[storeId][key].replace(/\.gpg$/i, ""); + const domain = pathToDomain(storeId + "/" + login); + const inCurrentDomain = + currentDomain === domain || currentDomain.endsWith("." + domain); + const recent = settings.recent[sha1(currentDomain + sha1(storeId + sha1(login)))]; + if (recent || inCurrentDomain) { + matchedPasswordsCount++; + } + } + } + + if (matchedPasswordsCount) { + // Set badge for the current tab + chrome.browserAction.setBadgeText({ + text: "" + matchedPasswordsCount, + tabId: tabId + }); + } + } catch (e) { + console.log(e); + } +} + /** * Copy text to clipboard * @@ -89,6 +170,11 @@ function saveRecent(settings, login, remove = false) { // save to local storage localStorage.setItem("recent", JSON.stringify(settings.recent)); + + // a new entry was added to the popup matching list, need to refresh the count + if (!login.inCurrentDomain && login.recent.count === 1) { + updateMatchingPasswordsCount(settings.tab.id); + } } /** @@ -226,6 +312,83 @@ function getLocalSettings() { return settings; } +/** + * Get full settings from the extension and host application + * + * @since 3.0.0 + * + * @return object Full settings object + */ +async function getFullSettings() { + var settings = getLocalSettings(); + var configureSettings = Object.assign(deepCopy(settings), { + defaultStore: {} + }); + var response = await hostAction(configureSettings, "configure"); + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); // TODO handle host error + } + settings.version = response.version; + if (Object.keys(settings.stores).length > 0) { + // there are user-configured stores present + for (var storeId in settings.stores) { + if (response.data.storeSettings.hasOwnProperty(storeId)) { + var fileSettings = JSON.parse(response.data.storeSettings[storeId]); + if (typeof settings.stores[storeId].settings !== "object") { + settings.stores[storeId].settings = {}; + } + var storeSettings = settings.stores[storeId].settings; + for (var settingKey in fileSettings) { + if (!storeSettings.hasOwnProperty(settingKey)) { + storeSettings[settingKey] = fileSettings[settingKey]; + } + } + } + } + } else { + // no user-configured stores, so use the default store + settings.stores.default = { + id: "default", + name: "pass", + path: response.data.defaultStore.path + }; + var fileSettings = JSON.parse(response.data.defaultStore.settings); + if (typeof settings.stores.default.settings !== "object") { + settings.stores.default.settings = {}; + } + var storeSettings = settings.stores.default.settings; + for (var settingKey in fileSettings) { + if (!storeSettings.hasOwnProperty(settingKey)) { + storeSettings[settingKey] = fileSettings[settingKey]; + } + } + } + + // Fill recent data + for (var storeId in settings.stores) { + var when = localStorage.getItem("recent:" + storeId); + if (when) { + settings.stores[storeId].when = JSON.parse(when); + } else { + settings.stores[storeId].when = 0; + } + } + settings.recent = localStorage.getItem("recent"); + if (settings.recent) { + settings.recent = JSON.parse(settings.recent); + } else { + settings.recent = {}; + } + + // Fill current tab info + try { + settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]; + settings.host = new URL(settings.tab.url).hostname; + } catch (e) {} + + return settings; +} + /** * Get most relevant setting value * @@ -602,73 +765,8 @@ async function receiveMessage(message, sender, sendResponse) { return; } - var settings = getLocalSettings(); try { - var configureSettings = Object.assign(deepCopy(settings), { - defaultStore: {} - }); - var response = await hostAction(configureSettings, "configure"); - if (response.status != "ok") { - throw new Error(JSON.stringify(response)); // TODO handle host error - } - settings.version = response.version; - if (Object.keys(settings.stores).length > 0) { - // there are user-configured stores present - for (var storeId in settings.stores) { - if (response.data.storeSettings.hasOwnProperty(storeId)) { - var fileSettings = JSON.parse(response.data.storeSettings[storeId]); - if (typeof settings.stores[storeId].settings !== "object") { - settings.stores[storeId].settings = {}; - } - var storeSettings = settings.stores[storeId].settings; - for (var settingKey in fileSettings) { - if (!storeSettings.hasOwnProperty(settingKey)) { - storeSettings[settingKey] = fileSettings[settingKey]; - } - } - } - } - } else { - // no user-configured stores, so use the default store - settings.stores.default = { - id: "default", - name: "pass", - path: response.data.defaultStore.path - }; - var fileSettings = JSON.parse(response.data.defaultStore.settings); - if (typeof settings.stores.default.settings !== "object") { - settings.stores.default.settings = {}; - } - var storeSettings = settings.stores.default.settings; - for (var settingKey in fileSettings) { - if (!storeSettings.hasOwnProperty(settingKey)) { - storeSettings[settingKey] = fileSettings[settingKey]; - } - } - } - - // Fill recent data - for (var storeId in settings.stores) { - var when = localStorage.getItem("recent:" + storeId); - if (when) { - settings.stores[storeId].when = JSON.parse(when); - } else { - settings.stores[storeId].when = 0; - } - } - settings.recent = localStorage.getItem("recent"); - if (settings.recent) { - settings.recent = JSON.parse(settings.recent); - } else { - settings.recent = {}; - } - - // Fill current tab info - try { - settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]; - settings.host = new URL(settings.tab.url).hostname; - } catch (e) {} - + const settings = await getFullSettings(); handleMessage(settings, message, sendResponse); } catch (e) { // handle error diff --git a/src/manifest-chromium.json b/src/manifest-chromium.json index 9819c0f..2c9598d 100644 --- a/src/manifest-chromium.json +++ b/src/manifest-chromium.json @@ -23,8 +23,9 @@ "open_in_tab": false }, "permissions": [ - "clipboardWrite", "activeTab", + "tabs", + "clipboardWrite", "nativeMessaging", "notifications", "webRequest", diff --git a/src/manifest-firefox.json b/src/manifest-firefox.json index 871c4dc..1b813bf 100644 --- a/src/manifest-firefox.json +++ b/src/manifest-firefox.json @@ -21,8 +21,9 @@ "open_in_tab": false }, "permissions": [ - "clipboardWrite", "activeTab", + "tabs", + "clipboardWrite", "nativeMessaging", "notifications", "webRequest",