applesauce-accounts 1.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 +11 -7
- package/dist/account.js +29 -38
- package/dist/accounts/extension-account.d.ts +5 -0
- package/dist/accounts/extension-account.js +9 -0
- package/dist/accounts/password-account.d.ts +3 -2
- package/dist/accounts/password-account.js +16 -4
- package/dist/accounts/readonly-account.d.ts +1 -0
- package/dist/accounts/readonly-account.js +4 -1
- package/dist/accounts/simple-account.d.ts +3 -1
- package/dist/accounts/simple-account.js +6 -5
- package/dist/manager.d.ts +4 -4
- package/dist/proxy-signer.d.ts +9 -8
- package/dist/proxy-signer.js +3 -2
- package/dist/types.d.ts +12 -3
- package/package.json +5 -4
- package/dist/__tests__/account.test.d.ts +0 -1
- package/dist/__tests__/account.test.js +0 -125
- package/dist/__tests__/manager.test.d.ts +0 -1
- package/dist/__tests__/manager.test.js +0 -65
package/dist/account.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
5
|
+
/** An error thrown when a signer is used with the wrong pubkey */
|
|
5
6
|
export declare class SignerMismatchError extends Error {
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
+
/** A base class for all accounts */
|
|
9
|
+
export declare class BaseAccount<Signer extends ISigner, SignerData, Metadata extends unknown> implements IAccount<Signer, SignerData, Metadata> {
|
|
8
10
|
pubkey: string;
|
|
9
11
|
signer: Signer;
|
|
10
12
|
id: string;
|
|
@@ -14,18 +16,20 @@ export declare class BaseAccount<Signer extends Nip07Interface, SignerData, Meta
|
|
|
14
16
|
metadata$: BehaviorSubject<Metadata | undefined>;
|
|
15
17
|
get metadata(): Metadata | undefined;
|
|
16
18
|
set metadata(metadata: Metadata);
|
|
17
|
-
get nip04():
|
|
18
|
-
get nip44():
|
|
19
|
+
get nip04(): ISigner["nip04"] | undefined;
|
|
20
|
+
get nip44(): ISigner["nip44"] | undefined;
|
|
19
21
|
constructor(pubkey: string, signer: Signer);
|
|
20
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>;
|
|
21
25
|
/** Adds the common fields to the serialized output of a toJSON method */
|
|
22
26
|
protected saveCommonFields(json: Omit<SerializedAccount<SignerData, Metadata>, "id" | "type" | "metadata" | "pubkey">): SerializedAccount<SignerData, Metadata>;
|
|
23
27
|
/** Sets an accounts id and metadata. NOTE: This should only be used in fromJSON methods */
|
|
24
28
|
static loadCommonFields<T extends IAccount>(account: T, json: SerializedAccount<any, any>): T;
|
|
25
29
|
/** Gets the pubkey from the signer */
|
|
26
|
-
getPublicKey():
|
|
30
|
+
getPublicKey(): Promise<string>;
|
|
27
31
|
/** sign the event and make sure its signed with the correct pubkey */
|
|
28
|
-
signEvent(template: EventTemplate): Promise<NostrEvent
|
|
32
|
+
signEvent(template: EventTemplate): Promise<NostrEvent>;
|
|
29
33
|
/** Aborts all pending requests in the queue */
|
|
30
34
|
abortQueue(reason: Error): void;
|
|
31
35
|
/** internal queue */
|
|
@@ -33,5 +37,5 @@ export declare class BaseAccount<Signer extends Nip07Interface, SignerData, Meta
|
|
|
33
37
|
protected lock: Promise<any> | null;
|
|
34
38
|
protected abort: AbortController | null;
|
|
35
39
|
protected reduceQueue(): void;
|
|
36
|
-
protected
|
|
40
|
+
protected waitForQueue<T extends unknown>(operation: () => Promise<T>): Promise<T>;
|
|
37
41
|
}
|
package/dist/account.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { nanoid } from "nanoid";
|
|
2
2
|
import { BehaviorSubject } from "rxjs";
|
|
3
|
+
import { getEventHash } from "nostr-tools";
|
|
4
|
+
/** Wraps a promise in an abort signal */
|
|
3
5
|
function wrapInSignal(promise, signal) {
|
|
4
6
|
return new Promise((res, rej) => {
|
|
5
7
|
signal.throwIfAborted();
|
|
@@ -21,8 +23,10 @@ function wrapInSignal(promise, signal) {
|
|
|
21
23
|
});
|
|
22
24
|
});
|
|
23
25
|
}
|
|
26
|
+
/** An error thrown when a signer is used with the wrong pubkey */
|
|
24
27
|
export class SignerMismatchError extends Error {
|
|
25
28
|
}
|
|
29
|
+
/** A base class for all accounts */
|
|
26
30
|
export class BaseAccount {
|
|
27
31
|
pubkey;
|
|
28
32
|
signer;
|
|
@@ -44,24 +48,16 @@ export class BaseAccount {
|
|
|
44
48
|
if (!this.signer.nip04)
|
|
45
49
|
return undefined;
|
|
46
50
|
return {
|
|
47
|
-
encrypt: (pubkey, plaintext) =>
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
decrypt: (pubkey, plaintext) => {
|
|
51
|
-
return this.waitForLock(() => this.signer.nip04.decrypt(pubkey, plaintext));
|
|
52
|
-
},
|
|
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)),
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
get nip44() {
|
|
56
56
|
if (!this.signer.nip44)
|
|
57
57
|
return undefined;
|
|
58
58
|
return {
|
|
59
|
-
encrypt: (pubkey, plaintext) =>
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
decrypt: (pubkey, plaintext) => {
|
|
63
|
-
return this.waitForLock(() => this.signer.nip44.decrypt(pubkey, plaintext));
|
|
64
|
-
},
|
|
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)),
|
|
65
61
|
};
|
|
66
62
|
}
|
|
67
63
|
constructor(pubkey, signer) {
|
|
@@ -72,6 +68,11 @@ export class BaseAccount {
|
|
|
72
68
|
toJSON() {
|
|
73
69
|
throw new Error("Not implemented");
|
|
74
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
|
+
}
|
|
75
76
|
/** Adds the common fields to the serialized output of a toJSON method */
|
|
76
77
|
saveCommonFields(json) {
|
|
77
78
|
return { ...json, id: this.id, pubkey: this.pubkey, metadata: this.metadata, type: this.type };
|
|
@@ -86,36 +87,26 @@ export class BaseAccount {
|
|
|
86
87
|
}
|
|
87
88
|
/** Gets the pubkey from the signer */
|
|
88
89
|
getPublicKey() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return result.then((pubkey) => {
|
|
92
|
-
if (this.pubkey !== pubkey)
|
|
93
|
-
throw new SignerMismatchError("Account signer mismatch");
|
|
94
|
-
return pubkey;
|
|
95
|
-
});
|
|
96
|
-
else {
|
|
90
|
+
return this.operation(async () => {
|
|
91
|
+
const result = await this.signer.getPublicKey();
|
|
97
92
|
if (this.pubkey !== result)
|
|
98
93
|
throw new SignerMismatchError("Account signer mismatch");
|
|
99
94
|
return result;
|
|
100
|
-
}
|
|
95
|
+
});
|
|
101
96
|
}
|
|
102
97
|
/** sign the event and make sure its signed with the correct pubkey */
|
|
103
98
|
signEvent(template) {
|
|
99
|
+
// If the template does not have a pubkey, set it to the accounts pubkey
|
|
104
100
|
if (!Reflect.has(template, "pubkey"))
|
|
105
101
|
Reflect.set(template, "pubkey", this.pubkey);
|
|
106
|
-
return this.
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
else {
|
|
115
|
-
if (result.pubkey !== this.pubkey)
|
|
116
|
-
throw new SignerMismatchError("Signer signed with wrong pubkey");
|
|
117
|
-
return result;
|
|
118
|
-
}
|
|
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;
|
|
119
110
|
});
|
|
120
111
|
}
|
|
121
112
|
/** Aborts all pending requests in the queue */
|
|
@@ -136,16 +127,16 @@ export class BaseAccount {
|
|
|
136
127
|
this.abort = null;
|
|
137
128
|
}
|
|
138
129
|
}
|
|
139
|
-
|
|
130
|
+
waitForQueue(operation) {
|
|
140
131
|
if (this.disableQueue)
|
|
141
|
-
return
|
|
132
|
+
return operation();
|
|
142
133
|
// if there is already a pending request, wait for it
|
|
143
134
|
if (this.lock && this.abort) {
|
|
144
135
|
// create a new promise that runs after the lock
|
|
145
136
|
const p = wrapInSignal(this.lock.then(() => {
|
|
146
137
|
// if the abort signal is triggered, don't call the signer
|
|
147
138
|
this.abort?.signal.throwIfAborted();
|
|
148
|
-
return
|
|
139
|
+
return operation();
|
|
149
140
|
}), this.abort.signal);
|
|
150
141
|
// set the lock the new promise that ignores errors
|
|
151
142
|
this.lock = p.catch(() => { }).finally(this.reduceQueue.bind(this));
|
|
@@ -153,7 +144,7 @@ export class BaseAccount {
|
|
|
153
144
|
return p;
|
|
154
145
|
}
|
|
155
146
|
else {
|
|
156
|
-
const result =
|
|
147
|
+
const result = operation();
|
|
157
148
|
// if the result is async, set the new lock
|
|
158
149
|
if (result instanceof Promise) {
|
|
159
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(
|
|
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
|
|
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
|
-
|
|
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");
|
|
@@ -6,5 +6,6 @@ export declare class ReadonlyAccount<Metadata extends unknown> extends BaseAccou
|
|
|
6
6
|
static readonly type = "readonly";
|
|
7
7
|
toJSON(): SerializedAccount<void, Metadata>;
|
|
8
8
|
static fromJSON<Metadata extends unknown>(json: SerializedAccount<void, Metadata>): ReadonlyAccount<Metadata>;
|
|
9
|
+
/** Creates a ReadonlyAccount from a hex public key or NIP-19 npub */
|
|
9
10
|
static fromPubkey(pubkey: string): ReadonlyAccount<unknown>;
|
|
10
11
|
}
|
|
@@ -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 */
|
|
@@ -12,7 +13,9 @@ export class ReadonlyAccount extends BaseAccount {
|
|
|
12
13
|
const account = new ReadonlyAccount(json.pubkey, new ReadonlySigner(json.pubkey));
|
|
13
14
|
return super.loadCommonFields(account, json);
|
|
14
15
|
}
|
|
16
|
+
/** Creates a ReadonlyAccount from a hex public key or NIP-19 npub */
|
|
15
17
|
static fromPubkey(pubkey) {
|
|
16
|
-
|
|
18
|
+
const signer = ReadonlySigner.fromPubkey(pubkey);
|
|
19
|
+
return new ReadonlyAccount(normalizeToPubkey(pubkey), signer);
|
|
17
20
|
}
|
|
18
21
|
}
|
|
@@ -8,6 +8,8 @@ export declare class SimpleAccount<Metadata extends unknown> extends BaseAccount
|
|
|
8
8
|
static readonly type = "nsec";
|
|
9
9
|
toJSON(): SerializedAccount<SimpleAccountSignerData, Metadata>;
|
|
10
10
|
static fromJSON<Metadata extends unknown>(json: SerializedAccount<SimpleAccountSignerData, Metadata>): SimpleAccount<Metadata>;
|
|
11
|
-
|
|
11
|
+
/** Creates a SimpleAccount from a hex private key or NIP-19 nsec */
|
|
12
|
+
static fromKey<Metadata extends unknown>(privateKey: Uint8Array | string): SimpleAccount<Metadata>;
|
|
13
|
+
/** Creates a new SimpleAccount with a random private key */
|
|
12
14
|
static generateNew<Metadata extends unknown>(): SimpleAccount<Metadata>;
|
|
13
15
|
}
|
|
@@ -14,12 +14,13 @@ export class SimpleAccount extends BaseAccount {
|
|
|
14
14
|
const account = new SimpleAccount(json.pubkey, new SimpleSigner(key));
|
|
15
15
|
return super.loadCommonFields(account, json);
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const pubkey = getPublicKey(key);
|
|
21
|
-
return new SimpleAccount(pubkey,
|
|
17
|
+
/** Creates a SimpleAccount from a hex private key or NIP-19 nsec */
|
|
18
|
+
static fromKey(privateKey) {
|
|
19
|
+
const signer = SimpleSigner.fromKey(privateKey);
|
|
20
|
+
const pubkey = getPublicKey(signer.key);
|
|
21
|
+
return new SimpleAccount(pubkey, signer);
|
|
22
22
|
}
|
|
23
|
+
/** Creates a new SimpleAccount with a random private key */
|
|
23
24
|
static generateNew() {
|
|
24
25
|
const key = generateSecretKey();
|
|
25
26
|
return SimpleAccount.fromKey(key);
|
package/dist/manager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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
|
|
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
|
|
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 */
|
package/dist/proxy-signer.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import {
|
|
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 ISigner> implements ISigner {
|
|
5
6
|
protected upstream: Observable<T | undefined>;
|
|
6
7
|
protected error?: string | undefined;
|
|
7
8
|
private _signer;
|
|
8
9
|
protected get signer(): T;
|
|
9
10
|
get nip04(): {
|
|
10
|
-
encrypt: (pubkey: string, plaintext: string) => Promise<string
|
|
11
|
-
decrypt: (pubkey: string, ciphertext: string) => Promise<string
|
|
11
|
+
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
|
12
|
+
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
|
12
13
|
};
|
|
13
14
|
get nip44(): {
|
|
14
|
-
encrypt: (pubkey: string, plaintext: string) => Promise<string
|
|
15
|
-
decrypt: (pubkey: string, ciphertext: string) => Promise<string
|
|
15
|
+
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
|
16
|
+
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
|
16
17
|
};
|
|
17
18
|
constructor(upstream: Observable<T | undefined>, error?: string | undefined);
|
|
18
|
-
signEvent(template: EventTemplate): Promise<NostrEvent
|
|
19
|
-
getPublicKey(): Promise<string
|
|
19
|
+
signEvent(template: EventTemplate): Promise<NostrEvent>;
|
|
20
|
+
getPublicKey(): Promise<string>;
|
|
20
21
|
}
|
package/dist/proxy-signer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** A signer class that proxies requests to another signer that isn't created yet */
|
|
1
2
|
export class ProxySigner {
|
|
2
3
|
upstream;
|
|
3
4
|
error;
|
|
@@ -22,10 +23,10 @@ export class ProxySigner {
|
|
|
22
23
|
this.error = error;
|
|
23
24
|
this.upstream.subscribe((signer) => (this._signer = signer));
|
|
24
25
|
}
|
|
25
|
-
signEvent(template) {
|
|
26
|
+
async signEvent(template) {
|
|
26
27
|
return this.signer.signEvent(template);
|
|
27
28
|
}
|
|
28
|
-
getPublicKey() {
|
|
29
|
+
async getPublicKey() {
|
|
29
30
|
return this.signer.getPublicKey();
|
|
30
31
|
}
|
|
31
32
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISigner } from "applesauce-signers";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
3
|
export type EventTemplate = {
|
|
3
4
|
kind: number;
|
|
4
5
|
content: string;
|
|
5
6
|
tags: string[][];
|
|
6
7
|
created_at: number;
|
|
7
8
|
};
|
|
9
|
+
/** A type for serializing an account */
|
|
8
10
|
export type SerializedAccount<SignerData, Metadata extends unknown> = {
|
|
9
11
|
/** Internal account ID */
|
|
10
12
|
id: string;
|
|
@@ -17,7 +19,8 @@ export type SerializedAccount<SignerData, Metadata extends unknown> = {
|
|
|
17
19
|
/** Extra application specific account metadata */
|
|
18
20
|
metadata?: Metadata;
|
|
19
21
|
};
|
|
20
|
-
|
|
22
|
+
/** An interface for an account */
|
|
23
|
+
export interface IAccount<Signer extends ISigner = ISigner, SignerData = any, Metadata extends unknown = any> extends ISigner {
|
|
21
24
|
id: string;
|
|
22
25
|
name?: string;
|
|
23
26
|
pubkey: string;
|
|
@@ -27,8 +30,14 @@ export interface IAccount<Signer extends Nip07Interface = Nip07Interface, Signer
|
|
|
27
30
|
disableQueue?: boolean;
|
|
28
31
|
toJSON(): SerializedAccount<SignerData, Metadata>;
|
|
29
32
|
}
|
|
30
|
-
|
|
33
|
+
/** A constructor for an account */
|
|
34
|
+
export interface IAccountConstructor<Signer extends ISigner, SignerData, Metadata extends unknown> {
|
|
31
35
|
readonly type: string;
|
|
32
36
|
new (pubkey: string, signer: Signer): IAccount<Signer, SignerData, Metadata>;
|
|
33
37
|
fromJSON(json: SerializedAccount<SignerData, Metadata>): IAccount<Signer, SignerData, Metadata>;
|
|
34
38
|
}
|
|
39
|
+
/** An interface for caching decrypted content of events */
|
|
40
|
+
export interface DecryptionCache {
|
|
41
|
+
getContent(event: NostrEvent): Promise<string>;
|
|
42
|
+
setContent(event: NostrEvent, content: string): Promise<void>;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-accounts",
|
|
3
|
-
"version": "
|
|
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,14 +33,15 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@noble/hashes": "^1.7.1",
|
|
36
|
-
"applesauce-signers": "^
|
|
36
|
+
"applesauce-signers": "^3.0.0",
|
|
37
|
+
"applesauce-core": "^3.0.0",
|
|
37
38
|
"nanoid": "^5.1.5",
|
|
38
|
-
"nostr-tools": "^2.
|
|
39
|
+
"nostr-tools": "^2.13",
|
|
39
40
|
"rxjs": "^7.8.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"typescript": "^5.8.3",
|
|
43
|
-
"vitest": "^3.
|
|
44
|
+
"vitest": "^3.2.3"
|
|
44
45
|
},
|
|
45
46
|
"funding": {
|
|
46
47
|
"type": "lightning",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { SimpleSigner } from "applesauce-signers";
|
|
3
|
-
import { finalizeEvent, generateSecretKey } from "nostr-tools";
|
|
4
|
-
import { BaseAccount } from "../account.js";
|
|
5
|
-
import { SimpleAccount } from "../accounts/simple-account.js";
|
|
6
|
-
describe("BaseAccount", () => {
|
|
7
|
-
describe("request queue", () => {
|
|
8
|
-
let signer;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
signer = new SimpleSigner();
|
|
11
|
-
});
|
|
12
|
-
it("should queue signing requests by default", async () => {
|
|
13
|
-
const account = new BaseAccount(await signer.getPublicKey(), signer);
|
|
14
|
-
let resolve = [];
|
|
15
|
-
vi.spyOn(signer, "signEvent").mockImplementation(() => {
|
|
16
|
-
return new Promise((res) => {
|
|
17
|
-
resolve.push(() => res(finalizeEvent({ kind: 1, content: "mock", created_at: 0, tags: [] }, signer.key)));
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
// make two signing requests
|
|
21
|
-
expect(account.signEvent({ kind: 1, content: "first", created_at: 0, tags: [] })).toEqual(expect.any(Promise));
|
|
22
|
-
expect(account.signEvent({ kind: 1, content: "second", created_at: 0, tags: [] })).toEqual(expect.any(Promise));
|
|
23
|
-
expect(signer.signEvent).toHaveBeenCalledOnce();
|
|
24
|
-
expect(signer.signEvent).toHaveBeenCalledWith(expect.objectContaining({ content: "first" }));
|
|
25
|
-
// resolve first
|
|
26
|
-
resolve.shift()?.();
|
|
27
|
-
// wait next tick
|
|
28
|
-
await new Promise((res) => setTimeout(res, 0));
|
|
29
|
-
expect(signer.signEvent).toHaveBeenCalledTimes(2);
|
|
30
|
-
expect(signer.signEvent).toHaveBeenCalledWith(expect.objectContaining({ content: "second" }));
|
|
31
|
-
// resolve second
|
|
32
|
-
resolve.shift()?.();
|
|
33
|
-
// wait next tick
|
|
34
|
-
await new Promise((res) => setTimeout(res, 0));
|
|
35
|
-
expect(Reflect.get(account, "queueLength")).toBe(0);
|
|
36
|
-
expect(Reflect.get(account, "lock")).toBeNull();
|
|
37
|
-
});
|
|
38
|
-
it("should cancel queue if request throws", () => { });
|
|
39
|
-
it("should not use queueing if its disabled", async () => {
|
|
40
|
-
const account = new BaseAccount(await signer.getPublicKey(), signer);
|
|
41
|
-
account.disableQueue = true;
|
|
42
|
-
let resolve = [];
|
|
43
|
-
vi.spyOn(signer, "signEvent").mockImplementation(() => {
|
|
44
|
-
return new Promise((res) => {
|
|
45
|
-
resolve.push(() => res(finalizeEvent({ kind: 1, content: "mock", created_at: 0, tags: [] }, signer.key)));
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
// make two signing requests
|
|
49
|
-
account.signEvent({ kind: 1, content: "first", created_at: 0, tags: [] });
|
|
50
|
-
account.signEvent({ kind: 1, content: "second", created_at: 0, tags: [] });
|
|
51
|
-
expect(Reflect.get(account, "lock")).toBeNull();
|
|
52
|
-
expect(signer.signEvent).toHaveBeenCalledTimes(2);
|
|
53
|
-
expect(signer.signEvent).toHaveBeenCalledWith(expect.objectContaining({ content: "first" }));
|
|
54
|
-
expect(signer.signEvent).toHaveBeenCalledWith(expect.objectContaining({ content: "second" }));
|
|
55
|
-
// resolve both
|
|
56
|
-
resolve.shift()?.();
|
|
57
|
-
resolve.shift()?.();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe("type", () => {
|
|
61
|
-
it("should return static account type", () => {
|
|
62
|
-
const account = SimpleAccount.fromKey(generateSecretKey());
|
|
63
|
-
expect(account.type).toBe("nsec");
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe("nip04 and nip44", () => {
|
|
67
|
-
it("should return undefined when signer does not support nip04/nip44", () => {
|
|
68
|
-
const signer = {
|
|
69
|
-
getPublicKey: () => "test-pubkey",
|
|
70
|
-
signEvent: () => ({ id: "", pubkey: "test-pubkey", created_at: 0, kind: 1, tags: [], content: "", sig: "" }),
|
|
71
|
-
};
|
|
72
|
-
const account = new BaseAccount("test-pubkey", signer);
|
|
73
|
-
expect(account.nip04).toBeUndefined();
|
|
74
|
-
expect(account.nip44).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
it("should return nip04/nip44 interface when signer supports them", async () => {
|
|
77
|
-
const signer = {
|
|
78
|
-
getPublicKey: () => "test-pubkey",
|
|
79
|
-
signEvent: () => ({ id: "", pubkey: "test-pubkey", created_at: 0, kind: 1, tags: [], content: "", sig: "" }),
|
|
80
|
-
nip04: {
|
|
81
|
-
encrypt: async () => "encrypted",
|
|
82
|
-
decrypt: async () => "decrypted",
|
|
83
|
-
},
|
|
84
|
-
nip44: {
|
|
85
|
-
encrypt: async () => "encrypted",
|
|
86
|
-
decrypt: async () => "decrypted",
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
const account = new BaseAccount("test-pubkey", signer);
|
|
90
|
-
expect(account.nip04).toBeDefined();
|
|
91
|
-
expect(account.nip44).toBeDefined();
|
|
92
|
-
const nip04Result = await account.nip04.encrypt("pubkey", "test");
|
|
93
|
-
expect(nip04Result).toBe("encrypted");
|
|
94
|
-
const nip44Result = await account.nip44.encrypt("pubkey", "test");
|
|
95
|
-
expect(nip44Result).toBe("encrypted");
|
|
96
|
-
});
|
|
97
|
-
it("should reflect changes in signer nip04/nip44 support", () => {
|
|
98
|
-
const signer = {
|
|
99
|
-
getPublicKey: () => "test-pubkey",
|
|
100
|
-
signEvent: () => ({ id: "", pubkey: "test-pubkey", created_at: 0, kind: 1, tags: [], content: "", sig: "" }),
|
|
101
|
-
};
|
|
102
|
-
const account = new BaseAccount("test-pubkey", signer);
|
|
103
|
-
expect(account.nip04).toBeUndefined();
|
|
104
|
-
expect(account.nip44).toBeUndefined();
|
|
105
|
-
// Add nip04 support
|
|
106
|
-
signer.nip04 = {
|
|
107
|
-
encrypt: async () => "encrypted",
|
|
108
|
-
decrypt: async () => "decrypted",
|
|
109
|
-
};
|
|
110
|
-
expect(account.nip04).toBeDefined();
|
|
111
|
-
expect(account.nip44).toBeUndefined();
|
|
112
|
-
// Add nip44 support
|
|
113
|
-
signer.nip44 = {
|
|
114
|
-
encrypt: async () => "encrypted",
|
|
115
|
-
decrypt: async () => "decrypted",
|
|
116
|
-
};
|
|
117
|
-
expect(account.nip04).toBeDefined();
|
|
118
|
-
expect(account.nip44).toBeDefined();
|
|
119
|
-
// Remove nip04 support
|
|
120
|
-
signer.nip04 = undefined;
|
|
121
|
-
expect(account.nip04).toBeUndefined();
|
|
122
|
-
expect(account.nip44).toBeDefined();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { AccountManager } from "../manager.js";
|
|
3
|
-
import { SimpleAccount } from "../accounts/simple-account.js";
|
|
4
|
-
import { generateSecretKey, getPublicKey } from "nostr-tools";
|
|
5
|
-
import { bytesToHex } from "@noble/hashes/utils";
|
|
6
|
-
let manager;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
manager = new AccountManager();
|
|
9
|
-
});
|
|
10
|
-
describe("toJSON", () => {
|
|
11
|
-
it("should return an array of serialized accounts", () => {
|
|
12
|
-
manager.addAccount(SimpleAccount.fromKey(generateSecretKey()));
|
|
13
|
-
manager.setAccountMetadata(manager.accounts[0], { name: "testing" });
|
|
14
|
-
expect(manager.toJSON()).toEqual([
|
|
15
|
-
{
|
|
16
|
-
id: expect.any(String),
|
|
17
|
-
type: "nsec",
|
|
18
|
-
pubkey: expect.any(String),
|
|
19
|
-
metadata: { name: "testing" },
|
|
20
|
-
signer: { key: expect.any(String) },
|
|
21
|
-
},
|
|
22
|
-
]);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
describe("fromJSON", () => {
|
|
26
|
-
it("should recreate accounts", () => {
|
|
27
|
-
const key = generateSecretKey();
|
|
28
|
-
const json = [
|
|
29
|
-
{
|
|
30
|
-
id: "custom-id",
|
|
31
|
-
type: "nsec",
|
|
32
|
-
pubkey: getPublicKey(key),
|
|
33
|
-
metadata: { name: "testing" },
|
|
34
|
-
signer: { key: bytesToHex(key) },
|
|
35
|
-
},
|
|
36
|
-
];
|
|
37
|
-
manager.registerType(SimpleAccount);
|
|
38
|
-
manager.fromJSON(json);
|
|
39
|
-
expect(manager.getAccount("custom-id")).toBeInstanceOf(SimpleAccount);
|
|
40
|
-
expect(manager.getAccountForPubkey(getPublicKey(key))).toBeInstanceOf(SimpleAccount);
|
|
41
|
-
expect(manager.getAccountMetadata("custom-id")).toEqual({ name: "testing" });
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe("signer", () => {
|
|
45
|
-
it("should proxy active account", async () => {
|
|
46
|
-
const account = SimpleAccount.generateNew();
|
|
47
|
-
manager.addAccount(account);
|
|
48
|
-
manager.setActive(account);
|
|
49
|
-
expect(await manager.signer.getPublicKey()).toBe(getPublicKey(account.signer.key));
|
|
50
|
-
});
|
|
51
|
-
it("should throw if there is no active account", () => {
|
|
52
|
-
expect(() => {
|
|
53
|
-
manager.signer.getPublicKey();
|
|
54
|
-
}).toThrow("No active account");
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe("removeAccount", () => {
|
|
58
|
-
it("should clear active account if removed account was active", () => {
|
|
59
|
-
const account = SimpleAccount.generateNew();
|
|
60
|
-
manager.addAccount(account);
|
|
61
|
-
manager.setActive(account);
|
|
62
|
-
manager.removeAccount(account);
|
|
63
|
-
expect(manager.active).toBeUndefined();
|
|
64
|
-
});
|
|
65
|
-
});
|