diff --git a/extensions/html-symbol-finder/package-lock.json b/extensions/html-symbol-finder/package-lock.json
index 122df558..c5962c83 100644
--- a/extensions/html-symbol-finder/package-lock.json
+++ b/extensions/html-symbol-finder/package-lock.json
@@ -35,6 +35,262 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
@@ -51,6 +307,150 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
diff --git a/extensions/html-symbol-finder/src/lib/used-recently-used-items.ts b/extensions/html-symbol-finder/src/lib/used-recently-used-items.ts
index 3def7b37..7803606e 100644
--- a/extensions/html-symbol-finder/src/lib/used-recently-used-items.ts
+++ b/extensions/html-symbol-finder/src/lib/used-recently-used-items.ts
@@ -1,6 +1,5 @@
import type { Dispatch, SetStateAction } from "react";
import { useCallback, useEffect, useState } from "react";
-
import { LocalStorage } from "@vicinae/api";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -40,7 +39,7 @@ const useStateAndLocalStorage = (
const setStateAndLocalStorage = useCallback((updater) => {
setState((state) => {
const newValue = typeof updater === "function" ? updater(state) : updater;
- LocalStorage.setItem(key, JSON.stringify(newValue));
+ LocalStorage.setItem(key, JSON.stringify(newValue)).then(null);
return newValue;
});
}, []);
diff --git a/extensions/keepassxc/CHANGELOG.md b/extensions/keepassxc/CHANGELOG.md
index 6f1a6c2b..e45e866c 100644
--- a/extensions/keepassxc/CHANGELOG.md
+++ b/extensions/keepassxc/CHANGELOG.md
@@ -1,3 +1,20 @@
+## [1.1.0] - 2026-03-24
+
+### Added
+
+- Support for KeePassXC installed via Flatpak, Snap, and AppImage — the CLI path and arguments are now auto-detected based on the installation type
+- Keyboard shortcut `Ctrl+C` for the Copy Password action
+- README: installation instructions for Flatpak, Snap, and AppImage
+
+### Fixed
+
+- `cacheCredentials` and `deleteCredentialsCache` are now properly awaited (`LocalStorage` calls were fire-and-forget)
+- `showToastCliErrors` now correctly returns `Promise` instead of `void`
+- `errorHandler` in `SearchDatabase` is now async so toast errors are awaited
+- Replaced `ImageLike` type alias import with inline `Image.ImageLike` to fix a TypeScript error
+- Fixed `subtitle` prop type on `List.Item` to match the current API
+- Fixed a typo: `chuncks` → `chunks`
+
## [1.0.0] - 2026-02-03
### Added
diff --git a/extensions/keepassxc/README.md b/extensions/keepassxc/README.md
index 7cf396a1..1a656b65 100644
--- a/extensions/keepassxc/README.md
+++ b/extensions/keepassxc/README.md
@@ -6,36 +6,70 @@
Search your passwords, usernames, TOTP codes, and more using KeePassXC directly from Vicinae.
-## Requirements
+# Requirements
To use this extension, you will need:
- Installed [KeePassXC](https://keepassxc.org)
-- The `keepassxc-cli` HAVE TO be in the $PATH (Mostly in `/usr/bin` or in `/usr/local/bin`)
- A `.kdbx` file that contains entries
+- The `keepassxc-cli` HAVE TO be in the $PATH (Mostly in `/usr/bin` or in `/usr/local/bin`) if the KeePassXC is not installed from `snap`, `AppImage` or `flatpak`
-## Installation
+# Installation
The KeePassXC can be installed on linux several ways
-#### Fedora
+## Fedora
```shell
$ sudo dnf install keepassxc
```
-#### Ubuntu and Debian based distros
+## Install via Flatpak
+
+[Here is](https://flathub.org/en/apps/org.keepassxc.KeePassXC) the flathub package.
+
+```shell
+$ flatpak install flathub org.keepassxc.KeePassXC
+```
+
+## Ubuntu and Debian based distros
```shell
$ sudo apt install keepassxc
```
-#### Arch based distros
+or [with Snap](https://snapcraft.io/keepassxc)
+
+```shell
+$ sudo snap install keepassxc
+```
+
+## Arch based distros
```shell
$ sudo pacman -Sy keepassxc
```
+## AppImage based installation
+
+You can download the latest AppImage from [here](https://github.com/keepassxreboot/keepassxc/releases/latest) and run it.
+
+### Install by hand:
+
+```shell
+$ chmod a+x KeePassXC-2.7.12-x86_64.AppImage
+```
+
+```shell
+$ ./KeePassXC-2.7.12-x86_64.AppImage
+```
+
+After this, you **HAVE TO** create a [`.desktop` entry](https://wiki.archlinux.org/title/Desktop_entries) for the AppImage.
+
+> The AppImage could be installed by an AppImage installer too, like [AppManager](https://github.com/kem-a/AppManager).
+
+> The `.desktop` can be created through a UI too: [dedicated apps](https://codeberg.org/libre-menu-editor/libre-menu-editor) for this.
+
## Security Notes
1. Your credentials required to access your KeePass database, which include a password and an optional key file, are securely stored in [Vicinae's local encrypted database](https://developers.raycast.com/information/security#data-storage). This storage prevents other extensions from accessing the storage of that extension.
diff --git a/extensions/keepassxc/package.json b/extensions/keepassxc/package.json
index 46cb31dc..3270eea5 100644
--- a/extensions/keepassxc/package.json
+++ b/extensions/keepassxc/package.json
@@ -4,7 +4,7 @@
"title": "KeePassXC",
"description": "Access a KeePass database through KeePassXC.",
"icon": "keepassxc.png",
- "author": "system7",
+ "author": "7system7",
"pastContributors": [
"pabroux",
"zkytech",
diff --git a/extensions/keepassxc/src/components/search-database.tsx b/extensions/keepassxc/src/components/search-database.tsx
index 3c3b0262..2eea3e13 100644
--- a/extensions/keepassxc/src/components/search-database.tsx
+++ b/extensions/keepassxc/src/components/search-database.tsx
@@ -13,7 +13,7 @@ import {
open,
showHUD,
showToast,
- Toast
+ Toast,
} from "@vicinae/api";
import { getFavicon } from "@raycast/utils";
import { KeePassLoader, showToastCliErrors } from "../utils/keepass-loader";
@@ -21,7 +21,6 @@ import { arrayToEntry, processPlaceholders } from "../utils/placeholder-processo
import { getTOTPCode } from "../utils/totp";
import { isValidUrl } from "../utils/url-checker";
import { useAccessories } from "../utils/use-accessories";
-import ImageLike = Image.ImageLike;
// eslint-disable-next-line no-undef
const preferences: ExtensionPreferences = getPreferenceValues();
@@ -105,16 +104,19 @@ export default function SearchDatabase({ setIsUnlocked }: SearchDatabaseParams):
const [isShowingDetail, setIsShowingDetail] = useState(false);
const accessories = useAccessories();
- const errorHandler = (e: { message: string }) => {
+ const errorHandler = async (e: { message: string }) => {
setIsUnlocked(false);
- showToastCliErrors(e);
+ await showToastCliErrors(e);
};
- const entryFavicon = useCallback((icon: string): ImageLike | undefined => {
+ const entryFavicon = useCallback((icon: string): Image.ImageLike | undefined => {
if (userInterfaceFavicon) {
- return isValidUrl(icon)
- ? getFavicon(icon, { fallback: Icon.QuestionMarkCircle })
- : { source: Icon.QuestionMarkCircle, tintColor: Color.SecondaryText };
+ if (isValidUrl(icon)) {
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ return getFavicon(icon, { fallback: Icon.QuestionMarkCircle });
+ }
+
+ return { source: Icon.QuestionMarkCircle, tintColor: Color.SecondaryText };
}
return undefined;
@@ -156,7 +158,8 @@ export default function SearchDatabase({ setIsUnlocked }: SearchDatabaseParams):
key={i}
title={title}
icon={entryFavicon(url)}
- subtitle={{ value: username, tooltip: "Username" }}
+ subtitle={username}
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
accessories={accessories(isShowingDetail, keePassEntry)}
detail={(
{
if ("" === password) {
await showToast(Toast.Style.Failure, "Error", "No Password Set");
diff --git a/extensions/keepassxc/src/components/unlock-database.tsx b/extensions/keepassxc/src/components/unlock-database.tsx
index 87dac7c2..2430c204 100644
--- a/extensions/keepassxc/src/components/unlock-database.tsx
+++ b/extensions/keepassxc/src/components/unlock-database.tsx
@@ -33,7 +33,7 @@ export default function UnlockDatabase({ setIsUnlocked }: UnlockDatabaseProps):
KeePassLoader.checkCredentials(value.password, value.keyFile?.[0] ?? "").then(async () => {
await showToast({ style: Toast.Style.Success, title: "Database Unlocked" });
- KeePassLoader.cacheCredentials(value.password, value.keyFile?.[0] ?? "");
+ await KeePassLoader.cacheCredentials(value.password, value.keyFile?.[0] ?? "");
KeePassLoader.setCredentials(value.password, value.keyFile?.[0] ?? "");
setIsUnlocked(true);
}, showToastCliErrors);
@@ -49,24 +49,15 @@ export default function UnlockDatabase({ setIsUnlocked }: UnlockDatabaseProps):
)}
>
-
-
-
+
+
+
diff --git a/extensions/keepassxc/src/utils/keepass-loader.ts b/extensions/keepassxc/src/utils/keepass-loader.ts
index 80e6d1c7..df625de2 100644
--- a/extensions/keepassxc/src/utils/keepass-loader.ts
+++ b/extensions/keepassxc/src/utils/keepass-loader.ts
@@ -1,4 +1,4 @@
-import { getApplications, getPreferenceValues, LocalStorage, Toast, showToast } from "@vicinae/api";
+import { getApplications, getPreferenceValues, LocalStorage, showToast, Toast } from "@vicinae/api";
import { parse } from "csv-parse/sync";
import child_process from "child_process";
import process from "process";
@@ -7,6 +7,9 @@ interface Preference {
database: string;
}
+// eslint-disable-next-line no-unused-vars
+type RejectFn = (reason: Error) => void;
+
/**
* Utility function to show a toast message for CLI errors.
* @param {Object} e - Error object with a `message` property.
@@ -15,7 +18,7 @@ interface Preference {
* Takes the error message from the CLI and shows a toast message with a human-readable description.
* If the error is due to an invalid preference, the toast message will be "Invalid Preference: ".
*/
-const showToastCliErrors = async (e: { message: string }): void => {
+const showToastCliErrors = async (e: { message: string }): Promise => {
let invalidPreference = "";
let toastMessage = e.message.trim();
@@ -41,6 +44,7 @@ class KeePassLoader {
private static database: string;
private static databasePassword: string;
private static keepassxcCli: string | undefined;
+ private static keepassxcCliArgs: string[] = [];
private static keyFile: string;
private static spawn = child_process.spawn;
static {
@@ -52,7 +56,7 @@ class KeePassLoader {
* Check if the folder is valid for a search
*
* KeePassXC's search doesn't include all folders.
- * That function aims to replicate which folder are used.
+ * That function aims to replicate which folder is used.
*
* @param {string} folder - The folder to check.
* @returns {boolean} - True if the folder is valid, false otherwise.
@@ -68,10 +72,10 @@ class KeePassLoader {
* If the error message contains the string "Enter password to unlock", or "Maximum depth of replacement has been reached",
* or the error message is empty, the error is ignored. Otherwise, the error is rejected.
*
- * @param {function} reject - The function to call with the error message.
- * @returns {function} - A function to handle errors on the stderr stream.
+ * @param reject - The function to call with the error message.
+ * @returns A function to handle errors on the stderr stream.
*/
- private static cliStderrErrorHandler = (reject: (reason: Error) => void) => (data: Buffer) => {
+ private static cliStderrErrorHandler = (reject: RejectFn) => (data: Buffer) => {
if (
-1 != data.toString().indexOf("Enter password to unlock")
|| -1 != data.toString().indexOf("Maximum depth of replacement has been reached")
@@ -92,10 +96,8 @@ class KeePassLoader {
* @param {string} keyFile - The key file path.
* @returns {string[]} - An array with the "-k" option and the key file path, or an empty array.
*/
- private static convertIntoKeyFileOption = (keyFile: string) =>
- "" != keyFile && null != keyFile
- ? ["-k", `${keyFile}`]
- : [];
+ private static convertIntoKeyFileOption = (keyFile: string): string[] =>
+ "" != keyFile && null != keyFile ? ["-k", `${keyFile}`] : [];
/**
* Converts a string from the KeePassXC CLI into a sorted array of strings.
@@ -175,10 +177,19 @@ class KeePassLoader {
const apps = installedApplications.filter(application => "KeePassXC" == application.name);
if (0 < apps.length) {
- // The flatpak/snap/appImage version should be supported later
- // apps[0].path.includes('flatpak')
- // this.keepassxcCli = "/usr/bin/flatpak run --branch=stable --command=keepassxc-cli org.keepassxc.KeePassXC";
- this.keepassxcCli = "keepassxc-cli";
+ if (apps[0].path.includes("flatpak")) {
+ this.keepassxcCli = "flatpak";
+ this.keepassxcCliArgs = ["run", "--branch=stable", "--command=keepassxc-cli", "org.keepassxc.KeePassXC"];
+ } else if (apps[0].path.endsWith(".AppImage")) {
+ this.keepassxcCli = apps[0].path;
+ this.keepassxcCliArgs = ["keepassxc-cli"];
+ } else if (apps[0].path.includes("/snap/")) {
+ this.keepassxcCli = "/snap/bin/keepassxc-cli";
+ this.keepassxcCliArgs = [];
+ } else {
+ this.keepassxcCli = "keepassxc-cli";
+ this.keepassxcCliArgs = [];
+ }
}
} else {
new Error(`KeePassXC not found: ${process.platform}`);
@@ -191,9 +202,9 @@ class KeePassLoader {
* @param password The password to cache.
* @param keyFile The path to the key file to cache.
*/
- static cacheCredentials = (password: string, keyFile = "") => {
- LocalStorage.setItem("databasePassword", password);
- LocalStorage.setItem("keyFile", keyFile);
+ static cacheCredentials = async (password: string, keyFile = "") => {
+ await LocalStorage.setItem("databasePassword", password);
+ await LocalStorage.setItem("keyFile", keyFile);
};
/**
@@ -204,26 +215,30 @@ class KeePassLoader {
* @returns A Promise that resolves if the credentials are valid, and rejects otherwise.
*/
static checkCredentials = (databasePassword: string, keyFile: string) =>
- KeePassLoader.findApplication().then(() => new Promise((resolve, reject) => {
- const cli = this.spawn(`${this.keepassxcCli}`, [
- "db-info",
- ...this.convertIntoKeyFileOption(keyFile),
- "-q",
- `${this.database}`,
- ]);
+ KeePassLoader.findApplication().then(
+ () =>
+ new Promise((resolve, reject) => {
+ const cli = this.spawn(`${this.keepassxcCli}`, [
+ ...this.keepassxcCliArgs,
+ "db-info",
+ ...this.convertIntoKeyFileOption(keyFile),
+ "-q",
+ `${this.database}`,
+ ]);
- cli.stdin.write(`${databasePassword}\n`);
- cli.stdin.end();
- cli.on("error", reject);
- cli.stderr.on("data", this.cliStderrErrorHandler(reject));
- cli.on("exit", code => {
- if (0 === code) {
- resolve();
- } else {
- reject(new Error("Invalid Credentials"));
- }
- });
- }));
+ cli.stdin.write(`${databasePassword}\n`);
+ cli.stdin.end();
+ cli.on("error", reject);
+ cli.stderr.on("data", this.cliStderrErrorHandler(reject));
+ cli.on("exit", code => {
+ if (0 === code) {
+ resolve();
+ } else {
+ reject(new Error("Invalid Credentials"));
+ }
+ });
+ }),
+ );
/**
* Removes the stored credentials from LocalStorage.
@@ -232,9 +247,9 @@ class KeePassLoader {
* from LocalStorage, ensuring that the credentials are no longer stored
* locally.
*/
- static deleteCredentialsCache = () => {
- LocalStorage.removeItem("databasePassword");
- LocalStorage.removeItem("keyFile");
+ static deleteCredentialsCache = async () => {
+ await LocalStorage.removeItem("databasePassword");
+ await LocalStorage.removeItem("keyFile");
};
/**
@@ -248,40 +263,43 @@ class KeePassLoader {
* @returns {Promise} The result of the command.
*/
static execKeepassxcCli = (options: string[]): Promise =>
- KeePassLoader.findApplication().then(() => new Promise((resolve, reject) => {
- const chuncks: Buffer[] = [];
- const cli = this.spawn(`${this.keepassxcCli}`, options);
- const tryResolve = () => {
- if (ended && exited) {
- resolve(result);
- }
- };
- let ended = false;
- let exited = false;
- let result: string;
+ KeePassLoader.findApplication().then(
+ () =>
+ new Promise((resolve, reject) => {
+ const chunks: Buffer[] = [];
+ const cli = this.spawn(`${this.keepassxcCli}`, [...this.keepassxcCliArgs, ...options]);
+ const tryResolve = () => {
+ if (ended && exited) {
+ resolve(result);
+ }
+ };
+ let ended = false;
+ let exited = false;
+ let result: string;
- cli.stdin.write(`${this.databasePassword}\n`);
- cli.stdin.end();
- cli.on("error", reject);
- cli.stderr.on("data", this.cliStderrErrorHandler(reject));
- cli.stdout.on("data", chunck => {
- chuncks.push(chunck);
- });
- cli.stdout.on("end", () => {
- result = chuncks.join("").toString();
- result = result.slice(0, result.length - 1);
- ended = true;
- tryResolve();
- });
- cli.on("exit", code => {
- if (0 === code) {
- exited = true;
- tryResolve();
- } else {
- reject(new Error(`Something went wrong when accessing the database (exit code: ${code})`));
- }
- });
- }));
+ cli.stdin.write(`${this.databasePassword}\n`);
+ cli.stdin.end();
+ cli.on("error", reject);
+ cli.stderr.on("data", this.cliStderrErrorHandler(reject));
+ cli.stdout.on("data", chunk => {
+ chunks.push(chunk);
+ });
+ cli.stdout.on("end", () => {
+ result = chunks.join("").toString();
+ result = result.slice(0, result.length - 1);
+ ended = true;
+ tryResolve();
+ });
+ cli.on("exit", code => {
+ if (0 === code) {
+ exited = true;
+ tryResolve();
+ } else {
+ reject(new Error(`Something went wrong when accessing the database (exit code: ${code})`));
+ }
+ });
+ }),
+ );
/**
* Load credentials from LocalStorage.
@@ -336,8 +354,8 @@ class KeePassLoader {
"-f",
"csv",
`${this.database}`,
- ]).then(entries => {
- LocalStorage.setItem("entries", entries);
+ ]).then(async entries => {
+ await LocalStorage.setItem("entries", entries);
return this.parseCsvEntries(entries);
});