wip: i'm tired

This commit is contained in:
2024-11-05 10:19:51 +07:00
parent 605b847a66
commit 8525ac3fdd
9 changed files with 973 additions and 1009 deletions

66
src-tauri/Cargo.lock generated
View File

@@ -2,39 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "COOP"
version = "0.2.0"
dependencies = [
"border",
"futures",
"itertools 0.13.0",
"keyring",
"keyring-search",
"nostr-connect",
"nostr-sdk",
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-decorum",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-notification",
"tauri-plugin-os",
"tauri-plugin-prevent-default",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-store",
"tauri-plugin-updater",
"tauri-specta",
"tokio",
"tracing-subscriber",
]
[[package]]
name = "Inflector"
version = "0.11.4"
@@ -1006,6 +973,39 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "coop"
version = "0.2.0"
dependencies = [
"border",
"futures",
"itertools 0.13.0",
"keyring",
"keyring-search",
"nostr-connect",
"nostr-sdk",
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-decorum",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-notification",
"tauri-plugin-os",
"tauri-plugin-prevent-default",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-store",
"tauri-plugin-updater",
"tauri-specta",
"tokio",
"tracing-subscriber",
]
[[package]]
name = "core-foundation"
version = "0.9.4"

View File

@@ -1,5 +1,5 @@
[package]
name = "COOP"
name = "coop"
version = "0.2.0"
description = "direct message client for desktop"
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]

View File

@@ -328,6 +328,13 @@ pub async fn login(
for url in urls.iter() {
let _ = client.add_relay(url).await;
let _ = client.connect_relay(url).await;
// Workaround for https://github.com/rust-nostr/nostr/issues/509
// TODO: remove
let filter = Filter::new().kind(Kind::TextNote).limit(0);
let _ = client
.fetch_events_from(vec![url], vec![filter], Some(Duration::from_secs(3)))
.await;
}
let mut inbox_relays = state.inbox_relays.write().await;
@@ -341,34 +348,32 @@ pub async fn login(
let inbox_relays = state.inbox_relays.read().await;
let relays = inbox_relays.get(&public_key).unwrap().to_owned();
let sub_id = SubscriptionId::new(SUBSCRIPTION_ID);
let subscription_id = SubscriptionId::new(SUBSCRIPTION_ID);
// Create a filter for getting new message
let new_message = Filter::new()
.kind(Kind::GiftWrap)
.pubkey(public_key)
.limit(0);
// Subscribe for new message
if let Err(e) = client
.subscribe_with_id_to(&relays, sub_id, vec![new_message], None)
.subscribe_with_id_to(&relays, subscription_id, vec![new_message], None)
.await
{
println!("Subscribe error: {}", e)
};
let filter = Filter::new()
.kind(Kind::GiftWrap)
.pubkey(public_key)
.limit(200);
// Create a filter for getting all gift wrapped events send to current user
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let mut rx = client
.stream_events_from(&relays, vec![filter], Some(Duration::from_secs(40)))
.await
.unwrap();
let opts = SubscribeAutoCloseOptions::default().filter(
FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(10)),
);
while let Some(event) = rx.next().await {
println!("Event: {}", event.as_json());
if let Ok(output) = client.subscribe_to(&relays, vec![filter], Some(opts)).await {
println!("Output: {:?}", output)
}
// handle.emit("synchronized", ()).unwrap();
});
Ok(public_key.to_hex())

View File

@@ -175,6 +175,17 @@ pub async fn connect_inbox_relays(
let _ = client.add_relay(&url).await;
let _ = client.connect_relay(&url).await;
// Workaround for https://github.com/rust-nostr/nostr/issues/509
// TODO: remove
let filter = Filter::new().kind(Kind::TextNote).limit(0);
let _ = client
.fetch_events_from(
vec![url.clone()],
vec![filter],
Some(Duration::from_secs(3)),
)
.await;
relays.push(url)
}
}

View File

