Pass pubkey to nip 55 signer to avoid mixing up sessions

This commit is contained in:
Jon Staab
2025-10-13 11:20:01 -07:00
parent bbab2e0628
commit 5c03ff3e29
4 changed files with 16 additions and 84 deletions
+4 -79
View File
@@ -18,7 +18,8 @@ import { Nip55Signer, getNip55 } from '@welshman/signer'
// Check for available signing apps // Check for available signing apps
const apps = await getNip55() const apps = await getNip55()
if (apps.length > 0) { if (apps.length > 0) {
const signer = new Nip55Signer(apps[0].packageName) const optionalSavedPubkey = localStorage.getItem('my-saved-pubkey')
const signer = new Nip55Signer(apps[0].packageName, optionalSavedPubkey)
} }
``` ```
@@ -40,28 +41,11 @@ interface AppInfo {
### Constructor ### Constructor
```typescript ```typescript
constructor(packageName: string) constructor(packageName: string, publicKey?: string)
``` ```
Creates a new signer instance that will communicate with the specified native app. Creates a new signer instance that will communicate with the specified native app.
- `packageName`: The package identifier of the native signing app - `packageName`: The package identifier of the native signing app
- `publicKey`: optional user pubkey. Recommended for resuming existing signer sessions when the signer is managing multiple user accounts.
### ISigner implementation
The `Nip55Signer` class implements the [`ISigner`](/signer/) interface
```typescript
class Nip55Signer implements ISigner {
// Constructor
constructor(private secret: string)
// ISigner implementation
sign: SignWithOptions
getPubkey: () => Promise<string>
nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt }
}
```
## Complete Example ## Complete Example
@@ -104,62 +88,3 @@ async function example() {
} }
} }
``` ```
## Implementation Details
### Request Serialization
The signer implements a lock mechanism to prevent concurrent requests:
```typescript
class Nip55Signer implements ISigner {
#lock = Promise.resolve()
#plugin = NostrSignerPlugin
#packageName: string
#packageNameSet = false
#then = async <T>(f: (signer: typeof NostrSignerPlugin) => Promise<T>) => {
const promise = this.#lock.then(async () => {
if (!this.#packageNameSet) {
await this.#initialize()
}
return f(this.#plugin)
})
this.#lock = promise.then(() => Promise.resolve())
return promise
}
}
```
### Public Key Caching
The signer caches the public key to minimize native app interactions:
```typescript
class Nip55Signer {
#npub?: string
#publicKey?: string
getPubkey = async (): Promise<string> => {
return this.#then(async signer => {
if (!this.#publicKey || !this.#npub) {
const {npub} = await signer.getPublicKey()
this.#npub = npub
const {data} = decode(npub)
this.#publicKey = data as string
}
return this.#publicKey
})
}
}
```
## Platform Support
- iOS: Requires compatible signing app
- Android: Requires compatible signing app
- Operations availability depends on native app implementation
- Some features might be platform-specific
+1 -1
View File
@@ -239,7 +239,7 @@ export const getSigner = cached({
getValue: ([session]: [Session | undefined]) => { getValue: ([session]: [Session | undefined]) => {
if (isNip07Session(session)) return wrapSigner(new Nip07Signer()) if (isNip07Session(session)) return wrapSigner(new Nip07Signer())
if (isNip01Session(session)) return wrapSigner(new Nip01Signer(session.secret)) if (isNip01Session(session)) return wrapSigner(new Nip01Signer(session.secret))
if (isNip55Session(session)) return wrapSigner(new Nip55Signer(session.signer)) if (isNip55Session(session)) return wrapSigner(new Nip55Signer(session.signer, session.pubkey))
if (isNip46Session(session)) { if (isNip46Session(session)) {
const { const {
secret: clientSecret, secret: clientSecret,
+1 -1
View File
@@ -622,7 +622,7 @@ export const sortBy = <T>(f: (x: T) => any, xs: T[]) =>
* @param xs - Array to group * @param xs - Array to group
* @returns Map of groups * @returns Map of groups
*/ */
export const groupBy = <T, K>(f: (x: T) => K, xs: T[]) => { export const groupBy = <T, K>(f: (x: T) => K, xs: Iterable<T>) => {
const r = new Map<K, T[]>() const r = new Map<K, T[]>()
for (const x of xs) { for (const x of xs) {
+10 -3
View File
@@ -1,5 +1,5 @@
import {NostrSignerPlugin, AppInfo} from "nostr-signer-capacitor-plugin" import {NostrSignerPlugin, AppInfo} from "nostr-signer-capacitor-plugin"
import {decode} from "nostr-tools/nip19" import * as nip19 from "nostr-tools/nip19"
import {SignedEvent, StampedEvent} from "@welshman/util" import {SignedEvent, StampedEvent} from "@welshman/util"
import {hash, own, signWithOptions, SignOptions, ISigner} from "../util.js" import {hash, own, signWithOptions, SignOptions, ISigner} from "../util.js"
@@ -16,8 +16,14 @@ export class Nip55Signer implements ISigner {
#npub?: string #npub?: string
#publicKey?: string #publicKey?: string
constructor(packageName: string) { constructor(packageName: string, publicKey?: string) {
this.#packageName = packageName this.#packageName = packageName
if (publicKey) {
this.#publicKey = publicKey
this.#npub = nip19.npubEncode(publicKey)
}
this.#initialize() this.#initialize()
} }
@@ -53,8 +59,9 @@ export class Nip55Signer implements ISigner {
if (!this.#publicKey || !this.#npub) { if (!this.#publicKey || !this.#npub) {
try { try {
const {npub} = await signer.getPublicKey() const {npub} = await signer.getPublicKey()
const {data} = nip19.decode(npub)
this.#npub = npub this.#npub = npub
const {data} = decode(npub)
this.#publicKey = data as string this.#publicKey = data as string
} catch (error) { } catch (error) {
throw new Error("Failed to get public key") throw new Error("Failed to get public key")