applesauce-accounts 2.0.0 → 3.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/dist/account.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Nip07Interface } from "applesauce-signers";
1
+ import { ISigner } from "applesauce-signers";
2
2
  import { BehaviorSubject } from "rxjs";
3
3
  import { NostrEvent } from "nostr-tools";
4
4
  import { EventTemplate, IAccount, SerializedAccount } from "./types.js";
@@ -6,7 +6,7 @@ import { EventTemplate, IAccount, SerializedAccount } from "./types.js";
6
6
  export declare class SignerMismatchError extends Error {
7
7
  }
8
8
  /** A base class for all accounts */
9
- export declare class BaseAccount<Signer extends Nip07Interface, SignerData, Metadata extends unknown> implements IAccount<Signer, SignerData, Metadata> {
9
+ export declare class BaseAccount<Signer extends ISigner, SignerData, Metadata extends unknown> implements IAccount<Signer, SignerData, Metadata> {
10
10
  pubkey: string;
11
11
  signer: Signer;
12
12
  id: string;
@@ -16,18 +16,20 @@ export declare class BaseAccount<Signer extends Nip07Interface, SignerData, Meta
16
16
  metadata$: BehaviorSubject<Metadata | undefined>;
17
17
  get metadata(): Metadata | undefined;
18
18
  set metadata(metadata: Metadata);
19
- get nip04(): Nip07Interface["nip04"] | undefined;
20
- get nip44(): Nip07Interface["nip44"] | undefined;
19
+ get nip04(): ISigner["nip04"] | undefined;
20
+ get nip44(): ISigner["nip44"] | undefined;
21
21
  constructor(pubkey: string, signer: Signer);
22
22
  toJSON(): SerializedAccount<SignerData, Metadata>;
23
+ /** A method that wraps any signer interaction, this allows the account to wait for unlock or queue requests */
24
+ protected operation<T extends unknown>(operation: () => Promise<T>): Promise<T>;
23
25
  /** Adds the common fields to the serialized output of a toJSON method */
24
26
  protected saveCommonFields(json: Omit<SerializedAccount<SignerData, Metadata>, "id" | "type" | "metadata" | "pubkey">): SerializedAccount<SignerData, Metadata>;
25
27
  /** Sets an accounts id and metadata. NOTE: This should only be used in fromJSON methods */
26
28
  static loadCommonFields<T extends IAccount>(account: T, json: SerializedAccount<any, any>): T;
27
29
  /** Gets the pubkey from the signer */
28
- getPublicKey(): string | Promise<string>;
30
+ getPublicKey(): Promise<string>;
29
31
  /** sign the event and make sure its signed with the correct pubkey */
30
- signEvent(template: EventTemplate): Promise<NostrEvent> | NostrEvent;
32
+ signEvent(template: EventTemplate): Promise<NostrEvent>;
31
33
  /** Aborts all pending requests in the queue */
32
34
  abortQueue(reason: Error): void;
33
35
  /** internal queue */
@@ -35,5 +37,5 @@ export declare class BaseAccount<Signer extends Nip07Interface, SignerData, Meta
35
37
  protected lock: Promise<any> | null;
36
38
  protected abort: AbortController | null;
37
39
  protected reduceQueue(): void;
38
- protected waitForLock<T>(fn: () => Promise<T> | T): Promise<T> | T;
40
+ protected waitForQueue<T extends unknown>(operation: () => Promise<T>): Promise<T>;
39
41
  }
package/dist/account.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { nanoid } from "nanoid";
2
2
  import { BehaviorSubject } from "rxjs";
3
+ import { getEventHash } from "nostr-tools";
3
4
  /** Wraps a promise in an abort signal */
4
5
  function wrapInSignal(promise, signal) {
5
6
  return new Promise((res, rej) => {
@@ -47,24 +48,16 @@ export class BaseAccount {
47
48
  if (!this.signer.nip04)
48
49
  return undefined;
49
50
  return {
50
- encrypt: (pubkey, plaintext) => {
51
- return this.waitForLock(() => this.signer.nip04.encrypt(pubkey, plaintext));
52
- },
53
- decrypt: (pubkey, plaintext) => {
54
- return this.waitForLock(() => this.signer.nip04.decrypt(pubkey, plaintext));
55
- },
51
+ encrypt: async (pubkey, plaintext) => this.operation(() => this.signer.nip04.encrypt(pubkey, plaintext)),
52
+ decrypt: async (pubkey, plaintext) => this.operation(() => this.signer.nip04.decrypt(pubkey, plaintext)),
56
53
  };
57
54
  }
58
55
  get nip44() {
59
56
  if (!this.signer.nip44)
60
57
  return undefined;
61
58
  return {
62
- encrypt: (pubkey, plaintext) => {
63
- return this.waitForLock(() => this.signer.nip44.encrypt(pubkey, plaintext));
64
- },
65
- decrypt: (pubkey, plaintext) => {
66
- return this.waitForLock(() => this.signer.nip44.decrypt(pubkey, plaintext));
67
- },
59
+ encrypt: async (pubkey, plaintext) => this.operation(() => this.signer.nip44.encrypt(pubkey, plaintext)),
60
+ decrypt: async (pubkey, plaintext) => this.operation(() => this.signer.nip44.decrypt(pubkey, plaintext)),
68
61
  };
69
62
  }
70
63
  constructor(pubkey, signer) {
@@ -75,6 +68,11 @@ export class BaseAccount {
75
68
  toJSON() {
76
69
  throw new Error("Not implemented");
77
70
  }
71
+ /** A method that wraps any signer interaction, this allows the account to wait for unlock or queue requests */
72
+ operation(operation) {
73
+ // If the queue is enabled, wait our turn in the queue
74
+ return this.waitForQueue(operation);
75
+ }
78
76
  /** Adds the common fields to the serialized output of a toJSON method */
79
77
  saveCommonFields(json) {
80
78
  return { ...json, id: this.id, pubkey: this.pubkey, metadata: this.metadata, type: this.type };
@@ -89,36 +87,26 @@ export class BaseAccount {
89
87
  }
90
88
  /** Gets the pubkey from the signer */
91
89
  getPublicKey() {
92
- const result = this.signer.getPublicKey();
93
- if (result instanceof Promise)
94
- return result.then((pubkey) => {
95
- if (this.pubkey !== pubkey)
96
- throw new SignerMismatchError("Account signer mismatch");
97
- return pubkey;
98
- });
99
- else {
90
+ return this.operation(async () => {
91
+ const result = await this.signer.getPublicKey();
100
92
  if (this.pubkey !== result)
101
93
  throw new SignerMismatchError("Account signer mismatch");
102
94
  return result;
103
- }
95
+ });
104
96
  }
105
97
  /** sign the event and make sure its signed with the correct pubkey */
106
98
  signEvent(template) {
99
+ // If the template does not have a pubkey, set it to the accounts pubkey
107
100
  if (!Reflect.has(template, "pubkey"))
108
101
  Reflect.set(template, "pubkey", this.pubkey);
109
- return this.waitForLock(() => {
110
- const result = this.signer.signEvent(template);
111
- if (result instanceof Promise)
112
- return result.then((signed) => {
113
- if (signed.pubkey !== this.pubkey)
114
- throw new SignerMismatchError("Signer signed with wrong pubkey");
115
- return signed;
116
- });
117
- else {
118
- if (result.pubkey !== this.pubkey)
119
- throw new SignerMismatchError("Signer signed with wrong pubkey");
120
- return result;
121
- }
102
+ return this.operation(async () => {
103
+ const id = getEventHash(template);
104
+ const result = await this.signer.signEvent(template);
105
+ if (result.pubkey !== this.pubkey)
106
+ throw new SignerMismatchError("Signer signed with wrong pubkey");
107
+ if (result.id !== id)
108
+ throw new SignerMismatchError("Signer modified event");
109
+ return result;
122
110
  });
123
111
  }
124
112
  /** Aborts all pending requests in the queue */
@@ -139,16 +127,16 @@ export class BaseAccount {
139
127
  this.abort = null;
140
128
  }
141
129
  }
142
- waitForLock(fn) {
130
+ waitForQueue(operation) {
143
131
  if (this.disableQueue)
144
- return fn();
132
+ return operation();
145
133
  // if there is already a pending request, wait for it
146
134
  if (this.lock && this.abort) {
147
135
  // create a new promise that runs after the lock
148
136
  const p = wrapInSignal(this.lock.then(() => {
149
137
  // if the abort signal is triggered, don't call the signer
150
138
  this.abort?.signal.throwIfAborted();
151
- return fn();
139
+ return operation();
152
140
  }), this.abort.signal);
153
141
  // set the lock the new promise that ignores errors
154
142
  this.lock = p.catch(() => { }).finally(this.reduceQueue.bind(this));
@@ -156,7 +144,7 @@ export class BaseAccount {
156
144
  return p;
157
145
  }
158
146
  else {
159
- const result = fn();
147
+ const result = operation();
160
148
  // if the result is async, set the new lock
161
149
  if (result instanceof Promise) {
162
150
  this.abort = new AbortController();
@@ -7,4 +7,9 @@ export declare class ExtensionAccount<Metadata extends unknown> extends BaseAcco
7
7
  constructor(pubkey: string, signer: ExtensionSigner);
8
8
  toJSON(): SerializedAccount<void, Metadata>;
9
9
  static fromJSON<Metadata extends unknown>(json: SerializedAccount<void, Metadata>): ExtensionAccount<Metadata>;
10
+ /**
11
+ * Creates a new account from the NIP-07 extension
12
+ * @throws {ExtensionMissingError} if the extension is not installed
13
+ */
14
+ static fromExtension(): Promise<ExtensionAccount<void>>;
10
15
  }
@@ -16,4 +16,13 @@ export class ExtensionAccount extends BaseAccount {
16
16
  const account = new ExtensionAccount(json.pubkey, new ExtensionSigner());
17
17
  return super.loadCommonFields(account, json);
18
18
  }
19
+ /**
20
+ * Creates a new account from the NIP-07 extension
21
+ * @throws {ExtensionMissingError} if the extension is not installed
22
+ */
23
+ static async fromExtension() {
24
+ const signer = new ExtensionSigner();
25
+ const pubkey = await signer.getPublicKey();
26
+ return new ExtensionAccount(pubkey, signer);
27
+ }
19
28
  }
@@ -4,16 +4,17 @@ import { SerializedAccount } from "../types.js";
4
4
  export type PasswordAccountSignerData = {
5
5
  ncryptsec: string;
6
6
  };
7
- export declare class PasswordAccount<Metadata extends unknown> extends BaseAccount<PasswordSigner, PasswordAccountSignerData, Metadata> {
7
+ export declare class PasswordAccount<Metadata extends unknown = unknown> extends BaseAccount<PasswordSigner, PasswordAccountSignerData, Metadata> {
8
8
  static readonly type = "ncryptsec";
9
9
  get unlocked(): boolean;
10
10
  /** called when PasswordAccount.unlock is called without a password */
11
- static requestUnlockPassword(_account: PasswordAccount<any>): Promise<string>;
11
+ static requestUnlockPassword?: (account: PasswordAccount<any>) => Promise<string>;
12
12
  /**
13
13
  * Attempt to unlock the signer with a password
14
14
  * @throws
15
15
  */
16
16
  unlock(password?: string): Promise<void>;
17
+ protected operation<T extends unknown>(operation: () => Promise<T>): Promise<T>;
17
18
  toJSON(): SerializedAccount<PasswordAccountSignerData, Metadata>;
18
19
  static fromJSON<Metadata extends unknown>(json: SerializedAccount<PasswordAccountSignerData, Metadata>): PasswordAccount<Metadata>;
19
20
  /** Creates a new PasswordAccount from a ncryptsec string */
@@ -6,17 +6,29 @@ export class PasswordAccount extends BaseAccount {
6
6
  return this.signer.unlocked;
7
7
  }
8
8
  /** called when PasswordAccount.unlock is called without a password */
9
- static async requestUnlockPassword(_account) {
10
- throw new Error("Cant unlock PasswordAccount without a password. either pass one in or set PasswordAccount.requestUnlockPassword");
11
- }
9
+ static requestUnlockPassword;
12
10
  /**
13
11
  * Attempt to unlock the signer with a password
14
12
  * @throws
15
13
  */
16
14
  async unlock(password) {
17
- password = password || (await PasswordAccount.requestUnlockPassword(this));
15
+ if (!password) {
16
+ if (!PasswordAccount.requestUnlockPassword)
17
+ throw new Error("Cant unlock PasswordAccount without a password. either pass one in or set PasswordAccount.requestUnlockPassword");
18
+ password = await PasswordAccount.requestUnlockPassword(this);
19
+ }
18
20
  await this.signer.unlock(password);
19
21
  }
22
+ operation(operation) {
23
+ // If the account is not unlocked, wait for the unlock password to be provided
24
+ if (!this.unlocked) {
25
+ if (!PasswordAccount.requestUnlockPassword)
26
+ throw new Error("Account is locked and there is no requestUnlockPassword method");
27
+ return this.unlock().then(() => super.operation(operation));
28
+ }
29
+ else
30
+ return super.operation(operation);
31
+ }
20
32
  toJSON() {
21
33
  if (!this.signer.ncryptsec)
22
34
  throw new Error("Cant save account without ncryptsec");
@@ -1,3 +1,4 @@
1
+ import { normalizeToPubkey } from "applesauce-core/helpers/pointers";
1
2
  import { ReadonlySigner } from "applesauce-signers/signers/readonly-signer";
2
3
  import { BaseAccount } from "../account.js";
3
4
  /** An account that cannot sign or encrypt anything */
@@ -15,6 +16,6 @@ export class ReadonlyAccount extends BaseAccount {
15
16
  /** Creates a ReadonlyAccount from a hex public key or NIP-19 npub */
16
17
  static fromPubkey(pubkey) {
17
18
  const signer = ReadonlySigner.fromPubkey(pubkey);
18
- return new ReadonlyAccount(signer.getPublicKey(), signer);
19
+ return new ReadonlyAccount(normalizeToPubkey(pubkey), signer);
19
20
  }
20
21
  }
package/dist/manager.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Nip07Interface } from "applesauce-signers";
1
+ import { ISigner } from "applesauce-signers";
2
2
  import { BehaviorSubject } from "rxjs";
3
3
  import { IAccount, IAccountConstructor, SerializedAccount } from "./types.js";
4
4
  export declare class AccountManager<Metadata extends unknown = any> {
@@ -8,16 +8,16 @@ export declare class AccountManager<Metadata extends unknown = any> {
8
8
  accounts$: BehaviorSubject<IAccount<any, any, Metadata>[]>;
9
9
  get accounts(): IAccount<any, any, Metadata>[];
10
10
  /** Proxy signer for currently active account */
11
- signer: Nip07Interface;
11
+ signer: ISigner;
12
12
  /** Disable request queueing for any accounts added to this manager */
13
13
  disableQueue?: boolean;
14
14
  constructor();
15
15
  /** Add account type class */
16
- registerType<S extends Nip07Interface>(accountType: IAccountConstructor<S, any, Metadata>): void;
16
+ registerType<S extends ISigner>(accountType: IAccountConstructor<S, any, Metadata>): void;
17
17
  /** Remove account type */
18
18
  unregisterType(type: string): void;
19
19
  /** gets an account in the manager */
20
- getAccount<Signer extends Nip07Interface>(id: string | IAccount<Signer, any, Metadata>): IAccount<Signer, any, Metadata> | undefined;
20
+ getAccount<Signer extends ISigner>(id: string | IAccount<Signer, any, Metadata>): IAccount<Signer, any, Metadata> | undefined;
21
21
  /** Return the first account for a pubkey */
22
22
  getAccountForPubkey(pubkey: string): IAccount<any, any, Metadata> | undefined;
23
23
  /** Returns all accounts for a pubkey */
@@ -1,21 +1,21 @@
1
- import { Nip07Interface } from "applesauce-signers";
1
+ import { ISigner } from "applesauce-signers";
2
2
  import { EventTemplate, NostrEvent } from "nostr-tools";
3
3
  import { Observable } from "rxjs";
4
4
  /** A signer class that proxies requests to another signer that isn't created yet */
5
- export declare class ProxySigner<T extends Nip07Interface> implements Nip07Interface {
5
+ export declare class ProxySigner<T extends ISigner> implements ISigner {
6
6
  protected upstream: Observable<T | undefined>;
7
7
  protected error?: string | undefined;
8
8
  private _signer;
9
9
  protected get signer(): T;
10
10
  get nip04(): {
11
- encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
12
- decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
11
+ encrypt: (pubkey: string, plaintext: string) => Promise<string>;
12
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
13
13
  };
14
14
  get nip44(): {
15
- encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
16
- decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
15
+ encrypt: (pubkey: string, plaintext: string) => Promise<string>;
16
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
17
17
  };
18
18
  constructor(upstream: Observable<T | undefined>, error?: string | undefined);
19
- signEvent(template: EventTemplate): Promise<NostrEvent> | NostrEvent;
20
- getPublicKey(): Promise<string> | string;
19
+ signEvent(template: EventTemplate): Promise<NostrEvent>;
20
+ getPublicKey(): Promise<string>;
21
21
  }
@@ -23,10 +23,10 @@ export class ProxySigner {
23
23
  this.error = error;
24
24
  this.upstream.subscribe((signer) => (this._signer = signer));
25
25
  }
26
- signEvent(template) {
26
+ async signEvent(template) {
27
27
  return this.signer.signEvent(template);
28
28
  }
29
- getPublicKey() {
29
+ async getPublicKey() {
30
30
  return this.signer.getPublicKey();
31
31
  }
32
32
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Nip07Interface } from "applesauce-signers";
1
+ import { ISigner } from "applesauce-signers";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  export type EventTemplate = {
4
4
  kind: number;
@@ -20,7 +20,7 @@ export type SerializedAccount<SignerData, Metadata extends unknown> = {
20
20
  metadata?: Metadata;
21
21
  };
22
22
  /** An interface for an account */
23
- export interface IAccount<Signer extends Nip07Interface = Nip07Interface, SignerData = any, Metadata extends unknown = any> extends Nip07Interface {
23
+ export interface IAccount<Signer extends ISigner = ISigner, SignerData = any, Metadata extends unknown = any> extends ISigner {
24
24
  id: string;
25
25
  name?: string;
26
26
  pubkey: string;
@@ -31,7 +31,7 @@ export interface IAccount<Signer extends Nip07Interface = Nip07Interface, Signer
31
31
  toJSON(): SerializedAccount<SignerData, Metadata>;
32
32
  }
33
33
  /** A constructor for an account */
34
- export interface IAccountConstructor<Signer extends Nip07Interface, SignerData, Metadata extends unknown> {
34
+ export interface IAccountConstructor<Signer extends ISigner, SignerData, Metadata extends unknown> {
35
35
  readonly type: string;
36
36
  new (pubkey: string, signer: Signer): IAccount<Signer, SignerData, Metadata>;
37
37
  fromJSON(json: SerializedAccount<SignerData, Metadata>): IAccount<Signer, SignerData, Metadata>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-accounts",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "A simple nostr account management system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,7 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@noble/hashes": "^1.7.1",
36
- "applesauce-signers": "^2.0.0",
36
+ "applesauce-signers": "^3.0.0",
37
+ "applesauce-core": "^3.0.0",
37
38
  "nanoid": "^5.1.5",
38
39
  "nostr-tools": "^2.13",
39
40
  "rxjs": "^7.8.1"