@konemono/nostr-login 1.9.13 → 1.10.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,6 @@
1
1
  import { Nostr, NostrParams } from './';
2
2
  import { EventEmitter } from 'tseep';
3
3
 
4
- declare global {
5
- interface Window {
6
- nostr?: any;
7
- }
8
- }
9
-
10
4
  class NostrExtensionService extends EventEmitter {
11
5
  private params: NostrParams;
12
6
  private nostrExtension: any | undefined;
@@ -1,13 +1,13 @@
1
+ import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
1
2
  import { Nip44 } from '../utils/nip44';
2
- import { getPublicKey, getEventHash, getSignature, nip04 } from 'nostr-tools';
3
+ import { getPublicKey } from 'nostr-tools';
3
4
 
4
- export class PrivateKeySigner {
5
+ export class PrivateKeySigner extends NDKPrivateKeySigner {
5
6
  private nip44: Nip44 = new Nip44();
6
7
  private _pubkey: string;
7
- public privateKey: string;
8
8
 
9
9
  constructor(privateKey: string) {
10
- this.privateKey = privateKey;
10
+ super(privateKey);
11
11
  this._pubkey = getPublicKey(privateKey);
12
12
  }
13
13
 
@@ -15,42 +15,11 @@ export class PrivateKeySigner {
15
15
  return this._pubkey;
16
16
  }
17
17
 
18
- async blockUntilReady() {
19
- return Promise.resolve();
18
+ encryptNip44(recipient: NDKUser, value: string): Promise<string> {
19
+ return Promise.resolve(this.nip44.encrypt(this.privateKey!, recipient.pubkey, value));
20
20
  }
21
21
 
22
- async user() {
23
- return { pubkey: this.pubkey };
24
- }
25
-
26
- async sign(event: any): Promise<string> {
27
- // ensure event has created_at
28
- if (!event.created_at) event.created_at = Math.floor(Date.now() / 1000);
29
- // compute id and signature
30
- const id = getEventHash(event as any);
31
- event.id = id;
32
- const sig = getSignature(event as any, this.privateKey);
33
- event.sig = sig;
34
- return sig;
35
- }
36
-
37
- async encrypt(recipient: any, plaintext: string): Promise<string> {
38
- const pubkey = typeof recipient === 'string' ? recipient : recipient.pubkey;
39
- return nip04.encrypt(this.privateKey, pubkey, plaintext);
40
- }
41
-
42
- async decrypt(sender: any, ciphertext: string): Promise<string> {
43
- const pubkey = typeof sender === 'string' ? sender : sender.pubkey;
44
- return nip04.decrypt(this.privateKey, pubkey, ciphertext);
45
- }
46
-
47
- encryptNip44(recipient: any, value: string): Promise<string> {
48
- const pubkey = typeof recipient === 'string' ? recipient : recipient.pubkey;
49
- return Promise.resolve(this.nip44.encrypt(this.privateKey, pubkey, value));
50
- }
51
-
52
- decryptNip44(sender: any, value: string): Promise<string> {
53
- const pubkey = typeof sender === 'string' ? sender : sender.pubkey;
54
- return Promise.resolve(this.nip44.decrypt(this.privateKey, pubkey, value));
22
+ decryptNip44(sender: NDKUser, value: string): Promise<string> {
23
+ return Promise.resolve(this.nip44.decrypt(this.privateKey!, sender.pubkey, value));
55
24
  }
56
25
  }
package/src/types.ts CHANGED
@@ -122,10 +122,3 @@ export interface Response {
122
122
  result?: string;
123
123
  error?: string;
124
124
  }
125
-
126
-
127
- export interface AmberResponse {
128
- id: string;
129
- type: string;
130
- result: string;
131
- }
@@ -1,5 +1,6 @@
1
1
  import { Info, RecentType } from 'nostr-login-components/dist/types/types';
2
- import { generatePrivateKey, SimplePool, getEventHash } from 'nostr-tools';
2
+ import NDK, { NDKEvent, NDKRelaySet, NDKSigner, NDKUser } from '@nostr-dev-kit/ndk';
3
+ import { generatePrivateKey } from 'nostr-tools';
3
4
  import { NostrLoginOptions } from '../types';
4
5
 
5
6
  const LOCAL_STORE_KEY = '__nostrlogin_nip46';
@@ -28,53 +29,12 @@ export const localStorageRemoveItem = (key: string) => {
28
29
  localStorage.removeItem(key);
29
30
  };
30
31
 
31
- export const fetchProfile = async (info: Info, profileNdkOrRelays?: any) => {
32
- // support legacy external profile fetcher (NDK compatibility)
33
- if (profileNdkOrRelays && typeof profileNdkOrRelays.fetchEvents === 'function') {
34
- try {
35
- const user = (profileNdkOrRelays as any).getUser({ pubkey: info.pubkey });
36
- user.ndk = profileNdkOrRelays;
37
- return await user.fetchProfile();
38
- } catch (e) {
39
- // fallthrough to SimplePool-based fetch
40
- }
41
- }
32
+ export const fetchProfile = async (info: Info, profileNdk: NDK) => {
33
+ const user = new NDKUser({ pubkey: info.pubkey });
42
34
 
43
- // fallback: use SimplePool to fetch latest kind:0 metadata for the pubkey
44
- const relays = Array.isArray(profileNdkOrRelays) && profileNdkOrRelays.length ? profileNdkOrRelays : DEFAULT_SIGNUP_RELAYS;
45
- const pool = new SimplePool();
46
- const sub = pool.sub(relays, [{ kinds: [0], authors: [info.pubkey], limit: 1 }]);
47
-
48
- return await new Promise(resolve => {
49
- const timer = setTimeout(() => {
50
- try {
51
- sub.unsub();
52
- } catch {}
53
- resolve(null);
54
- }, 3000);
55
-
56
- sub.on('event', (event: any) => {
57
- clearTimeout(timer);
58
- try {
59
- sub.unsub();
60
- } catch {}
61
- if (!event || !event.content) return resolve(null);
62
- try {
63
- const profile = JSON.parse(event.content);
64
- resolve(profile);
65
- } catch (e) {
66
- resolve(null);
67
- }
68
- });
69
-
70
- sub.on('eose', () => {
71
- clearTimeout(timer);
72
- try {
73
- sub.unsub();
74
- } catch {}
75
- resolve(null);
76
- });
77
- });
35
+ user.ndk = profileNdk;
36
+
37
+ return await user.fetchProfile();
78
38
  };
79
39
 
80
40
  export const prepareSignupRelays = (signupRelays?: string) => {
@@ -86,46 +46,43 @@ export const prepareSignupRelays = (signupRelays?: string) => {
86
46
  return relays;
87
47
  };
88
48
 
89
- export const createProfile = async (info: Info, signer: any, signupRelays?: string, outboxRelays?: string[]) => {
49
+ export const createProfile = async (info: Info, profileNdk: NDK, signer: NDKSigner, signupRelays?: string, outboxRelays?: string[]) => {
90
50
  const meta = {
91
51
  name: info.name,
92
52
  };
93
53
 
94
- const profileEvent: any = {
54
+ const profileEvent = new NDKEvent(profileNdk, {
95
55
  kind: 0,
96
56
  created_at: Math.floor(Date.now() / 1000),
97
57
  pubkey: info.pubkey,
98
58
  content: JSON.stringify(meta),
99
59
  tags: [],
100
- };
60
+ });
101
61
  if (window.location.hostname) profileEvent.tags.push(['client', window.location.hostname]);
102
62
 
103
- const relaysEvent: any = {
63
+ const relaysEvent = new NDKEvent(profileNdk, {
104
64
  kind: 10002,
105
65
  created_at: Math.floor(Date.now() / 1000),
106
66
  pubkey: info.pubkey,
107
67
  content: '',
108
68
  tags: [],
109
- };
69
+ });
110
70
 
111
- const relays = prepareSignupRelays(signupRelays);
71
+ const relays = prepareSignupRelays(signupRelays)
112
72
  for (const r of relays) {
113
73
  relaysEvent.tags.push(['r', r]);
114
74
  }
115
75
 
116
- // signer is expected to implement sign(event)
117
- await signer.sign(profileEvent);
76
+ await profileEvent.sign(signer);
118
77
  console.log('signed profile', profileEvent);
119
- await signer.sign(relaysEvent);
78
+ await relaysEvent.sign(signer);
120
79
  console.log('signed relays', relaysEvent);
121
80
 
122
81
  const outboxRelaysFinal = outboxRelays && outboxRelays.length ? outboxRelays : OUTBOX_RELAYS;
123
82
 
124
- // publish using SimplePool
125
- const pool = new SimplePool();
126
- await Promise.any(pool.publish(outboxRelaysFinal, profileEvent));
83
+ await profileEvent.publish(NDKRelaySet.fromRelayUrls(outboxRelaysFinal, profileNdk));
127
84
  console.log('published profile', profileEvent);
128
- await Promise.any(pool.publish(outboxRelaysFinal, relaysEvent));
85
+ await relaysEvent.publish(NDKRelaySet.fromRelayUrls(outboxRelaysFinal, profileNdk));
129
86
  console.log('published relays', relaysEvent);
130
87
  };
131
88
 
@@ -145,7 +102,9 @@ export const bunkerUrlToInfo = (bunkerUrl: string, sk = ''): Info => {
145
102
  export const isBunkerUrl = (value: string) => value.startsWith('bunker://');
146
103
 
147
104
  export const getBunkerUrl = async (value: string, optionsModal: NostrLoginOptions) => {
148
- if (!value) return '';
105
+ if (!value) {
106
+ return '';
107
+ }
149
108
 
150
109
  if (isBunkerUrl(value)) {
151
110
  return value;
@@ -154,26 +113,24 @@ export const getBunkerUrl = async (value: string, optionsModal: NostrLoginOption
154
113
  if (value.includes('@')) {
155
114
  const [name, domain] = value.toLocaleLowerCase().split('@');
156
115
  const origin = optionsModal.devOverrideBunkerOrigin || `https://${domain}`;
157
-
158
116
  const bunkerUrl = `${origin}/.well-known/nostr.json?name=_`;
159
117
  const userUrl = `${origin}/.well-known/nostr.json?name=${name}`;
160
-
161
- const bunkerRes = await fetch(bunkerUrl);
162
- const bunkerData = await bunkerRes.json();
118
+ const bunker = await fetch(bunkerUrl);
119
+ const bunkerData = await bunker.json();
163
120
  const bunkerPubkey = bunkerData.names['_'];
164
- const bunkerRelays: string[] = bunkerData.nip46[bunkerPubkey];
165
-
166
- const userRes = await fetch(userUrl);
167
- const userData = await userRes.json();
121
+ const bunkerRelays = bunkerData.nip46[bunkerPubkey];
122
+ const user = await fetch(userUrl);
123
+ const userData = await user.json();
168
124
  const userPubkey = userData.names[name];
169
-
170
- if (!bunkerRelays || bunkerRelays.length === 0) {
125
+ // console.log({
126
+ // bunkerData, userData, bunkerPubkey, bunkerRelays, userPubkey,
127
+ // name, domain, origin
128
+ // })
129
+ if (!bunkerRelays.length) {
171
130
  throw new Error('Bunker relay not provided');
172
131
  }
173
132
 
174
- const relayParams = bunkerRelays.map(r => `relay=${encodeURIComponent(r)}`).join('&');
175
-
176
- return `bunker://${userPubkey}?${relayParams}`;
133
+ return `bunker://${userPubkey}?relay=${bunkerRelays[0]}`;
177
134
  }
178
135
 
179
136
  throw new Error('Invalid user name or bunker url');
@@ -238,11 +195,6 @@ const upgradeInfo = (info: Info | RecentType) => {
238
195
  if (info.authMethod === 'connect' && !info.signerPubkey) {
239
196
  info.signerPubkey = info.pubkey;
240
197
  }
241
-
242
- if (info.authMethod === ('amber' as any)) {
243
- if (!info.signerPubkey) info.signerPubkey = '';
244
- if (!(info as any).relays) (info as any).relays = [];
245
- }
246
198
  };
247
199
 
248
200
  export const localStorageAddAccount = (info: Info) => {
package/tsconfig.json CHANGED
@@ -11,5 +11,5 @@
11
11
  "outDir": "./dist",
12
12
  },
13
13
  "include": ["src/**/*.ts"],
14
- "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"],
15
- }
14
+ "exclude": ["node_modules"],
15
+ }
@@ -1,242 +0,0 @@
1
- import { AmberResponse } from '../types';
2
- import { Signer } from './Nostr';
3
-
4
- export class AmberDirectSigner implements Signer {
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('[AmberDirectSigner] Requesting public key, URL:', url.substring(0, 100) + '...');
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
- const error = new Error('[AmberDirectSigner] getPublicKey timeout after 20s');
39
- console.error(error.message);
40
- reject(error);
41
- }, 20000);
42
- AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
43
- });
44
- }
45
-
46
- public async signEvent(event: any, id?: string): Promise<any> {
47
- id = id || Math.random().toString(36).substring(7);
48
- const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
49
- console.log('Amber redirecting to:', url);
50
- window.open(url, '_blank', 'width=400,height=600');
51
- return new Promise((resolve, reject) => {
52
- const timer = setTimeout(() => {
53
- AmberDirectSigner.pendingResolves.delete(id!);
54
- reject(new Error('AmberDirectSigner timeout'));
55
- }, 20000);
56
- AmberDirectSigner.pendingResolves.set(id!, {
57
- resolve: (result: string) => {
58
- try {
59
- resolve(JSON.parse(result));
60
- } catch (e) {
61
- resolve(result as any);
62
- }
63
- },
64
- timer,
65
- });
66
- });
67
- }
68
-
69
- public async encrypt04(pubkey: string, plaintext: string, id?: string): Promise<string> {
70
- id = id || Math.random().toString(36).substring(7);
71
- const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
72
- console.log('Amber redirecting to:', url);
73
- window.open(url, '_blank', 'width=400,height=600');
74
- return new Promise((resolve, reject) => {
75
- const timer = setTimeout(() => {
76
- AmberDirectSigner.pendingResolves.delete(id!);
77
- reject(new Error('AmberDirectSigner timeout'));
78
- }, 20000);
79
- AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
80
- });
81
- }
82
-
83
- public async decrypt04(pubkey: string, ciphertext: string, id?: string): Promise<string> {
84
- id = id || Math.random().toString(36).substring(7);
85
- const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
86
- console.log('Amber redirecting to:', url);
87
- window.open(url, '_blank', 'width=400,height=600');
88
- return new Promise((resolve, reject) => {
89
- const timer = setTimeout(() => {
90
- AmberDirectSigner.pendingResolves.delete(id!);
91
- reject(new Error('AmberDirectSigner timeout'));
92
- }, 20000);
93
- AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
94
- });
95
- }
96
-
97
- public async encrypt44(pubkey: string, plaintext: string, id?: string): Promise<string> {
98
- id = id || Math.random().toString(36).substring(7);
99
- const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
100
- console.log('Amber redirecting to:', url);
101
- window.open(url, '_blank', 'width=400,height=600');
102
- return new Promise((resolve, reject) => {
103
- const timer = setTimeout(() => {
104
- AmberDirectSigner.pendingResolves.delete(id!);
105
- reject(new Error('AmberDirectSigner timeout'));
106
- }, 20000);
107
- AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
108
- });
109
- }
110
-
111
- public async decrypt44(pubkey: string, ciphertext: string, id?: string): Promise<string> {
112
- id = id || Math.random().toString(36).substring(7);
113
- const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
114
- console.log('Amber redirecting to:', url);
115
- window.open(url, '_blank', 'width=400,height=600');
116
- return new Promise((resolve, reject) => {
117
- const timer = setTimeout(() => {
118
- AmberDirectSigner.pendingResolves.delete(id!);
119
- reject(new Error('AmberDirectSigner timeout'));
120
- }, 20000);
121
- AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
122
- });
123
- }
124
-
125
- public generateUrl(content: string, type: string, id: string, pubkey?: string): string {
126
- const baseUrl = window.location.href.split('#')[0].split('?')[0];
127
- const encodedContent = encodeURIComponent(content || '');
128
-
129
- // callbackUrl にはベースURLだけを渡す
130
- // Amberが自動的に ?event=<result> を追加する (NIP-55)
131
- const callbackUrl = baseUrl;
132
-
133
- localStorage.setItem('amber_last_type', type);
134
- localStorage.setItem('amber_last_id', id);
135
- localStorage.setItem('amber_last_timestamp', Date.now().toString());
136
-
137
- // NIP-55準拠: nostrsigner: スキーム
138
- let url = `nostrsigner:${encodedContent}`;
139
- const params = new URLSearchParams();
140
- params.append('compressionType', 'none');
141
- params.append('returnType', 'signature');
142
- params.append('type', type);
143
- params.append('callbackUrl', callbackUrl);
144
-
145
- // 三重の冗長性でアプリ名を渡す
146
- const appName = document.title || window.location.hostname;
147
- params.append('name', appName);
148
- params.append('appName', appName);
149
- params.append('app', appName);
150
-
151
- // NIP-46互換のmetadata形式も追加
152
- const metadata = {
153
- name: appName,
154
- url: window.location.origin,
155
- description: 'Nostr Login provided by nostr-login library',
156
- };
157
- params.append('metadata', JSON.stringify(metadata));
158
-
159
- if (pubkey) params.append('pubkey', pubkey);
160
- if (this._pubkey) params.append('current_user', this._pubkey);
161
-
162
- return `${url}?${params.toString()}`;
163
- }
164
-
165
- // AmberDirectSigner.ts の parseResponse を拡張
166
-
167
- static parseResponse(): AmberResponse | null {
168
- const url = new URL(window.location.href);
169
-
170
- // パターン1: NIP-55標準 (?event=...)
171
- let result = url.searchParams.get('event');
172
-
173
- // Amberが壊れた{result}プレースホルダーを返した場合のクリーンアップ
174
- // 例: ?event=result}5650178... → 5650178...
175
- if (result && result.includes('result}')) {
176
- result = result.replace(/^result\}/, '');
177
- console.log('[AmberDirectSigner] Cleaned up malformed result:', result);
178
- }
179
-
180
- // パターン2: パス末尾にhexId (/<hexId>)
181
- if (!result) {
182
- const pathParts = url.pathname.split('/').filter(Boolean);
183
- if (pathParts.length > 0) {
184
- const lastPart = pathParts[pathParts.length - 1];
185
- // hexIdっぽい(64文字の16進数)
186
- if (/^[0-9a-f]{64}$/i.test(lastPart)) {
187
- result = lastPart;
188
- }
189
- }
190
- }
191
-
192
- if (result) {
193
- console.log('Amber response detection:', {
194
- href: window.location.href,
195
- eventParam: url.searchParams.get('event'),
196
- pathResult: result,
197
- sessionId: localStorage.getItem('amber_last_id'),
198
- sessionType: localStorage.getItem('amber_last_type'),
199
- });
200
- }
201
-
202
- if (!result) return null;
203
-
204
- const id = localStorage.getItem('amber_last_id');
205
- const type = localStorage.getItem('amber_last_type');
206
-
207
- localStorage.removeItem('amber_last_id');
208
- localStorage.removeItem('amber_last_type');
209
- localStorage.removeItem('amber_last_timestamp');
210
-
211
- if (id && type) {
212
- return { id, type, result };
213
- }
214
-
215
- return null;
216
- }
217
-
218
- static resolvePending(id: string, type: string, result: string): boolean {
219
- const entry = this.pendingResolves.get(id);
220
- if (entry) {
221
- this.pendingResolves.delete(id);
222
- if (entry.timer) clearTimeout(entry.timer);
223
- console.log('[AmberDirectSigner] Resolving pending:', { id, type, resultLen: result.length });
224
- entry.resolve(result);
225
- return true;
226
- }
227
- console.warn('[AmberDirectSigner] No pending resolve found for:', id);
228
- return false;
229
- }
230
-
231
- static cleanupPending() {
232
- for (const [id, entry] of this.pendingResolves) {
233
- try {
234
- if (entry.timer) clearTimeout(entry.timer);
235
- entry.resolve('');
236
- } catch (e) {
237
- // ignore
238
- }
239
- }
240
- this.pendingResolves.clear();
241
- }
242
- }
@@ -1,124 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { IframeNostrRpc } from './Nip46';
3
- import { PrivateKeySigner } from './Signer';
4
- import { generatePrivateKey, validateEvent, verifySignature } from 'nostr-tools';
5
-
6
- describe('IframeNostrRpc integration', () => {
7
- it('roundtrips request/response via MessagePort', async () => {
8
- const localSk = generatePrivateKey();
9
- const remoteSk = generatePrivateKey();
10
- const localSigner = new PrivateKeySigner(localSk);
11
- const remoteSigner = new PrivateKeySigner(remoteSk);
12
-
13
- const rpc = new IframeNostrRpc(localSigner, 'https://example.com', ['wss://relay.example']);
14
-
15
- // mock MessagePort
16
- const port: any = {
17
- onmessage: undefined as any,
18
- postMessage: (msg: any) => {
19
- // ignore ping messages from keepalive
20
- if (typeof msg === 'string') return;
21
- // log for debugging
22
- console.log('[test] postMessage received', typeof msg, msg && typeof msg === 'object' && msg.pubkey);
23
- console.log('[test] msg.id', msg && msg.id, 'kind', msg && msg.kind, 'contentType', typeof (msg && msg.content), 'contentLen', msg && msg.content && msg.content.length);
24
- // simulate remote iframe processing (defer to avoid race with request setup)
25
- setTimeout(() => {
26
- (async () => {
27
- try {
28
- // remote decrypts request (sender pubkey is msg.pubkey)
29
- const decrypted = await remoteSigner.decrypt(msg.pubkey, msg.content);
30
- console.log('[test] decrypted raw', typeof decrypted, decrypted && decrypted.slice ? decrypted.slice(0, 120) : decrypted);
31
- const req = JSON.parse(decrypted);
32
- console.log('[test] parsed request id', req.id, 'method', req.method);
33
-
34
- const response = { id: req.id, result: 'ack' };
35
- const content = await remoteSigner.encrypt(msg.pubkey, JSON.stringify(response));
36
- const event: any = {
37
- kind: msg.kind,
38
- content,
39
- tags: [['p', msg.pubkey]],
40
- pubkey: remoteSigner.pubkey,
41
- created_at: Math.floor(Date.now() / 1000),
42
- };
43
-
44
- await remoteSigner.sign(event);
45
-
46
- // debug: validate/verify locally to see why rpc might ignore it
47
- console.log('[test] validateEvent', validateEvent(event), 'verifySignature', verifySignature(event));
48
-
49
- // instead of posting back via port, directly parse with rpc to avoid EventEmitter timing issues
50
- try {
51
- const parsed = await (rpc as any).parseEvent(event);
52
- console.log('[test] direct parsed result', parsed);
53
- if (parsed && (parsed as any).result === 'ack') {
54
- // resolve the outer test promise via port._resolve
55
- try {
56
- (port as any)._resolve?.();
57
- } catch (e) {
58
- console.error('[test] resolve error', e);
59
- }
60
- return;
61
- }
62
- } catch (e) {
63
- console.error('[test] parseEvent direct error', e);
64
- throw e;
65
- }
66
- } catch (e) {
67
- console.error('[test] postMessage handler error', e);
68
- throw e;
69
- }
70
- })();
71
- }, 0);
72
- },
73
- };
74
-
75
- rpc.setWorkerIframePort(port as MessagePort);
76
-
77
- // override getId so we can listen for the specific response event
78
- const id = 'test-iframe-id';
79
- (rpc as any).getId = () => id;
80
-
81
- // instrument rpc.emit to see emitted events
82
- const origEmit = (rpc as any).emit;
83
- (rpc as any).emit = function (ev: any, payload: any) {
84
- try {
85
- console.log('[test] rpc.emit', ev, payload && payload.id, payload && JSON.stringify(payload));
86
- } catch (e) {
87
- console.log('[test] rpc.emit', ev, payload && payload.id);
88
- }
89
- return origEmit.apply(this, arguments as any);
90
- };
91
-
92
- // instrument parseEvent to see if the rpc handler runs
93
- const origParse = (rpc as any).parseEvent;
94
- (rpc as any).parseEvent = async function (event: any) {
95
- console.log('[test] rpc.parseEvent called');
96
- try {
97
- const parsed = await origParse.apply(this, arguments as any);
98
- console.log('[test] rpc.parseEvent result', parsed && parsed.id, parsed && (parsed as any).method);
99
- return parsed;
100
- } catch (e) {
101
- console.error('[test] rpc.parseEvent error', e);
102
- throw e;
103
- }
104
- };
105
-
106
- await new Promise<void>((ok, err) => {
107
- // attach resolve onto the port so deferred handler can call it
108
- (port as any)._resolve = ok;
109
-
110
- // use internal setResponseHandler directly to ensure we catch responses (optional)
111
- (rpc as any).setResponseHandler(id, (res: any) => {
112
- console.log('[test] setResponseHandler callback', res);
113
- try {
114
- expect(res.result).toBe('ack');
115
- ok();
116
- } catch (e) {
117
- err(e);
118
- }
119
- });
120
-
121
- rpc.sendRequest(remoteSigner.pubkey, 'connect', [localSigner.pubkey], 24133);
122
- });
123
- });
124
- });
@@ -1,31 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { PrivateKeySigner } from './Signer';
3
- import { NostrRpc, IframeNostrRpc, Nip46Signer } from './Nip46';
4
- import { generatePrivateKey, getPublicKey } from 'nostr-tools';
5
-
6
- // basic smoke tests
7
-
8
- describe('NostrRpc basic', () => {
9
- it('creates and signs event', async () => {
10
- const sk = generatePrivateKey();
11
- const signer = new PrivateKeySigner(sk);
12
- const rpc = new NostrRpc(signer, ['wss://relay.nostr.example']);
13
-
14
- const event = await rpc.createRequestEvent('id1', getPublicKey(sk), 'echo', ['hello']);
15
- expect(event.kind).toBe(24133);
16
- expect(event.pubkey).toBe(signer.pubkey);
17
- expect(event.content).toBeTruthy();
18
- });
19
- });
20
-
21
- describe('Nip46Signer basic', () => {
22
- it('wraps signer and can sign', async () => {
23
- const sk = generatePrivateKey();
24
- const signer = new PrivateKeySigner(sk);
25
- const nip = new Nip46Signer(signer, signer.pubkey);
26
-
27
- const ev: any = { kind: 1, content: 'x', pubkey: signer.pubkey, tags: [] };
28
- const sig = await nip.sign(ev);
29
- expect(sig).toBeTruthy();
30
- });
31
- });