Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"CHECK_UPDATE_THREE_DAYS": "readonly",
"cloneInto": "readonly",
"compareVersion": "readonly",
"ConnectionMethod": "readonly",
"createResult": "readonly",
"createStylesheet": "readonly",
"DatabaseState": "readonly",
Expand Down
16 changes: 16 additions & 0 deletions keepassxc-browser/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
55 changes: 51 additions & 4 deletions keepassxc-browser/background/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}
});
};
7 changes: 6 additions & 1 deletion keepassxc-browser/background/keepass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 6 additions & 0 deletions keepassxc-browser/background/page.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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: '',
Expand Down
12 changes: 12 additions & 0 deletions keepassxc-browser/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,18 @@ <h2 class="pb-3 mt-0" data-i18n="optionsGeneralSettingsTab"></h2>
<div class="form-text" data-i18n="optionsClearCredentialsTimeoutHelpText"></div>
</div>

<!-- Connection method -->
<div class="form-group col-sm-3 pb-2 py-2">
<label for="connectionMethod" class="form-label" data-i18n="optionsConnectionMethod"></label>
<select class="form-select form-select-sm col-md-3 col-lg-2" id="connectionMethod" data-i18n="[title]optionsConnectionMethodSelection">
<option value="nativemessaging" data-i18n="optionsConnectionMethodNativeMessaging"></option>
<option value="websocket" data-i18n="optionsConnectionMethodWebSocket"></option>
</select>
</div>
<div>
<span class="form-text text-muted" data-i18n="optionsConnectionMethodHelpText"></span>
</div>

<!-- Debug logging -->
<div class="form-group mt-2 pb-1">
<div class="form-check form-switch">
Expand Down
7 changes: 7 additions & 0 deletions keepassxc-browser/options/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ options.initGeneralSettings = async function() {
});
});

// Connection method
$('#tab-general-settings select#connectionMethod').value = options.settings['connectionMethod'];
$('#tab-general-settings select#connectionMethod').addEventListener('change', async function(e) {
options.settings['connectionMethod'] = e.currentTarget.value;
await options.saveSettings();
});

// Default group
$('#defaultGroupButton').addEventListener('click', async function() {
const value = $('#defaultGroup').value;
Expand Down