diff --git a/README.md b/README.md index e7b3959..1b8d4f2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Then copy the contents of `./out` to your favorite static content host. ## Future Features -- [ ] Two-way collaboration +- [X] Two-way collaboration - [ ] File sharing - [ ] FAQ Page - [ ] Themes and Light/Dark support diff --git a/global.d.ts b/global.d.ts index 9252029..575a0f9 100644 --- a/global.d.ts +++ b/global.d.ts @@ -29,7 +29,7 @@ declare module "nostr-tools" { message: string sig: string tags: [[string, string]] - }) => Promise + }) => void filter: Record[] }) => { unsub: () => void diff --git a/package.json b/package.json index 8828564..987ff57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sendstr-web", - "version": "1.0.0", + "version": "1.1.0", "description": "", "main": "index.js", "scripts": { diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts new file mode 100644 index 0000000..02dcd16 --- /dev/null +++ b/src/lib/nostr.ts @@ -0,0 +1,68 @@ +import { getRelays } from "./localStorage" +import { NostrEventType, NostrKeysType, NostrPoolType } from "../types" + +export const getLatestEvent = (events: Record) => + Object.entries(events).reduce((acc, x) => { + if (acc === null) return x[1] + if (new Date(acc.created_at) < new Date(x[1].created_at)) return x[1] + return acc + }, null as NostrEventType | null) + +export const getReceivePeerKey = (events: Record) => + Object.entries(events).reduce((acc, x) => { + if (acc === null) return x[1] + if (new Date(acc.created_at) > new Date(x[1].created_at)) return x[1] + return acc + }, null as NostrEventType | null)?.pubkey + +export const subscribe = async ( + keys: NostrKeysType, + peerKey: string, + cb: (event: NostrEventType) => void, +) => { + const { decrypt } = await import("nostr-tools/nip04") + const { relayPool } = await import("nostr-tools") + const pool = relayPool() + pool.setPrivateKey(keys.priv) + const relays = getRelays() + relays.forEach((relay) => relay.enabled && pool.addRelay(relay.url, { read: true, write: true })) + const sub = pool.sub({ + cb: (event: NostrEventType) => { + try { + const p = event.tags.find(([tag]) => tag === "p") || ["p", ""] + const pubkey = event.pubkey === keys.pub ? p[1] : event.pubkey + const message = decrypt(keys.priv, pubkey, event.content) + cb({ ...event, message }) + } catch (e) { + console.warn(e) + } + }, + filter: [{ "#p": [keys.pub, peerKey] }, { authors: [keys.pub, peerKey] }], + }) + return { sub, pool } +} + +type SendEncryptedMessage = { + pool: NostrPoolType | null + priv: string + pub: string + peerKey: string + message: string +} + +export const sendEncryptedMessage = async ({ + pool, + priv, + pub, + peerKey, + message, +}: SendEncryptedMessage) => { + const { encrypt } = await import("nostr-tools/nip04") + return pool?.publish({ + pubkey: pub, + created_at: Math.round(Date.now() / 1000), + kind: 4, + tags: [["p", peerKey]], + content: encrypt(priv, peerKey, message), + }) +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c4af78b..bff7d05 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,70 +1,57 @@ import "../styles/global.css" import { AppProps } from "next/app" import { useEffect, useState } from "react" -import { getRelays } from "../lib/localStorage" -import { NostrEventType, NostrType } from "../types" +// import { getRelays } from "../lib/localStorage" +// import { NostrEventType, NostrType } from "../types" export default function App({ Component, pageProps }: AppProps) { - const [events, setEvents] = useState<{ [k: string]: NostrEventType }>({}) - const [nostr, setNostr] = useState({ - priv: "", - pub: "", - pool: null, - sub: null, - }) + // const [events, setEvents] = useState<{ [k: string]: NostrEventType }>({}) + // const [nostr, setNostr] = useState({ + // priv: "", + // pub: "", + // pool: null, + // sub: null, + // }) + const [keys, setKeys] = useState<{ + pub: string + priv: string + } | null>(null) - const updateEvents = async (pub: string, priv: string, event: NostrEventType) => { - const { decrypt } = await import("nostr-tools/nip04") - try { - const p = event.tags.find(([tag]) => tag === "p") || ["p", ""] - const pubkey = event.pubkey === pub ? p[1] : event.pubkey - const message = decrypt(priv, pubkey, event.content) - setEvents({ - ...events, - ...{ - [event.id]: { - ...event, - message, - }, - }, - }) - } catch (e) { - console.warn(e) - } - } + // const updateEvents = async (pub: string, priv: string, event: NostrEventType) => { + // const { decrypt } = await import("nostr-tools/nip04") + // try { + // const p = event.tags.find(([tag]) => tag === "p") || ["p", ""] + // const pubkey = event.pubkey === pub ? p[1] : event.pubkey + // const message = decrypt(priv, pubkey, event.content) + // setEvents({ + // ...events, + // ...{ + // [event.id]: { + // ...event, + // message, + // }, + // }, + // }) + // } catch (e) { + // console.warn(e) + // } + // } useEffect(() => { - let sub: { - unsub: () => void - } void (async () => { - const { generatePrivateKey, relayPool, getPublicKey } = await import("nostr-tools") + const { generatePrivateKey, getPublicKey } = await import("nostr-tools") const priv = generatePrivateKey() const pub = getPublicKey(priv) - const pool = relayPool() - pool.setPrivateKey(priv) - const relays = getRelays() - relays.forEach( - (relay) => relay.enabled && pool.addRelay(relay.url, { read: true, write: true }), - ) - sub = pool.sub({ - cb: (event: NostrEventType) => updateEvents(pub, priv, event), - filter: [{ "#p": [pub] }], - }) - setNostr({ priv, pub, pool, sub }) + setKeys({ priv, pub }) })() - - return () => { - sub.unsub() - } }, []) - const getLatestEvent = (events: Record) => - Object.entries(events).reduce((acc, x) => { - if (acc === null) return x[1] - if (new Date(acc.created_at) < new Date(x[1].created_at)) return x[1] - return acc - }, null as NostrEventType | null) + // const getLatestEvent = (events: Record) => + // Object.entries(events).reduce((acc, x) => { + // if (acc === null) return x[1] + // if (new Date(acc.created_at) < new Date(x[1].created_at)) return x[1] + // return acc + // }, null as NostrEventType | null) - return + return } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5cacda5..ccb6eb9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,14 +6,13 @@ import { Button } from "../components/button" import { Card } from "../components/card" import { SendView } from "../views/send" import { ReceiveView } from "../views/receive" -import { NostrEventType, NostrType } from "../types" +import { NostrKeysType } from "../types" type HomeProps = { - nostr: NostrType - event: NostrEventType + keys: NostrKeysType } -export default function Home({ nostr, event }: HomeProps) { +export default function Home({ keys }: HomeProps) { const [clientType, setClientType] = useState("") const LandingView = () => ( @@ -46,9 +45,9 @@ export default function Home({ nostr, event }: HomeProps) { const Page = () => { switch (true) { case clientType === "send": - return + return case clientType === "receive": - return + return default: return } diff --git a/src/types.ts b/src/types.ts index f2865c6..81fdf0e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -export type NostrSub = ({ +export type NostrSubType = ({ cb, filter, }: { @@ -11,13 +11,13 @@ export type NostrSub = ({ message: string sig: string tags: [[string, string]] - }) => Promise + }) => void filter: Record[] }) => { unsub: () => void } -export type NostrPublish = ({ +export type NostrPublishType = ({ pubkey, created_at, kind, @@ -31,17 +31,17 @@ export type NostrPublish = ({ content: string }) => Promise -export type NostrPool = { +export type NostrPoolType = { setPrivateKey: (priv: string) => void addRelay: (url: string, { read, write }: { read: boolean; write: boolean }) => void - sub: NostrSub - publish: NostrPublish + sub: NostrSubType + publish: NostrPublishType } export type NostrType = { priv: string pub: string - pool: NostrPool | null + pool: NostrPoolType | null sub: { unsub: () => void } | null @@ -57,3 +57,8 @@ export type NostrEventType = { sig: string tags: [[string, string]] } + +export type NostrKeysType = { + pub: string + priv: string +} diff --git a/src/views/receive/index.tsx b/src/views/receive/index.tsx index 80a4e7a..5e61476 100644 --- a/src/views/receive/index.tsx +++ b/src/views/receive/index.tsx @@ -1,32 +1,80 @@ import { QRCodeSVG } from "qrcode.react" +import { useEffect, useRef, useState } from "react" import Toastify from "toastify-js" import { Button } from "../../components/button" import { Card } from "../../components/card" -import { NostrEventType, NostrType } from "../../types" +import { getLatestEvent, getReceivePeerKey, sendEncryptedMessage, subscribe } from "../../lib/nostr" +import { debounce } from "../../lib/utils" +import { NostrEventType, NostrKeysType, NostrType } from "../../types" -const Message = ({ event }: { event: NostrEventType }) => { +type MessageProps = { + message: string + onChange: (x: string) => void +} + +const Message = ({ message, onChange }: MessageProps) => { return ( -

- {event.message} -

+
+
+