@konemono/nostr-login 1.7.70 → 1.9.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.
@@ -1,12 +1,11 @@
1
1
  import { Info, RecentType } from 'nostr-login-components/dist/types/types';
2
- import NDK, { NDKSigner } from '@nostr-dev-kit/ndk';
3
2
  import { NostrLoginOptions } from '../types';
4
3
  export declare const localStorageSetItem: (key: string, value: string) => void;
5
4
  export declare const localStorageGetItem: (key: string) => any;
6
5
  export declare const localStorageRemoveItem: (key: string) => void;
7
- export declare const fetchProfile: (info: Info, profileNdk: NDK) => Promise<import("@nostr-dev-kit/ndk").NDKUserProfile | null>;
6
+ export declare const fetchProfile: (info: Info, profileNdkOrRelays?: any) => Promise<any>;
8
7
  export declare const prepareSignupRelays: (signupRelays?: string) => string[];
9
- export declare const createProfile: (info: Info, profileNdk: NDK, signer: NDKSigner, signupRelays?: string, outboxRelays?: string[]) => Promise<void>;
8
+ export declare const createProfile: (info: Info, signer: any, signupRelays?: string, outboxRelays?: string[]) => Promise<void>;
10
9
  export declare const bunkerUrlToInfo: (bunkerUrl: string, sk?: string) => Info;
11
10
  export declare const isBunkerUrl: (value: string) => boolean;
12
11
  export declare const getBunkerUrl: (value: string, optionsModal: NostrLoginOptions) => Promise<string>;
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@konemono/nostr-login",
3
- "version": "1.7.70",
3
+ "version": "1.9.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
8
  "scripts": {
9
9
  "build": "rollup -c",
10
- "format": "npx prettier --write src"
10
+ "format": "npx prettier --write src",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest"
11
13
  },
12
14
  "author": "a-fralou",
13
15
  "dependencies": {
14
- "@nostr-dev-kit/ndk": "^2.3.1",
15
16
  "nostr-tools": "^1.17.0",
16
17
  "tseep": "^1.2.1"
17
18
  },
@@ -22,7 +23,9 @@
22
23
  "nostr-login-components": "^1.0.3",
23
24
  "prettier": "^3.2.2",
24
25
  "rollup": "^4.9.6",
25
- "rollup-plugin-typescript2": "^0.36.0"
26
+ "rollup-plugin-typescript2": "^0.36.0",
27
+ "vitest": "^1.0.0",
28
+ "@types/node": "^18.0.0"
26
29
  },
27
30
  "license": "MIT"
28
31
  }
