Compare commits
34 Commits
1f0f45e3f9
...
snort-prod
Author | SHA1 | Date | |
---|---|---|---|
ba62f0ef74
|
|||
fb844a5969
|
|||
79e2d33e06
|
|||
4e5feede23
|
|||
d4115e9073
|
|||
d22ce56ebc
|
|||
91c912a886
|
|||
07474a836e
|
|||
75324e4862
|
|||
948337228e
|
|||
e4446962ac
|
|||
d442166846
|
|||
fe4e17227e
|
|||
d30aca46e8
|
|||
18f60681bd
|
|||
3d778f7ec7
|
|||
992d7f19be
|
|||
1820e7426d
|
|||
e6ca368134
|
|||
f028de7c04
|
|||
0584089d92
|
|||
bc1d0512f8
|
|||
aaa4b0de97
|
|||
6977f80652
|
|||
e3a8495c01
|
|||
ea07f91651
|
|||
fb600afabc
|
|||
68790a4fbb
|
|||
723abea3d9
|
|||
d46b5a052b
|
|||
048fdf463b | |||
f8f07a02bb
|
|||
e9fd593468
|
|||
f425830678
|
11
nap.yaml
Normal file
11
nap.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
id: "social.snort.app"
|
||||
name: "Snort"
|
||||
description: ""
|
||||
icon: "https://snort.social/nostrich_256.png"
|
||||
images:
|
||||
- "https://snort.social/nostrich_512.png"
|
||||
repository: "https://github.com/v0l/snort"
|
||||
license: "MIT"
|
||||
tags:
|
||||
- "social"
|
||||
- "twitter"
|
@ -12,10 +12,10 @@
|
||||
"defaultZapPoolFee": 1,
|
||||
"features": {
|
||||
"analytics": true,
|
||||
"subscriptions": true,
|
||||
"deck": true,
|
||||
"zapPool": true,
|
||||
"communityLeaders": true,
|
||||
"subscriptions": false,
|
||||
"deck": false,
|
||||
"zapPool": false,
|
||||
"communityLeaders": false,
|
||||
"nostrAddress": true,
|
||||
"pushNotifications": true
|
||||
},
|
||||
@ -41,17 +41,26 @@
|
||||
"eventLinkPrefix": "nevent",
|
||||
"profileLinkPrefix": "nprofile",
|
||||
"defaultRelays": {
|
||||
"wss://relay.snort.social/": { "read": true, "write": true },
|
||||
"wss://nostr.wine/": { "read": true, "write": false },
|
||||
"wss://relay.damus.io/": { "read": true, "write": true },
|
||||
"wss://nos.lol/": { "read": true, "write": true }
|
||||
"wss://relay.snort.social/": {
|
||||
"read": true,
|
||||
"write": true
|
||||
},
|
||||
"wss://nostr.wine/": {
|
||||
"read": true,
|
||||
"write": false
|
||||
},
|
||||
"wss://relay.damus.io/": {
|
||||
"read": true,
|
||||
"write": true
|
||||
},
|
||||
"wss://nos.lol/": {
|
||||
"read": true,
|
||||
"write": true
|
||||
}
|
||||
},
|
||||
"alby": {
|
||||
"clientId": "pohiJjPhQR",
|
||||
"clientSecret": "GAl1YKLA3FveK1gLBYok"
|
||||
},
|
||||
"chatChannels": [
|
||||
{ "type": "telegram", "value": "https://t.me/irismessenger" },
|
||||
{ "type": "nip28", "value": "23286a4602ada10cc10200553bff62a110e8dc0eacddf73277395a89ddf26a09" }
|
||||
]
|
||||
"chatChannels": []
|
||||
}
|
||||
|
@ -44,10 +44,7 @@
|
||||
"wss://relay.nostr.band/": { "read": true, "write": true },
|
||||
"wss://relay.damus.io/": { "read": true, "write": true }
|
||||
},
|
||||
"chatChannels": [
|
||||
{ "type": "telegram", "value": "https://t.me/irismessenger" },
|
||||
{ "type": "nip28", "value": "23286a4602ada10cc10200553bff62a110e8dc0eacddf73277395a89ddf26a09" }
|
||||
],
|
||||
"chatChannels": [{ "type": "telegram", "value": "https://t.me/irismessenger" }],
|
||||
"alby": {
|
||||
"clientId": "5rYcHDrlDb",
|
||||
"clientSecret": "QAI3QmgiaPH3BfTMzzFd"
|
||||
|
2
packages/app/custom.d.ts
vendored
2
packages/app/custom.d.ts
vendored
@ -101,7 +101,7 @@ declare const CONFIG: {
|
||||
|
||||
// public chat channels for site
|
||||
chatChannels?: Array<{
|
||||
type: "nip28" | "telegram";
|
||||
type: "telegram";
|
||||
value: string;
|
||||
}>;
|
||||
};
|
||||
|
@ -131,7 +131,7 @@
|
||||
"vite": "^5.2.8",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-pwa": "^0.19.2",
|
||||
"vite-plugin-version-mark": "^0.0.10",
|
||||
"vite-plugin-version-mark": "^0.1.4",
|
||||
"vitest": "^0.34.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { IMeta } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import React, { CSSProperties, useEffect, useMemo, useRef } from "react";
|
||||
import React, { CSSProperties, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
import { ProxyImg } from "@/Components/ProxyImg";
|
||||
@ -45,6 +45,8 @@ const ImageElement = ({ url, meta, onMediaClick, size }: ImageElementProps) => {
|
||||
return style;
|
||||
}, [imageRef?.current, meta]);
|
||||
|
||||
const [alternatives, setAlternatives] = useState<Array<string>>(meta?.fallback ?? []);
|
||||
const [currentUrl, setCurrentUrl] = useState<string>(url);
|
||||
return (
|
||||
<div
|
||||
className={classNames("flex items-center -mx-4 md:mx-0 my-2", {
|
||||
@ -52,8 +54,8 @@ const ImageElement = ({ url, meta, onMediaClick, size }: ImageElementProps) => {
|
||||
"cursor-pointer": onMediaClick,
|
||||
})}>
|
||||
<ProxyImg
|
||||
key={url}
|
||||
src={url}
|
||||
key={currentUrl}
|
||||
src={currentUrl}
|
||||
size={size}
|
||||
sha256={meta?.sha256}
|
||||
onClick={onMediaClick}
|
||||
@ -62,6 +64,14 @@ const ImageElement = ({ url, meta, onMediaClick, size }: ImageElementProps) => {
|
||||
})}
|
||||
style={style}
|
||||
ref={imageRef}
|
||||
onError={() => {
|
||||
const next = alternatives.at(0);
|
||||
if (next) {
|
||||
console.warn("IMG FALLBACK", "Failed to load url, trying next: ", next);
|
||||
setAlternatives(z => z.filter(y => y !== next));
|
||||
setCurrentUrl(next);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -26,7 +26,7 @@ export default function Mention({ link }: { link: NostrLink }) {
|
||||
|
||||
return (
|
||||
<span className="highlight" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<ProfileLink pubkey={link.id} link={link} user={profile} onClick={e => e.stopPropagation()}>
|
||||
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
|
||||
@<DisplayName user={profile} pubkey={link.id} />
|
||||
</ProfileLink>
|
||||
{isHovering && <ProfileCard pubkey={link.id} user={profile} show={true} />}
|
||||
|
@ -12,6 +12,8 @@ import usePreferences from "@/Hooks/usePreferences";
|
||||
import { dedupe, findTag, getDisplayName, hexToBech32 } from "@/Utils";
|
||||
import { useWallet } from "@/Wallet";
|
||||
|
||||
import { ProxyImg } from "../ProxyImg";
|
||||
|
||||
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
|
||||
const wallet = useWallet();
|
||||
const defaultZapAmount = usePreferences(s => s.defaultZapAmount);
|
||||
@ -62,29 +64,33 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
|
||||
}
|
||||
}
|
||||
|
||||
const picture = findTag(ev, "image");
|
||||
return (
|
||||
<FollowListBase
|
||||
pubkeys={ids}
|
||||
className={className}
|
||||
title={findTag(ev, "title") ?? findTag(ev, "d")}
|
||||
actions={
|
||||
<>
|
||||
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Zap all {n} sats"
|
||||
id="IVbtTS"
|
||||
values={{
|
||||
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
|
||||
}}
|
||||
/>
|
||||
</AsyncButton>
|
||||
</>
|
||||
}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
{picture && <ProxyImg src={picture} className="br max-h-44 w-full object-cover mb-4" />}
|
||||
<FollowListBase
|
||||
pubkeys={ids}
|
||||
className={className}
|
||||
title={findTag(ev, "title") ?? findTag(ev, "d")}
|
||||
actions={
|
||||
<>
|
||||
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Zap all {n} sats"
|
||||
id="IVbtTS"
|
||||
values={{
|
||||
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
|
||||
}}
|
||||
/>
|
||||
</AsyncButton>
|
||||
</>
|
||||
}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
39
packages/app/src/Components/Event/Application.tsx
Normal file
39
packages/app/src/Components/Event/Application.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { mapEventToProfile, TaggedNostrEvent } from "@snort/system";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import KindName from "../kind-name";
|
||||
import Avatar from "../User/Avatar";
|
||||
import DisplayName from "../User/DisplayName";
|
||||
import ProfileImage from "../User/ProfileImage";
|
||||
|
||||
export function ApplicationHandler({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const profile = mapEventToProfile(ev);
|
||||
const kinds = ev.tags
|
||||
.filter(a => a[0] === "k")
|
||||
.map(a => Number(a[1]))
|
||||
.sort((a, b) => a - b);
|
||||
return (
|
||||
<div className="p flex gap-2 flex-col">
|
||||
<div className="flex items-center gap-2 text-xl">
|
||||
<Avatar user={profile} pubkey={""} size={120} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<DisplayName user={profile} pubkey={""} />
|
||||
<div className="text-sm flex items-center gap-2">
|
||||
<div className="text-gray-light">
|
||||
<FormattedMessage defaultMessage="Published by" />
|
||||
</div>
|
||||
<ProfileImage className="inline" pubkey={ev.pubkey} size={30} link="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormattedMessage defaultMessage="Supported Kinds:" />
|
||||
<div className="flex flex-wrap">
|
||||
{kinds.map(a => (
|
||||
<div key={a} className="pill">
|
||||
<KindName kind={a} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +1,16 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { fetchNip05Pubkey, unixNow } from "@snort/shared";
|
||||
import { EventBuilder, EventKind, NostrLink, NostrPrefix, TaggedNostrEvent, tryParseNostrLink } from "@snort/system";
|
||||
import {
|
||||
EventBuilder,
|
||||
EventKind,
|
||||
Nip94Tags,
|
||||
nip94TagsToIMeta,
|
||||
NostrLink,
|
||||
NostrPrefix,
|
||||
readNip94Tags,
|
||||
TaggedNostrEvent,
|
||||
tryParseNostrLink,
|
||||
} from "@snort/system";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
import { ZapTarget } from "@snort/wallet";
|
||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||
@ -28,7 +38,7 @@ import usePreferences from "@/Hooks/usePreferences";
|
||||
import useRelays from "@/Hooks/useRelays";
|
||||
import { useNoteCreator } from "@/State/NoteCreator";
|
||||
import { openFile, trackEvent } from "@/Utils";
|
||||
import useFileUpload, { addExtensionToNip94Url, nip94TagsToIMeta, readNip94Tags } from "@/Utils/Upload";
|
||||
import useFileUpload from "@/Utils/Upload";
|
||||
import { GetPowWorker } from "@/Utils/wasm";
|
||||
|
||||
import { OkResponseRow } from "./OkResponseRow";
|
||||
@ -139,7 +149,6 @@ export function NoteCreator() {
|
||||
extraTags ??= [];
|
||||
extraTags.push(["content-warning", note.sensitive]);
|
||||
}
|
||||
const kind = note.pollOptions ? EventKind.Polls : EventKind.TextNote;
|
||||
if (note.pollOptions) {
|
||||
extraTags ??= [];
|
||||
extraTags.push(...note.pollOptions.map((a, i) => ["poll_option", i.toString(), a]));
|
||||
@ -149,15 +158,32 @@ export function NoteCreator() {
|
||||
extraTags.push(...note.hashTags.map(a => ["t", a.toLowerCase()]));
|
||||
}
|
||||
|
||||
for (const ex of note.otherEvents ?? []) {
|
||||
const meta = readNip94Tags(ex.tags);
|
||||
if (!meta.url) continue;
|
||||
if (!note.note.endsWith("\n")) {
|
||||
note.note += "\n";
|
||||
}
|
||||
note.note += addExtensionToNip94Url(meta);
|
||||
// attach 1 link and use other duplicates as fallback urls
|
||||
for (const [, v] of Object.entries(note.attachments ?? {})) {
|
||||
const at = v[0];
|
||||
note.note += note.note.length > 0 ? `\n${at.url}` : at.url;
|
||||
console.debug(at);
|
||||
const n94 =
|
||||
(at.nip94?.length ?? 0) > 0
|
||||
? readNip94Tags(at.nip94!)
|
||||
: ({
|
||||
url: at.url,
|
||||
hash: at.sha256,
|
||||
size: at.size,
|
||||
mimeType: at.type,
|
||||
} as Nip94Tags);
|
||||
|
||||
// attach fallbacks
|
||||
n94.fallback ??= [];
|
||||
n94.fallback.push(
|
||||
...v
|
||||
.slice(1)
|
||||
.filter(a => a.url)
|
||||
.map(a => a.url!),
|
||||
);
|
||||
|
||||
extraTags ??= [];
|
||||
extraTags.push(nip94TagsToIMeta(meta));
|
||||
extraTags.push(nip94TagsToIMeta(n94));
|
||||
}
|
||||
|
||||
// add quote repost
|
||||
@ -179,7 +205,9 @@ export function NoteCreator() {
|
||||
const hk = (eb: EventBuilder) => {
|
||||
extraTags?.forEach(t => eb.tag(t));
|
||||
note.extraTags?.forEach(t => eb.tag(t));
|
||||
eb.kind(kind);
|
||||
if (note.pollOptions) {
|
||||
eb.kind(EventKind.Polls);
|
||||
}
|
||||
return eb;
|
||||
};
|
||||
const ev = note.replyTo
|
||||
@ -261,20 +289,12 @@ export function NoteCreator() {
|
||||
async function uploadFile(file: File) {
|
||||
try {
|
||||
if (file && uploader) {
|
||||
const rx = await uploader.upload(file, file.name);
|
||||
const rx = await uploader.upload(file);
|
||||
note.update(v => {
|
||||
if (rx.header) {
|
||||
v.otherEvents ??= [];
|
||||
v.otherEvents.push(rx.header);
|
||||
} else if (rx.url) {
|
||||
v.note = `${v.note ? `${v.note}\n` : ""}${rx.url}`;
|
||||
if (rx.metadata) {
|
||||
v.extraTags ??= [];
|
||||
const imeta = nip94TagsToIMeta(rx.metadata);
|
||||
v.extraTags.push(imeta);
|
||||
}
|
||||
} else if (rx?.error) {
|
||||
v.error = rx.error;
|
||||
if (rx.url) {
|
||||
v.attachments ??= {};
|
||||
v.attachments[rx.sha256] ??= [];
|
||||
v.attachments[rx.sha256].push(rx);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -666,31 +686,25 @@ export function NoteCreator() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(note.otherEvents?.length ?? 0) > 0 && !note.preview && (
|
||||
{Object.entries(note.attachments ?? {}).length > 0 && !note.preview && (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{note.otherEvents
|
||||
?.map(a => ({
|
||||
event: a,
|
||||
tags: readNip94Tags(a.tags),
|
||||
}))
|
||||
.filter(a => a.tags.url)
|
||||
.map(a => (
|
||||
<div key={a.tags.url} className="relative">
|
||||
<img
|
||||
className="object-cover w-[80px] h-[80px] !mt-0 rounded-lg"
|
||||
src={addExtensionToNip94Url(a.tags)}
|
||||
/>
|
||||
<Icon
|
||||
name="x"
|
||||
className="absolute -top-[0.25rem] -right-[0.25rem] bg-gray rounded-full cursor-pointer"
|
||||
onClick={() =>
|
||||
note.update(
|
||||
n => (n.otherEvents = n.otherEvents?.filter(b => readNip94Tags(b.tags).url !== a.tags.url)),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{Object.entries(note.attachments ?? {}).map(([k, v]) => (
|
||||
<div key={k} className="relative">
|
||||
<img className="object-cover w-[80px] h-[80px] !mt-0 rounded-lg" src={v[0].url} />
|
||||
<Icon
|
||||
name="x"
|
||||
className="absolute -top-[0.25rem] -right-[0.25rem] bg-gray rounded-full cursor-pointer"
|
||||
onClick={() =>
|
||||
note.update(n => {
|
||||
if (n.attachments?.[k]) {
|
||||
delete n.attachments[k];
|
||||
}
|
||||
return n;
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{noteCreatorFooter()}
|
||||
@ -722,8 +736,11 @@ export function NoteCreator() {
|
||||
<MediaServerFileList
|
||||
onPicked={files => {
|
||||
note.update(n => {
|
||||
n.otherEvents ??= [];
|
||||
n.otherEvents?.push(...files);
|
||||
for (const x of files) {
|
||||
n.attachments ??= {};
|
||||
n.attachments[x.sha256] ??= [];
|
||||
n.attachments[x.sha256].push(x);
|
||||
}
|
||||
n.filePicker = "hidden";
|
||||
});
|
||||
}}
|
||||
|
@ -1,16 +1,3 @@
|
||||
.note > .header .reply {
|
||||
font-size: 13px;
|
||||
color: var(--font-secondary-color);
|
||||
}
|
||||
|
||||
.note > .header .reply a {
|
||||
color: var(--highlight);
|
||||
}
|
||||
|
||||
.note > .header .reply a:hover {
|
||||
text-decoration-color: var(--highlight);
|
||||
}
|
||||
|
||||
.note .header .info {
|
||||
font-size: var(--font-size);
|
||||
margin-left: 4px;
|
||||
@ -57,8 +44,7 @@
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.note > .header img:hover,
|
||||
.note > .header .name > .reply:hover {
|
||||
.note > .header img:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
import "./EventComponent.css";
|
||||
|
||||
import { EventKind, NostrEvent, TaggedNostrEvent } from "@snort/system";
|
||||
import { EventKind, NostrEvent, parseIMeta, TaggedNostrEvent } from "@snort/system";
|
||||
import { memo, ReactNode } from "react";
|
||||
|
||||
import PubkeyList from "@/Components/Embed/PubkeyList";
|
||||
import ZapstrEmbed from "@/Components/Embed/ZapstrEmbed";
|
||||
import ErrorBoundary from "@/Components/ErrorBoundary";
|
||||
import { ApplicationHandler } from "@/Components/Event/Application";
|
||||
import { LongFormText } from "@/Components/Event/LongFormText";
|
||||
import { NostrFileElement } from "@/Components/Event/NostrFileHeader";
|
||||
import { Note } from "@/Components/Event/Note/Note";
|
||||
import NoteReaction from "@/Components/Event/NoteReaction";
|
||||
import { ZapGoal } from "@/Components/Event/ZapGoal";
|
||||
import { LiveEvent } from "@/Components/LiveStream/LiveEvent";
|
||||
import ProfilePreview from "@/Components/User/ProfilePreview";
|
||||
|
||||
import { LongFormText } from "./LongFormText";
|
||||
import { Note } from "./Note/Note";
|
||||
|
||||
export interface NotePropsOptions {
|
||||
isRoot?: boolean;
|
||||
showHeader?: boolean;
|
||||
@ -62,6 +62,7 @@ export default memo(function EventComponent(props: NoteProps) {
|
||||
case EventKind.ZapstrTrack:
|
||||
content = <ZapstrEmbed ev={ev} />;
|
||||
break;
|
||||
case EventKind.StarterPackSet:
|
||||
case EventKind.FollowSet:
|
||||
case EventKind.ContactList:
|
||||
content = <PubkeyList ev={ev} className={className} />;
|
||||
@ -75,6 +76,25 @@ export default memo(function EventComponent(props: NoteProps) {
|
||||
case 9041: // Assuming 9041 is a valid EventKind
|
||||
content = <ZapGoal ev={ev} />;
|
||||
break;
|
||||
case EventKind.ApplicationHandler: {
|
||||
content = <ApplicationHandler ev={ev} />;
|
||||
break;
|
||||
}
|
||||
case EventKind.Photo:
|
||||
case EventKind.Video:
|
||||
case EventKind.ShortVideo: {
|
||||
// append media to note as if kind1 post
|
||||
const media = parseIMeta(ev.tags);
|
||||
// Sometimes we cann call this twice so check the URL's are not already
|
||||
// in the content
|
||||
const urls = Object.entries(media ?? {}).map(([k]) => k);
|
||||
if (!urls.every(u => ev.content.includes(u))) {
|
||||
const newContent = ev.content + " " + urls.join("\n");
|
||||
props.data.content = newContent;
|
||||
}
|
||||
content = <Note {...props} />;
|
||||
break;
|
||||
}
|
||||
case EventKind.LongFormTextNote:
|
||||
content = (
|
||||
<LongFormText
|
||||
|
21
packages/app/src/Components/Event/Note/ClientTag.tsx
Normal file
21
packages/app/src/Components/Event/Note/ClientTag.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function ClientTag({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const tag = ev.tags.find(a => a[0] === "client");
|
||||
if (!tag) return;
|
||||
const link = tag[2] && tag[2].includes(":") ? NostrLink.tryFromTag(["a", tag[2]]) : undefined;
|
||||
return (
|
||||
<span className="text-xs text-gray-light">
|
||||
{" "}
|
||||
<FormattedMessage
|
||||
defaultMessage="via {client}"
|
||||
description="via {client name} tag"
|
||||
values={{
|
||||
client: link ? <Link to={`/${link.encode()}`}>{tag[1]}</Link> : tag[1],
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
@ -32,14 +32,21 @@ const defaultOptions = {
|
||||
showContextMenu: true,
|
||||
};
|
||||
|
||||
const canRenderAsTextNote = [EventKind.TextNote, EventKind.Polls];
|
||||
const canRenderAsTextNote = [
|
||||
EventKind.TextNote,
|
||||
EventKind.Polls,
|
||||
EventKind.Photo,
|
||||
EventKind.Video,
|
||||
EventKind.ShortVideo,
|
||||
EventKind.Comment,
|
||||
];
|
||||
const translationCache = new LRUCache<string, NoteTranslation>({ maxSize: 300 });
|
||||
|
||||
export function Note(props: NoteProps) {
|
||||
const { data: ev, highlight, options: opt, ignoreModeration = false, className, waitUntilInView } = props;
|
||||
const baseClassName = classNames("note min-h-[110px] flex flex-col gap-4 card", className ?? "");
|
||||
const { isEventMuted } = useModeration();
|
||||
const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "2000px" });
|
||||
const { ref, inView } = useInView({ triggerOnce: true });
|
||||
const { ref: setSeenAtRef, inView: setSeenAtInView } = useInView({ rootMargin: "0px", threshold: 1 });
|
||||
const [showTranslation, setShowTranslation] = useState(true);
|
||||
const [translated, setTranslated] = useState<NoteTranslation | null>(translationCache.get(ev.id));
|
||||
|
@ -9,11 +9,13 @@ import DisplayName from "@/Components/User/DisplayName";
|
||||
import { ProfileLink } from "@/Components/User/ProfileLink";
|
||||
import { hexToBech32 } from "@/Utils";
|
||||
|
||||
import { ClientTag } from "./ClientTag";
|
||||
|
||||
export default function ReplyTag({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const thread = EventExt.extractThread(ev);
|
||||
if (thread === undefined) {
|
||||
return undefined;
|
||||
return <ClientTag ev={ev} />;
|
||||
}
|
||||
|
||||
const maxMentions = 2;
|
||||
@ -33,7 +35,7 @@ export default function ReplyTag({ ev }: { ev: TaggedNostrEvent }) {
|
||||
name: u?.name ?? shortNpub,
|
||||
link: (
|
||||
<ProfileLink pubkey={pk} user={u}>
|
||||
<DisplayName pubkey={pk} user={u} />{" "}
|
||||
<DisplayName pubkey={pk} user={u} className="text-highlight" />
|
||||
</ProfileLink>
|
||||
),
|
||||
});
|
||||
@ -53,7 +55,7 @@ export default function ReplyTag({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const others = mentions.length > maxMentions ? formatMessage(messages.Others, { n: othersLength }) : "";
|
||||
const link = replyLink?.encode(CONFIG.eventLinkPrefix);
|
||||
return (
|
||||
<div className="reply">
|
||||
<small className="text-xs">
|
||||
re:
|
||||
{(mentions?.length ?? 0) > 0 ? (
|
||||
<>
|
||||
@ -62,6 +64,7 @@ export default function ReplyTag({ ev }: { ev: TaggedNostrEvent }) {
|
||||
) : (
|
||||
replyLink && <Link to={`/${link}`}>{link?.substring(0, 12)}</Link>
|
||||
)}
|
||||
</div>
|
||||
<ClientTag ev={ev} />
|
||||
</small>
|
||||
);
|
||||
}
|
||||
|
@ -61,17 +61,6 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "suggested",
|
||||
path: `${base}/suggested`,
|
||||
show: Boolean(pubKey),
|
||||
element: (
|
||||
<>
|
||||
<Icon name="thumbs-up" />
|
||||
<FormattedMessage defaultMessage="Suggested Follows" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "trending/hashtags",
|
||||
path: `${base}/trending/hashtags`,
|
||||
@ -94,6 +83,28 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "media",
|
||||
path: `${base}/media`,
|
||||
show: true,
|
||||
element: (
|
||||
<>
|
||||
<Icon name="camera-plus" />
|
||||
<FormattedMessage defaultMessage="Media" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "follow-sets",
|
||||
path: `${base}/follow-sets`,
|
||||
show: true,
|
||||
element: (
|
||||
<>
|
||||
<Icon name="thumbs-up" />
|
||||
<FormattedMessage defaultMessage="Follow Sets" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
] as Array<{
|
||||
tab: RootTabRoutePath;
|
||||
path: string;
|
||||
|
@ -15,12 +15,16 @@ import { AutoLoadMore } from "../Event/LoadMore";
|
||||
import TimelineChunk from "./TimelineChunk";
|
||||
|
||||
export interface TimelineFollowsProps {
|
||||
id?: string;
|
||||
postsOnly: boolean;
|
||||
liveStreams?: boolean;
|
||||
noteFilter?: (ev: NostrEvent) => boolean;
|
||||
noteRenderer?: (ev: NostrEvent) => ReactNode;
|
||||
noteOnClick?: (ev: NostrEvent) => void;
|
||||
displayAs?: DisplayAs;
|
||||
kinds?: Array<EventKind>;
|
||||
showDisplayAsSelector?: boolean;
|
||||
firstChunkSize?: number;
|
||||
windowSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,12 +42,15 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
||||
const { isFollowing, followList } = useFollowsControls();
|
||||
const { chunks, showMore } = useTimelineChunks({
|
||||
now: openedAt,
|
||||
firstChunkSize: Hour * 2,
|
||||
window: props.windowSize,
|
||||
firstChunkSize: props.firstChunkSize ?? Hour * 2,
|
||||
});
|
||||
|
||||
const builder = useCallback(
|
||||
(rb: RequestBuilder) => {
|
||||
rb.withFilter().authors(followList).kinds([EventKind.TextNote, EventKind.Repost, EventKind.Polls]);
|
||||
rb.withFilter()
|
||||
.authors(followList)
|
||||
.kinds(props.kinds ?? [EventKind.TextNote, EventKind.Repost, EventKind.Polls]);
|
||||
},
|
||||
[followList],
|
||||
);
|
||||
@ -58,11 +65,13 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DisplayAsSelector activeSelection={displayAs} onSelect={(displayAs: DisplayAs) => setDisplayAs(displayAs)} />
|
||||
{(props.showDisplayAsSelector ?? true) && (
|
||||
<DisplayAsSelector activeSelection={displayAs} onSelect={(displayAs: DisplayAs) => setDisplayAs(displayAs)} />
|
||||
)}
|
||||
{chunks.map(c => (
|
||||
<TimelineChunk
|
||||
key={c.until}
|
||||
id="follows"
|
||||
id={`follows${props.id ? `:${props.id}` : ""}`}
|
||||
chunk={c}
|
||||
builder={builder}
|
||||
noteFilter={filterEvents}
|
||||
|
@ -463,6 +463,14 @@
|
||||
<path d="M14 3C14 2.44772 14.4477 2 15 2H21C21.5523 2 22 2.44772 22 3V9C22 9.55229 21.5523 10 21 10C20.4477 10 20 9.55229 20 9V5.41421L14.7071 10.7071C14.3166 11.0976 13.6834 11.0976 13.2929 10.7071C12.9024 10.3166 12.9024 9.68342 13.2929 9.29289L18.5858 4H15C14.4477 4 14 3.55228 14 3Z" fill="currentColor" />
|
||||
<path d="M5.41421 20L10.7071 14.7071C11.0976 14.3166 11.0976 13.6834 10.7071 13.2929C10.3166 12.9024 9.68342 12.9024 9.29289 13.2929L4 18.5858L4 15C4 14.4477 3.55229 14 3 14C2.44772 14 2 14.4477 2 15V21C2 21.5523 2.44772 22 3 22H9C9.55228 22 10 21.5523 10 21C10 20.4477 9.55228 20 9 20H5.41421Z" fill="currentColor" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="mic" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 13.4375C11.0771 13.4363 12.1097 13.0078 12.8713 12.2463C13.6328 11.4847 14.0613 10.4521 14.0625 9.375V5C14.0625 3.92256 13.6345 2.88925 12.8726 2.12738C12.1108 1.36551 11.0774 0.9375 10 0.9375C8.92256 0.9375 7.88925 1.36551 7.12738 2.12738C6.36551 2.88925 5.9375 3.92256 5.9375 5V9.375C5.93874 10.4521 6.36715 11.4847 7.12875 12.2463C7.89035 13.0078 8.92294 13.4363 10 13.4375ZM7.8125 5C7.8125 4.41984 8.04297 3.86344 8.4532 3.4532C8.86344 3.04297 9.41984 2.8125 10 2.8125C10.5802 2.8125 11.1366 3.04297 11.5468 3.4532C11.957 3.86344 12.1875 4.41984 12.1875 5V9.375C12.1875 9.95516 11.957 10.5116 11.5468 10.9218C11.1366 11.332 10.5802 11.5625 10 11.5625C9.41984 11.5625 8.86344 11.332 8.4532 10.9218C8.04297 10.5116 7.8125 9.95516 7.8125 9.375V5ZM10.9375 16.5016V18.125C10.9375 18.3736 10.8387 18.6121 10.6629 18.7879C10.4871 18.9637 10.2486 19.0625 10 19.0625C9.75136 19.0625 9.5129 18.9637 9.33709 18.7879C9.16127 18.6121 9.0625 18.3736 9.0625 18.125V16.5016C7.3344 16.2719 5.74838 15.4229 4.59892 14.1122C3.44947 12.8016 2.81471 11.1183 2.8125 9.375C2.8125 9.12636 2.91127 8.8879 3.08709 8.71209C3.2629 8.53627 3.50136 8.4375 3.75 8.4375C3.99864 8.4375 4.2371 8.53627 4.41291 8.71209C4.58873 8.8879 4.6875 9.12636 4.6875 9.375C4.6875 10.784 5.24721 12.1352 6.2435 13.1315C7.23978 14.1278 8.59104 14.6875 10 14.6875C11.409 14.6875 12.7602 14.1278 13.7565 13.1315C14.7528 12.1352 15.3125 10.784 15.3125 9.375C15.3125 9.12636 15.4113 8.8879 15.5871 8.71209C15.7629 8.53627 16.0014 8.4375 16.25 8.4375C16.4986 8.4375 16.7371 8.53627 16.9129 8.71209C17.0887 8.8879 17.1875 9.12636 17.1875 9.375C17.1853 11.1183 16.5505 12.8016 15.4011 14.1122C14.2516 15.4229 12.6656 16.2719 10.9375 16.5016Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="mic-off" viewBox="0 0 24 30" fill="none">
|
||||
<path d="M3.10989 2.99125C2.97816 2.84276 2.81827 2.72189 2.63949 2.63565C2.46071 2.54942 2.26658 2.49952 2.06837 2.48885C1.87016 2.47819 1.67181 2.50697 1.48481 2.57353C1.2978 2.6401 1.12587 2.74311 0.978971 2.87661C0.832073 3.01011 0.713132 3.17143 0.629043 3.35124C0.544953 3.53104 0.497387 3.72575 0.489101 3.92407C0.480814 4.1224 0.511973 4.32039 0.580772 4.50658C0.64957 4.69278 0.754638 4.86346 0.889888 5.00875L5.49989 10.08V14C5.49894 15.0718 5.76306 16.1273 6.26872 17.0723C6.77439 18.0174 7.50591 18.8227 8.39814 19.4166C9.29038 20.0105 10.3156 20.3746 11.3826 20.4764C12.4496 20.5782 13.5252 20.4145 14.5136 20L15.9211 21.5487C14.7105 22.1791 13.3648 22.5055 11.9999 22.5C9.74626 22.4977 7.5856 21.6014 5.99204 20.0078C4.39848 18.4143 3.5022 16.2536 3.49989 14C3.49989 13.6022 3.34185 13.2206 3.06055 12.9393C2.77924 12.658 2.39771 12.5 1.99989 12.5C1.60206 12.5 1.22053 12.658 0.939228 12.9393C0.657924 13.2206 0.499888 13.6022 0.499888 14C0.503423 16.7893 1.51905 19.4825 3.35817 21.5795C5.19729 23.6766 7.73494 25.035 10.4999 25.4025V28C10.4999 28.3978 10.6579 28.7794 10.9392 29.0607C11.2205 29.342 11.6021 29.5 11.9999 29.5C12.3977 29.5 12.7792 29.342 13.0605 29.0607C13.3419 28.7794 13.4999 28.3978 13.4999 28V25.4037C15.0922 25.2004 16.6228 24.66 17.9899 23.8187L20.8899 27.0087C21.1588 27.2977 21.5308 27.4689 21.9252 27.4854C22.3195 27.5019 22.7045 27.3622 22.9966 27.0968C23.2887 26.8313 23.4644 26.4614 23.4856 26.0673C23.5068 25.6731 23.3718 25.2865 23.1099 24.9912L3.10989 2.99125ZM11.9999 17.5C11.0716 17.5 10.1814 17.1313 9.52501 16.4749C8.86864 15.8185 8.49989 14.9283 8.49989 14V13.375L12.2374 17.4862C12.1586 17.5 12.0799 17.5 11.9999 17.5ZM7.33364 4.65875C7.18979 4.52338 7.0741 4.36094 6.9932 4.18075C6.9123 4.00055 6.86778 3.80616 6.86221 3.60871C6.85663 3.41127 6.89011 3.21467 6.96071 3.0302C7.03132 2.84572 7.13766 2.67701 7.27364 2.53375C8.16735 1.58718 9.3247 0.930779 10.5958 0.649565C11.8668 0.368351 13.1931 0.475282 14.4027 0.956509C15.6123 1.43774 16.6495 2.27108 17.38 3.34861C18.1105 4.42614 18.5007 5.69819 18.4999 7V13.0675C18.4999 13.4653 18.3419 13.8469 18.0605 14.1282C17.7792 14.4095 17.3977 14.5675 16.9999 14.5675C16.6021 14.5675 16.2205 14.4095 15.9392 14.1282C15.6579 13.8469 15.4999 13.4653 15.4999 13.0675V7C15.4998 6.29921 15.2894 5.61457 14.8959 5.03471C14.5024 4.45486 13.9438 4.00649 13.2926 3.74766C12.6413 3.48884 11.9274 3.43147 11.2432 3.58298C10.5589 3.7345 9.93597 4.08792 9.45489 4.5975C9.31967 4.74087 9.15752 4.85619 8.97771 4.93687C8.7979 5.01755 8.60395 5.062 8.40696 5.06769C8.20996 5.07337 8.01377 5.04019 7.82961 4.97002C7.64544 4.89985 7.47691 4.79408 7.33364 4.65875ZM19.8749 17.1975C20.2886 16.1823 20.5009 15.0963 20.4999 14C20.4999 13.6022 20.6579 13.2206 20.9392 12.9393C21.2205 12.658 21.6021 12.5 21.9999 12.5C22.3977 12.5 22.7792 12.658 23.0605 12.9393C23.3419 13.2206 23.4999 13.6022 23.4999 14C23.5023 15.4831 23.2161 16.9524 22.6574 18.3262C22.4983 18.6801 22.2082 18.9585 21.8482 19.1031C21.4881 19.2476 21.0861 19.247 20.7264 19.1014C20.3668 18.9558 20.0776 18.6766 19.9195 18.3222C19.7614 17.9679 19.7468 17.5661 19.8786 17.2012L19.8749 17.1975Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="hand" viewBox="0 0 24 30" fill="none">
|
||||
<path d="M19.5 9.49996C19.1627 9.4993 18.8267 9.5413 18.5 9.62496V6.49996C18.5006 5.85364 18.3445 5.2168 18.0452 4.64397C17.7458 4.07114 17.3121 3.57937 16.7812 3.21077C16.2503 2.84216 15.638 2.6077 14.9967 2.52745C14.3553 2.4472 13.7041 2.52355 13.0988 2.74996C12.7035 1.9353 12.0435 1.27886 11.2267 0.887983C10.4099 0.497104 9.48469 0.394917 8.60229 0.598133C7.7199 0.80135 6.93256 1.29794 6.36903 2.00671C5.8055 2.71547 5.49913 3.59447 5.5 4.49996V4.62496C4.90875 4.4723 4.2904 4.45704 3.69233 4.58034C3.09427 4.70363 2.53237 4.96222 2.04971 5.33629C1.56705 5.71035 1.17644 6.18995 0.907819 6.73833C0.639196 7.28672 0.499693 7.88932 0.500001 8.49996V18C0.500001 21.05 1.7116 23.975 3.86827 26.1317C6.02494 28.2884 8.95001 29.5 12 29.5C15.05 29.5 17.9751 28.2884 20.1317 26.1317C22.2884 23.975 23.5 21.05 23.5 18V13.5C23.5 12.4391 23.0786 11.4217 22.3284 10.6715C21.5783 9.92139 20.5609 9.49996 19.5 9.49996ZM20.5 18C20.5 20.2543 19.6045 22.4163 18.0104 24.0104C16.4163 25.6044 14.2543 26.5 12 26.5C9.74566 26.5 7.58365 25.6044 5.98959 24.0104C4.39553 22.4163 3.5 20.2543 3.5 18V8.49996C3.5 8.23475 3.60536 7.98039 3.79289 7.79286C3.98043 7.60532 4.23478 7.49996 4.5 7.49996C4.76522 7.49996 5.01957 7.60532 5.20711 7.79286C5.39464 7.98039 5.5 8.23475 5.5 8.49996V14C5.5 14.3978 5.65804 14.7793 5.93934 15.0606C6.22064 15.3419 6.60218 15.5 7 15.5C7.39783 15.5 7.77936 15.3419 8.06066 15.0606C8.34197 14.7793 8.5 14.3978 8.5 14V4.49996C8.5 4.23475 8.60536 3.98039 8.79289 3.79286C8.98043 3.60532 9.23478 3.49996 9.5 3.49996C9.76522 3.49996 10.0196 3.60532 10.2071 3.79286C10.3946 3.98039 10.5 4.23475 10.5 4.49996V13C10.5 13.3978 10.658 13.7793 10.9393 14.0606C11.2206 14.3419 11.6022 14.5 12 14.5C12.3978 14.5 12.7794 14.3419 13.0607 14.0606C13.342 13.7793 13.5 13.3978 13.5 13V6.49996C13.5 6.23475 13.6054 5.98039 13.7929 5.79286C13.9804 5.60532 14.2348 5.49996 14.5 5.49996C14.7652 5.49996 15.0196 5.60532 15.2071 5.79286C15.3946 5.98039 15.5 6.23475 15.5 6.49996V14.675C14.0773 15.0144 12.8103 15.823 11.9033 16.9705C10.9962 18.1179 10.5019 19.5373 10.5 21C10.5 21.3978 10.658 21.7793 10.9393 22.0606C11.2206 22.3419 11.6022 22.5 12 22.5C12.3978 22.5 12.7794 22.3419 13.0607 22.0606C13.342 21.7793 13.5 21.3978 13.5 21C13.5 20.0717 13.8687 19.1815 14.5251 18.5251C15.1815 17.8687 16.0717 17.5 17 17.5C17.3978 17.5 17.7794 17.3419 18.0607 17.0606C18.342 16.7793 18.5 16.3978 18.5 16V13.5C18.5 13.2347 18.6054 12.9804 18.7929 12.7929C18.9804 12.6053 19.2348 12.5 19.5 12.5C19.7652 12.5 20.0196 12.6053 20.2071 12.7929C20.3946 12.9804 20.5 13.2347 20.5 13.5V18Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 139 KiB |
@ -10,6 +10,7 @@ import useLiveStreams from "@/Hooks/useLiveStreams";
|
||||
import { findTag } from "@/Utils";
|
||||
|
||||
import Avatar from "../User/Avatar";
|
||||
import { NestsParticipants } from "./nests-participants";
|
||||
|
||||
export function LiveStreams() {
|
||||
const streams = useLiveStreams();
|
||||
@ -17,9 +18,18 @@ export function LiveStreams() {
|
||||
|
||||
return (
|
||||
<div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
|
||||
{streams.map(v => (
|
||||
<LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} className="h-[80px]" />
|
||||
))}
|
||||
{streams.map(v => {
|
||||
const k = `${v.kind}:${v.pubkey}:${findTag(v, "d")}`;
|
||||
const isVideoStream = v.tags.some(a => a[0] === "streaming" && a[1].includes(".m3u8"));
|
||||
if (isVideoStream) {
|
||||
return <LiveStreamEvent ev={v} key={k} className="h-[80px]" />;
|
||||
}
|
||||
|
||||
const isNests = v.tags.some(a => a[0] === "streaming" && a[1].startsWith("wss+livekit://"));
|
||||
if (isNests) {
|
||||
return <AudioRoom ev={v} key={k} className="h-[80px]" />;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -66,3 +76,37 @@ export function LiveStreamEvent({ ev, className }: { ev: NostrEvent; className?:
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function AudioRoom({ ev, className }: { ev: NostrEvent; className?: string }) {
|
||||
const { proxy } = useImgProxy();
|
||||
const title = findTag(ev, "title");
|
||||
const image = findTag(ev, "image");
|
||||
|
||||
const link = NostrLink.fromEvent(ev).encode();
|
||||
const imageProxy = proxy(image ?? "");
|
||||
|
||||
return (
|
||||
<Link className={classNames("flex gap-2", className)} to={`/${link}`}>
|
||||
<div className="relative aspect-video">
|
||||
<div
|
||||
className="absolute h-full w-full bg-center bg-cover bg-gray-ultradark rounded-lg flex items-end justify-center"
|
||||
style={
|
||||
{
|
||||
backgroundImage: `url(${imageProxy})`,
|
||||
} as CSSProperties
|
||||
}>
|
||||
<div className="flex items-center gap-1">
|
||||
<NestsParticipants ev={ev} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-0 top-0 w-full overflow-hidden">
|
||||
<div
|
||||
className="whitespace-nowrap px-1 text-ellipsis overflow-hidden text-xs font-medium bg-background opacity-70 text-center"
|
||||
title={title}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
77
packages/app/src/Components/LiveStream/VU.tsx
Normal file
77
packages/app/src/Components/LiveStream/VU.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function VuBar({
|
||||
track,
|
||||
full,
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
}: {
|
||||
track?: MediaStreamTrack;
|
||||
full?: boolean;
|
||||
height?: number;
|
||||
width?: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const ref = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref && track) {
|
||||
const audioContext = new AudioContext();
|
||||
|
||||
const trackClone = track;
|
||||
const mediaStreamSource = audioContext.createMediaStreamSource(new MediaStream([trackClone]));
|
||||
const analyser = audioContext.createAnalyser();
|
||||
const minVU = -60;
|
||||
const maxVU = 0;
|
||||
const minFreq = 50;
|
||||
const maxFreq = 7_000;
|
||||
analyser.minDecibels = -100;
|
||||
analyser.maxDecibels = 0;
|
||||
analyser.smoothingTimeConstant = 0.4;
|
||||
analyser.fftSize = 1024;
|
||||
mediaStreamSource.connect(analyser);
|
||||
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
|
||||
const filteredAudio = (i: Uint8Array) => {
|
||||
const binFreq = audioContext.sampleRate / 2 / dataArray.length;
|
||||
return i.subarray(minFreq / binFreq, maxFreq / binFreq);
|
||||
};
|
||||
const peakVolume = (data: Uint8Array) => {
|
||||
const max = data.reduce((acc, v) => (v > acc ? v : acc), 0);
|
||||
return (maxVU - minVU) * (max / 256) + minVU;
|
||||
};
|
||||
|
||||
const canvas = ref.current!;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
const t = setInterval(() => {
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
const data = filteredAudio(dataArray);
|
||||
const vol = peakVolume(data);
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (full) {
|
||||
ctx.fillStyle = "#00FF00";
|
||||
for (let x = 0; x < data.length; x++) {
|
||||
const bx = data[x];
|
||||
const h = canvas.height / data.length;
|
||||
ctx.fillRect(0, x * h, (bx / 255) * canvas.width, h);
|
||||
}
|
||||
}
|
||||
|
||||
const barLen = ((vol - minVU) / (maxVU - minVU)) * canvas.height;
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, canvas.height - barLen, canvas.width, barLen);
|
||||
}, 50);
|
||||
|
||||
return () => {
|
||||
clearInterval(t);
|
||||
audioContext.close();
|
||||
};
|
||||
}
|
||||
}, [ref, track, full]);
|
||||
|
||||
return <canvas ref={ref} width={width ?? 200} height={height ?? 10} className={className}></canvas>;
|
||||
}
|
@ -1,25 +1,43 @@
|
||||
import { LiveKitRoom as LiveKitRoomContext, RoomAudioRenderer, useParticipants } from "@livekit/components-react";
|
||||
import { dedupe, unixNow } from "@snort/shared";
|
||||
import { EventKind, NostrLink, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
/* eslint-disable max-lines */
|
||||
import {
|
||||
LiveKitRoom as LiveKitRoomContext,
|
||||
RoomAudioRenderer,
|
||||
useEnsureRoom,
|
||||
useParticipantPermissions,
|
||||
useParticipants,
|
||||
} from "@livekit/components-react";
|
||||
import { unixNow } from "@snort/shared";
|
||||
import { EventKind, EventPublisher, NostrLink, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
|
||||
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
|
||||
import { LocalParticipant, RemoteParticipant } from "livekit-client";
|
||||
import classNames from "classnames";
|
||||
import { LocalParticipant, LocalTrackPublication, RemoteParticipant, RoomEvent, Track } from "livekit-client";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Text from "@/Components/Text/Text";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import { extractStreamInfo } from "@/Utils/stream";
|
||||
|
||||
import AsyncButton from "../Button/AsyncButton";
|
||||
import IconButton from "../Button/IconButton";
|
||||
import { ProxyImg } from "../ProxyImg";
|
||||
import Avatar from "../User/Avatar";
|
||||
import { AvatarGroup } from "../User/AvatarGroup";
|
||||
import DisplayName from "../User/DisplayName";
|
||||
import ProfileImage from "../User/ProfileImage";
|
||||
import { NestsParticipants } from "./nests-participants";
|
||||
import VuBar from "./VU";
|
||||
|
||||
enum RoomTab {
|
||||
Participants,
|
||||
Chat,
|
||||
}
|
||||
|
||||
export default function LiveKitRoom({ ev, canJoin }: { ev: TaggedNostrEvent; canJoin?: boolean }) {
|
||||
const { stream, service, id } = extractStreamInfo(ev);
|
||||
const { publisher } = useEventPublisher();
|
||||
const { publisher, system } = useEventPublisher();
|
||||
const [join, setJoin] = useState(false);
|
||||
const [token, setToken] = useState<string>();
|
||||
const [tab, setTab] = useState(RoomTab.Participants);
|
||||
|
||||
async function getToken() {
|
||||
if (!service || !publisher) return;
|
||||
@ -43,6 +61,17 @@ export default function LiveKitRoom({ ev, canJoin }: { ev: TaggedNostrEvent; can
|
||||
}
|
||||
}
|
||||
|
||||
async function publishPresence(publisher: EventPublisher, system: SystemInterface) {
|
||||
const e = await publisher.generic(eb => {
|
||||
const aTag = NostrLink.fromEvent(ev).toEventTag();
|
||||
return eb
|
||||
.kind(10_312 as EventKind)
|
||||
.tag(aTag!)
|
||||
.tag(["expiration", (unixNow() + 60).toString()]);
|
||||
});
|
||||
await system.BroadcastEvent(e);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (join && !token) {
|
||||
getToken()
|
||||
@ -51,6 +80,18 @@ export default function LiveKitRoom({ ev, canJoin }: { ev: TaggedNostrEvent; can
|
||||
}
|
||||
}, [join]);
|
||||
|
||||
useEffect(() => {
|
||||
if (token && publisher && system) {
|
||||
publishPresence(publisher, system);
|
||||
const t = setInterval(async () => {
|
||||
if (token) {
|
||||
publishPresence(publisher, system);
|
||||
}
|
||||
}, 60_000);
|
||||
return () => clearInterval(t);
|
||||
}
|
||||
}, [token, publisher, system]);
|
||||
|
||||
if (!join) {
|
||||
return (
|
||||
<div className="p flex flex-col gap-2">
|
||||
@ -65,8 +106,8 @@ export default function LiveKitRoom({ ev, canJoin }: { ev: TaggedNostrEvent; can
|
||||
}
|
||||
return (
|
||||
<LiveKitRoomContext token={token} serverUrl={stream?.replace("wss+livekit://", "wss://")} connect={true}>
|
||||
<RoomAudioRenderer volume={1} />
|
||||
<ParticipantList ev={ev} />
|
||||
<RoomAudioRenderer volume={1} muted={false} />
|
||||
<RoomBody ev={ev} tab={tab} onSelectTab={setTab} />
|
||||
</LiveKitRoomContext>
|
||||
);
|
||||
}
|
||||
@ -75,57 +116,198 @@ function RoomHeader({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const { image, title } = extractStreamInfo(ev);
|
||||
return (
|
||||
<div className="relative rounded-xl h-[140px] w-full overflow-hidden">
|
||||
{image ? <ProxyImg src={image} className="w-full" /> : <div className="absolute bg-gray-dark w-full h-full" />}
|
||||
<div className="absolute left-4 top-4 w-full flex justify-between pr-4">
|
||||
{image ? (
|
||||
<ProxyImg src={image} className="w-full h-full object-cover object-center" />
|
||||
) : (
|
||||
<div className="absolute bg-gray-dark w-full h-full" />
|
||||
)}
|
||||
<div className="absolute left-4 top-4 w-full flex justify-between pr-8">
|
||||
<div className="text-2xl">{title}</div>
|
||||
<div>
|
||||
<NostrParticipants ev={ev} />
|
||||
<div className="flex gap-2 items-center">
|
||||
<NestsParticipants ev={ev} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ParticipantList({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const participants = useParticipants();
|
||||
function RoomBody({ ev, tab, onSelectTab }: { ev: TaggedNostrEvent; tab: RoomTab; onSelectTab: (t: RoomTab) => void }) {
|
||||
const participants = useParticipants({
|
||||
updateOnlyOn: [
|
||||
RoomEvent.ParticipantConnected,
|
||||
RoomEvent.ParticipantDisconnected,
|
||||
RoomEvent.ParticipantPermissionsChanged,
|
||||
RoomEvent.TrackMuted,
|
||||
RoomEvent.TrackPublished,
|
||||
RoomEvent.TrackUnmuted,
|
||||
RoomEvent.TrackUnmuted,
|
||||
],
|
||||
});
|
||||
return (
|
||||
<div className="p">
|
||||
<RoomHeader ev={ev} />
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Participants" />
|
||||
</h3>
|
||||
<div className="grid grid-cols-4">
|
||||
{participants.map(a => (
|
||||
<LiveKitUser p={a} key={a.identity} />
|
||||
))}
|
||||
<MyControls />
|
||||
<div className="flex text-center items-center text-xl font-medium mb-2">
|
||||
<div
|
||||
className={classNames("flex-1 py-2 cursor-pointer select-none border-b border-transparent", {
|
||||
"!border-highlight": tab === RoomTab.Participants,
|
||||
})}
|
||||
onClick={() => onSelectTab(RoomTab.Participants)}>
|
||||
<FormattedMessage defaultMessage="Participants" />
|
||||
</div>
|
||||
<div
|
||||
className={classNames("flex-1 py-2 cursor-pointer select-none border-b border-transparent", {
|
||||
"!border-highlight": tab === RoomTab.Chat,
|
||||
})}
|
||||
onClick={() => onSelectTab(RoomTab.Chat)}>
|
||||
<FormattedMessage defaultMessage="Chat" />
|
||||
</div>
|
||||
</div>
|
||||
{tab === RoomTab.Participants && (
|
||||
<div className="grid grid-cols-4">
|
||||
{participants.map(a => (
|
||||
<LiveKitUser p={a} key={a.identity} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{tab === RoomTab.Chat && (
|
||||
<>
|
||||
<RoomChat ev={ev} />
|
||||
<WriteChatMessage ev={ev} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MyControls() {
|
||||
const room = useEnsureRoom();
|
||||
const p = room.localParticipant;
|
||||
const permissions = useParticipantPermissions({
|
||||
participant: p,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (permissions && p instanceof LocalParticipant) {
|
||||
const handler = (lt: LocalTrackPublication) => {
|
||||
lt.mute();
|
||||
};
|
||||
p.on("localTrackPublished", handler);
|
||||
if (permissions.canPublish && p.audioTrackPublications.size === 0) {
|
||||
p.setMicrophoneEnabled(true);
|
||||
}
|
||||
return () => {
|
||||
p.off("localTrackPublished", handler);
|
||||
};
|
||||
}
|
||||
}, [p, permissions]);
|
||||
const isMuted = p.getTrackPublication(Track.Source.Microphone)?.isMuted ?? true;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center mt-2">
|
||||
{p.permissions?.canPublish && (
|
||||
<IconButton
|
||||
icon={{ name: !isMuted ? "mic" : "mic-off", size: 20 }}
|
||||
onClick={async () => {
|
||||
if (isMuted) {
|
||||
await p.setMicrophoneEnabled(true);
|
||||
} else {
|
||||
await p.setMicrophoneEnabled(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/*<IconButton icon={{ name: "hand", size: 20 }} />*/}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RoomChat({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const link = NostrLink.fromEvent(ev);
|
||||
const sub = useMemo(() => {
|
||||
const sub = new RequestBuilder(`room-chat:${link.tagKey}`);
|
||||
sub.withOptions({ leaveOpen: true, replaceable: true });
|
||||
sub.withFilter().replyToLink([link]).kinds([EventKind.LiveEventChat]).limit(100);
|
||||
return sub;
|
||||
}, [link.tagKey]);
|
||||
const chat = useRequestBuilder(sub);
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100dvh-370px)] overflow-x-hidden overflow-y-scroll">
|
||||
<div className="flex flex-col gap-1 flex-col-reverse w-full">
|
||||
{chat
|
||||
.sort((a, b) => b.created_at - a.created_at)
|
||||
.map(e => (
|
||||
<ChatMessage key={e.id} ev={e} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NostrParticipants({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const link = NostrLink.fromEvent(ev);
|
||||
const sub = useMemo(() => {
|
||||
const sub = new RequestBuilder(`livekit-participants:${link.tagKey}`);
|
||||
sub
|
||||
.withFilter()
|
||||
.replyToLink([link])
|
||||
.kinds([10_312 as EventKind])
|
||||
.since(unixNow() - 600);
|
||||
return sub;
|
||||
}, [link.tagKey]);
|
||||
function ChatMessage({ ev }: { ev: TaggedNostrEvent }) {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_1fr] items-center gap-2">
|
||||
<ProfileImage
|
||||
pubkey={ev.pubkey}
|
||||
size={20}
|
||||
showBadges={false}
|
||||
showFollowDistance={false}
|
||||
className="text-highlight"
|
||||
/>
|
||||
<Text id={ev.id} content={ev.content} creator={ev.pubkey} tags={ev.tags} disableMedia={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const presense = useRequestBuilder(sub);
|
||||
return <AvatarGroup ids={dedupe(presense.map(a => a.pubkey))} size={32} />;
|
||||
function WriteChatMessage({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const link = NostrLink.fromEvent(ev);
|
||||
const [chat, setChat] = useState("");
|
||||
const { publisher, system } = useEventPublisher();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
async function sendMessage() {
|
||||
if (!publisher || !system || chat.length < 2) return;
|
||||
const eChat = await publisher.generic(eb => eb.kind(EventKind.LiveEventChat).tag(link.toEventTag()!).content(chat));
|
||||
await system.BroadcastEvent(eChat);
|
||||
setChat("");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 mt-2">
|
||||
<input
|
||||
type="text"
|
||||
value={chat}
|
||||
placeholder={formatMessage({ defaultMessage: "Write message" })}
|
||||
onChange={e => setChat(e.target.value)}
|
||||
className="grow"
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Enter") {
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton icon={{ name: "arrow-right" }} onClick={sendMessage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LiveKitUser({ p }: { p: RemoteParticipant | LocalParticipant }) {
|
||||
const pubkey = p.identity.startsWith("guest-") ? "anon" : p.identity;
|
||||
const profile = useUserProfile(pubkey);
|
||||
const mic = p.getTrackPublication(Track.Source.Microphone);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 items-center text-center">
|
||||
<Avatar pubkey={pubkey} className={p.isSpeaking ? "outline" : ""} user={profile} size={48} />
|
||||
<DisplayName pubkey={pubkey} user={pubkey === "anon" ? { name: "Anon" } : profile} />
|
||||
<div className="relative w-[45px] h-[45px] flex items-center justify-center rounded-full overflow-hidden">
|
||||
{mic?.audioTrack?.mediaStreamTrack && (
|
||||
<VuBar track={mic.audioTrack?.mediaStreamTrack} className="absolute h-full w-full" />
|
||||
)}
|
||||
<Avatar pubkey={pubkey} user={profile} size={40} className="absolute" />
|
||||
</div>
|
||||
<div>
|
||||
<DisplayName pubkey={pubkey} user={pubkey === "anon" ? { name: "Anon" } : profile} />
|
||||
{p.permissions?.canPublish && <div className="text-highlight">Speaker</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { dedupe, unixNow } from "@snort/shared";
|
||||
import { EventKind, NostrLink, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { AvatarGroup } from "../User/AvatarGroup";
|
||||
|
||||
export function NestsParticipants({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const link = NostrLink.fromEvent(ev);
|
||||
const sub = useMemo(() => {
|
||||
const sub = new RequestBuilder(`livekit-participants:${link.tagKey}`);
|
||||
sub.withOptions({ leaveOpen: true });
|
||||
sub
|
||||
.withFilter()
|
||||
.replyToLink([link])
|
||||
.kinds([10_312 as EventKind])
|
||||
.since(unixNow() - 600);
|
||||
return sub;
|
||||
}, [link.tagKey]);
|
||||
|
||||
const presense = useRequestBuilder(sub);
|
||||
const filteredPresence = presense.filter(ev => ev.created_at > unixNow() - 600);
|
||||
return <AvatarGroup ids={dedupe(filteredPresence.map(a => a.pubkey)).slice(0, 5)} size={32} />;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import "./SearchBox.css";
|
||||
|
||||
import { NostrLink, tryParseNostrLink } from "@snort/system";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { ChangeEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
@ -25,7 +25,8 @@ export default function SearchBox() {
|
||||
const [activeIndex, setActiveIndex] = useState<number>(-1);
|
||||
const resultListRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const results = useProfileSearch(search);
|
||||
const searchFn = useProfileSearch();
|
||||
const results = useMemo(() => searchFn(search), [search, searchFn]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HexKey, NostrPrefix } from "@snort/system";
|
||||
import { NostrPrefix } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
@ -61,7 +61,7 @@ export default function SuggestedProfiles() {
|
||||
</select>
|
||||
</div>
|
||||
<FollowListBase
|
||||
pubkeys={userList as HexKey[]}
|
||||
pubkeys={userList}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
|
@ -9,7 +9,7 @@ import TextareaAutosize from "react-textarea-autosize";
|
||||
import Avatar from "@/Components/User/Avatar";
|
||||
import Nip05 from "@/Components/User/Nip05";
|
||||
import { FuzzySearchResult } from "@/Db/FuzzySearch";
|
||||
import { userSearch } from "@/Hooks/useProfileSearch";
|
||||
import useProfileSearch from "@/Hooks/useProfileSearch";
|
||||
import searchEmoji from "@/Utils/emoji-search";
|
||||
|
||||
import messages from "../messages";
|
||||
@ -58,6 +58,7 @@ interface TextareaProps {
|
||||
|
||||
const Textarea = (props: TextareaProps) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const userSearch = useProfileSearch();
|
||||
|
||||
const userDataProvider = (token: string) => {
|
||||
return userSearch(token).slice(0, 10);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { NostrEvent } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
@ -7,8 +6,7 @@ import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import useImgProxy from "@/Hooks/useImgProxy";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import { useMediaServerList } from "@/Hooks/useMediaServerList";
|
||||
import { findTag } from "@/Utils";
|
||||
import { Nip96Uploader } from "@/Utils/Upload/Nip96";
|
||||
import { BlobDescriptor, Blossom } from "@/Utils/Upload/blossom";
|
||||
|
||||
import AsyncButton from "../Button/AsyncButton";
|
||||
|
||||
@ -16,12 +14,12 @@ export function MediaServerFileList({
|
||||
onPicked,
|
||||
cols,
|
||||
}: {
|
||||
onPicked: (files: Array<NostrEvent>) => void;
|
||||
onPicked: (files: Array<BlobDescriptor>) => void;
|
||||
cols?: number;
|
||||
}) {
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
const { publisher } = useEventPublisher();
|
||||
const [fileList, setFilesList] = useState<Array<NostrEvent>>([]);
|
||||
const [fileList, setFilesList] = useState<Array<BlobDescriptor>>([]);
|
||||
const [pickedFiles, setPickedFiles] = useState<Array<string>>([]);
|
||||
const servers = useMediaServerList();
|
||||
|
||||
@ -30,11 +28,9 @@ export function MediaServerFileList({
|
||||
if (!publisher) return;
|
||||
for (const s of servers.servers) {
|
||||
try {
|
||||
const sx = new Nip96Uploader(s, publisher);
|
||||
const files = await sx.listFiles();
|
||||
if (files?.files) {
|
||||
res.push(...files.files);
|
||||
}
|
||||
const sx = new Blossom(s, publisher);
|
||||
const files = await sx.list(state.pubkey);
|
||||
res.push(...files);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -42,14 +38,12 @@ export function MediaServerFileList({
|
||||
setFilesList(res);
|
||||
}
|
||||
|
||||
function toggleFile(ev: NostrEvent) {
|
||||
const hash = findTag(ev, "x");
|
||||
if (!hash) return;
|
||||
function toggleFile(b: BlobDescriptor) {
|
||||
setPickedFiles(a => {
|
||||
if (a.includes(hash)) {
|
||||
return a.filter(a => a != hash);
|
||||
if (a.includes(b.sha256)) {
|
||||
return a.filter(a => a != b.sha256);
|
||||
} else {
|
||||
return [...a, hash];
|
||||
return [...a, b.sha256];
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -58,6 +52,17 @@ export function MediaServerFileList({
|
||||
listFiles().catch(console.error);
|
||||
}, [servers.servers.length, state?.version]);
|
||||
|
||||
const finalFileList = fileList
|
||||
.sort((a, b) => (b.uploaded ?? 0) - (a.uploaded ?? 0))
|
||||
.reduce(
|
||||
(acc, v) => {
|
||||
acc[v.sha256] ??= [];
|
||||
acc[v.sha256].push(v);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Array<BlobDescriptor>>,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
@ -65,33 +70,25 @@ export function MediaServerFileList({
|
||||
"grid-cols-2": cols === 2 || cols === undefined,
|
||||
"grid-cols-6": cols === 6,
|
||||
})}>
|
||||
{fileList.map(a => (
|
||||
<Nip96File
|
||||
key={a.id}
|
||||
file={a}
|
||||
onClick={() => toggleFile(a)}
|
||||
checked={pickedFiles.includes(findTag(a, "x") ?? "")}
|
||||
/>
|
||||
{Object.entries(finalFileList).map(([k, v]) => (
|
||||
<ServerFile key={k} file={v[0]} onClick={() => toggleFile(v[0])} checked={pickedFiles.includes(k)} />
|
||||
))}
|
||||
</div>
|
||||
<AsyncButton
|
||||
disabled={pickedFiles.length === 0}
|
||||
onClick={() => onPicked(fileList.filter(a => pickedFiles.includes(findTag(a, "x") ?? "")))}>
|
||||
onClick={() => onPicked(fileList.filter(a => pickedFiles.includes(a.sha256)))}>
|
||||
<FormattedMessage defaultMessage="Select" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Nip96File({ file, checked, onClick }: { file: NostrEvent; checked: boolean; onClick: () => void }) {
|
||||
const mime = findTag(file, "m");
|
||||
const url = findTag(file, "url");
|
||||
const size = findTag(file, "size");
|
||||
function ServerFile({ file, checked, onClick }: { file: BlobDescriptor; checked: boolean; onClick: () => void }) {
|
||||
const { proxy } = useImgProxy();
|
||||
|
||||
function backgroundImage() {
|
||||
if (url && (mime?.startsWith("image/") || mime?.startsWith("video/"))) {
|
||||
return `url(${proxy(url, 512)})`;
|
||||
if (file.url && (file.type?.startsWith("image/") || file.type?.startsWith("video/"))) {
|
||||
return `url(${proxy(file.url, 512)})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,26 +100,25 @@ function Nip96File({ file, checked, onClick }: { file: NostrEvent; checked: bool
|
||||
backgroundImage: backgroundImage(),
|
||||
}}>
|
||||
<div className="absolute w-full h-full opacity-0 bg-black hover:opacity-80 flex flex-col items-center justify-center gap-4">
|
||||
<div>{file.content.length === 0 ? <FormattedMessage defaultMessage="Untitled" /> : file.content}</div>
|
||||
<div>
|
||||
{Number(size) > 1024 * 1024 && (
|
||||
{file.size > 1024 * 1024 && (
|
||||
<FormattedMessage
|
||||
defaultMessage="{n}MiB"
|
||||
values={{
|
||||
n: <FormattedNumber value={Number(size) / 1024 / 1024} />,
|
||||
n: <FormattedNumber value={file.size / 1024 / 1024} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{Number(size) < 1024 * 1024 && (
|
||||
{file.size < 1024 * 1024 && (
|
||||
<FormattedMessage
|
||||
defaultMessage="{n}KiB"
|
||||
values={{
|
||||
n: <FormattedNumber value={Number(size) / 1024} />,
|
||||
n: <FormattedNumber value={file.size / 1024} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>{new Date(file.created_at * 1000).toLocaleString()}</div>
|
||||
<div>{file.uploaded && new Date(file.uploaded * 1000).toLocaleString()}</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames("w-4 h-4 border border-2 rounded-full right-1 top-1 absolute", {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { HexKey } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import ProfilePreview, { ProfilePreviewProps } from "@/Components/User/ProfilePreview";
|
||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import useWoT from "@/Hooks/useWoT";
|
||||
|
||||
import AsyncButton from "../Button/AsyncButton";
|
||||
import messages from "../messages";
|
||||
|
||||
export interface FollowListBaseProps {
|
||||
pubkeys: HexKey[];
|
||||
pubkeys: string[];
|
||||
title?: ReactNode;
|
||||
showFollowAll?: boolean;
|
||||
className?: string;
|
||||
@ -27,7 +27,8 @@ export default function FollowListBase({
|
||||
profilePreviewProps,
|
||||
}: FollowListBaseProps) {
|
||||
const control = useFollowsControls();
|
||||
const login = useLogin();
|
||||
const readonly = useLogin(s => s.readonly);
|
||||
const wot = useWoT();
|
||||
|
||||
async function followAll() {
|
||||
await control.addFollow(pubkeys);
|
||||
@ -37,15 +38,17 @@ export default function FollowListBase({
|
||||
<div className="flex flex-col gap-2">
|
||||
{(showFollowAll ?? true) && (
|
||||
<div className="flex items-center">
|
||||
<div className="grow font-bold">{title}</div>
|
||||
<div className="grow font-bold text-xl">{title}</div>
|
||||
{actions}
|
||||
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}>
|
||||
<FormattedMessage {...messages.FollowAll} />
|
||||
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={readonly}>
|
||||
<FormattedMessage defaultMessage="Follow All" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
<div className={className}>
|
||||
{pubkeys?.slice(0, 20).map(a => <ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />)}
|
||||
<div className={classNames("flex flex-col gap-2", className)}>
|
||||
{wot.sortPubkeys(pubkeys).map(a => (
|
||||
<ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -13,7 +13,7 @@ export default function FollowedBy({ pubkey }: { pubkey: HexKey }) {
|
||||
const wot = useWoT();
|
||||
const followDistance = wot.followDistance(pubkey);
|
||||
const { followedByFriendsArray, totalFollowedByFriends } = useMemo(() => {
|
||||
const followedByFriends = wot.followedByCount(pubkey);
|
||||
const followedByFriends = wot.followedBy(pubkey);
|
||||
return {
|
||||
followedByFriendsArray: Array.from(followedByFriends).slice(0, MAX_FOLLOWED_BY_FRIENDS),
|
||||
totalFollowedByFriends: followedByFriends.size,
|
||||
|
@ -118,7 +118,7 @@ export default function ProfileImage({
|
||||
const classNamesOverInner = classNames(
|
||||
"min-w-0",
|
||||
{
|
||||
"grid grid-cols-[min-content_auto] gap-3 items-center": showUsername,
|
||||
"grid grid-cols-[min-content_auto] gap-2 items-center": showUsername,
|
||||
},
|
||||
className,
|
||||
);
|
||||
|
@ -1,5 +1,10 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
// Take the markdown kinds table and find-replace with following regex:
|
||||
// FIND: ^\|\s+`([`\d\-]+)`\s+\| ([\w \-\(\)\/]+)[\s]*\|.*$
|
||||
// REPLACE: case $1:\n\treturn <FormattedMessage defaultMessage="$2" />;
|
||||
|
||||
export default function KindName({ kind }: { kind: number }) {
|
||||
switch (kind) {
|
||||
case 0:
|
||||
@ -21,21 +26,37 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
case 8:
|
||||
return <FormattedMessage defaultMessage="Badge Award" />;
|
||||
case 9:
|
||||
return <FormattedMessage defaultMessage="Group Chat Message" />;
|
||||
return <FormattedMessage defaultMessage="Chat Message" />;
|
||||
case 10:
|
||||
return <FormattedMessage defaultMessage="Group Chat Threaded Reply" />;
|
||||
case 11:
|
||||
return <FormattedMessage defaultMessage="Group Thread" />;
|
||||
return <FormattedMessage defaultMessage="Thread" />;
|
||||
case 12:
|
||||
return <FormattedMessage defaultMessage="Group Thread Reply" />;
|
||||
case 13:
|
||||
return <FormattedMessage defaultMessage="Seal" />;
|
||||
case 14:
|
||||
return <FormattedMessage defaultMessage="Direct Message" />;
|
||||
case 15:
|
||||
return <FormattedMessage defaultMessage="File Message" />;
|
||||
case 16:
|
||||
return <FormattedMessage defaultMessage="Generic Repost" />;
|
||||
case 17:
|
||||
return <FormattedMessage defaultMessage="Reaction to a website" />;
|
||||
case 20:
|
||||
return <FormattedMessage defaultMessage="Picture" />;
|
||||
case 21:
|
||||
return <FormattedMessage defaultMessage="Video Event" />;
|
||||
case 22:
|
||||
return <FormattedMessage defaultMessage="Short-form Portrait Video Event" />;
|
||||
case 30:
|
||||
return <FormattedMessage defaultMessage="internal reference" />;
|
||||
case 31:
|
||||
return <FormattedMessage defaultMessage="external web reference" />;
|
||||
case 32:
|
||||
return <FormattedMessage defaultMessage="hardcopy reference" />;
|
||||
case 33:
|
||||
return <FormattedMessage defaultMessage="prompt reference" />;
|
||||
case 40:
|
||||
return <FormattedMessage defaultMessage="Channel Creation" />;
|
||||
case 41:
|
||||
@ -46,10 +67,14 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Channel Hide Message" />;
|
||||
case 44:
|
||||
return <FormattedMessage defaultMessage="Channel Mute User" />;
|
||||
case 62:
|
||||
return <FormattedMessage defaultMessage="Request to Vanish" />;
|
||||
case 64:
|
||||
return <FormattedMessage defaultMessage="Chess (PGN)" />;
|
||||
case 818:
|
||||
return <FormattedMessage defaultMessage="Merge Requests" />;
|
||||
case 1018:
|
||||
return <FormattedMessage defaultMessage="Poll Response" />;
|
||||
case 1021:
|
||||
return <FormattedMessage defaultMessage="Bid" />;
|
||||
case 1022:
|
||||
@ -60,14 +85,20 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Gift Wrap" />;
|
||||
case 1063:
|
||||
return <FormattedMessage defaultMessage="File Metadata" />;
|
||||
case 1068:
|
||||
return <FormattedMessage defaultMessage="Poll" />;
|
||||
case 1111:
|
||||
return <FormattedMessage defaultMessage="Comment" />;
|
||||
case 1311:
|
||||
return <FormattedMessage defaultMessage="Live Chat Message" />;
|
||||
case 1337:
|
||||
return <FormattedMessage defaultMessage="Code Snippet" />;
|
||||
case 1617:
|
||||
return <FormattedMessage defaultMessage="Patches" />;
|
||||
case 1621:
|
||||
return <FormattedMessage defaultMessage="Issues" />;
|
||||
case 1622:
|
||||
return <FormattedMessage defaultMessage="Replies" />;
|
||||
return <FormattedMessage defaultMessage="Git Replies (deprecated)" />;
|
||||
case 1971:
|
||||
return <FormattedMessage defaultMessage="Problem Tracker" />;
|
||||
case 1984:
|
||||
@ -88,8 +119,16 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Community Post Approval" />;
|
||||
case 7000:
|
||||
return <FormattedMessage defaultMessage="Job Feedback" />;
|
||||
case 7374:
|
||||
return <FormattedMessage defaultMessage="Reserved Cashu Wallet Tokens" />;
|
||||
case 7375:
|
||||
return <FormattedMessage defaultMessage="Cashu Wallet Tokens" />;
|
||||
case 7376:
|
||||
return <FormattedMessage defaultMessage="Cashu Wallet History" />;
|
||||
case 9041:
|
||||
return <FormattedMessage defaultMessage="Zap Goal" />;
|
||||
case 9321:
|
||||
return <FormattedMessage defaultMessage="Nutzap" />;
|
||||
case 9467:
|
||||
return <FormattedMessage defaultMessage="Tidal login" />;
|
||||
case 9734:
|
||||
@ -116,8 +155,12 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Search relays list" />;
|
||||
case 10009:
|
||||
return <FormattedMessage defaultMessage="User groups" />;
|
||||
case 10013:
|
||||
return <FormattedMessage defaultMessage="Private event relay list" />;
|
||||
case 10015:
|
||||
return <FormattedMessage defaultMessage="Interests list" />;
|
||||
case 10019:
|
||||
return <FormattedMessage defaultMessage="Nutzap Mint Recommendation" />;
|
||||
case 10030:
|
||||
return <FormattedMessage defaultMessage="User emoji list" />;
|
||||
case 10050:
|
||||
@ -126,8 +169,12 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="User server list" />;
|
||||
case 10096:
|
||||
return <FormattedMessage defaultMessage="File storage server list" />;
|
||||
case 10166:
|
||||
return <FormattedMessage defaultMessage="Relay Monitor Announcement" />;
|
||||
case 13194:
|
||||
return <FormattedMessage defaultMessage="Wallet Info" />;
|
||||
case 17375:
|
||||
return <FormattedMessage defaultMessage="Cashu Wallet Event" />;
|
||||
case 21000:
|
||||
return <FormattedMessage defaultMessage="Lightning Pub RPC" />;
|
||||
case 22242:
|
||||
@ -177,17 +224,23 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
case 30030:
|
||||
return <FormattedMessage defaultMessage="Emoji sets" />;
|
||||
case 30040:
|
||||
return <FormattedMessage defaultMessage="Modular Article Header" />;
|
||||
return <FormattedMessage defaultMessage="Curated Publication Index" />;
|
||||
case 30041:
|
||||
return <FormattedMessage defaultMessage="Modular Article Content" />;
|
||||
return <FormattedMessage defaultMessage="Curated Publication Content" />;
|
||||
case 30063:
|
||||
return <FormattedMessage defaultMessage="Release artifact sets" />;
|
||||
case 30078:
|
||||
return <FormattedMessage defaultMessage="Application-specific Data" />;
|
||||
case 30166:
|
||||
return <FormattedMessage defaultMessage="Relay Discovery" />;
|
||||
case 30267:
|
||||
return <FormattedMessage defaultMessage="App curation sets" />;
|
||||
case 30311:
|
||||
return <FormattedMessage defaultMessage="Live Event" />;
|
||||
case 30315:
|
||||
return <FormattedMessage defaultMessage="User Statuses" />;
|
||||
case 30388:
|
||||
return <FormattedMessage defaultMessage="Slide Set" />;
|
||||
case 30402:
|
||||
return <FormattedMessage defaultMessage="Classified Listing" />;
|
||||
case 30403:
|
||||
@ -200,6 +253,10 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Wiki article" />;
|
||||
case 30819:
|
||||
return <FormattedMessage defaultMessage="Redirects" />;
|
||||
case 31234:
|
||||
return <FormattedMessage defaultMessage="Draft Event" />;
|
||||
case 31388:
|
||||
return <FormattedMessage defaultMessage="Link Set" />;
|
||||
case 31890:
|
||||
return <FormattedMessage defaultMessage="Feed" />;
|
||||
case 31922:
|
||||
@ -214,13 +271,17 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Handler recommendation" />;
|
||||
case 31990:
|
||||
return <FormattedMessage defaultMessage="Handler information" />;
|
||||
case 34235:
|
||||
return <FormattedMessage defaultMessage="Video Event" />;
|
||||
case 34236:
|
||||
return <FormattedMessage defaultMessage="Short-form Portrait Video Event" />;
|
||||
case 34237:
|
||||
return <FormattedMessage defaultMessage="Video View Event" />;
|
||||
case 32267:
|
||||
return <FormattedMessage defaultMessage="Software Application" />;
|
||||
case 34550:
|
||||
return <FormattedMessage defaultMessage="Community Definition" />;
|
||||
case 38383:
|
||||
return <FormattedMessage defaultMessage="Peer-to-peer Order events" />;
|
||||
case 39089:
|
||||
return <FormattedMessage defaultMessage="Starter Pack" />;
|
||||
case 39701:
|
||||
return <FormattedMessage defaultMessage="Web bookmarks" />;
|
||||
default:
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export default function useLoginFeed() {
|
||||
const { publisher, system } = useEventPublisher();
|
||||
|
||||
useEffect(() => {
|
||||
//system.checkSigs = checkSigs;
|
||||
system.checkSigs = checkSigs;
|
||||
}, [system, checkSigs]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -37,6 +37,7 @@ export default function useLoginFeed() {
|
||||
}, [pubKey]);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("UserState: start init from LoginFeed", login.state.didInit);
|
||||
login.state.init(publisher?.signer, system).catch(console.error);
|
||||
}, [login, publisher, system]);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { EventKind, Nip10, NostrLink, RequestBuilder } from "@snort/system";
|
||||
import { EventKind, Nip10, Nip22, NostrLink, RequestBuilder } from "@snort/system";
|
||||
import { SnortContext, useRequestBuilder } from "@snort/system-react";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
@ -34,7 +34,7 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
for (const v of Object.values(grouped)) {
|
||||
sub
|
||||
.withFilter()
|
||||
.kinds([EventKind.TextNote])
|
||||
.kinds([EventKind.TextNote, EventKind.Comment])
|
||||
.replyToLink(v)
|
||||
.relay(rootRelays ?? []);
|
||||
}
|
||||
@ -48,7 +48,9 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
const links = store
|
||||
.map(a => [
|
||||
NostrLink.fromEvent(a),
|
||||
...a.tags.filter(a => a[0] === "e" || a[0] === "a").map(v => NostrLink.fromTag(v)),
|
||||
...a.tags
|
||||
.filter(a => a[0] === "e" || a[0] === "a" || a[0] === "E" || a[0] === "A")
|
||||
.map(v => NostrLink.fromTag(v)),
|
||||
])
|
||||
.flat();
|
||||
setAllEvents(links);
|
||||
@ -56,14 +58,19 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
// load the thread structure from the current note
|
||||
const current = store.find(a => link.matchesEvent(a));
|
||||
if (current) {
|
||||
const t = Nip10.parseThread(current);
|
||||
if (t) {
|
||||
const rootOrReplyAsRoot = t?.root ?? t?.replyTo;
|
||||
if (rootOrReplyAsRoot) {
|
||||
setRoot(rootOrReplyAsRoot);
|
||||
if (current.kind === EventKind.TextNote) {
|
||||
const t = Nip10.parseThread(current);
|
||||
if (t) {
|
||||
const rootOrReplyAsRoot = t?.root ?? t?.replyTo;
|
||||
if (rootOrReplyAsRoot) {
|
||||
setRoot(rootOrReplyAsRoot);
|
||||
}
|
||||
} else {
|
||||
setRoot(link);
|
||||
}
|
||||
} else {
|
||||
setRoot(link);
|
||||
const root = Nip22.rootScopeOf(current);
|
||||
setRoot(NostrLink.fromTag(root));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { useMemo } from "react";
|
||||
export default function useDiscoverMediaServers() {
|
||||
const sub = useMemo(() => {
|
||||
const rb = new RequestBuilder("media-servers-all");
|
||||
rb.withFilter().kinds([EventKind.StorageServerList]);
|
||||
rb.withFilter().kinds([EventKind.BlossomServerList]);
|
||||
return rb;
|
||||
}, []);
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { decodeTLV, EventKind, NostrPrefix, RequestBuilder, TLVEntryType } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { createEmptyChatObject } from "@/chat";
|
||||
|
||||
export function useEmptyChatSystem(id?: string) {
|
||||
const sub = useMemo(() => {
|
||||
if (id?.startsWith(NostrPrefix.Chat28)) {
|
||||
const cx = unwrap(decodeTLV(id).find(a => a.type === TLVEntryType.Special)).value as string;
|
||||
const rb = new RequestBuilder(`nip28:${id}`);
|
||||
rb.withFilter().ids([cx]).kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
|
||||
rb.withFilter()
|
||||
.tag("e", [cx])
|
||||
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMessage, EventKind.PublicChatMetadata]);
|
||||
|
||||
return rb;
|
||||
} else {
|
||||
return new RequestBuilder(id ?? "");
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const data = useRequestBuilder(sub);
|
||||
return useMemo(() => {
|
||||
if (!id) return;
|
||||
return createEmptyChatObject(id, data);
|
||||
}, [id, data.length]);
|
||||
}
|
@ -11,7 +11,7 @@ export default function useLiveStreams() {
|
||||
const rb = new RequestBuilder("streams");
|
||||
rb.withFilter()
|
||||
.kinds([EventKind.LiveEvent])
|
||||
.since(unixNow() - Hour);
|
||||
.since(unixNow() - 4 * Hour);
|
||||
return rb;
|
||||
}, []);
|
||||
|
||||
|
@ -2,23 +2,21 @@ import { removeUndefined, sanitizeRelayUrl } from "@snort/shared";
|
||||
import { EventKind, UnknownTag } from "@snort/system";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Nip96Uploader } from "@/Utils/Upload/Nip96";
|
||||
|
||||
import useEventPublisher from "./useEventPublisher";
|
||||
import useLogin from "./useLogin";
|
||||
|
||||
export const DefaultMediaServers = [
|
||||
//"https://media.zap.stream",
|
||||
new UnknownTag(["server", "https://nostr.build/"]),
|
||||
new UnknownTag(["server", "https://nostr.download/"]),
|
||||
new UnknownTag(["server", "https://blossom.build/"]),
|
||||
new UnknownTag(["server", "https://nostrcheck.me/"]),
|
||||
new UnknownTag(["server", "https://files.v0l.io/"]),
|
||||
new UnknownTag(["server", "https://blossom.primal.net/"]),
|
||||
];
|
||||
|
||||
export function useMediaServerList() {
|
||||
const { publisher } = useEventPublisher();
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
|
||||
let servers = state?.getList(EventKind.StorageServerList) ?? [];
|
||||
let servers = state?.getList(EventKind.BlossomServerList) ?? [];
|
||||
if (servers.length === 0) {
|
||||
servers = DefaultMediaServers;
|
||||
}
|
||||
@ -33,14 +31,12 @@ export function useMediaServerList() {
|
||||
|
||||
const u = sanitizeRelayUrl(s);
|
||||
if (!u) return;
|
||||
const server = new Nip96Uploader(u, publisher);
|
||||
await server.loadInfo();
|
||||
await state?.addToList(EventKind.StorageServerList, new UnknownTag(["server", u]), true);
|
||||
await state?.addToList(EventKind.BlossomServerList, new UnknownTag(["server", u]), true);
|
||||
},
|
||||
removeServer: async (s: string) => {
|
||||
const u = sanitizeRelayUrl(s);
|
||||
if (!u) return;
|
||||
await state?.removeFromList(EventKind.StorageServerList, new UnknownTag(["server", u]), true);
|
||||
await state?.removeFromList(EventKind.BlossomServerList, new UnknownTag(["server", u]), true);
|
||||
},
|
||||
}),
|
||||
[servers],
|
||||
|
@ -1,13 +1,13 @@
|
||||
import fuzzySearch from "@/Db/FuzzySearch";
|
||||
|
||||
import useWoT from "./useWoT";
|
||||
import useWoT, { WoT } from "./useWoT";
|
||||
|
||||
export default function useProfileSearch(search: string | undefined) {
|
||||
return userSearch(search);
|
||||
export default function useProfileSearch() {
|
||||
const wot = useWoT();
|
||||
return (search: string | undefined) => userSearch(wot, search);
|
||||
}
|
||||
|
||||
export function userSearch(search: string | undefined) {
|
||||
const wot = useWoT();
|
||||
function userSearch(wot: WoT, search: string | undefined) {
|
||||
const searchString = search?.trim() ?? "";
|
||||
const fuseResults = (searchString?.length ?? 0) > 0 ? fuzzySearch.search(searchString) : [];
|
||||
|
||||
|
@ -1,28 +1,29 @@
|
||||
import { TaggedNostrEvent } from "@snort/system";
|
||||
import { SystemInterface, TaggedNostrEvent } from "@snort/system";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import { useContext, useMemo } from "react";
|
||||
|
||||
export interface WoT {
|
||||
sortEvents: (events: Array<TaggedNostrEvent>) => Array<TaggedNostrEvent>;
|
||||
sortPubkeys: (events: Array<string>) => Array<string>;
|
||||
followDistance: (pk: string) => number;
|
||||
followedByCount: (pk: string) => number;
|
||||
followedBy: (pk: string) => Set<string>;
|
||||
}
|
||||
|
||||
function wotOnSystem(system: SystemInterface) {
|
||||
const sgi = system.config.socialGraphInstance;
|
||||
return {
|
||||
sortEvents: (events: Array<TaggedNostrEvent>) =>
|
||||
events.sort((a, b) => sgi.getFollowDistance(a.pubkey) - sgi.getFollowDistance(b.pubkey)),
|
||||
sortPubkeys: (events: Array<string>) => events.sort((a, b) => sgi.getFollowDistance(a) - sgi.getFollowDistance(b)),
|
||||
followDistance: (pk: string) => sgi.getFollowDistance(pk),
|
||||
followedByCount: (pk: string) => sgi.followedByFriendsCount(pk),
|
||||
followedBy: (pk: string) => sgi.followedByFriends(pk),
|
||||
instance: sgi,
|
||||
};
|
||||
}
|
||||
|
||||
export default function useWoT() {
|
||||
const system = useContext(SnortContext);
|
||||
return useMemo(
|
||||
() => ({
|
||||
sortEvents: (events: Array<TaggedNostrEvent>) =>
|
||||
events.sort(
|
||||
(a, b) =>
|
||||
system.config.socialGraphInstance.getFollowDistance(a.pubkey) -
|
||||
system.config.socialGraphInstance.getFollowDistance(b.pubkey),
|
||||
),
|
||||
sortPubkeys: (events: Array<string>) =>
|
||||
events.sort(
|
||||
(a, b) =>
|
||||
system.config.socialGraphInstance.getFollowDistance(a) -
|
||||
system.config.socialGraphInstance.getFollowDistance(b),
|
||||
),
|
||||
followDistance: (pk: string) => system.config.socialGraphInstance.getFollowDistance(pk),
|
||||
followedByCount: (pk: string) => system.config.socialGraphInstance.followedByFriendsCount(pk),
|
||||
followedBy: (pk: string) => system.config.socialGraphInstance.followedByFriends(pk),
|
||||
instance: system.config.socialGraphInstance,
|
||||
}),
|
||||
[system.config.socialGraphInstance],
|
||||
);
|
||||
return useMemo<WoT>(() => wotOnSystem(system), [system]);
|
||||
}
|
||||
|
@ -1,29 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import Changelog from "@/../CHANGELOG.md";
|
||||
import Changelog from "@/../CHANGELOG.md?raw";
|
||||
import { Markdown } from "@/Components/Event/Markdown";
|
||||
|
||||
export function AboutPage() {
|
||||
const [changelog, setChangelog] = useState("");
|
||||
|
||||
async function getChangelog() {
|
||||
const res = await fetch(Changelog);
|
||||
const content = await res.text();
|
||||
setChangelog(content);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getChangelog().catch(console.error);
|
||||
}, []);
|
||||
|
||||
const version = document.querySelector("meta[name='application-name']")?.getAttribute("content");
|
||||
return (
|
||||
<div className="main-content p">
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="About" />
|
||||
</h1>
|
||||
Version: <b>{__SNORT_VERSION__}</b>
|
||||
<Markdown content={changelog} tags={[]} />
|
||||
Version: <b>{version?.split(":")?.at(1) ?? "unknown version"}</b>
|
||||
<Markdown content={Changelog} tags={[]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ export function MediaCol({ setThread }: { setThread: (e: NostrLink) => void }) {
|
||||
</div>
|
||||
<TimelineFollows
|
||||
postsOnly={true}
|
||||
liveStreams={false}
|
||||
noteFilter={e => {
|
||||
const parsed = transformTextCached(e.id, e.content, e.tags);
|
||||
const images = parsed.filter(a => a.type === "media" && a.mimeType?.startsWith("image/"));
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { HexKey } from "@snort/system";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import Telegram from "@/assets/img/telegram.svg";
|
||||
import { Nip28ChatSystem } from "@/chat/nip28";
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import Copy from "@/Components/Copy/Copy";
|
||||
import ZapButton from "@/Components/Event/ZapButton";
|
||||
@ -22,7 +21,6 @@ const DonatePage = () => {
|
||||
const [today, setSumToday] = useState<RevenueToday>();
|
||||
const [onChain, setOnChain] = useState("");
|
||||
const api = new SnortApi(ApiHost);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function getOnChainAddress() {
|
||||
const { address } = await api.onChainDonation();
|
||||
@ -105,18 +103,6 @@ const DonatePage = () => {
|
||||
</AsyncButton>
|
||||
);
|
||||
}
|
||||
case "nip28": {
|
||||
return (
|
||||
<AsyncButton
|
||||
onClick={() => {
|
||||
const id = Nip28ChatSystem.chatId(a.value);
|
||||
navigate(`/messages/${id}`);
|
||||
}}>
|
||||
<img src={CONFIG.icon} width={24} height={24} className="rounded-full" />
|
||||
<FormattedMessage defaultMessage="Nostr Public Chat" />
|
||||
</AsyncButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
@ -39,5 +39,7 @@ export const Translators = [
|
||||
bech32ToHex("npub1ust7u0v3qffejwhqee45r49zgcyewrcn99vdwkednd356c9resyqtnn3mj"), // Petri - FI
|
||||
|
||||
bech32ToHex("npub1p94p6d4p04mhjt2hdpkhhvkl93v7j7ada4w9lztj0y0fzg2m959sux5h5k"), // Jeremy - SV
|
||||
|
||||
bech32ToHex("npub1dnvslq0vvrs8d603suykc4harv94yglcxwna9sl2xu8grt2afm3qgfh0tp"), // summoner001 - HU
|
||||
];
|
||||
export const DonateLNURL = "donate@snort.social";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { EventKind, NostrLink, NostrPrefix, parseNostrLink } from "@snort/system";
|
||||
import { Bech32Regex, unwrap } from "@snort/shared";
|
||||
import { EventKind, NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
|
||||
import { useEventFeed } from "@snort/system-react";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
@ -9,24 +9,25 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { rootTabItems } from "@/Components/Feed/RootTabItems";
|
||||
import { RootTabs } from "@/Components/Feed/RootTabs";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
import KindName from "@/Components/kind-name";
|
||||
import DisplayName from "@/Components/User/DisplayName";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import { LogoHeader } from "@/Pages/Layout/LogoHeader";
|
||||
import NotificationsHeader from "@/Pages/Layout/NotificationsHeader";
|
||||
import { bech32ToHex } from "@/Utils";
|
||||
import { bech32ToHex, findTag } from "@/Utils";
|
||||
|
||||
export function Header() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const pageName = decodeURIComponent(location.pathname.split("/")[1]);
|
||||
const pathSplit = location.pathname.split("/");
|
||||
const pageName = decodeURIComponent(pathSplit[1]);
|
||||
|
||||
const nostrLink = useMemo(() => {
|
||||
try {
|
||||
return parseNostrLink(pageName);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
const nostrEntity = pathSplit.find(a => a.match(Bech32Regex));
|
||||
if (nostrEntity) {
|
||||
return tryParseNostrLink(nostrEntity);
|
||||
}
|
||||
}, [pageName]);
|
||||
}, [pathSplit]);
|
||||
|
||||
const { publicKey, tags } = useLogin(s => ({
|
||||
publicKey: s.publicKey,
|
||||
@ -64,7 +65,11 @@ export function Header() {
|
||||
</>
|
||||
);
|
||||
} else if (nostrLink) {
|
||||
if (nostrLink.type === NostrPrefix.Event || nostrLink.type === NostrPrefix.Note) {
|
||||
if (
|
||||
nostrLink.type === NostrPrefix.Event ||
|
||||
nostrLink.type === NostrPrefix.Note ||
|
||||
nostrLink.type === NostrPrefix.Address
|
||||
) {
|
||||
title = <NoteTitle link={nostrLink} />;
|
||||
} else if (nostrLink.type === NostrPrefix.PublicKey || nostrLink.type === NostrPrefix.Profile) {
|
||||
try {
|
||||
@ -111,17 +116,20 @@ export function Header() {
|
||||
function NoteTitle({ link }: { link: NostrLink }) {
|
||||
const ev = useEventFeed(link);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return { name: <DisplayName pubkey={ev?.pubkey ?? ""} /> };
|
||||
}, [ev?.pubkey]);
|
||||
|
||||
if (!ev?.pubkey) {
|
||||
return <FormattedMessage defaultMessage="Note" />;
|
||||
}
|
||||
|
||||
const title = findTag(ev, "title");
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage defaultMessage="Note by {name}" values={values} />
|
||||
<FormattedMessage
|
||||
defaultMessage="{note_type} by {name}{title}"
|
||||
values={{
|
||||
note_type: <KindName kind={ev.kind} />,
|
||||
name: <DisplayName pubkey={ev.pubkey} />,
|
||||
title: title ? ` - ${title}` : "",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { dedupe, unwrap } from "@snort/shared";
|
||||
import { EventKind, parseNostrLink } from "@snort/system";
|
||||
import { parseNostrLink } from "@snort/system";
|
||||
import { useEventFeed } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@ -27,7 +27,8 @@ export function ListFeedPage() {
|
||||
);
|
||||
|
||||
if (!data) return <PageSpinner />;
|
||||
if (data.kind !== EventKind.ContactList && data.kind !== EventKind.FollowSet) {
|
||||
const hasPTags = data.tags.some(a => a[0] === "p");
|
||||
if (!hasPTags) {
|
||||
return (
|
||||
<b>
|
||||
<FormattedMessage defaultMessage="Must be a contact list or pubkey list" />
|
||||
|
@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { Chat, ChatMessage, ChatType, setLastReadIn } from "@/chat";
|
||||
import { Chat, ChatMessage, ChatType } from "@/chat";
|
||||
import NoteTime from "@/Components/Event/Note/NoteTime";
|
||||
import messages from "@/Components/messages";
|
||||
import Text from "@/Components/Text/Text";
|
||||
@ -31,9 +31,7 @@ export default function DM(props: DMProps) {
|
||||
if (publisher) {
|
||||
const decrypted = await msg.decrypt(publisher);
|
||||
setContent(decrypted || "<ERROR>");
|
||||
if (!isMe) {
|
||||
setLastReadIn(msg.id);
|
||||
}
|
||||
props.chat.markRead(msg.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { Chat, ChatType, useChatSystems } from "@/chat";
|
||||
import { CollapsedSection } from "@/Components/Collapsed";
|
||||
import NoteTime from "@/Components/Event/Note/NoteTime";
|
||||
import NoteToSelf from "@/Components/User/NoteToSelf";
|
||||
import ProfileImage from "@/Components/User/ProfileImage";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import usePageDimensions from "@/Hooks/usePageDimensions";
|
||||
import useWoT from "@/Hooks/useWoT";
|
||||
import { ChatParticipantProfile } from "@/Pages/Messages/ChatParticipant";
|
||||
import DmWindow from "@/Pages/Messages/DmWindow";
|
||||
import NewChatWindow from "@/Pages/Messages/NewChatWindow";
|
||||
@ -24,8 +26,12 @@ export default function MessagesPage() {
|
||||
const { width: pageWidth } = usePageDimensions();
|
||||
|
||||
const chats = useChatSystems();
|
||||
const wot = useWoT();
|
||||
const trustedChats = chats.filter(a => wot.followDistance(a.participants[0].id) <= 2);
|
||||
const otherChats = chats.filter(a => wot.followDistance(a.participants[0].id) > 2);
|
||||
|
||||
const unreadCount = useMemo(() => chats.reduce((p, c) => p + c.unread, 0), [chats]);
|
||||
const unreadTrustedCount = useMemo(() => trustedChats.reduce((p, c) => p + c.unread, 0), [trustedChats]);
|
||||
const unreadOtherCount = useMemo(() => otherChats.reduce((p, c) => p + c.unread, 0), [otherChats]);
|
||||
|
||||
function openChat(e: React.MouseEvent<HTMLDivElement>, type: ChatType, id: string) {
|
||||
e.stopPropagation();
|
||||
@ -81,26 +87,45 @@ export default function MessagesPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function sortMessages(a: Chat, b: Chat) {
|
||||
const aSelf = a.participants.length === 1 && a.participants[0].id === login.publicKey;
|
||||
const bSelf = b.participants.length === 1 && b.participants[0].id === login.publicKey;
|
||||
if (aSelf || bSelf) {
|
||||
return aSelf ? -1 : 1;
|
||||
}
|
||||
return b.lastMessage > a.lastMessage ? 1 : -1;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 md:h-screen md:overflow-hidden">
|
||||
{(pageWidth >= TwoCol || !id) && (
|
||||
<div className="overflow-y-auto md:h-screen p-1 w-full md:w-1/3 flex-shrink-0">
|
||||
<div className="flex items-center justify-between p-2">
|
||||
<button disabled={unreadCount <= 0} type="button" className="text-sm font-semibold">
|
||||
<button
|
||||
disabled={unreadTrustedCount <= 0}
|
||||
type="button"
|
||||
className="text-sm font-semibold"
|
||||
onClick={() => {
|
||||
chats.forEach(c => c.markRead());
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Mark all read" />
|
||||
</button>
|
||||
<NewChatWindow />
|
||||
</div>
|
||||
{chats
|
||||
.sort((a, b) => {
|
||||
const aSelf = a.participants.length === 1 && a.participants[0].id === login.publicKey;
|
||||
const bSelf = b.participants.length === 1 && b.participants[0].id === login.publicKey;
|
||||
if (aSelf || bSelf) {
|
||||
return aSelf ? -1 : 1;
|
||||
}
|
||||
return b.lastMessage > a.lastMessage ? 1 : -1;
|
||||
})
|
||||
.map(conversation)}
|
||||
{trustedChats.sort(sortMessages).map(conversation)}
|
||||
{otherChats.sort(sortMessages).length > 0 && (
|
||||
<>
|
||||
<CollapsedSection
|
||||
title={
|
||||
<div className="text-xl flex items-center gap-4">
|
||||
<FormattedMessage defaultMessage="Other Chats" />
|
||||
{unreadOtherCount > 0 && <div className="has-unread" />}
|
||||
</div>
|
||||
}>
|
||||
{otherChats.map(conversation)}
|
||||
</CollapsedSection>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{id ? <DmWindow id={id} /> : pageWidth >= TwoCol && <div className="flex-1 rt-border"></div>}
|
||||
|
@ -1,21 +1,15 @@
|
||||
import { decodeTLV, EventKind, NostrPrefix } from "@snort/system";
|
||||
import { useUserSearch } from "@snort/system-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ChatType, createChatLink } from "@/chat";
|
||||
import { Nip28ChatSystem } from "@/chat/nip28";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
import Modal from "@/Components/Modal/Modal";
|
||||
import ProfileImage from "@/Components/User/ProfileImage";
|
||||
import ProfilePreview from "@/Components/User/ProfilePreview";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import Nip28ChatProfile from "@/Pages/Messages/Nip28ChatProfile";
|
||||
import { appendDedupe, debounce } from "@/Utils";
|
||||
import { LoginSession, LoginStore } from "@/Utils/Login";
|
||||
|
||||
export default function NewChatWindow() {
|
||||
const [show, setShow] = useState(false);
|
||||
@ -24,8 +18,6 @@ export default function NewChatWindow() {
|
||||
const [term, setSearchTerm] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const search = useUserSearch();
|
||||
const login = useLogin();
|
||||
const { system, publisher } = useEventPublisher();
|
||||
const { followList } = useFollowsControls();
|
||||
|
||||
useEffect(() => {
|
||||
@ -115,32 +107,6 @@ export default function NewChatWindow() {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{results.length === 1 && (
|
||||
<Nip28ChatProfile
|
||||
id={results[0]}
|
||||
onClick={async id => {
|
||||
setShow(false);
|
||||
const chats = appendDedupe(login.extraChats, [Nip28ChatSystem.chatId(id)]);
|
||||
LoginStore.updateSession({
|
||||
...login,
|
||||
extraChats: chats,
|
||||
} as LoginSession);
|
||||
const evList = await publisher?.generic(eb => {
|
||||
eb.kind(EventKind.PublicChatsList);
|
||||
chats.forEach(c => {
|
||||
if (c.startsWith(NostrPrefix.Chat28)) {
|
||||
eb.tag(["e", decodeTLV(c)[0].value as string]);
|
||||
}
|
||||
});
|
||||
return eb;
|
||||
});
|
||||
if (evList) {
|
||||
await system.BroadcastEvent(evList);
|
||||
}
|
||||
navigate(createChatLink(ChatType.PublicGroupChat, id));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { NostrLink, UserMetadata } from "@snort/system";
|
||||
import { useEventFeed } from "@snort/system-react";
|
||||
import React from "react";
|
||||
|
||||
import ProfilePreview from "@/Components/User/ProfilePreview";
|
||||
|
||||
export default function Nip28ChatProfile({ id, onClick }: { id: string; onClick: (id: string) => void }) {
|
||||
const channel = useEventFeed(new NostrLink(CONFIG.eventLinkPrefix, id, 40));
|
||||
if (channel) {
|
||||
const meta = JSON.parse(channel.content) as UserMetadata;
|
||||
return (
|
||||
<ProfilePreview
|
||||
pubkey=""
|
||||
profile={meta}
|
||||
profileImageProps={{
|
||||
link: "",
|
||||
}}
|
||||
options={{ about: false }}
|
||||
actions={<></>}
|
||||
onClick={() => onClick(id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -74,8 +74,8 @@ export function FollowersTab({ id }: { id: HexKey }) {
|
||||
const followers = useFollowersFeed(id);
|
||||
return (
|
||||
<FollowsList
|
||||
pubkeys={followers}
|
||||
className="p"
|
||||
pubkeys={followers.map(a => a.pubkey)}
|
||||
className="p flex flex-col gap-1"
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
@ -90,7 +90,7 @@ export function FollowsTab({ id }: { id: HexKey }) {
|
||||
return (
|
||||
<FollowsList
|
||||
pubkeys={follows}
|
||||
className="p"
|
||||
className="p flex flex-col gap-1"
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
|
100
packages/app/src/Pages/Root/FollowSets.tsx
Normal file
100
packages/app/src/Pages/Root/FollowSets.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { dedupe } from "@snort/shared";
|
||||
import { EventKind, NostrLink, RequestBuilder } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import { AutoLoadMore } from "@/Components/Event/LoadMore";
|
||||
import { AvatarGroup } from "@/Components/User/AvatarGroup";
|
||||
import DisplayName from "@/Components/User/DisplayName";
|
||||
import { ProfileLink } from "@/Components/User/ProfileLink";
|
||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||
import useWoT from "@/Hooks/useWoT";
|
||||
import { findTag } from "@/Utils";
|
||||
|
||||
export default function FollowSetsPage() {
|
||||
const sub = new RequestBuilder("follow-sets");
|
||||
sub.withFilter().kinds([EventKind.StarterPackSet, EventKind.FollowSet]);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const data = useRequestBuilder(sub);
|
||||
const wot = useWoT();
|
||||
const control = useFollowsControls();
|
||||
const dataSorted = wot.sortEvents(data);
|
||||
const [showN, setShowN] = useState(10);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filtered = dataSorted.filter(s => {
|
||||
if (search) {
|
||||
const ss = search.toLowerCase();
|
||||
return s.content.toLowerCase().includes(ss) || s.tags.some(t => t[1].toLowerCase().includes(ss));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="p flex flex-col gap-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={formatMessage({ defaultMessage: "Search sets.." })}
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
{filtered.slice(0, showN).map(a => {
|
||||
const title = findTag(a, "title") ?? findTag(a, "d") ?? a.content;
|
||||
const pTags = wot.sortPubkeys(dedupe(a.tags.filter(a => a[0] === "p").map(a => a[1])));
|
||||
const isFollowingAll = pTags.every(a => control.isFollowing(a));
|
||||
if (pTags.length === 0) return;
|
||||
const link = NostrLink.fromEvent(a);
|
||||
return (
|
||||
<div key={a.id} className="p br bg-gray-ultradark flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-xl">{title}</div>
|
||||
<div className="text-gray-medium font-medium flex items-center gap-2">
|
||||
<Link to={`/${link.encode()}`} state={a}>
|
||||
<FormattedMessage defaultMessage="{n} people" values={{ n: pTags.length }} />
|
||||
</Link>
|
||||
-
|
||||
<Link to={`/list-feed/${link.encode()}`}>
|
||||
<FormattedMessage defaultMessage="View Feed" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!isFollowingAll && (
|
||||
<div className="flex gap-4">
|
||||
<AsyncButton
|
||||
className="secondary"
|
||||
onClick={async () => {
|
||||
await control.addFollow(pTags);
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Follow All" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<AvatarGroup ids={pTags.slice(0, 10)} size={40} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="<dark>Created by</dark> {name}"
|
||||
values={{
|
||||
dark: c => <span className="text-gray-medium">{c}</span>,
|
||||
name: (
|
||||
<ProfileLink pubkey={a.pubkey}>
|
||||
<DisplayName pubkey={a.pubkey} />
|
||||
</ProfileLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{filtered.length > showN && <AutoLoadMore onClick={() => setShowN(n => n + 10)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
19
packages/app/src/Pages/Root/Media.tsx
Normal file
19
packages/app/src/Pages/Root/Media.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { EventKind } from "@snort/system";
|
||||
|
||||
import TimelineFollows from "@/Components/Feed/TimelineFollows";
|
||||
import { Day } from "@/Utils/Const";
|
||||
|
||||
export default function MediaPosts() {
|
||||
return (
|
||||
<div className="p">
|
||||
<TimelineFollows
|
||||
id="media"
|
||||
postsOnly={true}
|
||||
kinds={[EventKind.Photo, EventKind.Video, EventKind.ShortVideo]}
|
||||
showDisplayAsSelector={true}
|
||||
firstChunkSize={Day}
|
||||
windowSize={Day}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { lazy } from "react";
|
||||
import { Outlet, RouteObject } from "react-router-dom";
|
||||
import { Outlet, RouteObject, useLocation } from "react-router-dom";
|
||||
|
||||
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
|
||||
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
|
||||
@ -8,9 +8,10 @@ import { getCurrentRefCode } from "@/Utils";
|
||||
const InviteModal = lazy(() => import("@/Components/Invite"));
|
||||
export default function RootPage() {
|
||||
const code = getCurrentRefCode();
|
||||
const location = useLocation();
|
||||
return (
|
||||
<>
|
||||
<LiveStreams />
|
||||
{(location.pathname === "/" || location.pathname === "/following") && <LiveStreams />}
|
||||
<div className="main-content">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import SuggestedProfiles from "@/Components/SuggestedProfiles";
|
||||
import TrendingHashtags from "@/Components/Trending/TrendingHashtags";
|
||||
import TrendingNotes from "@/Components/Trending/TrendingPosts";
|
||||
import Discover from "@/Pages/Discover";
|
||||
@ -6,7 +5,9 @@ import HashTagsPage from "@/Pages/HashTagsPage";
|
||||
import { ConversationsTab } from "@/Pages/Root/ConversationsTab";
|
||||
import { DefaultTab } from "@/Pages/Root/DefaultTab";
|
||||
import { FollowedByFriendsTab } from "@/Pages/Root/FollowedByFriendsTab";
|
||||
import FollowSetsPage from "@/Pages/Root/FollowSets";
|
||||
import { ForYouTab } from "@/Pages/Root/ForYouTab";
|
||||
import MediaPosts from "@/Pages/Root/Media";
|
||||
import { NotesTab } from "@/Pages/Root/NotesTab";
|
||||
import { TagsTab } from "@/Pages/Root/TagsTab";
|
||||
import { TopicsPage } from "@/Pages/TopicsPage";
|
||||
@ -23,7 +24,9 @@ export type RootTabRoutePath =
|
||||
| "trending/hashtags"
|
||||
| "suggested"
|
||||
| "t/:tag"
|
||||
| "topics";
|
||||
| "topics"
|
||||
| "media"
|
||||
| "follow-sets";
|
||||
|
||||
export type RootTabRoute = {
|
||||
path: RootTabRoutePath;
|
||||
@ -67,14 +70,6 @@ export const RootTabRoutes: RootTabRoute[] = [
|
||||
path: "trending/hashtags",
|
||||
element: <TrendingHashtags />,
|
||||
},
|
||||
{
|
||||
path: "suggested",
|
||||
element: (
|
||||
<div className="p">
|
||||
<SuggestedProfiles />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "t/:tag",
|
||||
element: <HashTagsPage />,
|
||||
@ -83,4 +78,12 @@ export const RootTabRoutes: RootTabRoute[] = [
|
||||
path: "topics",
|
||||
element: <TopicsPage />,
|
||||
},
|
||||
{
|
||||
path: "media",
|
||||
element: <MediaPosts />,
|
||||
},
|
||||
{
|
||||
path: "follow-sets",
|
||||
element: <FollowSetsPage />,
|
||||
},
|
||||
];
|
||||
|
@ -15,7 +15,8 @@ const NOTES = 0;
|
||||
const PROFILES = 1;
|
||||
|
||||
const Profiles = ({ keyword }: { keyword: string }) => {
|
||||
const results = useProfileSearch(keyword);
|
||||
const searchFn = useProfileSearch();
|
||||
const results = useMemo(() => searchFn(keyword), [keyword, searchFn]);
|
||||
const ids = useMemo(() => results.map(r => r.pubkey), [results]);
|
||||
const content = keyword ? (
|
||||
<FollowListBase
|
||||
|
@ -21,7 +21,7 @@ const RelaySettingsPage = () => {
|
||||
async function addNewRelay() {
|
||||
const urls = removeUndefined(
|
||||
(newRelay?.trim()?.split("\n") ?? []).map(a => {
|
||||
if (!a.startsWith("wss://")) {
|
||||
if (!a.startsWith("wss://") && !a.startsWith("ws://")) {
|
||||
a = `wss://${a}`;
|
||||
}
|
||||
return sanitizeRelayUrl(a);
|
||||
|
@ -2,10 +2,8 @@ import { ReactNode } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import LndLogo from "@/assets/img/lnd-logo.png";
|
||||
import AlbyIcon from "@/Components/Icons/Alby";
|
||||
import BlueWallet from "@/Components/Icons/BlueWallet";
|
||||
//import CashuIcon from "@/Components/Icons/Cashu";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
import NWCIcon from "@/Components/Icons/NWC";
|
||||
import { getAlbyOAuth } from "@/Pages/settings/wallet/utils";
|
||||
@ -57,24 +55,12 @@ const WalletSettings = () => {
|
||||
url="/settings/wallet/nwc"
|
||||
desc={<FormattedMessage defaultMessage="Native nostr wallet connection" />}
|
||||
/>
|
||||
<WalletRow
|
||||
logo={<img src={LndLogo} />}
|
||||
name="LND via LNC"
|
||||
url="/settings/wallet/lnc"
|
||||
desc={<FormattedMessage defaultMessage="Connect to your own LND node with Lightning Node Connect" />}
|
||||
/>
|
||||
<WalletRow
|
||||
logo={<BlueWallet width={64} height={64} />}
|
||||
name="LNDHub"
|
||||
url="/settings/wallet/lndhub"
|
||||
desc={<FormattedMessage defaultMessage="Generic LNDHub wallet (BTCPayServer / Alby / LNBits)" />}
|
||||
/>
|
||||
{/*<WalletRow
|
||||
logo={<CashuIcon size={64} />}
|
||||
name="Cashu"
|
||||
url="/settings/wallet/cashu"
|
||||
desc={<FormattedMessage defaultMessage="Cashu mint wallet" />}
|
||||
/>*/}
|
||||
{CONFIG.alby && (
|
||||
<WalletRow
|
||||
logo={<AlbyIcon size={64} />}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { sanitizeRelayUrl, unwrap } from "@snort/shared";
|
||||
import { EventKind, UnknownTag } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
@ -8,35 +8,15 @@ import IconButton from "@/Components/Button/IconButton";
|
||||
import { CollapsedSection } from "@/Components/Collapsed";
|
||||
import { RelayFavicon } from "@/Components/Relay/RelaysMetadata";
|
||||
import useDiscoverMediaServers from "@/Hooks/useDiscoverMediaServers";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import { Nip96Uploader } from "@/Utils/Upload/Nip96";
|
||||
import { getRelayName } from "@/Utils";
|
||||
|
||||
export default function MediaSettingsPage() {
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
const { publisher } = useEventPublisher();
|
||||
const list = state.getList(EventKind.StorageServerList);
|
||||
const list = state.getList(EventKind.BlossomServerList);
|
||||
const [newServer, setNewServer] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const knownServers = useDiscoverMediaServers();
|
||||
|
||||
async function validateServer(url: string) {
|
||||
if (!publisher) return;
|
||||
|
||||
setError("");
|
||||
try {
|
||||
const svc = new Nip96Uploader(url, publisher);
|
||||
await svc.loadInfo();
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="text-xl">
|
||||
@ -57,7 +37,7 @@ export default function MediaSettingsPage() {
|
||||
size: 15,
|
||||
}}
|
||||
onClick={async () => {
|
||||
await state.removeFromList(EventKind.StorageServerList, [new UnknownTag(["server", addr])], true);
|
||||
await state.removeFromList(EventKind.BlossomServerList, [new UnknownTag(["server", addr])], true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -83,9 +63,9 @@ export default function MediaSettingsPage() {
|
||||
/>
|
||||
<AsyncButton
|
||||
onClick={async () => {
|
||||
if (await validateServer(newServer)) {
|
||||
if (sanitizeRelayUrl(newServer)) {
|
||||
await state.addToList(
|
||||
EventKind.StorageServerList,
|
||||
EventKind.BlossomServerList,
|
||||
[new UnknownTag(["server", new URL(newServer).toString()])],
|
||||
true,
|
||||
);
|
||||
@ -95,7 +75,6 @@ export default function MediaSettingsPage() {
|
||||
<FormattedMessage defaultMessage="Add" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
{error && <b className="text-warning">{error}</b>}
|
||||
</div>
|
||||
<CollapsedSection
|
||||
title={
|
||||
@ -127,7 +106,7 @@ export default function MediaSettingsPage() {
|
||||
<tr key={k}>
|
||||
<td className="flex gap-2 items-center">
|
||||
<RelayFavicon url={k} />
|
||||
{k}
|
||||
{getRelayName(k)}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<FormattedNumber value={v} />
|
||||
@ -136,9 +115,7 @@ export default function MediaSettingsPage() {
|
||||
<AsyncButton
|
||||
className="!py-1 mb-1"
|
||||
onClick={async () => {
|
||||
if (await validateServer(k)) {
|
||||
await state.addToList(EventKind.StorageServerList, [new UnknownTag(["server", k])], true);
|
||||
}
|
||||
await state.addToList(EventKind.BlossomServerList, [new UnknownTag(["server", k])], true);
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Add" />
|
||||
</AsyncButton>
|
||||
|
@ -1,78 +0,0 @@
|
||||
import { CashuWallet, WalletKind } from "@snort/wallet";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import { unwrap } from "@/Utils";
|
||||
import { WalletConfig, Wallets } from "@/Wallet";
|
||||
|
||||
const ConnectCashu = () => {
|
||||
const navigate = useNavigate();
|
||||
const { formatMessage } = useIntl();
|
||||
const [mintUrl, setMintUrl] = useState<string>("https://8333.space:3338");
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
async function tryConnect(config: string) {
|
||||
try {
|
||||
if (!mintUrl) {
|
||||
throw new Error("Mint URL is required");
|
||||
}
|
||||
|
||||
const connection = new CashuWallet({
|
||||
url: config,
|
||||
keys: {},
|
||||
proofs: [],
|
||||
keysets: [],
|
||||
});
|
||||
await connection.login();
|
||||
const info = await connection.getInfo();
|
||||
const newWallet = {
|
||||
id: uuid(),
|
||||
kind: WalletKind.Cashu,
|
||||
active: true,
|
||||
info,
|
||||
data: JSON.stringify(connection.getConfig()),
|
||||
} as WalletConfig;
|
||||
Wallets.add(newWallet);
|
||||
navigate("/settings/wallet");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError((e as Error).message);
|
||||
} else {
|
||||
setError(
|
||||
formatMessage({
|
||||
defaultMessage: "Unknown error",
|
||||
id: "qDwvZ4",
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Enter mint URL" id="KoFlZg" />
|
||||
</h4>
|
||||
<div className="flex">
|
||||
<div className="grow mr10">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Mint URL"
|
||||
className="w-max"
|
||||
value={mintUrl}
|
||||
onChange={e => setMintUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<AsyncButton onClick={() => tryConnect(unwrap(mintUrl))} disabled={!mintUrl}>
|
||||
<FormattedMessage defaultMessage="Connect" id="+vVZ/G" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
{error && <b className="error p10">{error}</b>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectCashu;
|
@ -1,123 +0,0 @@
|
||||
import { LNCWallet, LNWallet, WalletInfo, WalletKind } from "@snort/wallet";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import { unwrap } from "@/Utils";
|
||||
import { Wallets } from "@/Wallet";
|
||||
|
||||
const ConnectLNC = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const [pairingPhrase, setPairingPhrase] = useState<string>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [connectedLNC, setConnectedLNC] = useState<LNWallet & { setPassword(pw: string): void }>();
|
||||
const [walletInfo, setWalletInfo] = useState<WalletInfo>();
|
||||
const [walletPassword, setWalletPassword] = useState<string>();
|
||||
|
||||
async function tryConnect(cfg: string) {
|
||||
try {
|
||||
const lnc = await LNCWallet.Initialize(cfg);
|
||||
const info = await lnc.getInfo();
|
||||
|
||||
// prompt password
|
||||
setConnectedLNC(lnc);
|
||||
setWalletInfo(info as WalletInfo);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError(
|
||||
formatMessage({
|
||||
defaultMessage: "Unknown error",
|
||||
id: "qDwvZ4",
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setLNCPassword(pw: string) {
|
||||
connectedLNC?.setPassword(pw);
|
||||
Wallets.add({
|
||||
id: uuid(),
|
||||
kind: WalletKind.LNC,
|
||||
active: true,
|
||||
info: unwrap(walletInfo),
|
||||
});
|
||||
navigate("/settings/wallet");
|
||||
}
|
||||
|
||||
function flowConnect() {
|
||||
if (connectedLNC) return null;
|
||||
return (
|
||||
<>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Enter pairing phrase" />
|
||||
</h4>
|
||||
<div className="flex">
|
||||
<div className="grow mr10">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={formatMessage({ defaultMessage: "Pairing phrase", id: "8v1NN+" })}
|
||||
className="w-max"
|
||||
value={pairingPhrase}
|
||||
onChange={e => setPairingPhrase(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<AsyncButton onClick={() => tryConnect(unwrap(pairingPhrase))} disabled={!pairingPhrase}>
|
||||
<FormattedMessage defaultMessage="Connect" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
{error && <b className="error p10">{error}</b>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function flowSetPassword() {
|
||||
if (!connectedLNC) return null;
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
defaultMessage="Connected to: {node} 🎉"
|
||||
id="1c4YST"
|
||||
values={{
|
||||
node: walletInfo?.alias,
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Enter password" />
|
||||
</h4>
|
||||
<div className="flex w-max">
|
||||
<div className="grow mr10">
|
||||
<input
|
||||
type="password"
|
||||
placeholder={formatMessage({ defaultMessage: "Wallet password", id: "lTbT3s" })}
|
||||
className="w-max"
|
||||
value={walletPassword}
|
||||
onChange={e => setWalletPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<AsyncButton
|
||||
onClick={() => setLNCPassword(unwrap(walletPassword))}
|
||||
disabled={(walletPassword?.length ?? 0) < 8}>
|
||||
<FormattedMessage defaultMessage="Save" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{flowConnect()}
|
||||
{flowSetPassword()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectLNC;
|
@ -2,8 +2,6 @@ import { RouteObject } from "react-router-dom";
|
||||
|
||||
import WalletSettings from "../WalletSettings";
|
||||
import AlbyOAuth from "./Alby";
|
||||
//import ConnectCashu from "./Cashu";
|
||||
import ConnectLNC from "./LNC";
|
||||
import ConnectLNDHub from "./LNDHub";
|
||||
import ConnectNostrWallet from "./NWC";
|
||||
|
||||
@ -12,10 +10,6 @@ export const WalletSettingsRoutes = [
|
||||
path: "/settings/wallet",
|
||||
element: <WalletSettings />,
|
||||
},
|
||||
{
|
||||
path: "/settings/wallet/lnc",
|
||||
element: <ConnectLNC />,
|
||||
},
|
||||
{
|
||||
path: "/settings/wallet/lndhub",
|
||||
element: <ConnectLNDHub />,
|
||||
@ -24,10 +18,6 @@ export const WalletSettingsRoutes = [
|
||||
path: "/settings/wallet/nwc",
|
||||
element: <ConnectNostrWallet />,
|
||||
},
|
||||
/*{
|
||||
path: "/settings/wallet/cashu",
|
||||
element: <ConnectCashu />,
|
||||
},*/
|
||||
{
|
||||
path: "/settings/wallet/alby",
|
||||
element: <AlbyOAuth />,
|
||||
|
@ -3,6 +3,8 @@ import { NostrEvent, TaggedNostrEvent } from "@snort/system";
|
||||
import { ZapTarget } from "@snort/wallet";
|
||||
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
||||
|
||||
import { BlobDescriptor } from "@/Utils/Upload/blossom";
|
||||
|
||||
interface NoteCreatorDataSnapshot {
|
||||
show: boolean;
|
||||
note: string;
|
||||
@ -17,6 +19,7 @@ interface NoteCreatorDataSnapshot {
|
||||
sensitive?: string;
|
||||
pollOptions?: Array<string>;
|
||||
otherEvents?: Array<NostrEvent>;
|
||||
attachments?: Record<string, Array<BlobDescriptor>>;
|
||||
extraTags?: Array<Array<string>>;
|
||||
sending?: Array<NostrEvent>;
|
||||
sendStarted: boolean;
|
||||
|
@ -62,6 +62,9 @@ export interface LoginSession {
|
||||
*/
|
||||
publicKey?: HexKey;
|
||||
|
||||
/**
|
||||
* Login state for the current user
|
||||
*/
|
||||
state: UserState<SnortAppData>;
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
EventPublisher,
|
||||
HexKey,
|
||||
KeyStorage,
|
||||
NotEncrypted,
|
||||
RelaySettings,
|
||||
UserState,
|
||||
UserStateObject,
|
||||
@ -63,6 +62,7 @@ const LoggedOut = {
|
||||
|
||||
export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
#activeAccount?: HexKey;
|
||||
#saveDebounce?: ReturnType<typeof setTimeout>;
|
||||
#accounts: Map<string, LoginSession> = new Map();
|
||||
#publishers = new Map<string, EventPublisher>();
|
||||
|
||||
@ -107,8 +107,12 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
},
|
||||
stateObj,
|
||||
);
|
||||
stateClass.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||
stateClass.checkIsStandardList(EventKind.BlossomServerList); // track blossom list
|
||||
stateClass.on("change", () => this.#save());
|
||||
if (v.state instanceof UserState) {
|
||||
v.state.destroy();
|
||||
}
|
||||
console.debug("UserState assign = ", stateClass);
|
||||
v.state = stateClass;
|
||||
|
||||
// always activate signer
|
||||
@ -117,7 +121,6 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
this.#publishers.set(v.id, signer);
|
||||
}
|
||||
}
|
||||
this.#loadIrisKeyIfExists();
|
||||
}
|
||||
|
||||
getSessions() {
|
||||
@ -191,8 +194,8 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
stalker: stalker ?? false,
|
||||
} as LoginSession;
|
||||
|
||||
newSession.state.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||
newSession.state.on("change", () => this.#save());
|
||||
newSession.state!.checkIsStandardList(EventKind.BlossomServerList); // track blossom list
|
||||
newSession.state!.on("change", () => this.#save());
|
||||
const pub = createPublisher(newSession);
|
||||
if (pub) {
|
||||
this.#publishers.set(newSession.id, pub);
|
||||
@ -240,8 +243,8 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
appdataId: "snort",
|
||||
}),
|
||||
} as LoginSession;
|
||||
newSession.state.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||
newSession.state.on("change", () => this.#save());
|
||||
newSession.state!.checkIsStandardList(EventKind.BlossomServerList); // track blossom list
|
||||
newSession.state!.on("change", () => this.#save());
|
||||
|
||||
if ("nostr_os" in window && window?.nostr_os) {
|
||||
window?.nostr_os.saveKey(key.value);
|
||||
@ -280,22 +283,6 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
return { ...s };
|
||||
}
|
||||
|
||||
#loadIrisKeyIfExists() {
|
||||
try {
|
||||
const irisKeyJSON = window.localStorage.getItem("iris.myKey");
|
||||
if (irisKeyJSON) {
|
||||
const irisKeyObj = JSON.parse(irisKeyJSON);
|
||||
if (irisKeyObj.priv) {
|
||||
const privateKey = new NotEncrypted(irisKeyObj.priv);
|
||||
this.loginWithPrivateKey(privateKey);
|
||||
window.localStorage.removeItem("iris.myKey");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load iris key", e);
|
||||
}
|
||||
}
|
||||
|
||||
#migrate() {
|
||||
let didMigrate = false;
|
||||
|
||||
@ -367,27 +354,33 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
}
|
||||
|
||||
#save() {
|
||||
if (!this.#activeAccount && this.#accounts.size > 0) {
|
||||
this.#activeAccount = this.#accounts.keys().next().value;
|
||||
if (this.#saveDebounce !== undefined) {
|
||||
clearTimeout(this.#saveDebounce);
|
||||
}
|
||||
const toSave = [];
|
||||
for (const v of this.#accounts.values()) {
|
||||
if (v.privateKeyData instanceof KeyStorage) {
|
||||
toSave.push({
|
||||
...v,
|
||||
state: v.state instanceof UserState ? v.state.serialize() : v.state,
|
||||
privateKeyData: v.privateKeyData.toPayload(),
|
||||
});
|
||||
} else {
|
||||
toSave.push({
|
||||
...v,
|
||||
state: v.state instanceof UserState ? v.state.serialize() : v.state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.debug("Trying to save", toSave);
|
||||
window.localStorage.setItem(AccountStoreKey, JSON.stringify(toSave));
|
||||
this.notifyChange();
|
||||
this.#saveDebounce = setTimeout(() => {
|
||||
if (!this.#activeAccount && this.#accounts.size > 0) {
|
||||
this.#activeAccount = this.#accounts.keys().next().value;
|
||||
}
|
||||
const toSave = [];
|
||||
for (const v of this.#accounts.values()) {
|
||||
if (v.privateKeyData instanceof KeyStorage) {
|
||||
toSave.push({
|
||||
...v,
|
||||
state: v.state instanceof UserState ? v.state.serialize() : v.state,
|
||||
privateKeyData: v.privateKeyData.toPayload(),
|
||||
});
|
||||
} else {
|
||||
toSave.push({
|
||||
...v,
|
||||
state: v.state instanceof UserState ? v.state.serialize() : v.state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.debug("Trying to save", toSave);
|
||||
window.localStorage.setItem(AccountStoreKey, JSON.stringify(toSave));
|
||||
this.#saveDebounce = undefined;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Nip10, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
import { EventKind, Nip10, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
|
||||
/**
|
||||
* Get the chain key as a reply event
|
||||
@ -6,9 +6,14 @@ import { Nip10, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
* ie. Get the key for which this event is replying to
|
||||
*/
|
||||
export function replyChainKey(ev: TaggedNostrEvent) {
|
||||
const t = Nip10.parseThread(ev);
|
||||
const tag = t?.replyTo ?? t?.root;
|
||||
return tag?.tagKey;
|
||||
if (ev.kind !== EventKind.Comment) {
|
||||
const t = Nip10.parseThread(ev);
|
||||
const tag = t?.replyTo ?? t?.root;
|
||||
return tag?.tagKey;
|
||||
} else {
|
||||
const k = ev.tags.find(t => ["e", "a", "i"].includes(t[0]));
|
||||
return k?.[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,115 +0,0 @@
|
||||
import { base64 } from "@scure/base";
|
||||
import { throwIfOffline } from "@snort/shared";
|
||||
import { EventKind, EventPublisher, NostrEvent } from "@snort/system";
|
||||
|
||||
import { addExtensionToNip94Url, readNip94Tags, UploadResult } from ".";
|
||||
|
||||
export class Nip96Uploader {
|
||||
#info?: Nip96Info;
|
||||
|
||||
constructor(
|
||||
readonly url: string,
|
||||
readonly publisher: EventPublisher,
|
||||
) {
|
||||
this.url = new URL(this.url).toString();
|
||||
}
|
||||
|
||||
get progress() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async loadInfo() {
|
||||
const u = new URL(this.url);
|
||||
|
||||
const rsp = await fetch(`${u.protocol}//${u.host}/.well-known/nostr/nip96.json`);
|
||||
this.#info = (await rsp.json()) as Nip96Info;
|
||||
return this.#info;
|
||||
}
|
||||
|
||||
async listFiles(page = 0, count = 50) {
|
||||
const rsp = await this.#req(`?page=${page}&count=${count}`, "GET");
|
||||
if (rsp.ok) {
|
||||
return (await rsp.json()) as Nip96FileList;
|
||||
}
|
||||
}
|
||||
|
||||
async upload(file: File | Blob, filename: string): Promise<UploadResult> {
|
||||
const fd = new FormData();
|
||||
fd.append("size", file.size.toString());
|
||||
fd.append("caption", filename);
|
||||
fd.append("content_type", file.type);
|
||||
fd.append("file", file);
|
||||
|
||||
const rsp = await this.#req("", "POST", fd);
|
||||
if (rsp.ok) {
|
||||
const data = (await rsp.json()) as Nip96Result;
|
||||
if (data.status === "success") {
|
||||
const meta = readNip94Tags(data.nip94_event.tags);
|
||||
return {
|
||||
url: addExtensionToNip94Url(meta),
|
||||
header: data.nip94_event,
|
||||
metadata: meta,
|
||||
};
|
||||
}
|
||||
return {
|
||||
error: data.message,
|
||||
};
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
try {
|
||||
const obj = JSON.parse(text) as Nip96Result;
|
||||
return {
|
||||
error: obj.message,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
error: `Upload failed: ${text}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #req(path: string, method: "GET" | "POST" | "DELETE", body?: BodyInit) {
|
||||
throwIfOffline();
|
||||
const auth = async (url: string, method: string) => {
|
||||
const auth = await this.publisher.generic(eb => {
|
||||
return eb.kind(EventKind.HttpAuthentication).tag(["u", url]).tag(["method", method]);
|
||||
});
|
||||
return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`;
|
||||
};
|
||||
|
||||
const info = this.#info ?? (await this.loadInfo());
|
||||
let u = info.api_url;
|
||||
if (u.startsWith("/")) {
|
||||
u = `${this.url}${u.slice(1)}`;
|
||||
}
|
||||
u += path;
|
||||
return await fetch(u, {
|
||||
method,
|
||||
body,
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
authorization: await auth(u, method),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface Nip96Info {
|
||||
api_url: string;
|
||||
download_url?: string;
|
||||
}
|
||||
|
||||
export interface Nip96Result {
|
||||
status: string;
|
||||
message: string;
|
||||
processing_url?: string;
|
||||
nip94_event: NostrEvent;
|
||||
}
|
||||
|
||||
export interface Nip96FileList {
|
||||
count: number;
|
||||
total: number;
|
||||
page: number;
|
||||
files: Array<NostrEvent>;
|
||||
}
|
134
packages/app/src/Utils/Upload/blossom.ts
Normal file
134
packages/app/src/Utils/Upload/blossom.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { base64, bytesToString } from "@scure/base";
|
||||
import { throwIfOffline, unixNow } from "@snort/shared";
|
||||
import { EventKind, EventPublisher } from "@snort/system";
|
||||
|
||||
export interface BlobDescriptor {
|
||||
url?: string;
|
||||
sha256: string;
|
||||
size: number;
|
||||
type?: string;
|
||||
uploaded?: number;
|
||||
nip94?: Array<Array<string>>;
|
||||
}
|
||||
|
||||
export class Blossom {
|
||||
constructor(
|
||||
readonly url: string,
|
||||
readonly publisher: EventPublisher,
|
||||
) {
|
||||
this.url = new URL(this.url).toString();
|
||||
}
|
||||
|
||||
async upload(file: File) {
|
||||
const hash = await window.crypto.subtle.digest("SHA-256", await file.arrayBuffer());
|
||||
const tags = [["x", bytesToString("hex", new Uint8Array(hash))]];
|
||||
|
||||
const rsp = await this.#req("upload", "PUT", "upload", file, tags);
|
||||
if (rsp.ok) {
|
||||
const ret = (await rsp.json()) as BlobDescriptor;
|
||||
this.#fixTags(ret);
|
||||
return ret;
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
async media(file: File) {
|
||||
const hash = await window.crypto.subtle.digest("SHA-256", await file.arrayBuffer());
|
||||
const tags = [["x", bytesToString("hex", new Uint8Array(hash))]];
|
||||
|
||||
const rsp = await this.#req("media", "PUT", "media", file, tags);
|
||||
if (rsp.ok) {
|
||||
const ret = (await rsp.json()) as BlobDescriptor;
|
||||
this.#fixTags(ret);
|
||||
return ret;
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
async mirror(url: string) {
|
||||
const rsp = await this.#req("mirror", "PUT", "mirror", JSON.stringify({ url }), undefined, {
|
||||
"content-type": "application/json",
|
||||
});
|
||||
if (rsp.ok) {
|
||||
const ret = (await rsp.json()) as BlobDescriptor;
|
||||
this.#fixTags(ret);
|
||||
return ret;
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
async list(pk: string) {
|
||||
const rsp = await this.#req(`list/${pk}`, "GET", "list");
|
||||
if (rsp.ok) {
|
||||
const ret = (await rsp.json()) as Array<BlobDescriptor>;
|
||||
ret.forEach(a => this.#fixTags(a));
|
||||
return ret;
|
||||
} else {
|
||||
const text = await rsp.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
const tags = [["x", id]];
|
||||
|
||||
const rsp = await this.#req(id, "DELETE", "delete", undefined, tags);
|
||||
if (!rsp.ok) {
|
||||
const text = await rsp.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
#fixTags(r: BlobDescriptor) {
|
||||
if (!r.nip94) return;
|
||||
if (Array.isArray(r.nip94)) return;
|
||||
// blossom.band invalid response
|
||||
if (r.nip94 && "tags" in r.nip94) {
|
||||
r.nip94 = r.nip94["tags"];
|
||||
return;
|
||||
}
|
||||
r.nip94 = Object.entries(r.nip94 as Record<string, string>);
|
||||
}
|
||||
|
||||
async #req(
|
||||
path: string,
|
||||
method: "GET" | "POST" | "DELETE" | "PUT",
|
||||
term: string,
|
||||
body?: BodyInit,
|
||||
tags?: Array<Array<string>>,
|
||||
headers?: Record<string, string>,
|
||||
) {
|
||||
throwIfOffline();
|
||||
|
||||
const url = `${this.url}${path}`;
|
||||
const now = unixNow();
|
||||
const auth = async (url: string, method: string) => {
|
||||
const auth = await this.publisher.generic(eb => {
|
||||
eb.kind(24_242 as EventKind)
|
||||
.tag(["u", url])
|
||||
.tag(["method", method.toLowerCase()])
|
||||
.tag(["t", term])
|
||||
.tag(["expiration", (now + 10).toString()]);
|
||||
tags?.forEach(t => eb.tag(t));
|
||||
return eb;
|
||||
});
|
||||
return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`;
|
||||
};
|
||||
|
||||
return await fetch(url, {
|
||||
method,
|
||||
body,
|
||||
headers: {
|
||||
...headers,
|
||||
accept: "application/json",
|
||||
authorization: await auth(url, method),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -1,27 +1,11 @@
|
||||
import { EventPublisher, NostrEvent } from "@snort/system";
|
||||
import { EventPublisher, Nip94Tags, NostrEvent } from "@snort/system";
|
||||
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import { useMediaServerList } from "@/Hooks/useMediaServerList";
|
||||
import { bech32ToHex, randomSample } from "@/Utils";
|
||||
import { FileExtensionRegex, KieranPubKey } from "@/Utils/Const";
|
||||
import { KieranPubKey } from "@/Utils/Const";
|
||||
|
||||
import { Nip96Uploader } from "./Nip96";
|
||||
|
||||
export interface Nip94Tags {
|
||||
url?: string;
|
||||
mimeType?: string;
|
||||
hash?: string;
|
||||
originalHash?: string;
|
||||
size?: number;
|
||||
dimensions?: [number, number];
|
||||
magnet?: string;
|
||||
blurHash?: string;
|
||||
thumb?: string;
|
||||
image?: Array<string>;
|
||||
summary?: string;
|
||||
alt?: string;
|
||||
fallback?: Array<string>;
|
||||
}
|
||||
import { Blossom } from "./blossom";
|
||||
|
||||
export interface UploadResult {
|
||||
url?: string;
|
||||
@ -81,129 +65,8 @@ export default function useFileUpload(privKey?: string) {
|
||||
const pub = privKey ? EventPublisher.privateKey(privKey) : publisher;
|
||||
if (servers.length > 0 && pub) {
|
||||
const random = randomSample(servers, 1)[0];
|
||||
return new Nip96Uploader(random, pub);
|
||||
return new Blossom(random, pub);
|
||||
} else if (pub) {
|
||||
return new Nip96Uploader("https://nostr.build", pub);
|
||||
return new Blossom("https://blossom.build", pub);
|
||||
}
|
||||
}
|
||||
|
||||
export function addExtensionToNip94Url(meta: Nip94Tags) {
|
||||
if (!meta.url?.match(FileExtensionRegex) && meta.mimeType) {
|
||||
switch (meta.mimeType) {
|
||||
case "image/webp": {
|
||||
return `${meta.url}.webp`;
|
||||
}
|
||||
case "image/jpeg":
|
||||
case "image/jpg": {
|
||||
return `${meta.url}.jpg`;
|
||||
}
|
||||
case "video/mp4": {
|
||||
return `${meta.url}.mp4`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read NIP-94 tags from `imeta` tag
|
||||
*/
|
||||
export function readNip94TagsFromIMeta(tag: Array<string>) {
|
||||
const asTags = tag.slice(1).map(a => a.split(" ", 2));
|
||||
return readNip94Tags(asTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read NIP-94 tags from event tags
|
||||
*/
|
||||
export function readNip94Tags(tags: Array<Array<string>>) {
|
||||
const res: Nip94Tags = {};
|
||||
for (const tx of tags) {
|
||||
const [k, v] = tx;
|
||||
switch (k) {
|
||||
case "url": {
|
||||
res.url = v;
|
||||
break;
|
||||
}
|
||||
case "m": {
|
||||
res.mimeType = v;
|
||||
break;
|
||||
}
|
||||
case "x": {
|
||||
res.hash = v;
|
||||
break;
|
||||
}
|
||||
case "ox": {
|
||||
res.originalHash = v;
|
||||
break;
|
||||
}
|
||||
case "size": {
|
||||
res.size = Number(v);
|
||||
break;
|
||||
}
|
||||
case "dim": {
|
||||
res.dimensions = v.split("x").map(Number) as [number, number];
|
||||
break;
|
||||
}
|
||||
case "magnet": {
|
||||
res.magnet = v;
|
||||
break;
|
||||
}
|
||||
case "blurhash": {
|
||||
res.blurHash = v;
|
||||
break;
|
||||
}
|
||||
case "thumb": {
|
||||
res.thumb = v;
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
res.image ??= [];
|
||||
res.image.push(v);
|
||||
break;
|
||||
}
|
||||
case "summary": {
|
||||
res.summary = v;
|
||||
break;
|
||||
}
|
||||
case "alt": {
|
||||
res.alt = v;
|
||||
break;
|
||||
}
|
||||
case "fallback": {
|
||||
res.fallback ??= [];
|
||||
res.fallback.push(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function nip94TagsToIMeta(meta: Nip94Tags) {
|
||||
const ret: Array<string> = ["imeta"];
|
||||
const ifPush = (key: string, value?: string | number) => {
|
||||
if (value) {
|
||||
ret.push(`${key} ${value}`);
|
||||
}
|
||||
};
|
||||
ifPush("url", meta.url);
|
||||
ifPush("m", meta.mimeType);
|
||||
ifPush("x", meta.hash);
|
||||
ifPush("ox", meta.originalHash);
|
||||
ifPush("size", meta.size);
|
||||
ifPush("dim", meta.dimensions?.join("x"));
|
||||
ifPush("magnet", meta.magnet);
|
||||
ifPush("blurhash", meta.blurHash);
|
||||
ifPush("thumb", meta.thumb);
|
||||
ifPush("summary", meta.summary);
|
||||
ifPush("alt", meta.alt);
|
||||
if (meta.image) {
|
||||
meta.image.forEach(a => ifPush("image", a));
|
||||
}
|
||||
if (meta.fallback) {
|
||||
meta.fallback.forEach(a => ifPush("fallback", a));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,8 +1,21 @@
|
||||
import { TaggedNostrEvent } from "@snort/system";
|
||||
import { EventKind, ParsedFragment, readNip94TagsFromIMeta, TaggedNostrEvent } from "@snort/system";
|
||||
|
||||
import { transformTextCached } from "@/Hooks/useTextTransformCache";
|
||||
|
||||
export default function getEventMedia(event: TaggedNostrEvent) {
|
||||
// emulate parsed media from imeta kinds
|
||||
const mediaKinds = [EventKind.Photo, EventKind.Video, EventKind.ShortVideo];
|
||||
if (mediaKinds.includes(event.kind)) {
|
||||
const meta = event.tags.filter(a => a[0] === "imeta").map(readNip94TagsFromIMeta);
|
||||
return meta.map(
|
||||
a =>
|
||||
({
|
||||
type: "media",
|
||||
mimeType: a.mimeType,
|
||||
content: a.url,
|
||||
}) as ParsedFragment,
|
||||
);
|
||||
}
|
||||
const parsed = transformTextCached(event.id, event.content, event.tags);
|
||||
return parsed.filter(
|
||||
a => a.type === "media" && (a.mimeType?.startsWith("image/") || a.mimeType?.startsWith("video/")),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { unixNow, unwrap } from "@snort/shared";
|
||||
import { ExternalStore, unixNow, unwrap } from "@snort/shared";
|
||||
import {
|
||||
encodeTLVEntries,
|
||||
EventKind,
|
||||
@ -13,9 +13,8 @@ import {
|
||||
UserMetadata,
|
||||
} from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
||||
|
||||
import { useEmptyChatSystem } from "@/Hooks/useEmptyChatSystem";
|
||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import useModeration from "@/Hooks/useModeration";
|
||||
@ -23,7 +22,6 @@ import { findTag } from "@/Utils";
|
||||
import { LoginSession } from "@/Utils/Login";
|
||||
|
||||
import { Nip17Chats, Nip17ChatSystem } from "./nip17";
|
||||
import { Nip28Chats, Nip28ChatSystem } from "./nip28";
|
||||
|
||||
export enum ChatType {
|
||||
PublicGroupChat = 2,
|
||||
@ -57,6 +55,7 @@ export interface Chat {
|
||||
messages: Array<ChatMessage>;
|
||||
createMessage(msg: string, pub: EventPublisher): Promise<Array<NostrEvent>>;
|
||||
sendMessage(ev: Array<NostrEvent>, system: SystemInterface): void | Promise<void>;
|
||||
markRead(id?: string): void;
|
||||
}
|
||||
|
||||
export interface ChatSystem {
|
||||
@ -106,10 +105,13 @@ export function lastReadInChat(id: string) {
|
||||
return parseInt(window.localStorage.getItem(k) ?? "0");
|
||||
}
|
||||
|
||||
export function setLastReadIn(id: string) {
|
||||
const now = unixNow();
|
||||
export function setLastReadIn(id: string, time?: number) {
|
||||
const now = time ?? unixNow();
|
||||
const k = `dm:seen:${id}`;
|
||||
window.localStorage.setItem(k, now.toString());
|
||||
const current = lastReadInChat(id);
|
||||
if (current < now) {
|
||||
window.localStorage.setItem(k, now.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export function createChatLink(type: ChatType, ...params: Array<string>) {
|
||||
@ -135,41 +137,39 @@ export function createChatLink(type: ChatType, ...params: Array<string>) {
|
||||
),
|
||||
)}`;
|
||||
}
|
||||
case ChatType.PublicGroupChat: {
|
||||
return `/messages/${Nip28ChatSystem.chatId(params[0])}`;
|
||||
}
|
||||
}
|
||||
throw new Error("Unknown chat type");
|
||||
}
|
||||
|
||||
export function createEmptyChatObject(id: string, messages?: Array<TaggedNostrEvent>) {
|
||||
export function createEmptyChatObject(id: string) {
|
||||
if (id.startsWith(NostrPrefix.Chat17)) {
|
||||
return Nip17ChatSystem.createChatObj(id, []);
|
||||
}
|
||||
if (id.startsWith(NostrPrefix.Chat28)) {
|
||||
return Nip28ChatSystem.createChatObj(id, messages ?? []);
|
||||
}
|
||||
throw new Error("Cant create new empty chat, unknown id");
|
||||
}
|
||||
|
||||
export function useChatSystem(chat: ChatSystem) {
|
||||
export function useChatSystem<T extends ChatSystem & ExternalStore<Array<Chat>>>(sys: T) {
|
||||
const login = useLogin();
|
||||
const { publisher } = useEventPublisher();
|
||||
const chat = useSyncExternalStore(
|
||||
s => sys.hook(s),
|
||||
() => sys.snapshot(),
|
||||
);
|
||||
const sub = useMemo(() => {
|
||||
return chat.subscription(login);
|
||||
}, [chat, login]);
|
||||
return sys.subscription(login);
|
||||
}, [login]);
|
||||
const data = useRequestBuilder(sub);
|
||||
const { isMuted } = useModeration();
|
||||
|
||||
useEffect(() => {
|
||||
if (publisher) {
|
||||
chat.processEvents(publisher, data);
|
||||
sys.processEvents(publisher, data);
|
||||
}
|
||||
}, [data, publisher]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (login.publicKey) {
|
||||
return chat.listChats(
|
||||
return sys.listChats(
|
||||
login.publicKey,
|
||||
data.filter(a => !isMuted(a.pubkey)),
|
||||
);
|
||||
@ -179,23 +179,12 @@ export function useChatSystem(chat: ChatSystem) {
|
||||
}
|
||||
|
||||
export function useChatSystems() {
|
||||
const nip28 = useChatSystem(Nip28Chats);
|
||||
const nip17 = useChatSystem(Nip17Chats);
|
||||
|
||||
return [...nip28, ...nip17];
|
||||
return nip17;
|
||||
}
|
||||
|
||||
export function useChat(id: string) {
|
||||
const getStore = () => {
|
||||
if (id.startsWith(NostrPrefix.Chat17)) {
|
||||
return Nip17Chats;
|
||||
}
|
||||
if (id.startsWith(NostrPrefix.Chat28)) {
|
||||
return Nip28Chats;
|
||||
}
|
||||
throw new Error("Unsupported chat system");
|
||||
};
|
||||
const ret = useChatSystem(getStore()).find(a => a.id === id);
|
||||
const emptyChat = useEmptyChatSystem(ret === undefined ? id : undefined);
|
||||
return ret ?? emptyChat;
|
||||
const ret = useChatSystem(Nip17Chats).find(a => a.id === id);
|
||||
return ret;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
|
||||
import { GiftsCache } from "@/Cache";
|
||||
import { GiftWrapCache } from "@/Cache/GiftWrapCache";
|
||||
import { Chat, ChatSystem, ChatType, lastReadInChat } from "@/chat";
|
||||
import { Chat, ChatSystem, ChatType, lastReadInChat, setLastReadIn } from "@/chat";
|
||||
import { UnwrappedGift } from "@/Db";
|
||||
import { LoginSession } from "@/Utils/Login";
|
||||
import { GetPowWorker } from "@/Utils/wasm";
|
||||
@ -100,8 +100,8 @@ export class Nip17ChatSystem extends ExternalStore<Array<Chat>> implements ChatS
|
||||
type: ChatType.PrivateDirectMessage,
|
||||
id,
|
||||
title: title.title,
|
||||
unread: messages.reduce((acc, v) => (v.created_at > last ? acc++ : acc), 0),
|
||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
||||
unread: messages.reduce((acc, v) => (v.inner.created_at > last ? acc + 1 : acc), 0),
|
||||
lastMessage: messages.reduce((acc, v) => (v.inner.created_at > acc ? v.created_at : acc), 0),
|
||||
participants,
|
||||
messages: messages.map(m => ({
|
||||
id: m.id,
|
||||
@ -128,11 +128,18 @@ export class Nip17ChatSystem extends ExternalStore<Array<Chat>> implements ChatS
|
||||
messages.push(recvSealedN);
|
||||
}
|
||||
messages.push(pub.giftWrap(await pub.sealRumor(gossip, pub.pubKey), pub.pubKey, powTarget, GetPowWorker()));
|
||||
return await Promise.all(messages);
|
||||
const ret = await Promise.all(messages);
|
||||
Nip17Chats.notifyChange();
|
||||
return ret;
|
||||
},
|
||||
sendMessage: (ev, system) => {
|
||||
ev.forEach(a => system.BroadcastEvent(a));
|
||||
},
|
||||
markRead: msgId => {
|
||||
const msg = messages.find(a => a.id === msgId);
|
||||
setLastReadIn(id, msg?.inner.created_at);
|
||||
Nip17Chats.notifyChange();
|
||||
},
|
||||
} as Chat;
|
||||
}
|
||||
|
||||
|
@ -1,144 +0,0 @@
|
||||
import { unwrap } from "@snort/shared";
|
||||
import {
|
||||
decodeTLV,
|
||||
encodeTLVEntries,
|
||||
EventKind,
|
||||
NostrEvent,
|
||||
NostrPrefix,
|
||||
RequestBuilder,
|
||||
SystemInterface,
|
||||
TaggedNostrEvent,
|
||||
TLVEntryType,
|
||||
UserMetadata,
|
||||
} from "@snort/system";
|
||||
|
||||
import { Chat, ChatParticipant, ChatSystem, ChatType, lastReadInChat } from "@/chat";
|
||||
import { findTag } from "@/Utils";
|
||||
import { LoginSession } from "@/Utils/Login";
|
||||
|
||||
export class Nip28ChatSystem implements ChatSystem {
|
||||
readonly ChannelKinds = [
|
||||
EventKind.PublicChatChannel,
|
||||
EventKind.PublicChatMessage,
|
||||
EventKind.PublicChatMetadata,
|
||||
EventKind.PublicChatMuteMessage,
|
||||
EventKind.PublicChatMuteUser,
|
||||
];
|
||||
|
||||
subscription(session: LoginSession): RequestBuilder {
|
||||
const chats = (session.extraChats ?? []).filter(a => a.startsWith(NostrPrefix.Chat28));
|
||||
const chatId = (v: string) => unwrap(decodeTLV(v).find(a => a.type === TLVEntryType.Special)).value as string;
|
||||
|
||||
const rb = new RequestBuilder(`nip28:${session.id}`);
|
||||
if (chats.length > 0) {
|
||||
rb.withFilter()
|
||||
.ids(chats.map(v => chatId(v)))
|
||||
.kinds([EventKind.PublicChatChannel, EventKind.PublicChatMetadata]);
|
||||
for (const c of chats) {
|
||||
const id = chatId(c);
|
||||
rb.withFilter().tag("e", [id]).kinds(this.ChannelKinds);
|
||||
}
|
||||
}
|
||||
return rb;
|
||||
}
|
||||
|
||||
processEvents(): Promise<void> {
|
||||
// nothing to do
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
listChats(pk: string, evs: Array<TaggedNostrEvent>): Chat[] {
|
||||
const chats = this.#chatChannels(evs);
|
||||
const ret = Object.entries(chats).map(([k, v]) => {
|
||||
return Nip28ChatSystem.createChatObj(Nip28ChatSystem.chatId(k), v);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
static chatId(id: string) {
|
||||
return encodeTLVEntries(NostrPrefix.Chat28, {
|
||||
type: TLVEntryType.Special,
|
||||
value: id,
|
||||
length: id.length,
|
||||
});
|
||||
}
|
||||
|
||||
static createChatObj(id: string, messages: Array<NostrEvent>) {
|
||||
const last = lastReadInChat(id);
|
||||
const participants = decodeTLV(id)
|
||||
.filter(v => v.type === TLVEntryType.Special)
|
||||
.map(
|
||||
v =>
|
||||
({
|
||||
type: "generic",
|
||||
id: v.value as string,
|
||||
profile: this.#chatProfileFromMessages(messages),
|
||||
}) as ChatParticipant,
|
||||
);
|
||||
return {
|
||||
type: ChatType.PublicGroupChat,
|
||||
id,
|
||||
unread: messages.reduce((acc, v) => (v.created_at > last ? acc++ : acc), 0),
|
||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
||||
participants,
|
||||
messages: messages
|
||||
.filter(a => a.kind === EventKind.PublicChatMessage)
|
||||
.map(m => ({
|
||||
id: m.id,
|
||||
created_at: m.created_at,
|
||||
from: m.pubkey,
|
||||
tags: m.tags,
|
||||
content: m.content,
|
||||
needsDecryption: false,
|
||||
})),
|
||||
createMessage: async (msg, pub) => {
|
||||
return [
|
||||
await pub.generic(eb => {
|
||||
return eb.kind(EventKind.PublicChatMessage).content(msg).tag(["e", participants[0].id, "", "root"]);
|
||||
}),
|
||||
];
|
||||
},
|
||||
sendMessage: (ev, system: SystemInterface) => {
|
||||
ev.forEach(a => system.BroadcastEvent(a));
|
||||
},
|
||||
} as Chat;
|
||||
}
|
||||
|
||||
static #chatProfileFromMessages(messages: Array<NostrEvent>) {
|
||||
const chatDefs = messages.filter(
|
||||
a => a.kind === EventKind.PublicChatChannel || a.kind === EventKind.PublicChatMetadata,
|
||||
);
|
||||
const chatDef =
|
||||
chatDefs.length > 0
|
||||
? chatDefs.reduce((acc, v) => (acc.created_at > v.created_at ? acc : v), chatDefs[0])
|
||||
: undefined;
|
||||
return chatDef ? (JSON.parse(chatDef.content) as UserMetadata) : undefined;
|
||||
}
|
||||
|
||||
#chatChannels(evs: Array<TaggedNostrEvent>) {
|
||||
const chats = evs.reduce(
|
||||
(acc, v) => {
|
||||
const k = this.#chatId(v);
|
||||
if (k) {
|
||||
acc[k] ??= [];
|
||||
acc[k].push(v);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Array<NostrEvent>>,
|
||||
);
|
||||
return chats;
|
||||
}
|
||||
|
||||
#chatId(ev: NostrEvent) {
|
||||
if (ev.kind === EventKind.PublicChatChannel) {
|
||||
return ev.id;
|
||||
} else if (ev.kind === EventKind.PublicChatMetadata) {
|
||||
return findTag(ev, "e");
|
||||
} else if (this.ChannelKinds.includes(ev.kind)) {
|
||||
return ev.tags.find(a => a[0] === "e" && a[3] === "root")?.[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const Nip28Chats = new Nip28ChatSystem();
|
@ -1,109 +0,0 @@
|
||||
import { dedupe, ExternalStore, FeedCache, removeUndefined } from "@snort/shared";
|
||||
import { EventKind, NostrEvent, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
|
||||
|
||||
import { Chat, ChatSystem, ChatType, lastReadInChat } from "@/chat";
|
||||
import { LoginSession } from "@/Utils/Login";
|
||||
|
||||
export class Nip29ChatSystem extends ExternalStore<Array<Chat>> implements ChatSystem {
|
||||
readonly #cache: FeedCache<NostrEvent>;
|
||||
|
||||
constructor(cache: FeedCache<NostrEvent>) {
|
||||
super();
|
||||
this.#cache = cache;
|
||||
}
|
||||
|
||||
processEvents(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
takeSnapshot(): Chat[] {
|
||||
return this.listChats();
|
||||
}
|
||||
|
||||
subscription(session: LoginSession) {
|
||||
const id = session.publicKey;
|
||||
if (!id) return;
|
||||
const gs = id.split("/", 2);
|
||||
const rb = new RequestBuilder(`nip29:${id}`);
|
||||
const last = this.listChats().find(a => a.id === id)?.lastMessage;
|
||||
rb.withFilter()
|
||||
.relay(`wss://${gs[0]}`)
|
||||
.kinds([EventKind.SimpleChatMessage])
|
||||
.tag("g", [`/${gs[1]}`])
|
||||
.since(last);
|
||||
rb.withFilter()
|
||||
.relay(`wss://${gs[0]}`)
|
||||
.kinds([EventKind.SimpleChatMetadata])
|
||||
.tag("d", [`/${gs[1]}`]);
|
||||
return rb;
|
||||
}
|
||||
|
||||
async onEvent(evs: readonly TaggedNostrEvent[]) {
|
||||
const msg = evs.filter(a => a.kind === EventKind.SimpleChatMessage && a.tags.some(b => b[0] === "g"));
|
||||
if (msg.length > 0) {
|
||||
await this.#cache.bulkSet(msg);
|
||||
this.notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
listChats(): Chat[] {
|
||||
const allMessages = this.#nip29Chats();
|
||||
const groups = dedupe(
|
||||
removeUndefined(allMessages.map(a => a.tags.find(b => b[0] === "g"))).map(a => `${a[2]}${a[1]}`),
|
||||
);
|
||||
return groups.map(g => {
|
||||
const [relay, channel] = g.split("/", 2);
|
||||
const messages = allMessages.filter(
|
||||
a => `${a.tags.find(b => b[0] === "g")?.[2]}${a.tags.find(b => b[0] === "g")?.[1]}` === g,
|
||||
);
|
||||
const lastRead = lastReadInChat(g);
|
||||
return {
|
||||
type: ChatType.PublicGroupChat,
|
||||
id: g,
|
||||
title: `${relay}/${channel}`,
|
||||
unread: messages.reduce((acc, v) => (v.created_at > lastRead ? acc++ : acc), 0),
|
||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
||||
messages: messages.map(m => ({
|
||||
id: m.id,
|
||||
created_at: m.created_at,
|
||||
from: m.pubkey,
|
||||
tags: m.tags,
|
||||
needsDecryption: false,
|
||||
content: m.content,
|
||||
decrypt: async () => {
|
||||
return m.content;
|
||||
},
|
||||
})),
|
||||
participants: [
|
||||
{
|
||||
type: "generic",
|
||||
id: "",
|
||||
profile: {
|
||||
name: `${relay}/${channel}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
createMessage: async (msg, pub) => {
|
||||
return [
|
||||
await pub.generic(eb => {
|
||||
return eb
|
||||
.kind(EventKind.SimpleChatMessage)
|
||||
.tag(["g", `/${channel}`, relay])
|
||||
.content(msg);
|
||||
}),
|
||||
];
|
||||
},
|
||||
sendMessage: async (ev, system: SystemInterface) => {
|
||||
ev.forEach(async a => {
|
||||
system.HandleEvent("*", { ...a, relays: [] });
|
||||
await system.WriteOnceToRelay(`wss://${relay}`, a);
|
||||
});
|
||||
},
|
||||
} as Chat;
|
||||
});
|
||||
}
|
||||
|
||||
#nip29Chats() {
|
||||
return this.#cache.snapshot().filter(a => a.kind === EventKind.SimpleChatMessage);
|
||||
}
|
||||
}
|
@ -179,6 +179,10 @@ a.ext {
|
||||
-webkit-text-stroke: 1px black;
|
||||
}
|
||||
|
||||
.text-highlight {
|
||||
color: var(--highlight);
|
||||
}
|
||||
|
||||
.br {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
@ -2,16 +2,17 @@ import "./index.css";
|
||||
import "@szhsin/react-menu/dist/index.css";
|
||||
import "@/assets/fonts/inter.css";
|
||||
|
||||
import { unixNowMs } from "@snort/shared";
|
||||
import { unixNow, unixNowMs } from "@snort/shared";
|
||||
import { EventBuilder } from "@snort/system";
|
||||
import { SnortContext } from "@snort/system-react";
|
||||
import { StrictMode } from "react";
|
||||
import * as ReactDOM from "react-dom/client";
|
||||
import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-dom";
|
||||
|
||||
import { preload, UserCache } from "@/Cache";
|
||||
import { initRelayWorker, preload, Relay, UserCache } from "@/Cache";
|
||||
import { ThreadRoute } from "@/Components/Event/Thread/ThreadRoute";
|
||||
import { IntlProvider } from "@/Components/IntlProvider/IntlProvider";
|
||||
import { db } from "@/Db";
|
||||
import { addCachedMetadataToFuzzySearch } from "@/Db/FuzzySearch";
|
||||
import { AboutPage } from "@/Pages/About";
|
||||
import { DebugPage } from "@/Pages/CacheDebug";
|
||||
@ -39,6 +40,7 @@ import { WalletSendPage } from "@/Pages/wallet/send";
|
||||
import ZapPoolPage from "@/Pages/ZapPool/ZapPool";
|
||||
import { System } from "@/system";
|
||||
import { storeRefCode, unwrap } from "@/Utils";
|
||||
import { hasWasm, wasmInit, WasmPath } from "@/Utils/wasm";
|
||||
import { Wallets } from "@/Wallet";
|
||||
import { setupWebLNWalletConfig } from "@/Wallet";
|
||||
|
||||
@ -52,10 +54,14 @@ async function initSite() {
|
||||
"31990:84de35e2584d2b144aae823c9ed0b0f3deda09648530b93d1a2a146d1dea9864:app-profile",
|
||||
];
|
||||
storeRefCode();
|
||||
if (hasWasm) {
|
||||
await wasmInit(WasmPath);
|
||||
await initRelayWorker();
|
||||
}
|
||||
|
||||
setupWebLNWalletConfig(Wallets);
|
||||
|
||||
//db.ready = await db.isAvailable();
|
||||
db.ready = await db.isAvailable();
|
||||
|
||||
const login = LoginStore.snapshot();
|
||||
preload(login.state.follows).then(async () => {
|
||||
@ -78,7 +84,7 @@ async function initSite() {
|
||||
});
|
||||
|
||||
// cleanup
|
||||
//Relay.delete(["REQ", "cleanup", { kinds: [1, 7, 9735], until: unixNow() - Day * 30 }]);
|
||||
Relay.delete(["REQ", "cleanup", { kinds: [1, 7, 9735], until: unixNow() - Day * 30 }]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -17,9 +17,18 @@
|
||||
"+Vxixo": {
|
||||
"defaultMessage": "Secret Group Chat"
|
||||
},
|
||||
"+W+Kof": {
|
||||
"defaultMessage": "Cashu Wallet History"
|
||||
},
|
||||
"+aZY2h": {
|
||||
"defaultMessage": "Zap Type"
|
||||
},
|
||||
"+mO9i4": {
|
||||
"defaultMessage": "Code Snippet"
|
||||
},
|
||||
"+sDqqq": {
|
||||
"defaultMessage": "Peer-to-peer Order events"
|
||||
},
|
||||
"+tShPg": {
|
||||
"defaultMessage": "following"
|
||||
},
|
||||
@ -53,9 +62,6 @@
|
||||
"/Xf4UW": {
|
||||
"defaultMessage": "Send anonymous usage metrics"
|
||||
},
|
||||
"/b1IHW": {
|
||||
"defaultMessage": "Group Chat Message"
|
||||
},
|
||||
"/d6vEc": {
|
||||
"defaultMessage": "Make your profile easier to find and share"
|
||||
},
|
||||
@ -92,6 +98,9 @@
|
||||
"0mch2Y": {
|
||||
"defaultMessage": "name has disallowed characters"
|
||||
},
|
||||
"0oMk/p": {
|
||||
"defaultMessage": "Other Chats"
|
||||
},
|
||||
"0siT4z": {
|
||||
"defaultMessage": "Politics"
|
||||
},
|
||||
@ -108,6 +117,9 @@
|
||||
"1/BFEj": {
|
||||
"defaultMessage": "git stuff"
|
||||
},
|
||||
"1GvBMj": {
|
||||
"defaultMessage": "internal reference"
|
||||
},
|
||||
"1Mo59U": {
|
||||
"defaultMessage": "Are you sure you want to remove this note from bookmarks?"
|
||||
},
|
||||
@ -117,9 +129,6 @@
|
||||
"1UWegE": {
|
||||
"defaultMessage": "Be sure to back up your keys!"
|
||||
},
|
||||
"1c4YST": {
|
||||
"defaultMessage": "Connected to: {node} 🎉"
|
||||
},
|
||||
"1nYUGC": {
|
||||
"defaultMessage": "{n} Following"
|
||||
},
|
||||
@ -156,15 +165,15 @@
|
||||
"2IFGap": {
|
||||
"defaultMessage": "Donate"
|
||||
},
|
||||
"2LbrkB": {
|
||||
"defaultMessage": "Enter password"
|
||||
},
|
||||
"2O2sfp": {
|
||||
"defaultMessage": "Finish"
|
||||
},
|
||||
"2Qsf9/": {
|
||||
"defaultMessage": "Generic lists"
|
||||
},
|
||||
"2YxhJx": {
|
||||
"defaultMessage": "Reserved Cashu Wallet Tokens"
|
||||
},
|
||||
"2a2YiP": {
|
||||
"defaultMessage": "{n} Bookmarks"
|
||||
},
|
||||
@ -186,9 +195,6 @@
|
||||
"2z7Kky": {
|
||||
"defaultMessage": "Latest Articles"
|
||||
},
|
||||
"3/onCd": {
|
||||
"defaultMessage": "Replies"
|
||||
},
|
||||
"39AHJm": {
|
||||
"defaultMessage": "Sign Up"
|
||||
},
|
||||
@ -213,9 +219,6 @@
|
||||
"3gOsZq": {
|
||||
"defaultMessage": "Translators"
|
||||
},
|
||||
"3kbIhS": {
|
||||
"defaultMessage": "Untitled"
|
||||
},
|
||||
"3qnJlS": {
|
||||
"defaultMessage": "You are voting with {amount} sats"
|
||||
},
|
||||
@ -258,6 +261,9 @@
|
||||
"4OB335": {
|
||||
"defaultMessage": "Dislike"
|
||||
},
|
||||
"4OQuna": {
|
||||
"defaultMessage": "Draft Event"
|
||||
},
|
||||
"4P/kKm": {
|
||||
"defaultMessage": "Private Key Encryption"
|
||||
},
|
||||
@ -270,9 +276,18 @@
|
||||
"4emo2p": {
|
||||
"defaultMessage": "Missing Relays"
|
||||
},
|
||||
"4oPRxH": {
|
||||
"defaultMessage": "Supported Kinds:"
|
||||
},
|
||||
"4rYCjn": {
|
||||
"defaultMessage": "Note to Self"
|
||||
},
|
||||
"4rxi5n": {
|
||||
"defaultMessage": "hardcopy reference"
|
||||
},
|
||||
"4tKMJS": {
|
||||
"defaultMessage": "Software Application"
|
||||
},
|
||||
"4wgYpI": {
|
||||
"defaultMessage": "Recommended Application Handlers"
|
||||
},
|
||||
@ -333,12 +348,21 @@
|
||||
"6WWD34": {
|
||||
"defaultMessage": "Looking for: {noteId}"
|
||||
},
|
||||
"6Z3bDG": {
|
||||
"defaultMessage": "Cashu Wallet Event"
|
||||
},
|
||||
"6bgpn+": {
|
||||
"defaultMessage": "Not all clients support this, you may still receive some zaps as if zap splits was not configured"
|
||||
},
|
||||
"6dmn4m": {
|
||||
"defaultMessage": "Thread"
|
||||
},
|
||||
"6ewQqw": {
|
||||
"defaultMessage": "Likes ({n})"
|
||||
},
|
||||
"6m1Zkw": {
|
||||
"defaultMessage": "Relay Monitor Announcement"
|
||||
},
|
||||
"6mr8WU": {
|
||||
"defaultMessage": "Followed by"
|
||||
},
|
||||
@ -384,9 +408,6 @@
|
||||
"7hp70g": {
|
||||
"defaultMessage": "NIP-05"
|
||||
},
|
||||
"7jfPsW": {
|
||||
"defaultMessage": "Modular Article Content"
|
||||
},
|
||||
"7nAz/z": {
|
||||
"defaultMessage": "Mute notes from people who are outside your web of trust"
|
||||
},
|
||||
@ -402,6 +423,9 @@
|
||||
"8BDFvJ": {
|
||||
"defaultMessage": "Conventions for clients' use of e and p tags in text events"
|
||||
},
|
||||
"8Cw1Fj": {
|
||||
"defaultMessage": "Link Set"
|
||||
},
|
||||
"8ED/4u": {
|
||||
"defaultMessage": "Reply To"
|
||||
},
|
||||
@ -417,18 +441,12 @@
|
||||
"8Y6bZQ": {
|
||||
"defaultMessage": "Invalid zap split: {input}"
|
||||
},
|
||||
"8ZGqWl": {
|
||||
"defaultMessage": "Group Thread"
|
||||
},
|
||||
"8g2vyB": {
|
||||
"defaultMessage": "name too long"
|
||||
},
|
||||
"8jmwT8": {
|
||||
"defaultMessage": "bech32-encoded entities"
|
||||
},
|
||||
"8v1NN+": {
|
||||
"defaultMessage": "Pairing phrase"
|
||||
},
|
||||
"8xdDLn": {
|
||||
"defaultMessage": "Follow sets"
|
||||
},
|
||||
@ -438,9 +456,15 @@
|
||||
"9+Ddtu": {
|
||||
"defaultMessage": "Next"
|
||||
},
|
||||
"92gdbw": {
|
||||
"defaultMessage": "Relay Discovery"
|
||||
},
|
||||
"9HU8vw": {
|
||||
"defaultMessage": "Reply"
|
||||
},
|
||||
"9RNiUn": {
|
||||
"defaultMessage": "View Feed"
|
||||
},
|
||||
"9SvQep": {
|
||||
"defaultMessage": "Follows {n}"
|
||||
},
|
||||
@ -450,6 +474,9 @@
|
||||
"9WRlF4": {
|
||||
"defaultMessage": "Send"
|
||||
},
|
||||
"9WTAKy": {
|
||||
"defaultMessage": "Search sets.."
|
||||
},
|
||||
"9kO0VQ": {
|
||||
"defaultMessage": "Hide muted notes"
|
||||
},
|
||||
@ -469,9 +496,6 @@
|
||||
"defaultMessage": "Parent",
|
||||
"description": "Link to parent note in thread"
|
||||
},
|
||||
"ALdW69": {
|
||||
"defaultMessage": "Note by {name}"
|
||||
},
|
||||
"AN0Z7Q": {
|
||||
"defaultMessage": "Muted Words"
|
||||
},
|
||||
@ -508,6 +532,9 @@
|
||||
"AyGauy": {
|
||||
"defaultMessage": "Login"
|
||||
},
|
||||
"Ayx8rG": {
|
||||
"defaultMessage": "Curated Publication Index"
|
||||
},
|
||||
"B4C47Y": {
|
||||
"defaultMessage": "name too short"
|
||||
},
|
||||
@ -560,15 +587,15 @@
|
||||
"C8FsOr": {
|
||||
"defaultMessage": "Popular Servers"
|
||||
},
|
||||
"C8HhVE": {
|
||||
"defaultMessage": "Suggested Follows"
|
||||
},
|
||||
"CA1efg": {
|
||||
"defaultMessage": "Video sets"
|
||||
},
|
||||
"CHTbO3": {
|
||||
"defaultMessage": "Failed to load invoice"
|
||||
},
|
||||
"CJ0biq": {
|
||||
"defaultMessage": "Poll Response"
|
||||
},
|
||||
"CJx5Nd": {
|
||||
"defaultMessage": "Profile Zaps"
|
||||
},
|
||||
@ -578,6 +605,9 @@
|
||||
"CM0k0d": {
|
||||
"defaultMessage": "Prune follow list"
|
||||
},
|
||||
"CSOaM+": {
|
||||
"defaultMessage": "{note_type} by {name}{title}"
|
||||
},
|
||||
"CVWeJ6": {
|
||||
"defaultMessage": "Trending People"
|
||||
},
|
||||
@ -755,6 +785,12 @@
|
||||
"GL8aXW": {
|
||||
"defaultMessage": "Bookmarks ({n})"
|
||||
},
|
||||
"GLdw/8": {
|
||||
"defaultMessage": "File Message"
|
||||
},
|
||||
"GQo+OV": {
|
||||
"defaultMessage": "prompt reference"
|
||||
},
|
||||
"GSye7T": {
|
||||
"defaultMessage": "Lightning Address"
|
||||
},
|
||||
@ -767,12 +803,22 @@
|
||||
"GpkNYn": {
|
||||
"defaultMessage": "Torrent"
|
||||
},
|
||||
"GqKcVm": {
|
||||
"defaultMessage": "App curation sets"
|
||||
},
|
||||
"GqQeu/": {
|
||||
"defaultMessage": "Invalid Lightning Address"
|
||||
},
|
||||
"GrDnue": {
|
||||
"defaultMessage": "Nutzap Mint Recommendation"
|
||||
},
|
||||
"GspYR7": {
|
||||
"defaultMessage": "{n} Dislike"
|
||||
},
|
||||
"GtIxzZ": {
|
||||
"defaultMessage": "via {client}",
|
||||
"description": "via {client name} tag"
|
||||
},
|
||||
"Gxcr08": {
|
||||
"defaultMessage": "Broadcast Event"
|
||||
},
|
||||
@ -819,6 +865,9 @@
|
||||
"HqRNN8": {
|
||||
"defaultMessage": "Support"
|
||||
},
|
||||
"Hqo/rL": {
|
||||
"defaultMessage": "Curated Publication Content"
|
||||
},
|
||||
"HzSFeV": {
|
||||
"defaultMessage": "Expiration Timestamp"
|
||||
},
|
||||
@ -1120,6 +1169,9 @@
|
||||
"P8JC58": {
|
||||
"defaultMessage": "Distance"
|
||||
},
|
||||
"P8zI6H": {
|
||||
"defaultMessage": "Slide Set"
|
||||
},
|
||||
"PCSt5T": {
|
||||
"defaultMessage": "Preferences"
|
||||
},
|
||||
@ -1156,6 +1208,9 @@
|
||||
"R/6nsx": {
|
||||
"defaultMessage": "Subscription"
|
||||
},
|
||||
"R7x0mX": {
|
||||
"defaultMessage": "Chat Message"
|
||||
},
|
||||
"R81upa": {
|
||||
"defaultMessage": "People you follow"
|
||||
},
|
||||
@ -1228,6 +1283,12 @@
|
||||
"SmuYUd": {
|
||||
"defaultMessage": "What should we call you?"
|
||||
},
|
||||
"So+Y+A": {
|
||||
"defaultMessage": "Nutzap"
|
||||
},
|
||||
"SopQOK": {
|
||||
"defaultMessage": "Web bookmarks"
|
||||
},
|
||||
"Ss0sWu": {
|
||||
"defaultMessage": "Pay Now"
|
||||
},
|
||||
@ -1286,6 +1347,9 @@
|
||||
"TwyMau": {
|
||||
"defaultMessage": "Account"
|
||||
},
|
||||
"U/fbvs": {
|
||||
"defaultMessage": "Git Replies (deprecated)"
|
||||
},
|
||||
"U1aPPi": {
|
||||
"defaultMessage": "Stop listening"
|
||||
},
|
||||
@ -1298,6 +1362,9 @@
|
||||
"ULXFfP": {
|
||||
"defaultMessage": "Receive"
|
||||
},
|
||||
"ULsJTk": {
|
||||
"defaultMessage": "Published by"
|
||||
},
|
||||
"UNjfWJ": {
|
||||
"defaultMessage": "Check all event signatures received from relays"
|
||||
},
|
||||
@ -1331,6 +1398,9 @@
|
||||
"V20Og0": {
|
||||
"defaultMessage": "Labeling"
|
||||
},
|
||||
"V93INS": {
|
||||
"defaultMessage": "<dark>Created by</dark> {name}"
|
||||
},
|
||||
"VOjC1i": {
|
||||
"defaultMessage": "Pick which upload service you want to upload attachments to"
|
||||
},
|
||||
@ -1361,6 +1431,9 @@
|
||||
"W9355R": {
|
||||
"defaultMessage": "Unmute"
|
||||
},
|
||||
"WTrOy3": {
|
||||
"defaultMessage": "Chat"
|
||||
},
|
||||
"WeLEuL": {
|
||||
"defaultMessage": "From Server"
|
||||
},
|
||||
@ -1415,6 +1488,9 @@
|
||||
"YH2RKk": {
|
||||
"defaultMessage": "Popular media servers."
|
||||
},
|
||||
"YLGfQn": {
|
||||
"defaultMessage": "Write message"
|
||||
},
|
||||
"YQZY/S": {
|
||||
"defaultMessage": "It looks like you dont follow enough people, take a look at {newUsersPage} to discover people to follow!"
|
||||
},
|
||||
@ -1436,9 +1512,6 @@
|
||||
"Z48UEo": {
|
||||
"defaultMessage": "Channel Metadata"
|
||||
},
|
||||
"Z4BMCZ": {
|
||||
"defaultMessage": "Enter pairing phrase"
|
||||
},
|
||||
"Z7kkeJ": {
|
||||
"defaultMessage": "Delegated Event Signing"
|
||||
},
|
||||
@ -1454,6 +1527,9 @@
|
||||
"ZS+jRE": {
|
||||
"defaultMessage": "Send zap splits to"
|
||||
},
|
||||
"ZT17bG": {
|
||||
"defaultMessage": "Cashu Wallet Tokens"
|
||||
},
|
||||
"Zff6lu": {
|
||||
"defaultMessage": "Username iris.to/<b>{name}</b> is reserved for you!"
|
||||
},
|
||||
@ -1481,9 +1557,6 @@
|
||||
"aRex7h": {
|
||||
"defaultMessage": "Paid {amount} sats, fee {fee} sats"
|
||||
},
|
||||
"aSGz4J": {
|
||||
"defaultMessage": "Connect to your own LND node with Lightning Node Connect"
|
||||
},
|
||||
"aWpBzj": {
|
||||
"defaultMessage": "Show more"
|
||||
},
|
||||
@ -1544,6 +1617,12 @@
|
||||
"c3g2hL": {
|
||||
"defaultMessage": "Broadcast Again"
|
||||
},
|
||||
"c6BMLV": {
|
||||
"defaultMessage": "Starter Pack"
|
||||
},
|
||||
"cF3ruj": {
|
||||
"defaultMessage": "Follow All"
|
||||
},
|
||||
"cFbU1B": {
|
||||
"defaultMessage": "Using Alby? Go to {link} to get your NWC config!"
|
||||
},
|
||||
@ -1605,9 +1684,6 @@
|
||||
"dOQCL8": {
|
||||
"defaultMessage": "Display name"
|
||||
},
|
||||
"dZZIGe": {
|
||||
"defaultMessage": "Modular Article Header"
|
||||
},
|
||||
"ddd3JX": {
|
||||
"defaultMessage": "Popular Hashtags"
|
||||
},
|
||||
@ -1725,6 +1801,9 @@
|
||||
"gDzDRs": {
|
||||
"defaultMessage": "Emoji to send when reactiong to a note"
|
||||
},
|
||||
"gPxSgn": {
|
||||
"defaultMessage": "Follow Sets"
|
||||
},
|
||||
"gXgY3+": {
|
||||
"defaultMessage": "Not all clients support this yet"
|
||||
},
|
||||
@ -1749,9 +1828,15 @@
|
||||
"grQ+mI": {
|
||||
"defaultMessage": "Proof of Work"
|
||||
},
|
||||
"grRQTM": {
|
||||
"defaultMessage": "{n} people"
|
||||
},
|
||||
"gtNjNP": {
|
||||
"defaultMessage": "Basic protocol flow description"
|
||||
},
|
||||
"h1gtUi": {
|
||||
"defaultMessage": "Poll"
|
||||
},
|
||||
"h7jvCs": {
|
||||
"defaultMessage": "{site} is more fun together!"
|
||||
},
|
||||
@ -1929,9 +2014,6 @@
|
||||
"lPWASz": {
|
||||
"defaultMessage": "Snort nostr address"
|
||||
},
|
||||
"lTbT3s": {
|
||||
"defaultMessage": "Wallet password"
|
||||
},
|
||||
"lbr3Lq": {
|
||||
"defaultMessage": "Copy link"
|
||||
},
|
||||
@ -2124,6 +2206,9 @@
|
||||
"qtWLmt": {
|
||||
"defaultMessage": "Like"
|
||||
},
|
||||
"qx+v3H": {
|
||||
"defaultMessage": "Request to Vanish"
|
||||
},
|
||||
"qyJtWy": {
|
||||
"defaultMessage": "Show less"
|
||||
},
|
||||
@ -2190,6 +2275,9 @@
|
||||
"saorw+": {
|
||||
"defaultMessage": "Event Deletion Request"
|
||||
},
|
||||
"sbSCT3": {
|
||||
"defaultMessage": "Private event relay list"
|
||||
},
|
||||
"sfL/O+": {
|
||||
"defaultMessage": "Muted notes will not be shown"
|
||||
},
|
||||
@ -2214,9 +2302,6 @@
|
||||
"tU0ADf": {
|
||||
"defaultMessage": "Unknown NIP-{x}"
|
||||
},
|
||||
"tVuVg9": {
|
||||
"defaultMessage": "Video View Event"
|
||||
},
|
||||
"tf1lIh": {
|
||||
"defaultMessage": "Free"
|
||||
},
|
||||
@ -2265,6 +2350,9 @@
|
||||
"uc0din": {
|
||||
"defaultMessage": "Send sats splits to"
|
||||
},
|
||||
"uex/ui": {
|
||||
"defaultMessage": "external web reference"
|
||||
},
|
||||
"ufvXH1": {
|
||||
"defaultMessage": "Found {n} events"
|
||||
},
|
||||
@ -2325,9 +2413,6 @@
|
||||
"wc9st7": {
|
||||
"defaultMessage": "Media Attachments"
|
||||
},
|
||||
"whSrs+": {
|
||||
"defaultMessage": "Nostr Public Chat"
|
||||
},
|
||||
"wih7iJ": {
|
||||
"defaultMessage": "name is blocked"
|
||||
},
|
||||
@ -2343,6 +2428,9 @@
|
||||
"wtLjP6": {
|
||||
"defaultMessage": "Copy ID"
|
||||
},
|
||||
"wvoA3H": {
|
||||
"defaultMessage": "Picture"
|
||||
},
|
||||
"x+3fl6": {
|
||||
"defaultMessage": "My Relays"
|
||||
},
|
||||
|
@ -1,24 +1,25 @@
|
||||
import { removeUndefined, throwIfOffline } from "@snort/shared";
|
||||
import { mapEventToProfile, NostrEvent, NostrSystem } from "@snort/system";
|
||||
|
||||
import { EventsCache, Relay, RelayMetrics, SystemDb, UserCache, UserFollows, UserRelays } from "@/Cache";
|
||||
import { addEventToFuzzySearch } from "@/Db/FuzzySearch";
|
||||
import { LoginStore } from "@/Utils/Login";
|
||||
import { hasWasm, WasmOptimizer } from "@/Utils/wasm";
|
||||
|
||||
/**
|
||||
* Singleton nostr system
|
||||
*/
|
||||
export const System = new NostrSystem({
|
||||
//relays: UserRelays,
|
||||
//events: EventsCache,
|
||||
//profiles: UserCache,
|
||||
//relayMetrics: RelayMetrics,
|
||||
//cachingRelay: Relay,
|
||||
//contactLists: UserFollows,
|
||||
//optimizer: hasWasm ? WasmOptimizer : undefined,
|
||||
//db: SystemDb,
|
||||
relays: UserRelays,
|
||||
events: EventsCache,
|
||||
profiles: UserCache,
|
||||
relayMetrics: RelayMetrics,
|
||||
cachingRelay: Relay,
|
||||
contactLists: UserFollows,
|
||||
optimizer: hasWasm ? WasmOptimizer : undefined,
|
||||
db: SystemDb,
|
||||
buildFollowGraph: true,
|
||||
automaticOutboxModel: true,
|
||||
checkSigs: false,
|
||||
});
|
||||
|
||||
System.on("auth", async (c, r, cb) => {
|
||||
@ -30,8 +31,8 @@ System.on("auth", async (c, r, cb) => {
|
||||
});
|
||||
|
||||
System.on("event", (_, ev) => {
|
||||
//EventsCache.discover(ev);
|
||||
//UserCache.discover(ev);
|
||||
EventsCache.discover(ev);
|
||||
UserCache.discover(ev);
|
||||
addEventToFuzzySearch(ev);
|
||||
});
|
||||
|
||||
|
@ -5,7 +5,10 @@
|
||||
"+QMdsy": "Relay Stats",
|
||||
"+UjDmN": "Logged in with write access",
|
||||
"+Vxixo": "Secret Group Chat",
|
||||
"+W+Kof": "Cashu Wallet History",
|
||||
"+aZY2h": "Zap Type",
|
||||
"+mO9i4": "Code Snippet",
|
||||
"+sDqqq": "Peer-to-peer Order events",
|
||||
"+tShPg": "following",
|
||||
"+vA//S": "Logins",
|
||||
"+vIQlC": "Please make sure to save the following password in order to manage your handle in the future",
|
||||
@ -17,7 +20,6 @@
|
||||
"/JE/X+": "Account Support",
|
||||
"/T7HId": "HTTP File Storage Integration",
|
||||
"/Xf4UW": "Send anonymous usage metrics",
|
||||
"/b1IHW": "Group Chat Message",
|
||||
"/d6vEc": "Make your profile easier to find and share",
|
||||
"/ioUrF": "From File",
|
||||
"/n5KSF": "{n} ms",
|
||||
@ -30,15 +32,16 @@
|
||||
"0jOEtS": "Invalid LNURL",
|
||||
"0kOBMu": "Handling Mentions",
|
||||
"0mch2Y": "name has disallowed characters",
|
||||
"0oMk/p": "Other Chats",
|
||||
"0siT4z": "Politics",
|
||||
"0uoY11": "Show Status",
|
||||
"0yO7wF": "{n} secs",
|
||||
"0zASjL": "Go",
|
||||
"1/BFEj": "git stuff",
|
||||
"1GvBMj": "internal reference",
|
||||
"1Mo59U": "Are you sure you want to remove this note from bookmarks?",
|
||||
"1R43+L": "Enter Nostr Wallet Connect config",
|
||||
"1UWegE": "Be sure to back up your keys!",
|
||||
"1c4YST": "Connected to: {node} 🎉",
|
||||
"1nYUGC": "{n} Following",
|
||||
"1o2BgB": "Check Signatures",
|
||||
"1ozeyg": "Nature",
|
||||
@ -51,9 +54,9 @@
|
||||
"2BBGxX": "Subject tag in text events",
|
||||
"2HIqeO": "User emoji list",
|
||||
"2IFGap": "Donate",
|
||||
"2LbrkB": "Enter password",
|
||||
"2O2sfp": "Finish",
|
||||
"2Qsf9/": "Generic lists",
|
||||
"2YxhJx": "Reserved Cashu Wallet Tokens",
|
||||
"2a2YiP": "{n} Bookmarks",
|
||||
"2k0Cv+": "Dislikes ({n})",
|
||||
"2mcwT8": "New Note",
|
||||
@ -61,7 +64,6 @@
|
||||
"2raFAu": "Application-specific data",
|
||||
"2ukA4d": "{n} hours",
|
||||
"2z7Kky": "Latest Articles",
|
||||
"3/onCd": "Replies",
|
||||
"39AHJm": "Sign Up",
|
||||
"3GWu6/": "User Statuses",
|
||||
"3KNMbJ": "Articles",
|
||||
@ -70,7 +72,6 @@
|
||||
"3adEeb": "{n} viewers",
|
||||
"3cc4Ct": "Light",
|
||||
"3gOsZq": "Translators",
|
||||
"3kbIhS": "Untitled",
|
||||
"3qnJlS": "You are voting with {amount} sats",
|
||||
"3t3kok": "{n,plural,=1{{n} new note} other{{n} new notes}}",
|
||||
"3tVy+Z": "{n} Followers",
|
||||
@ -85,11 +86,15 @@
|
||||
"4L2vUY": "Your new NIP-05 handle is:",
|
||||
"4MjsHk": "Life",
|
||||
"4OB335": "Dislike",
|
||||
"4OQuna": "Draft Event",
|
||||
"4P/kKm": "Private Key Encryption",
|
||||
"4Vmpt4": "Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices",
|
||||
"4Z3t5i": "Use imgproxy to compress images",
|
||||
"4emo2p": "Missing Relays",
|
||||
"4oPRxH": "Supported Kinds:",
|
||||
"4rYCjn": "Note to Self",
|
||||
"4rxi5n": "hardcopy reference",
|
||||
"4tKMJS": "Software Application",
|
||||
"4wgYpI": "Recommended Application Handlers",
|
||||
"5BVs2e": "zap",
|
||||
"5CB6zB": "Zap Splits",
|
||||
@ -110,8 +115,11 @@
|
||||
"6KGebm": "Seal",
|
||||
"6OSOXl": "Reason: <i>{reason}</i>",
|
||||
"6WWD34": "Looking for: {noteId}",
|
||||
"6Z3bDG": "Cashu Wallet Event",
|
||||
"6bgpn+": "Not all clients support this, you may still receive some zaps as if zap splits was not configured",
|
||||
"6dmn4m": "Thread",
|
||||
"6ewQqw": "Likes ({n})",
|
||||
"6m1Zkw": "Relay Monitor Announcement",
|
||||
"6mr8WU": "Followed by",
|
||||
"6pdxsi": "Extra metadata fields and tags",
|
||||
"6uMqL1": "Unpaid",
|
||||
@ -127,35 +135,35 @@
|
||||
"7YkSA2": "Community Leader",
|
||||
"7gMmSL": "Reaction",
|
||||
"7hp70g": "NIP-05",
|
||||
"7jfPsW": "Modular Article Content",
|
||||
"7nAz/z": "Mute notes from people who are outside your web of trust",
|
||||
"7pFGAQ": "Close Relays",
|
||||
"8/vBbP": "Reposts ({n})",
|
||||
"89q5wc": "Confirm Reposts",
|
||||
"8BDFvJ": "Conventions for clients' use of e and p tags in text events",
|
||||
"8Cw1Fj": "Link Set",
|
||||
"8ED/4u": "Reply To",
|
||||
"8HJxXG": "Sign up",
|
||||
"8QDesP": "Zap {n} sats",
|
||||
"8Rkoyb": "Recipient",
|
||||
"8Y6bZQ": "Invalid zap split: {input}",
|
||||
"8ZGqWl": "Group Thread",
|
||||
"8g2vyB": "name too long",
|
||||
"8jmwT8": "bech32-encoded entities",
|
||||
"8v1NN+": "Pairing phrase",
|
||||
"8xdDLn": "Follow sets",
|
||||
"8za9Pq": "Draft Classified Listing",
|
||||
"9+Ddtu": "Next",
|
||||
"92gdbw": "Relay Discovery",
|
||||
"9HU8vw": "Reply",
|
||||
"9RNiUn": "View Feed",
|
||||
"9SvQep": "Follows {n}",
|
||||
"9V0wg3": "Calendar Event RSVP",
|
||||
"9WRlF4": "Send",
|
||||
"9WTAKy": "Search sets..",
|
||||
"9kO0VQ": "Hide muted notes",
|
||||
"9kSari": "Retry publishing",
|
||||
"9pMqYs": "Nostr Address",
|
||||
"9wO4wJ": "Lightning Invoice",
|
||||
"A86fJ+": "Generic Repost",
|
||||
"ADmfQT": "Parent",
|
||||
"ALdW69": "Note by {name}",
|
||||
"AN0Z7Q": "Muted Words",
|
||||
"ASRK0S": "This author has been muted",
|
||||
"AedFVZ": "Create or update a product",
|
||||
@ -168,6 +176,7 @@
|
||||
"Awq32I": "Push notifications",
|
||||
"AxDOiG": "Months",
|
||||
"AyGauy": "Login",
|
||||
"Ayx8rG": "Curated Publication Index",
|
||||
"B4C47Y": "name too short",
|
||||
"B6+XJy": "zapped",
|
||||
"B6H7eJ": "nsec, npub, nip-05, hex",
|
||||
@ -185,12 +194,13 @@
|
||||
"C7642/": "Quote Repost",
|
||||
"C81/uG": "Logout",
|
||||
"C8FsOr": "Popular Servers",
|
||||
"C8HhVE": "Suggested Follows",
|
||||
"CA1efg": "Video sets",
|
||||
"CHTbO3": "Failed to load invoice",
|
||||
"CJ0biq": "Poll Response",
|
||||
"CJx5Nd": "Profile Zaps",
|
||||
"CM+Cfj": "Follow List",
|
||||
"CM0k0d": "Prune follow list",
|
||||
"CSOaM+": "{note_type} by {name}{title}",
|
||||
"CVWeJ6": "Trending People",
|
||||
"CYkOCI": "and {count} others you follow",
|
||||
"Cdxwi0": "Repository announcements",
|
||||
@ -250,12 +260,17 @@
|
||||
"GFOoEE": "Salt",
|
||||
"GIqktu": "Supported NIPs",
|
||||
"GL8aXW": "Bookmarks ({n})",
|
||||
"GLdw/8": "File Message",
|
||||
"GQo+OV": "prompt reference",
|
||||
"GSye7T": "Lightning Address",
|
||||
"GUlSVG": "Claim your included Snort nostr address",
|
||||
"Gcn9NQ": "Magnet Link",
|
||||
"GpkNYn": "Torrent",
|
||||
"GqKcVm": "App curation sets",
|
||||
"GqQeu/": "Invalid Lightning Address",
|
||||
"GrDnue": "Nutzap Mint Recommendation",
|
||||
"GspYR7": "{n} Dislike",
|
||||
"GtIxzZ": "via {client}",
|
||||
"Gxcr08": "Broadcast Event",
|
||||
"H+vHiz": "Hex Key..",
|
||||
"H/oroO": "Dealing with Unknown Events",
|
||||
@ -271,6 +286,7 @@
|
||||
"HhcAVH": "You don't follow this person, click here to load media from <i>{link}</i>, or update <a><i>your preferences</i></a> to always load media from everybody.",
|
||||
"HpAmQZ": "Relay reviews",
|
||||
"HqRNN8": "Support",
|
||||
"Hqo/rL": "Curated Publication Content",
|
||||
"HzSFeV": "Expiration Timestamp",
|
||||
"I0tYZf": "Create or update a stall",
|
||||
"I1AoOu": "Last post {time}",
|
||||
@ -371,6 +387,7 @@
|
||||
"P7FD0F": "System (Default)",
|
||||
"P7nJT9": "Total today (UTC): {amount} sats",
|
||||
"P8JC58": "Distance",
|
||||
"P8zI6H": "Slide Set",
|
||||
"PCSt5T": "Preferences",
|
||||
"PXQ0z0": "Receiving to <b>{wallet}</b>",
|
||||
"PamNxw": "Unknown file header: {name}",
|
||||
@ -383,6 +400,7 @@
|
||||
"Qxv0B2": "You currently have {number} sats in your zap pool.",
|
||||
"Qy6/Ft": "Private Direct Messages",
|
||||
"R/6nsx": "Subscription",
|
||||
"R7x0mX": "Chat Message",
|
||||
"R81upa": "People you follow",
|
||||
"RDha9y": "Service Worker Not Running",
|
||||
"RRz1cA": "Repository state announcements",
|
||||
@ -407,6 +425,8 @@
|
||||
"ShdEie": "Mark all read",
|
||||
"Sjo1P4": "Custom",
|
||||
"SmuYUd": "What should we call you?",
|
||||
"So+Y+A": "Nutzap",
|
||||
"SopQOK": "Web bookmarks",
|
||||
"Ss0sWu": "Pay Now",
|
||||
"SsUQnC": "Application-specific Data",
|
||||
"StKzTE": "The author has marked this note as a <i>sensitive topic</i>",
|
||||
@ -426,10 +446,12 @@
|
||||
"Tpy00S": "People",
|
||||
"TvKqBp": "liked",
|
||||
"TwyMau": "Account",
|
||||
"U/fbvs": "Git Replies (deprecated)",
|
||||
"U1aPPi": "Stop listening",
|
||||
"U30H69": "Community Definition",
|
||||
"UJTWqI": "Remove from my relays",
|
||||
"ULXFfP": "Receive",
|
||||
"ULsJTk": "Published by",
|
||||
"UNjfWJ": "Check all event signatures received from relays",
|
||||
"UT7Nkj": "New Chat",
|
||||
"UUPFlt": "Users must accept the content warning to show the content of your note.",
|
||||
@ -441,6 +463,7 @@
|
||||
"UsCzPc": "Share a personalized invitation with friends!",
|
||||
"UxgyeY": "Your referral code is {code}",
|
||||
"V20Og0": "Labeling",
|
||||
"V93INS": "<dark>Created by</dark> {name}",
|
||||
"VOjC1i": "Pick which upload service you want to upload attachments to",
|
||||
"VR5eHw": "Public key (npub/nprofile)",
|
||||
"VcwrfF": "Yes please",
|
||||
@ -451,6 +474,7 @@
|
||||
"W2PiAr": "{n} Blocked",
|
||||
"W4SaxY": "Local",
|
||||
"W9355R": "Unmute",
|
||||
"WTrOy3": "Chat",
|
||||
"WeLEuL": "From Server",
|
||||
"Wj5TbN": "Issues",
|
||||
"WmZhfL": "Automatically translate notes to your local language",
|
||||
@ -469,6 +493,7 @@
|
||||
"YDMrKK": "Users",
|
||||
"YDURw6": "Service URL",
|
||||
"YH2RKk": "Popular media servers.",
|
||||
"YLGfQn": "Write message",
|
||||
"YQZY/S": "It looks like you dont follow enough people, take a look at {newUsersPage} to discover people to follow!",
|
||||
"YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.",
|
||||
"YU7ZYp": "Public Chat",
|
||||
@ -476,12 +501,12 @@
|
||||
"Yf3DwC": "Connect a wallet to send instant payments",
|
||||
"YuoEb9": "Try another relay",
|
||||
"Z48UEo": "Channel Metadata",
|
||||
"Z4BMCZ": "Enter pairing phrase",
|
||||
"Z7kkeJ": "Delegated Event Signing",
|
||||
"ZFe9tl": "Compose a note",
|
||||
"ZKORll": "Activate Now",
|
||||
"ZLmyG9": "Contributors",
|
||||
"ZS+jRE": "Send zap splits to",
|
||||
"ZT17bG": "Cashu Wallet Tokens",
|
||||
"Zff6lu": "Username iris.to/<b>{name}</b> is reserved for you!",
|
||||
"ZlIh4/": "Encrypted Direct Messages",
|
||||
"ZlmK/p": "{name} invited you to {app}",
|
||||
@ -491,7 +516,6 @@
|
||||
"aHje0o": "Name or nym",
|
||||
"aMaLBK": "Supported Extensions",
|
||||
"aRex7h": "Paid {amount} sats, fee {fee} sats",
|
||||
"aSGz4J": "Connect to your own LND node with Lightning Node Connect",
|
||||
"aWpBzj": "Show more",
|
||||
"abbGKq": "{n} km",
|
||||
"ak3MTf": "Invite Friends",
|
||||
@ -512,6 +536,8 @@
|
||||
"c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}",
|
||||
"c3LlRO": "{n}KiB",
|
||||
"c3g2hL": "Broadcast Again",
|
||||
"c6BMLV": "Starter Pack",
|
||||
"cF3ruj": "Follow All",
|
||||
"cFbU1B": "Using Alby? Go to {link} to get your NWC config!",
|
||||
"cG/bKQ": "Native nostr wallet connection",
|
||||
"cHCwbF": "Photography",
|
||||
@ -532,7 +558,6 @@
|
||||
"d7d0/x": "LN Address",
|
||||
"dK2CcV": "The public key is like your username, you can share it with anyone.",
|
||||
"dOQCL8": "Display name",
|
||||
"dZZIGe": "Modular Article Header",
|
||||
"ddd3JX": "Popular Hashtags",
|
||||
"deEeEI": "Register",
|
||||
"djLctd": "Amount in sats",
|
||||
@ -572,6 +597,7 @@
|
||||
"g5pX+a": "About",
|
||||
"g985Wp": "Failed to send vote",
|
||||
"gDzDRs": "Emoji to send when reactiong to a note",
|
||||
"gPxSgn": "Follow Sets",
|
||||
"gXgY3+": "Not all clients support this yet",
|
||||
"gczcC5": "Subscribe",
|
||||
"geppt8": "{count} ({count2} in memory)",
|
||||
@ -580,7 +606,9 @@
|
||||
"gl1NeW": "Lists",
|
||||
"go2/QF": "User server list",
|
||||
"grQ+mI": "Proof of Work",
|
||||
"grRQTM": "{n} people",
|
||||
"gtNjNP": "Basic protocol flow description",
|
||||
"h1gtUi": "Poll",
|
||||
"h7jvCs": "{site} is more fun together!",
|
||||
"h8XMJL": "Badges",
|
||||
"h9M0rW": "User Metadata",
|
||||
@ -640,7 +668,6 @@
|
||||
"lD3+8a": "Pay",
|
||||
"lEnclp": "My events: {n}",
|
||||
"lPWASz": "Snort nostr address",
|
||||
"lTbT3s": "Wallet password",
|
||||
"lbr3Lq": "Copy link",
|
||||
"lfOesV": "Non-Zap",
|
||||
"lgg1KN": "account page",
|
||||
@ -705,6 +732,7 @@
|
||||
"qkvYUb": "Add to Profile",
|
||||
"qmJ8kD": "Translation failed",
|
||||
"qtWLmt": "Like",
|
||||
"qx+v3H": "Request to Vanish",
|
||||
"qyJtWy": "Show less",
|
||||
"qydxOd": "Science",
|
||||
"qz9fty": "Incorrect pin",
|
||||
@ -727,6 +755,7 @@
|
||||
"sZQzjQ": "Failed to parse zap split: {input}",
|
||||
"saInmO": "The relay name shown is not the same as the full URL entered.",
|
||||
"saorw+": "Event Deletion Request",
|
||||
"sbSCT3": "Private event relay list",
|
||||
"sfL/O+": "Muted notes will not be shown",
|
||||
"t79a6U": "Connection Success:",
|
||||
"tDDiRL": "Interests list",
|
||||
@ -735,7 +764,6 @@
|
||||
"tOdNiY": "Dark",
|
||||
"tRGdV1": "Versioned Encryption",
|
||||
"tU0ADf": "Unknown NIP-{x}",
|
||||
"tVuVg9": "Video View Event",
|
||||
"tf1lIh": "Free",
|
||||
"th5lxp": "Send note to a subset of your write relays",
|
||||
"thnRpU": "Getting NIP-05 verified can help:",
|
||||
@ -752,6 +780,7 @@
|
||||
"uJaMkO": "Relay list to receive DMs",
|
||||
"uSV4Ti": "Reposts need to be manually confirmed",
|
||||
"uc0din": "Send sats splits to",
|
||||
"uex/ui": "external web reference",
|
||||
"ufvXH1": "Found {n} events",
|
||||
"uhu5aG": "Public",
|
||||
"un1nGw": "{n} notes",
|
||||
@ -772,12 +801,12 @@
|
||||
"wOyDTB": "File storage server list",
|
||||
"wSZR47": "Submit",
|
||||
"wc9st7": "Media Attachments",
|
||||
"whSrs+": "Nostr Public Chat",
|
||||
"wih7iJ": "name is blocked",
|
||||
"wlWMuh": "Patches",
|
||||
"wofVHy": "Moderation",
|
||||
"wqyN/i": "Find out more info about {service} at {link}",
|
||||
"wtLjP6": "Copy ID",
|
||||
"wvoA3H": "Picture",
|
||||
"x+3fl6": "My Relays",
|
||||
"x/Fx2P": "Fund the services that you use by splitting a portion of all your zaps into a pool of funds!",
|
||||
"x82IOl": "Mute",
|
||||
|
@ -35,7 +35,7 @@ export default defineConfig({
|
||||
name: "snort",
|
||||
ifGitSHA: true,
|
||||
command: "git describe --always --tags",
|
||||
ifMeta: false,
|
||||
ifMeta: true,
|
||||
ifLog: false,
|
||||
ifGlobal: false,
|
||||
}),
|
||||
|
@ -238,11 +238,13 @@ export function isOffline() {
|
||||
return !("navigator" in globalThis && globalThis.navigator.onLine);
|
||||
}
|
||||
|
||||
export function isHex(s: string) {
|
||||
export function isHex(s?: string) {
|
||||
if (!s) return false;
|
||||
// 48-57 = 0-9
|
||||
// 65-90 = A-Z
|
||||
// 97-122 = a-z
|
||||
return [...s]
|
||||
.map(v => v.charCodeAt(0))
|
||||
.every(v => (v >= 48 && v <= 57) || (v >= 65 && v <= 90) || v >= 97 || v <= 122);
|
||||
return (
|
||||
s.length % 2 == 0 &&
|
||||
[...s].map(v => v.charCodeAt(0)).every(v => (v >= 48 && v <= 57) || (v >= 65 && v <= 90) || v >= 97 || v <= 122)
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@snort/system-react",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"description": "React hooks for @snort/system",
|
||||
"main": "dist/index.js",
|
||||
"module": "src/index.ts",
|
||||
@ -17,7 +17,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@snort/shared": "^1.0.17",
|
||||
"@snort/system": "^1.6.0",
|
||||
"@snort/system": "^1.6.1",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -31,7 +31,7 @@ export function useReactions(
|
||||
}
|
||||
others?.(rb);
|
||||
return rb;
|
||||
}, [ids]);
|
||||
}, [ids, others]);
|
||||
|
||||
return useRequestBuilder(sub);
|
||||
}
|
||||
|
210
packages/system-wasm/Cargo.lock
generated
210
packages/system-wasm/Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
@ -19,15 +19,21 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.8"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.82"
|
||||
version = "0.1.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -36,9 +42,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin-io"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
|
||||
dependencies = [
|
||||
"bitcoin-io",
|
||||
"hex-conservative",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@ -63,9 +85,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
@ -75,9 +97,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.18"
|
||||
version = "1.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@ -117,18 +139,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.17"
|
||||
version = "4.5.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.17"
|
||||
version = "4.5.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@ -136,9 +158,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
@ -152,9 +174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -197,9 +219,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
@ -216,9 +238,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
@ -295,6 +317,15 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-conservative"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.13"
|
||||
@ -317,39 +348,40 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.70"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@ -359,9 +391,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169"
|
||||
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"walkdir",
|
||||
@ -378,9 +410,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@ -427,18 +459,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -495,9 +527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -507,9 +539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -518,9 +550,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@ -537,18 +575,14 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.29.1"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
||||
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"rand",
|
||||
"secp256k1-sys",
|
||||
]
|
||||
|
||||
@ -563,9 +597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -583,9 +617,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -594,9 +628,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@ -635,9 +669,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
version = "2.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -651,7 +685,7 @@ dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"criterion",
|
||||
"hex",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.14.0",
|
||||
"rand",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
@ -680,9 +714,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@ -708,24 +742,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.93"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -734,21 +768,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.43"
|
||||
version = "0.4.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.93"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -756,9 +791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.93"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -769,20 +804,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.93"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.43"
|
||||
version = "0.3.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9"
|
||||
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"minicov",
|
||||
"scoped-tls",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
@ -790,9 +826,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.43"
|
||||
version = "0.3.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
|
||||
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -801,9 +837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.70"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -10,8 +10,8 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
hex = { version = "0.4.3", features = [], default-features = false }
|
||||
itertools = "0.13.0"
|
||||
secp256k1 = { version = "0.29.0", features = ["global-context"] }
|
||||
itertools = "0.14.0"
|
||||
secp256k1 = { version = "0.30.0", features = ["global-context"] }
|
||||
serde = { version = "1.0.188", features = ["derive"], default-features = false }
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
serde_json = "1.0.105"
|
||||
|
62
packages/system-wasm/pkg/system_wasm.d.ts
vendored
62
packages/system-wasm/pkg/system_wasm.d.ts
vendored
@ -1,72 +1,38 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* @param {any} prev
|
||||
* @param {any} next
|
||||
* @returns {any}
|
||||
*/
|
||||
export function diff_filters(prev: any, next: any): any;
|
||||
/**
|
||||
* @param {any} val
|
||||
* @returns {any}
|
||||
*/
|
||||
export function expand_filter(val: any): any;
|
||||
/**
|
||||
* @param {any} prev
|
||||
* @param {any} next
|
||||
* @returns {any}
|
||||
*/
|
||||
export function get_diff(prev: any, next: any): any;
|
||||
/**
|
||||
* @param {any} val
|
||||
* @returns {any}
|
||||
*/
|
||||
export function flat_merge(val: any): any;
|
||||
/**
|
||||
* @param {any} val
|
||||
* @returns {any}
|
||||
*/
|
||||
export function compress(val: any): any;
|
||||
/**
|
||||
* @param {any} val
|
||||
* @param {any} target
|
||||
* @returns {any}
|
||||
*/
|
||||
export function pow(val: any, target: any): any;
|
||||
/**
|
||||
* @param {any} hash
|
||||
* @param {any} sig
|
||||
* @param {any} pub_key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function schnorr_verify(hash: any, sig: any, pub_key: any): boolean;
|
||||
/**
|
||||
* @param {any} event
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function schnorr_verify_event(event: any): boolean;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly diff_filters: (a: number, b: number, c: number) => void;
|
||||
readonly expand_filter: (a: number, b: number) => void;
|
||||
readonly get_diff: (a: number, b: number, c: number) => void;
|
||||
readonly flat_merge: (a: number, b: number) => void;
|
||||
readonly compress: (a: number, b: number) => void;
|
||||
readonly pow: (a: number, b: number, c: number) => void;
|
||||
readonly schnorr_verify: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly schnorr_verify_event: (a: number, b: number) => void;
|
||||
readonly diff_filters: (a: any, b: any) => [number, number, number];
|
||||
readonly expand_filter: (a: any) => [number, number, number];
|
||||
readonly get_diff: (a: any, b: any) => [number, number, number];
|
||||
readonly flat_merge: (a: any) => [number, number, number];
|
||||
readonly compress: (a: any) => [number, number, number];
|
||||
readonly pow: (a: any, b: any) => [number, number, number];
|
||||
readonly schnorr_verify: (a: any, b: any, c: any) => [number, number, number];
|
||||
readonly schnorr_verify_event: (a: any) => [number, number, number];
|
||||
readonly rustsecp256k1_v0_10_0_context_create: (a: number) => number;
|
||||
readonly rustsecp256k1_v0_10_0_context_destroy: (a: number) => void;
|
||||
readonly rustsecp256k1_v0_10_0_default_illegal_callback_fn: (a: number, b: number) => void;
|
||||
readonly rustsecp256k1_v0_10_0_default_error_callback_fn: (a: number, b: number) => void;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __externref_table_dealloc: (a: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
@ -1,28 +1,32 @@
|
||||
let wasm;
|
||||
|
||||
const heap = new Array(128).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) {
|
||||
return heap[idx];
|
||||
function addToExternrefTable0(obj) {
|
||||
const idx = wasm.__externref_table_alloc();
|
||||
wasm.__wbindgen_export_2.set(idx, obj);
|
||||
return idx;
|
||||
}
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
const idx = addToExternrefTable0(e);
|
||||
wasm.__wbindgen_exn_store(idx);
|
||||
}
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
const cachedTextDecoder =
|
||||
typeof TextDecoder !== "undefined"
|
||||
? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true })
|
||||
: {
|
||||
decode: () => {
|
||||
throw Error("TextDecoder not available");
|
||||
},
|
||||
};
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
if (typeof TextDecoder !== "undefined") {
|
||||
cachedTextDecoder.decode();
|
||||
}
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
@ -33,6 +37,13 @@ function getUint8ArrayMemory0() {
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
const cachedTextEncoder =
|
||||
typeof TextEncoder !== "undefined"
|
||||
? new TextEncoder("utf-8")
|
||||
@ -96,10 +107,6 @@ function passStringToWasm0(arg, malloc, realloc) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
|
||||
function getDataViewMemory0() {
|
||||
@ -113,31 +120,8 @@ function getDataViewMemory0() {
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
const cachedTextDecoder =
|
||||
typeof TextDecoder !== "undefined"
|
||||
? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true })
|
||||
: {
|
||||
decode: () => {
|
||||
throw Error("TextDecoder not available");
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof TextDecoder !== "undefined") {
|
||||
cachedTextDecoder.decode();
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
@ -181,7 +165,7 @@ function debugString(val) {
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches.length > 1) {
|
||||
if (builtInMatches && builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
@ -204,25 +188,23 @@ function debugString(val) {
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
function takeFromExternrefTable0(idx) {
|
||||
const value = wasm.__wbindgen_export_2.get(idx);
|
||||
wasm.__externref_table_dealloc(idx);
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* @param {any} prev
|
||||
* @param {any} next
|
||||
* @returns {any}
|
||||
*/
|
||||
export function diff_filters(prev, next) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.diff_filters(retptr, addHeapObject(prev), addHeapObject(next));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.diff_filters(prev, next);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,19 +212,11 @@ export function diff_filters(prev, next) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function expand_filter(val) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.expand_filter(retptr, addHeapObject(val));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.expand_filter(val);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -251,19 +225,11 @@ export function expand_filter(val) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function get_diff(prev, next) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.get_diff(retptr, addHeapObject(prev), addHeapObject(next));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.get_diff(prev, next);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -271,19 +237,11 @@ export function get_diff(prev, next) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function flat_merge(val) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.flat_merge(retptr, addHeapObject(val));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.flat_merge(val);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,19 +249,11 @@ export function flat_merge(val) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function compress(val) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.compress(retptr, addHeapObject(val));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.compress(val);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,19 +262,11 @@ export function compress(val) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function pow(val, target) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.pow(retptr, addHeapObject(val), addHeapObject(target));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return takeObject(r0);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.pow(val, target);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,19 +276,11 @@ export function pow(val, target) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function schnorr_verify(hash, sig, pub_key) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.schnorr_verify(retptr, addHeapObject(hash), addHeapObject(sig), addHeapObject(pub_key));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return r0 !== 0;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
const ret = wasm.schnorr_verify(hash, sig, pub_key);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return ret[0] !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,27 +288,11 @@ export function schnorr_verify(hash, sig, pub_key) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function schnorr_verify_event(event) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
wasm.schnorr_verify_event(retptr, addHeapObject(event));
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
if (r2) {
|
||||
throw takeObject(r1);
|
||||
}
|
||||
return r0 !== 0;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
const ret = wasm.schnorr_verify_event(event);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return ret[0] !== 0;
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
@ -385,7 +303,7 @@ async function __wbg_load(module, imports) {
|
||||
} catch (e) {
|
||||
if (module.headers.get("Content-Type") != "application/wasm") {
|
||||
console.warn(
|
||||
"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",
|
||||
"`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",
|
||||
e,
|
||||
);
|
||||
} else {
|
||||
@ -410,196 +328,21 @@ async function __wbg_load(module, imports) {
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function (arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function (arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof obj === "string" ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_object = function (arg0) {
|
||||
const val = getObject(arg0);
|
||||
const ret = typeof val === "object" && val !== null;
|
||||
imports.wbg.__wbg_buffer_609cc3eee51ed158 = function (arg0) {
|
||||
const ret = arg0.buffer;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function (arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_in = function (arg0, arg1) {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_bigint = function (arg0) {
|
||||
const ret = typeof getObject(arg0) === "bigint";
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {
|
||||
const ret = BigInt.asUintN(64, arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {
|
||||
const ret = getObject(arg0) === getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function (arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {
|
||||
const ret = getObject(arg0) == getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function (arg0) {
|
||||
const v = getObject(arg0);
|
||||
const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_number_get = function (arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof obj === "number" ? obj : undefined;
|
||||
getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
|
||||
};
|
||||
imports.wbg.__wbindgen_as_number = function (arg0) {
|
||||
const ret = +getObject(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_number_new = function (arg0) {
|
||||
const ret = arg0;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function (arg0) {
|
||||
const ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function (arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_getwithrefkey_edc2c8960f0f1191 = function (arg0, arg1) {
|
||||
const ret = getObject(arg0)[getObject(arg1)];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_f975102236d3c502 = function (arg0, arg1, arg2) {
|
||||
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
|
||||
};
|
||||
imports.wbg.__wbg_get_3baa728f9d58d3f6 = function (arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_length_ae22078168b726f5 = function (arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_a220cf903aa02ca2 = function () {
|
||||
const ret = new Array();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function (arg0) {
|
||||
const ret = typeof getObject(arg0) === "function";
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_next_de3e9db4440638b2 = function (arg0) {
|
||||
const ret = getObject(arg0).next;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_next_f9cb570345655b9a = function () {
|
||||
return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).next();
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_done_bfda7aa8f252b39f = function (arg0) {
|
||||
const ret = getObject(arg0).done;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_6d39332ab4788d86 = function (arg0) {
|
||||
const ret = getObject(arg0).value;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_iterator_888179a48810a9fe = function () {
|
||||
const ret = Symbol.iterator;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_224d16597dbbfd96 = function () {
|
||||
imports.wbg.__wbg_call_672a4d21634d4a24 = function () {
|
||||
return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_call_1084a111329e68ce = function () {
|
||||
return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_new_525245e2b9901204 = function () {
|
||||
const ret = new Object();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_673dda6c73d19609 = function (arg0, arg1, arg2) {
|
||||
getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
|
||||
};
|
||||
imports.wbg.__wbg_isArray_8364a5371e9737d8 = function (arg0) {
|
||||
const ret = Array.isArray(getObject(arg0));
|
||||
imports.wbg.__wbg_done_769e5ede4b31c67b = function (arg0) {
|
||||
const ret = arg0.done;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_61dfc3198373c902 = function (arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof ArrayBuffer;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_isSafeInteger_7f1ed56200d90674 = function (arg0) {
|
||||
const ret = Number.isSafeInteger(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_buffer_b7b08af79b0b0974 = function (arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_ea1883e1e5e86686 = function (arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_d1e79e2388520f18 = function (arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbg_length_8339fcf5d8ecd12e = function (arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_247a91427532499e = function (arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof Uint8Array;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_abda76e883ba8a5f = function () {
|
||||
const ret = new Error();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) {
|
||||
const ret = getObject(arg1).stack;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) {
|
||||
imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function (arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
@ -610,25 +353,202 @@ function __wbg_get_imports() {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {
|
||||
const v = getObject(arg1);
|
||||
const ret = typeof v === "bigint" ? v : undefined;
|
||||
getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
|
||||
imports.wbg.__wbg_get_67b2ba62fc30de12 = function () {
|
||||
return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(arg0, arg1);
|
||||
return ret;
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {
|
||||
const ret = debugString(getObject(arg1));
|
||||
imports.wbg.__wbg_get_b9b93047fe3cf45b = function (arg0, arg1) {
|
||||
const ret = arg0[arg1 >>> 0];
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getwithrefkey_1dc361bd10053bfe = function (arg0, arg1) {
|
||||
const ret = arg0[arg1];
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_e14585432e3737fc = function (arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof ArrayBuffer;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_17156bcf118086a9 = function (arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof Uint8Array;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_isArray_a1eab7e0d067391b = function (arg0) {
|
||||
const ret = Array.isArray(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_isSafeInteger_343e2beeeece1bb0 = function (arg0) {
|
||||
const ret = Number.isSafeInteger(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_iterator_9a24c88df860dc65 = function () {
|
||||
const ret = Symbol.iterator;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_a446193dc22c12f8 = function (arg0) {
|
||||
const ret = arg0.length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_e2d2a49132c1b256 = function (arg0) {
|
||||
const ret = arg0.length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_405e22f390576ce2 = function () {
|
||||
const ret = new Object();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_78feb108b6472713 = function () {
|
||||
const ret = new Array();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_8a6f238a6ece86ea = function () {
|
||||
const ret = new Error();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_a12002a7f91c75be = function (arg0) {
|
||||
const ret = new Uint8Array(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_next_25feadfc0913fea9 = function (arg0) {
|
||||
const ret = arg0.next;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_next_6574e1a8a62d1055 = function () {
|
||||
return handleError(function (arg0) {
|
||||
const ret = arg0.next();
|
||||
return ret;
|
||||
}, arguments);
|
||||
};
|
||||
imports.wbg.__wbg_set_37837023f3d740e8 = function (arg0, arg1, arg2) {
|
||||
arg0[arg1 >>> 0] = arg2;
|
||||
};
|
||||
imports.wbg.__wbg_set_3f1d0b984ed272ed = function (arg0, arg1, arg2) {
|
||||
arg0[arg1] = arg2;
|
||||
};
|
||||
imports.wbg.__wbg_set_65595bdd868b3009 = function (arg0, arg1, arg2) {
|
||||
arg0.set(arg1, arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbg_stack_0ed75d68575b0f3c = function (arg0, arg1) {
|
||||
const ret = arg1.stack;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
imports.wbg.__wbg_value_cd1ffa7b1ab794f1 = function (arg0) {
|
||||
const ret = arg0.value;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_as_number = function (arg0) {
|
||||
const ret = +arg0;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {
|
||||
const ret = BigInt.asUintN(64, arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {
|
||||
const v = arg1;
|
||||
const ret = typeof v === "bigint" ? v : undefined;
|
||||
getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function (arg0) {
|
||||
const v = arg0;
|
||||
const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {
|
||||
const ret = debugString(arg1);
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function (arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_in = function (arg0, arg1) {
|
||||
const ret = arg0 in arg1;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function () {
|
||||
const table = wasm.__wbindgen_export_2;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_bigint = function (arg0) {
|
||||
const ret = typeof arg0 === "bigint";
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function (arg0) {
|
||||
const ret = typeof arg0 === "function";
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_object = function (arg0) {
|
||||
const val = arg0;
|
||||
const ret = typeof val === "object" && val !== null;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function (arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {
|
||||
const ret = arg0 === arg1;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {
|
||||
const ret = arg0 == arg1;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_memory = function () {
|
||||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_number_get = function (arg0, arg1) {
|
||||
const obj = arg1;
|
||||
const ret = typeof obj === "number" ? obj : undefined;
|
||||
getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
|
||||
};
|
||||
imports.wbg.__wbindgen_number_new = function (arg0) {
|
||||
const ret = arg0;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function (arg0, arg1) {
|
||||
const obj = arg1;
|
||||
const ret = typeof obj === "string" ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function (arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
@ -642,14 +562,20 @@ function __wbg_finalize_init(instance, module) {
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
if (typeof module !== "undefined" && Object.getPrototypeOf(module) === Object.prototype) ({ module } = module);
|
||||
else console.warn("using deprecated parameters for `initSync()`; pass a single object instead");
|
||||
if (typeof module !== "undefined") {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({ module } = module);
|
||||
} else {
|
||||
console.warn("using deprecated parameters for `initSync()`; pass a single object instead");
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
@ -667,9 +593,13 @@ function initSync(module) {
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
if (typeof module_or_path !== "undefined" && Object.getPrototypeOf(module_or_path) === Object.prototype)
|
||||
({ module_or_path } = module_or_path);
|
||||
else console.warn("using deprecated parameters for the initialization function; pass a single object instead");
|
||||
if (typeof module_or_path !== "undefined") {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({ module_or_path } = module_or_path);
|
||||
} else {
|
||||
console.warn("using deprecated parameters for the initialization function; pass a single object instead");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module_or_path === "undefined") {
|
||||
module_or_path = new URL("system_wasm_bg.wasm", import.meta.url);
|
||||
|
Binary file not shown.
@ -1,20 +1,23 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export function diff_filters(a: number, b: number, c: number): void;
|
||||
export function expand_filter(a: number, b: number): void;
|
||||
export function get_diff(a: number, b: number, c: number): void;
|
||||
export function flat_merge(a: number, b: number): void;
|
||||
export function compress(a: number, b: number): void;
|
||||
export function pow(a: number, b: number, c: number): void;
|
||||
export function schnorr_verify(a: number, b: number, c: number, d: number): void;
|
||||
export function schnorr_verify_event(a: number, b: number): void;
|
||||
export function rustsecp256k1_v0_10_0_context_create(a: number): number;
|
||||
export function rustsecp256k1_v0_10_0_context_destroy(a: number): void;
|
||||
export function rustsecp256k1_v0_10_0_default_illegal_callback_fn(a: number, b: number): void;
|
||||
export function rustsecp256k1_v0_10_0_default_error_callback_fn(a: number, b: number): void;
|
||||
export function __wbindgen_malloc(a: number, b: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_exn_store(a: number): void;
|
||||
export function __wbindgen_free(a: number, b: number, c: number): void;
|
||||
export const diff_filters: (a: any, b: any) => [number, number, number];
|
||||
export const expand_filter: (a: any) => [number, number, number];
|
||||
export const get_diff: (a: any, b: any) => [number, number, number];
|
||||
export const flat_merge: (a: any) => [number, number, number];
|
||||
export const compress: (a: any) => [number, number, number];
|
||||
export const pow: (a: any, b: any) => [number, number, number];
|
||||
export const schnorr_verify: (a: any, b: any, c: any) => [number, number, number];
|
||||
export const schnorr_verify_event: (a: any) => [number, number, number];
|
||||
export const rustsecp256k1_v0_10_0_context_create: (a: number) => number;
|
||||
export const rustsecp256k1_v0_10_0_context_destroy: (a: number) => void;
|
||||
export const rustsecp256k1_v0_10_0_default_illegal_callback_fn: (a: number, b: number) => void;
|
||||
export const rustsecp256k1_v0_10_0_default_error_callback_fn: (a: number, b: number) => void;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __externref_table_dealloc: (a: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
|
@ -1,9 +1,9 @@
|
||||
extern crate console_error_panic_hook;
|
||||
|
||||
use secp256k1::{Message, XOnlyPublicKey, SECP256K1};
|
||||
use crate::filter::{FlatReqFilter, ReqFilter};
|
||||
use secp256k1::{XOnlyPublicKey, SECP256K1};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use crate::filter::{FlatReqFilter, ReqFilter};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod diff;
|
||||
@ -96,10 +96,11 @@ pub fn schnorr_verify(hash: JsValue, sig: JsValue, pub_key: JsValue) -> Result<b
|
||||
let sig_hex: String = serde_wasm_bindgen::from_value(sig)?;
|
||||
let pub_key_hex: String = serde_wasm_bindgen::from_value(pub_key)?;
|
||||
|
||||
let msg = Message::from_digest_slice(&hex::decode(msg_hex).unwrap()).unwrap();
|
||||
let key = XOnlyPublicKey::from_slice(&hex::decode(pub_key_hex).unwrap()).unwrap();
|
||||
let sig = secp256k1::schnorr::Signature::from_slice(&hex::decode(sig_hex).unwrap()).unwrap();
|
||||
Ok(SECP256K1.verify_schnorr(&sig, &msg, &key).is_ok())
|
||||
Ok(SECP256K1
|
||||
.verify_schnorr(&sig, &hex::decode(msg_hex).unwrap(), &key)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -107,13 +108,23 @@ pub fn schnorr_verify_event(event: JsValue) -> Result<bool, JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
let event_obj: Event = serde_wasm_bindgen::from_value(event)?;
|
||||
|
||||
let json = json!([0, event_obj.pubkey, event_obj.created_at, event_obj.kind, event_obj.tags, event_obj.content]);
|
||||
let json = json!([
|
||||
0,
|
||||
event_obj.pubkey,
|
||||
event_obj.created_at,
|
||||
event_obj.kind,
|
||||
event_obj.tags,
|
||||
event_obj.content
|
||||
]);
|
||||
let id = sha256::digest(json.to_string().as_bytes());
|
||||
|
||||
let msg = Message::from_digest_slice(&hex::decode(id).unwrap()).unwrap();
|
||||
let key = XOnlyPublicKey::from_slice(&hex::decode(&event_obj.pubkey).unwrap()).unwrap();
|
||||
let sig = secp256k1::schnorr::Signature::from_slice(&hex::decode(&event_obj.sig.unwrap()).unwrap()).unwrap();
|
||||
Ok(SECP256K1.verify_schnorr(&sig, &msg, &key).is_ok())
|
||||
let sig =
|
||||
secp256k1::schnorr::Signature::from_slice(&hex::decode(&event_obj.sig.unwrap()).unwrap())
|
||||
.unwrap();
|
||||
Ok(SECP256K1
|
||||
.verify_schnorr(&sig, &hex::decode(id).unwrap(), &key)
|
||||
.is_ok())
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@snort/system",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"description": "Snort nostr system package",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
4
packages/system/src/cache/user-metadata.ts
vendored
4
packages/system/src/cache/user-metadata.ts
vendored
@ -7,8 +7,8 @@ export class UserProfileCache extends FeedCache<CachedMetadata> {
|
||||
|
||||
constructor(table?: DexieTableLike<CachedMetadata>) {
|
||||
super("UserCache", table);
|
||||
this.#processZapperQueue();
|
||||
this.#processNip5Queue();
|
||||
//this.#processZapperQueue();
|
||||
//this.#processNip5Queue();
|
||||
}
|
||||
|
||||
key(of: CachedMetadata): string {
|
||||
|
@ -12,12 +12,16 @@ const enum EventKind {
|
||||
SimpleChatMessage = 9, // NIP-29
|
||||
SealedRumor = 13, // NIP-59
|
||||
ChatRumor = 14, // NIP-24
|
||||
Photo = 20, // NIP-68
|
||||
Video = 21, // NIP-71
|
||||
ShortVideo = 22, // NIP-71
|
||||
PublicChatChannel = 40, // NIP-28
|
||||
PublicChatMetadata = 41, // NIP-28
|
||||
PublicChatMessage = 42, // NIP-28
|
||||
PublicChatMuteMessage = 43, // NIP-28
|
||||
PublicChatMuteUser = 44, // NIP-28
|
||||
SnortSubscriptions = 1000, // NIP-XX
|
||||
Comment = 1111, // NIP-22
|
||||
Polls = 6969, // NIP-69
|
||||
GiftWrap = 1059, // NIP-59
|
||||
FileHeader = 1063, // NIP-94
|
||||
@ -34,6 +38,7 @@ const enum EventKind {
|
||||
SearchRelaysList = 10_007, // NIP-51
|
||||
InterestsList = 10_015, // NIP-51
|
||||
EmojisList = 10_030, // NIP-51
|
||||
BlossomServerList = 10_063,
|
||||
StorageServerList = 10_096, // NIP-96 server list
|
||||
|
||||
FollowSet = 30_000, // NIP-51
|
||||
@ -42,15 +47,18 @@ const enum EventKind {
|
||||
CurationSet = 30_004, // NIP-51
|
||||
InterestSet = 30_015, // NIP-15
|
||||
EmojiSet = 30_030, // NIP-51
|
||||
StarterPackSet = 39_089, // NIP-51
|
||||
|
||||
Badge = 30009, // NIP-58
|
||||
ProfileBadges = 30008, // NIP-58
|
||||
|
||||
LongFormTextNote = 30023, // NIP-23
|
||||
AppData = 30_078, // NIP-78
|
||||
LiveEvent = 30311, // NIP-102
|
||||
LiveEvent = 30311, // NIP-53
|
||||
LiveEventChat = 1311, // NIP-53
|
||||
UserStatus = 30315, // NIP-38
|
||||
ZapstrTrack = 31337,
|
||||
ApplicationHandler = 31_990,
|
||||
SimpleChatMetadata = 39_000, // NIP-29
|
||||
ZapRequest = 9734, // NIP 57
|
||||
ZapReceipt = 9735, // NIP 57
|
||||
|
@ -25,6 +25,7 @@ import { EventBuilder } from "./event-builder";
|
||||
import { findTag } from "./utils";
|
||||
import { Nip7Signer } from "./impl/nip7";
|
||||
import { Nip10 } from "./impl/nip10";
|
||||
import { Nip22 } from "./impl/nip22";
|
||||
|
||||
type EventBuilderHook = (ev: EventBuilder) => EventBuilder;
|
||||
|
||||
@ -195,12 +196,19 @@ export class EventPublisher {
|
||||
|
||||
/**
|
||||
* Reply to a note
|
||||
*
|
||||
* Replies to kind 1 notes are kind 1, otherwise kind 1111
|
||||
*/
|
||||
async reply(replyTo: TaggedNostrEvent, msg: string, fnExtra?: EventBuilderHook) {
|
||||
const eb = this.#eb(EventKind.TextNote);
|
||||
const kind = replyTo.kind === EventKind.TextNote ? EventKind.TextNote : EventKind.Comment;
|
||||
const eb = this.#eb(kind);
|
||||
eb.content(msg);
|
||||
|
||||
Nip10.replyTo(replyTo, eb);
|
||||
if (kind === EventKind.TextNote) {
|
||||
Nip10.replyTo(replyTo, eb);
|
||||
} else {
|
||||
Nip22.replyTo(replyTo, eb);
|
||||
}
|
||||
eb.processContent();
|
||||
fnExtra?.(eb);
|
||||
return await this.#sign(eb);
|
||||
@ -211,6 +219,7 @@ export class EventPublisher {
|
||||
eb.content(content);
|
||||
eb.tag(unwrap(NostrLink.fromEvent(evRef).toEventTag()));
|
||||
eb.tag(["p", evRef.pubkey]);
|
||||
eb.tag(["k", evRef.kind.toString()]);
|
||||
return await this.#sign(eb);
|
||||
}
|
||||
|
||||
|
35
packages/system/src/impl/nip22.ts
Normal file
35
packages/system/src/impl/nip22.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { findTag } from "../utils";
|
||||
import { EventBuilder, NostrEvent, NostrLink, NostrPrefix } from "../index";
|
||||
|
||||
export class Nip22 {
|
||||
/**
|
||||
* Get the root scope tag (E/A/I) or
|
||||
* create a root scope tag from the provided event
|
||||
*/
|
||||
static rootScopeOf(other: NostrEvent) {
|
||||
const linkOther = NostrLink.fromEvent(other);
|
||||
return other.tags.find(t => ["E", "A", "I"].includes(t[0])) ?? linkOther.toEventTagNip22(true)!;
|
||||
}
|
||||
|
||||
static replyTo(other: NostrEvent, eb: EventBuilder) {
|
||||
const linkOther = NostrLink.fromEvent(other);
|
||||
const rootScope = Nip22.rootScopeOf(other);
|
||||
const rootKind = ["K", findTag(other, "K") ?? other.kind.toString()];
|
||||
const rootAuthor = ["P", findTag(other, "P") ?? other.pubkey];
|
||||
|
||||
const replyScope = linkOther.toEventTagNip22(false);
|
||||
const replyKind = ["k", other.kind.toString()];
|
||||
const replyAuthor = ["p", other.pubkey];
|
||||
|
||||
if (rootScope === undefined || replyScope === undefined) {
|
||||
throw new Error("RootScope or ReplyScope are undefined!");
|
||||
}
|
||||
|
||||
eb.tag(rootScope);
|
||||
eb.tag(rootKind);
|
||||
eb.tag(rootAuthor);
|
||||
eb.tag(replyScope);
|
||||
eb.tag(replyKind);
|
||||
eb.tag(replyAuthor);
|
||||
}
|
||||
}
|
42
packages/system/src/impl/nip92.ts
Normal file
42
packages/system/src/impl/nip92.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Nip94Tags, readNip94Tags } from "./nip94";
|
||||
|
||||
/**
|
||||
* Read NIP-94 tags from `imeta` tag
|
||||
*/
|
||||
export function readNip94TagsFromIMeta(tag: Array<string>) {
|
||||
const asTags = tag.slice(1).map(a => a.split(" ", 2));
|
||||
return readNip94Tags(asTags);
|
||||
}
|
||||
|
||||
export function nip94TagsToIMeta(meta: Nip94Tags) {
|
||||
if (!meta.url) {
|
||||
throw new Error("URL is required!");
|
||||
}
|
||||
const ret: Array<string> = ["imeta"];
|
||||
const ifPush = (key: string, value?: string | number) => {
|
||||
if (value) {
|
||||
ret.push(`${key} ${value}`);
|
||||
}
|
||||
};
|
||||
ifPush("url", meta.url);
|
||||
ifPush("m", meta.mimeType);
|
||||
ifPush("x", meta.hash);
|
||||
ifPush("ox", meta.originalHash);
|
||||
ifPush("size", meta.size);
|
||||
ifPush("dim", meta.dimensions?.join("x"));
|
||||
ifPush("magnet", meta.magnet);
|
||||
ifPush("blurhash", meta.blurHash);
|
||||
ifPush("thumb", meta.thumb);
|
||||
ifPush("summary", meta.summary);
|
||||
ifPush("alt", meta.alt);
|
||||
ifPush("duration", meta.duration);
|
||||
ifPush("bitrate", meta.bitrate);
|
||||
if (meta.image) {
|
||||
meta.image.forEach(a => ifPush("image", a));
|
||||
}
|
||||
if (meta.fallback) {
|
||||
meta.fallback.forEach(a => ifPush("fallback", a));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
112
packages/system/src/impl/nip94.ts
Normal file
112
packages/system/src/impl/nip94.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { FileExtensionRegex } from "../const";
|
||||
|
||||
export interface Nip94Tags {
|
||||
url?: string;
|
||||
mimeType?: string;
|
||||
hash?: string;
|
||||
originalHash?: string;
|
||||
size?: number;
|
||||
dimensions?: [number, number];
|
||||
magnet?: string;
|
||||
blurHash?: string;
|
||||
thumb?: string;
|
||||
image?: Array<string>;
|
||||
summary?: string;
|
||||
alt?: string;
|
||||
fallback?: Array<string>;
|
||||
duration?: number;
|
||||
bitrate?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read NIP-94 tags from event tags
|
||||
*/
|
||||
export function readNip94Tags(tags: Array<Array<string>>) {
|
||||
const res: Nip94Tags = {};
|
||||
for (const tx of tags) {
|
||||
const [k, v] = tx;
|
||||
switch (k) {
|
||||
case "url": {
|
||||
res.url = v;
|
||||
break;
|
||||
}
|
||||
case "m": {
|
||||
res.mimeType = v;
|
||||
break;
|
||||
}
|
||||
case "x": {
|
||||
res.hash = v;
|
||||
break;
|
||||
}
|
||||
case "ox": {
|
||||
res.originalHash = v;
|
||||
break;
|
||||
}
|
||||
case "size": {
|
||||
res.size = Number(v);
|
||||
break;
|
||||
}
|
||||
case "dim": {
|
||||
res.dimensions = v.split("x").map(Number) as [number, number];
|
||||
break;
|
||||
}
|
||||
case "magnet": {
|
||||
res.magnet = v;
|
||||
break;
|
||||
}
|
||||
case "blurhash": {
|
||||
res.blurHash = v;
|
||||
break;
|
||||
}
|
||||
case "thumb": {
|
||||
res.thumb = v;
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
res.image ??= [];
|
||||
res.image.push(v);
|
||||
break;
|
||||
}
|
||||
case "summary": {
|
||||
res.summary = v;
|
||||
break;
|
||||
}
|
||||
case "alt": {
|
||||
res.alt = v;
|
||||
break;
|
||||
}
|
||||
case "fallback": {
|
||||
res.fallback ??= [];
|
||||
res.fallback.push(v);
|
||||
break;
|
||||
}
|
||||
case "duration": {
|
||||
res.duration = Number(v);
|
||||
break;
|
||||
}
|
||||
case "bitrate": {
|
||||
res.bitrate = Number(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function addExtensionToNip94Url(meta: Nip94Tags) {
|
||||
if (!meta.url?.match(FileExtensionRegex) && meta.mimeType) {
|
||||
switch (meta.mimeType) {
|
||||
case "image/webp": {
|
||||
return `${meta.url}.webp`;
|
||||
}
|
||||
case "image/jpeg":
|
||||
case "image/jpg": {
|
||||
return `${meta.url}.jpg`;
|
||||
}
|
||||
case "video/mp4": {
|
||||
return `${meta.url}.mp4`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta.url;
|
||||
}
|
@ -30,10 +30,13 @@ export * from "./encryption";
|
||||
export * from "./impl/nip4";
|
||||
export * from "./impl/nip7";
|
||||
export * from "./impl/nip10";
|
||||
export * from "./impl/nip22";
|
||||
export * from "./impl/nip44";
|
||||
export * from "./impl/nip46";
|
||||
export * from "./impl/nip57";
|
||||
export * from "./impl/nip55";
|
||||
export * from "./impl/nip94";
|
||||
export * from "./impl/nip92";
|
||||
|
||||
export * from "./cache/index";
|
||||
export * from "./cache/user-relays";
|
||||
|
@ -14,7 +14,6 @@ export enum NostrPrefix {
|
||||
Address = "naddr",
|
||||
Req = "nreq",
|
||||
Chat17 = "nchat17",
|
||||
Chat28 = "nchat28",
|
||||
}
|
||||
|
||||
export enum TLVEntryType {
|
||||
|
@ -55,7 +55,7 @@ export class NostrLink implements ToNostrEventTag {
|
||||
readonly marker?: string,
|
||||
) {
|
||||
if (type !== NostrPrefix.Address && !isHex(id)) {
|
||||
throw new Error("ID must be hex");
|
||||
throw new Error(`ID must be hex: ${JSON.stringify(id)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,6 +114,32 @@ export class NostrLink implements ToNostrEventTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an event tag from this link as per NIP-22 (no marker position)
|
||||
*/
|
||||
toEventTagNip22(root?: boolean) {
|
||||
// emulate root flag by root marker
|
||||
root ??= this.marker === "root";
|
||||
|
||||
const suffix: Array<string> = [];
|
||||
if (this.relays && this.relays.length > 0) {
|
||||
suffix.push(this.relays[0]);
|
||||
}
|
||||
if (this.type === NostrPrefix.PublicKey || this.type === NostrPrefix.Profile) {
|
||||
return [root ? "P" : "p", this.id, ...suffix];
|
||||
} else if (this.type === NostrPrefix.Note || this.type === NostrPrefix.Event) {
|
||||
if (this.author) {
|
||||
if (suffix[0] === undefined) {
|
||||
suffix.push(""); // empty relay hint
|
||||
}
|
||||
suffix.push(this.author);
|
||||
}
|
||||
return [root ? "E" : "e", this.id, ...suffix];
|
||||
} else if (this.type === NostrPrefix.Address) {
|
||||
return [root ? "A" : "a", `${this.kind}:${this.author}:${this.id}`, ...suffix];
|
||||
}
|
||||
}
|
||||
|
||||
matchesEvent(ev: NostrEvent) {
|
||||
if (this.type === NostrPrefix.Address) {
|
||||
const dTag = findTag(ev, "d");
|
||||
@ -124,7 +150,11 @@ export class NostrLink implements ToNostrEventTag {
|
||||
const ifSetCheck = <T>(a: T | undefined, b: T) => {
|
||||
return !Boolean(a) || a === b;
|
||||
};
|
||||
return ifSetCheck(this.id, ev.id) && ifSetCheck(this.author, ev.pubkey) && ifSetCheck(this.kind, ev.kind);
|
||||
return (
|
||||
(EventExt.isReplaceable(ev.kind) || ifSetCheck(this.id, ev.id)) &&
|
||||
ifSetCheck(this.author, ev.pubkey) &&
|
||||
ifSetCheck(this.kind, ev.kind)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -194,20 +224,41 @@ export class NostrLink implements ToNostrEventTag {
|
||||
static fromTag(tag: Array<string>, author?: string, kind?: number) {
|
||||
const relays = tag.length > 2 ? [tag[2]] : undefined;
|
||||
switch (tag[0]) {
|
||||
case "E": {
|
||||
return new NostrLink(NostrPrefix.Event, tag[1], kind, author ?? tag[3], relays, "root");
|
||||
}
|
||||
case "e": {
|
||||
return new NostrLink(NostrPrefix.Event, tag[1], kind, author ?? tag[4], relays, tag[3]);
|
||||
}
|
||||
case "p": {
|
||||
return new NostrLink(NostrPrefix.Profile, tag[1], kind, author, relays);
|
||||
}
|
||||
case "A": {
|
||||
const [kind, author, dTag] = tag[1].split(":");
|
||||
if (!isHex(author)) {
|
||||
throw new Error(`Invalid author in A tag: ${tag[1]}`);
|
||||
}
|
||||
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, "root");
|
||||
}
|
||||
case "a": {
|
||||
const [kind, author, dTag] = tag[1].split(":");
|
||||
if (!isHex(author)) {
|
||||
throw new Error(`Invalid author in a tag: ${tag[1]}`);
|
||||
}
|
||||
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]);
|
||||
}
|
||||
}
|
||||
throw new Error("Unknown tag!");
|
||||
}
|
||||
|
||||
static tryFromTag(tag: Array<string>, author?: string, kind?: number) {
|
||||
try {
|
||||
return NostrLink.fromTag(tag, author, kind);
|
||||
} catch (e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
static fromTags(tags: ReadonlyArray<Array<string>>) {
|
||||
return removeUndefined(
|
||||
tags.map(a => {
|
||||
@ -242,7 +293,7 @@ export class NostrLink implements ToNostrEventTag {
|
||||
let relays = "relays" in ev ? ev.relays : undefined;
|
||||
const eventRelays = removeUndefined(
|
||||
ev.tags
|
||||
.filter(a => a[0] === "relays" || a[0] === "relay" || a[0] === "r")
|
||||
.filter(a => a[0] === "relays" || a[0] === "relay" || (a[0] === "r" && ev.kind == EventKind.Relays))
|
||||
.flatMap(a => a.slice(1).map(b => sanitizeRelayUrl(b))),
|
||||
);
|
||||
relays = appendDedupe(relays, eventRelays);
|
||||
|
@ -14,7 +14,7 @@ export interface TaggedNostrEvent extends NostrEvent {
|
||||
/**
|
||||
* A list of relays this event was seen on
|
||||
*/
|
||||
relays: Array<string>;
|
||||
relays?: Array<string>;
|
||||
|
||||
/**
|
||||
* Additional context
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user