This commit is contained in:
2023-09-21 22:01:39 +01:00
parent 8244441929
commit 96d4e4bcc5
16 changed files with 480 additions and 384 deletions

View File

@ -9,9 +9,11 @@ export interface ModalProps {
} }
export default function Modal(props: ModalProps) { export default function Modal(props: ModalProps) {
return <div className={`modal${props.className ? ` ${props.className}` : ""}`} onClick={props.onClose}> return (
<div className={`modal${props.className ? ` ${props.className}` : ""}`} onClick={props.onClose}>
<div className="modal-body" onClick={e => e.stopPropagation()}> <div className="modal-body" onClick={e => e.stopPropagation()}>
{props.children} {props.children}
</div> </div>
</div>; </div>
);
} }

View File

@ -1,6 +1,14 @@
import "./NoteCreator.css"; import "./NoteCreator.css";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { EventKind, NostrPrefix, TaggedNostrEvent, EventBuilder, tryParseNostrLink, NostrLink, NostrEvent } from "@snort/system"; import {
EventKind,
NostrPrefix,
TaggedNostrEvent,
EventBuilder,
tryParseNostrLink,
NostrLink,
NostrEvent,
} from "@snort/system";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import useEventPublisher from "Hooks/useEventPublisher"; import useEventPublisher from "Hooks/useEventPublisher";
@ -30,7 +38,7 @@ export function NoteCreator() {
async function buildNote() { async function buildNote() {
try { try {
note.update(v => v.error = ""); note.update(v => (v.error = ""));
if (note && publisher) { if (note && publisher) {
let extraTags: Array<Array<string>> | undefined; let extraTags: Array<Array<string>> | undefined;
if (note.zapSplits) { if (note.zapSplits) {
@ -99,7 +107,9 @@ export function NoteCreator() {
eb.kind(kind); eb.kind(kind);
return eb; return eb;
}; };
const ev = note.replyTo ? await publisher.reply(note.replyTo, note.note, hk) : await publisher.note(note.note, hk); const ev = note.replyTo
? await publisher.reply(note.replyTo, note.note, hk)
: await publisher.note(note.note, hk);
return ev; return ev;
} }
} catch (e) { } catch (e) {
@ -131,7 +141,7 @@ export function NoteCreator() {
note.update(v => { note.update(v => {
v.reset(); v.reset();
v.show = false; v.show = false;
}) });
} }
} }
@ -181,7 +191,7 @@ export function NoteCreator() {
function onChange(ev: React.ChangeEvent<HTMLTextAreaElement>) { function onChange(ev: React.ChangeEvent<HTMLTextAreaElement>) {
const { value } = ev.target; const { value } = ev.target;
note.update(n => n.note = value); note.update(n => (n.note = value));
} }
function cancel() { function cancel() {
@ -198,10 +208,10 @@ export function NoteCreator() {
async function loadPreview() { async function loadPreview() {
if (note.preview) { if (note.preview) {
note.update(v => v.preview = undefined); note.update(v => (v.preview = undefined));
} else if (publisher) { } else if (publisher) {
const tmpNote = await buildNote(); const tmpNote = await buildNote();
note.update(v => v.preview = tmpNote); note.update(v => (v.preview = tmpNote));
} }
} }
@ -244,7 +254,7 @@ export function NoteCreator() {
</div> </div>
</div> </div>
))} ))}
<button onClick={() => note.update(v => v.pollOptions = [...(note.pollOptions ?? []), ""])}> <button onClick={() => note.update(v => (v.pollOptions = [...(note.pollOptions ?? []), ""]))}>
<Icon name="plus" size={14} /> <Icon name="plus" size={14} />
</button> </button>
</> </>
@ -256,7 +266,7 @@ export function NoteCreator() {
if (note.pollOptions) { if (note.pollOptions) {
const copy = [...note.pollOptions]; const copy = [...note.pollOptions];
copy[i] = v; copy[i] = v;
note.update(v => v.pollOptions = copy); note.update(v => (v.pollOptions = copy));
} }
} }
@ -264,7 +274,7 @@ export function NoteCreator() {
if (note.pollOptions) { if (note.pollOptions) {
const copy = [...note.pollOptions]; const copy = [...note.pollOptions];
copy.splice(i, 1); copy.splice(i, 1);
note.update(v => v.pollOptions = copy); note.update(v => (v.pollOptions = copy));
} }
} }
@ -281,15 +291,22 @@ export function NoteCreator() {
type="checkbox" type="checkbox"
checked={!note.selectedCustomRelays || note.selectedCustomRelays.includes(r)} checked={!note.selectedCustomRelays || note.selectedCustomRelays.includes(r)}
onChange={e => { onChange={e => {
note.update(v => v.selectedCustomRelays = ( note.update(
v =>
(v.selectedCustomRelays =
// set false if all relays selected // set false if all relays selected
e.target.checked && note.selectedCustomRelays && note.selectedCustomRelays.length == a.length - 1 e.target.checked &&
note.selectedCustomRelays &&
note.selectedCustomRelays.length == a.length - 1
? undefined ? undefined
: // otherwise return selectedCustomRelays with target relay added / removed : // otherwise return selectedCustomRelays with target relay added / removed
a.filter(el => el === r ? e.target.checked : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el)) a.filter(el =>
)); el === r
} ? e.target.checked
} : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el),
)),
);
}}
/> />
</div> </div>
</div> </div>
@ -330,7 +347,8 @@ export function NoteCreator() {
}; };
if (!note.show) return null; if (!note.show) return null;
return (<Modal id="note-creator" className="note-creator-modal" onClose={() => note.update(v => v.show = false)}> return (
<Modal id="note-creator" className="note-creator-modal" onClose={() => note.update(v => (v.show = false))}>
{note.replyTo && ( {note.replyTo && (
<Note <Note
data={note.replyTo} data={note.replyTo}
@ -352,7 +370,7 @@ export function NoteCreator() {
className={`textarea ${note.active ? "textarea--focused" : ""}`} className={`textarea ${note.active ? "textarea--focused" : ""}`}
onChange={c => onChange(c)} onChange={c => onChange(c)}
value={note.note} value={note.note}
onFocus={() => note.update(v => v.active = true)} onFocus={() => note.update(v => (v.active = true))}
onKeyDown={e => { onKeyDown={e => {
if (e.key === "Enter" && e.metaKey) { if (e.key === "Enter" && e.metaKey) {
sendNote().catch(console.warn); sendNote().catch(console.warn);
@ -364,14 +382,20 @@ export function NoteCreator() {
)} )}
<div className="flex f-space"> <div className="flex f-space">
<div className="flex g8"> <div className="flex g8">
<ProfileImage pubkey={login.publicKey ?? ""} className="note-creator-icon" link="" showUsername={false} showFollowingMark={false} /> <ProfileImage
pubkey={login.publicKey ?? ""}
className="note-creator-icon"
link=""
showUsername={false}
showFollowingMark={false}
/>
{note.pollOptions === undefined && !note.replyTo && ( {note.pollOptions === undefined && !note.replyTo && (
<div className="note-creator-icon"> <div className="note-creator-icon">
<Icon name="pie-chart" onClick={() => note.update(v => v.pollOptions = ["A", "B"])} size={24} /> <Icon name="pie-chart" onClick={() => note.update(v => (v.pollOptions = ["A", "B"]))} size={24} />
</div> </div>
)} )}
<AsyncIcon iconName="image-plus" iconSize={24} onClick={attachFile} className="note-creator-icon" /> <AsyncIcon iconName="image-plus" iconSize={24} onClick={attachFile} className="note-creator-icon" />
<button className="secondary" onClick={() => note.update(v => v.advanced = !v.advanced)}> <button className="secondary" onClick={() => note.update(v => (v.advanced = !v.advanced))}>
<FormattedMessage defaultMessage="Advanced" /> <FormattedMessage defaultMessage="Advanced" />
</button> </button>
</div> </div>
@ -414,7 +438,11 @@ export function NoteCreator() {
<input <input
type="text" type="text"
value={v.value} value={v.value}
onChange={e => note.update(v => v.zapSplits = arr.map((vv, ii) => (ii === i ? { ...vv, value: e.target.value } : vv)))} onChange={e =>
note.update(
v => (v.zapSplits = arr.map((vv, ii) => (ii === i ? { ...vv, value: e.target.value } : vv))),
)
}
placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address" })} placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address" })}
/> />
</div> </div>
@ -426,21 +454,30 @@ export function NoteCreator() {
type="number" type="number"
min={0} min={0}
value={v.weight} value={v.weight}
onChange={e => note.update(v => v.zapSplits = arr.map((vv, ii) => (ii === i ? { ...vv, weight: Number(e.target.value) } : vv)))} onChange={e =>
note.update(
v =>
(v.zapSplits = arr.map((vv, ii) =>
ii === i ? { ...vv, weight: Number(e.target.value) } : vv,
)),
)
}
/> />
</div> </div>
<div className="flex-column f-shrink g4"> <div className="flex-column f-shrink g4">
<div>&nbsp;</div> <div>&nbsp;</div>
<Icon <Icon
name="close" name="close"
onClick={() => note.update(v => v.zapSplits = (v.zapSplits ?? []).filter((_v, ii) => ii !== i))} onClick={() => note.update(v => (v.zapSplits = (v.zapSplits ?? []).filter((_v, ii) => ii !== i)))}
/> />
</div> </div>
</div> </div>
))} ))}
<button <button
type="button" type="button"
onClick={() => note.update(v => v.zapSplits = [...(v.zapSplits ?? []), { type: "pubkey", value: "", weight: 1 }])}> onClick={() =>
note.update(v => (v.zapSplits = [...(v.zapSplits ?? []), { type: "pubkey", value: "", weight: 1 }]))
}>
<FormattedMessage defaultMessage="Add" /> <FormattedMessage defaultMessage="Add" />
</button> </button>
</div> </div>
@ -457,7 +494,7 @@ export function NoteCreator() {
className="w-max" className="w-max"
type="text" type="text"
value={note.sensitive} value={note.sensitive}
onChange={e => note.update(v => v.sensitive = e.target.value)} onChange={e => note.update(v => (v.sensitive = e.target.value))}
maxLength={50} maxLength={50}
minLength={1} minLength={1}
placeholder={formatMessage({ placeholder={formatMessage({

View File

@ -11,7 +11,15 @@ import Modal from "./Modal";
import Spinner from "Icons/Spinner"; import Spinner from "Icons/Spinner";
const PinLen = 6; const PinLen = 6;
export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: string) => Promise<void>, onCancel: () => void, subTitle?: ReactNode }) { export function PinPrompt({
onResult,
onCancel,
subTitle,
}: {
onResult: (v: string) => Promise<void>;
onCancel: () => void;
subTitle?: ReactNode;
}) {
const [pin, setPin] = useState(""); const [pin, setPin] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@ -20,8 +28,9 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
const handleKey = (e: KeyboardEvent) => { const handleKey = (e: KeyboardEvent) => {
console.debug(e); console.debug(e);
if (!isNaN(Number(e.key)) && pin.length < PinLen) { if (!isNaN(Number(e.key)) && pin.length < PinLen) {
setPin(s => s += e.key); setPin(s => (s += e.key));
} if (e.key === "Backspace") { }
if (e.key === "Backspace") {
setPin(s => s.slice(0, -1)); setPin(s => s.slice(0, -1));
} else { } else {
e.preventDefault(); e.preventDefault();
@ -42,13 +51,15 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
console.error(e); console.error(e);
setPin(""); setPin("");
if (e instanceof InvalidPinError) { if (e instanceof InvalidPinError) {
setError(formatMessage({ setError(
defaultMessage: "Incorrect pin" formatMessage({
})); defaultMessage: "Incorrect pin",
}),
);
} else if (e instanceof Error) { } else if (e instanceof Error) {
setError(e.message); setError(e.message);
} }
}) });
} }
}, [pin]); }, [pin]);
@ -57,20 +68,17 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
boxes.push(<Spinner className="flex f-center f-1" />); boxes.push(<Spinner className="flex f-center f-1" />);
} else { } else {
for (let x = 0; x < PinLen; x++) { for (let x = 0; x < PinLen; x++) {
boxes.push(<div className="pin-box flex f-center f-1"> boxes.push(<div className="pin-box flex f-center f-1">{pin[x]}</div>);
{pin[x]}
</div>)
} }
} }
return <Modal id="pin" onClose={() => onCancel()}> return (
<Modal id="pin" onClose={() => onCancel()}>
<div className="flex-column g12"> <div className="flex-column g12">
<h2> <h2>
<FormattedMessage defaultMessage="Enter Pin" /> <FormattedMessage defaultMessage="Enter Pin" />
</h2> </h2>
{subTitle} {subTitle}
<div className="flex g4"> <div className="flex g4">{boxes}</div>
{boxes}
</div>
{error && <b className="error">{error}</b>} {error && <b className="error">{error}</b>}
<div> <div>
<button type="button" onClick={() => onCancel()}> <button type="button" onClick={() => onCancel()}>
@ -79,6 +87,7 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri
</div> </div>
</div> </div>
</Modal> </Modal>
);
} }
export function LoginUnlock() { export function LoginUnlock() {
@ -97,7 +106,7 @@ export function LoginUnlock() {
LoginStore.updateSession({ LoginStore.updateSession({
...login, ...login,
privateKeyData: newPin, privateKeyData: newPin,
privateKey: undefined privateKey: undefined,
}); });
} }
@ -115,16 +124,32 @@ export function LoginUnlock() {
if (login.publicKey && !publisher && sessionNeedsPin(login)) { if (login.publicKey && !publisher && sessionNeedsPin(login)) {
if (login.privateKey !== undefined) { if (login.privateKey !== undefined) {
return <PinPrompt subTitle={<p> return (
<PinPrompt
subTitle={
<p>
<FormattedMessage defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open Snort." /> <FormattedMessage defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open Snort." />
</p>} onResult={encryptMigration} onCancel={() => { </p>
// nothing
}} />
} }
return <PinPrompt subTitle={<p> onResult={encryptMigration}
onCancel={() => {
// nothing
}}
/>
);
}
return (
<PinPrompt
subTitle={
<p>
<FormattedMessage defaultMessage="Enter pin to unlock private key" /> <FormattedMessage defaultMessage="Enter pin to unlock private key" />
</p>} onResult={unlockSession} onCancel={() => { </p>
}
onResult={unlockSession}
onCancel={() => {
//nothing //nothing
}} /> }}
/>
);
} }
} }

