This commit is contained in:
Martin Drechsel
2025-05-07 11:32:09 +02:00
parent a653111071
commit 5960613357
10 changed files with 1802 additions and 1396 deletions

View File

@ -31,7 +31,7 @@ But first things first.
The technical foundation of the project is Tauri. This framework makes it possible to provide native applications for all common devices (Desktops, Laptops, Tablets, Smartphones) and systems (Windows, Linux, macOS, Android, iOS) with the same codebase. Tauri is comparable to Electron (the technical basis for Visual Studio Code, for example), but the applications created with it are significantly smaller because Tauri uses the native rendering engine of the respective platform (WebView2 (Windows), WKWebView (macOS), WebKitGTK (Linux)) and does not bundle a (customized Chromium) browser, as is the case with Electron. Furthermore, Tauri offers significant advantages over Electron in terms of security and resource efficiency. There is also a sophisticated permission system, which effectively shields the frontend from the host. All access to the host system is only possible with the appropriate permission. This permission concept is also used for the (HaexHub) extensions, thereby ensuring the security of third-party extensions as well.
The project follows a strict local-first approach. This means that HaexHub can fundamentally be used without any form of online account or internet access. The extensions are also stored locally and can be used offline, provided, of course, that the extension itself can function without the internet. A messenger extension will likely make limited sense without internet access. An image viewer or text editor, however, should work fine without the internet.
All user data can be persistently stored and used in a locally encrypted SQLite database, even across extensions, with the appropriate permissions, of course. Unlike many other applications that call themselves local-first, this project implements this approach more consistently. Most applications claiming to be local-first often aren't truly so. The data usually resides (unencrypted) on a backend server and is merely "cached" to varying degrees in the frontend. While this allows these applications to be used offline for a while, the usage is either restricted (read-only in Bitwarden, for example) or the persistence is temporary at best. Most approaches, like this project, use an SQLite (or similar) database in the frontend to achieve offline capability, but this is usually implemented in a browser via IndexedDB or OPFS. Examples include [powersync](https://www.powersync.com/) , [evolu](https://www.evolu.dev/), or [electricSql](https://electric-sql.com/). The problem here is that such persistence is never truly permanent, as the operating system and/or browser can decide when to free up storage. For instance, it's common for Apple to clear the storage of web applications that haven't been used for over a week. As long as the user's data is still present in the backend, this is only moderately tragic, as the "source of truth" residing there can be synchronized back to the frontend at any time. However, this always requires an online account and internet access. Furthermore, with these approaches, the user cannot simply copy their data onto a USB stick and take it with them to use on a completely different computer (perhaps where only intranet is available).
All user data can be persistently stored and used in a locally encrypted SQLite database, even across extensions, with the appropriate permissions, of course. Unlike many other applications that call themselves local-first, this project implements this approach more consistently. Most applications claiming to be local-first often aren't truly so. The data usually resides (unencrypted) on a backend server and is merely "cached" to varying degrees in the frontend. While this allows these applications to be used offline for a while, the usage is either restricted (read-only in Bitwarden, for example) or the persistence is temporary at best. Most approaches, like this project, use an SQLite (or similar) database in the frontend to achieve offline capability, but this is usually implemented in a browser via IndexedDB or OPFS. Examples include [powersync](https://www.powersync.com/), [evolu](https://www.evolu.dev/), or [electricSql](https://electric-sql.com/). The problem here is that such persistence is never truly permanent, as the operating system and/or browser can decide when to free up storage. For instance, it's common for Apple to clear the storage of web applications that haven't been used for over a week. As long as the user's data is still present in the backend, this is only moderately tragic, as the "source of truth" residing there can be synchronized back to the frontend at any time. However, this always requires an online account and internet access. Furthermore, with these approaches, the user cannot simply copy their data onto a USB stick and take it with them to use on a completely different computer (perhaps where only intranet is available).
Moreover, all these approaches are subject to the limitations of the respective browser. The limitation on persistent storage is particularly noteworthy here. All browsers have strict limits, which is why this approach is not suitable for all requirements. Since HaexHub stores data not in the browser, but in a real SQLite database on the hard drive, it is only subject to the hardware limitations of the host system (or USB stick/storage medium).
With HaexHub, all user and extension data can be permanently stored in the local and encrypted database without requiring an online account. However, to make the user's data conveniently and securely available on multiple devices, there will be a synchronization service to synchronize the database state across the user's various devices and systems. The user can, of course, also host this service themselves on their (local) systems or servers. The database state is thus temporarily stored on a (third-party) server and can be synchronized from there with other instances of the local SQLite database. To further enhance data security, the user can also encrypt the data before sending it to the backend, making it unreadable by third parties. This will likely be enabled by default, but it can also be turned off, as there are legitimate use cases where it might be disadvantageous or undesirable. Particularly in corporate or government environments, it could be problematic if all user (employee) data were stored encrypted on the company servers. If the employee becomes unavailable (resignation, accident, death) and their database password (or the encryption key stored in the database) is unknown, there would be no way to access this data.