package/src/index.ts CHANGED
@@ -149,7 +149,7 @@ export class NostrLoginInitializer {
149
149
 
150
150
  private openPopup(url: string) {
151
151
  if (url.startsWith('nostrsigner:')) {
152
- window.location.href = url;
152
+ window.open(url, '_blank', 'width=400,height=600');
153
153
  } else {
154
154
  this.popupManager.openPopup(url);
155
155
  }
@@ -2,184 +2,227 @@ import { AmberResponse } from '../types';
2
2
  import { Signer } from './Nostr';
3
3
 
4
4
  export class AmberDirectSigner implements Signer {
5
- private _pubkey: string;
6
- private static pendingResolves: Map<string, (result: string) => void> = new Map();
7
-
8
- constructor(pubkey: string = '') {
9
- this._pubkey = pubkey;
10
- }
11
-
12
- get pubkey() {
13
- return this._pubkey;
14
- }
15
-
16
- set pubkey(v: string) {
17
- this._pubkey = v;
18
- }
19
-
20
- public nip04 = {
21
- encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
22
- decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
5
+ private _pubkey: string;
6
+ private static pendingResolves: Map<string, { resolve: (result: string) => void; timer: NodeJS.Timeout | null }> = new Map();
7
+
8
+ constructor(pubkey: string = '') {
9
+ this._pubkey = pubkey;
10
+ }
11
+
12
+ get pubkey() {
13
+ return this._pubkey;
14
+ }
15
+
16
+ set pubkey(v: string) {
17
+ this._pubkey = v;
18
+ }
19
+
20
+ public nip04 = {
21
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
22
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
23
+ };
24
+
25
+ public nip44 = {
26
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
27
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
28
+ };
29
+
30
+ public async getPublicKey(id?: string): Promise<string> {
31
+ id = id || Math.random().toString(36).substring(7);
32
+ const url = this.generateUrl('', 'get_public_key', id);
33
+ console.log('Amber redirecting to:', url);
34
+ window.open(url, '_blank', 'width=400,height=600');
35
+ return new Promise((resolve, reject) => {
36
+ const timer = setTimeout(() => {
37
+ AmberDirectSigner.pendingResolves.delete(id!);
38
+ reject(new Error('AmberDirectSigner timeout'));
39
+ }, 20000);
40
+ AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
41
+ });
42
+ }
43
+
44
+ public async signEvent(event: any, id?: string): Promise<any> {
45
+ id = id || Math.random().toString(36).substring(7);
46
+ const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
47
+ console.log('Amber redirecting to:', url);
48
+ window.open(url, '_blank', 'width=400,height=600');
49
+ return new Promise((resolve, reject) => {
50
+ const timer = setTimeout(() => {
51
+ AmberDirectSigner.pendingResolves.delete(id!);
52
+ reject(new Error('AmberDirectSigner timeout'));
53
+ }, 20000);
54
+ AmberDirectSigner.pendingResolves.set(id!, {
55
+ resolve: (result: string) => {
56
+ try {
57
+ resolve(JSON.parse(result));
58
+ } catch (e) {
59
+ resolve(result as any);
60
+ }
61
+ },
62
+ timer,
63
+ });
64
+ });
65
+ }
66
+
67
+ public async encrypt04(pubkey: string, plaintext: string, id?: string): Promise<string> {
68
+ id = id || Math.random().toString(36).substring(7);
69
+ const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
70
+ console.log('Amber redirecting to:', url);
71
+ window.open(url, '_blank', 'width=400,height=600');
72
+ return new Promise((resolve, reject) => {
73
+ const timer = setTimeout(() => {
74
+ AmberDirectSigner.pendingResolves.delete(id!);
75
+ reject(new Error('AmberDirectSigner timeout'));
76
+ }, 20000);
77
+ AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
78
+ });
79
+ }
80
+
81
+ public async decrypt04(pubkey: string, ciphertext: string, id?: string): Promise<string> {
82
+ id = id || Math.random().toString(36).substring(7);
83
+ const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
84
+ console.log('Amber redirecting to:', url);
85
+ window.open(url, '_blank', 'width=400,height=600');
86
+ return new Promise((resolve, reject) => {
87
+ const timer = setTimeout(() => {
88
+ AmberDirectSigner.pendingResolves.delete(id!);
89
+ reject(new Error('AmberDirectSigner timeout'));
90
+ }, 20000);
91
+ AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
92
+ });
93
+ }
94
+
95
+ public async encrypt44(pubkey: string, plaintext: string, id?: string): Promise<string> {
96
+ id = id || Math.random().toString(36).substring(7);
97
+ const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
98
+ console.log('Amber redirecting to:', url);
99
+ window.open(url, '_blank', 'width=400,height=600');
100
+ return new Promise((resolve, reject) => {
101
+ const timer = setTimeout(() => {
102
+ AmberDirectSigner.pendingResolves.delete(id!);
103
+ reject(new Error('AmberDirectSigner timeout'));
104
+ }, 20000);
105
+ AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
106
+ });
107
+ }
108
+
109
+ public async decrypt44(pubkey: string, ciphertext: string, id?: string): Promise<string> {
110
+ id = id || Math.random().toString(36).substring(7);
111
+ const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
112
+ console.log('Amber redirecting to:', url);
113
+ window.open(url, '_blank', 'width=400,height=600');
114
+ return new Promise((resolve, reject) => {
115
+ const timer = setTimeout(() => {
116
+ AmberDirectSigner.pendingResolves.delete(id!);
117
+ reject(new Error('AmberDirectSigner timeout'));
118
+ }, 20000);
119
+ AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
120
+ });
121
+ }
122
+
123
+ public generateUrl(content: string, type: string, id: string, pubkey?: string): string {
124
+ const callbackUrl = window.location.href.split('#')[0].split('?')[0];
125
+ const encodedContent = encodeURIComponent(content || '');
126
+ const encodedCallback = encodeURIComponent(callbackUrl);
127
+
128
+ localStorage.setItem('amber_last_type', type);
129
+ localStorage.setItem('amber_last_id', id);
130
+ localStorage.setItem('amber_last_timestamp', Date.now().toString());
131
+
132
+ // NIP-55準拠: nostrsigner: スキーム
133
+ let url = `nostrsigner:${encodedContent}`;
134
+ const params = new URLSearchParams();
135
+ params.append('compressionType', 'none');
136
+ params.append('returnType', 'signature');
137
+ params.append('type', type);
138
+ params.append('callbackUrl', callbackUrl);
139
+
140
+ // 三重の冗長性でアプリ名を渡す
141
+ const appName = document.title || window.location.hostname;
142
+ params.append('name', appName);
143
+ params.append('appName', appName);
144
+ params.append('app', appName);
145
+
146
+ // NIP-46互換のmetadata形式も追加
147
+ const metadata = {
148
+ name: appName,
149
+ url: window.location.origin,
150
+ description: 'Nostr Login provided by nostr-login library',
23
151
  };
152
+ params.append('metadata', JSON.stringify(metadata));
24
153
 
25
- public nip44 = {
26
- encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
27
- decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
28
- };
29
-
30
- public async getPublicKey(id?: string): Promise<string> {
31
- id = id || Math.random().toString(36).substring(7);
32
- const url = this.generateUrl('', 'get_public_key', id);
33
- console.log('Amber redirecting to:', url);
34
- window.location.assign(url);
35
- return new Promise((resolve) => {
36
- AmberDirectSigner.pendingResolves.set(id!, resolve);
37
- });
38
- }
154
+ if (pubkey) params.append('pubkey', pubkey);
155
+ if (this._pubkey) params.append('current_user', this._pubkey);
39
156
 
40
- public async signEvent(event: any, id?: string): Promise<any> {
41
- id = id || Math.random().toString(36).substring(7);
42
- const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
43
- console.log('Amber redirecting to:', url);
44
- window.location.assign(url);
45
- return new Promise((resolve) => {
46
- AmberDirectSigner.pendingResolves.set(id!, (result: string) => {
47
- resolve(JSON.parse(result));
48
- });
49
- });
50
- }
157
+ return `${url}?${params.toString()}`;
158
+ }
51
159
 
52
- public async encrypt04(pubkey: string, plaintext: string, id?: string): Promise<string> {
53
- id = id || Math.random().toString(36).substring(7);
54
- const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
55
- console.log('Amber redirecting to:', url);
56
- window.location.assign(url);
57
- return new Promise((resolve) => {
58
- AmberDirectSigner.pendingResolves.set(id!, resolve);
59
- });
60
- }
160
+ // AmberDirectSigner.ts parseResponse を拡張
61
161
 
62
- public async decrypt04(pubkey: string, ciphertext: string, id?: string): Promise<string> {
63
- id = id || Math.random().toString(36).substring(7);
64
- const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
65
- console.log('Amber redirecting to:', url);
66
- window.location.assign(url);
67
- return new Promise((resolve) => {
68
- AmberDirectSigner.pendingResolves.set(id!, resolve);
69
- });
70
- }
162
+ static parseResponse(): AmberResponse | null {
163
+ const url = new URL(window.location.href);
71
164
 
72
- public async encrypt44(pubkey: string, plaintext: string, id?: string): Promise<string> {
73
- id = id || Math.random().toString(36).substring(7);
74
- const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
75
- console.log('Amber redirecting to:', url);
76
- window.location.assign(url);
77
- return new Promise((resolve) => {
78
- AmberDirectSigner.pendingResolves.set(id!, resolve);
79
- });
80
- }
165
+ // パターン1: NIP-55標準 (?event=...)
166
+ let result = url.searchParams.get('event');
81
167
 
82
- public async decrypt44(pubkey: string, ciphertext: string, id?: string): Promise<string> {
83
- id = id || Math.random().toString(36).substring(7);
84
- const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
85
- console.log('Amber redirecting to:', url);
86
- window.location.assign(url);
87
- return new Promise((resolve) => {
88
- AmberDirectSigner.pendingResolves.set(id!, resolve);
89
- });
168
+ // パターン2: パス末尾にhexId (/<hexId>)
169
+ if (!result) {
170
+ const pathParts = url.pathname.split('/').filter(Boolean);
171
+ if (pathParts.length > 0) {
172
+ const lastPart = pathParts[pathParts.length - 1];
173
+ // hexIdっぽい(64文字の16進数)
174
+ if (/^[0-9a-f]{64}$/i.test(lastPart)) {
175
+ result = lastPart;
176
+ }
177
+ }
90
178
  }
91
179
 
92
-
93
- public generateUrl(content: string, type: string, id: string, pubkey?: string): string {
94
- const callbackUrl = window.location.href.split('#')[0].split('?')[0];
95
- const encodedContent = encodeURIComponent(content || '');
96
- const encodedCallback = encodeURIComponent(callbackUrl);
97
-
98
- localStorage.setItem('amber_last_type', type);
99
- localStorage.setItem('amber_last_id', id);
100
- localStorage.setItem('amber_last_timestamp', Date.now().toString());
101
-
102
- // NIP-55準拠: nostrsigner: スキーム
103
- let url = `nostrsigner:${encodedContent}`;
104
- const params = new URLSearchParams();
105
- params.append('compressionType', 'none');
106
- params.append('returnType', 'signature');
107
- params.append('type', type);
108
- params.append('callbackUrl', callbackUrl);
109
-
110
- // 三重の冗長性でアプリ名を渡す
111
- const appName = document.title || window.location.hostname;
112
- params.append('name', appName);
113
- params.append('appName', appName);
114
- params.append('app', appName);
115
-
116
- // NIP-46互換のmetadata形式も追加
117
- const metadata = {
118
- name: appName,
119
- url: window.location.origin,
120
- description: 'Nostr Login provided by nostr-login library'
121
- };
122
- params.append('metadata', JSON.stringify(metadata));
123
-
124
- if (pubkey) params.append('pubkey', pubkey);
125
- if (this._pubkey) params.append('current_user', this._pubkey);
126
-
127
- return `${url}?${params.toString()}`;
180
+ if (result) {
181
+ console.log('Amber response detection:', {
182
+ href: window.location.href,
183
+ eventParam: url.searchParams.get('event'),
184
+ pathResult: result,
185
+ sessionId: localStorage.getItem('amber_last_id'),
186
+ sessionType: localStorage.getItem('amber_last_type'),
187
+ });
128
188
  }
129
189
 
130
- // AmberDirectSigner.ts parseResponse を拡張
131
-
132
- static parseResponse(): AmberResponse | null {
133
- const url = new URL(window.location.href);
190
+ if (!result) return null;
134
191
 
135
- // パターン1: NIP-55標準 (?event=...)
136
- let result = url.searchParams.get('event');
192
+ const id = localStorage.getItem('amber_last_id');
193
+ const type = localStorage.getItem('amber_last_type');
137
194
 
138
- // パターン2: パス末尾にhexId (/<hexId>)
139
- if (!result) {
140
- const pathParts = url.pathname.split('/').filter(Boolean);
141
- if (pathParts.length > 0) {
142
- const lastPart = pathParts[pathParts.length - 1];
143
- // hexIdっぽい(64文字の16進数)
144
- if (/^[0-9a-f]{64}$/i.test(lastPart)) {
145
- result = lastPart;
146
- }
147
- }
148
- }
149
-
150
- if (result) {
151
- console.log('Amber response detection:', {
152
- href: window.location.href,
153
- eventParam: url.searchParams.get('event'),
154
- pathResult: result,
155
- sessionId: localStorage.getItem('amber_last_id'),
156
- sessionType: localStorage.getItem('amber_last_type')
157
- });
158
- }
195
+ localStorage.removeItem('amber_last_id');
196
+ localStorage.removeItem('amber_last_type');
197
+ localStorage.removeItem('amber_last_timestamp');
159
198
 
160
- if (!result) return null;
161
-
162
- const id = localStorage.getItem('amber_last_id');
163
- const type = localStorage.getItem('amber_last_type');
164
-
165
- localStorage.removeItem('amber_last_id');
166
- localStorage.removeItem('amber_last_type');
167
- localStorage.removeItem('amber_last_timestamp');
199
+ if (id && type) {
200
+ return { id, type, result };
201
+ }
168
202
 
169
- if (id && type) {
170
- return { id, type, result };
171
- }
203
+ return null;
204
+ }
172
205
 
173
- return null;
206
+ static resolvePending(id: string, type: string, result: string): boolean {
207
+ const entry = this.pendingResolves.get(id);
208
+ if (entry) {
209
+ this.pendingResolves.delete(id);
210
+ if (entry.timer) clearTimeout(entry.timer);
211
+ entry.resolve(result);
212
+ return true;
174
213
  }
175
-
176
- static resolvePending(id: string, type: string, result: string): boolean {
177
- const resolve = this.pendingResolves.get(id);
178
- if (resolve) {
179
- this.pendingResolves.delete(id);
180
- resolve(result);
181
- return true;
182
- }
183
- return false;
214
+ return false;
215
+ }
216
+
217
+ static cleanupPending() {
218
+ for (const [id, entry] of this.pendingResolves) {
219
+ try {
220
+ if (entry.timer) clearTimeout(entry.timer);
221
+ entry.resolve('');
222
+ } catch (e) {
223
+ // ignore
224
+ }
184
225
  }
226
+ this.pendingResolves.clear();
227
+ }
185
228
  }