View File

@ -8,7 +8,7 @@ import messages from "./messages";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import { System } from "index"; import { System } from "index";
export function ReBroadcaster({ onClose, ev }: { onClose: () => void, ev: TaggedNostrEvent }) { export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: TaggedNostrEvent }) {
const [selected, setSelected] = useState<Array<string>>(); const [selected, setSelected] = useState<Array<string>>();
const publisher = useEventPublisher(); const publisher = useEventPublisher();
@ -44,10 +44,12 @@ export function ReBroadcaster({ onClose, ev }: { onClose: () => void, ev: Tagged
<input <input
type="checkbox" type="checkbox"
checked={!selected || selected.includes(r)} checked={!selected || selected.includes(r)}
onChange={e => setSelected( onChange={e =>
setSelected(
e.target.checked && selected && selected.length == a.length - 1 e.target.checked && selected && selected.length == a.length - 1
? undefined ? undefined
: a.filter(el => el === r ? e.target.checked : !selected || selected.includes(el))) : a.filter(el => (el === r ? e.target.checked : !selected || selected.includes(el))),
)
} }
/> />
</div> </div>

View File

@ -53,7 +53,8 @@ export default function Layout() {
} }
}, [location]); }, [location]);
return (<> return (
<>
<div className={pageClass}> <div className={pageClass}>
{!shouldHideHeader && ( {!shouldHideHeader && (
<header className="main-content"> <header className="main-content">
@ -62,13 +63,16 @@ export default function Layout() {
</header> </header>
)} )}
<Outlet /> <Outlet />
{!shouldHideNoteCreator && ( {!shouldHideNoteCreator && (
<> <>
<button className="primary note-create-button" onClick={() => note.update(v => { <button
className="primary note-create-button"
onClick={() =>
note.update(v => {
v.replyTo = undefined; v.replyTo = undefined;
v.show = true v.show = true;
})}> })
}>
<Icon name="plus" size={16} /> <Icon name="plus" size={16} />
</button> </button>
<NoteCreator /> <NoteCreator />
@ -138,7 +142,7 @@ const AccountHeader = () => {
<button type="button" onClick={() => navigate("/login")}> <button type="button" onClick={() => navigate("/login")}>
<FormattedMessage {...messages.Login} /> <FormattedMessage {...messages.Login} />
</button> </button>
) );
} }
return ( return (
<div className="header-actions"> <div className="header-actions">

View File

@ -300,15 +300,23 @@ export default function LoginPage() {
<AsyncButton onClick={() => setPin(true)}> <AsyncButton onClick={() => setPin(true)}>
<FormattedMessage defaultMessage="Create Account" /> <FormattedMessage defaultMessage="Create Account" />
</AsyncButton> </AsyncButton>
{pin && <PinPrompt subTitle={<p> {pin && (
<PinPrompt
subTitle={
<p>
<FormattedMessage defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open Snort." /> <FormattedMessage defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open Snort." />
</p>} onResult={async pin => { </p>
}
onResult={async pin => {
if (key) { if (key) {
await doLogin(pin); await doLogin(pin);
} else { } else {
await makeRandomKey(pin); await makeRandomKey(pin);
} }
}} onCancel={() => setPin(false)} />} }}
onCancel={() => setPin(false)}
/>
)}
{altLogins()} {altLogins()}
</div> </div>
{installExtension()} {installExtension()}

View File

@ -38,7 +38,7 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
update: (fn: (v: NoteCreatorDataSnapshot) => void) => { update: (fn: (v: NoteCreatorDataSnapshot) => void) => {
fn(this.#data); fn(this.#data);
this.notifyChange(); this.notifyChange();
} },
}; };
} }
@ -67,7 +67,7 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
fn(this.#data); fn(this.#data);
console.debug(this.#data); console.debug(this.#data);
this.notifyChange(); this.notifyChange();
} },
} as NoteCreatorDataSnapshot; } as NoteCreatorDataSnapshot;
return sn; return sn;
} }
@ -76,5 +76,8 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
const NoteCreatorState = new NoteCreatorStore(); const NoteCreatorState = new NoteCreatorStore();
export function useNoteCreator() { export function useNoteCreator() {
return useSyncExternalStore(c => NoteCreatorState.hook(c), () => NoteCreatorState.snapshot()); return useSyncExternalStore(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot(),
);
} }

