@nostr-dev-kit/ndk-mobile
Version:
NDK Mobile
187 lines (165 loc) • 6.92 kB
text/typescript
import type NDK from "@nostr-dev-kit/ndk";
import { type NDKEncryptionScheme, type NDKSigner, NDKUser, type NostrEvent } from "@nostr-dev-kit/ndk";
import { registerSigner } from "@nostr-dev-kit/ndk";
import * as Nip55 from "expo-nip55";
export type NDKNip55Permission = Nip55.Permission;
export class NDKNip55Signer implements NDKSigner {
private _pubkey: string;
private _user?: NDKUser;
public packageName: string;
private ndk?: NDK;
public permissions: NDKNip55Permission[];
constructor(packageName: string, ndk?: NDK, permissions: NDKNip55Permission[] = []) {
this.packageName = packageName;
this.ndk = ndk;
this.permissions = permissions;
}
/**
* Blocks until the signer is ready and returns the associated NDKUser.
* @returns A promise that resolves to the NDKUser instance.
*/
async blockUntilReady(): Promise<NDKUser> {
if (this._user) return this._user;
await Nip55.setPackageName(this.packageName);
const data = await Nip55.getPublicKey(this.packageName, this.permissions);
if (!data) throw new Error("No signer available found");
this._user = new NDKUser({ npub: data.npub });
this._pubkey = this._user.pubkey;
return this._user;
}
/**
* Getter for the user property.
* @returns A promise that resolves to the NDKUser instance.
*/
async user(): Promise<NDKUser> {
return this.blockUntilReady();
}
public get userSync(): NDKUser {
if (!this._user) throw new Error("User not ready");
return this._user;
}
public get pubkey(): string {
if (!this._pubkey) throw new Error("Pubkey not ready");
return this._pubkey;
}
/**
* Signs the given Nostr event.
* @param event - The Nostr event to be signed.
* @returns A promise that resolves to the signature of the signed event.
*/
async sign(event: NostrEvent): Promise<string> {
const result = await Nip55.signEvent(this.packageName, JSON.stringify(event), event.id, this._pubkey);
return result.signature;
}
/**
* Determine the types of encryption (by nip) that this signer can perform.
* Implementing classes SHOULD return a value even for legacy (only nip04) third party signers.
* @nip Optionally returns an array with single supported nip or empty, to check for truthy or falsy.
* @return A promised list of any (or none) of these strings ['nip04', 'nip44']
*/
async encryptionEnabled?(scheme?: NDKEncryptionScheme): Promise<NDKEncryptionScheme[]> {
if (scheme) return [scheme];
return Promise.resolve(["nip04", "nip44"]);
}
/**
* Encrypts the given Nostr event for the given recipient.
* Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined
* @param recipient - The recipient (pubkey or conversationKey) of the encrypted value.
* @param value - The value to be encrypted.
* @param nip - which NIP is being implemented ('nip04', 'nip44')
*/
async encrypt(recipient: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string> {
const randomId = Math.random().toString(36).substring(2, 15);
if (scheme === "nip04") {
const result = (await Nip55.nip04Encrypt(
this.packageName,
value,
randomId,
recipient.pubkey,
this._pubkey,
)) as unknown as { result: string };
return result.result;
} else {
const result = (await Nip55.nip44Encrypt(
this.packageName,
value,
randomId,
recipient.pubkey,
this._pubkey,
)) as unknown as { result: string };
return result.result;
}
}
/**
* Decrypts the given value.
* Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined
* @param sender - The sender (pubkey or conversationKey) of the encrypted value
* @param value - The value to be decrypted
* @param scheme - which NIP is being implemented ('nip04', 'nip44', 'nip49')
*/
async decrypt(sender: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string> {
const randomId = Math.random().toString(36).substring(2, 15);
if (scheme === "nip04") {
const result = (await Nip55.nip04Decrypt(
this.packageName,
value,
randomId,
this.pubkey,
sender.pubkey,
)) as unknown as { result: string };
return result.result;
} else {
const result = (await Nip55.nip44Decrypt(
this.packageName,
value,
randomId,
this.pubkey,
sender.pubkey,
)) as unknown as { result: string };
return result.result;
}
}
/**
* Serializes the signer's package name and pubkey.
* @returns A JSON string containing the type, package name, and pubkey.
*/
public toPayload(): string {
if (!this._pubkey) {
throw new Error("NIP-55 signer not ready for serialization (missing pubkey)");
}
const payload = {
type: "nip55",
payload: {
packageName: this.packageName,
pubkey: this._pubkey, // Include pubkey for potential re-initialization
},
};
return JSON.stringify(payload);
}
/**
* Deserializes the signer from a payload string.
* @param payloadString The JSON string obtained from toPayload().
* @param ndk Optional NDK instance.
* @returns An instance of NDKNip55Signer.
*/
public static async fromPayload(payloadString: string, ndk?: NDK): Promise<NDKNip55Signer> {
const parsed = JSON.parse(payloadString);
if (parsed.type !== "nip55") {
throw new Error(`Invalid payload type: expected 'nip55', got ${parsed.type}`);
}
const payload = parsed.payload;
if (!payload || typeof payload !== "object" || !payload.packageName) {
throw new Error("Invalid payload content for nip55 signer");
}
const signer = new NDKNip55Signer(payload.packageName, ndk);
// We can optionally pre-set the pubkey if available in the payload
// to potentially speed up the initial blockUntilReady, though it will verify anyway.
if (payload.pubkey) {
signer._pubkey = payload.pubkey;
}
return signer;
}
}
// Register this signer type with the core registry *outside* the class definition
// This ensures ndkSignerFromPayload can deserialize it.
registerSigner("nip55", NDKNip55Signer);