@konemono/nostr-login 1.7.50 → 1.7.52

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konemono/nostr-login",
3
- "version": "1.7.50",
3
+ "version": "1.7.52",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1,106 @@
1
+ import { NDKUser } from '@nostr-dev-kit/ndk';
2
+ import { Signer } from './Nostr';
3
+
4
+ export class AmberDirectSigner implements Signer {
5
+ private _pubkey: string = '';
6
+
7
+ constructor(pubkey?: string) {
8
+ this._pubkey = pubkey || '';
9
+ }
10
+
11
+ get pubkey() {
12
+ return this._pubkey;
13
+ }
14
+
15
+ public set pubkey(v: string) {
16
+ this._pubkey = v;
17
+ }
18
+
19
+ nip04 = {
20
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
21
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
22
+ };
23
+
24
+ nip44 = {
25
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
26
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
27
+ };
28
+
29
+ async signEvent(event: any): Promise<any> {
30
+ const id = Math.random().toString(36).substring(7);
31
+ const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
32
+ window.location.href = url;
33
+ // This will never resolve because of page reload
34
+ return new Promise(() => {});
35
+ }
36
+
37
+ async encrypt04(pubkey: string, plaintext: string): Promise<string> {
38
+ const id = Math.random().toString(36).substring(7);
39
+ const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
40
+ window.location.href = url;
41
+ return new Promise(() => {});
42
+ }
43
+
44
+ async decrypt04(pubkey: string, ciphertext: string): Promise<string> {
45
+ const id = Math.random().toString(36).substring(7);
46
+ const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
47
+ window.location.href = url;
48
+ return new Promise(() => {});
49
+ }
50
+
51
+ async encrypt44(pubkey: string, plaintext: string): Promise<string> {
52
+ const id = Math.random().toString(36).substring(7);
53
+ const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
54
+ window.location.href = url;
55
+ return new Promise(() => {});
56
+ }
57
+
58
+ async decrypt44(pubkey: string, ciphertext: string): Promise<string> {
59
+ const id = Math.random().toString(36).substring(7);
60
+ const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
61
+ window.location.href = url;
62
+ return new Promise(() => {});
63
+ }
64
+
65
+ public async getPublicKey(): Promise<string> {
66
+ const id = Math.random().toString(36).substring(7);
67
+ const url = this.generateUrl('', 'get_public_key', id);
68
+ window.location.href = url;
69
+ return new Promise(() => {});
70
+ }
71
+
72
+ private generateUrl(content: string, type: string, id: string, recipient?: string): string {
73
+ const callbackUrl = new URL(window.location.href);
74
+ callbackUrl.searchParams.set('amberType', type);
75
+ callbackUrl.searchParams.set('amberId', id);
76
+
77
+ const params = new URLSearchParams();
78
+ params.set('type', type);
79
+ params.set('id', id);
80
+ params.set('callbackUrl', callbackUrl.toString());
81
+ if (this._pubkey) params.set('pubkey', this._pubkey);
82
+ if (recipient) params.set('pubkey', recipient); // Amber uses pubkey param for recipient in encrypt/decrypt
83
+
84
+ return `nostrsigner:${encodeURIComponent(content)}?${params.toString()}`;
85
+ }
86
+
87
+ public static parseResponse(): { type: string; id: string; result: string } | null {
88
+ const params = new URLSearchParams(window.location.search);
89
+ const type = params.get('amberType');
90
+ const id = params.get('amberId');
91
+ const result = params.get('signature') || params.get('result'); // Amber uses signature for events, result for others?
92
+
93
+ if (type && id && result) {
94
+ // Clean up URL
95
+ const newUrl = new URL(window.location.href);
96
+ newUrl.searchParams.delete('amberType');
97
+ newUrl.searchParams.delete('amberId');
98
+ newUrl.searchParams.delete('signature');
99
+ newUrl.searchParams.delete('result');
100
+ window.history.replaceState({}, '', newUrl.toString());
101
+
102
+ return { type, id, result };
103
+ }
104
+ return null;
105
+ }
106
+ }
@@ -9,6 +9,8 @@ import { Signer } from './Nostr';
9
9
  import { Nip44 } from '../utils/nip44';
10
10
  import { IframeNostrRpc, Nip46Signer, ReadyListener } from './Nip46';
11
11
  import { PrivateKeySigner } from './Signer';
12
+ import { AmberDirectSigner } from './AmberDirectSigner';
13
+
12
14
 
13
15
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
14
16
  const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
@@ -28,6 +30,12 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
28
30
  link: '<nostrconnect>',
29
31
  relays: DEFAULT_NOSTRCONNECT_RELAYS,
30
32
  },
