OKAY IT'S WORKING
This commit is contained in:
parent
333d3e76d6
commit
5c4b73f943
|
@ -1,4 +1,8 @@
|
||||||
import { RootState, type AppDispatch } from 'soapbox/store';
|
import { NostrSigner, NRelay1 } from '@nostrify/nostrify';
|
||||||
|
|
||||||
|
import { NostrRPC } from 'soapbox/features/nostr/NostrRPC';
|
||||||
|
import { useBunkerStore } from 'soapbox/hooks/useBunkerStore';
|
||||||
|
import { type AppDispatch } from 'soapbox/store';
|
||||||
|
|
||||||
import { authLoggedIn, verifyCredentials } from './auth';
|
import { authLoggedIn, verifyCredentials } from './auth';
|
||||||
import { obtainOAuthToken } from './oauth';
|
import { obtainOAuthToken } from './oauth';
|
||||||
|
@ -6,42 +10,67 @@ import { obtainOAuthToken } from './oauth';
|
||||||
const NOSTR_PUBKEY_SET = 'NOSTR_PUBKEY_SET';
|
const NOSTR_PUBKEY_SET = 'NOSTR_PUBKEY_SET';
|
||||||
|
|
||||||
/** Log in with a Nostr pubkey. */
|
/** Log in with a Nostr pubkey. */
|
||||||
function logInNostr(pubkey: string) {
|
function logInNostr(signer: NostrSigner, relay: NRelay1, signal: AbortSignal) {
|
||||||
return async (dispatch: AppDispatch, getState: () => RootState) => {
|
return async (dispatch: AppDispatch) => {
|
||||||
dispatch(setNostrPubkey(pubkey));
|
const pubkey = await signer.getPublicKey();
|
||||||
|
const bunker = useBunkerStore.getState();
|
||||||
|
const authorization = bunker.authorize(pubkey);
|
||||||
|
const bunkerPubkey = await authorization.signer.getPublicKey();
|
||||||
|
|
||||||
const secret = sessionStorage.getItem('soapbox:nip46:secret');
|
const rpc = new NostrRPC(relay, authorization.signer);
|
||||||
if (!secret) {
|
const sub = rpc.req([{ kinds: [24133], '#p': [bunkerPubkey], limit: 0 }], { signal: AbortSignal.timeout(1_000) });
|
||||||
throw new Error('No secret found in session storage');
|
|
||||||
}
|
|
||||||
|
|
||||||
const relay = getState().instance.nostr?.relay;
|
const tokenPromise = dispatch(obtainOAuthToken({
|
||||||
|
|
||||||
// HACK: waits 1 second to ensure the relay subscription is open
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
const token = await dispatch(obtainOAuthToken({
|
|
||||||
grant_type: 'nostr_bunker',
|
grant_type: 'nostr_bunker',
|
||||||
pubkey,
|
pubkey: bunkerPubkey,
|
||||||
relays: relay ? [relay] : undefined,
|
relays: [relay.socket.url],
|
||||||
secret,
|
secret: authorization.secret,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { access_token } = dispatch(authLoggedIn(token));
|
let authorizedPubkey: string | undefined;
|
||||||
await dispatch(verifyCredentials(access_token as string));
|
|
||||||
|
|
||||||
dispatch(setNostrPubkey(undefined));
|
for await (const { request, respond, requestEvent } of sub) {
|
||||||
|
if (request.method === 'connect') {
|
||||||
|
const [, secret] = request.params;
|
||||||
|
|
||||||
|
if (secret === authorization.secret) {
|
||||||
|
authorizedPubkey = requestEvent.pubkey;
|
||||||
|
await respond({ result: 'ack' });
|
||||||
|
} else {
|
||||||
|
await respond({ result: '', error: 'Invalid secret' });
|
||||||
|
throw new Error('Invalid secret');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.method === 'get_public_key') {
|
||||||
|
await respond({ result: pubkey });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authorizedPubkey) {
|
||||||
|
throw new Error('Authorization failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { access_token } = dispatch(authLoggedIn(await tokenPromise));
|
||||||
|
|
||||||
|
useBunkerStore.getState().connect({
|
||||||
|
accessToken: access_token as string,
|
||||||
|
authorizedPubkey,
|
||||||
|
bunkerPubkey,
|
||||||
|
secret: authorization.secret,
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatch(verifyCredentials(access_token as string));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Log in with a Nostr extension. */
|
/** Log in with a Nostr extension. */
|
||||||
function nostrExtensionLogIn() {
|
function nostrExtensionLogIn(relay: NRelay1, signal: AbortSignal) {
|
||||||
return async (dispatch: AppDispatch) => {
|
return async (dispatch: AppDispatch) => {
|
||||||
if (!window.nostr) {
|
if (!window.nostr) {
|
||||||
throw new Error('No Nostr signer available');
|
throw new Error('No Nostr signer available');
|
||||||
}
|
}
|
||||||
const pubkey = await window.nostr.getPublicKey();
|
return dispatch(logInNostr(window.nostr, relay, signal));
|
||||||
return dispatch(logInNostr(pubkey));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { NKeys } from 'soapbox/features/nostr/keys';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { useBunkerStore } from 'soapbox/hooks/useBunkerStore';
|
||||||
|
|
||||||
|
export function useSigner() {
|
||||||
|
const { connections } = useBunkerStore();
|
||||||
|
|
||||||
|
const pubkey = useAppSelector(({ auth }) => {
|
||||||
|
const accessToken = auth.me ? auth.tokens[auth.me]?.access_token : undefined;
|
||||||
|
if (accessToken) {
|
||||||
|
return connections.find((conn) => conn.accessToken === accessToken)?.pubkey;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: signer, ...rest } = useQuery({
|
||||||
|
queryKey: ['nostr', 'signer', pubkey],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!pubkey) return null;
|
||||||
|
|
||||||
|
const signer = NKeys.get(pubkey);
|
||||||
|
if (signer) return signer;
|
||||||
|
|
||||||
|
if (window.nostr && await window.nostr.getPublicKey() === pubkey) {
|
||||||
|
return window.nostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { signer: signer ?? undefined, ...rest };
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
import { NRelay, NRelay1, NostrSigner } from '@nostrify/nostrify';
|
import { NRelay1, NostrSigner } from '@nostrify/nostrify';
|
||||||
import React, { createContext, useContext, useState, useEffect, useMemo } from 'react';
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { NKeys } from 'soapbox/features/nostr/keys';
|
import { useSigner } from 'soapbox/api/hooks/nostr/useSigner';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
|
||||||
import { useInstance } from 'soapbox/hooks/useInstance';
|
import { useInstance } from 'soapbox/hooks/useInstance';
|
||||||
|
|
||||||
interface NostrContextType {
|
interface NostrContextType {
|
||||||
relay?: NRelay;
|
relay?: NRelay1;
|
||||||
signer?: NostrSigner;
|
signer?: NostrSigner;
|
||||||
hasNostr: boolean;
|
hasNostr: boolean;
|
||||||
isRelayOpen: boolean;
|
isRelayOpen: boolean;
|
||||||
|
@ -20,18 +19,14 @@ interface NostrProviderProps {
|
||||||
|
|
||||||
export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
|
export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstance();
|
||||||
|
const { signer } = useSigner();
|
||||||
|
|
||||||
const hasNostr = !!instance.nostr;
|
const hasNostr = !!instance.nostr;
|
||||||
|
|
||||||
const [relay, setRelay] = useState<NRelay1>();
|
const [relay, setRelay] = useState<NRelay1>();
|
||||||
const [isRelayOpen, setIsRelayOpen] = useState(false);
|
const [isRelayOpen, setIsRelayOpen] = useState(false);
|
||||||
|
|
||||||
const url = instance.nostr?.relay;
|
const url = instance.nostr?.relay;
|
||||||
const accountPubkey = useAppSelector(({ meta, auth }) => meta.pubkey ?? auth.users[auth.me!]?.id);
|
|
||||||
|
|
||||||
const signer = useMemo(
|
|
||||||
() => accountPubkey ? NKeys.get(accountPubkey) ?? window.nostr : undefined,
|
|
||||||
[accountPubkey, window.nostr],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRelayOpen = () => {
|
const handleRelayOpen = () => {
|
||||||
setIsRelayOpen(true);
|
setIsRelayOpen(true);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { produce, enableMapSet } from 'immer';
|
import { produce } from 'immer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ENTITIES_IMPORT,
|
ENTITIES_IMPORT,
|
||||||
|
@ -17,8 +17,6 @@ import { createCache, createList, updateStore, updateList } from './utils';
|
||||||
import type { DeleteEntitiesOpts } from './actions';
|
import type { DeleteEntitiesOpts } from './actions';
|
||||||
import type { EntitiesTransaction, Entity, EntityCache, EntityListState, ImportPosition } from './types';
|
import type { EntitiesTransaction, Entity, EntityCache, EntityListState, ImportPosition } from './types';
|
||||||
|
|
||||||
enableMapSet();
|
|
||||||
|
|
||||||
/** Entity reducer state. */
|
/** Entity reducer state. */
|
||||||
interface State {
|
interface State {
|
||||||
[entityType: string]: EntityCache | undefined;
|
[entityType: string]: EntityCache | undefined;
|
||||||
|
|
|
@ -96,8 +96,6 @@ const SearchZapSplit = (props: ISearchZapSplit) => {
|
||||||
|
|
||||||
const getAccount = (accountId: string) => (dispatch: any, getState: () => RootState) => {
|
const getAccount = (accountId: string) => (dispatch: any, getState: () => RootState) => {
|
||||||
const account = selectAccount(getState(), accountId);
|
const account = selectAccount(getState(), accountId);
|
||||||
console.log(account);
|
|
||||||
|
|
||||||
props.onChange(account!);
|
props.onChange(account!);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import {
|
||||||
|
NRelay,
|
||||||
|
NostrConnectRequest,
|
||||||
|
NostrConnectResponse,
|
||||||
|
NostrEvent,
|
||||||
|
NostrFilter,
|
||||||
|
NostrSigner,
|
||||||
|
NSchema as n,
|
||||||
|
} from '@nostrify/nostrify';
|
||||||
|
|
||||||
|
export class NostrRPC {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private relay: NRelay,
|
||||||
|
private signer: NostrSigner,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async *req(
|
||||||
|
filters: NostrFilter[],
|
||||||
|
opts: { signal?: AbortSignal },
|
||||||
|
): AsyncIterable<{
|
||||||
|
requestEvent: NostrEvent;
|
||||||
|
request: NostrConnectRequest;
|
||||||
|
respond: (response: Omit<NostrConnectResponse, 'id'>) => Promise<void>;
|
||||||
|
}> {
|
||||||
|
for await (const msg of this.relay.req(filters, opts)) {
|
||||||
|
if (msg[0] === 'EVENT') {
|
||||||
|
const [,, requestEvent] = msg;
|
||||||
|
|
||||||
|
const decrypted = await this.decrypt(this.signer, requestEvent.pubkey, requestEvent.content);
|
||||||
|
const request = n.json().pipe(n.connectRequest()).parse(decrypted);
|
||||||
|
|
||||||
|
const respond = async (response: Omit<NostrConnectResponse, 'id'>): Promise<void> => {
|
||||||
|
await this.respond(requestEvent, { ...response, id: request.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
yield { requestEvent, request, respond };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg[0] === 'CLOSED') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async respond(requestEvent: NostrEvent, response: NostrConnectResponse): Promise<void> {
|
||||||
|
const responseEvent = await this.signer.signEvent({
|
||||||
|
kind: 24133,
|
||||||
|
content: await this.signer.nip04!.encrypt(requestEvent.pubkey, JSON.stringify(response)),
|
||||||
|
tags: [['p', requestEvent.pubkey]],
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.relay.event(responseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Auto-decrypt NIP-44 or NIP-04 ciphertext. */
|
||||||
|
private async decrypt(signer: NostrSigner, pubkey: string, ciphertext: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await signer.nip44!.decrypt(pubkey, ciphertext);
|
||||||
|
} catch {
|
||||||
|
return await signer.nip04!.decrypt(pubkey, ciphertext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,14 +5,18 @@ import { closeModal } from 'soapbox/actions/modals';
|
||||||
import { nostrExtensionLogIn } from 'soapbox/actions/nostr';
|
import { nostrExtensionLogIn } from 'soapbox/actions/nostr';
|
||||||
import Stack from 'soapbox/components/ui/stack/stack';
|
import Stack from 'soapbox/components/ui/stack/stack';
|
||||||
import Text from 'soapbox/components/ui/text/text';
|
import Text from 'soapbox/components/ui/text/text';
|
||||||
|
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
const NostrExtensionIndicator: React.FC = () => {
|
const NostrExtensionIndicator: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { relay } = useNostr();
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
dispatch(nostrExtensionLogIn());
|
if (relay) {
|
||||||
|
dispatch(nostrExtensionLogIn(relay, AbortSignal.timeout(30_000)));
|
||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderBody(): React.ReactNode {
|
function renderBody(): React.ReactNode {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { openModal } from 'soapbox/actions/modals';
|
||||||
import { nostrExtensionLogIn } from 'soapbox/actions/nostr';
|
import { nostrExtensionLogIn } from 'soapbox/actions/nostr';
|
||||||
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
||||||
import { Button, Stack, Modal, Text, Divider, HStack } from 'soapbox/components/ui';
|
import { Button, Stack, Modal, Text, Divider, HStack } from 'soapbox/components/ui';
|
||||||
|
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { useAppDispatch, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
import { useAppDispatch, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||||
|
|
||||||
interface IExtensionStep {
|
interface IExtensionStep {
|
||||||
|
@ -18,6 +19,7 @@ const ExtensionStep: React.FC<IExtensionStep> = ({ isLogin, onClickAlt, onClose
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstance();
|
||||||
const { logo } = useSoapboxConfig();
|
const { logo } = useSoapboxConfig();
|
||||||
|
const { relay } = useNostr();
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -25,8 +27,10 @@ const ExtensionStep: React.FC<IExtensionStep> = ({ isLogin, onClickAlt, onClose
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
dispatch(nostrExtensionLogIn());
|
if (relay) {
|
||||||
|
dispatch(nostrExtensionLogIn(relay, AbortSignal.timeout(30_000)));
|
||||||
onClose();
|
onClose();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
||||||
import { logInNostr } from 'soapbox/actions/nostr';
|
import { logInNostr } from 'soapbox/actions/nostr';
|
||||||
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
||||||
import { Button, Stack, Modal, Input, FormGroup, Form, Divider } from 'soapbox/components/ui';
|
import { Button, Stack, Modal, Input, FormGroup, Form, Divider } from 'soapbox/components/ui';
|
||||||
|
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { NKeys } from 'soapbox/features/nostr/keys';
|
import { NKeys } from 'soapbox/features/nostr/keys';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ const KeyAddStep: React.FC<IKeyAddStep> = ({ onClose }) => {
|
||||||
const [error, setError] = useState<string | undefined>();
|
const [error, setError] = useState<string | undefined>();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { relay } = useNostr();
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setNsec(e.target.value);
|
setNsec(e.target.value);
|
||||||
|
@ -26,13 +28,13 @@ const KeyAddStep: React.FC<IKeyAddStep> = ({ onClose }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!relay) return;
|
||||||
try {
|
try {
|
||||||
const result = nip19.decode(nsec);
|
const result = nip19.decode(nsec);
|
||||||
if (result.type === 'nsec') {
|
if (result.type === 'nsec') {
|
||||||
const seckey = result.data;
|
const seckey = result.data;
|
||||||
const signer = NKeys.add(seckey);
|
const signer = NKeys.add(seckey);
|
||||||
const pubkey = await signer.getPublicKey();
|
dispatch(logInNostr(signer, relay, AbortSignal.timeout(30_000)));
|
||||||
dispatch(logInNostr(pubkey));
|
|
||||||
onClose();
|
onClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,9 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
|
if (!relay) return;
|
||||||
|
|
||||||
const signer = NKeys.add(secretKey);
|
const signer = NKeys.add(secretKey);
|
||||||
const pubkey = await signer.getPublicKey();
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
const [kind0, ...events] = await Promise.all([
|
const [kind0, ...events] = await Promise.all([
|
||||||
|
@ -57,12 +58,12 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
signer.signEvent({ kind: 30078, content: '', tags: [['d', 'pub.ditto.pleroma_settings_store']], created_at: now }),
|
signer.signEvent({ kind: 30078, content: '', tags: [['d', 'pub.ditto.pleroma_settings_store']], created_at: now }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await relay?.event(kind0);
|
await relay.event(kind0);
|
||||||
await Promise.all(events.map((event) => relay?.event(event)));
|
await Promise.all(events.map((event) => relay.event(event)));
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
await dispatch(logInNostr(pubkey));
|
await dispatch(logInNostr(signer, relay, AbortSignal.timeout(30_000)));
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
dispatch(closeSidebar());
|
dispatch(closeSidebar());
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { NSchema as n, NostrSigner, NSecSigner } from '@nostrify/nostrify';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
@ -7,13 +8,6 @@ import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
import { filteredArray, jsonSchema } from 'soapbox/schemas/utils';
|
import { filteredArray, jsonSchema } from 'soapbox/schemas/utils';
|
||||||
|
|
||||||
/** User-facing authorization string. */
|
|
||||||
interface BunkerURI {
|
|
||||||
pubkey: string;
|
|
||||||
relays: string[];
|
|
||||||
secret?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary authorization details to establish a bunker connection with an app.
|
* Temporary authorization details to establish a bunker connection with an app.
|
||||||
* Will be upgraded to a `BunkerConnection` once the connection is established.
|
* Will be upgraded to a `BunkerConnection` once the connection is established.
|
||||||
|
@ -54,19 +48,17 @@ interface BunkerConnectRequest {
|
||||||
secret: string;
|
secret: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nsecSchema = z.custom<`nsec1${string}`>((v) => typeof v === 'string' && v.startsWith('nsec1'));
|
|
||||||
|
|
||||||
const connectionSchema = z.object({
|
const connectionSchema = z.object({
|
||||||
pubkey: z.string(),
|
pubkey: z.string(),
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
authorizedPubkey: z.string(),
|
authorizedPubkey: z.string(),
|
||||||
bunkerSeckey: nsecSchema,
|
bunkerSeckey: n.bech32('nsec'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const authorizationSchema = z.object({
|
const authorizationSchema = z.object({
|
||||||
secret: z.string(),
|
secret: z.string(),
|
||||||
pubkey: z.string(),
|
pubkey: z.string(),
|
||||||
bunkerSeckey: nsecSchema,
|
bunkerSeckey: n.bech32('nsec'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const stateSchema = z.object({
|
const stateSchema = z.object({
|
||||||
|
@ -77,7 +69,7 @@ const stateSchema = z.object({
|
||||||
interface BunkerState {
|
interface BunkerState {
|
||||||
connections: BunkerConnection[];
|
connections: BunkerConnection[];
|
||||||
authorizations: BunkerAuthorization[];
|
authorizations: BunkerAuthorization[];
|
||||||
authorize(pubkey: string): BunkerURI;
|
authorize(pubkey: string): { signer: NostrSigner; relays: string[]; secret: string };
|
||||||
connect(request: BunkerConnectRequest): void;
|
connect(request: BunkerConnectRequest): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +80,7 @@ export const useBunkerStore = create<BunkerState>()(
|
||||||
authorizations: [],
|
authorizations: [],
|
||||||
|
|
||||||
/** Generate a new authorization and persist it into the store. */
|
/** Generate a new authorization and persist it into the store. */
|
||||||
authorize(pubkey: string): BunkerURI {
|
authorize(pubkey: string): { signer: NostrSigner; relays: string[]; secret: string } {
|
||||||
const authorization: BunkerAuthorization = {
|
const authorization: BunkerAuthorization = {
|
||||||
pubkey,
|
pubkey,
|
||||||
secret: crypto.randomUUID(),
|
secret: crypto.randomUUID(),
|
||||||
|
@ -102,7 +94,7 @@ export const useBunkerStore = create<BunkerState>()(
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pubkey: getPublicKey(authorization.bunkerSeckey),
|
signer: new NSecSigner(authorization.bunkerSeckey),
|
||||||
secret: authorization.secret,
|
secret: authorization.secret,
|
||||||
relays: [],
|
relays: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { enableMapSet } from 'immer';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
@ -25,6 +26,8 @@ import './styles/tailwind.css';
|
||||||
import ready from './ready';
|
import ready from './ready';
|
||||||
import { registerSW, lockSW } from './utils/sw';
|
import { registerSW, lockSW } from './utils/sw';
|
||||||
|
|
||||||
|
enableMapSet();
|
||||||
|
|
||||||
if (BuildConfig.NODE_ENV === 'production') {
|
if (BuildConfig.NODE_ENV === 'production') {
|
||||||
printConsoleWarning();
|
printConsoleWarning();
|
||||||
registerSW('/sw.js');
|
registerSW('/sw.js');
|
||||||
|
|
Loading…
Reference in New Issue