Version 0.2.2. See the CHANGELOG

This commit is contained in:
varjolintu 2017-07-04 21:45:48 +03:00
parent 686e98495f
commit 29c1b54cc7
16 changed files with 503 additions and 446 deletions

View file

@ -1,3 +1,10 @@
0.2.2 (2017-07-04)
=========================
- Some code cleaning and rewriting
- Fixed displaying 'Database not opened' error message
- Changed icon color to gray when database is not opened or connected
- Added support for UDP port selector for proxy applications
0.2.1 (2017-06-27)
=========================
- get-databasehash request/response is now encrypted

View file

@ -40,6 +40,7 @@ Request:
{
"action": "change-public-keys",
"publicKey": "<current public key>",
"proxyPort": "<UDP port for proxy applications>",
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
}
```

View file

@ -6,12 +6,12 @@ window.browser = (function () {
var browserAction = {};
var BLINK_TIMEOUT_DEFAULT = 7500;
var BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1;
var BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
const BLINK_TIMEOUT_DEFAULT = 7500;
const BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1;
const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
browserAction.show = function(callback, tab) {
var data = {};
let data = {};
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length == 0) {
browserAction.showDefault(callback, tab);
return;
@ -38,10 +38,10 @@ browserAction.update = function(interval) {
return;
}
var data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
if (typeof data.visibleForMilliSeconds != "undefined") {
if(data.visibleForMilliSeconds <= 0) {
if (data.visibleForMilliSeconds <= 0) {
browserAction.stackPop(page.currentTabId);
browserAction.show(null, {"id": page.currentTabId});
page.clearCredentials(page.currentTabId);
@ -52,7 +52,7 @@ browserAction.update = function(interval) {
if (data.intervalIcon) {
data.intervalIcon.counter += 1;
if(data.intervalIcon.counter < data.intervalIcon.max) {
if (data.intervalIcon.counter < data.intervalIcon.max) {
return;
}
@ -71,12 +71,12 @@ browserAction.update = function(interval) {
}
browserAction.showDefault = function(callback, tab) {
var stackData = {
let stackData = {
level: 1,
iconType: "normal",
popup: "popup.html"
}
keepass.isConfigured(function(response) {
keepass.isConfigured((response) => {
if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable || page.tabs[tab.id].errorMessage) {
stackData.iconType = "cross";
}
@ -87,19 +87,18 @@ browserAction.showDefault = function(callback, tab) {
}
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(null, tab);
});
}
browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
var id = tab.id || page.currentTabId;
const id = tab.id || page.currentTabId;
if (!level) {
level = 1;
}
var stackData = {
let stackData = {
"level": level,
"icon": icon
}
@ -142,18 +141,18 @@ browserAction.removeLevelFromStack = function(callback, tab, level, type, dontSh
type = "<=";
}
var newStack = [];
for (var i = 0; i < page.tabs[tab.id].stack.length; i++) {
let newStack = [];
for (const i of page.tabs[tab.id].stack) {
if (
(type == "<" && page.tabs[tab.id].stack[i].level >= level) ||
(type == "<=" && page.tabs[tab.id].stack[i].level > level) ||
(type == "=" && page.tabs[tab.id].stack[i].level != level) ||
(type == "==" && page.tabs[tab.id].stack[i].level != level) ||
(type == "!=" && page.tabs[tab.id].stack[i].level == level) ||
(type == ">" && page.tabs[tab.id].stack[i].level <= level) ||
(type == ">=" && page.tabs[tab.id].stack[i].level < level)
(type == "<" && i.level >= level) ||
(type == "<=" && i.level > level) ||
(type == "=" && i.level != level) ||
(type == "==" && i.level != level) ||
(type == "!=" && i.level == level) ||
(type == ">" && i.level <= level) ||
(type == ">=" && i.level < level)
) {
newStack.push(page.tabs[tab.id].stack[i]);
newStack.push(i);
}
}
@ -165,21 +164,18 @@ browserAction.removeLevelFromStack = function(callback, tab, level, type, dontSh
}
browserAction.stackPop = function(tabId) {
var id = tabId || page.currentTabId;
const id = tabId || page.currentTabId;
page.tabs[id].stack.pop();
};
browserAction.stackPush = function(data, tabId) {
var id = tabId || page.currentTabId;
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack(null, {"id": id}, data.level, "<=", true);
page.tabs[id].stack.push(data);
};
browserAction.stackUnshift = function(data, tabId) {
var id = tabId || page.currentTabId;
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack(null, {"id": id}, data.level, "<=", true);
page.tabs[id].stack.unshift(data);
};
@ -194,11 +190,11 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
page.clearCredentials(tab.id);
return;
}
var data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
const data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
if (removeImmediately || !isNaN(data.visibleForPageUpdates)) {
var currentMS = Date.now();
if( removeImmediately || (data.visibleForPageUpdates <= 0 && data.redirectOffset > 0)) {
const currentMS = Date.now();
if (removeImmediately || (data.visibleForPageUpdates <= 0 && data.redirectOffset > 0)) {
browserAction.stackPop(tab.id);
browserAction.show(null, {"id": tab.id});
page.clearCredentials(tab.id);
@ -211,18 +207,18 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
};
browserAction.setRememberPopup = function(tabId, username, password, url, usernameExists, credentialsList) {
var settings = typeof(localStorage.settings)=='undefined' ? {} : JSON.parse(localStorage.settings);
const settings = typeof(localStorage.settings)=='undefined' ? {} : JSON.parse(localStorage.settings);
const id = tabId || page.currentTabId;
var timeoutMinMillis = parseInt(getValueOrDefault(settings, "blinkMinTimeout", BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, 0));
var id = tabId || page.currentTabId;
var timeoutMinMillis = parseInt(getValueOrDefault(settings, "blinkMinTimeout", BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, 0)) ;
if (timeoutMinMillis > 0) {
timeoutMinMillis += Date.now();
}
var blinkTimeout = getValueOrDefault(settings, "blinkTimeout", BLINK_TIMEOUT_DEFAULT, 0);
var pageUpdateAllowance = getValueOrDefault(settings, "allowedRedirect", BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
var stackData = {
const blinkTimeout = getValueOrDefault(settings, "blinkTimeout", BLINK_TIMEOUT_DEFAULT, 0);
const pageUpdateAllowance = getValueOrDefault(settings, "allowedRedirect", BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
const stackData = {
visibleForMilliSeconds: blinkTimeout,
visibleForPageUpdates: pageUpdateAllowance,
redirectOffset: timeoutMinMillis,
@ -252,7 +248,7 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna
function getValueOrDefault(settings, key, defaultVal, min) {
try {
var val = settings[key];
let val = settings[key];
if (isNaN(val) || val < min) {
val = defaultVal;
}
@ -265,7 +261,7 @@ browserAction.generateIconName = function(iconType, icon) {
return icon;
}
var name = "icon_";
let name = "icon_";
name += (keepass.keePassXCUpdateAvailable()) ? "new_" : "";
name += (!iconType || iconType == "normal") ? "normal" : iconType;
name += "_19x19.png";

View file

@ -6,7 +6,6 @@ window.browser = (function () {
var event = {};
event.onMessage = function(request, sender, callback) {
if (request.action in event.messageHandlers) {
//console.log("onMessage(" + request.action + ") for #" + sender.tab.id);
@ -49,7 +48,7 @@ event.invoke = function(handler, callback, senderTabId, args, secondTime) {
// remove information from no longer existing tabs
page.removePageInformationFromNotExistingTabs();
browser.tabs.get(senderTabId, function(tab) {
browser.tabs.get(senderTabId, (tab) => {
//browser.tabs.query({"active": true, "windowId": browser.windows.WINDOW_ID_CURRENT}, function(tabs) {
//if (tabs.length === 0)
// return; // For example: only the background devtools or a popup are opened
@ -119,8 +118,8 @@ event.onSaveSettings = function(callback, tab, settings) {
}
event.onGetStatus = function(callback, tab) {
keepass.testAssociation(function(response) {
keepass.isConfigured(function(configured) {
keepass.testAssociation((response) => {
keepass.isConfigured((configured) => {
var keyId = null;
if (configured) {
keyId = keepass.keyRing[keepass.databaseHash].id;
@ -145,11 +144,11 @@ event.onGetStatus = function(callback, tab) {
event.onReconnect = function(callback, tab) {
keepass.connectToNative();
keepass.generateNewKeyPair();
keepass.changePublicKeys(null, function(pkRes) {
keepass.getDatabaseHash(function(gdRes) {
keepass.changePublicKeys(null, (pkRes) => {
keepass.getDatabaseHash((gdRes) => {
if (gdRes) {
keepass.testAssociation(function(response) {
keepass.isConfigured(function(configured) {
keepass.testAssociation((response) => {
keepass.isConfigured((configured) => {
var keyId = null;
if (configured) {
keyId = keepass.keyRing[keepass.databaseHash].id;
@ -179,8 +178,7 @@ event.onPopStack = function(callback, tab) {
}
event.onGetTabInformation = function(callback, tab) {
var id = tab.id || page.currentTabId;
const id = tab.id || page.currentTabId;
callback(page.tabs[id]);
}
@ -193,7 +191,7 @@ event.onGetConnectedDatabase = function(callback, tab) {
event.onGetKeePassXCVersions = function(callback, tab) {
if (keepass.currentKeePassXC.version == 0) {
keepass.getDatabaseHash(function(response) {
keepass.getDatabaseHash((response) => {
callback({"current": keepass.currentKeePassXC.version, "latest": keepass.latestKeePassXC.version});
}, tab);
}
@ -210,8 +208,7 @@ event.onUpdateAvailableKeePassXC = function(callback, tab) {
}
event.onRemoveCredentialsFromTabInformation = function(callback, tab) {
var id = tab.id || page.currentTabId;
const id = tab.id || page.currentTabId;
page.clearCredentials(id);
}
@ -220,39 +217,34 @@ event.onSetRememberPopup = function(callback, tab, username, password, url, user
}
event.onLoginPopup = function(callback, tab, logins) {
var stackData = {
let stackData = {
level: 1,
iconType: "questionmark",
popup: "popup_login.html"
}
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = logins;
browserAction.show(null, tab);
}
event.onHTTPAuthPopup = function(callback, tab, data) {
var stackData = {
let stackData = {
level: 1,
iconType: "questionmark",
popup: "popup_httpauth.html"
}
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = data;
browserAction.show(null, tab);
}
event.onMultipleFieldsPopup = function(callback, tab) {
var stackData = {
let stackData = {
level: 1,
iconType: "normal",
popup: "popup_multiple-fields.html"
}
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(null, tab);
}

View file

@ -7,8 +7,8 @@ page.initOpenedTabs();
// initial connection with KeePassXC
keepass.connectToNative();
keepass.generateNewKeyPair();
keepass.changePublicKeys(null, function(pkRes) {
keepass.getDatabaseHash(function(gdRes) {}, null);
keepass.changePublicKeys(null, (pkRes) => {
keepass.getDatabaseHash((gdRes) => {}, null);
});
window.browser = (function () {
@ -18,13 +18,13 @@ window.browser = (function () {
})();
// set initial tab-ID
browser.tabs.query({"active": true, "windowId": browser.windows.WINDOW_ID_CURRENT}, function(tabs) {
browser.tabs.query({"active": true, "windowId": browser.windows.WINDOW_ID_CURRENT}, (tabs) => {
if (tabs.length === 0)
return; // For example: only the background devtools or a popup are opened
page.currentTabId = tabs[0].id;
});
// Milliseconds for intervall (e.g. to update browserAction)
var _interval = 250;
let _interval = 250;
/**
@ -32,7 +32,7 @@ var _interval = 250;
* functions if tab is created in foreground
* @param {object} tab
*/
browser.tabs.onCreated.addListener(function(tab) {
browser.tabs.onCreated.addListener((tab) => {
if (tab.id > 0) {
//console.log("browser.tabs.onCreated(" + tab.id+ ")");
if (tab.selected) {
@ -47,7 +47,7 @@ browser.tabs.onCreated.addListener(function(tab) {
* @param {integer} tabId
* @param {object} removeInfo
*/
browser.tabs.onRemoved.addListener(function(tabId, removeInfo) {
browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
delete page.tabs[tabId];
if (page.currentTabId == tabId) {
page.currentTabId = -1;
@ -59,12 +59,12 @@ browser.tabs.onRemoved.addListener(function(tabId, removeInfo) {
* Invoke functions to retrieve credentials for focused tab
* @param {object} activeInfo
*/
browser.tabs.onActivated.addListener(function(activeInfo) {
browser.tabs.onActivated.addListener((activeInfo) => {
// remove possible credentials from old tab information
page.clearCredentials(page.currentTabId, true);
browserAction.removeRememberPopup(null, {"id": page.currentTabId}, true);
browser.tabs.get(activeInfo.tabId, function(info) {
browser.tabs.get(activeInfo.tabId, (info) => {
//console.log(info.id + ": " + info.url);
if (info && info.id) {
page.currentTabId = info.id;
@ -81,7 +81,7 @@ browser.tabs.onActivated.addListener(function(activeInfo) {
* @param {integer} tabId
* @param {object} changeInfo
*/
browser.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status == "complete") {
event.invoke(browserAction.removeRememberPopup, null, tabId, []);
}
@ -105,7 +105,7 @@ browser.runtime.onMessage.addListener(event.onMessage);
* Add context menu entry for filling in username + password
*/
browser.contextMenus.create({
"title": "Fill User + Pass",
"title": "Fill &User + Pass",
"contexts": [ "editable" ],
"onclick": function(info, tab) {
browser.tabs.sendMessage(tab.id, {
@ -118,7 +118,7 @@ browser.contextMenus.create({
* Add context menu entry for filling in only password which matches for given username
*/
browser.contextMenus.create({
"title": "Fill Pass Only",
"title": "Fill &Pass Only",
"contexts": [ "editable" ],
"onclick": function(info, tab) {
browser.tabs.sendMessage(tab.id, {
@ -131,7 +131,7 @@ browser.contextMenus.create({
* Add context menu entry for creating icon for generate-password dialog
*/
browser.contextMenus.create({
"title": "Show Password Generator Icons",
"title": "Show Password &Generator Icons",
"contexts": [ "editable" ],
"onclick": function(info, tab) {
browser.tabs.sendMessage(tab.id, {
@ -144,7 +144,7 @@ browser.contextMenus.create({
* Add context menu entry for creating icon for generate-password dialog
*/
browser.contextMenus.create({
"title": "Save credentials",
"title": "&Save credentials",
"contexts": [ "editable" ],
"onclick": function(info, tab) {
browser.tabs.sendMessage(tab.id, {
@ -156,9 +156,9 @@ browser.contextMenus.create({
/**
* Listen for keyboard shortcuts specified by user
*/
browser.commands.onCommand.addListener(function(command) {
browser.commands.onCommand.addListener((command) => {
if (command === "fill-username-password") {
browser.tabs.query({ active: true, currentWindow: true }, function(tabs) {
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, { action: "fill_user_pass" });
}
@ -166,7 +166,7 @@ browser.commands.onCommand.addListener(function(command) {
}
if (command === "fill-password") {
browser.tabs.query({ active: true, currentWindow: true }, function(tabs) {
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, { action: "fill_pass_only" });
}

View file

@ -13,7 +13,6 @@ keepass.requiredKeePassXC = 220;
keepass.nativeHostName = "com.varjolintu.keepassxc_browser";
keepass.nativePort = null;
keepass.keySize = 24;
keepass.proxyPort = 19700;
keepass.latestVersionUrl = "https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest";
keepass.cacheTimeout = 30 * 1000; // milliseconds
keepass.databaseHash = "no-hash"; //no-hash = KeePassXC is too old and does not return a hash value
@ -37,7 +36,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
// unset error message
page.tabs[tab.id].errorMessage = null;
keepass.testAssociation(function(response) {
keepass.testAssociation((response) => {
if (!response)
{
browserAction.showDefault(null, tab);
@ -47,11 +46,11 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
return;
}
var dbkeys = keepass.getCryptoKey();
var id = dbkeys[0];
const dbkeys = keepass.getCryptoKey();
const id = dbkeys[0];
// build request
var messageData = {
let messageData = {
action: "set-login",
id: id,
login: username,
@ -60,32 +59,32 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
submitUrl: url
};
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
if (entryId) {
messageData.uuid = entryId;
}
var request = {
const request = {
action: "set-login",
message: keepass.encrypt(messageData, nonce),
nonce: keepass.b64e(nonce)
};
console.log(request);
keepass.callbackOnId(keepass.nativePort.onMessage, "set-login", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "set-login", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res)
{
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
var code = "error";
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
let code = "error";
if (keepass.verifyResponse(parsed, response.nonce)) {
code = "success";
@ -107,7 +106,7 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
keepass.retrieveCredentials = function (callback, tab, url, submiturl, forceCallback, triggerUnlock) {
page.debug("keepass.retrieveCredentials(callback, {1}, {2}, {3}, {4})", tab.id, url, submiturl, forceCallback);
keepass.testAssociation(function(response) {
keepass.testAssociation((response) => {
if (!response)
{
browserAction.showDefault(null, tab);
@ -124,13 +123,13 @@ keepass.retrieveCredentials = function (callback, tab, url, submiturl, forceCall
return;
}
var entries = [];
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
var dbkeys = keepass.getCryptoKey();
var id = dbkeys[0];
let entries = [];
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
const dbkeys = keepass.getCryptoKey();
const id = dbkeys[0];
var messageData = {
let messageData = {
action: "get-logins",
id: id,
url: url
@ -140,23 +139,23 @@ keepass.retrieveCredentials = function (callback, tab, url, submiturl, forceCall
messageData.submitUrl = submiturl;
}
var request = {
const request = {
action: "get-logins",
message: keepass.encrypt(messageData, nonce),
nonce: keepass.b64e(nonce)
};
keepass.callbackOnId(keepass.nativePort.onMessage, "get-logins", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "get-logins", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res)
{
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
keepass.setcurrentKeePassXCVersion(parsed.version);
@ -188,8 +187,8 @@ keepass.retrieveCredentials = function (callback, tab, url, submiturl, forceCall
// Redirects the callback to a listener (handleReply())
keepass.callbackOnId = function (ev, id, callback) {
var listener = ( function(port, id) {
var handler = function(msg) {
let listener = ( function(port, id) {
let handler = function(msg) {
if (msg && msg.action == id) {
ev.removeListener(handler);
callback(msg);
@ -205,8 +204,8 @@ keepass.generatePassword = function (callback, tab, forceCallback) {
return;
}
keepass.testAssociation(function(response) {
if (!response)
keepass.testAssociation((taresponse) => {
if (!taresponse)
{
browserAction.showDefault(null, tab);
if (forceCallback) {
@ -220,30 +219,30 @@ keepass.generatePassword = function (callback, tab, forceCallback) {
return;
}
var passwords = [];
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
let passwords = [];
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
var request = {
const request = {
action: "generate-password",
nonce: keepass.b64e(nonce)
};
keepass.callbackOnId(keepass.nativePort.onMessage, "generate-password", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "generate-password", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res)
{
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
keepass.setcurrentKeePassXCVersion(parsed.version);
if (keepass.verifyResponse(parsed, response.nonce)) {
var rIv = response.nonce;
const rIv = response.nonce;
if (parsed.entries) {
passwords = parsed.entries;
keepass.updateLastUsed(keepass.databaseHash);
@ -267,10 +266,10 @@ keepass.generatePassword = function (callback, tab, forceCallback) {
}
keepass.copyPassword = function(callback, tab, password) {
browser.runtime.getBackgroundPage(function(bg) {
var c2c = bg.document.getElementById("copy2clipboard");
browser.runtime.getBackgroundPage((bg) => {
let c2c = bg.document.getElementById("copy2clipboard");
if (!c2c) {
var input = document.createElement('input');
let input = document.createElement('input');
input.type = "text";
input.id = "copy2clipboard";
bg.document.getElementsByTagName('body')[0].appendChild(input);
@ -279,9 +278,14 @@ keepass.copyPassword = function(callback, tab, password) {
c2c.value = password;
c2c.select();
document.execCommand("copy");
c2c.value = "";
callback(true);
try {
document.execCommand("copy");
c2c.value = "";
callback(true);
}
catch (err) {
console.log("Couldn't copy password to clipboard: " + err);
}
});
}
@ -290,38 +294,38 @@ keepass.associate = function(callback, tab) {
return;
}
keepass.getDatabaseHash(function(res) {
keepass.getDatabaseHash((res) => {
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
return;
}
page.tabs[tab.id].errorMessage = null;
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
var messageData = {
const messageData = {
action: "associate",
key: key
};
var request = {
const request = {
action: "associate",
message: keepass.encrypt(messageData, nonce),
nonce: keepass.b64e(nonce)
};
keepass.callbackOnId(keepass.nativePort.onMessage, "associate", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "associate", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res)
{
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
if (parsed.version) {
keepass.currentKeePassXC = {
@ -329,7 +333,7 @@ keepass.associate = function(callback, tab) {
"versionParsed": parseInt(parsed.version.replace(/\./g,""))};
}
var id = parsed.id;
const id = parsed.id;
if (!keepass.verifyResponse(parsed, response.nonce)) {
page.tabs[tab.id].errorMessage = "KeePassXC association failed, try again.";
}
@ -351,10 +355,10 @@ keepass.associate = function(callback, tab) {
}
keepass.testAssociation = function (callback, tab, triggerUnlock) {
keepass.getDatabaseHash(function(dbHash) {
keepass.getDatabaseHash((dbHash) => {
if (!dbHash) {
callback(false);
return;
return false;
}
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
@ -369,7 +373,7 @@ keepass.testAssociation = function (callback, tab, triggerUnlock) {
if (!keepass.serverPublicKey) {
if (tab && page.tabs[tab.id]) {
var errorMessage = "No KeePassXC public key available.";
const errorMessage = "No KeePassXC public key available.";
page.tabs[tab.id].errorMessage = errorMessage;
console.log(errorMessage);
}
@ -377,12 +381,12 @@ keepass.testAssociation = function (callback, tab, triggerUnlock) {
return false;
}
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
var dbkeys = keepass.getCryptoKey();
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
const dbkeys = keepass.getCryptoKey();
if (dbkeys == null) {
if (tab && page.tabs[tab.id]) {
var errorMessage = "No saved databases found.";
const errorMessage = "No saved databases found.";
page.tabs[tab.id].errorMessage = errorMessage;
console.log(errorMessage);
}
@ -390,31 +394,31 @@ keepass.testAssociation = function (callback, tab, triggerUnlock) {
return false;
}
var id = dbkeys[0];
var idkey = dbkeys[1];
const id = dbkeys[0];
const idkey = dbkeys[1];
var messageData = {
const messageData = {
action: "test-associate",
id: id,
key: idkey
};
var request = {
const request = {
action: "test-associate",
message: keepass.encrypt(messageData, nonce),
nonce: keepass.b64e(nonce)
};
keepass.callbackOnId(keepass.nativePort.onMessage, "test-associate", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "test-associate", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res) {
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
if (parsed.version) {
keepass.currentKeePassXC = {
@ -422,10 +426,10 @@ keepass.testAssociation = function (callback, tab, triggerUnlock) {
"versionParsed": parseInt(parsed.version.replace(/\./g,""))};
}
var id = parsed.id;
const id = parsed.id;
keepass.isEncryptionKeyUnrecognized = false;
if (!keepass.verifyResponse(parsed, response.nonce)) {
var hash = response.hash || 0;
const hash = response.hash || 0;
keepass.deleteKey(hash);
keepass.isEncryptionKeyUnrecognized = true;
console.log("Encryption key is not recognized!");
@ -464,36 +468,35 @@ keepass.getDatabaseHash = function (callback, tab, triggerUnlock) {
keepass.changePublicKeys(tab, function(res) {});
}
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
const key = keepass.b64e(keepass.keyPair.publicKey);
const nonce = nacl.randomBytes(keepass.keySize);
var messageData = {
const messageData = {
action: "get-databasehash"
};
var request = {
const request = {
action: "get-databasehash",
message: keepass.encrypt(messageData, nonce),
nonce: keepass.b64e(nonce)
};
//var message = { "action": "get-databasehash" };
keepass.callbackOnId(keepass.nativePort.onMessage, "get-databasehash", function(response) {
keepass.callbackOnId(keepass.nativePort.onMessage, "get-databasehash", (response) => {
if (response.message && response.nonce) {
var res = keepass.decrypt(response.message, response.nonce);
const res = keepass.decrypt(response.message, response.nonce);
if (!res)
{
console.log("Failed to decrypt message");
}
else
{
var message = nacl.util.encodeUTF8(res);
var parsed = JSON.parse(message);
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
if (parsed.hash)
{
console.log("hash reply received: "+ parsed.hash);
var oldDatabaseHash = keepass.databaseHash;
const oldDatabaseHash = keepass.databaseHash;
keepass.setcurrentKeePassXCVersion(parsed.version);
keepass.databaseHash = parsed.hash || "no-hash";
@ -517,14 +520,11 @@ keepass.getDatabaseHash = function (callback, tab, triggerUnlock) {
}
}
}
else if (response.error && response.errorCode) {
keepass.handleError(tab.id, response.error, response.errorCode);
}
else
{
keepass.databaseHash = "no-hash";
if (tab && page.tabs[tab.id]) {
page.tabs[tab.id].errorMessage = "Database hash not received.";
page.tabs[tab.id].errorMessage = response.error.length > 0 ? response.error : "Database hash not received.";
}
callback(keepass.databaseHash);
}
@ -537,14 +537,14 @@ keepass.changePublicKeys = function(tab, callback) {
return;
}
var key = keepass.b64e(keepass.keyPair.publicKey);
var nonce = nacl.randomBytes(keepass.keySize);
nonce = keepass.b64e(nonce);
const key = keepass.b64e(keepass.keyPair.publicKey);
let nonce = nacl.randomBytes(keepass.keySize);
nonce = keepass.b64e(nonce)
var message = {
const message = {
"action": "change-public-keys",
"publicKey": key,
"proxyPort": keepass.proxyPort,
"proxyPort": (page.settings.port ? page.settings.port : 19700),
"nonce": nonce
}
@ -579,7 +579,7 @@ keepass.generateNewKeyPair = function() {
keepass.isConfigured = function(callback) {
if (typeof(keepass.databaseHash) == "undefined") {
keepass.getDatabaseHash(function(dbHash) {
keepass.getDatabaseHash((dbHash) => {
callback(keepass.databaseHash in keepass.keyRing);
}, null);
}
@ -595,7 +595,7 @@ keepass.isAssociated = function() {
keepass.convertKeyToKeyRing = function() {
if (keepass.keyId in localStorage && keepass.keyBody in localStorage && !("keyRing" in localStorage)) {
keepass.getDatabaseHash(function(hash) {
keepass.getDatabaseHash((hash) => {
keepass.saveKey(hash, localStorage[keepass.keyId], localStorage[keepass.keyBody]);
if ("keyRing" in localStorage) {
@ -653,8 +653,8 @@ keepass.setcurrentKeePassXCVersion = function(version) {
keepass.keePassXCUpdateAvailable = function() {
if (page.settings.checkUpdateKeePassXC && page.settings.checkUpdateKeePassXC > 0) {
var lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date("11/21/1986");
var daysSinceLastCheck = Math.floor(((new Date()).getTime()-lastChecked.getTime())/86400000);
const lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date("11/21/1986");
const daysSinceLastCheck = Math.floor(((new Date()).getTime()-lastChecked.getTime())/86400000);
if (daysSinceLastCheck >= page.settings.checkUpdateKeePassXC) {
keepass.checkForNewKeePassXCVersion();
}
@ -664,13 +664,13 @@ keepass.keePassXCUpdateAvailable = function() {
}
keepass.checkForNewKeePassXCVersion = function() {
var xhr = new XMLHttpRequest();
var version = -1;
let xhr = new XMLHttpRequest();
let version = -1;
xhr.open("GET", keepass.latestVersionUrl, true);
xhr.onload = function(e) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var json = JSON.parse(xhr.responseText);
const json = JSON.parse(xhr.responseText);
if (json.tag_name) {
version = json.tag_name;
keepass.latestKeePassXC.version = version;
@ -729,9 +729,7 @@ keepass.verifyKeyResponse = function(response, key, nonce) {
return false;
}
var reply = false;
var respnonce = keepass.b64d(response.nonce);
let reply = false;
if (keepass.b64d(nonce).length !== nacl.secretbox.nonceLength)
return false;
@ -788,8 +786,8 @@ keepass.getCryptoKey = function() {
return null;
}
var id = keepass.keyRing[keepass.databaseHash].id;
var key = null;
const id = keepass.keyRing[keepass.databaseHash].id;
let key = null;
if (id) {
key = keepass.keyRing[keepass.databaseHash].key;
@ -803,10 +801,10 @@ keepass.setCryptoKey = function(id, key) {
}
keepass.encrypt = function(input, nonce) {
var messageData = nacl.util.decodeUTF8(JSON.stringify(input));
const messageData = nacl.util.decodeUTF8(JSON.stringify(input));
if (keepass.serverPublicKey) {
var message = nacl.box(messageData, nonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
const message = nacl.box(messageData, nonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
if (message) {
return keepass.b64e(message);
}
@ -816,7 +814,7 @@ keepass.encrypt = function(input, nonce) {
}
keepass.decrypt = function(input, nonce, toStr) {
var m = keepass.b64d(input);
var n = keepass.b64d(nonce);
const m = keepass.b64d(input);
const n = keepass.b64d(nonce);
return nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey);
}

View file

@ -33,26 +33,28 @@ page.initSettings = function() {
if (!("autoRetrieveCredentials" in page.settings)) {
page.settings.autoRetrieveCredentials = true;
}
if (!("port" in page.settings)) {
page.settings.port = "19700";
}
localStorage.settings = JSON.stringify(page.settings);
}
page.initOpenedTabs = function() {
browser.tabs.query({}, function(tabs) {
for (var i = 0; i < tabs.length; i++) {
page.createTabEntry(tabs[i].id);
browser.tabs.query({}, (tabs) => {
for (const i of tabs) {
page.createTabEntry(i.id);
}
});
}
page.isValidProtocol = function(url) {
var protocol = url.substring(0, url.indexOf(":"));
let protocol = url.substring(0, url.indexOf(":"));
protocol = protocol.toLowerCase();
return !(url.indexOf(".") == -1 || (protocol != "http" && protocol != "https" && protocol != "ftp" && protocol != "sftp"));
}
page.switchTab = function(callback, tab) {
browserAction.showDefault(null, tab);
browser.tabs.sendMessage(tab.id, {action: "activated_tab"});
}
@ -83,19 +85,19 @@ page.createTabEntry = function(tabId) {
}
page.removePageInformationFromNotExistingTabs = function() {
var rand = Math.floor(Math.random()*1001);
let rand = Math.floor(Math.random()*1001);
if (rand == 28) {
browser.tabs.query({}, function(tabs) {
var $tabIds = {};
var $infoIds = Object.keys(page.tabs);
browser.tabs.query({}, (tabs) => {
let $tabIds = {};
const $infoIds = Object.keys(page.tabs);
for (var i = 0; i < tabs.length; i++) {
$tabIds[tabs[i].id] = true;
for (const t of tabs) {
$tabIds[t.id] = true;
}
for (var i = 0; i < $infoIds.length; i++) {
if (!($infoIds[i] in $tabIds)) {
delete page.tabs[$infoIds[i]];
for (const i of $infoIds) {
if (!(i in $tabIds)) {
delete page.tabs[i];
}
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -69,26 +69,50 @@ input.genpw-text {
position: absolute;
cursor: pointer;
}
.cip-genpw-icon.cip-icon-key-small {
.cip-genpw-icon.small {
width: 16px;
height: 16px;
background-image: url(chrome-extension://__MSG_@@extension_id__/icons/key_16x16.png);
}
.cip-genpw-icon.cip-icon-key-big {
.cip-genpw-icon.big {
width: 24px;
height: 24px;
background-image: url(chrome-extension://__MSG_@@extension_id__/icons/key_24x24.png);
}
.cip-genpw-icon.cip-icon-key-small-moz {
.cip-genpw-icon.small-moz {
width: 16px;
height: 16px;
background-image: url(moz-extension://__MSG_@@extension_id__/icons/key_16x16.png);
}
.cip-genpw-icon.cip-icon-key-big-moz {
.cip-genpw-icon.big-moz {
width: 24px;
height: 24px;
background-image: url(moz-extension://__MSG_@@extension_id__/icons/key_24x24.png);
}
.cip-warning-icon {
position: absolute;
cursor: pointer;
}
.cip-warning-icon.small {
width: 16px;
height: 16px;
background-image: url(chrome-extension://__MSG_@@extension_id__/icons/warning_16x16.png);
}
.cip-warning-icon.big {
width: 24px;
height: 24px;
background-image: url(chrome-extension://__MSG_@@extension_id__/icons/warning_24x24.png);
}
.cip-warning-icon.small-moz {
width: 16px;
height: 16px;
background-image: url(moz-extension://__MSG_@@extension_id__/icons/warning_16x16.png);
}
.cip-warning-icon.big-moz {
width: 24px;
height: 24px;
background-image: url(moz-extension://__MSG_@@extension_id__/icons/warning_24x24.png);
}
#cip-genpw-btn-fillin {
margin-right: 5px;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "keepassxc-browser",
"version": "0.2.1",
"version": "0.2.2",
"description": "KeePassXC integration for modern web browsers",
"author": "Sami Vänttinen",
"icons": {
@ -75,7 +75,7 @@
"applications": {
"gecko": {
"id": "keepassxc-browser@sami.vanttinen",
"strict_min_version": "56.0"
"strict_min_version": "56.0a1"
}
}
}

View file

@ -81,6 +81,24 @@
</span>
</div>
</p>
<p>
<div class="form-group">
<label for="port">UDP port for proxy applications:</label>
<div class="control-group">
<div class="input-append">
<input type="number" id="port" placeholder="19700" value="19700" />
<button class="btn btn-sm" id="portButton" type="button">Save</button>
</div>
<span class="help-inline">
Change the port if you have trouble with running KeePassXC on the default port.
<br />
You have to set the same port number in KeePassXC options.
<br />
Default: 19700
</span>
</div>
</div>
</p>
<hr />
<p>
<div class="checkbox">
@ -271,6 +289,9 @@
<p>
<a target="_blank" href="https://chrome.google.com/webstore/detail/keepassxc-browser/iopaggbpplllidnfmcghoonnokmjoicf">Visit extension's page in Chrome Web store</a>
</p>
<p>
<a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/">Visit extension's page in Mozilla's Add-ons page</a>
</p>
<p>
<a target="_blank" href="https://github.com/varjolintu/keepassxc-browser">Visit project's page in GitHub</a>.
</p>

View file

@ -74,13 +74,34 @@ options.initGeneralSettings = function() {
}, options.showKeePassXCVersions);
});
$("#port").val(options.settings["port"]);
$("#blinkTimeout").val(options.settings["blinkTimeout"]);
$("#blinkMinTimeout").val(options.settings["blinkMinTimeout"]);
$("#allowedRedirect").val(options.settings["allowedRedirect"]);
$("#portButton").click(function() {
const port = $.trim($("#port").val());
const portNumber = parseInt(port);
if (isNaN(port) || portNumber < 1025 || portNumber > 99999) {
$("#port").closest(".control-group").addClass("error");
alert("The port number has to be in range 1025 - 99999.\nNothing saved!");
return;
}
options.settings["port"] = portNumber.toString();
$("#port").closest(".control-group").removeClass("error").addClass("success");
setTimeout(function() {$("#port").closest(".control-group").removeClass("success")}, 2500);
localStorage.settings = JSON.stringify(options.settings);
chrome.extension.sendMessage({
action: 'load_settings'
});
});
$("#blinkTimeoutButton").click(function(){
var blinkTimeout = $.trim($("#blinkTimeout").val());
var blinkTimeoutval = parseInt(blinkTimeout);
const blinkTimeout = $.trim($("#blinkTimeout").val());
const blinkTimeoutval = parseInt(blinkTimeout);
options.settings["blinkTimeout"] = blinkTimeoutval.toString();
$("#blinkTimeout").closest(".control-group").removeClass("error").addClass("success");
@ -94,8 +115,8 @@ options.initGeneralSettings = function() {
});
$("#blinkMinTimeoutButton").click(function(){
var blinkMinTimeout = $.trim($("#blinkMinTimeout").val());
var blinkMinTimeoutval = parseInt(blinkMinTimeout);
const blinkMinTimeout = $.trim($("#blinkMinTimeout").val());
const blinkMinTimeoutval = parseInt(blinkMinTimeout);
options.settings["blinkMinTimeout"] = blinkMinTimeoutval.toString();
$("#blinkMinTimeout").closest(".control-group").removeClass("error").addClass("success");
@ -109,8 +130,8 @@ options.initGeneralSettings = function() {
});
$("#allowedRedirectButton").click(function(){
var allowedRedirect = $.trim($("#allowedRedirect").val());
var allowedRedirectval = parseInt(allowedRedirect);
const allowedRedirect = $.trim($("#allowedRedirect").val());
const allowedRedirectval = parseInt(allowedRedirect);
options.settings["allowedRedirect"] = allowedRedirectval.toString();
$("#allowedRedirect").closest(".control-group").removeClass("error").addClass("success");
@ -125,10 +146,10 @@ options.initGeneralSettings = function() {
};
options.showKeePassXCVersions = function(response) {
if(response.current <= 0) {
if (response.current <= 0) {
response.current = "unknown";
}
if(response.latest <= 0) {
if (response.latest <= 0) {
response.latest = "unknown";
}
$("#tab-general-settings .kphVersion:first em.yourVersion:first").text(response.current);
@ -151,7 +172,7 @@ options.initConnectedDatabases = function() {
$("#dialogDeleteConnectedDatabase .modal-footer:first button.yes:first").click(function(e) {
$("#dialogDeleteConnectedDatabase").modal("hide");
var $hash = $("#dialogDeleteConnectedDatabase").data("hash");
const $hash = $("#dialogDeleteConnectedDatabase").data("hash");
$("#tab-connected-databases #tr-cd-" + $hash).remove();
delete options.keyRing[$hash];
@ -161,7 +182,7 @@ options.initConnectedDatabases = function() {
action: 'load_keyring'
});
if($("#tab-connected-databases table tbody:first tr").length > 2) {
if ($("#tab-connected-databases table tbody:first tr").length > 2) {
$("#tab-connected-databases table tbody:first tr.empty:first").hide();
}
else {
@ -171,26 +192,26 @@ options.initConnectedDatabases = function() {
$("#tab-connected-databases tr.clone:first .dropdown-menu:first").width("230px");
var $trClone = $("#tab-connected-databases table tr.clone:first").clone(true);
const $trClone = $("#tab-connected-databases table tr.clone:first").clone(true);
$trClone.removeClass("clone");
for(var hash in options.keyRing) {
var $tr = $trClone.clone(true);
for (let hash in options.keyRing) {
const $tr = $trClone.clone(true);
$tr.data("hash", hash);
$tr.attr("id", "tr-cd-" + hash);
var $icon = options.keyRing[hash].icon || "blue";
const $icon = options.keyRing[hash].icon || "blue";
$("a.dropdown-toggle:first img:first", $tr).attr("src", "/icons/19x19/icon_normal_" + $icon + "_19x19.png");
$tr.children("td:first").text(options.keyRing[hash].id);
$tr.children("td:eq(1)").text(options.keyRing[hash].key);
var lastUsed = (options.keyRing[hash].lastUsed) ? new Date(options.keyRing[hash].lastUsed).toLocaleString() : "unknown";
const lastUsed = (options.keyRing[hash].lastUsed) ? new Date(options.keyRing[hash].lastUsed).toLocaleString() : "unknown";
$tr.children("td:eq(2)").text(lastUsed);
var date = (options.keyRing[hash].created) ? new Date(options.keyRing[hash].created).toLocaleDateString() : "unknown";
const date = (options.keyRing[hash].created) ? new Date(options.keyRing[hash].created).toLocaleDateString() : "unknown";
$tr.children("td:eq(3)").text(date);
$("#tab-connected-databases table tbody:first").append($tr);
}
if($("#tab-connected-databases table tbody:first tr").length > 2) {
if ($("#tab-connected-databases table tbody:first tr").length > 2) {
$("#tab-connected-databases table tbody:first tr.empty:first").hide();
}
else {
@ -217,8 +238,8 @@ options.initSpecifiedCredentialFields = function() {
$("#dialogDeleteSpecifiedCredentialFields .modal-footer:first button.yes:first").click(function(e) {
$("#dialogDeleteSpecifiedCredentialFields").modal("hide");
var $url = $("#dialogDeleteSpecifiedCredentialFields").data("url");
var $trId = $("#dialogDeleteSpecifiedCredentialFields").data("tr-id");
const $url = $("#dialogDeleteSpecifiedCredentialFields").data("url");
const $trId = $("#dialogDeleteSpecifiedCredentialFields").data("tr-id");
$("#tab-specified-fields #" + $trId).remove();
delete options.settings["defined-credential-fields"][$url];
@ -236,11 +257,11 @@ options.initSpecifiedCredentialFields = function() {
}
});
var $trClone = $("#tab-specified-fields table tr.clone:first").clone(true);
const $trClone = $("#tab-specified-fields table tr.clone:first").clone(true);
$trClone.removeClass("clone");
var counter = 1;
for(var url in options.settings["defined-credential-fields"]) {
var $tr = $trClone.clone(true);
let counter = 1;
for(let url in options.settings["defined-credential-fields"]) {
const $tr = $trClone.clone(true);
$tr.data("url", url);
$tr.attr("id", "tr-scf" + counter);
counter += 1;

View file

@ -60,7 +60,7 @@ $(function() {
});
$("#redetect-fields-button").click(function() {
browser.tabs.query({"active": true, "windowId": browser.windows.WINDOW_ID_CURRENT}, function(tabs) {
browser.tabs.query({"active": true, "windowId": browser.windows.WINDOW_ID_CURRENT}, (tabs) => {
if (tabs.length === 0)
return; // For example: only the background devtools or a popup are opened
var tab = tabs[0];