33
+ {
34
+ name: 'Amber (Direct)',
35
+ img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
36
+ link: 'amber',
37
+ relays: DEFAULT_NOSTRCONNECT_RELAYS,
38
+ },
31
39
  {
32
40
  name: 'Other key stores',
33
41
  img: '',
@@ -40,6 +48,7 @@ class AuthNostrService extends EventEmitter implements Signer {
40
48
  private ndk: NDK;
41
49
  private profileNdk: NDK;
42
50
  private signer: Nip46Signer | null = null;
51
+ private amberSigner: AmberDirectSigner | null = null;
43
52
  private localSigner: PrivateKeySigner | null = null;
44
53
  private params: NostrParams;
45
54
  private signerPromise?: Promise<void>;
@@ -82,6 +91,31 @@ class AuthNostrService extends EventEmitter implements Signer {
82
91
  encrypt: this.encrypt44.bind(this),
83
92
  decrypt: this.decrypt44.bind(this),
84
93
  };
94
+
95
+ this.checkAmberResponse();
96
+ }
97
+
98
+ private checkAmberResponse() {
99
+ const response = AmberDirectSigner.parseResponse();
100
+ if (response) {
101
+ if (response.type === 'get_public_key') {
102
+ const info: Info = {
103
+ pubkey: response.result,
104
+ authMethod: 'amber' as any,
105
+ };
106
+ this.onAuth('login', info);
107
+ } else {
108
+ // For other types, we might want to store the result in a way
109
+ // that the next call to the same method can return it immediately
110
+ // but for now, we just log it.
111
+ console.log('Amber response', response);
112
+ if (response.type === 'sign_event') {
113
+ // If it's a signed event, we could potentially use it if someone asks for it.
114
+ // But usually the app will re-request signing.
115
+ // A better way would be to have a session-based cache.
116
+ }
117
+ }
118
+ }
85
119
  }
86
120
 
87
121
  public isIframe() {
@@ -137,7 +171,13 @@ class AuthNostrService extends EventEmitter implements Signer {
137
171
  console.log('nostrconnect info', info, link);
138
172
 
139
173
  // non-iframe flow
140
- if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
174
+ if (link && !iframeUrl) {
175
+ if (link === 'amber') {
176
+ const signer = new AmberDirectSigner();
177
+ return (signer as any).getPublicKey(); // will redirect
178
+ }
179
+ window.open(link, '_blank', 'width=400,height=700');
180
+ }
141
181
 
142
182
  // init nip46 signer
143
183
  await this.initSigner(info, { listen: true });
@@ -299,6 +339,12 @@ class AuthNostrService extends EventEmitter implements Signer {
299
339
  await this.endAuth();
300
340
  }
301
341
 
342
+ public async setAmber(info: Info) {
343
+ this.releaseSigner();
344
+ this.amberSigner = new AmberDirectSigner(info.pubkey);
345
+ this.onAuth('login', info);
346
+ }
347
+
302
348
  public async createAccount(nip05: string) {
303
349
  const [name, domain] = nip05.split('@');
304
350
 
@@ -329,6 +375,7 @@ class AuthNostrService extends EventEmitter implements Signer {
329
375
  this.signer = null;
330
376
  this.signerErrCallback?.('cancelled');
331
377
  this.localSigner = null;
378
+ this.amberSigner = null;
332
379
 
333
380
  // disconnect from signer relays
334
381
  for (const r of this.ndk.pool.relays.keys()) {
@@ -678,6 +725,10 @@ class AuthNostrService extends EventEmitter implements Signer {
678
725
  event.pubkey = getPublicKey(this.localSigner.privateKey!);
679
726
  event.id = getEventHash(event);
680
727
  event.sig = await this.localSigner.sign(event);
728
+ } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
729
+ const userInfo = this.params.userInfo!;
730
+ if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
731
+ return this.amberSigner.signEvent(event);
681
732
  } else {
682
733
  event.pubkey = this.signer?.remotePubkey;
683
734
  event.id = getEventHash(event);
@@ -710,6 +761,10 @@ class AuthNostrService extends EventEmitter implements Signer {
710
761
  public async encrypt04(pubkey: string, plaintext: string) {
711
762
  if (this.localSigner) {
712
763
  return this.localSigner.encrypt(new NDKUser({ pubkey }), plaintext);
764
+ } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
765
+ const userInfo = this.params.userInfo!;
766
+ if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
767
+ return this.amberSigner.encrypt04(pubkey, plaintext);
713
768
  } else {
714
769
  return this.signer!.encrypt(new NDKUser({ pubkey }), plaintext);
715
770
  }
@@ -718,6 +773,10 @@ class AuthNostrService extends EventEmitter implements Signer {
718
773
  public async decrypt04(pubkey: string, ciphertext: string) {
719
774
  if (this.localSigner) {
720
775
  return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
776
+ } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
777
+ const userInfo = this.params.userInfo!;
778
+ if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
779
+ return this.amberSigner.decrypt04(pubkey, ciphertext);
721
780
  } else {
722
781
  // decrypt is broken in ndk v2.3.1, and latest
723
782
  // ndk v2.8.1 doesn't allow to override connect easily,
@@ -730,6 +789,10 @@ class AuthNostrService extends EventEmitter implements Signer {
730
789
  public async encrypt44(pubkey: string, plaintext: string) {
731
790
  if (this.localSigner) {
732
791
  return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
792
+ } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
793
+ const userInfo = this.params.userInfo!;
794
+ if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
795
+ return this.amberSigner.encrypt44(pubkey, plaintext);
733
796
  } else {
734
797
  // no support of nip44 in ndk yet
735
798
  return this.codec_call('nip44_encrypt', pubkey, plaintext);
@@ -739,6 +802,10 @@ class AuthNostrService extends EventEmitter implements Signer {
739
802
  public async decrypt44(pubkey: string, ciphertext: string) {
740
803
  if (this.localSigner) {
741
804
  return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
805
+ } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
806
+ const userInfo = this.params.userInfo!;
807
+ if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
808
+ return this.amberSigner.decrypt44(pubkey, ciphertext);
742
809
  } else {
743
810
  // no support of nip44 in ndk yet
744
811
  return this.codec_call('nip44_decrypt', pubkey, ciphertext);
@@ -402,6 +402,9 @@ class ModalManager extends EventEmitter {
402
402
  } else if (userInfo.authMethod === 'extension') {
403
403
  await this.extensionService.trySetExtensionForPubkey(userInfo.pubkey);
404
404
  dialog.close();
405
+ } else if (userInfo.authMethod === ('amber' as any)) {
406
+ this.authNostrService.setAmber(userInfo);
407
+ dialog.close();
405
408
  } else {
406
409
  const input = userInfo.bunkerUrl || userInfo.nip05;
407
410
  if (!input) throw new Error('Bad connect info');