kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'nostr-machina' into 'main'
Implement Nostr Machina See merge request soapbox-pub/soapbox!2775environments/review-main-yi2y9f/deployments/4091
commit
f02255ff6a
|
@ -123,6 +123,7 @@
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.7.11",
|
"lodash": "^4.7.11",
|
||||||
"mini-css-extract-plugin": "^2.6.0",
|
"mini-css-extract-plugin": "^2.6.0",
|
||||||
|
"nostr-machina": "^0.1.0",
|
||||||
"nostr-tools": "^1.14.2",
|
"nostr-tools": "^1.14.2",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.29",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
import { importEntities } from 'soapbox/entity-store/actions';
|
import { importEntities } from 'soapbox/entity-store/actions';
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
|
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||||
import { selectAccount } from 'soapbox/selectors';
|
import { selectAccount } from 'soapbox/selectors';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features';
|
import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features';
|
||||||
|
@ -128,9 +131,15 @@ const maybeRedirectLogin = (error: AxiosError, history?: History) => {
|
||||||
const noOp = () => new Promise(f => f(undefined));
|
const noOp = () => new Promise(f => f(undefined));
|
||||||
|
|
||||||
const createAccount = (params: Record<string, any>) =>
|
const createAccount = (params: Record<string, any>) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
async (dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
|
const { instance } = getState();
|
||||||
|
const { nostrSignup } = getFeatures(instance);
|
||||||
|
const pubkey = nostrSignup ? await getPublicKey() : undefined;
|
||||||
|
|
||||||
dispatch({ type: ACCOUNT_CREATE_REQUEST, params });
|
dispatch({ type: ACCOUNT_CREATE_REQUEST, params });
|
||||||
return api(getState, 'app').post('/api/v1/accounts', params).then(({ data: token }) => {
|
return api(getState, 'app').post('/api/v1/accounts', params, {
|
||||||
|
headers: pubkey ? { authorization: `Bearer ${nip19.npubEncode(pubkey)}` } : undefined,
|
||||||
|
}).then(({ data: token }) => {
|
||||||
return dispatch({ type: ACCOUNT_CREATE_SUCCESS, params, token });
|
return dispatch({ type: ACCOUNT_CREATE_SUCCESS, params, token });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: ACCOUNT_CREATE_FAIL, error, params });
|
dispatch({ type: ACCOUNT_CREATE_FAIL, error, params });
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
|
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||||
|
import { type AppDispatch } from 'soapbox/store';
|
||||||
|
|
||||||
|
import { verifyCredentials } from './auth';
|
||||||
|
|
||||||
|
/** Log in with a Nostr pubkey. */
|
||||||
|
function nostrLogIn() {
|
||||||
|
return async (dispatch: AppDispatch) => {
|
||||||
|
const pubkey = await getPublicKey();
|
||||||
|
const npub = nip19.npubEncode(pubkey);
|
||||||
|
|
||||||
|
return dispatch(verifyCredentials(npub));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { nostrLogIn };
|
|
@ -1,6 +1,7 @@
|
||||||
import { relayInit, type Relay } from 'nostr-tools';
|
import { NiceRelay } from 'nostr-machina';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { nip04, signEvent } from 'soapbox/features/nostr/sign';
|
||||||
import { useInstance } from 'soapbox/hooks';
|
import { useInstance } from 'soapbox/hooks';
|
||||||
import { connectRequestSchema } from 'soapbox/schemas/nostr';
|
import { connectRequestSchema } from 'soapbox/schemas/nostr';
|
||||||
import { jsonSchema } from 'soapbox/schemas/utils';
|
import { jsonSchema } from 'soapbox/schemas/utils';
|
||||||
|
@ -11,47 +12,50 @@ function useSignerStream() {
|
||||||
const relayUrl = instance.nostr?.relay;
|
const relayUrl = instance.nostr?.relay;
|
||||||
const pubkey = instance.nostr?.pubkey;
|
const pubkey = instance.nostr?.pubkey;
|
||||||
|
|
||||||
useEffect(() => {
|
const relay = useMemo(() => {
|
||||||
let relay: Relay | undefined;
|
if (relayUrl) {
|
||||||
|
return new NiceRelay(relayUrl);
|
||||||
if (relayUrl && pubkey && window.nostr?.nip04) {
|
|
||||||
relay = relayInit(relayUrl);
|
|
||||||
relay.connect();
|
|
||||||
|
|
||||||
relay
|
|
||||||
.sub([{ kinds: [24133], authors: [pubkey], limit: 0 }])
|
|
||||||
.on('event', async (event) => {
|
|
||||||
if (!relay || !window.nostr?.nip04) return;
|
|
||||||
|
|
||||||
const decrypted = await window.nostr.nip04.decrypt(pubkey, event.content);
|
|
||||||
const reqMsg = jsonSchema.pipe(connectRequestSchema).safeParse(decrypted);
|
|
||||||
|
|
||||||
if (!reqMsg.success) {
|
|
||||||
console.warn(decrypted);
|
|
||||||
console.warn(reqMsg.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const signed = await window.nostr.signEvent(reqMsg.data.params[0]);
|
|
||||||
const respMsg = {
|
|
||||||
id: reqMsg.data.id,
|
|
||||||
result: signed,
|
|
||||||
};
|
|
||||||
|
|
||||||
const respEvent = await window.nostr.signEvent({
|
|
||||||
kind: 24133,
|
|
||||||
content: await window.nostr.nip04.encrypt(pubkey, JSON.stringify(respMsg)),
|
|
||||||
tags: [['p', pubkey]],
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
});
|
|
||||||
|
|
||||||
relay.publish(respEvent);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}, [relayUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!relay || !pubkey) return;
|
||||||
|
|
||||||
|
const sub = relay.req([{ kinds: [24133], authors: [pubkey], limit: 0 }]);
|
||||||
|
|
||||||
|
const readEvents = async () => {
|
||||||
|
for await (const event of sub) {
|
||||||
|
const decrypted = await nip04.decrypt(pubkey, event.content);
|
||||||
|
|
||||||
|
const reqMsg = jsonSchema.pipe(connectRequestSchema).safeParse(decrypted);
|
||||||
|
if (!reqMsg.success) {
|
||||||
|
console.warn(decrypted);
|
||||||
|
console.warn(reqMsg.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const respMsg = {
|
||||||
|
id: reqMsg.data.id,
|
||||||
|
result: await signEvent(reqMsg.data.params[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const respEvent = await signEvent({
|
||||||
|
kind: 24133,
|
||||||
|
content: await nip04.encrypt(pubkey, JSON.stringify(respMsg)),
|
||||||
|
tags: [['p', pubkey]],
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
relay.send(['EVENT', respEvent]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
readEvents();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
relay?.close();
|
relay?.close();
|
||||||
};
|
};
|
||||||
}, [relayUrl, pubkey]);
|
}, [relay, pubkey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useSignerStream };
|
export { useSignerStream };
|
||||||
|
|
|
@ -245,46 +245,52 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<Input
|
{!features.nostrSignup && (
|
||||||
type='email'
|
|
||||||
name='email'
|
|
||||||
placeholder={intl.formatMessage(messages.email)}
|
|
||||||
autoComplete='off'
|
|
||||||
autoCorrect='off'
|
|
||||||
autoCapitalize='off'
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={params.get('email', '')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
type='password'
|
|
||||||
name='password'
|
|
||||||
placeholder={intl.formatMessage(messages.password)}
|
|
||||||
autoComplete='off'
|
|
||||||
autoCorrect='off'
|
|
||||||
autoCapitalize='off'
|
|
||||||
onChange={onPasswordChange}
|
|
||||||
value={params.get('password', '')}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
errors={passwordMismatch ? [intl.formatMessage(messages.passwordMismatch)] : undefined}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
type='password'
|
type='email'
|
||||||
name='password_confirmation'
|
name='email'
|
||||||
placeholder={intl.formatMessage(messages.confirm)}
|
placeholder={intl.formatMessage(messages.email)}
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
autoCorrect='off'
|
autoCorrect='off'
|
||||||
autoCapitalize='off'
|
autoCapitalize='off'
|
||||||
onChange={onPasswordConfirmChange}
|
onChange={onInputChange}
|
||||||
onBlur={onPasswordConfirmBlur}
|
value={params.get('email', '')}
|
||||||
value={passwordConfirmation}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
)}
|
||||||
|
|
||||||
|
{!features.nostrSignup && (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
type='password'
|
||||||
|
name='password'
|
||||||
|
placeholder={intl.formatMessage(messages.password)}
|
||||||
|
autoComplete='off'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoCapitalize='off'
|
||||||
|
onChange={onPasswordChange}
|
||||||
|
value={params.get('password', '')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
errors={passwordMismatch ? [intl.formatMessage(messages.passwordMismatch)] : undefined}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type='password'
|
||||||
|
name='password_confirmation'
|
||||||
|
placeholder={intl.formatMessage(messages.confirm)}
|
||||||
|
autoComplete='off'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoCapitalize='off'
|
||||||
|
onChange={onPasswordConfirmChange}
|
||||||
|
onBlur={onPasswordConfirmBlur}
|
||||||
|
value={passwordConfirmation}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{birthdayRequired && (
|
{birthdayRequired && (
|
||||||
<BirthdayInput
|
<BirthdayInput
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
type Event,
|
||||||
|
type EventTemplate,
|
||||||
|
generatePrivateKey,
|
||||||
|
getPublicKey as _getPublicKey,
|
||||||
|
finishEvent,
|
||||||
|
nip04 as _nip04,
|
||||||
|
} from 'nostr-tools';
|
||||||
|
|
||||||
|
/** localStorage key for the Nostr private key (if not using NIP-07). */
|
||||||
|
const LOCAL_KEY = 'soapbox:nostr:privateKey';
|
||||||
|
|
||||||
|
/** Get the private key from the browser, or generate one. */
|
||||||
|
const getPrivateKey = (): string => {
|
||||||
|
const local = localStorage.getItem(LOCAL_KEY);
|
||||||
|
|
||||||
|
if (!local) {
|
||||||
|
const key = generatePrivateKey();
|
||||||
|
localStorage.setItem(LOCAL_KEY, key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return local;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get the user's public key from NIP-07, or generate one. */
|
||||||
|
async function getPublicKey(): Promise<string> {
|
||||||
|
return window.nostr ? window.nostr.getPublicKey() : _getPublicKey(getPrivateKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sign an event with NIP-07, or the locally generated key. */
|
||||||
|
async function signEvent<K extends number>(event: EventTemplate<K>): Promise<Event<K>> {
|
||||||
|
return window.nostr ? window.nostr.signEvent(event) as Promise<Event<K>> : finishEvent(event, getPrivateKey()) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Crypto function with NIP-07, or the local key. */
|
||||||
|
const nip04 = {
|
||||||
|
/** Encrypt with NIP-07, or the local key. */
|
||||||
|
encrypt: async (pubkey: string, content: string) => {
|
||||||
|
return window.nostr?.nip04
|
||||||
|
? window.nostr.nip04.encrypt(pubkey, content)
|
||||||
|
: _nip04.encrypt(getPrivateKey(), pubkey, content);
|
||||||
|
},
|
||||||
|
/** Decrypt with NIP-07, or the local key. */
|
||||||
|
decrypt: async (pubkey: string, content: string) => {
|
||||||
|
return window.nostr?.nip04
|
||||||
|
? window.nostr.nip04.decrypt(pubkey, content)
|
||||||
|
: _nip04.decrypt(getPrivateKey(), pubkey, content);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getPublicKey, signEvent, nip04 };
|
|
@ -5,6 +5,7 @@ import { Link, Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||||
import { fetchInstance } from 'soapbox/actions/instance';
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
|
import { nostrLogIn } from 'soapbox/actions/nostr';
|
||||||
import { openSidebar } from 'soapbox/actions/sidebar';
|
import { openSidebar } from 'soapbox/actions/sidebar';
|
||||||
import SiteLogo from 'soapbox/components/site-logo';
|
import SiteLogo from 'soapbox/components/site-logo';
|
||||||
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
|
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
|
||||||
|
@ -38,6 +39,12 @@ const Navbar = () => {
|
||||||
|
|
||||||
const onOpenSidebar = () => dispatch(openSidebar());
|
const onOpenSidebar = () => dispatch(openSidebar());
|
||||||
|
|
||||||
|
const handleNostrLogin = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
await dispatch(nostrLogIn()).catch(console.error);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit: React.FormEventHandler = (event) => {
|
const handleSubmit: React.FormEventHandler = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -107,50 +114,66 @@ const Navbar = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Form className='hidden items-center space-x-2 rtl:space-x-reverse lg:flex' onSubmit={handleSubmit}>
|
{features.nostrSignup ? (
|
||||||
<Input
|
<div className='hidden items-center xl:flex'>
|
||||||
required
|
<Button
|
||||||
value={username}
|
theme='primary'
|
||||||
onChange={(event) => setUsername(event.target.value)}
|
onClick={handleNostrLogin}
|
||||||
type='text'
|
disabled={isLoading}
|
||||||
placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)}
|
>
|
||||||
className='max-w-[200px]'
|
{intl.formatMessage(messages.login)}
|
||||||
/>
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Form className='hidden items-center space-x-2 rtl:space-x-reverse xl:flex' onSubmit={handleSubmit}>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
value={username}
|
||||||
|
onChange={(event) => setUsername(event.target.value)}
|
||||||
|
type='text'
|
||||||
|
placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)}
|
||||||
|
className='max-w-[200px]'
|
||||||
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
required
|
required
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(event) => setPassword(event.target.value)}
|
onChange={(event) => setPassword(event.target.value)}
|
||||||
type='password'
|
type='password'
|
||||||
placeholder={intl.formatMessage(messages.password)}
|
placeholder={intl.formatMessage(messages.password)}
|
||||||
className='max-w-[200px]'
|
className='max-w-[200px]'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Link to='/reset-password'>
|
<Link to='/reset-password'>
|
||||||
<Tooltip text={intl.formatMessage(messages.forgotPassword)}>
|
<Tooltip text={intl.formatMessage(messages.forgotPassword)}>
|
||||||
<IconButton
|
<IconButton
|
||||||
src={require('@tabler/icons/help.svg')}
|
src={require('@tabler/icons/help.svg')}
|
||||||
className='cursor-pointer bg-transparent text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200'
|
className='cursor-pointer bg-transparent text-gray-400 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200'
|
||||||
iconClassName='h-5 w-5'
|
iconClassName='h-5 w-5'
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
theme='primary'
|
||||||
|
type='submit'
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.login)}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='space-x-1.5 xl:hidden'>
|
||||||
<Button
|
<Button
|
||||||
theme='primary'
|
theme='tertiary'
|
||||||
type='submit'
|
size='sm'
|
||||||
disabled={isLoading}
|
{...(features.nostrSignup ? { onClick: handleNostrLogin } : { to: '/login' })}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.login)}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
<div className='space-x-1.5 lg:hidden'>
|
|
||||||
<Button theme='tertiary' to='/login' size='sm'>
|
|
||||||
<FormattedMessage id='account.login' defaultMessage='Log In' />
|
<FormattedMessage id='account.login' defaultMessage='Log In' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isOpen && (
|
{(isOpen) && (
|
||||||
<Button theme='primary' to='/signup' size='sm'>
|
<Button theme='primary' to='/signup' size='sm'>
|
||||||
<FormattedMessage id='account.register' defaultMessage='Sign up' />
|
<FormattedMessage id='account.register' defaultMessage='Sign up' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { z } from 'zod';
|
||||||
/** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */
|
/** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */
|
||||||
const nostrIdSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
const nostrIdSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
||||||
/** Nostr kinds are positive integers. */
|
/** Nostr kinds are positive integers. */
|
||||||
const kindSchema = z.number().int().positive();
|
const kindSchema = z.number().int().nonnegative();
|
||||||
|
|
||||||
/** Nostr event template schema. */
|
/** Nostr event template schema. */
|
||||||
const eventTemplateSchema = z.object({
|
const eventTemplateSchema = z.object({
|
||||||
|
|
|
@ -685,6 +685,12 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
*/
|
*/
|
||||||
nostrSign: v.software === DITTO,
|
nostrSign: v.software === DITTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the backend uses Ditto's Nosteric way of registration.
|
||||||
|
* @see POST /api/v1/accounts
|
||||||
|
*/
|
||||||
|
nostrSignup: v.software === DITTO,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add private notes to accounts.
|
* Add private notes to accounts.
|
||||||
* @see POST /api/v1/accounts/:id/note
|
* @see POST /api/v1/accounts/:id/note
|
||||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -6512,6 +6512,26 @@ normalize-url@^6.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||||
|
|
||||||
|
nostr-machina@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nostr-machina/-/nostr-machina-0.1.0.tgz#e111e86eb51655e5de31862174d23de184e6e98a"
|
||||||
|
integrity sha512-sNswM9vgq7R/96YIJKZOlG0M/m2mZrb1TiPA7hpOMrnWHBGdDuAeON0vLWJaGbvpuDKYQ1b5ZiLZ8HM3EZPevw==
|
||||||
|
dependencies:
|
||||||
|
nostr-tools "^1.14.0"
|
||||||
|
zod "^3.21.0"
|
||||||
|
|
||||||
|
nostr-tools@^1.14.0:
|
||||||
|
version "1.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.16.0.tgz#5867f1d8bd055a5a3b27aadb199457dceb244314"
|
||||||
|
integrity sha512-sx/aOl0gmkeHVoIVbyOhEQhzF88NsrBXMC8bsjhPASqA6oZ8uSOAyEGgRLMfC3SKgzQD5Gr6KvDoAahaD6xKcg==
|
||||||
|
dependencies:
|
||||||
|
"@noble/ciphers" "^0.2.0"
|
||||||
|
"@noble/curves" "1.1.0"
|
||||||
|
"@noble/hashes" "1.3.1"
|
||||||
|
"@scure/base" "1.1.1"
|
||||||
|
"@scure/bip32" "1.3.1"
|
||||||
|
"@scure/bip39" "1.2.1"
|
||||||
|
|
||||||
nostr-tools@^1.14.2:
|
nostr-tools@^1.14.2:
|
||||||
version "1.14.2"
|
version "1.14.2"
|
||||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.14.2.tgz#161c9401467725e87c07fcf1c9924d31b12fd45c"
|
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.14.2.tgz#161c9401467725e87c07fcf1c9924d31b12fd45c"
|
||||||
|
@ -9674,6 +9694,11 @@ yocto-queue@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
||||||
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
||||||
|
|
||||||
|
zod@^3.21.0:
|
||||||
|
version "3.22.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060"
|
||||||
|
integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==
|
||||||
|
|
||||||
zod@^3.21.4:
|
zod@^3.21.4:
|
||||||
version "3.21.4"
|
version "3.21.4"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
|
||||||
|
|
Ładowanie…
Reference in New Issue