View File

@ -99,6 +99,9 @@
"25V4l1": { "25V4l1": {
"defaultMessage": "Banner" "defaultMessage": "Banner"
}, },
"27jzYm": {
"defaultMessage": "Enter pin to unlock private key"
},
"2IFGap": { "2IFGap": {
"defaultMessage": "Donate" "defaultMessage": "Donate"
}, },
@ -539,6 +542,9 @@
"KoFlZg": { "KoFlZg": {
"defaultMessage": "Enter mint URL" "defaultMessage": "Enter mint URL"
}, },
"KtsyO0": {
"defaultMessage": "Enter Pin"
},
"LF5kYT": { "LF5kYT": {
"defaultMessage": "Other Connections" "defaultMessage": "Other Connections"
}, },
@ -887,6 +893,9 @@
"defaultMessage": "Install Extension", "defaultMessage": "Install Extension",
"description": "Heading for install key manager extension" "description": "Heading for install key manager extension"
}, },
"c2DTVd": {
"defaultMessage": "Enter a pin to encrypt your private key, you must enter this pin every time you open Snort."
},
"c35bj2": { "c35bj2": {
"defaultMessage": "If you have an enquiry about your NIP-05 order please DM {link}" "defaultMessage": "If you have an enquiry about your NIP-05 order please DM {link}"
}, },
@ -1230,6 +1239,9 @@
"qtWLmt": { "qtWLmt": {
"defaultMessage": "Like" "defaultMessage": "Like"
}, },
"qz9fty": {
"defaultMessage": "Incorrect pin"
},
"r3C4x/": { "r3C4x/": {
"defaultMessage": "Software" "defaultMessage": "Software"
}, },