View File

@ -1,4 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import tailwindcss from "@tailwindcss/vite";
export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
@ -54,7 +56,17 @@ export default defineNuxtConfig({
},
},
/* tailwindcss: {
cssPath: [`assets/css/main.css`, { injectPosition: "first" }],
config: {},
viewer: true,
exposeConfig: false,
},
*/
css: ["~/assets/css/main.css"],
devtools: { enabled: true },
srcDir: "./src",
// Enable SSG
ssr: false,
@ -72,10 +84,12 @@ export default defineNuxtConfig({
strictPort: true,
},
plugins: [tailwindcss()],
/* plugins: [wasm(), topLevelAwait()],
worker: {
format: 'es',
plugins: () => [wasm(), topLevelAwait()],
}, */
},
});
});

View File

@ -17,8 +17,9 @@
"@libsql/client": "^0.15.4",
"@nuxt/icon": "1.11.0",
"@nuxt/image": "1.10.0",
"@nuxtjs/i18n": "^9.5.3",
"@pinia/nuxt": "^0.10.1",
"@nuxtjs/i18n": "^9.5.4",
"@pinia/nuxt": "^0.11.0",
"@tailwindcss/vite": "^4.1.5",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
@ -29,24 +30,24 @@
"@tauri-apps/plugin-store": "^2.2.0",
"@vueuse/core": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"drizzle-orm": "^0.41.0",
"nuxt": "^3.17.0",
"drizzle-orm": "^0.43.0",
"nuxt": "^3.17.2",
"nuxt-snackbar": "1.3.0",
"nuxt-zod-i18n": "^1.11.5",
"tailwindcss": "^4.1.5",
"vue": "^3.5.13",
"zod": "^3.24.3"
"zod": "^3.24.4"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.0",
"@iconify/json": "^2.2.332",
"@iconify/tailwind": "^1.2.0",
"@iconify/json": "^2.2.336",
"@iconify/tailwind4": "^1.0.6",
"@nuxtjs/tailwindcss": "^6.14.0",
"@tauri-apps/cli": "^2.5.0",
"@vitejs/plugin-vue": "^5.2.3",
"drizzle-kit": "^0.30.6",
"flyonui": "^1.3.1",
"typescript": "~5.6.3",
"vite": "^6.3.3",
"drizzle-kit": "^0.31.1",
"flyonui": "^2.1.0",
"typescript": "~5.8.3",
"vite": "^6.3.5",
"vue-tsc": "^2.2.10"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",

3104
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ use std::sync::Mutex;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let protocol_name = "haex-extension";
//let protocol_name = "haex-extension";
tauri::Builder::default()
/* .register_uri_scheme_protocol(protocol_name, move |app_handle, request| {

View File

@ -4,7 +4,7 @@
"version": "0.1.0",
"identifier": "space.haex.hub",
"build": {
"beforeDevCommand": "pnpm dev",
"beforeDevCommand": "pnpm generate && pnpm dev",
"devUrl": "http://localhost:3003",
"beforeBuildCommand": "pnpm generate",
"frontendDist": "../dist"

19
src/assets/css/main.css Normal file
View File

@ -0,0 +1,19 @@
@import "tailwindcss";
@plugin "@iconify/tailwind4";
@plugin "flyonui" {
themes: all;
}
@import "flyonui/variants.css";
@source "../../node_modules/flyonui/flyonui.js";
/* Import Third-party override css */
/* @import "flyonui/src/vendor/flatpickr.css"; */
/* @import "flyonui/src/vendor/notyf.css"; */
/* @import "flyonui/src/vendor/datatables.css"; */
/* @import "flyonui/src/vendor/editor.css"; */
/* @import "flyonui/src/vendor/fullcalendar.css"; */
/* @import "flyonui/src/vendor/raty.css"; */
/* @import "flyonui/src/vendor/waves.css"; */
/* @import "flyonui/src/vendor/apexcharts.css"; */

View File

@ -1,4 +1,16 @@
import 'flyonui/flyonui';
//import { useRouter } from "vue-router";
// FlyonUI
import "flyonui/flyonui";
export default defineNuxtPlugin(() => {
const router = useRouter();
router.afterEach(async () => {
setTimeout(() => window.HSStaticMethods.autoInit());
});
});
/* import 'flyonui/flyonui';
import { type IStaticMethods } from 'flyonui/flyonui';
declare global {
interface Window {
@ -10,4 +22,4 @@ export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:finish', () => {
window.HSStaticMethods.autoInit();
});
});
}); */

12
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import type { IStaticMethods } from "flyonui/flyonui";
declare global {
interface Window {
// Optional third-party libraries
// FlyonUI
HSStaticMethods: IStaticMethods;
}
}
export {};