wip: i'm tired
This commit is contained in:
66
src-tauri/Cargo.lock
generated
66
src-tauri/Cargo.lock
generated
@@ -2,39 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
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]]
|
[[package]]
|
||||||
name = "Inflector"
|
name = "Inflector"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@@ -1006,6 +973,39 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "COOP"
|
name = "coop"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
description = "direct message client for desktop"
|
description = "direct message client for desktop"
|
||||||
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
|
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
|
||||||
|
|||||||
@@ -328,6 +328,13 @@ pub async fn login(
|
|||||||
for url in urls.iter() {
|
for url in urls.iter() {
|
||||||
let _ = client.add_relay(url).await;
|
let _ = client.add_relay(url).await;
|
||||||
let _ = client.connect_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;
|
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 inbox_relays = state.inbox_relays.read().await;
|
||||||
let relays = inbox_relays.get(&public_key).unwrap().to_owned();
|
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()
|
let new_message = Filter::new()
|
||||||
.kind(Kind::GiftWrap)
|
.kind(Kind::GiftWrap)
|
||||||
.pubkey(public_key)
|
.pubkey(public_key)
|
||||||
.limit(0);
|
.limit(0);
|
||||||
|
|
||||||
|
// Subscribe for new message
|
||||||
if let Err(e) = client
|
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
|
.await
|
||||||
{
|
{
|
||||||
println!("Subscribe error: {}", e)
|
println!("Subscribe error: {}", e)
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = Filter::new()
|
// Create a filter for getting all gift wrapped events send to current user
|
||||||
.kind(Kind::GiftWrap)
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
.pubkey(public_key)
|
|
||||||
.limit(200);
|
|
||||||
|
|
||||||
let mut rx = client
|
let opts = SubscribeAutoCloseOptions::default().filter(
|
||||||
.stream_events_from(&relays, vec![filter], Some(Duration::from_secs(40)))
|
FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(10)),
|
||||||
.await
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
while let Some(event) = rx.next().await {
|
if let Ok(output) = client.subscribe_to(&relays, vec![filter], Some(opts)).await {
|
||||||
println!("Event: {}", event.as_json());
|
println!("Output: {:?}", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle.emit("synchronized", ()).unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(public_key.to_hex())
|
Ok(public_key.to_hex())
|
||||||
|
|||||||
@@ -175,6 +175,17 @@ pub async fn connect_inbox_relays(
|
|||||||
let _ = client.add_relay(&url).await;
|
let _ = client.add_relay(&url).await;
|
||||||
let _ = client.connect_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)
|
relays.push(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,10 +163,7 @@ fn main() {
|
|||||||
.expect("Error: cannot create database.");
|
.expect("Error: cannot create database.");
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new().gossip(true).max_avg_latency(Duration::from_millis(500));
|
||||||
.gossip(true)
|
|
||||||
.automatic_authentication(false)
|
|
||||||
.max_avg_latency(Duration::from_millis(500));
|
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
@@ -207,6 +204,7 @@ fn main() {
|
|||||||
// Connect
|
// Connect
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
|
|
||||||
|
// Return nostr client
|
||||||
client
|
client
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -271,30 +269,13 @@ fn main() {
|
|||||||
let _ = client
|
let _ = client
|
||||||
.handle_notifications(|notification| async {
|
.handle_notifications(|notification| async {
|
||||||
#[allow(clippy::collapsible_match)]
|
#[allow(clippy::collapsible_match)]
|
||||||
if let RelayPoolNotification::Message { message, relay_url, .. } = notification {
|
if let RelayPoolNotification::Message { message, .. } = notification {
|
||||||
if let RelayMessage::Auth { challenge } = message {
|
if let RelayMessage::Event { event, subscription_id, .. } = 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 event.kind == Kind::GiftWrap {
|
if event.kind == Kind::GiftWrap {
|
||||||
if let Ok(UnwrappedGift { rumor, sender }) =
|
if let Ok(UnwrappedGift { rumor, sender }) =
|
||||||
client.unwrap_gift_wrap(&event).await
|
client.unwrap_gift_wrap(&event).await
|
||||||
{
|
{
|
||||||
|
let subscription_id = subscription_id.to_string();
|
||||||
let mut rumor_clone = rumor.clone();
|
let mut rumor_clone = rumor.clone();
|
||||||
|
|
||||||
// Compute event id if not exist
|
// Compute event id if not exist
|
||||||
@@ -312,10 +293,11 @@ fn main() {
|
|||||||
|
|
||||||
// Save rumor to database to further query
|
// Save rumor to database to further query
|
||||||
if let Err(e) = client.database().save_event(&ev).await {
|
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(
|
if let Err(e) = handle.emit(
|
||||||
"event",
|
"event",
|
||||||
EventPayload {
|
EventPayload {
|
||||||
@@ -323,7 +305,13 @@ fn main() {
|
|||||||
sender: sender.to_hex(),
|
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 {
|
} else if event.kind == Kind::Metadata {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"productName": "COOP",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
|
"productName": "Coop",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"identifier": "su.reya.coop",
|
"identifier": "su.reya.coop",
|
||||||
"build": {
|
"build": {
|
||||||
@@ -44,9 +45,7 @@
|
|||||||
"targets": "all",
|
"targets": "all",
|
||||||
"active": true,
|
"active": true,
|
||||||
"category": "SocialNetworking",
|
"category": "SocialNetworking",
|
||||||
"resources": [
|
"resources": ["resources/*"],
|
||||||
"resources/*"
|
|
||||||
],
|
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ function List() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={scrollRef}
|
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
|
<Virtualizer
|
||||||
scrollRef={scrollRef as unknown as RefObject<HTMLElement>}
|
scrollRef={scrollRef as unknown as RefObject<HTMLElement>}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
X,
|
X,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
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 * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
||||||
@@ -105,21 +104,9 @@ function ChatList() {
|
|||||||
refetchOnWindowFocus: false,
|
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(() => {
|
useEffect(() => {
|
||||||
const unlisten = listen("synchronized", async () => {
|
const unlisten = listen("synchronized", async () => {
|
||||||
await queryClient.refetchQueries({ queryKey: ["chats"] });
|
await queryClient.invalidateQueries({ queryKey: ["chats"] });
|
||||||
setIsSync(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -138,15 +125,12 @@ function ChatList() {
|
|||||||
const index = chats.findIndex((item) => item.pubkey === event.pubkey);
|
const index = chats.findIndex((item) => item.pubkey === event.pubkey);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
await queryClient.setQueryData(
|
queryClient.setQueryData(["chats"], (prevEvents: NostrEvent[]) => {
|
||||||
["chats"],
|
|
||||||
(prevEvents: NostrEvent[]) => {
|
|
||||||
if (!prevEvents) return prevEvents;
|
if (!prevEvents) return prevEvents;
|
||||||
if (event.pubkey === account) return;
|
if (event.pubkey === account) return;
|
||||||
|
|
||||||
return [event, ...prevEvents];
|
return [event, ...prevEvents];
|
||||||
},
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
const newEvents = [...chats];
|
const newEvents = [...chats];
|
||||||
|
|
||||||
@@ -155,8 +139,9 @@ function ChatList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
queryClient.setQueryData(["chats"], newEvents);
|
queryClient.setQueryData(["chats"], newEvents);
|
||||||
await queryClient.invalidateQueries({ queryKey: ["chats"] });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ["chats"] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,7 +169,7 @@ function ChatList() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : isSync && !data?.length ? (
|
) : !data?.length ? (
|
||||||
<div className="p-2">
|
<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">
|
<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.
|
No chats.
|
||||||
@@ -228,7 +213,6 @@ function ChatList() {
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
{!isSync ? <SyncPopup progress={progress} /> : null}
|
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
orientation="vertical"
|
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() {
|
function Compose() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [target, setTarget] = useState("");
|
const [target, setTarget] = useState("");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CoopIcon } from '@/icons/coop'
|
import { CoopIcon } from "@/icons/coop";
|
||||||
import { createLazyFileRoute } from '@tanstack/react-router'
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/$account/_layout/chats/new')({
|
export const Route = createLazyFileRoute("/$account/_layout/chats/new")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
})
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
return (
|
return (
|
||||||
@@ -13,8 +13,8 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<CoopIcon className="size-10 text-neutral-200 dark:text-neutral-800" />
|
<CoopIcon className="size-10 text-neutral-200 dark:text-neutral-800" />
|
||||||
<h1 className="text-center font-bold text-neutral-300 dark:text-neutral-700">
|
<h1 className="text-center font-bold text-neutral-300 dark:text-neutral-700">
|
||||||
coop on nostr.
|
let's gathering on nostr.
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user