Add NSpec and SoapboxSigner
This commit is contained in:
@ -2,7 +2,7 @@ import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { importEntities } from 'soapbox/entity-store/actions';
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { selectAccount } from 'soapbox/selectors';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features';
|
||||
@ -134,7 +134,7 @@ const createAccount = (params: Record<string, any>) =>
|
||||
async (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const { instance } = getState();
|
||||
const { nostrSignup } = getFeatures(instance);
|
||||
const pubkey = nostrSignup ? await getPublicKey() : undefined;
|
||||
const pubkey = nostrSignup ? await signer.getPublicKey() : undefined;
|
||||
|
||||
dispatch({ type: ACCOUNT_CREATE_REQUEST, params });
|
||||
return api(getState, 'app').post('/api/v1/accounts', params, {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { type AppDispatch } from 'soapbox/store';
|
||||
|
||||
import { verifyCredentials } from './auth';
|
||||
@ -8,7 +8,7 @@ import { verifyCredentials } from './auth';
|
||||
/** Log in with a Nostr pubkey. */
|
||||
function nostrLogIn() {
|
||||
return async (dispatch: AppDispatch) => {
|
||||
const pubkey = await getPublicKey();
|
||||
const pubkey = await signer.getPublicKey();
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
return dispatch(verifyCredentials(npub));
|
||||
|
||||
@ -2,7 +2,7 @@ import { NiceRelay } from 'nostr-machina';
|
||||
import { type Event } from 'nostr-tools';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { nip04, signEvent } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr';
|
||||
import { jsonSchema } from 'soapbox/schemas/utils';
|
||||
@ -21,7 +21,7 @@ function useSignerStream() {
|
||||
|
||||
async function handleConnectEvent(event: Event) {
|
||||
if (!relay || !pubkey) return;
|
||||
const decrypted = await nip04.decrypt(pubkey, event.content);
|
||||
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
|
||||
|
||||
const reqMsg = jsonSchema.pipe(connectRequestSchema).safeParse(decrypted);
|
||||
if (!reqMsg.success) {
|
||||
@ -32,12 +32,12 @@ function useSignerStream() {
|
||||
|
||||
const respMsg = {
|
||||
id: reqMsg.data.id,
|
||||
result: await signEvent(reqMsg.data.params[0], reqMsg.data.params[1]),
|
||||
result: await signer.signEvent(reqMsg.data.params[0]),
|
||||
};
|
||||
|
||||
const respEvent = await signEvent({
|
||||
const respEvent = await signer.signEvent({
|
||||
kind: 24133,
|
||||
content: await nip04.encrypt(pubkey, JSON.stringify(respMsg)),
|
||||
content: await signer.nip04!.encrypt(pubkey, JSON.stringify(respMsg)),
|
||||
tags: [['p', pubkey]],
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
@ -48,7 +48,7 @@ function useSignerStream() {
|
||||
async function handleWalletEvent(event: Event) {
|
||||
if (!relay || !pubkey) return;
|
||||
|
||||
const decrypted = await nip04.decrypt(pubkey, event.content);
|
||||
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
|
||||
|
||||
const reqMsg = jsonSchema.pipe(nwcRequestSchema).safeParse(decrypted);
|
||||
if (!reqMsg.success) {
|
||||
|
||||
44
src/features/nostr/SoapboxSigner.ts
Normal file
44
src/features/nostr/SoapboxSigner.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { hexToBytes } from '@noble/hashes/utils';
|
||||
import { type NostrSigner, type NostrEvent, NSecSigner } from 'nspec';
|
||||
|
||||
/** Use key from `localStorage` if available, falling back to NIP-07. */
|
||||
export class SoapboxSigner implements NostrSigner {
|
||||
|
||||
#signer: NostrSigner;
|
||||
|
||||
constructor() {
|
||||
const privateKey = localStorage.getItem('soapbox:nostr:privateKey');
|
||||
const signer = privateKey ? new NSecSigner(hexToBytes(privateKey)) : window.nostr;
|
||||
|
||||
if (!signer) {
|
||||
throw new Error('No Nostr signer available');
|
||||
}
|
||||
|
||||
this.#signer = signer;
|
||||
}
|
||||
|
||||
async getPublicKey(): Promise<string> {
|
||||
return this.#signer.getPublicKey();
|
||||
}
|
||||
|
||||
async signEvent(event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<NostrEvent> {
|
||||
return this.#signer.signEvent(event);
|
||||
}
|
||||
|
||||
nip04 = {
|
||||
encrypt: (pubkey: string, plaintext: string): Promise<string> => {
|
||||
if (!this.#signer.nip04) {
|
||||
throw new Error('NIP-04 not supported by signer');
|
||||
}
|
||||
return this.#signer.nip04.encrypt(pubkey, plaintext);
|
||||
},
|
||||
|
||||
decrypt: (pubkey: string, ciphertext: string): Promise<string> => {
|
||||
if (!this.#signer.nip04) {
|
||||
throw new Error('NIP-04 not supported by signer');
|
||||
}
|
||||
return this.#signer.nip04.decrypt(pubkey, ciphertext);
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@ import {
|
||||
finishEvent,
|
||||
nip04 as _nip04,
|
||||
} from 'nostr-tools';
|
||||
import { type NostrSigner } from 'nspec';
|
||||
|
||||
import { powWorker } from 'soapbox/workers';
|
||||
|
||||
@ -60,4 +61,10 @@ const nip04 = {
|
||||
},
|
||||
};
|
||||
|
||||
export { getPublicKey, signEvent, nip04 };
|
||||
const signer: NostrSigner = {
|
||||
getPublicKey,
|
||||
signEvent,
|
||||
nip04,
|
||||
};
|
||||
|
||||
export { signer };
|
||||
@ -1,12 +0,0 @@
|
||||
import type { Event, EventTemplate } from 'nostr-tools';
|
||||
|
||||
interface Nostr {
|
||||
getPublicKey(): Promise<string>;
|
||||
signEvent(event: EventTemplate): Promise<Event>;
|
||||
nip04?: {
|
||||
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export default Nostr;
|
||||
4
src/types/window.d.ts
vendored
4
src/types/window.d.ts
vendored
@ -1,7 +1,7 @@
|
||||
import type Nostr from './nostr';
|
||||
import type { NostrSigner } from 'nspec';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
nostr?: Nostr;
|
||||
nostr?: NostrSigner;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user