diff --git a/.eslintrc b/.eslintrc index 5ddc93e61..6c5d235b4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -103,6 +103,7 @@ "CHECK_UPDATE_THREE_DAYS": "readonly", "cloneInto": "readonly", "compareVersion": "readonly", + "ConnectionMethod": "readonly", "createResult": "readonly", "createStylesheet": "readonly", "DatabaseState": "readonly", diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index b6f96c259..90a8f57f6 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -830,6 +830,22 @@ "message": "Allow filling HTTP Basic Auth credentials", "description": "Allow filling HTTP Basic Auth credentials checkbox text." }, + "optionsConnectionMethod": { + "message": "Connection method", + "description": "Connection method selection text." + }, + "optionsConnectionMethodHelpText": { + "message": "If native messaging is blocked in your browser, you can switch to a direct WebSocket sonnection. The feature must be enabled from KeePassXC side to work. Browser restart is required.", + "description": "Connection method help text." + }, + "optionsConnectionMethodNativeMessaging": { + "message": "Native messaging", + "description": "Native messaging option for Connection method." + }, + "optionsConnectionMethodWebSocket": { + "message": "WebSocket", + "description": "WebSocket option for Connection method." + }, "optionsDebugLogging": { "message": "Debug logging", "description": "Debug logging checkbox text." diff --git a/keepassxc-browser/background/client.js b/keepassxc-browser/background/client.js index c98b01dec..fec47582e 100644 --- a/keepassxc-browser/background/client.js +++ b/keepassxc-browser/background/client.js @@ -5,11 +5,12 @@ keepassClient.keySize = 24; keepassClient.messageTimeout = 500; // Milliseconds keepassClient.nativeHostName = 'org.keepassxc.keepassxc_browser'; keepassClient.nativePort = null; +keepassClient.webSocket = null; const kpErrors = { UNKNOWN_ERROR: 0, DATABASE_NOT_OPENED: 1, - DATABASE_HASH_NOT_RECEIVED: 2, + DATABASE_HASH_NOT_RECEIVED: 2, CLIENT_PUBLIC_KEY_NOT_RECEIVED: 3, CANNOT_DECRYPT_MESSAGE: 4, TIMEOUT_OR_NOT_CONNECTED: 5, @@ -157,7 +158,10 @@ class Message { //-------------------------------------------------------------------------- keepassClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue) { - if (!keepassClient.nativePort) { + if (page?.settings?.connectionMethod === ConnectionMethod.WEBSOCKET && !keepassClient.webSocket) { + logError('No WebSocket defined.'); + return; + } else if (page?.settings?.connectionMethod === ConnectionMethod.NATIVE_MESSAGING && !keepassClient.nativePort) { logError('No native messaging port defined.'); return; } @@ -167,7 +171,11 @@ keepassClient.sendNativeMessage = async function(request, enableTimeout = false, messageBuffer.addMessage(message); }); - keepassClient.nativePort.postMessage(request); + if (page?.settings?.connectionMethod === ConnectionMethod.WEBSOCKET) { + keepassClient.webSocket.send(JSON.stringify(request)); + } else { + keepassClient.nativePort.postMessage(request); + } const response = await message.promise; @@ -400,7 +408,7 @@ function onDisconnected() { page.clearAllLogins(); keepass.updatePopup('cross'); keepass.updateDatabaseHashToContent(); - logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`); + logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError?.message)}`); } keepassClient.onNativeMessage = function(response) { @@ -413,3 +421,42 @@ keepassClient.onNativeMessage = function(response) { // Generic response handling keepassClient.handleNativeMessage(response); }; + +//-------------------------------------------------------------------------- +// WebSocket related +//-------------------------------------------------------------------------- + +keepassClient.connectToWebSocket = async function() { + return new Promise((resolve, reject) => { + if (keepassClient.webSocket) { + keepassClient.webSocket.close(); + } + + console.log(`${EXTENSION_NAME}: Connecting to WebSocket`); + + try { + keepassClient.webSocket = new WebSocket('ws://localhost:7580'); + keepassClient.webSocket.addEventListener('close', (event) => { + logError('Close WebSocket:', event); + onDisconnected(); + reject(); + }); + keepassClient.webSocket.addEventListener('error', (event) => { + logError('WebSocket error:', event); + onDisconnected(); + reject(); + }); + keepassClient.webSocket.addEventListener('message', (event) => { + keepassClient.onNativeMessage(JSON.parse(event?.data)); + }); + keepassClient.webSocket.addEventListener('open', (event) => { + console.log(`${EXTENSION_NAME}: WebSocket connected`); + keepass.isConnected = true; + resolve(); + }); + } catch (e) { + keepassClient.webSocket = null; + onDisconnected(); + } + }); +}; diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index ae6ec86ae..d650a7ab7 100755 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -805,7 +805,12 @@ keepass.disableAutomaticReconnect = function() { }; keepass.reconnect = async function(tab = null, connectionTimeout = 1500) { - keepassClient.connectToNative(); + if (page?.settings?.connectionMethod === ConnectionMethod.WEBSOCKET) { + await keepassClient.connectToWebSocket(); + } else { + keepassClient.connectToNative(); + } + keepass.generateNewKeyPair(); const keyChangeResult = await keepass.changePublicKeys(tab, !!connectionTimeout, connectionTimeout).catch(() => false); diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 5a9f8a030..f306c3a53 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -1,5 +1,10 @@ 'use strict'; +const ConnectionMethod = { + NATIVE_MESSAGING: 'nativemessaging', + WEBSOCKET: 'websocket', +}; + const defaultSettings = { afterFillSorting: SORT_BY_MATCHING_CREDENTIALS_SETTING, afterFillSortingTotp: SORT_BY_RELEVANT_ENTRY, @@ -15,6 +20,7 @@ const defaultSettings = { checkUpdateKeePassXC: CHECK_UPDATE_NEVER, clearCredentialsTimeout: 10, colorTheme: 'system', + connectionMethod: ConnectionMethod.NATIVE_MESSAGING, credentialSorting: SORT_BY_GROUP_AND_TITLE, debugLogging: false, defaultGroup: '', diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 28e851d2f..2ab484bc1 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -522,6 +522,18 @@
+ +