View File

@ -32,6 +32,7 @@
"1udzha": "Conversations", "1udzha": "Conversations",
"2/2yg+": "Add", "2/2yg+": "Add",
"25V4l1": "Banner", "25V4l1": "Banner",
"27jzYm": "Enter pin to unlock private key",
"2IFGap": "Donate", "2IFGap": "Donate",
"2LbrkB": "Enter password", "2LbrkB": "Enter password",
"2a2YiP": "{n} Bookmarks", "2a2YiP": "{n} Bookmarks",
@ -177,6 +178,7 @@
"KWuDfz": "I have saved my keys, continue", "KWuDfz": "I have saved my keys, continue",
"KahimY": "Unknown event kind: {kind}", "KahimY": "Unknown event kind: {kind}",
"KoFlZg": "Enter mint URL", "KoFlZg": "Enter mint URL",
"KtsyO0": "Enter Pin",
"LF5kYT": "Other Connections", "LF5kYT": "Other Connections",
"LXxsbk": "Anonymous", "LXxsbk": "Anonymous",
"LgbKvU": "Comment", "LgbKvU": "Comment",
@ -290,6 +292,7 @@
"brAXSu": "Pick a username", "brAXSu": "Pick a username",
"bxv59V": "Just now", "bxv59V": "Just now",
"c+oiJe": "Install Extension", "c+oiJe": "Install Extension",
"c2DTVd": "Enter a pin to encrypt your private key, you must enter this pin every time you open Snort.",
"c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}", "c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}",
"c3g2hL": "Broadcast Again", "c3g2hL": "Broadcast Again",
"cFbU1B": "Using Alby? Go to {link} to get your NWC config!", "cFbU1B": "Using Alby? Go to {link} to get your NWC config!",
@ -402,6 +405,7 @@
"qkvYUb": "Add to Profile", "qkvYUb": "Add to Profile",
"qmJ8kD": "Translation failed", "qmJ8kD": "Translation failed",
"qtWLmt": "Like", "qtWLmt": "Like",
"qz9fty": "Incorrect pin",
"r3C4x/": "Software", "r3C4x/": "Software",
"r5srDR": "Enter wallet password", "r5srDR": "Enter wallet password",
"rT14Ow": "Add Relays", "rT14Ow": "Add Relays",

