logInNostr: rewrite with new Bunker API
This commit is contained in:
parent
1b54bcf5f3
commit
cfd4908e8d
|
@ -1,7 +1,7 @@
|
|||
import { NostrSigner, NRelay1, NSecSigner } from '@nostrify/nostrify';
|
||||
import { generateSecretKey } from 'nostr-tools';
|
||||
|
||||
import { NostrRPC } from 'soapbox/features/nostr/NostrRPC';
|
||||
import { NBunker } from 'soapbox/features/nostr/NBunker';
|
||||
import { useBunkerStore } from 'soapbox/hooks/nostr/useBunkerStore';
|
||||
import { type AppDispatch } from 'soapbox/store';
|
||||
|
||||
|
@ -11,52 +11,44 @@ import { obtainOAuthToken } from './oauth';
|
|||
const NOSTR_PUBKEY_SET = 'NOSTR_PUBKEY_SET';
|
||||
|
||||
/** Log in with a Nostr pubkey. */
|
||||
function logInNostr(signer: NostrSigner, relay: NRelay1, signal: AbortSignal) {
|
||||
function logInNostr(signer: NostrSigner, relay: NRelay1) {
|
||||
return async (dispatch: AppDispatch) => {
|
||||
const authorization = generateBunkerAuth();
|
||||
|
||||
const pubkey = await signer.getPublicKey();
|
||||
const bunkerPubkey = await authorization.signer.getPublicKey();
|
||||
|
||||
const rpc = new NostrRPC(relay, authorization.signer);
|
||||
const sub = rpc.req([{ kinds: [24133], '#p': [bunkerPubkey], limit: 0 }], { signal: AbortSignal.timeout(1_000) });
|
||||
let authorizedPubkey: string | undefined;
|
||||
|
||||
const tokenPromise = dispatch(obtainOAuthToken({
|
||||
using bunker = new NBunker({
|
||||
relay,
|
||||
userSigner: signer,
|
||||
bunkerSigner: authorization.signer,
|
||||
onConnect(request, event) {
|
||||
const [, secret] = request.params;
|
||||
|
||||
if (secret === authorization.secret) {
|
||||
bunker.authorize(event.pubkey);
|
||||
authorizedPubkey = event.pubkey;
|
||||
return { id: request.id, result: 'ack' };
|
||||
} else {
|
||||
return { id: request.id, result: '', error: 'Invalid secret' };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const token = await dispatch(obtainOAuthToken({
|
||||
grant_type: 'nostr_bunker',
|
||||
pubkey: bunkerPubkey,
|
||||
relays: [relay.socket.url],
|
||||
secret: authorization.secret,
|
||||
}));
|
||||
|
||||
let authorizedPubkey: string | 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;
|
||||
}
|
||||
|
||||
// FIXME: this needs to actually be a full bunker that handles all methods...
|
||||
// maybe NBunker can be modular? It should be okay to make multiple instances of it at once.
|
||||
// Then it could be used for knox too.
|
||||
}
|
||||
|
||||
if (!authorizedPubkey) {
|
||||
throw new Error('Authorization failed');
|
||||
}
|
||||
|
||||
const accessToken = dispatch(authLoggedIn(await tokenPromise)).access_token as string;
|
||||
const accessToken = dispatch(authLoggedIn(token)).access_token as string;
|
||||
const bunkerState = useBunkerStore.getState();
|
||||
|
||||
bunkerState.connect({
|
||||
|
@ -71,12 +63,12 @@ function logInNostr(signer: NostrSigner, relay: NRelay1, signal: AbortSignal) {
|
|||
}
|
||||
|
||||
/** Log in with a Nostr extension. */
|
||||
function nostrExtensionLogIn(relay: NRelay1, signal: AbortSignal) {
|
||||
function nostrExtensionLogIn(relay: NRelay1) {
|
||||
return async (dispatch: AppDispatch) => {
|
||||
if (!window.nostr) {
|
||||
throw new Error('No Nostr signer available');
|
||||
}
|
||||
return dispatch(logInNostr(window.nostr, relay, signal));
|
||||
return dispatch(logInNostr(window.nostr, relay));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,26 @@ export interface NBunkerOpts {
|
|||
/**
|
||||
* Callback when a `connect` request has been received.
|
||||
* This is a good place to call `bunker.authorize()` with the remote client's pubkey.
|
||||
* It's up to the caller to verify the request parameters and secret.
|
||||
* It's up to the caller to verify the request parameters and secret, and then return a response object.
|
||||
* All other methods are handled by the bunker automatically.
|
||||
*
|
||||
* ```ts
|
||||
* const bunker = new Bunker({
|
||||
* ...opts,
|
||||
* onConnect(request, event) {
|
||||
* const [, secret] = request.params;
|
||||
*
|
||||
* if (secret === authorization.secret) {
|
||||
* bunker.authorize(event.pubkey); // Authorize the pubkey for signer actions.
|
||||
* return { id: request.id, result: 'ack' }; // Return a success response.
|
||||
* } else {
|
||||
* return { id: request.id, result: '', error: 'Invalid secret' };
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
onConnect?(request: NostrConnectRequest, event: NostrEvent): Promise<void> | void;
|
||||
onConnect?(request: NostrConnectRequest, event: NostrEvent): Promise<NostrConnectResponse> | NostrConnectResponse;
|
||||
/**
|
||||
* Callback when an error occurs while parsing a request event.
|
||||
* Client errors are not captured here, only errors that occur before arequest's `id` can be known,
|
||||
|
@ -86,7 +102,10 @@ export class NBunker {
|
|||
const { pubkey } = event;
|
||||
|
||||
if (request.method === 'connect') {
|
||||
onConnect?.(request, event);
|
||||
if (onConnect) {
|
||||
const response = await onConnect(request, event);
|
||||
return this.sendResponse(pubkey, response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -191,7 +210,7 @@ export class NBunker {
|
|||
this.controller.abort();
|
||||
}
|
||||
|
||||
[Symbol.asyncDispose](): void {
|
||||
[Symbol.dispose](): void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const NostrExtensionIndicator: React.FC = () => {
|
|||
|
||||
const onClick = () => {
|
||||
if (relay) {
|
||||
dispatch(nostrExtensionLogIn(relay, AbortSignal.timeout(30_000)));
|
||||
dispatch(nostrExtensionLogIn(relay));
|
||||
dispatch(closeModal());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ const ExtensionStep: React.FC<IExtensionStep> = ({ isLogin, onClickAlt, onClose
|
|||
|
||||
const onClick = () => {
|
||||
if (relay) {
|
||||
dispatch(nostrExtensionLogIn(relay, AbortSignal.timeout(30_000)));
|
||||
dispatch(nostrExtensionLogIn(relay));
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ const KeyAddStep: React.FC<IKeyAddStep> = ({ onClose }) => {
|
|||
if (result.type === 'nsec') {
|
||||
const seckey = result.data;
|
||||
const signer = NKeys.add(seckey);
|
||||
dispatch(logInNostr(signer, relay, AbortSignal.timeout(30_000)));
|
||||
dispatch(logInNostr(signer, relay));
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
|||
|
||||
onClose();
|
||||
|
||||
await dispatch(logInNostr(signer, relay, AbortSignal.timeout(30_000)));
|
||||
await dispatch(logInNostr(signer, relay));
|
||||
|
||||
if (isMobile) {
|
||||
dispatch(closeSidebar());
|
||||
|
|
Loading…
Reference in New Issue