Compare commits

..
10 Commits
23 changed files with 1553 additions and 1783 deletions
+16 -16
View File
@@ -9,8 +9,8 @@
"preview": "vite preview"
},
"dependencies": {
"@lume/system": "workspace:^",
"@lume/icons": "workspace:^",
"@lume/system": "workspace:^",
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@radix-ui/react-avatar": "^1.0.4",
@@ -22,42 +22,42 @@
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/query-sync-storage-persister": "^5.36.0",
"@tanstack/react-query": "^5.36.0",
"@tanstack/react-query-persist-client": "^5.36.0",
"@tanstack/react-router": "1.32.5",
"i18next": "^23.11.4",
"@tanstack/query-sync-storage-persister": "^5.40.0",
"@tanstack/react-query": "^5.40.0",
"@tanstack/react-query-persist-client": "^5.40.0",
"@tanstack/react-router": "^1.34.5",
"i18next": "^23.11.5",
"i18next-resources-to-backend": "^1.2.1",
"minidenticons": "^4.2.1",
"nanoid": "^5.0.7",
"nostr-tools": "^2.5.2",
"nostr-tools": "^2.6.0",
"react": "^18.3.1",
"react-currency-input-field": "^3.8.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.4",
"react-hook-form": "^7.51.5",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.1",
"react-i18next": "^14.1.2",
"react-string-replace": "^1.1.1",
"slate": "^0.103.0",
"slate-react": "^0.102.0",
"slate-react": "^0.104.0",
"sonner": "^1.4.41",
"use-debounce": "^10.0.0",
"use-debounce": "^10.0.1",
"virtua": "^0.31.0"
},
"devDependencies": {
"@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@tanstack/router-devtools": "^1.32.5",
"@tanstack/router-vite-plugin": "^1.32.2",
"@types/react": "^18.3.2",
"@tanstack/router-devtools": "^1.34.5",
"@tanstack/router-vite-plugin": "^1.34.1",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite": "^5.2.12",
"vite-plugin-top-level-await": "^1.4.1",
"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 { toast } from "sonner";
export const Route = createFileRoute("/auth/new/$account/backup")({
export const Route = createFileRoute("/auth/$account/backup")({
component: Screen,
});
@@ -1,9 +1,8 @@
import { LaurelIcon } from "@lume/icons";
import { NostrQuery } from "@lume/system";
import { NostrAccount, NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui";
import * as Switch from "@radix-ui/react-switch";
import { createFileRoute } from "@tanstack/react-router";
import { requestPermission } from "@tauri-apps/plugin-notification";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -18,28 +17,14 @@ export const Route = createFileRoute("/auth/$account/settings")({
});
function Screen() {
const navigate = Route.useNavigate();
const { account } = Route.useParams();
const { settings } = Route.useRouteContext();
const { account } = Route.useParams();
const { t } = useTranslation();
const [newSettings, setNewSettings] = useState(settings);
const [loading, setLoading] = useState(false);
const toggleNofitication = async () => {
await requestPermission();
setNewSettings((prev) => ({
...prev,
notification: !newSettings.notification,
}));
};
const toggleAutoUpdate = () => {
setNewSettings((prev) => ({
...prev,
autoUpdate: !newSettings.autoUpdate,
}));
};
const navigate = Route.useNavigate();
const toggleEnhancedPrivacy = () => {
setNewSettings((prev) => ({
@@ -69,11 +54,13 @@ function Screen() {
// publish settings
const eventId = await NostrQuery.setSettings(newSettings);
const allAccounts = await NostrAccount.getAccounts();
if (eventId) {
return navigate({
to: "/$account/home",
params: { account },
search: { accounts: [...new Set([account, ...allAccounts])] },
replace: true,
});
}
@@ -100,22 +87,6 @@ function Screen() {
</div>
<div className="flex flex-col gap-5">
<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-1">
<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.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-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-1">
<h3 className="font-semibold">Zap</h3>
@@ -186,7 +142,7 @@ function Screen() {
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"
>
{t("global.continue")}
{loading ? <Spinner /> : t("global.continue")}
</button>
</div>
</div>
@@ -45,7 +45,7 @@ function Screen() {
if (eventId) {
navigate({
to: "/auth/new/$account/backup",
to: "/auth/$account/backup",
params: { account: account.npub },
replace: true,
});
@@ -23,12 +23,12 @@ function Screen() {
try {
setLoading(true);
const npub = await NostrAccount.connectRemoteAccount(uri);
const remoteAccount = await NostrAccount.connectRemoteAccount(uri);
if (npub) {
navigate({
if (remoteAccount?.length) {
return navigate({
to: "/auth/$account/settings",
params: { account: npub },
params: { account: remoteAccount },
replace: true,
});
}
+8
View File
@@ -34,6 +34,8 @@ function Screen() {
className="h-auto w-full aspect-square rounded-lg shadow-md transform"
controls
muted
preload="none"
poster="/poster_1.jpeg"
>
<source
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"
controls
muted
preload="none"
poster="/poster_2.jpeg"
>
<source
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"
controls
muted
preload="none"
poster="/poster_3.jpeg"
>
<source
src="https://video.nostr.build/927abbfde2097e470ac751181b1db456b7e4b9149550408efff1a966a7ffb9a8.mp4"
@@ -97,6 +103,8 @@ function Screen() {
className="h-auto w-full rounded-lg shadow-md transform"
controls
muted
preload="none"
poster="/poster_4.jpeg"
>
<source
src="https://video.nostr.build/513de4824b6abaf7e9698c1dad2f68096574356848c0c200bc8cb8074df29410.mp4"
+1 -1
View File
@@ -13,7 +13,7 @@
"@astrojs/check": "^0.5.10",
"@astrojs/tailwind": "^5.1.0",
"@fontsource/alice": "^5.0.13",
"astro": "^4.8.3",
"astro": "^4.9.2",
"astro-seo-meta": "^4.1.1",
"astro-seo-schema": "^4.0.2",
"schema-dts": "^1.1.2",
+1 -1
View File
@@ -20,7 +20,7 @@
"node": ">=18"
},
"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-clipboard-manager": "2.1.0-beta.1",
"@tauri-apps/plugin-dialog": "2.0.0-beta.3",
+1 -1
View File
@@ -8,7 +8,7 @@
},
"devDependencies": {
"@lume/tsconfig": "workspace:*",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"typescript": "^5.4.5"
}
}
+2 -2
View File
@@ -5,13 +5,13 @@
"main": "./src/index.ts",
"dependencies": {
"@lume/utils": "workspace:^",
"@tanstack/react-query": "^5.36.0",
"@tanstack/react-query": "^5.40.0",
"react": "^18.3.1"
},
"devDependencies": {
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"typescript": "^5.4.5"
}
}
+19 -12
View File
@@ -1,5 +1,5 @@
import { Metadata } from "@lume/types";
import { commands } from "./commands";
import { Result, commands } from "./commands";
export class NostrAccount {
static async getAccounts() {
@@ -13,7 +13,14 @@ export class NostrAccount {
}
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") {
return query.data;
@@ -62,19 +69,19 @@ export class NostrAccount {
}
static async connectRemoteAccount(uri: string) {
const remoteKey = uri.replace("bunker://", "").split("?")[0];
const npub = await commands.toNpub(remoteKey);
const connect = await commands.connectRemoteAccount(uri);
if (npub.status === "ok") {
const connect = await commands.nostrConnect(npub.data, uri);
if (connect.status === "ok") {
const npub = connect.data;
const parsed = new URL(uri);
parsed.searchParams.delete("secret");
if (connect.status === "ok") {
return connect.data;
} else {
throw new Error(connect.error);
}
// save connection string
localStorage.setItem(`${npub}_bunker`, parsed.toString());
return npub;
} else {
throw new Error(npub.error);
throw new Error(connect.error);
}
}
+385 -579
View File
@@ -1,598 +1,404 @@
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
export const commands = {
async getRelays() : Promise<Result<Relays, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_relays") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async connectRelay(relay: string) : Promise<Result<boolean, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("connect_relay", { relay }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async removeRelay(relay: string) : Promise<Result<boolean, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("remove_relay", { relay }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getAccounts() : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_accounts") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async createAccount() : Promise<Result<Account, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("create_account") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async saveAccount(nsec: string, password: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("save_account", { nsec, password }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEncryptedKey(npub: string, password: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_encrypted_key", { npub, password }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async connectRemoteAccount(uri: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async loadAccount(npub: string, bunker: string | null) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("load_account", { npub, bunker }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async eventToBech32(id: string, relays: string[]) : Promise<Result<string, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("event_to_bech32", { id, relays }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async userToBech32(key: string, relays: string[]) : Promise<Result<string, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("user_to_bech32", { key, relays }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async verifyNip05(key: string, nip05: string) : Promise<Result<boolean, string>> {
try {
return { 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) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getCurrentUserProfile() : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_current_user_profile") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getProfile(id: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getContactList() : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_contact_list") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setContactList(pubkeys: string[]) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("set_contact_list", { pubkeys }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async createProfile(name: string, displayName: string, about: string, picture: string, banner: string, nip05: string, lud16: string, website: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("create_profile", { name, displayName, about, picture, banner, nip05, lud16, website }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async follow(id: string, alias: string | null) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("follow", { id, alias }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async unfollow(id: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("unfollow", { id }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getNstore(key: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_nstore", { key }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setNstore(key: string, content: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("set_nstore", { key, content }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setNwc(uri: string) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("set_nwc", { uri }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async loadNwc() : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("load_nwc") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getBalance() : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_balance") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async zapProfile(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async zapEvent(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async friendToFriend(npub: string) : Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("friend_to_friend", { npub }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEvent(id: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_event", { id }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getReplies(id: string) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_replies", { id }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEventsBy(publicKey: string, asOf: string | null) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getLocalEvents(pubkeys: string[], until: string | null) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_local_events", { pubkeys, until }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getGlobalEvents(until: string | null) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getHashtagEvents(hashtags: string[], until: string | null) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async publish(content: string, tags: string[][]) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("publish", { content, tags }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async repost(raw: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("repost", { raw }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async showInFolder(path: string) : Promise<void> {
await TAURI_INVOKE("show_in_folder", { path });
},
async createColumn(label: string, x: number, y: number, width: number, height: number, url: string) : Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("create_column", { label, x, y, width, height, url }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async closeColumn(label: string) : Promise<Result<boolean, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("close_column", { label }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async repositionColumn(label: string, x: number, y: number) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("reposition_column", { label, x, y }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async resizeColumn(label: string, width: number, height: number) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("resize_column", { label, width, height }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async openWindow(label: string, title: string, url: string, width: number, height: number) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("open_window", { label, title, url, width, height }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setBadge(count: number) : Promise<void> {
await TAURI_INVOKE("set_badge", { count });
}
}
export const commands = {
async getRelays(): Promise<Result<Relays, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_relays") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async connectRelay(relay: string): Promise<Result<boolean, null>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("connect_relay", { relay }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async removeRelay(relay: string): Promise<Result<boolean, null>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("remove_relay", { relay }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getAccounts(): Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_accounts") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async createAccount(): Promise<Result<Account, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("create_account") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async saveAccount(
nsec: string,
password: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("save_account", { nsec, password }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEncryptedKey(
npub: string,
password: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_encrypted_key", { npub, password }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async nostrConnect(
npub: string,
uri: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("nostr_connect", { npub, uri }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async loadAccount(npub: string): Promise<Result<boolean, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("load_account", { npub }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async eventToBech32(
id: string,
relays: string[],
): Promise<Result<string, null>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("event_to_bech32", { id, relays }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async userToBech32(
key: string,
relays: string[],
): Promise<Result<string, null>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("user_to_bech32", { key, relays }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async toNpub(hex: string): Promise<Result<string, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("to_npub", { hex }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async verifyNip05(
key: string,
nip05: string,
): Promise<Result<boolean, string>> {
try {
return {
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) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getCurrentUserProfile(): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_current_user_profile"),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getProfile(id: string): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getContactList(): Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_contact_list") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setContactList(pubkeys: string[]): Promise<Result<boolean, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("set_contact_list", { pubkeys }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async createProfile(
name: string,
displayName: string,
about: string,
picture: string,
banner: string,
nip05: string,
lud16: string,
website: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("create_profile", {
name,
displayName,
about,
picture,
banner,
nip05,
lud16,
website,
}),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async follow(
id: string,
alias: string | null,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("follow", { id, alias }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async unfollow(id: string): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("unfollow", { id }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getNstore(key: string): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_nstore", { key }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setNstore(
key: string,
content: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("set_nstore", { key, content }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setNwc(uri: string): Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("set_nwc", { uri }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async loadNwc(): Promise<Result<boolean, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("load_nwc") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getBalance(): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_balance") };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async zapProfile(
id: string,
amount: string,
message: string,
): Promise<Result<boolean, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("zap_profile", { id, amount, message }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async zapEvent(
id: string,
amount: string,
message: string,
): Promise<Result<boolean, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("zap_event", { id, amount, message }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async friendToFriend(npub: string): Promise<Result<boolean, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("friend_to_friend", { npub }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEvent(id: string): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_event", { id }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getReplies(id: string): Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_replies", { id }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEventsBy(
publicKey: string,
asOf: string | null,
): Promise<Result<string[], string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getLocalEvents(
pubkeys: string[],
until: string | null,
): Promise<Result<string[], string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_local_events", { pubkeys, until }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getGlobalEvents(
until: string | null,
): Promise<Result<string[], string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_global_events", { until }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getHashtagEvents(
hashtags: string[],
until: string | null,
): Promise<Result<string[], string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async publish(
content: string,
tags: string[][],
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("publish", { content, tags }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async repost(raw: string): Promise<Result<string, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("repost", { raw }) };
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async showInFolder(path: string): Promise<void> {
await TAURI_INVOKE("show_in_folder", { path });
},
async createColumn(
label: string,
x: number,
y: number,
width: number,
height: number,
url: string,
): Promise<Result<string, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("create_column", {
label,
x,
y,
width,
height,
url,
}),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async closeColumn(label: string): Promise<Result<boolean, null>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("close_column", { label }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async repositionColumn(
label: string,
x: number,
y: number,
): Promise<Result<null, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("reposition_column", { label, x, y }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async resizeColumn(
label: string,
width: number,
height: number,
): Promise<Result<null, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("resize_column", { label, width, height }),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async openWindow(
label: string,
title: string,
url: string,
width: number,
height: number,
): Promise<Result<null, string>> {
try {
return {
status: "ok",
data: await TAURI_INVOKE("open_window", {
label,
title,
url,
width,
height,
}),
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async setBadge(count: number): Promise<void> {
await TAURI_INVOKE("set_badge", { count });
},
};
/** user-defined types **/
export type Account = { npub: string; nsec: string };
export type Relays = {
connected: string[];
read: string[] | null;
write: string[] | null;
both: string[] | null;
};
export type Account = { npub: string; nsec: string }
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
/** tauri-specta globals **/
import { invoke as TAURI_INVOKE } from "@tauri-apps/api/core";
import { invoke as TAURI_INVOKE } from "@tauri-apps/api/core";
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
type __EventObj__<T> = {
listen: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
once: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
emit: T extends null
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
listen: (
cb: TAURI_API_EVENT.EventCallback<T>
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
once: (
cb: TAURI_API_EVENT.EventCallback<T>
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
emit: T extends null
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
};
export type Result<T, E> =
| { status: "ok"; data: T }
| { status: "error"; error: E };
| { status: "ok"; data: T }
| { status: "error"; error: E };
function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>,
mappings: Record<keyof T, string>
) {
return new Proxy(
{} as unknown as {
[K in keyof T]: __EventObj__<T[K]> & {
(handle: __WebviewWindow__): __EventObj__<T[K]>;
};
},
{
get: (_, event) => {
const name = mappings[event as keyof T];
return new Proxy(
{} as unknown as {
[K in keyof T]: __EventObj__<T[K]> & {
(handle: __WebviewWindow__): __EventObj__<T[K]>;
};
},
{
get: (_, event) => {
const name = mappings[event as keyof T];
return new Proxy((() => {}) as any, {
apply: (_, __, [window]: [__WebviewWindow__]) => ({
listen: (arg: any) => window.listen(name, arg),
once: (arg: any) => window.once(name, arg),
emit: (arg: any) => window.emit(name, arg),
}),
get: (_, command: keyof __EventObj__<any>) => {
switch (command) {
case "listen":
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
case "once":
return (arg: any) => TAURI_API_EVENT.once(name, arg);
case "emit":
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
}
},
});
},
},
);
return new Proxy((() => {}) as any, {
apply: (_, __, [window]: [__WebviewWindow__]) => ({
listen: (arg: any) => window.listen(name, arg),
once: (arg: any) => window.once(name, arg),
emit: (arg: any) => window.emit(name, arg),
}),
get: (_, command: keyof __EventObj__<any>) => {
switch (command) {
case "listen":
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
case "once":
return (arg: any) => TAURI_API_EVENT.once(name, arg);
case "emit":
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
}
},
});
},
}
);
}
+7 -10
View File
@@ -236,31 +236,28 @@ export class NostrQuery {
}
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);
if (query.status === "ok") {
const columns: LumeColumn[] = query.data ? JSON.parse(query.data) : [];
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 columns;
} else {
return [];
return systemColumns;
}
}
static async setColumns(columns: LumeColumn[]) {
const query = await commands.setNstore(
NSTORE_KEYS.columns,
JSON.stringify(columns),
);
const content = JSON.stringify(columns);
const query = await commands.setNstore(NSTORE_KEYS.columns, content);
if (query.status === "ok") {
return query.data;
+1 -1
View File
@@ -14,7 +14,7 @@
"@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5"
}
+4 -4
View File
@@ -8,21 +8,21 @@
"access": "public"
},
"dependencies": {
"@tanstack/react-query": "^5.36.0",
"@tanstack/react-query": "^5.40.0",
"bitcoin-units": "^1.0.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"light-bolt11-decoder": "^3.1.1",
"nostr-tools": "^2.5.2",
"nostr-tools": "^2.6.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"slate": "^0.103.0",
"slate-react": "^0.102.0"
"slate-react": "^0.104.0"
},
"devDependencies": {
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"tailwind-merge": "^2.3.0",
"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::save_account,
nostr::keys::get_encrypted_key,
nostr::keys::nostr_connect,
nostr::keys::connect_remote_account,
nostr::keys::load_account,
nostr::keys::event_to_bech32,
nostr::keys::user_to_bech32,
nostr::keys::to_npub,
nostr::keys::verify_nip05,
nostr::metadata::get_activities,
nostr::metadata::get_current_user_profile,
+103 -126
View File
@@ -102,158 +102,144 @@ pub async fn save_account(
#[specta::specta]
pub async fn load_account(
npub: &str,
bunker: Option<&str>,
state: State<'_, Nostr>,
app: tauri::AppHandle,
) -> Result<bool, String> {
let client = &state.client;
let keyring = Entry::new(npub, "nostr_secret").unwrap();
match keyring.get_password() {
Ok(password) => {
if password.starts_with("bunker://") {
let local_keyring = Entry::new(npub, "bunker_local_account").unwrap();
if let Ok(password) = keyring.get_password() {
match bunker {
Some(uri) => {
let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
match local_keyring.get_password() {
Ok(local_password) => {
let secret_key = SecretKey::from_bech32(local_password).unwrap();
let app_keys = Keys::new(secret_key);
let bunker_uri = NostrConnectURI::parse(password).unwrap();
let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None)
.await
.unwrap();
// Update signer
client.set_signer(Some(signer.into())).await;
match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => {
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None).await {
Ok(signer) => client.set_signer(Some(signer.into())).await,
Err(err) => return Err(err.to_string()),
}
}
Err(_) => todo!(),
Err(err) => return Err(err.to_string()),
}
} else {
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);
// Update signer
client.set_signer(Some(signer)).await;
}
}
// Verify signer
let signer = client.signer().await.unwrap();
let public_key = signer.public_key().await.unwrap();
// Verify signer
let signer = client.signer().await.unwrap();
let public_key = signer.public_key().await.unwrap();
let filter = Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1);
let filter = Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1);
// Connect to user's relay (NIP-65)
// #TODO: Let rust-nostr handle it
match client
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await
{
Ok(events) => {
if let Some(event) = events.first() {
let relay_list = nip65::extract_relay_list(event);
for item in relay_list.into_iter() {
println!("connecting to relay: {} - {:?}", item.0, item.1);
// Connect to user's relay (NIP-65)
// #TODO: Let rust-nostr handle it
if let Ok(events) = client
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await
{
if let Some(event) = events.first() {
let relay_list = nip65::extract_relay_list(event);
for item in relay_list.into_iter() {
println!("connecting to relay: {} - {:?}", item.0, item.1);
let relay_url = item.0.to_string();
let opts = match item.1 {
Some(val) => {
if val == &RelayMetadata::Read {
RelayOptions::new().read(true).write(false)
} else {
RelayOptions::new().write(true).read(false)
}
}
None => RelayOptions::new(),
};
// Add relay to relay pool
let _ = client
.add_relay_with_opts(relay_url.clone(), opts)
.await
.unwrap_or_default();
// Connect relay
client.connect_relay(relay_url).await.unwrap_or_default();
}
}
}
Err(_) => todo!(),
};
// Run notification service
tauri::async_runtime::spawn(async move {
let window = app.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let subscription = Filter::new()
.pubkey(public_key)
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
.since(Timestamp::now());
let activity_id = SubscriptionId::new("activity");
// Create a subscription for activity
client
.subscribe_with_id(activity_id.clone(), vec![subscription], None)
.await;
// Handle notifications
let _ = client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Event {
subscription_id,
event,
..
} = notification
{
if subscription_id == activity_id {
let _ = app.emit("activity", event.as_json());
let relay_url = item.0.to_string();
let opts = match item.1 {
Some(val) => {
if val == &RelayMetadata::Read {
RelayOptions::new().read(true).write(false)
} else {
RelayOptions::new().write(true).read(false)
}
}
Ok(false)
})
.await;
});
None => RelayOptions::new(),
};
Ok(true)
}
Err(err) => Err(err.to_string()),
// Add relay to relay pool
let _ = client
.add_relay_with_opts(relay_url.clone(), opts)
.await
.unwrap_or_default();
// Connect relay
client.connect_relay(relay_url).await.unwrap_or_default();
}
}
};
// Run notification service
tauri::async_runtime::spawn(async move {
let window = app.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let subscription = Filter::new()
.pubkey(public_key)
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
.since(Timestamp::now());
let activity_id = SubscriptionId::new("activity");
// Create a subscription for activity
client
.subscribe_with_id(activity_id.clone(), vec![subscription], None)
.await;
// Handle notifications
let _ = client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Event {
subscription_id,
event,
..
} = notification
{
if subscription_id == activity_id {
let _ = app.emit("activity", event.as_json());
}
}
Ok(false)
})
.await;
});
Ok(true)
} else {
Err("Key not found.".into())
}
}
#[tauri::command]
#[specta::specta]
pub async fn nostr_connect(
npub: &str,
uri: &str,
state: State<'_, Nostr>,
) -> Result<String, String> {
pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let local_key = Keys::generate();
match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => {
match Nip46Signer::new(
bunker_uri,
local_key.clone(),
Duration::from_secs(120),
None,
)
.await
{
let app_keys = Keys::generate();
let app_secret = app_keys.secret_key().unwrap().to_string();
// Get remote user
let remote_user = bunker_uri.signer_public_key().unwrap();
let remote_npub = remote_user.to_bech32().unwrap();
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await {
Ok(signer) => {
let local_secret = local_key.secret_key().unwrap().to_bech32().unwrap();
let secret_keyring = Entry::new(&npub, "nostr_secret").unwrap();
let account_keyring = Entry::new(&npub, "bunker_local_account").unwrap();
let _ = secret_keyring.set_password(uri);
let _ = account_keyring.set_password(&local_secret);
let keyring = Entry::new(&remote_npub, "nostr_secret").unwrap();
let _ = keyring.set_password(&app_secret);
// Update signer
let _ = client.set_signer(Some(signer.into())).await;
Ok(npub.into())
Ok(remote_npub.into())
}
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())
}
#[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]
#[specta::specta]
pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> {
+121 -121
View File
@@ -1,123 +1,123 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "Lume",
"version": "4.0.6",
"identifier": "nu.lume.Lume",
"build": {
"beforeBuildCommand": "pnpm desktop:build",
"beforeDevCommand": "pnpm desktop:dev",
"devUrl": "http://localhost:3000",
"frontendDist": "../dist"
},
"app": {
"macOSPrivateApi": true,
"withGlobalTauri": true,
"trayIcon": {
"id": "main_tray",
"iconPath": "./icons/tray.png",
"iconAsTemplate": true
},
"security": {
"assetProtocol": {
"enable": true,
"scope": [
"$APPDATA/*",
"$DATA/*",
"$LOCALDATA/*",
"$DESKTOP/*",
"$DOCUMENT/*",
"$DOWNLOAD/*",
"$HOME/*",
"$PICTURE/*",
"$PUBLIC/*",
"$VIDEO/*",
"$APPCONFIG/*",
"$RESOURCE/*"
]
}
}
},
"bundle": {
"licenseFile": "../LICENSE",
"longDescription": "nostr client for desktop",
"shortDescription": "nostr client",
"targets": "all",
"active": true,
"category": "SocialNetworking",
"resources": ["resources/*", "locales/*"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"linux": {
"appimage": {
"bundleMediaFramework": true,
"files": {}
},
"deb": {
"files": {}
},
"rpm": {
"epoch": 0,
"files": {},
"release": "1"
}
},
"macOS": {
"dmg": {
"appPosition": {
"x": 180,
"y": 170
},
"applicationFolderPosition": {
"x": 480,
"y": 170
},
"windowSize": {
"height": 400,
"width": 660
}
},
"files": {},
"minimumSystemVersion": "10.15"
},
"windows": {
"allowDowngrades": true,
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"nsis": null,
"timestampUrl": null,
"tsp": false,
"webviewFixedRuntimePath": null,
"webviewInstallMode": {
"silent": true,
"type": "downloadBootstrapper"
},
"wix": null
},
"fileAssociations": [
{
"name": "bech32",
"description": "Nostr Bech32",
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
"role": "Viewer"
}
]
},
"plugins": {
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
"windows": {
"installMode": "quiet"
},
"endpoints": [
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
]
}
}
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "Lume",
"version": "4.0.8",
"identifier": "nu.lume.Lume",
"build": {
"beforeBuildCommand": "pnpm desktop:build",
"beforeDevCommand": "pnpm desktop:dev",
"devUrl": "http://localhost:3000",
"frontendDist": "../dist"
},
"app": {
"macOSPrivateApi": true,
"withGlobalTauri": true,
"trayIcon": {
"id": "main_tray",
"iconPath": "./icons/tray.png",
"iconAsTemplate": true
},
"security": {
"assetProtocol": {
"enable": true,
"scope": [
"$APPDATA/*",
"$DATA/*",
"$LOCALDATA/*",
"$DESKTOP/*",
"$DOCUMENT/*",
"$DOWNLOAD/*",
"$HOME/*",
"$PICTURE/*",
"$PUBLIC/*",
"$VIDEO/*",
"$APPCONFIG/*",
"$RESOURCE/*"
]
}
}
},
"bundle": {
"licenseFile": "../LICENSE",
"longDescription": "nostr client for desktop",
"shortDescription": "nostr client",
"targets": "all",
"active": true,
"category": "SocialNetworking",
"resources": ["resources/*", "locales/*"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"linux": {
"appimage": {
"bundleMediaFramework": true,
"files": {}
},
"deb": {
"files": {}
},
"rpm": {
"epoch": 0,
"files": {},
"release": "1"
}
},
"macOS": {
"dmg": {
"appPosition": {
"x": 180,
"y": 170
},
"applicationFolderPosition": {
"x": 480,
"y": 170
},
"windowSize": {
"height": 400,
"width": 660
}
},
"files": {},
"minimumSystemVersion": "10.15"
},
"windows": {
"allowDowngrades": true,
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"nsis": null,
"timestampUrl": null,
"tsp": false,
"webviewFixedRuntimePath": null,
"webviewInstallMode": {
"silent": true,
"type": "downloadBootstrapper"
},
"wix": null
},
"fileAssociations": [
{
"name": "bech32",
"description": "Nostr Bech32",
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
"role": "Viewer"
}
]
},
"plugins": {
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
"windows": {
"installMode": "quiet"
},
"endpoints": [
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
]
}
}
}