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); });