@konemono/nostr-login 1.7.69 → 1.8.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.69",
3
+ "version": "1.8.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,170 +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
- };
154
+ if (pubkey) params.append('pubkey', pubkey);
155
+ if (this._pubkey) params.append('current_user', this._pubkey);
29
156
 
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
- }
157
+ return `${url}?${params.toString()}`;
158
+ }
39
159
 
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
- }
160
+ // AmberDirectSigner.ts parseResponse を拡張
51
161
 
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
- }
162
+ static parseResponse(): AmberResponse | null {
163
+ const url = new URL(window.location.href);
61
164
 
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
- }
165
+ // パターン1: NIP-55標準 (?event=...)
166
+ let result = url.searchParams.get('event');
71
167
 
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
- });
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
+ }
80
178
  }
81
179
 
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
- });
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
+ });
90
188
  }
91
189
 
190
+ if (!result) return null;
92
191
 
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());
192
+ const id = localStorage.getItem('amber_last_id');
193
+ const type = localStorage.getItem('amber_last_type');
101
194
 
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
- params.append('name', window.location.hostname);
110
- if (pubkey) params.append('pubkey', pubkey);
111
- if (this._pubkey) params.append('current_user', this._pubkey);
195
+ localStorage.removeItem('amber_last_id');
196
+ localStorage.removeItem('amber_last_type');
197
+ localStorage.removeItem('amber_last_timestamp');
112
198
 
113
- return `${url}?${params.toString()}`;
199
+ if (id && type) {
200
+ return { id, type, result };
114
201
  }
115
202
 
116
- // AmberDirectSigner.ts の parseResponse を拡張
203
+ return null;
204
+ }
117
205
 
118
- static parseResponse(): AmberResponse | null {
119
- const url = new URL(window.location.href);
120
-
121
- // パターン1: NIP-55標準 (?event=...)
122
- let result = url.searchParams.get('event');
123
-
124
- // パターン2: パス末尾にhexId (/<hexId>)
125
- if (!result) {
126
- const pathParts = url.pathname.split('/').filter(Boolean);
127
- if (pathParts.length > 0) {
128
- const lastPart = pathParts[pathParts.length - 1];
129
- // hexIdっぽい(64文字の16進数)
130
- if (/^[0-9a-f]{64}$/i.test(lastPart)) {
131
- result = lastPart;
132
- }
133
- }
134
- }
135
-
136
- if (result) {
137
- console.log('Amber response detection:', {
138
- href: window.location.href,
139
- eventParam: url.searchParams.get('event'),
140
- pathResult: result,
141
- sessionId: localStorage.getItem('amber_last_id'),
142
- sessionType: localStorage.getItem('amber_last_type')
143
- });
144
- }
145
-
146
- if (!result) return null;
147
-
148
- const id = localStorage.getItem('amber_last_id');
149
- const type = localStorage.getItem('amber_last_type');
150
-
151
- localStorage.removeItem('amber_last_id');
152
- localStorage.removeItem('amber_last_type');
153
- localStorage.removeItem('amber_last_timestamp');
154
-
155
- if (id && type) {
156
- return { id, type, result };
157
- }
158
-
159
- 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;
160
213
  }
161
-
162
- static resolvePending(id: string, type: string, result: string): boolean {
163
- const resolve = this.pendingResolves.get(id);
164
- if (resolve) {
165
- this.pendingResolves.delete(id);
166
- resolve(result);
167
- return true;
168
- }
169
- 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
+ }
170
225
  }
226
+ this.pendingResolves.clear();
227
+ }
171
228
  }