applesauce-signers 0.12.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # applesauce-signer
2
2
 
3
- A collection of signer classes for applesauce
3
+ A collection of signer classes for applesauce that are compatible with the [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md) API.
4
4
 
5
- See [documentation](https://hzrd149.github.io/applesauce/signers/signers.html)
5
+ ## Documentation
6
+
7
+ For detailed documentation and API reference, see:
8
+
9
+ - [Signers Documentation](https://hzrd149.github.io/applesauce/signers/signers.html)
10
+ - [Nostr Connect Documentation](https://hzrd149.github.io/applesauce/signers/nostr-connect.html)
11
+ - [API Reference](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce_signers.html)
12
+
13
+ ## Available Signers
14
+
15
+ ### Password Signer (NIP-49)
16
+
17
+ A secure signer that encrypts private keys using [NIP-49](https://github.com/nostr-protocol/nips/blob/master/49.md).
18
+
19
+ ```ts
20
+ // Create a new password signer
21
+ const signer = new PasswordSigner();
22
+
23
+ // Set up with a new key and password
24
+ const randomBytes = new Uint8Array(64);
25
+ window.crypto.getRandomValues(randomBytes);
26
+
27
+ signer.key = randomBytes;
28
+ signer.setPassword("your-password");
29
+
30
+ // Unlock the signer when needed
31
+ await signer.unlock("your-password");
32
+ ```
33
+
34
+ ### Simple Signer
35
+
36
+ A basic signer that holds the secret key in memory with NIP-04 and NIP-44 encryption support.
37
+
38
+ ```ts
39
+ // Create new signer with random key
40
+ const signer = new SimpleSigner();
41
+
42
+ // Or import existing key
43
+ const key = new Uint8Array(32);
44
+ window.crypto.getRandomValues(key);
45
+ const signer = new SimpleSigner(key);
46
+ ```
47
+
48
+ ### Nostr Connect Signer (NIP-46)
49
+
50
+ A client-side implementation for remote signing using [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md).
51
+
52
+ ```ts
53
+ // First, set up the required relay communication methods
54
+ import { Observable } from "rxjs";
55
+
56
+ // Define subscription method for receiving events
57
+ const subscriptionMethod = (filters, relays) => {
58
+ return new Observable((observer) => {
59
+ // Create subscription to relays
60
+ const cleanup = subscribeToRelays(relays, filters, (event) => {
61
+ observer.next(event);
62
+ });
63
+ return () => cleanup();
64
+ });
65
+ };
66
+
67
+ // Define publish method for sending events
68
+ const publishMethod = async (event, relays) => {
69
+ for (const relay of relays) await publishToRelay(relay, event);
70
+ };
71
+
72
+ // You can set these methods globally at app initialization
73
+ NostrConnectSigner.subscriptionMethod = subscriptionMethod;
74
+ NostrConnectSigner.publishMethod = publishMethod;
75
+
76
+ // Now create and use the signer
77
+ const signer = new NostrConnectSigner({
78
+ remote: "<remote signer pubkey>",
79
+ relays: ["wss://relay.example.com"],
80
+ // Or pass methods directly to the constructor
81
+ subscriptionMethod,
82
+ publishMethod,
83
+ });
84
+
85
+ // Create a connection URI for your app
86
+ const uri = signer.getNostrConnectURI({
87
+ name: "My App",
88
+ url: "https://example.com",
89
+ permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3]),
90
+ });
91
+
92
+ // Connect using bunker URI
93
+ const bunkerSigner = await NostrConnectSigner.fromBunkerURI("bunker://...your-uri-here...", {
94
+ permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3]),
95
+ });
96
+ ```
97
+
98
+ ### Other Signers
99
+
100
+ - **Serial Port Signer**: For hardware signing devices (Chrome browsers only)
101
+ - **Amber Clipboard Signer**: Integration with Amber wallet's web API
@@ -64,17 +64,26 @@ export type NostrConnectSignerOptions = {
64
64
  remote?: string;
65
65
  /** Users pubkey */
66
66
  pubkey?: string;
67
- };
68
- export type NostrConnectConnectionMethods = {
69
- /** A method that is called when the subscription needs to be updated */
70
- onSubOpen: (filters: Filter[], relays: string[], onEvent: (event: NostrEvent) => void) => Promise<void>;
71
- /** A method called when the subscription should be closed */
72
- onSubClose: () => Promise<void>;
73
- /** A method that is called when an event needs to be published */
74
- onPublishEvent: (event: NostrEvent, relays: string[]) => Promise<void>;
75
67
  /** A method for handling "auth" requests */
76
68
  onAuth?: (url: string) => Promise<void>;
69
+ /** A method for subscribing to relays */
70
+ subscriptionMethod?: NostrSubscriptionMethod;
71
+ /** A method for publishing events */
72
+ publishMethod?: NostrPublishMethod;
73
+ };
74
+ interface Unsubscribable {
75
+ unsubscribe(): void;
76
+ }
77
+ interface Observer<T> {
78
+ next: (value: T) => void;
79
+ error: (err: any) => void;
80
+ complete: () => void;
81
+ }
82
+ type Subscribable<T extends unknown> = {
83
+ subscribe: (observer: Partial<Observer<T>>) => Unsubscribable;
77
84
  };
85
+ export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Subscribable<NostrEvent>;
86
+ export type NostrPublishMethod = (relays: string[], event: NostrEvent) => void | Promise<void>;
78
87
  export type NostrConnectAppMetadata = {
79
88
  name?: string;
80
89
  image?: string;
@@ -82,12 +91,10 @@ export type NostrConnectAppMetadata = {
82
91
  permissions?: string[];
83
92
  };
84
93
  export declare class NostrConnectSigner implements Nip07Interface {
85
- /** A method that is called when the subscription needs to be updated */
86
- onSubOpen?: (filters: Filter[], relays: string[], onEvent: (event: NostrEvent) => void) => Promise<void>;
87
- /** A method called when the subscription should be closed */
88
- onSubClose?: () => Promise<void>;
89
94
  /** A method that is called when an event needs to be published */
90
- onPublishEvent?: (event: NostrEvent, relays: string[]) => Promise<void>;
95
+ protected publishMethod: NostrPublishMethod;
96
+ /** The active nostr subscription */
97
+ protected subscriptionMethod: NostrSubscriptionMethod;
91
98
  protected log: import("debug").Debugger;
92
99
  /** The local client signer */
93
100
  signer: SimpleSigner;
@@ -102,6 +109,7 @@ export declare class NostrConnectSigner implements Nip07Interface {
102
109
  remote?: string;
103
110
  /** Client pubkey */
104
111
  get clientPubkey(): string;
112
+ /** A method for handling "auth" requests */
105
113
  onAuth: (url: string) => Promise<void>;
106
114
  verifyEvent: typeof verifyEvent;
107
115
  /** A secret used when initiating a connection from the client side */
@@ -114,7 +122,13 @@ export declare class NostrConnectSigner implements Nip07Interface {
114
122
  encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
115
123
  decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
116
124
  } | undefined;
117
- constructor(opts: NostrConnectConnectionMethods & NostrConnectSignerOptions);
125
+ /** A fallback method to use for subscriptionMethod if none is pass in when creating the signer */
126
+ static subscriptionMethod: NostrSubscriptionMethod | undefined;
127
+ /** A fallback method to use for publishMethod if none is pass in when creating the signer */
128
+ static publishMethod: NostrPublishMethod | undefined;
129
+ constructor(opts: NostrConnectSignerOptions);
130
+ /** The currently active REQ subscription */
131
+ protected req?: Unsubscribable;
118
132
  /** Open the connection */
119
133
  open(): Promise<void>;
120
134
  /** Close the connection */
@@ -155,7 +169,7 @@ export declare class NostrConnectSigner implements Nip07Interface {
155
169
  /** Builds an array of signing permissions for event kinds */
156
170
  static buildSigningPermissions(kinds: number[]): string[];
157
171
  /** Create a {@link NostrConnectSigner} from a bunker:// URI */
158
- static fromBunkerURI(uri: string, options: NostrConnectConnectionMethods & {
172
+ static fromBunkerURI(uri: string, options?: Omit<NostrConnectSignerOptions, "relays"> & {
159
173
  permissions?: string[];
160
174
  signer?: SimpleSigner;
161
175
  }): Promise<NostrConnectSigner>;
@@ -33,14 +33,10 @@ async function defaultHandleAuth(url) {
33
33
  window.open(url, "auth", "width=400,height=600,resizable=no,status=no,location=no,toolbar=no,menubar=no");
34
34
  }
35
35
  export class NostrConnectSigner {
36
- /** A method that is called when the subscription needs to be updated */
37
- onSubOpen;
38
- /** A method called when the subscription should be closed */
39
- onSubClose;
40
36
  /** A method that is called when an event needs to be published */
41
- onPublishEvent;
42
- // protected pool: IConnectionPool;
43
- // protected sub: MultiSubscription;
37
+ publishMethod;
38
+ /** The active nostr subscription */
39
+ subscriptionMethod;
44
40
  log = logger.extend("NostrConnectSigner");
45
41
  /** The local client signer */
46
42
  signer;
@@ -57,19 +53,29 @@ export class NostrConnectSigner {
57
53
  get clientPubkey() {
58
54
  return getPublicKey(this.signer.key);
59
55
  }
56
+ /** A method for handling "auth" requests */
60
57
  onAuth = defaultHandleAuth;
61
58
  verifyEvent = verifyEvent;
62
59
  /** A secret used when initiating a connection from the client side */
63
60
  clientSecret = nanoid(12);
64
61
  nip04;
65
62
  nip44;
63
+ /** A fallback method to use for subscriptionMethod if none is pass in when creating the signer */
64
+ static subscriptionMethod = undefined;
65
+ /** A fallback method to use for publishMethod if none is pass in when creating the signer */
66
+ static publishMethod = undefined;
66
67
  constructor(opts) {
67
68
  this.relays = opts.relays;
68
69
  this.pubkey = opts.pubkey;
69
70
  this.remote = opts.remote;
70
- this.onSubOpen = opts.onSubOpen;
71
- this.onSubClose = opts.onSubClose;
72
- this.onPublishEvent = opts.onPublishEvent;
71
+ const subscriptionMethod = opts.subscriptionMethod || NostrConnectSigner.subscriptionMethod;
72
+ if (!subscriptionMethod)
73
+ throw new Error("Missing subscriptionMethod, either pass a method or set NostrConnectSigner.subscriptionMethod");
74
+ const publishMethod = opts.publishMethod || NostrConnectSigner.publishMethod;
75
+ if (!publishMethod)
76
+ throw new Error("Missing publishMethod, either pass a method or set NostrConnectSigner.publishMethod");
77
+ this.subscriptionMethod = subscriptionMethod;
78
+ this.publishMethod = publishMethod;
73
79
  if (opts.onAuth)
74
80
  this.onAuth = opts.onAuth;
75
81
  this.signer = opts?.signer || new SimpleSigner();
@@ -82,6 +88,8 @@ export class NostrConnectSigner {
82
88
  decrypt: this.nip44Decrypt.bind(this),
83
89
  };
84
90
  }
91
+ /** The currently active REQ subscription */
92
+ req;
85
93
  /** Open the connection */
86
94
  async open() {
87
95
  if (this.subscriptionOpen)
@@ -89,19 +97,21 @@ export class NostrConnectSigner {
89
97
  this.subscriptionOpen = true;
90
98
  const pubkey = await this.signer.getPublicKey();
91
99
  // Setup subscription
92
- await this.onSubOpen?.([
100
+ this.req = this.subscriptionMethod(this.relays, [
93
101
  {
94
102
  kinds: [kinds.NostrConnect],
95
103
  "#p": [pubkey],
96
104
  },
97
- ], this.relays, this.handleEvent.bind(this));
105
+ ]).subscribe({
106
+ next: (event) => this.handleEvent(event),
107
+ });
98
108
  this.log("Opened", this.relays);
99
109
  }
100
110
  /** Close the connection */
101
111
  async close() {
102
112
  this.subscriptionOpen = false;
103
113
  this.isConnected = false;
104
- await this.onSubClose?.();
114
+ this.req?.unsubscribe();
105
115
  this.log("Closed");
106
116
  }
107
117
  requests = new Map();
@@ -178,7 +188,7 @@ export class NostrConnectSigner {
178
188
  this.log(`Sending request ${id} (${method}) ${JSON.stringify(params)}`);
179
189
  const p = createDefer();
180
190
  this.requests.set(id, p);
181
- await this.onPublishEvent?.(event, this.relays);
191
+ await this.publishMethod?.(this.relays, event);
182
192
  return p;
183
193
  }
184
194
  /** Connect to remote signer */
@@ -321,7 +331,7 @@ export class NostrConnectSigner {
321
331
  static async fromBunkerURI(uri, options) {
322
332
  const { remote, relays, secret } = NostrConnectSigner.parseBunkerURI(uri);
323
333
  const client = new NostrConnectSigner({ relays, remote, ...options });
324
- await client.connect(secret, options.permissions);
334
+ await client.connect(secret, options?.permissions);
325
335
  return client;
326
336
  }
327
337
  }
@@ -3,23 +3,21 @@ import { NostrConnectSigner } from "./nostr-connect-signer.js";
3
3
  import { SimpleSigner } from "./simple-signer.js";
4
4
  describe("NostrConnectSigner", () => {
5
5
  describe("connection", () => {
6
- it("should call onSubOpen with filters", async () => {
6
+ it("should call subscription method with filters", async () => {
7
7
  const relays = ["wss://relay.signer.com"];
8
- const onSubOpen = vi.fn(async () => { });
9
- const onSubClose = vi.fn(async () => { });
10
- const onPublishEvent = vi.fn(async () => { });
8
+ const subscription = vi.fn().mockReturnValue({ subscribe: vi.fn() });
9
+ const publish = vi.fn(async () => { });
11
10
  const client = new SimpleSigner();
12
11
  const remote = new SimpleSigner();
13
12
  const signer = new NostrConnectSigner({
14
- onSubOpen,
15
- onSubClose,
16
- onPublishEvent,
17
13
  relays,
18
14
  remote: await remote.getPublicKey(),
19
15
  signer: client,
16
+ subscriptionMethod: subscription,
17
+ publishMethod: publish,
20
18
  });
21
19
  signer.connect();
22
- expect(onSubOpen).toHaveBeenCalledWith([{ "#p": [await client.getPublicKey()], kinds: [24133] }], relays, expect.any(Function));
20
+ expect(subscription).toHaveBeenCalledWith(relays, [{ "#p": [await client.getPublicKey()], kinds: [24133] }]);
23
21
  });
24
22
  });
25
23
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-signers",
3
- "version": "0.12.0",
3
+ "version": "1.0.0",
4
4
  "description": "Signer classes for applesauce",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "@noble/hashes": "^1.7.1",
37
37
  "@noble/secp256k1": "^1.7.1",
38
38
  "@scure/base": "^1.2.4",
39
- "applesauce-core": "^0.12.0",
39
+ "applesauce-core": "^1.0.0",
40
40
  "debug": "^4.4.0",
41
41
  "nanoid": "^5.0.9",
42
42
  "nostr-tools": "^2.10.4"
@@ -44,8 +44,8 @@
44
44
  "devDependencies": {
45
45
  "@types/debug": "^4.1.12",
46
46
  "@types/dom-serial": "^1.0.6",
47
- "typescript": "^5.7.3",
48
- "vitest": "^3.0.5"
47
+ "typescript": "^5.8.3",
48
+ "vitest": "^3.1.1"
49
49
  },
50
50
  "funding": {
51
51
  "type": "lightning",