mirror of
https://github.com/aljazceru/sendstr-web.git
synced 2025-12-17 06:24:24 +01:00
feat: two way collab
This commit is contained in:
@@ -17,7 +17,7 @@ Then copy the contents of `./out` to your favorite static content host.
|
|||||||
|
|
||||||
## Future Features
|
## Future Features
|
||||||
|
|
||||||
- [ ] Two-way collaboration
|
- [X] Two-way collaboration
|
||||||
- [ ] File sharing
|
- [ ] File sharing
|
||||||
- [ ] FAQ Page
|
- [ ] FAQ Page
|
||||||
- [ ] Themes and Light/Dark support
|
- [ ] Themes and Light/Dark support
|
||||||
|
|||||||
2
global.d.ts
vendored
2
global.d.ts
vendored
@@ -29,7 +29,7 @@ declare module "nostr-tools" {
|
|||||||
message: string
|
message: string
|
||||||
sig: string
|
sig: string
|
||||||
tags: [[string, string]]
|
tags: [[string, string]]
|
||||||
}) => Promise<void>
|
}) => void
|
||||||
filter: Record<string, string[]>[]
|
filter: Record<string, string[]>[]
|
||||||
}) => {
|
}) => {
|
||||||
unsub: () => void
|
unsub: () => void
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sendstr-web",
|
"name": "sendstr-web",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
68
src/lib/nostr.ts
Normal file
68
src/lib/nostr.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { getRelays } from "./localStorage"
|
||||||
|
import { NostrEventType, NostrKeysType, NostrPoolType } from "../types"
|
||||||
|
|
||||||
|
export const getLatestEvent = (events: Record<string, NostrEventType>) =>
|
||||||
|
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<string, NostrEventType>) =>
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,70 +1,57 @@
|
|||||||
import "../styles/global.css"
|
import "../styles/global.css"
|
||||||
import { AppProps } from "next/app"
|
import { AppProps } from "next/app"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { getRelays } from "../lib/localStorage"
|
// import { getRelays } from "../lib/localStorage"
|
||||||
import { NostrEventType, NostrType } from "../types"
|
// import { NostrEventType, NostrType } from "../types"
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
const [events, setEvents] = useState<{ [k: string]: NostrEventType }>({})
|
// const [events, setEvents] = useState<{ [k: string]: NostrEventType }>({})
|
||||||
const [nostr, setNostr] = useState<NostrType>({
|
// const [nostr, setNostr] = useState<NostrType>({
|
||||||
priv: "",
|
// priv: "",
|
||||||
pub: "",
|
// pub: "",
|
||||||
pool: null,
|
// pool: null,
|
||||||
sub: null,
|
// sub: null,
|
||||||
})
|
// })
|
||||||
|
const [keys, setKeys] = useState<{
|
||||||
|
pub: string
|
||||||
|
priv: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
const updateEvents = async (pub: string, priv: string, event: NostrEventType) => {
|
// const updateEvents = async (pub: string, priv: string, event: NostrEventType) => {
|
||||||
const { decrypt } = await import("nostr-tools/nip04")
|
// const { decrypt } = await import("nostr-tools/nip04")
|
||||||
try {
|
// try {
|
||||||
const p = event.tags.find(([tag]) => tag === "p") || ["p", ""]
|
// const p = event.tags.find(([tag]) => tag === "p") || ["p", ""]
|
||||||
const pubkey = event.pubkey === pub ? p[1] : event.pubkey
|
// const pubkey = event.pubkey === pub ? p[1] : event.pubkey
|
||||||
const message = decrypt(priv, pubkey, event.content)
|
// const message = decrypt(priv, pubkey, event.content)
|
||||||
setEvents({
|
// setEvents({
|
||||||
...events,
|
// ...events,
|
||||||
...{
|
// ...{
|
||||||
[event.id]: {
|
// [event.id]: {
|
||||||
...event,
|
// ...event,
|
||||||
message,
|
// message,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
console.warn(e)
|
// console.warn(e)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let sub: {
|
|
||||||
unsub: () => void
|
|
||||||
}
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const { generatePrivateKey, relayPool, getPublicKey } = await import("nostr-tools")
|
const { generatePrivateKey, getPublicKey } = await import("nostr-tools")
|
||||||
const priv = generatePrivateKey()
|
const priv = generatePrivateKey()
|
||||||
const pub = getPublicKey(priv)
|
const pub = getPublicKey(priv)
|
||||||
const pool = relayPool()
|
setKeys({ priv, pub })
|
||||||
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 })
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
return () => {
|
|
||||||
sub.unsub()
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const getLatestEvent = (events: Record<string, NostrEventType>) =>
|
// const getLatestEvent = (events: Record<string, NostrEventType>) =>
|
||||||
Object.entries(events).reduce((acc, x) => {
|
// Object.entries(events).reduce((acc, x) => {
|
||||||
if (acc === null) return x[1]
|
// if (acc === null) return x[1]
|
||||||
if (new Date(acc.created_at) < new Date(x[1].created_at)) return x[1]
|
// if (new Date(acc.created_at) < new Date(x[1].created_at)) return x[1]
|
||||||
return acc
|
// return acc
|
||||||
}, null as NostrEventType | null)
|
// }, null as NostrEventType | null)
|
||||||
|
|
||||||
return <Component {...{ ...pageProps, nostr, event: getLatestEvent(events) }} />
|
return <Component {...{ ...pageProps, keys }} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ import { Button } from "../components/button"
|
|||||||
import { Card } from "../components/card"
|
import { Card } from "../components/card"
|
||||||
import { SendView } from "../views/send"
|
import { SendView } from "../views/send"
|
||||||
import { ReceiveView } from "../views/receive"
|
import { ReceiveView } from "../views/receive"
|
||||||
import { NostrEventType, NostrType } from "../types"
|
import { NostrKeysType } from "../types"
|
||||||
|
|
||||||
type HomeProps = {
|
type HomeProps = {
|
||||||
nostr: NostrType
|
keys: NostrKeysType
|
||||||
event: NostrEventType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home({ nostr, event }: HomeProps) {
|
export default function Home({ keys }: HomeProps) {
|
||||||
const [clientType, setClientType] = useState("")
|
const [clientType, setClientType] = useState("")
|
||||||
|
|
||||||
const LandingView = () => (
|
const LandingView = () => (
|
||||||
@@ -46,9 +45,9 @@ export default function Home({ nostr, event }: HomeProps) {
|
|||||||
const Page = () => {
|
const Page = () => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case clientType === "send":
|
case clientType === "send":
|
||||||
return <SendView nostr={nostr} />
|
return <SendView keys={keys} />
|
||||||
case clientType === "receive":
|
case clientType === "receive":
|
||||||
return <ReceiveView nostr={nostr} event={event} />
|
return <ReceiveView keys={keys} />
|
||||||
default:
|
default:
|
||||||
return <LandingView />
|
return <LandingView />
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/types.ts
19
src/types.ts
@@ -1,4 +1,4 @@
|
|||||||
export type NostrSub = ({
|
export type NostrSubType = ({
|
||||||
cb,
|
cb,
|
||||||
filter,
|
filter,
|
||||||
}: {
|
}: {
|
||||||
@@ -11,13 +11,13 @@ export type NostrSub = ({
|
|||||||
message: string
|
message: string
|
||||||
sig: string
|
sig: string
|
||||||
tags: [[string, string]]
|
tags: [[string, string]]
|
||||||
}) => Promise<void>
|
}) => void
|
||||||
filter: Record<string, string[]>[]
|
filter: Record<string, string[]>[]
|
||||||
}) => {
|
}) => {
|
||||||
unsub: () => void
|
unsub: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NostrPublish = ({
|
export type NostrPublishType = ({
|
||||||
pubkey,
|
pubkey,
|
||||||
created_at,
|
created_at,
|
||||||
kind,
|
kind,
|
||||||
@@ -31,17 +31,17 @@ export type NostrPublish = ({
|
|||||||
content: string
|
content: string
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
|
|
||||||
export type NostrPool = {
|
export type NostrPoolType = {
|
||||||
setPrivateKey: (priv: string) => void
|
setPrivateKey: (priv: string) => void
|
||||||
addRelay: (url: string, { read, write }: { read: boolean; write: boolean }) => void
|
addRelay: (url: string, { read, write }: { read: boolean; write: boolean }) => void
|
||||||
sub: NostrSub
|
sub: NostrSubType
|
||||||
publish: NostrPublish
|
publish: NostrPublishType
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NostrType = {
|
export type NostrType = {
|
||||||
priv: string
|
priv: string
|
||||||
pub: string
|
pub: string
|
||||||
pool: NostrPool | null
|
pool: NostrPoolType | null
|
||||||
sub: {
|
sub: {
|
||||||
unsub: () => void
|
unsub: () => void
|
||||||
} | null
|
} | null
|
||||||
@@ -57,3 +57,8 @@ export type NostrEventType = {
|
|||||||
sig: string
|
sig: string
|
||||||
tags: [[string, string]]
|
tags: [[string, string]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NostrKeysType = {
|
||||||
|
pub: string
|
||||||
|
priv: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +1,80 @@
|
|||||||
import { QRCodeSVG } from "qrcode.react"
|
import { QRCodeSVG } from "qrcode.react"
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
import Toastify from "toastify-js"
|
import Toastify from "toastify-js"
|
||||||
import { Button } from "../../components/button"
|
import { Button } from "../../components/button"
|
||||||
import { Card } from "../../components/card"
|
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 (
|
return (
|
||||||
<p className="bg-custom-green-dark border-2 border-custom-black rounded w-full p-3 whitespace-pre-wrap">
|
<section className="p-4">
|
||||||
{event.message}
|
<div className="border-0">
|
||||||
</p>
|
<textarea
|
||||||
|
className="bg-custom-green-dark border-2 border-custom-black rounded w-full min-h-[100px] max-h-[700px]"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => onChange(e.currentTarget.value || "")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceiveViewProps = {
|
type ReceiveViewProps = {
|
||||||
nostr: NostrType
|
keys: NostrKeysType
|
||||||
event: NostrEventType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReceiveView = ({ nostr, event }: ReceiveViewProps) => {
|
export const ReceiveView = ({ keys }: ReceiveViewProps) => {
|
||||||
|
const [peerKey, setPeerKey] = useState("")
|
||||||
|
const [message, setMessage] = useState("")
|
||||||
|
const events = useRef<{ [k: string]: NostrEventType } | null>(null)
|
||||||
|
const nostr = useRef<NostrType | null>(null)
|
||||||
|
|
||||||
|
const processEvent = (event: NostrEventType) => {
|
||||||
|
events.current = { ...events.current, ...{ [event.id]: event } }
|
||||||
|
setMessage(getLatestEvent(events?.current)?.message || "")
|
||||||
|
setPeerKey(getReceivePeerKey(events.current) || "")
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void (async () => {
|
||||||
|
const sub = await subscribe(keys, peerKey, processEvent)
|
||||||
|
nostr.current = { ...sub, ...keys }
|
||||||
|
return () => {
|
||||||
|
nostr?.current?.sub?.unsub()
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [peerKey])
|
||||||
|
|
||||||
|
const sendMessage = useRef(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
debounce(async (peerKey: string, message: string) => {
|
||||||
|
if (nostr?.current?.pool) {
|
||||||
|
await sendEncryptedMessage({ ...nostr.current, peerKey, message })
|
||||||
|
}
|
||||||
|
}, 500),
|
||||||
|
).current
|
||||||
|
|
||||||
|
const onMessageChange = (message: string) => {
|
||||||
|
setMessage(message)
|
||||||
|
sendMessage(peerKey, message)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-[64rem] m-auto">
|
<div className="max-w-[64rem] m-auto">
|
||||||
<Card>
|
<Card>
|
||||||
<div className="p-10">
|
<div className="p-10">
|
||||||
<div className="flex flex-col lg:flex-row">
|
<div className="flex flex-col lg:flex-row">
|
||||||
{!event && (
|
{peerKey === "" && (
|
||||||
<div className="overflow-visible py-5 max-w-[20rem] mx-auto lg:pr-5">
|
<div className="overflow-visible py-5 max-w-[20rem] mx-auto lg:pr-5">
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={nostr.pub}
|
value={keys.pub}
|
||||||
level="H"
|
level="H"
|
||||||
bgColor="transparent"
|
bgColor="transparent"
|
||||||
fgColor="#3C3744"
|
fgColor="#3C3744"
|
||||||
@@ -43,12 +91,12 @@ export const ReceiveView = ({ nostr, event }: ReceiveViewProps) => {
|
|||||||
id="mypubkey"
|
id="mypubkey"
|
||||||
className="border-2 border-custom-black rounded-md p-2 bg-custom-green-dark break-all"
|
className="border-2 border-custom-black rounded-md p-2 bg-custom-green-dark break-all"
|
||||||
>
|
>
|
||||||
{nostr.pub}
|
{keys.pub}
|
||||||
</div>
|
</div>
|
||||||
<div className="py-6 max-w-[20rem] m-auto">
|
<div className="py-6 max-w-[20rem] m-auto">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(nostr.pub).catch(console.warn)
|
navigator.clipboard.writeText(keys.pub).catch(console.warn)
|
||||||
Toastify({
|
Toastify({
|
||||||
text: "Pubkey copied",
|
text: "Pubkey copied",
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
@@ -67,7 +115,7 @@ export const ReceiveView = ({ nostr, event }: ReceiveViewProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{event && <Message event={event} />}</div>
|
<div>{<Message message={message} onChange={onMessageChange} />}</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { MdQrCodeScanner } from "react-icons/md"
|
|||||||
import { IconButton } from "../../components/icon-button"
|
import { IconButton } from "../../components/icon-button"
|
||||||
import { Card } from "../../components/card"
|
import { Card } from "../../components/card"
|
||||||
import { debounce } from "../../lib/utils"
|
import { debounce } from "../../lib/utils"
|
||||||
import { NostrType } from "../../types"
|
import { NostrEventType, NostrKeysType, NostrType } from "../../types"
|
||||||
|
import { subscribe, sendEncryptedMessage, getLatestEvent } from "../../lib/nostr"
|
||||||
|
|
||||||
type MessageProps = {
|
type MessageProps = {
|
||||||
message: string
|
message: string
|
||||||
@@ -55,43 +56,48 @@ const PeerInput = ({ peerKey, onChange, setShowScan }: PeerInputProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SendViewProps = {
|
export type SendViewProps = {
|
||||||
nostr: NostrType
|
keys: NostrKeysType
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SendView = ({ nostr }: SendViewProps) => {
|
export const SendView = ({ keys }: SendViewProps) => {
|
||||||
const [showScan, setShowScan] = useState(false)
|
const [showScan, setShowScan] = useState(false)
|
||||||
const [peerKey, setPeerKey] = useState("")
|
const [peerKey, setPeerKey] = useState("")
|
||||||
const [message, setMessage] = useState("")
|
const [message, setMessage] = useState("")
|
||||||
|
const events = useRef<{ [k: string]: NostrEventType } | null>(null)
|
||||||
|
const nostr = useRef<NostrType | null>(null)
|
||||||
|
|
||||||
const ScanView = dynamic(async () => (await import("../scan")).ScanView)
|
const ScanView = dynamic(async () => (await import("../scan")).ScanView)
|
||||||
|
|
||||||
|
const processEvent = (event: NostrEventType) => {
|
||||||
|
events.current = { ...events.current, ...{ [event.id]: event } }
|
||||||
|
setMessage(getLatestEvent(events?.current)?.message || "")
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMobile) {
|
void (async () => {
|
||||||
setShowScan(true)
|
if (isMobile) {
|
||||||
}
|
setShowScan(true)
|
||||||
|
}
|
||||||
|
const sub = await subscribe(keys, peerKey, processEvent)
|
||||||
|
nostr.current = { ...sub, ...keys }
|
||||||
|
return () => {
|
||||||
|
nostr?.current?.sub?.unsub()
|
||||||
|
}
|
||||||
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const sendNostrMessage = useRef(
|
const sendMessage = useRef(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
debounce(async (m: string, p: string, n: NostrType) => {
|
debounce(async (peerKey: string, message: string) => {
|
||||||
try {
|
if (nostr.current?.pool) {
|
||||||
const { encrypt } = await import("nostr-tools/nip04")
|
await sendEncryptedMessage({ ...nostr.current, peerKey, message })
|
||||||
await n.pool?.publish({
|
|
||||||
pubkey: n.pub,
|
|
||||||
created_at: Math.round(Date.now() / 1000),
|
|
||||||
kind: 4,
|
|
||||||
tags: [["p", p]],
|
|
||||||
content: encrypt(n.priv, p, m),
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e)
|
|
||||||
}
|
}
|
||||||
}, 750),
|
}, 500),
|
||||||
).current
|
).current
|
||||||
|
|
||||||
const sendMessage = (message: string) => {
|
const onMessageChange = (message: string) => {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
sendNostrMessage(message, peerKey, nostr)
|
sendMessage(peerKey, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -109,7 +115,7 @@ export const SendView = ({ nostr }: SendViewProps) => {
|
|||||||
<Card>
|
<Card>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<PeerInput peerKey={peerKey} setShowScan={setShowScan} onChange={setPeerKey} />
|
<PeerInput peerKey={peerKey} setShowScan={setShowScan} onChange={setPeerKey} />
|
||||||
{isValidPeerKey(peerKey) && <Message message={message} onChange={sendMessage} />}
|
{isValidPeerKey(peerKey) && <Message message={message} onChange={onMessageChange} />}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user