feat: NIP-96 server list
This commit is contained in:
@ -125,6 +125,12 @@ const SettingsIndex = () => {
|
|||||||
message: <FormattedMessage defaultMessage="Cache" />,
|
message: <FormattedMessage defaultMessage="Cache" />,
|
||||||
path: "cache",
|
path: "cache",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: "camera-plus",
|
||||||
|
iconBg: "bg-lime-500",
|
||||||
|
message: <FormattedMessage defaultMessage="Media" />,
|
||||||
|
path: "media",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -480,19 +480,6 @@ const PreferencesPage = () => {
|
|||||||
<option value="nostrimg.com">nostrimg.com</option>
|
<option value="nostrimg.com">nostrimg.com</option>
|
||||||
<option value="nostrcheck.me">nostrcheck.me (NIP-96)</option>
|
<option value="nostrcheck.me">nostrcheck.me (NIP-96)</option>
|
||||||
</select>
|
</select>
|
||||||
{pref.fileUploader === "nip96" && (
|
|
||||||
<>
|
|
||||||
<small>
|
|
||||||
<FormattedMessage defaultMessage="Custom server URL" />
|
|
||||||
</small>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={pref.nip96Server}
|
|
||||||
onChange={e => setPref({ ...pref, nip96Server: e.target.value })}
|
|
||||||
placeholder="https://my-nip96-server.com/"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex flex-col g8">
|
<div className="flex flex-col g8">
|
||||||
|
@ -4,6 +4,7 @@ import AccountsPage from "@/Pages/settings/Accounts";
|
|||||||
import { CacheSettings } from "@/Pages/settings/Cache";
|
import { CacheSettings } from "@/Pages/settings/Cache";
|
||||||
import { ManageHandleRoutes } from "@/Pages/settings/handle";
|
import { ManageHandleRoutes } from "@/Pages/settings/handle";
|
||||||
import ExportKeys from "@/Pages/settings/Keys";
|
import ExportKeys from "@/Pages/settings/Keys";
|
||||||
|
import MediaSettingsPage from "@/Pages/settings/media-settings";
|
||||||
import Menu from "@/Pages/settings/Menu/Menu";
|
import Menu from "@/Pages/settings/Menu/Menu";
|
||||||
import ModerationSettings from "@/Pages/settings/Moderation";
|
import ModerationSettings from "@/Pages/settings/Moderation";
|
||||||
import Notifications from "@/Pages/settings/Notifications";
|
import Notifications from "@/Pages/settings/Notifications";
|
||||||
@ -65,6 +66,10 @@ export default [
|
|||||||
path: "cache",
|
path: "cache",
|
||||||
element: <CacheSettings />,
|
element: <CacheSettings />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "media",
|
||||||
|
element: <MediaSettingsPage />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "invite",
|
path: "invite",
|
||||||
element: <ReferralsPage />,
|
element: <ReferralsPage />,
|
||||||
|
98
packages/app/src/Pages/settings/media-settings.tsx
Normal file
98
packages/app/src/Pages/settings/media-settings.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { unwrap } from "@snort/shared";
|
||||||
|
import { EventKind, UnknownTag } from "@snort/system";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||||
|
import IconButton from "@/Components/Button/IconButton";
|
||||||
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import { Nip96Uploader } from "@/Utils/Upload/Nip96";
|
||||||
|
|
||||||
|
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 [newServer, setNewServer] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
async function validateServer() {
|
||||||
|
if (!publisher) return;
|
||||||
|
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const svc = new Nip96Uploader(newServer, 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">
|
||||||
|
<FormattedMessage defaultMessage="Media Servers" />
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage defaultMessage="Media servers store media which you can share in notes as images and videos" />
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{list.map(a => {
|
||||||
|
const [, addr] = unwrap(a.toEventTag());
|
||||||
|
return (
|
||||||
|
<div key={addr} className="p br bg-ultradark flex justify-between items-center">
|
||||||
|
{addr}
|
||||||
|
<IconButton
|
||||||
|
icon={{
|
||||||
|
name: "trash",
|
||||||
|
size: 15,
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
await state.removeFromList(EventKind.StorageServerList, [new UnknownTag(["server", addr])], true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{list.length === 0 && (
|
||||||
|
<small>
|
||||||
|
<FormattedMessage defaultMessage="You dont have any media servers, try adding some." />
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p br bg-ultradark flex flex-col gap-2">
|
||||||
|
<div className="text-lg">
|
||||||
|
<FormattedMessage defaultMessage="Add Server" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="flex-grow"
|
||||||
|
placeholder="https://my-files.com/"
|
||||||
|
value={newServer}
|
||||||
|
onChange={e => setNewServer(e.target.value)}
|
||||||
|
/>
|
||||||
|
<AsyncButton
|
||||||
|
onClick={async () => {
|
||||||
|
if (await validateServer()) {
|
||||||
|
await state.addToList(
|
||||||
|
EventKind.StorageServerList,
|
||||||
|
[new UnknownTag(["server", new URL(newServer).toString()])],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
setNewServer("");
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<FormattedMessage defaultMessage="Add" />
|
||||||
|
</AsyncButton>
|
||||||
|
</div>
|
||||||
|
{error && <b className="text-warning">{error}</b>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import * as utils from "@noble/curves/abstract/utils";
|
|||||||
import * as secp from "@noble/curves/secp256k1";
|
import * as secp from "@noble/curves/secp256k1";
|
||||||
import { ExternalStore, unwrap } from "@snort/shared";
|
import { ExternalStore, unwrap } from "@snort/shared";
|
||||||
import {
|
import {
|
||||||
|
EventKind,
|
||||||
EventPublisher,
|
EventPublisher,
|
||||||
HexKey,
|
HexKey,
|
||||||
KeyStorage,
|
KeyStorage,
|
||||||
@ -111,6 +112,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
},
|
},
|
||||||
stateObj,
|
stateObj,
|
||||||
);
|
);
|
||||||
|
stateClass.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||||
stateClass.on("change", () => this.#save());
|
stateClass.on("change", () => this.#save());
|
||||||
v.state = stateClass;
|
v.state = stateClass;
|
||||||
|
|
||||||
@ -197,6 +199,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
stalker: stalker ?? false,
|
stalker: stalker ?? false,
|
||||||
} as LoginSession;
|
} as LoginSession;
|
||||||
|
|
||||||
|
newSession.state.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||||
newSession.state.on("change", () => this.#save());
|
newSession.state.on("change", () => this.#save());
|
||||||
const pub = createPublisher(newSession);
|
const pub = createPublisher(newSession);
|
||||||
if (pub) {
|
if (pub) {
|
||||||
@ -246,6 +249,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
appdataId: "snort",
|
appdataId: "snort",
|
||||||
}),
|
}),
|
||||||
} as LoginSession;
|
} as LoginSession;
|
||||||
|
newSession.state.checkIsStandardList(EventKind.StorageServerList); // track nip96 list
|
||||||
newSession.state.on("change", () => this.#save());
|
newSession.state.on("change", () => this.#save());
|
||||||
|
|
||||||
if ("nostr_os" in window && window?.nostr_os) {
|
if ("nostr_os" in window && window?.nostr_os) {
|
||||||
|
@ -48,11 +48,6 @@ export interface UserPreferences {
|
|||||||
*/
|
*/
|
||||||
fileUploader: "void.cat" | "nostr.build" | "nostrimg.com" | "void.cat-NIP96" | "nostrcheck.me" | "nip96";
|
fileUploader: "void.cat" | "nostr.build" | "nostrimg.com" | "void.cat-NIP96" | "nostrcheck.me" | "nip96";
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom file server to upload files to
|
|
||||||
*/
|
|
||||||
nip96Server?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use imgproxy to optimize images
|
* Use imgproxy to optimize images
|
||||||
*/
|
*/
|
||||||
|
@ -62,8 +62,8 @@ export class Nip96Uploader implements Uploader {
|
|||||||
?.split("x");
|
?.split("x");
|
||||||
const mime = data.nip94_event.tags.find(a => a[0] === "m")?.at(1) ?? "";
|
const mime = data.nip94_event.tags.find(a => a[0] === "m")?.at(1) ?? "";
|
||||||
let url = data.nip94_event.tags.find(a => a[0] === "url")?.at(1) ?? "";
|
let url = data.nip94_event.tags.find(a => a[0] === "url")?.at(1) ?? "";
|
||||||
if(!url.match(FileExtensionRegex) && mime) {
|
if (!url.match(FileExtensionRegex) && mime) {
|
||||||
switch(mime) {
|
switch (mime) {
|
||||||
case "image/webp": {
|
case "image/webp": {
|
||||||
url += ".webp";
|
url += ".webp";
|
||||||
break;
|
break;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { NostrEvent } from "@snort/system";
|
import { removeUndefined } from "@snort/shared";
|
||||||
|
import { EventKind, NostrEvent } from "@snort/system";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import usePreferences from "@/Hooks/usePreferences";
|
import usePreferences from "@/Hooks/usePreferences";
|
||||||
import { bech32ToHex, unwrap } from "@/Utils";
|
import { bech32ToHex, randomSample, unwrap } from "@/Utils";
|
||||||
import { KieranPubKey } from "@/Utils/Const";
|
import { KieranPubKey } from "@/Utils/Const";
|
||||||
import NostrBuild from "@/Utils/Upload/NostrBuild";
|
import NostrBuild from "@/Utils/Upload/NostrBuild";
|
||||||
import NostrImg from "@/Utils/Upload/NostrImg";
|
import NostrImg from "@/Utils/Upload/NostrImg";
|
||||||
@ -48,6 +50,10 @@ export const UploaderServices = [
|
|||||||
name: "nostrimg.com",
|
name: "nostrimg.com",
|
||||||
owner: bech32ToHex("npub1xv6axulxcx6mce5mfvfzpsy89r4gee3zuknulm45cqqpmyw7680q5pxea6"),
|
owner: bech32ToHex("npub1xv6axulxcx6mce5mfvfzpsy89r4gee3zuknulm45cqqpmyw7680q5pxea6"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "nostrcheck.me",
|
||||||
|
owner: bech32ToHex("npub138s5hey76qrnm2pmv7p8nnffhfddsm8sqzm285dyc0wy4f8a6qkqtzx624"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Uploader {
|
export interface Uploader {
|
||||||
@ -65,14 +71,44 @@ export interface UploadProgress {
|
|||||||
export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined;
|
export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined;
|
||||||
|
|
||||||
export default function useFileUpload(): Uploader {
|
export default function useFileUpload(): Uploader {
|
||||||
const { fileUploader, nip96Server } = usePreferences(s => ({
|
const fileUploader = usePreferences(s => s.fileUploader);
|
||||||
fileUploader: s.fileUploader,
|
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||||
nip96Server: s.nip96Server,
|
|
||||||
}));
|
|
||||||
const { publisher } = useEventPublisher();
|
const { publisher } = useEventPublisher();
|
||||||
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
||||||
const [stage, setStage] = useState<UploadStage>();
|
const [stage, setStage] = useState<UploadStage>();
|
||||||
|
|
||||||
|
const defaultUploader = {
|
||||||
|
upload: async (f, n) => {
|
||||||
|
const id = uuid();
|
||||||
|
setProgress(s => [
|
||||||
|
...s,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
file: f,
|
||||||
|
progress: 0,
|
||||||
|
stage: undefined,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const px = (n: number) => {
|
||||||
|
setProgress(s =>
|
||||||
|
s.map(v =>
|
||||||
|
v.id === id
|
||||||
|
? {
|
||||||
|
...v,
|
||||||
|
progress: n,
|
||||||
|
}
|
||||||
|
: v,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const ret = await VoidCat(f, n, publisher, px, s => setStage(s));
|
||||||
|
setProgress(s => s.filter(a => a.id !== id));
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
progress,
|
||||||
|
stage,
|
||||||
|
} as Uploader;
|
||||||
|
|
||||||
switch (fileUploader) {
|
switch (fileUploader) {
|
||||||
case "nostr.build": {
|
case "nostr.build": {
|
||||||
return {
|
return {
|
||||||
@ -80,9 +116,6 @@ export default function useFileUpload(): Uploader {
|
|||||||
progress: [],
|
progress: [],
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
case "nip96": {
|
|
||||||
return new Nip96Uploader(unwrap(nip96Server), unwrap(publisher));
|
|
||||||
}
|
|
||||||
case "void.cat-NIP96": {
|
case "void.cat-NIP96": {
|
||||||
return new Nip96Uploader("https://void.cat/nostr", unwrap(publisher));
|
return new Nip96Uploader("https://void.cat/nostr", unwrap(publisher));
|
||||||
}
|
}
|
||||||
@ -95,38 +128,17 @@ export default function useFileUpload(): Uploader {
|
|||||||
progress: [],
|
progress: [],
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
|
case "nip96": {
|
||||||
|
const servers = removeUndefined(state.getList(EventKind.StorageServerList).map(a => a.toEventTag()?.at(1)));
|
||||||
|
if (servers.length > 0) {
|
||||||
|
const random = randomSample(servers, 1)[0];
|
||||||
|
return new Nip96Uploader(random, unwrap(publisher));
|
||||||
|
} else {
|
||||||
|
return defaultUploader;
|
||||||
|
}
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return {
|
return defaultUploader;
|
||||||
upload: async (f, n) => {
|
|
||||||
const id = uuid();
|
|
||||||
setProgress(s => [
|
|
||||||
...s,
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
file: f,
|
|
||||||
progress: 0,
|
|
||||||
stage: undefined,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const px = (n: number) => {
|
|
||||||
setProgress(s =>
|
|
||||||
s.map(v =>
|
|
||||||
v.id === id
|
|
||||||
? {
|
|
||||||
...v,
|
|
||||||
progress: n,
|
|
||||||
}
|
|
||||||
: v,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const ret = await VoidCat(f, n, publisher, px, s => setStage(s));
|
|
||||||
setProgress(s => s.filter(a => a.id !== id));
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
progress,
|
|
||||||
stage,
|
|
||||||
} as Uploader;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,6 +517,9 @@
|
|||||||
"FvanT6": {
|
"FvanT6": {
|
||||||
"defaultMessage": "Accounts"
|
"defaultMessage": "Accounts"
|
||||||
},
|
},
|
||||||
|
"FzbSGg": {
|
||||||
|
"defaultMessage": "You dont have any media servers, try adding some."
|
||||||
|
},
|
||||||
"G/yZLu": {
|
"G/yZLu": {
|
||||||
"defaultMessage": "Remove"
|
"defaultMessage": "Remove"
|
||||||
},
|
},
|
||||||
@ -949,6 +952,9 @@
|
|||||||
"UUPFlt": {
|
"UUPFlt": {
|
||||||
"defaultMessage": "Users must accept the content warning to show the content of your note."
|
"defaultMessage": "Users must accept the content warning to show the content of your note."
|
||||||
},
|
},
|
||||||
|
"UaCh1c": {
|
||||||
|
"defaultMessage": "Add Server"
|
||||||
|
},
|
||||||
"Ub+AGc": {
|
"Ub+AGc": {
|
||||||
"defaultMessage": "Sign In"
|
"defaultMessage": "Sign In"
|
||||||
},
|
},
|
||||||
@ -1063,6 +1069,9 @@
|
|||||||
"ZlmK/p": {
|
"ZlmK/p": {
|
||||||
"defaultMessage": "{name} invited you to {app}"
|
"defaultMessage": "{name} invited you to {app}"
|
||||||
},
|
},
|
||||||
|
"a1x4gD": {
|
||||||
|
"defaultMessage": "Media servers store media which you can share in notes as images and videos"
|
||||||
|
},
|
||||||
"a5UPxh": {
|
"a5UPxh": {
|
||||||
"defaultMessage": "Fund developers and platforms providing NIP-05 verification services"
|
"defaultMessage": "Fund developers and platforms providing NIP-05 verification services"
|
||||||
},
|
},
|
||||||
@ -1142,6 +1151,9 @@
|
|||||||
"defaultMessage": "URL..",
|
"defaultMessage": "URL..",
|
||||||
"description": "Placeholder text for imgproxy url textbox"
|
"description": "Placeholder text for imgproxy url textbox"
|
||||||
},
|
},
|
||||||
|
"cVcgLJ": {
|
||||||
|
"defaultMessage": "Media Servers"
|
||||||
|
},
|
||||||
"cWx9t8": {
|
"cWx9t8": {
|
||||||
"defaultMessage": "Mute all"
|
"defaultMessage": "Mute all"
|
||||||
},
|
},
|
||||||
@ -1313,9 +1325,6 @@
|
|||||||
"ha8JKG": {
|
"ha8JKG": {
|
||||||
"defaultMessage": "Show graph"
|
"defaultMessage": "Show graph"
|
||||||
},
|
},
|
||||||
"hf6g/W": {
|
|
||||||
"defaultMessage": "Custom server URL"
|
|
||||||
},
|
|
||||||
"hicxcO": {
|
"hicxcO": {
|
||||||
"defaultMessage": "Show replies"
|
"defaultMessage": "Show replies"
|
||||||
},
|
},
|
||||||
|
@ -171,6 +171,7 @@
|
|||||||
"FfYsOb": "An error has occured!",
|
"FfYsOb": "An error has occured!",
|
||||||
"FmXUJg": "follows you",
|
"FmXUJg": "follows you",
|
||||||
"FvanT6": "Accounts",
|
"FvanT6": "Accounts",
|
||||||
|
"FzbSGg": "You dont have any media servers, try adding some.",
|
||||||
"G/yZLu": "Remove",
|
"G/yZLu": "Remove",
|
||||||
"G1BGCg": "Select Wallet",
|
"G1BGCg": "Select Wallet",
|
||||||
"G3A56c": "Subscribed to Push",
|
"G3A56c": "Subscribed to Push",
|
||||||
@ -314,6 +315,7 @@
|
|||||||
"UNjfWJ": "Check all event signatures received from relays",
|
"UNjfWJ": "Check all event signatures received from relays",
|
||||||
"UT7Nkj": "New Chat",
|
"UT7Nkj": "New Chat",
|
||||||
"UUPFlt": "Users must accept the content warning to show the content of your note.",
|
"UUPFlt": "Users must accept the content warning to show the content of your note.",
|
||||||
|
"UaCh1c": "Add Server",
|
||||||
"Ub+AGc": "Sign In",
|
"Ub+AGc": "Sign In",
|
||||||
"Up5U7K": "Block",
|
"Up5U7K": "Block",
|
||||||
"Ups2/p": "Your application is pending",
|
"Ups2/p": "Your application is pending",
|
||||||
@ -352,6 +354,7 @@
|
|||||||
"ZS+jRE": "Send zap splits to",
|
"ZS+jRE": "Send zap splits to",
|
||||||
"Zff6lu": "Username iris.to/<b>{name}</b> is reserved for you!",
|
"Zff6lu": "Username iris.to/<b>{name}</b> is reserved for you!",
|
||||||
"ZlmK/p": "{name} invited you to {app}",
|
"ZlmK/p": "{name} invited you to {app}",
|
||||||
|
"a1x4gD": "Media servers store media which you can share in notes as images and videos",
|
||||||
"a5UPxh": "Fund developers and platforms providing NIP-05 verification services",
|
"a5UPxh": "Fund developers and platforms providing NIP-05 verification services",
|
||||||
"a7TDNm": "Notes will stream in real time into global and notes tab",
|
"a7TDNm": "Notes will stream in real time into global and notes tab",
|
||||||
"aHje0o": "Name or nym",
|
"aHje0o": "Name or nym",
|
||||||
@ -378,6 +381,7 @@
|
|||||||
"cHCwbF": "Photography",
|
"cHCwbF": "Photography",
|
||||||
"cPIKU2": "Following",
|
"cPIKU2": "Following",
|
||||||
"cQfLWb": "URL..",
|
"cQfLWb": "URL..",
|
||||||
|
"cVcgLJ": "Media Servers",
|
||||||
"cWx9t8": "Mute all",
|
"cWx9t8": "Mute all",
|
||||||
"cg1VJ2": "Connect Wallet",
|
"cg1VJ2": "Connect Wallet",
|
||||||
"cuP16y": "Multi account support",
|
"cuP16y": "Multi account support",
|
||||||
@ -435,7 +439,6 @@
|
|||||||
"hY4lzx": "Supports",
|
"hY4lzx": "Supports",
|
||||||
"hYOE+U": "Invite",
|
"hYOE+U": "Invite",
|
||||||
"ha8JKG": "Show graph",
|
"ha8JKG": "Show graph",
|
||||||
"hf6g/W": "Custom server URL",
|
|
||||||
"hicxcO": "Show replies",
|
"hicxcO": "Show replies",
|
||||||
"hmZ3Bz": "Media",
|
"hmZ3Bz": "Media",
|
||||||
"hniz8Z": "here",
|
"hniz8Z": "here",
|
||||||
|
@ -34,6 +34,7 @@ const enum EventKind {
|
|||||||
SearchRelaysList = 10_007, // NIP-51
|
SearchRelaysList = 10_007, // NIP-51
|
||||||
InterestsList = 10_015, // NIP-51
|
InterestsList = 10_015, // NIP-51
|
||||||
EmojisList = 10_030, // NIP-51
|
EmojisList = 10_030, // NIP-51
|
||||||
|
StorageServerList = 10_096, // NIP-96 server list
|
||||||
|
|
||||||
FollowSet = 30_000, // NIP-51
|
FollowSet = 30_000, // NIP-51
|
||||||
RelaySet = 30_002, // NIP-51
|
RelaySet = 30_002, // NIP-51
|
||||||
|
@ -76,7 +76,7 @@ export class QueryTrace extends EventEmitter<QueryTraceEvents> {
|
|||||||
* Total time spent waiting for relay to respond
|
* Total time spent waiting for relay to respond
|
||||||
*/
|
*/
|
||||||
get responseTime() {
|
get responseTime() {
|
||||||
return this.finished ? unwrap(this.eose) - unwrap(this.sent) : 0;
|
return this.finished ? unwrap(this.eose) - (this.sent ?? unixNowMs()) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,7 +160,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
? (change.tag as Array<Array<string>>)
|
? (change.tag as Array<Array<string>>)
|
||||||
: [change.tag as Array<string>];
|
: [change.tag as Array<string>];
|
||||||
for (const changeTag of changeTags) {
|
for (const changeTag of changeTags) {
|
||||||
const existing = tags.findIndex(a => change.tag[0] === a[0] && change.tag[1] === a[1]);
|
const existing = tags.findIndex(a => changeTag[0] === a[0] && changeTag[1] === a[1]);
|
||||||
if (existing === -1) {
|
if (existing === -1) {
|
||||||
tags.push(changeTag);
|
tags.push(changeTag);
|
||||||
} else {
|
} else {
|
||||||
@ -174,7 +174,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
? (change.tag as Array<Array<string>>)
|
? (change.tag as Array<Array<string>>)
|
||||||
: [change.tag as Array<string>];
|
: [change.tag as Array<string>];
|
||||||
for (const changeTag of changeTags) {
|
for (const changeTag of changeTags) {
|
||||||
const existing = tags.findIndex(a => change.tag[0] === a[0] && change.tag[1] === a[1]);
|
const existing = tags.findIndex(a => changeTag[0] === a[0] && changeTag[1] === a[1]);
|
||||||
if (existing !== -1) {
|
if (existing !== -1) {
|
||||||
tags.splice(existing, 1);
|
tags.splice(existing, 1);
|
||||||
} else {
|
} else {
|
||||||
@ -188,7 +188,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
? (change.tag as Array<Array<string>>)
|
? (change.tag as Array<Array<string>>)
|
||||||
: [change.tag as Array<string>];
|
: [change.tag as Array<string>];
|
||||||
for (const changeTag of changeTags) {
|
for (const changeTag of changeTags) {
|
||||||
const existing = tags.findIndex(a => change.tag[0] === a[0] && change.tag[1] === a[1]);
|
const existing = tags.findIndex(a => changeTag[0] === a[0] && changeTag[1] === a[1]);
|
||||||
if (existing !== -1) {
|
if (existing !== -1) {
|
||||||
tags[existing] = changeTag;
|
tags[existing] = changeTag;
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,7 +88,7 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// always track mute list
|
// always track mute list
|
||||||
this.#checkIsStandardList(EventKind.MuteList);
|
this.checkIsStandardList(EventKind.MuteList);
|
||||||
|
|
||||||
this.#profile.on("change", () => this.emit("change", UserStateChangeType.Profile));
|
this.#profile.on("change", () => this.emit("change", UserStateChangeType.Profile));
|
||||||
this.#contacts.on("change", () => this.emit("change", UserStateChangeType.Contacts));
|
this.#contacts.on("change", () => this.emit("change", UserStateChangeType.Contacts));
|
||||||
@ -338,7 +338,7 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
|
|||||||
autoCommit = false,
|
autoCommit = false,
|
||||||
encrypted = false,
|
encrypted = false,
|
||||||
) {
|
) {
|
||||||
this.#checkIsStandardList(kind);
|
this.checkIsStandardList(kind);
|
||||||
this.#checkInit();
|
this.#checkInit();
|
||||||
const list = this.#standardLists.get(kind);
|
const list = this.#standardLists.get(kind);
|
||||||
const tags = removeUndefined(Array.isArray(links) ? links.map(a => a.toEventTag()) : [links.toEventTag()]);
|
const tags = removeUndefined(Array.isArray(links) ? links.map(a => a.toEventTag()) : [links.toEventTag()]);
|
||||||
@ -363,7 +363,7 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
|
|||||||
autoCommit = false,
|
autoCommit = false,
|
||||||
encrypted = false,
|
encrypted = false,
|
||||||
) {
|
) {
|
||||||
this.#checkIsStandardList(kind);
|
this.checkIsStandardList(kind);
|
||||||
this.#checkInit();
|
this.#checkInit();
|
||||||
const list = this.#standardLists.get(kind);
|
const list = this.#standardLists.get(kind);
|
||||||
const tags = removeUndefined(Array.isArray(links) ? links.map(a => a.toEventTag()) : [links.toEventTag()]);
|
const tags = removeUndefined(Array.isArray(links) ? links.map(a => a.toEventTag()) : [links.toEventTag()]);
|
||||||
@ -416,7 +416,7 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkIsStandardList(kind: EventKind) {
|
checkIsStandardList(kind: EventKind) {
|
||||||
if (!(kind >= 10_000 && kind < 20_000)) {
|
if (!(kind >= 10_000 && kind < 20_000)) {
|
||||||
throw new Error("Not a standar list");
|
throw new Error("Not a standar list");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user