View File

@ -1,5 +1,5 @@
import { scryptAsync } from "@noble/hashes/scrypt"; import { scryptAsync } from "@noble/hashes/scrypt";
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from "@noble/hashes/sha256";
import { hmac } from "@noble/hashes/hmac"; import { hmac } from "@noble/hashes/hmac";
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils"; import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
import { base64 } from "@scure/base"; import { base64 } from "@scure/base";
@ -15,9 +15,9 @@ export class InvalidPinError extends Error {
* Pin protected data * Pin protected data
*/ */
export class PinEncrypted { export class PinEncrypted {
static readonly #opts = {N: 2**20, r: 8, p: 1, dkLen: 32} static readonly #opts = { N: 2 ** 20, r: 8, p: 1, dkLen: 32 };
#decrypted?: Uint8Array #decrypted?: Uint8Array;
#encrypted: PinEncryptedPayload #encrypted: PinEncryptedPayload;
constructor(enc: PinEncryptedPayload) { constructor(enc: PinEncryptedPayload) {
this.#encrypted = enc; this.#encrypted = enc;
@ -54,7 +54,7 @@ export class PinEncrypted {
salt: base64.encode(salt), salt: base64.encode(salt),
ciphertext: base64.encode(ciphertext), ciphertext: base64.encode(ciphertext),
iv: base64.encode(nonce), iv: base64.encode(nonce),
mac mac,
}); });
ret.#decrypted = plaintext; ret.#decrypted = plaintext;
return ret; return ret;
@ -62,9 +62,8 @@ export class PinEncrypted {
} }
export interface PinEncryptedPayload { export interface PinEncryptedPayload {
salt: string, // for KDF salt: string; // for KDF
ciphertext: string ciphertext: string;
iv: string, iv: string;
mac: string mac: string;
} }