Muted words: phase 1
This commit is contained in:
parent
4d629f5087
commit
0e9ca7e2e3
@ -339,6 +339,9 @@
|
|||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12.5008C9 10.8439 10.3431 9.50076 12 9.50076C13.6569 9.50076 15 10.8439 15 12.5008C15 14.1576 13.6569 15.5008 12 15.5008C10.3431 15.5008 9 14.1576 9 12.5008Z" fill="currentColor"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12.5008C9 10.8439 10.3431 9.50076 12 9.50076C13.6569 9.50076 15 10.8439 15 12.5008C15 14.1576 13.6569 15.5008 12 15.5008C10.3431 15.5008 9 14.1576 9 12.5008Z" fill="currentColor"/>
|
||||||
</g>
|
</g>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="shield-tick" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M9 11.5L11 13.5L15.5 8.99999M20 12C20 16.9084 14.646 20.4784 12.698 21.6149C12.4766 21.744 12.3659 21.8086 12.2097 21.8421C12.0884 21.8681 11.9116 21.8681 11.7903 21.8421C11.6341 21.8086 11.5234 21.744 11.302 21.6149C9.35396 20.4784 4 16.9084 4 12V7.21759C4 6.41808 4 6.01833 4.13076 5.6747C4.24627 5.37113 4.43398 5.10027 4.67766 4.88552C4.9535 4.64243 5.3278 4.50207 6.0764 4.22134L11.4382 2.21067C11.6461 2.13271 11.75 2.09373 11.857 2.07827C11.9518 2.06457 12.0482 2.06457 12.143 2.07827C12.25 2.09373 12.3539 2.13271 12.5618 2.21067L17.9236 4.22134C18.6722 4.50207 19.0465 4.64243 19.3223 4.88552C19.566 5.10027 19.7537 5.37113 19.8692 5.6747C20 6.01833 20 6.41808 20 7.21759V12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 91 KiB |
@ -164,10 +164,6 @@
|
|||||||
min-height: unset;
|
min-height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden-note button {
|
|
||||||
max-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-note {
|
.expand-note {
|
||||||
padding: 0 0 16px 0;
|
padding: 0 0 16px 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -67,14 +67,14 @@ export interface NoteProps {
|
|||||||
const HiddenNote = ({ children }: { children: React.ReactNode }) => {
|
const HiddenNote = ({ children }: { children: React.ReactNode }) => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
return show ? (
|
return show ? (
|
||||||
<>{children}</>
|
children
|
||||||
) : (
|
) : (
|
||||||
<div className="card note hidden-note">
|
<div className="card note hidden-note">
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage {...messages.MutedAuthor} />
|
<FormattedMessage defaultMessage="This note has been muted" />
|
||||||
</p>
|
</p>
|
||||||
<button onClick={() => setShow(true)}>
|
<button type="button" onClick={() => setShow(true)}>
|
||||||
<FormattedMessage {...messages.Show} />
|
<FormattedMessage {...messages.Show} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -116,8 +116,7 @@ export function NoteInner(props: NoteProps) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showReactions, setShowReactions] = useState(false);
|
const [showReactions, setShowReactions] = useState(false);
|
||||||
const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]);
|
const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]);
|
||||||
const { isMuted } = useModeration();
|
const { isEventMuted } = useModeration();
|
||||||
const isOpMuted = isMuted(ev?.pubkey);
|
|
||||||
const { ref, inView } = useInView({ triggerOnce: true });
|
const { ref, inView } = useInView({ triggerOnce: true });
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { pinned, bookmarked } = login;
|
const { pinned, bookmarked } = login;
|
||||||
@ -466,5 +465,5 @@ export function NoteInner(props: NoteProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return !ignoreModeration && isOpMuted ? <HiddenNote>{note}</HiddenNote> : note;
|
return !ignoreModeration && isEventMuted(ev) ? <HiddenNote>{note}</HiddenNote> : note;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import useEventPublisher from "Hooks/useEventPublisher";
|
|||||||
import { getMutedKeys } from "Feed/MuteList";
|
import { getMutedKeys } from "Feed/MuteList";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { addSubscription, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
|
import { SnortAppData, addSubscription, setAppData, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
|
||||||
import { SnortPubKey } from "Const";
|
import { SnortPubKey } from "Const";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
import useRelaysFeedFollows from "./RelaysFeedFollows";
|
import useRelaysFeedFollows from "./RelaysFeedFollows";
|
||||||
@ -38,7 +38,9 @@ export default function useLoginFeed() {
|
|||||||
leaveOpen: true,
|
leaveOpen: true,
|
||||||
});
|
});
|
||||||
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]);
|
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]);
|
||||||
|
b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]);
|
||||||
b.withFilter()
|
b.withFilter()
|
||||||
|
.relay("wss://relay.snort.social")
|
||||||
.kinds([EventKind.SnortSubscriptions])
|
.kinds([EventKind.SnortSubscriptions])
|
||||||
.authors([bech32ToHex(SnortPubKey)])
|
.authors([bech32ToHex(SnortPubKey)])
|
||||||
.tag("p", [pubKey])
|
.tag("p", [pubKey])
|
||||||
@ -97,6 +99,14 @@ export default function useLoginFeed() {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).then(a => addSubscription(login, ...a.filter(a => a !== undefined).map(unwrap)));
|
).then(a => addSubscription(login, ...a.filter(a => a !== undefined).map(unwrap)));
|
||||||
|
|
||||||
|
const appData = getNewest(loginFeed.data.filter(a => a.kind === EventKind.AppData));
|
||||||
|
if(appData) {
|
||||||
|
publisher.decryptGeneric(appData.content, appData.pubkey).then(d => {
|
||||||
|
setAppData(login, JSON.parse(d) as SnortAppData, appData.created_at * 1000);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [loginFeed, publisher]);
|
}, [loginFeed, publisher]);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HexKey } from "@snort/system";
|
import { HexKey, TaggedNostrEvent } from "@snort/system";
|
||||||
import useEventPublisher from "Hooks/useEventPublisher";
|
import useEventPublisher from "Hooks/useEventPublisher";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setBlocked, setMuted } from "Login";
|
import { setBlocked, setMuted } from "Login";
|
||||||
@ -7,7 +7,7 @@ import { System } from "index";
|
|||||||
|
|
||||||
export default function useModeration() {
|
export default function useModeration() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { muted, blocked } = login;
|
const { muted, blocked, appData } = login;
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
|
|
||||||
async function setMutedList(pub: HexKey[], priv: HexKey[]) {
|
async function setMutedList(pub: HexKey[], priv: HexKey[]) {
|
||||||
@ -57,6 +57,14 @@ export default function useModeration() {
|
|||||||
setMuted(login, newMuted, ts);
|
setMuted(login, newMuted, ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMutedWord(word: string) {
|
||||||
|
return appData.item.mutedWords.includes(word.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEventMuted(ev: TaggedNostrEvent) {
|
||||||
|
return isMuted(ev.pubkey) || appData.item.mutedWords.some(w => ev.content.toLowerCase().includes(w));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
muted: muted.item,
|
muted: muted.item,
|
||||||
mute,
|
mute,
|
||||||
@ -67,5 +75,7 @@ export default function useModeration() {
|
|||||||
block,
|
block,
|
||||||
unblock,
|
unblock,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
|
isMutedWord,
|
||||||
|
isEventMuted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import * as secp from "@noble/curves/secp256k1";
|
|||||||
import * as utils from "@noble/curves/abstract/utils";
|
import * as utils from "@noble/curves/abstract/utils";
|
||||||
|
|
||||||
import { DefaultRelays, SnortPubKey } from "Const";
|
import { DefaultRelays, SnortPubKey } from "Const";
|
||||||
import { LoginStore, UserPreferences, LoginSession, LoginSessionType } from "Login";
|
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData } from "Login";
|
||||||
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
||||||
import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
@ -157,6 +157,15 @@ export function setBookmarked(state: LoginSession, bookmarked: Array<string>, ts
|
|||||||
LoginStore.updateSession(state);
|
LoginStore.updateSession(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setAppData(state: LoginSession, data: SnortAppData, ts: number) {
|
||||||
|
if(state.appData.timestamp >= ts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.appData.item = data;
|
||||||
|
state.appData.timestamp = ts;
|
||||||
|
LoginStore.updateSession(state);
|
||||||
|
}
|
||||||
|
|
||||||
export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) {
|
export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) {
|
||||||
const newSubs = dedupeById([...(state.subscriptions || []), ...subs]);
|
const newSubs = dedupeById([...(state.subscriptions || []), ...subs]);
|
||||||
if (newSubs.length !== state.subscriptions.length) {
|
if (newSubs.length !== state.subscriptions.length) {
|
||||||
|
@ -18,6 +18,10 @@ export enum LoginSessionType {
|
|||||||
Nip7os = "nip7_os",
|
Nip7os = "nip7_os",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SnortAppData {
|
||||||
|
mutedWords: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginSession {
|
export interface LoginSession {
|
||||||
/**
|
/**
|
||||||
* Unique ID to identify this session
|
* Unique ID to identify this session
|
||||||
@ -114,4 +118,9 @@ export interface LoginSession {
|
|||||||
* Remote signer relays (NIP-46)
|
* Remote signer relays (NIP-46)
|
||||||
*/
|
*/
|
||||||
remoteSignerRelays?: Array<string>;
|
remoteSignerRelays?: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snort application data
|
||||||
|
*/
|
||||||
|
appData: Newest<SnortAppData>;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,12 @@ const LoggedOut = {
|
|||||||
latestNotification: 0,
|
latestNotification: 0,
|
||||||
readNotifications: 0,
|
readNotifications: 0,
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
|
appData: {
|
||||||
|
item: {
|
||||||
|
mutedWords: []
|
||||||
|
},
|
||||||
|
timestamp: 0,
|
||||||
|
},
|
||||||
} as LoginSession;
|
} as LoginSession;
|
||||||
|
|
||||||
export class MultiAccountStore extends ExternalStore<LoginSession> {
|
export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||||
|
@ -9,6 +9,7 @@ import AccountsPage from "Pages/settings/Accounts";
|
|||||||
import { WalletSettingsRoutes } from "Pages/settings/WalletSettings";
|
import { WalletSettingsRoutes } from "Pages/settings/WalletSettings";
|
||||||
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 { ModerationSettings } from "./settings/Moderation";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
@ -56,6 +57,10 @@ export const SettingsRoutes: RouteObject[] = [
|
|||||||
path: "keys",
|
path: "keys",
|
||||||
element: <ExportKeys />,
|
element: <ExportKeys />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "moderation",
|
||||||
|
element: <ModerationSettings />,
|
||||||
|
},
|
||||||
...ManageHandleRoutes,
|
...ManageHandleRoutes,
|
||||||
...WalletSettingsRoutes,
|
...WalletSettingsRoutes,
|
||||||
],
|
],
|
||||||
|
44
packages/app/src/Pages/settings/Moderation.tsx
Normal file
44
packages/app/src/Pages/settings/Moderation.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { unixNowMs } from "@snort/shared";
|
||||||
|
import useLogin from "Hooks/useLogin";
|
||||||
|
import { setAppData } from "Login";
|
||||||
|
import { appendDedupe } from "SnortUtils";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
export function ModerationSettings() {
|
||||||
|
const login = useLogin();
|
||||||
|
const [muteWord, setMuteWord] = useState("");
|
||||||
|
|
||||||
|
function addMutedWord() {
|
||||||
|
login.appData ??= {
|
||||||
|
item: {
|
||||||
|
mutedWords: []
|
||||||
|
},
|
||||||
|
timestamp: 0
|
||||||
|
};
|
||||||
|
setAppData(login, {
|
||||||
|
...login.appData.item,
|
||||||
|
mutedWords: appendDedupe(login.appData.item.mutedWords, [muteWord])
|
||||||
|
}, unixNowMs());
|
||||||
|
setMuteWord("");
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage defaultMessage="Muted Words" />
|
||||||
|
</h2>
|
||||||
|
<div className="flex-column g12">
|
||||||
|
<div className="flex g8">
|
||||||
|
<input type="text" placeholder="eg. crypto" className="w-max" value={muteWord} onChange={e => setMuteWord(e.target.value)} />
|
||||||
|
<button type="button" onClick={addMutedWord}>
|
||||||
|
<FormattedMessage defaultMessage="Add" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{login.appData.item.mutedWords.map(v => <div className="p br b flex f-space">
|
||||||
|
<div>{v}</div>
|
||||||
|
<button type="button">
|
||||||
|
<FormattedMessage defaultMessage="Delete" />
|
||||||
|
</button>
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
@ -51,6 +51,11 @@ const SettingsIndex = () => {
|
|||||||
<FormattedMessage defaultMessage="Export Keys" />
|
<FormattedMessage defaultMessage="Export Keys" />
|
||||||
<Icon name="arrowFront" size={16} />
|
<Icon name="arrowFront" size={16} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="settings-row" onClick={() => navigate("moderation")}>
|
||||||
|
<Icon name="shield-tick" size={24} />
|
||||||
|
<FormattedMessage defaultMessage="Moderation" />
|
||||||
|
<Icon name="arrowFront" size={16} />
|
||||||
|
</div>
|
||||||
<div className="settings-row" onClick={() => navigate("handle")}>
|
<div className="settings-row" onClick={() => navigate("handle")}>
|
||||||
<Icon name="badge" size={24} />
|
<Icon name="badge" size={24} />
|
||||||
<FormattedMessage defaultMessage="Nostr Adddress" />
|
<FormattedMessage defaultMessage="Nostr Adddress" />
|
||||||
|
@ -133,6 +133,10 @@ code {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.b {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
background: var(--primary-gradient);
|
background: var(--primary-gradient);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ enum EventKind {
|
|||||||
Badge = 30009, // NIP-58
|
Badge = 30009, // NIP-58
|
||||||
ProfileBadges = 30008, // NIP-58
|
ProfileBadges = 30008, // NIP-58
|
||||||
LongFormTextNote = 30023, // NIP-23
|
LongFormTextNote = 30023, // NIP-23
|
||||||
|
AppData = 30_078, // NIP-78
|
||||||
LiveEvent = 30311, // NIP-102
|
LiveEvent = 30311, // NIP-102
|
||||||
UserStatus = 30315, // NIP-38
|
UserStatus = 30315, // NIP-38
|
||||||
ZapstrTrack = 31337,
|
ZapstrTrack = 31337,
|
||||||
|
@ -3,11 +3,13 @@ import * as utils from "@noble/curves/abstract/utils";
|
|||||||
import { unwrap, getPublicKey, unixNow } from "@snort/shared";
|
import { unwrap, getPublicKey, unixNow } from "@snort/shared";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
decodeEncryptionPayload,
|
||||||
EventKind,
|
EventKind,
|
||||||
EventSigner,
|
EventSigner,
|
||||||
FullRelaySettings,
|
FullRelaySettings,
|
||||||
HexKey,
|
HexKey,
|
||||||
Lists,
|
Lists,
|
||||||
|
MessageEncryptorVersion,
|
||||||
NostrEvent,
|
NostrEvent,
|
||||||
NostrLink,
|
NostrLink,
|
||||||
NotSignedNostrEvent,
|
NotSignedNostrEvent,
|
||||||
@ -24,6 +26,7 @@ import { EventBuilder } from "./event-builder";
|
|||||||
import { EventExt } from "./event-ext";
|
import { EventExt } from "./event-ext";
|
||||||
import { findTag } from "./utils";
|
import { findTag } from "./utils";
|
||||||
import { Nip7Signer } from "./impl/nip7";
|
import { Nip7Signer } from "./impl/nip7";
|
||||||
|
import { base64 } from "@scure/base";
|
||||||
|
|
||||||
type EventBuilderHook = (ev: EventBuilder) => EventBuilder;
|
type EventBuilderHook = (ev: EventBuilder) => EventBuilder;
|
||||||
|
|
||||||
@ -269,6 +272,23 @@ export class EventPublisher {
|
|||||||
return await this.#sign(eb);
|
return await this.#sign(eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic decryption using NIP-23 payload scheme
|
||||||
|
*/
|
||||||
|
async decryptGeneric(content: string, from: string) {
|
||||||
|
const pl = decodeEncryptionPayload(content);
|
||||||
|
switch(pl.v) {
|
||||||
|
case MessageEncryptorVersion.Nip4: {
|
||||||
|
const nip4Payload = `${base64.encode(pl.ciphertext)}?iv=${base64.encode(pl.nonce)}`;
|
||||||
|
return await this.#signer.nip4Decrypt(nip4Payload, from);
|
||||||
|
}
|
||||||
|
case MessageEncryptorVersion.XChaCha20: {
|
||||||
|
return await this.#signer.nip44Decrypt(content, from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Not supported version");
|
||||||
|
}
|
||||||
|
|
||||||
async decryptDm(note: NostrEvent) {
|
async decryptDm(note: NostrEvent) {
|
||||||
if (note.kind === EventKind.SealedRumor) {
|
if (note.kind === EventKind.SealedRumor) {
|
||||||
const unseal = await this.unsealRumor(note);
|
const unseal = await this.unsealRumor(note);
|
||||||
|
@ -6,6 +6,7 @@ import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr";
|
|||||||
import { ProfileLoaderService } from "./profile-cache";
|
import { ProfileLoaderService } from "./profile-cache";
|
||||||
import { RelayCache } from "./gossip-model";
|
import { RelayCache } from "./gossip-model";
|
||||||
import { QueryOptimizer } from "./query-optimizer";
|
import { QueryOptimizer } from "./query-optimizer";
|
||||||
|
import { base64 } from "@scure/base";
|
||||||
|
|
||||||
export * from "./nostr-system";
|
export * from "./nostr-system";
|
||||||
export { default as EventKind } from "./event-kind";
|
export { default as EventKind } from "./event-kind";
|
||||||
@ -136,3 +137,25 @@ export interface MessageEncryptor {
|
|||||||
encryptData(plaintext: string, sharedSecet: Uint8Array): Promise<MessageEncryptorPayload> | MessageEncryptorPayload;
|
encryptData(plaintext: string, sharedSecet: Uint8Array): Promise<MessageEncryptorPayload> | MessageEncryptorPayload;
|
||||||
decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise<string> | string;
|
decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise<string> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function decodeEncryptionPayload(p: string) {
|
||||||
|
if (p.startsWith("{") && p.endsWith("}")) {
|
||||||
|
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
||||||
|
return {
|
||||||
|
v: pj.v,
|
||||||
|
nonce: base64.decode(pj.nonce),
|
||||||
|
ciphertext: base64.decode(pj.ciphertext),
|
||||||
|
} as MessageEncryptorPayload;
|
||||||
|
} else {
|
||||||
|
const buf = base64.decode(p);
|
||||||
|
return {
|
||||||
|
v: buf[0],
|
||||||
|
nonce: buf.subarray(1, 25),
|
||||||
|
ciphertext: buf.subarray(25),
|
||||||
|
} as MessageEncryptorPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeEncryptionPayload(p: MessageEncryptorPayload) {
|
||||||
|
return base64.encode(new Uint8Array([p.v, ...p.nonce, ...p.ciphertext]));
|
||||||
|
}
|
@ -3,7 +3,7 @@ import { getPublicKey } from "@snort/shared";
|
|||||||
import { EventExt } from "./event-ext";
|
import { EventExt } from "./event-ext";
|
||||||
import { Nip4WebCryptoEncryptor } from "./impl/nip4";
|
import { Nip4WebCryptoEncryptor } from "./impl/nip4";
|
||||||
import { XChaCha20Encryptor } from "./impl/nip44";
|
import { XChaCha20Encryptor } from "./impl/nip44";
|
||||||
import { MessageEncryptorPayload, MessageEncryptorVersion } from "./index";
|
import { MessageEncryptorVersion, decodeEncryptionPayload, encodeEncryptionPayload } from "./index";
|
||||||
import { NostrEvent } from "./nostr";
|
import { NostrEvent } from "./nostr";
|
||||||
import { base64 } from "@scure/base";
|
import { base64 } from "@scure/base";
|
||||||
|
|
||||||
@ -74,11 +74,11 @@ export class PrivateKeySigner implements EventSigner {
|
|||||||
const enc = new XChaCha20Encryptor();
|
const enc = new XChaCha20Encryptor();
|
||||||
const shared = enc.getSharedSecret(this.#privateKey, key);
|
const shared = enc.getSharedSecret(this.#privateKey, key);
|
||||||
const data = enc.encryptData(content, shared);
|
const data = enc.encryptData(content, shared);
|
||||||
return this.#encodePayload(data);
|
return encodeEncryptionPayload(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async nip44Decrypt(content: string, otherKey: string) {
|
async nip44Decrypt(content: string, otherKey: string) {
|
||||||
const payload = this.#decodePayload(content);
|
const payload = decodeEncryptionPayload(content);
|
||||||
if (payload.v !== MessageEncryptorVersion.XChaCha20) throw new Error("Invalid payload version");
|
if (payload.v !== MessageEncryptorVersion.XChaCha20) throw new Error("Invalid payload version");
|
||||||
|
|
||||||
const enc = new XChaCha20Encryptor();
|
const enc = new XChaCha20Encryptor();
|
||||||
@ -86,28 +86,6 @@ export class PrivateKeySigner implements EventSigner {
|
|||||||
return enc.decryptData(payload, shared);
|
return enc.decryptData(payload, shared);
|
||||||
}
|
}
|
||||||
|
|
||||||
#decodePayload(p: string) {
|
|
||||||
if (p.startsWith("{") && p.endsWith("}")) {
|
|
||||||
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
|
||||||
return {
|
|
||||||
v: pj.v,
|
|
||||||
nonce: base64.decode(pj.nonce),
|
|
||||||
ciphertext: base64.decode(pj.ciphertext),
|
|
||||||
} as MessageEncryptorPayload;
|
|
||||||
} else {
|
|
||||||
const buf = base64.decode(p);
|
|
||||||
return {
|
|
||||||
v: buf[0],
|
|
||||||
nonce: buf.subarray(1, 25),
|
|
||||||
ciphertext: buf.subarray(25),
|
|
||||||
} as MessageEncryptorPayload;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#encodePayload(p: MessageEncryptorPayload) {
|
|
||||||
return base64.encode(new Uint8Array([p.v, ...p.nonce, ...p.ciphertext]));
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(ev: NostrEvent): Promise<NostrEvent> {
|
sign(ev: NostrEvent): Promise<NostrEvent> {
|
||||||
EventExt.sign(ev, this.#privateKey);
|
EventExt.sign(ev, this.#privateKey);
|
||||||
return Promise.resolve(ev);
|
return Promise.resolve(ev);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user