@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.
- package/dist/const/index.d.ts +3 -0
- package/dist/index.esm.js +12 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +13 -8
- package/dist/modules/ModalManager.d.ts +1 -0
- package/dist/modules/Nip46.d.ts +44 -47
- package/dist/modules/NostrExtensionService.d.ts +0 -5
- package/dist/modules/Signer.d.ts +4 -11
- package/dist/modules/nip46/Nip46Adapter.d.ts +2 -10
- package/dist/modules/nip46/Nip46Client.d.ts +1 -16
- package/dist/modules/nip46/types.d.ts +0 -8
- package/dist/types.d.ts +0 -5
- package/dist/unpkg.js +12 -10
- package/dist/utils/index.d.ts +3 -2
- package/package.json +4 -7
- package/src/const/index.ts +7 -0
- package/src/index.ts +2 -8
- package/src/modules/AuthNostrService.ts +168 -165
- package/src/modules/ModalManager.ts +14 -10
- package/src/modules/Nip46.ts +400 -208
- package/src/modules/NostrExtensionService.ts +0 -6
- package/src/modules/Signer.ts +8 -39
- package/src/types.ts +0 -7
- package/src/utils/index.ts +31 -79
- package/tsconfig.json +2 -2
- package/src/modules/AmberDirectSigner.ts +0 -242
- package/src/modules/Nip46.iframe.test.ts +0 -124
- package/src/modules/Nip46.test.ts +0 -31
- package/src/modules/nip46/Nip46Adapter.ts +0 -180
- package/src/modules/nip46/Nip46Client.ts +0 -363
- package/src/modules/nip46/types.ts +0 -34
- package/vitest.config.ts +0 -9
|
@@ -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;
|
package/src/modules/Signer.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
|
|
1
2
|
import { Nip44 } from '../utils/nip44';
|
|
2
|
-
import { getPublicKey
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
return
|
|
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
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Info, RecentType } from 'nostr-login-components/dist/types/types';
|
|
2
|
-
import {
|
|
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,
|
|
32
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
117
|
-
await signer.sign(profileEvent);
|
|
76
|
+
await profileEvent.sign(signer);
|
|
118
77
|
console.log('signed profile', profileEvent);
|
|
119
|
-
await
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
165
|
-
|
|
166
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
});
|