@@ -163,10 +163,7 @@ fn main() {
.expect("Error: cannot create database.");
// Config
let opts = Options::new()
.gossip(true)
.automatic_authentication(false)
.max_avg_latency(Duration::from_millis(500));
let opts = Options::new().gossip(true).max_avg_latency(Duration::from_millis(500));
// Setup nostr client
let client = ClientBuilder::default()
@@ -207,6 +204,7 @@ fn main() {
// Connect
client.connect().await;
// Return nostr client
client
});
@@ -271,30 +269,13 @@ fn main() {
let _ = client
.handle_notifications(|notification| async {
#[allow(clippy::collapsible_match)]
if let RelayPoolNotification::Message { message, relay_url, .. } = notification {
if let RelayMessage::Auth { challenge } = message {
match client.auth(challenge, relay_url.clone()).await {
Ok(..) => {
if let Ok(relay) = client.relay(relay_url).await {
if let Err(e) = relay.resubscribe().await {
println!("Resubscribe error: {}", e)
}
// Workaround for https://github.com/rust-nostr/nostr/issues/509
// TODO: remove
let filter = Filter::new().kind(Kind::TextNote).limit(0);
let _ = client.fetch_events(vec![filter], Some(Duration::from_secs(1))).await;
}
}
Err(e) => {
println!("Auth error: {}", e)
}
}
} else if let RelayMessage::Event { event, .. } = message {
if let RelayPoolNotification::Message { message, .. } = notification {
if let RelayMessage::Event { event, subscription_id, .. } = message {
if event.kind == Kind::GiftWrap {
if let Ok(UnwrappedGift { rumor, sender }) =
client.unwrap_gift_wrap(&event).await
{
let subscription_id = subscription_id.to_string();
let mut rumor_clone = rumor.clone();
// Compute event id if not exist
@@ -312,10 +293,11 @@ fn main() {
// Save rumor to database to further query
if let Err(e) = client.database().save_event(&ev).await {
println!("[save event] error: {}", e)
println!("Error: {}", e)
}
// Emit new event to frontend
if subscription_id == SUBSCRIPTION_ID {
// Emit new message to current chat screen
if let Err(e) = handle.emit(
"event",
EventPayload {
@@ -323,7 +305,13 @@ fn main() {
sender: sender.to_hex(),
},
) {
println!("[emit] error: {}", e)
println!("Emit error: {}", e)
}
} else {
// Emit new message to home screen
if let Err(e) = handle.emit("synchronized", ()) {
println!("Emit error: {}", e)
}
}
}
} else if event.kind == Kind::Metadata {

View File

@@ -1,5 +1,6 @@
{
"productName": "COOP",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Coop",
"version": "0.2.0",
"identifier": "su.reya.coop",
"build": {
@@ -44,9 +45,7 @@
"targets": "all",
"active": true,
"category": "SocialNetworking",
"resources": [
"resources/*"
],
"resources": ["resources/*"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",

View File

@@ -186,7 +186,7 @@ function List() {
>
<ScrollArea.Viewport
ref={scrollRef}
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end [&>div]:min-h-full"
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end"
>
<Virtualizer
scrollRef={scrollRef as unknown as RefObject<HTMLElement>}

View File

@@ -10,7 +10,6 @@ import {
X,
} from "@phosphor-icons/react";
import * as Dialog from "@radix-ui/react-dialog";
import * as Progress from "@radix-ui/react-progress";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useQuery } from "@tanstack/react-query";
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
@@ -105,21 +104,9 @@ function ChatList() {
refetchOnWindowFocus: false,
});
const [isSync, setIsSync] = useState(false);
const [progress, setProgress] = useState(0);
useEffect(() => {
const timer = setInterval(
() => setProgress((prev) => (prev <= 100 ? prev + 4 : 100)),
1200,
);
return () => clearInterval(timer);
}, []);
useEffect(() => {
const unlisten = listen("synchronized", async () => {
await queryClient.refetchQueries({ queryKey: ["chats"] });
setIsSync(true);
await queryClient.invalidateQueries({ queryKey: ["chats"] });
});
return () => {
@@ -138,15 +125,12 @@ function ChatList() {
const index = chats.findIndex((item) => item.pubkey === event.pubkey);
if (index === -1) {
await queryClient.setQueryData(
["chats"],
(prevEvents: NostrEvent[]) => {
queryClient.setQueryData(["chats"], (prevEvents: NostrEvent[]) => {
if (!prevEvents) return prevEvents;
if (event.pubkey === account) return;
return [event, ...prevEvents];
},
);
});
} else {
const newEvents = [...chats];
@@ -155,8 +139,9 @@ function ChatList() {
};
queryClient.setQueryData(["chats"], newEvents);
await queryClient.invalidateQueries({ queryKey: ["chats"] });
}
await queryClient.invalidateQueries({ queryKey: ["chats"] });
}
});
@@ -184,7 +169,7 @@ function ChatList() {
</div>
))}
</>
) : isSync && !data?.length ? (
) : !data?.length ? (
<div className="p-2">
<div className="px-2 h-12 w-full rounded-lg bg-black/5 dark:bg-white/5 flex items-center justify-center text-sm">
No chats.
@@ -228,7 +213,6 @@ function ChatList() {
))
)}
</ScrollArea.Viewport>
{!isSync ? <SyncPopup progress={progress} /> : null}
<ScrollArea.Scrollbar
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
orientation="vertical"
@@ -240,29 +224,6 @@ function ChatList() {
);
}
function SyncPopup({ progress }: { progress: number }) {
return (
<div className="absolute bottom-0 w-full h-36 flex flex-col justify-end">
<div className="absolute left-0 bottom-0 w-full h-32 gradient-mask-t-10 bg-white dark:bg-black" />
<div className="relative flex flex-col items-center gap-1.5 p-4">
<Progress.Root
className="relative overflow-hidden bg-black/20 dark:bg-white/20 rounded-full w-full h-1"
style={{
transform: "translateZ(0)",
}}
value={progress}
>
<Progress.Indicator
className="bg-blue-500 size-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
style={{ transform: `translateX(-${100 - progress}%)` }}
/>
</Progress.Root>
<span className="text-center text-xs">Syncing message...</span>
</div>
</div>
);
}
function Compose() {
const [isOpen, setIsOpen] = useState(false);
const [target, setTarget] = useState("");

View File

@@ -1,9 +1,9 @@
import { CoopIcon } from '@/icons/coop'
import { createLazyFileRoute } from '@tanstack/react-router'
import { CoopIcon } from "@/icons/coop";
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute('/$account/_layout/chats/new')({
export const Route = createLazyFileRoute("/$account/_layout/chats/new")({
component: Screen,
})
});
function Screen() {
return (
@@ -13,8 +13,8 @@ function Screen() {
>
<CoopIcon className="size-10 text-neutral-200 dark:text-neutral-800" />
<h1 className="text-center font-bold text-neutral-300 dark:text-neutral-700">
coop on nostr.
let's gathering on nostr.
</h1>
</div>
)
);
}