Compare commits

...
10 Commits
23 changed files with 1553 additions and 1783 deletions
+16 -16
View File
@@ -9,8 +9,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@lume/system": "workspace:^",
"@lume/icons": "workspace:^", "@lume/icons": "workspace:^",
"@lume/system": "workspace:^",
"@lume/ui": "workspace:^", "@lume/ui": "workspace:^",
"@lume/utils": "workspace:^", "@lume/utils": "workspace:^",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
@@ -22,42 +22,42 @@
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/query-sync-storage-persister": "^5.36.0", "@tanstack/query-sync-storage-persister": "^5.40.0",
"@tanstack/react-query": "^5.36.0", "@tanstack/react-query": "^5.40.0",
"@tanstack/react-query-persist-client": "^5.36.0", "@tanstack/react-query-persist-client": "^5.40.0",
"@tanstack/react-router": "1.32.5", "@tanstack/react-router": "^1.34.5",
"i18next": "^23.11.4", "i18next": "^23.11.5",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"minidenticons": "^4.2.1", "minidenticons": "^4.2.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"nostr-tools": "^2.5.2", "nostr-tools": "^2.6.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-currency-input-field": "^3.8.0", "react-currency-input-field": "^3.8.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.51.4", "react-hook-form": "^7.51.5",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.1", "react-i18next": "^14.1.2",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"slate": "^0.103.0", "slate": "^0.103.0",
"slate-react": "^0.102.0", "slate-react": "^0.104.0",
"sonner": "^1.4.41", "sonner": "^1.4.41",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.1",
"virtua": "^0.31.0" "virtua": "^0.31.0"
}, },
"devDependencies": { "devDependencies": {
"@lume/tailwindcss": "workspace:^", "@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^", "@lume/types": "workspace:^",
"@tanstack/router-devtools": "^1.32.5", "@tanstack/router-devtools": "^1.34.5",
"@tanstack/router-vite-plugin": "^1.32.2", "@tanstack/router-vite-plugin": "^1.34.1",
"@types/react": "^18.3.2", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.11", "vite": "^5.2.12",
"vite-plugin-top-level-await": "^1.4.1", "vite-plugin-top-level-await": "^1.4.1",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@@ -9,7 +9,7 @@ import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
export const Route = createFileRoute("/auth/new/$account/backup")({ export const Route = createFileRoute("/auth/$account/backup")({
component: Screen, component: Screen,
}); });
@@ -1,9 +1,8 @@
import { LaurelIcon } from "@lume/icons"; import { LaurelIcon } from "@lume/icons";
import { NostrQuery } from "@lume/system"; import { NostrAccount, NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import * as Switch from "@radix-ui/react-switch"; import * as Switch from "@radix-ui/react-switch";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { requestPermission } from "@tauri-apps/plugin-notification";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -18,28 +17,14 @@ export const Route = createFileRoute("/auth/$account/settings")({
}); });
function Screen() { function Screen() {
const navigate = Route.useNavigate();
const { account } = Route.useParams();
const { settings } = Route.useRouteContext(); const { settings } = Route.useRouteContext();
const { account } = Route.useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [newSettings, setNewSettings] = useState(settings); const [newSettings, setNewSettings] = useState(settings);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const toggleNofitication = async () => { const navigate = Route.useNavigate();
await requestPermission();
setNewSettings((prev) => ({
...prev,
notification: !newSettings.notification,
}));
};
const toggleAutoUpdate = () => {
setNewSettings((prev) => ({
...prev,
autoUpdate: !newSettings.autoUpdate,
}));
};
const toggleEnhancedPrivacy = () => { const toggleEnhancedPrivacy = () => {
setNewSettings((prev) => ({ setNewSettings((prev) => ({
@@ -69,11 +54,13 @@ function Screen() {
// publish settings // publish settings
const eventId = await NostrQuery.setSettings(newSettings); const eventId = await NostrQuery.setSettings(newSettings);
const allAccounts = await NostrAccount.getAccounts();
if (eventId) { if (eventId) {
return navigate({ return navigate({
to: "/$account/home", to: "/$account/home",
params: { account }, params: { account },
search: { accounts: [...new Set([account, ...allAccounts])] },
replace: true, replace: true,
}); });
} }
@@ -100,22 +87,6 @@ function Screen() {
</div> </div>
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1">
<h3 className="font-semibold">Push Notification</h3>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
Enabling push notifications will allow you to receive
notifications from Lume.
</p>
</div>
<Switch.Root
checked={newSettings.notification}
onClick={() => toggleNofitication()}
className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20"
>
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10"> <div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold">Enhanced Privacy</h3> <h3 className="font-semibold">Enhanced Privacy</h3>
@@ -132,21 +103,6 @@ function Screen() {
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" /> <Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root> </Switch.Root>
</div> </div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1">
<h3 className="font-semibold">Auto Update</h3>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
Automatically download and install new version.
</p>
</div>
<Switch.Root
checked={newSettings.autoUpdate}
onClick={() => toggleAutoUpdate()}
className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20"
>
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10"> <div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold">Zap</h3> <h3 className="font-semibold">Zap</h3>
@@ -186,7 +142,7 @@ function Screen() {
disabled={loading} disabled={loading}
className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
> >
{t("global.continue")} {loading ? <Spinner /> : t("global.continue")}
</button> </button>
</div> </div>
</div> </div>
@@ -45,7 +45,7 @@ function Screen() {
if (eventId) { if (eventId) {
navigate({ navigate({
to: "/auth/new/$account/backup", to: "/auth/$account/backup",
params: { account: account.npub }, params: { account: account.npub },
replace: true, replace: true,
}); });
@@ -23,12 +23,12 @@ function Screen() {
try { try {
setLoading(true); setLoading(true);
const npub = await NostrAccount.connectRemoteAccount(uri); const remoteAccount = await NostrAccount.connectRemoteAccount(uri);
if (npub) { if (remoteAccount?.length) {
navigate({ return navigate({
to: "/auth/$account/settings", to: "/auth/$account/settings",
params: { account: npub }, params: { account: remoteAccount },
replace: true, replace: true,
}); });
} }
+8
View File
@@ -34,6 +34,8 @@ function Screen() {
className="h-auto w-full aspect-square rounded-lg shadow-md transform" className="h-auto w-full aspect-square rounded-lg shadow-md transform"
controls controls
muted muted
preload="none"
poster="/poster_1.jpeg"
> >
<source <source
src="https://video.nostr.build/692f71e2be47ecfc29edcbdaa198cc5979bfb9c900f05d78682895dd546d8d4f.mp4" src="https://video.nostr.build/692f71e2be47ecfc29edcbdaa198cc5979bfb9c900f05d78682895dd546d8d4f.mp4"
@@ -55,6 +57,8 @@ function Screen() {
className="h-auto w-full aspect-square rounded-lg shadow-md transform" className="h-auto w-full aspect-square rounded-lg shadow-md transform"
controls controls
muted muted
preload="none"
poster="/poster_2.jpeg"
> >
<source <source
src="https://video.nostr.build/d33962520506d86acfb4b55a7b265821e10ae637f5ec830a173b7e6092b16ec8.mp4" src="https://video.nostr.build/d33962520506d86acfb4b55a7b265821e10ae637f5ec830a173b7e6092b16ec8.mp4"
@@ -76,6 +80,8 @@ function Screen() {
className="h-auto w-full aspect-square rounded-lg shadow-md transform" className="h-auto w-full aspect-square rounded-lg shadow-md transform"
controls controls
muted muted
preload="none"
poster="/poster_3.jpeg"
> >
<source <source
src="https://video.nostr.build/927abbfde2097e470ac751181b1db456b7e4b9149550408efff1a966a7ffb9a8.mp4" src="https://video.nostr.build/927abbfde2097e470ac751181b1db456b7e4b9149550408efff1a966a7ffb9a8.mp4"
@@ -97,6 +103,8 @@ function Screen() {
className="h-auto w-full rounded-lg shadow-md transform" className="h-auto w-full rounded-lg shadow-md transform"
controls controls
muted muted
preload="none"
poster="/poster_4.jpeg"
> >
<source <source
src="https://video.nostr.build/513de4824b6abaf7e9698c1dad2f68096574356848c0c200bc8cb8074df29410.mp4" src="https://video.nostr.build/513de4824b6abaf7e9698c1dad2f68096574356848c0c200bc8cb8074df29410.mp4"
+1 -1
View File
@@ -13,7 +13,7 @@
"@astrojs/check": "^0.5.10", "@astrojs/check": "^0.5.10",
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"@fontsource/alice": "^5.0.13", "@fontsource/alice": "^5.0.13",
"astro": "^4.8.3", "astro": "^4.9.2",
"astro-seo-meta": "^4.1.1", "astro-seo-meta": "^4.1.1",
"astro-seo-schema": "^4.0.2", "astro-seo-schema": "^4.0.2",
"schema-dts": "^1.1.2", "schema-dts": "^1.1.2",
+1 -1
View File
@@ -20,7 +20,7 @@
"node": ">=18" "node": ">=18"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.0.0-beta.7", "@tauri-apps/api": "2.0.0-beta.13",
"@tauri-apps/plugin-autostart": "2.0.0-beta.3", "@tauri-apps/plugin-autostart": "2.0.0-beta.3",
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.1", "@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.1",
"@tauri-apps/plugin-dialog": "2.0.0-beta.3", "@tauri-apps/plugin-dialog": "2.0.0-beta.3",
+1 -1
View File
@@ -8,7 +8,7 @@
}, },
"devDependencies": { "devDependencies": {
"@lume/tsconfig": "workspace:*", "@lume/tsconfig": "workspace:*",
"@types/react": "^18.3.2", "@types/react": "^18.3.3",
"typescript": "^5.4.5" "typescript": "^5.4.5"
} }
} }
+2 -2
View File
@@ -5,13 +5,13 @@
"main": "./src/index.ts", "main": "./src/index.ts",
"dependencies": { "dependencies": {
"@lume/utils": "workspace:^", "@lume/utils": "workspace:^",
"@tanstack/react-query": "^5.36.0", "@tanstack/react-query": "^5.40.0",
"react": "^18.3.1" "react": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^", "@lume/types": "workspace:^",
"@types/react": "^18.3.2", "@types/react": "^18.3.3",
"typescript": "^5.4.5" "typescript": "^5.4.5"
} }
} }
+18 -11
View File
@@ -1,5 +1,5 @@
import { Metadata } from "@lume/types"; import { Metadata } from "@lume/types";
import { commands } from "./commands"; import { Result, commands } from "./commands";
export class NostrAccount { export class NostrAccount {
static async getAccounts() { static async getAccounts() {
@@ -13,7 +13,14 @@ export class NostrAccount {
} }
static async loadAccount(npub: string) { static async loadAccount(npub: string) {
const query = await commands.loadAccount(npub); const bunker: string = localStorage.getItem(`${npub}_bunker`);
let query: Result<boolean, string>;
if (bunker?.length && bunker?.startsWith("bunker://")) {
query = await commands.loadAccount(npub, bunker);
} else {
query = await commands.loadAccount(npub, null);
}
if (query.status === "ok") { if (query.status === "ok") {
return query.data; return query.data;
@@ -62,20 +69,20 @@ export class NostrAccount {
} }
static async connectRemoteAccount(uri: string) { static async connectRemoteAccount(uri: string) {
const remoteKey = uri.replace("bunker://", "").split("?")[0]; const connect = await commands.connectRemoteAccount(uri);
const npub = await commands.toNpub(remoteKey);
if (npub.status === "ok") {
const connect = await commands.nostrConnect(npub.data, uri);
if (connect.status === "ok") { if (connect.status === "ok") {
return connect.data; const npub = connect.data;
const parsed = new URL(uri);
parsed.searchParams.delete("secret");
// save connection string
localStorage.setItem(`${npub}_bunker`, parsed.toString());
return npub;
} else { } else {
throw new Error(connect.error); throw new Error(connect.error);
} }
} else {
throw new Error(npub.error);
}
} }
static async setContactList(pubkeys: string[]) { static async setContactList(pubkeys: string[]) {
+62 -256
View File
@@ -11,10 +11,7 @@ export const commands = {
}, },
async connectRelay(relay: string) : Promise<Result<boolean, null>> { async connectRelay(relay: string) : Promise<Result<boolean, null>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("connect_relay", { relay }) };
status: "ok",
data: await TAURI_INVOKE("connect_relay", { relay }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -22,10 +19,7 @@ export const commands = {
}, },
async removeRelay(relay: string) : Promise<Result<boolean, null>> { async removeRelay(relay: string) : Promise<Result<boolean, null>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("remove_relay", { relay }) };
status: "ok",
data: await TAURI_INVOKE("remove_relay", { relay }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -47,118 +41,65 @@ export const commands = {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async saveAccount( async saveAccount(nsec: string, password: string) : Promise<Result<string, string>> {
nsec: string,
password: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("save_account", { nsec, password }) };
status: "ok",
data: await TAURI_INVOKE("save_account", { nsec, password }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getEncryptedKey( async getEncryptedKey(npub: string, password: string) : Promise<Result<string, string>> {
npub: string,
password: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_encrypted_key", { npub, password }) };
status: "ok",
data: await TAURI_INVOKE("get_encrypted_key", { npub, password }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async nostrConnect( async connectRemoteAccount(uri: string) : Promise<Result<string, string>> {
npub: string,
uri: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) };
status: "ok",
data: await TAURI_INVOKE("nostr_connect", { npub, uri }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async loadAccount(npub: string): Promise<Result<boolean, string>> { async loadAccount(npub: string, bunker: string | null) : Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("load_account", { npub, bunker }) };
status: "ok",
data: await TAURI_INVOKE("load_account", { npub }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async eventToBech32( async eventToBech32(id: string, relays: string[]) : Promise<Result<string, null>> {
id: string,
relays: string[],
): Promise<Result<string, null>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("event_to_bech32", { id, relays }) };
status: "ok",
data: await TAURI_INVOKE("event_to_bech32", { id, relays }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async userToBech32( async userToBech32(key: string, relays: string[]) : Promise<Result<string, null>> {
key: string,
relays: string[],
): Promise<Result<string, null>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("user_to_bech32", { key, relays }) };
status: "ok",
data: await TAURI_INVOKE("user_to_bech32", { key, relays }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async toNpub(hex: string): Promise<Result<string, null>> { async verifyNip05(key: string, nip05: string) : Promise<Result<boolean, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("to_npub", { hex }) }; return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) };
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async verifyNip05( async getActivities(account: string, kind: string) : Promise<Result<string[], string>> {
key: string,
nip05: string,
): Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_activities", { account, kind }) };
status: "ok",
data: await TAURI_INVOKE("verify_nip05", { key, nip05 }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getActivities(
account: string,
kind: string,
): Promise<Result<string[], string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_activities", { account, kind }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -166,10 +107,7 @@ export const commands = {
}, },
async getCurrentUserProfile() : Promise<Result<string, string>> { async getCurrentUserProfile() : Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_current_user_profile") };
status: "ok",
data: await TAURI_INVOKE("get_current_user_profile"),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -193,53 +131,23 @@ export const commands = {
}, },
async setContactList(pubkeys: string[]) : Promise<Result<boolean, string>> { async setContactList(pubkeys: string[]) : Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("set_contact_list", { pubkeys }) };
status: "ok",
data: await TAURI_INVOKE("set_contact_list", { pubkeys }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async createProfile( async createProfile(name: string, displayName: string, about: string, picture: string, banner: string, nip05: string, lud16: string, website: string) : Promise<Result<string, string>> {
name: string,
displayName: string,
about: string,
picture: string,
banner: string,
nip05: string,
lud16: string,
website: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("create_profile", { name, displayName, about, picture, banner, nip05, lud16, website }) };
status: "ok",
data: await TAURI_INVOKE("create_profile", {
name,
displayName,
about,
picture,
banner,
nip05,
lud16,
website,
}),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async follow( async follow(id: string, alias: string | null) : Promise<Result<string, string>> {
id: string,
alias: string | null,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("follow", { id, alias }) };
status: "ok",
data: await TAURI_INVOKE("follow", { id, alias }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -261,15 +169,9 @@ export const commands = {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async setNstore( async setNstore(key: string, content: string) : Promise<Result<string, string>> {
key: string,
content: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("set_nstore", { key, content }) };
status: "ok",
data: await TAURI_INVOKE("set_nstore", { key, content }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -299,31 +201,17 @@ export const commands = {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async zapProfile( async zapProfile(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
id: string,
amount: string,
message: string,
): Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
status: "ok",
data: await TAURI_INVOKE("zap_profile", { id, amount, message }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async zapEvent( async zapEvent(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
id: string,
amount: string,
message: string,
): Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
status: "ok",
data: await TAURI_INVOKE("zap_event", { id, amount, message }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -331,10 +219,7 @@ export const commands = {
}, },
async friendToFriend(npub: string) : Promise<Result<boolean, string>> { async friendToFriend(npub: string) : Promise<Result<boolean, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("friend_to_friend", { npub }) };
status: "ok",
data: await TAURI_INVOKE("friend_to_friend", { npub }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -356,70 +241,41 @@ export const commands = {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getEventsBy( async getEventsBy(publicKey: string, asOf: string | null) : Promise<Result<string[], string>> {
publicKey: string,
asOf: string | null,
): Promise<Result<string[], string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }) };
status: "ok",
data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getLocalEvents( async getLocalEvents(pubkeys: string[], until: string | null) : Promise<Result<string[], string>> {
pubkeys: string[],
until: string | null,
): Promise<Result<string[], string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_local_events", { pubkeys, until }) };
status: "ok",
data: await TAURI_INVOKE("get_local_events", { pubkeys, until }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getGlobalEvents( async getGlobalEvents(until: string | null) : Promise<Result<string[], string>> {
until: string | null,
): Promise<Result<string[], string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
status: "ok",
data: await TAURI_INVOKE("get_global_events", { until }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getHashtagEvents( async getHashtagEvents(hashtags: string[], until: string | null) : Promise<Result<string[], string>> {
hashtags: string[],
until: string | null,
): Promise<Result<string[], string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }) };
status: "ok",
data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async publish( async publish(content: string, tags: string[][]) : Promise<Result<string, string>> {
content: string,
tags: string[][],
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("publish", { content, tags }) };
status: "ok",
data: await TAURI_INVOKE("publish", { content, tags }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -436,26 +292,9 @@ export const commands = {
async showInFolder(path: string) : Promise<void> { async showInFolder(path: string) : Promise<void> {
await TAURI_INVOKE("show_in_folder", { path }); await TAURI_INVOKE("show_in_folder", { path });
}, },
async createColumn( async createColumn(label: string, x: number, y: number, width: number, height: number, url: string) : Promise<Result<string, string>> {
label: string,
x: number,
y: number,
width: number,
height: number,
url: string,
): Promise<Result<string, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("create_column", { label, x, y, width, height, url }) };
status: "ok",
data: await TAURI_INVOKE("create_column", {
label,
x,
y,
width,
height,
url,
}),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -463,63 +302,31 @@ export const commands = {
}, },
async closeColumn(label: string) : Promise<Result<boolean, null>> { async closeColumn(label: string) : Promise<Result<boolean, null>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("close_column", { label }) };
status: "ok",
data: await TAURI_INVOKE("close_column", { label }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async repositionColumn( async repositionColumn(label: string, x: number, y: number) : Promise<Result<null, string>> {
label: string,
x: number,
y: number,
): Promise<Result<null, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("reposition_column", { label, x, y }) };
status: "ok",
data: await TAURI_INVOKE("reposition_column", { label, x, y }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async resizeColumn( async resizeColumn(label: string, width: number, height: number) : Promise<Result<null, string>> {
label: string,
width: number,
height: number,
): Promise<Result<null, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("resize_column", { label, width, height }) };
status: "ok",
data: await TAURI_INVOKE("resize_column", { label, width, height }),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async openWindow( async openWindow(label: string, title: string, url: string, width: number, height: number) : Promise<Result<null, string>> {
label: string,
title: string,
url: string,
width: number,
height: number,
): Promise<Result<null, string>> {
try { try {
return { return { status: "ok", data: await TAURI_INVOKE("open_window", { label, title, url, width, height }) };
status: "ok",
data: await TAURI_INVOKE("open_window", {
label,
title,
url,
width,
height,
}),
};
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -527,18 +334,15 @@ export const commands = {
}, },
async setBadge(count: number) : Promise<void> { async setBadge(count: number) : Promise<void> {
await TAURI_INVOKE("set_badge", { count }); await TAURI_INVOKE("set_badge", { count });
}, }
}; }
/** user-defined types **/ /** user-defined types **/
export type Account = { npub: string; nsec: string }; export type Account = { npub: string; nsec: string }
export type Relays = { export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
connected: string[];
read: string[] | null;
write: string[] | null;
both: string[] | null;
};
/** tauri-specta globals **/ /** tauri-specta globals **/
@@ -548,10 +352,10 @@ import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webview
type __EventObj__<T> = { type __EventObj__<T> = {
listen: ( listen: (
cb: TAURI_API_EVENT.EventCallback<T>, cb: TAURI_API_EVENT.EventCallback<T>
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>; ) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
once: ( once: (
cb: TAURI_API_EVENT.EventCallback<T>, cb: TAURI_API_EVENT.EventCallback<T>
) => ReturnType<typeof TAURI_API_EVENT.once<T>>; ) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
emit: T extends null emit: T extends null
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit> ? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
@@ -563,7 +367,7 @@ export type Result<T, E> =
| { status: "error"; error: E }; | { status: "error"; error: E };
function __makeEvents__<T extends Record<string, any>>( function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>, mappings: Record<keyof T, string>
) { ) {
return new Proxy( return new Proxy(
{} as unknown as { {} as unknown as {
@@ -593,6 +397,8 @@ function __makeEvents__<T extends Record<string, any>>(
}, },
}); });
}, },
}, }
); );
} }
+7 -10
View File
@@ -236,31 +236,28 @@ export class NostrQuery {
} }
static async getColumns() { static async getColumns() {
const systemPath = "resources/system_columns.json";
const resourcePath = await resolveResource(systemPath);
const resourceFile = await readTextFile(resourcePath);
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
const query = await commands.getNstore(NSTORE_KEYS.columns); const query = await commands.getNstore(NSTORE_KEYS.columns);
if (query.status === "ok") { if (query.status === "ok") {
const columns: LumeColumn[] = query.data ? JSON.parse(query.data) : []; const columns: LumeColumn[] = query.data ? JSON.parse(query.data) : [];
if (columns.length < 1) { if (columns.length < 1) {
const systemPath = "resources/system_columns.json";
const resourcePath = await resolveResource(systemPath);
const resourceFile = await readTextFile(resourcePath);
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
return systemColumns; return systemColumns;
} }
return columns; return columns;
} else { } else {
return []; return systemColumns;
} }
} }
static async setColumns(columns: LumeColumn[]) { static async setColumns(columns: LumeColumn[]) {
const query = await commands.setNstore( const content = JSON.stringify(columns);
NSTORE_KEYS.columns, const query = await commands.setNstore(NSTORE_KEYS.columns, content);
JSON.stringify(columns),
);
if (query.status === "ok") { if (query.status === "ok") {
return query.data; return query.data;
+1 -1
View File
@@ -14,7 +14,7 @@
"@lume/tailwindcss": "workspace:^", "@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^", "@lume/types": "workspace:^",
"@types/react": "^18.3.2", "@types/react": "^18.3.3",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5" "typescript": "^5.4.5"
} }
+4 -4
View File
@@ -8,21 +8,21 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-query": "^5.36.0", "@tanstack/react-query": "^5.40.0",
"bitcoin-units": "^1.0.0", "bitcoin-units": "^1.0.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"light-bolt11-decoder": "^3.1.1", "light-bolt11-decoder": "^3.1.1",
"nostr-tools": "^2.5.2", "nostr-tools": "^2.6.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"slate": "^0.103.0", "slate": "^0.103.0",
"slate-react": "^0.102.0" "slate-react": "^0.104.0"
}, },
"devDependencies": { "devDependencies": {
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^", "@lume/types": "workspace:^",
"@types/react": "^18.3.2", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"typescript": "^5.4.5" "typescript": "^5.4.5"
+871 -851
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -34,11 +34,10 @@ fn main() {
nostr::keys::create_account, nostr::keys::create_account,
nostr::keys::save_account, nostr::keys::save_account,
nostr::keys::get_encrypted_key, nostr::keys::get_encrypted_key,
nostr::keys::nostr_connect, nostr::keys::connect_remote_account,
nostr::keys::load_account, nostr::keys::load_account,
nostr::keys::event_to_bech32, nostr::keys::event_to_bech32,
nostr::keys::user_to_bech32, nostr::keys::user_to_bech32,
nostr::keys::to_npub,
nostr::keys::verify_nip05, nostr::keys::verify_nip05,
nostr::metadata::get_activities, nostr::metadata::get_activities,
nostr::metadata::get_current_user_profile, nostr::metadata::get_current_user_profile,
+31 -54
View File
@@ -102,39 +102,36 @@ pub async fn save_account(
#[specta::specta] #[specta::specta]
pub async fn load_account( pub async fn load_account(
npub: &str, npub: &str,
bunker: Option<&str>,
state: State<'_, Nostr>, state: State<'_, Nostr>,
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = &state.client; let client = &state.client;
let keyring = Entry::new(npub, "nostr_secret").unwrap(); let keyring = Entry::new(npub, "nostr_secret").unwrap();
match keyring.get_password() { if let Ok(password) = keyring.get_password() {
Ok(password) => { match bunker {
if password.starts_with("bunker://") { Some(uri) => {
let local_keyring = Entry::new(npub, "bunker_local_account").unwrap(); let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
match local_keyring.get_password() { match NostrConnectURI::parse(uri) {
Ok(local_password) => { Ok(bunker_uri) => {
let secret_key = SecretKey::from_bech32(local_password).unwrap(); match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None).await {
let app_keys = Keys::new(secret_key); Ok(signer) => client.set_signer(Some(signer.into())).await,
let bunker_uri = NostrConnectURI::parse(password).unwrap(); Err(err) => return Err(err.to_string()),
let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None)
.await
.unwrap();
// Update signer
client.set_signer(Some(signer.into())).await;
} }
Err(_) => todo!(),
} }
} else { Err(err) => return Err(err.to_string()),
let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed"); }
let keys = Keys::new(secret_key); }
None => {
let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
let signer = NostrSigner::Keys(keys); let signer = NostrSigner::Keys(keys);
// Update signer // Update signer
client.set_signer(Some(signer)).await; client.set_signer(Some(signer)).await;
} }
}
// Verify signer // Verify signer
let signer = client.signer().await.unwrap(); let signer = client.signer().await.unwrap();
@@ -147,11 +144,10 @@ pub async fn load_account(
// Connect to user's relay (NIP-65) // Connect to user's relay (NIP-65)
// #TODO: Let rust-nostr handle it // #TODO: Let rust-nostr handle it
match client if let Ok(events) = client
.get_events_of(vec![filter], Some(Duration::from_secs(10))) .get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await .await
{ {
Ok(events) => {
if let Some(event) = events.first() { if let Some(event) = events.first() {
let relay_list = nip65::extract_relay_list(event); let relay_list = nip65::extract_relay_list(event);
for item in relay_list.into_iter() { for item in relay_list.into_iter() {
@@ -179,8 +175,6 @@ pub async fn load_account(
client.connect_relay(relay_url).await.unwrap_or_default(); client.connect_relay(relay_url).await.unwrap_or_default();
} }
} }
}
Err(_) => todo!(),
}; };
// Run notification service // Run notification service
@@ -218,42 +212,34 @@ pub async fn load_account(
}); });
Ok(true) Ok(true)
} } else {
Err(err) => Err(err.to_string()), Err("Key not found.".into())
} }
} }
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub async fn nostr_connect( pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
npub: &str,
uri: &str,
state: State<'_, Nostr>,
) -> Result<String, String> {
let client = &state.client; let client = &state.client;
let local_key = Keys::generate();
match NostrConnectURI::parse(uri) { match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => { Ok(bunker_uri) => {
match Nip46Signer::new( let app_keys = Keys::generate();
bunker_uri, let app_secret = app_keys.secret_key().unwrap().to_string();
local_key.clone(),
Duration::from_secs(120), // Get remote user
None, let remote_user = bunker_uri.signer_public_key().unwrap();
) let remote_npub = remote_user.to_bech32().unwrap();
.await
{ match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await {
Ok(signer) => { Ok(signer) => {
let local_secret = local_key.secret_key().unwrap().to_bech32().unwrap(); let keyring = Entry::new(&remote_npub, "nostr_secret").unwrap();
let secret_keyring = Entry::new(&npub, "nostr_secret").unwrap(); let _ = keyring.set_password(&app_secret);
let account_keyring = Entry::new(&npub, "bunker_local_account").unwrap();
let _ = secret_keyring.set_password(uri);
let _ = account_keyring.set_password(&local_secret);
// Update signer // Update signer
let _ = client.set_signer(Some(signer.into())).await; let _ = client.set_signer(Some(signer.into())).await;
Ok(npub.into()) Ok(remote_npub.into())
} }
Err(err) => Err(err.to_string()), Err(err) => Err(err.to_string()),
} }
@@ -299,15 +285,6 @@ pub fn user_to_bech32(key: &str, relays: Vec<String>) -> Result<String, ()> {
Ok(profile.to_bech32().unwrap()) Ok(profile.to_bech32().unwrap())
} }
#[tauri::command]
#[specta::specta]
pub fn to_npub(hex: &str) -> Result<String, ()> {
let public_key = PublicKey::from_str(hex).unwrap();
let npub = Nip19::Pubkey(public_key);
Ok(npub.to_bech32().unwrap())
}
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> { pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "Lume", "productName": "Lume",
"version": "4.0.6", "version": "4.0.8",
"identifier": "nu.lume.Lume", "identifier": "nu.lume.Lume",
"build": { "build": {
"beforeBuildCommand": "pnpm desktop:build", "beforeBuildCommand": "pnpm desktop:build",