window.nostr.js 0.4.6 → 0.4.8
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/README.md +3 -4
- package/dist/index.html +1 -1
- package/dist/window.nostr.js +13 -11
- package/package.json +2 -2
- package/src/App.svelte +92 -142
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "window.nostr.js",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/window.nostr.js",
|
|
6
6
|
"devDependencies": {
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"debounce": "2.0.0",
|
|
27
|
-
"nostr-tools": "2.
|
|
27
|
+
"nostr-tools": "2.12.0"
|
|
28
28
|
}
|
|
29
29
|
}
|
package/src/App.svelte
CHANGED
|
@@ -11,27 +11,26 @@
|
|
|
11
11
|
import {
|
|
12
12
|
BunkerSigner,
|
|
13
13
|
type BunkerSignerParams,
|
|
14
|
-
createAccount,
|
|
15
14
|
parseBunkerInput,
|
|
16
15
|
type BunkerPointer,
|
|
17
|
-
|
|
18
|
-
BUNKER_REGEX,
|
|
19
|
-
queryBunkerProfile
|
|
16
|
+
BUNKER_REGEX
|
|
20
17
|
} from 'nostr-tools/nip46'
|
|
21
|
-
import {NIP05_REGEX
|
|
18
|
+
import {NIP05_REGEX} from 'nostr-tools/nip05'
|
|
22
19
|
import {npubEncode} from 'nostr-tools/nip19'
|
|
23
20
|
import {onMount} from 'svelte'
|
|
24
21
|
import mediaQueryStore from './mediaQueryStore.js'
|
|
25
22
|
import Spinner from './Spinner.svelte'
|
|
26
23
|
|
|
24
|
+
const currentDomain = window.location.hostname
|
|
25
|
+
const currentProtocol = window.location.protocol
|
|
26
|
+
|
|
27
27
|
const mobileMode = mediaQueryStore('only screen and (max-width: 640px)')
|
|
28
28
|
const lskeys = {
|
|
29
29
|
ORIGIN: 'wnj:origin',
|
|
30
30
|
CLIENT_SECRET: 'wnj:clientSecret',
|
|
31
31
|
Y_POS: 'wnj:ypos',
|
|
32
32
|
CALLBACK_TOKEN: 'wnj:callbackToken',
|
|
33
|
-
BUNKER_POINTER: 'wnj:bunkerPointer'
|
|
34
|
-
CACHED_PUBKEY: 'wnj:cachedPubKey'
|
|
33
|
+
BUNKER_POINTER: 'wnj:bunkerPointer'
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
let myself: HTMLDivElement
|
|
@@ -48,9 +47,6 @@
|
|
|
48
47
|
const pool = new SimplePool()
|
|
49
48
|
let bunkerInput: HTMLInputElement
|
|
50
49
|
let bunkerInputValue: string
|
|
51
|
-
let nameInput: HTMLInputElement
|
|
52
|
-
let nameInputValue: string
|
|
53
|
-
let chosenProvider: BunkerProfile | undefined
|
|
54
50
|
let clientSecret: Uint8Array
|
|
55
51
|
const local = localStorage.getItem(lskeys.CLIENT_SECRET)
|
|
56
52
|
if (local) {
|
|
@@ -61,6 +57,7 @@
|
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
let state: 'opened' | 'closed' | 'justopened' | 'justclosed' = 'closed'
|
|
60
|
+
let nostrLogin = false
|
|
64
61
|
let bunkerPointer: BunkerPointer | null
|
|
65
62
|
let resolveBunker: (_: BunkerSigner) => void
|
|
66
63
|
let rejectBunker: (_: string) => void
|
|
@@ -71,6 +68,7 @@
|
|
|
71
68
|
let showLogin: string | null = null
|
|
72
69
|
let showConfirmAction: string | null = null
|
|
73
70
|
let takingTooLong = false
|
|
71
|
+
let hasTriedToConnectButFailed = false
|
|
74
72
|
let creating: boolean
|
|
75
73
|
let awaitingCreation: boolean
|
|
76
74
|
let errorMessage: string
|
|
@@ -83,17 +81,6 @@
|
|
|
83
81
|
event: NostrEvent | null
|
|
84
82
|
}
|
|
85
83
|
let metadataSub: SubCloser | null
|
|
86
|
-
let providers: BunkerProfile[] = [
|
|
87
|
-
{
|
|
88
|
-
picture: 'https://nsec.app/favicon.ico',
|
|
89
|
-
name: 'Nsec.app',
|
|
90
|
-
about:
|
|
91
|
-
'Store keys safely without a browser extension! Nsec.app is a non-custodial web app that can be connected to Nostr apps and provide restricted access to your keys.',
|
|
92
|
-
nip05: '_@nsec.app',
|
|
93
|
-
website: 'https://nsec.app',
|
|
94
|
-
domain: 'nsec.app'
|
|
95
|
-
} as BunkerProfile
|
|
96
|
-
]
|
|
97
84
|
|
|
98
85
|
const connectBunkerError =
|
|
99
86
|
'We could not connect to a NIP-46 bunker with that url, are you sure it is set up correctly?'
|
|
@@ -112,7 +99,6 @@
|
|
|
112
99
|
let clickStart: number
|
|
113
100
|
|
|
114
101
|
$: opened = state === 'justopened' || state === 'opened'
|
|
115
|
-
|
|
116
102
|
$: movingStyle = hasMoved
|
|
117
103
|
? 'cursor-grabbing outline-dashed outline-' +
|
|
118
104
|
accent +
|
|
@@ -131,7 +117,7 @@
|
|
|
131
117
|
showAuth = url
|
|
132
118
|
} else if (identity) {
|
|
133
119
|
showConfirmAction = url
|
|
134
|
-
|
|
120
|
+
state = 'opened'
|
|
135
121
|
} else {
|
|
136
122
|
showLogin = url
|
|
137
123
|
}
|
|
@@ -176,7 +162,7 @@
|
|
|
176
162
|
let windowNostr = {
|
|
177
163
|
isWnj: true,
|
|
178
164
|
async getPublicKey(): Promise<string> {
|
|
179
|
-
if (!connecting && !connected)
|
|
165
|
+
if (!connecting && !connected) connectOrOpen()
|
|
180
166
|
return (await bunker).getPublicKey()
|
|
181
167
|
},
|
|
182
168
|
async signEvent(event: NostrEvent): Promise<VerifiedEvent> {
|
|
@@ -191,8 +177,7 @@
|
|
|
191
177
|
async getRelays(): Promise<{
|
|
192
178
|
[url: string]: {read: boolean; write: boolean}
|
|
193
179
|
}> {
|
|
194
|
-
|
|
195
|
-
return (await bunker).getRelays()
|
|
180
|
+
return {}
|
|
196
181
|
},
|
|
197
182
|
nip04: {
|
|
198
183
|
async encrypt(pubkey: string, plaintext: string): Promise<string> {
|
|
@@ -223,18 +208,48 @@
|
|
|
223
208
|
}
|
|
224
209
|
|
|
225
210
|
onMount(() => {
|
|
211
|
+
// Verify Nstart callback if the hash contains "nostr-login"
|
|
212
|
+
const hash = window.location.hash
|
|
213
|
+
if (hash.startsWith('#nostr-login=')) {
|
|
214
|
+
// Extract the value after "nostr-login="
|
|
215
|
+
const value = hash.substring(hash.indexOf('=') + 1)
|
|
216
|
+
|
|
217
|
+
// Reset nostr-login data
|
|
218
|
+
const urlWithoutHash = window.location.href.split('#')[0]
|
|
219
|
+
history.replaceState(null, '', urlWithoutHash)
|
|
220
|
+
|
|
221
|
+
if (value.startsWith('bunker://')) {
|
|
222
|
+
bunkerInputValue = value
|
|
223
|
+
const event = new SubmitEvent('submit', {
|
|
224
|
+
bubbles: true,
|
|
225
|
+
cancelable: true
|
|
226
|
+
})
|
|
227
|
+
nostrLogin = true
|
|
228
|
+
open()
|
|
229
|
+
handleConnect(event)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
226
233
|
if (!bunkerPointer) {
|
|
227
234
|
let data = localStorage.getItem(lskeys.BUNKER_POINTER)
|
|
228
235
|
if (data) {
|
|
229
|
-
bunkerPointer = JSON.parse(data)
|
|
236
|
+
bunkerPointer = JSON.parse(data) as BunkerPointer
|
|
237
|
+
|
|
238
|
+
// rebuild a bunker url from the pointer data so we can fill in the input
|
|
239
|
+
let bunkerURL = new URL(`bunker://${bunkerPointer.pubkey}`)
|
|
240
|
+
bunkerPointer.relays.forEach(relay => {
|
|
241
|
+
bunkerURL.searchParams.append('relay', relay)
|
|
242
|
+
})
|
|
243
|
+
if (bunkerPointer.secret) {
|
|
244
|
+
bunkerURL.searchParams.set('secret', bunkerPointer.secret)
|
|
245
|
+
}
|
|
246
|
+
bunkerInputValue = bunkerURL.toString()
|
|
247
|
+
// ~
|
|
248
|
+
|
|
230
249
|
identify()
|
|
231
250
|
|
|
232
251
|
// we must connect here so identify() works because we can't rely on the bunker params to read our pubkey
|
|
233
|
-
|
|
234
|
-
let cachedPubkey = localStorage.getItem(lskeys.CACHED_PUBKEY)
|
|
235
|
-
if (!cachedPubkey) {
|
|
236
|
-
connect()
|
|
237
|
-
}
|
|
252
|
+
connect()
|
|
238
253
|
}
|
|
239
254
|
}
|
|
240
255
|
|
|
@@ -300,9 +315,9 @@
|
|
|
300
315
|
async function handleConnect(ev: SubmitEvent) {
|
|
301
316
|
ev.preventDefault()
|
|
302
317
|
try {
|
|
303
|
-
bunkerPointer = await parseBunkerInput(
|
|
318
|
+
bunkerPointer = await parseBunkerInput(bunkerInputValue)
|
|
304
319
|
if (!bunkerPointer) {
|
|
305
|
-
if (
|
|
320
|
+
if (bunkerInputValue.match(BUNKER_REGEX)) {
|
|
306
321
|
errorMessage = connectBunkerError
|
|
307
322
|
} else {
|
|
308
323
|
errorMessage = connectNip05Error
|
|
@@ -318,7 +333,7 @@
|
|
|
318
333
|
// wait until the connection has succeeded before loading user data
|
|
319
334
|
identify()
|
|
320
335
|
} catch (error) {
|
|
321
|
-
if (
|
|
336
|
+
if (bunkerInputValue.match(BUNKER_REGEX)) {
|
|
322
337
|
errorMessage = connectBunkerError
|
|
323
338
|
} else {
|
|
324
339
|
errorMessage = connectNip05Error
|
|
@@ -330,83 +345,26 @@
|
|
|
330
345
|
async function handleDisconnect(ev: MouseEvent) {
|
|
331
346
|
ev.preventDefault()
|
|
332
347
|
localStorage.removeItem(lskeys.BUNKER_POINTER)
|
|
333
|
-
localStorage.removeItem(lskeys.CACHED_PUBKEY)
|
|
334
348
|
reset()
|
|
335
349
|
}
|
|
336
350
|
|
|
337
|
-
async function
|
|
351
|
+
async function handleErasePointer(ev: MouseEvent) {
|
|
338
352
|
ev.preventDefault()
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
let toRemove: BunkerProfile[] = []
|
|
343
|
-
let promises: Promise<void>[] = []
|
|
344
|
-
|
|
345
|
-
for (let i = 0; i < providers.length; i++) {
|
|
346
|
-
let promise = queryBunkerProfile(providers[i].nip05).then(
|
|
347
|
-
(bp: BunkerPointer | null) => {
|
|
348
|
-
if (!bp) {
|
|
349
|
-
toRemove.push(providers[i])
|
|
350
|
-
} else {
|
|
351
|
-
providers[i].bunkerPointer = bp
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
)
|
|
355
|
-
promises.push(promise)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
await Promise.all(promises)
|
|
359
|
-
for (let r = 0; r < toRemove.length; r++) {
|
|
360
|
-
let idx = providers.indexOf(toRemove[r])
|
|
361
|
-
providers.splice(idx, 1)
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
providers = providers
|
|
365
|
-
}
|
|
353
|
+
bunkerInputValue = ''
|
|
354
|
+
localStorage.removeItem(lskeys.BUNKER_POINTER)
|
|
355
|
+
hasTriedToConnectButFailed = false
|
|
366
356
|
}
|
|
367
357
|
|
|
368
358
|
function handleOpenLogin(_: MouseEvent) {
|
|
369
359
|
creating = false
|
|
370
360
|
}
|
|
371
361
|
|
|
372
|
-
async function handleCreate(ev: SubmitEvent) {
|
|
373
|
-
ev.preventDefault()
|
|
374
|
-
if (!chosenProvider) return
|
|
375
|
-
|
|
376
|
-
awaitingCreation = true
|
|
377
|
-
let bunker = await createAccount(
|
|
378
|
-
chosenProvider,
|
|
379
|
-
bunkerSignerParams,
|
|
380
|
-
nameInput.value,
|
|
381
|
-
chosenProvider.domain,
|
|
382
|
-
undefined,
|
|
383
|
-
clientSecret
|
|
384
|
-
)
|
|
385
|
-
awaitingCreation = false
|
|
386
|
-
|
|
387
|
-
open()
|
|
388
|
-
creating = false
|
|
389
|
-
|
|
390
|
-
bunkerPointer = bunker.bp
|
|
391
|
-
identify()
|
|
392
|
-
connect(bunker)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const checkNameInput = debounce(async () => {
|
|
396
|
-
if (chosenProvider && nameInput.value.length > 0) {
|
|
397
|
-
if (await queryProfile(nameInput.value + '@' + chosenProvider.domain)) {
|
|
398
|
-
nameInput.setCustomValidity(`'${nameInput.value}' is already taken.`)
|
|
399
|
-
} else {
|
|
400
|
-
nameInput.setCustomValidity('')
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}, 500)
|
|
404
|
-
|
|
405
362
|
function handleAbortConnection() {
|
|
406
363
|
takingTooLong = false
|
|
407
364
|
connecting = false
|
|
408
365
|
rejectBunker('connection aborted')
|
|
409
366
|
reset()
|
|
367
|
+
open()
|
|
410
368
|
}
|
|
411
369
|
|
|
412
370
|
async function connect(b: BunkerSigner | undefined = undefined) {
|
|
@@ -415,7 +373,7 @@
|
|
|
415
373
|
|
|
416
374
|
let connectionTimeout = setTimeout(() => {
|
|
417
375
|
takingTooLong = true
|
|
418
|
-
|
|
376
|
+
state = 'opened'
|
|
419
377
|
}, 5000)
|
|
420
378
|
|
|
421
379
|
try {
|
|
@@ -434,18 +392,20 @@
|
|
|
434
392
|
showAuth = null
|
|
435
393
|
showLogin = null
|
|
436
394
|
showConfirmAction = null
|
|
395
|
+
if (nostrLogin) {
|
|
396
|
+
open()
|
|
397
|
+
}
|
|
437
398
|
}
|
|
438
399
|
}
|
|
439
400
|
|
|
440
401
|
// identify() is what gives a name and picture to our floating widget
|
|
441
402
|
async function identify() {
|
|
442
|
-
let pubkey
|
|
443
|
-
|
|
403
|
+
let pubkey: string
|
|
404
|
+
try {
|
|
444
405
|
pubkey = await (await bunker).getPublicKey()
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
localStorage.setItem(lskeys.CACHED_PUBKEY, pubkey)
|
|
406
|
+
} catch (err) {
|
|
407
|
+
hasTriedToConnectButFailed = true
|
|
408
|
+
return
|
|
449
409
|
}
|
|
450
410
|
|
|
451
411
|
identity = {
|
|
@@ -534,6 +494,10 @@
|
|
|
534
494
|
localStorage.setItem(lskeys.Y_POS, ypos.toString())
|
|
535
495
|
}
|
|
536
496
|
}
|
|
497
|
+
|
|
498
|
+
function handleCreateAccount() {
|
|
499
|
+
window.location.href = `https://nstart.me?an=${currentDomain}&at=web&ac=${currentProtocol}//${currentDomain}&sfb=yes`
|
|
500
|
+
}
|
|
537
501
|
</script>
|
|
538
502
|
|
|
539
503
|
<svelte:window
|
|
@@ -708,39 +672,17 @@
|
|
|
708
672
|
<!-- Create account view ################### -->
|
|
709
673
|
{:else if creating}
|
|
710
674
|
<div class="text-center text-lg">Create a Nostr account</div>
|
|
711
|
-
<
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
/>
|
|
723
|
-
<div class="mx-2 text-2xl">@</div>
|
|
724
|
-
<select
|
|
725
|
-
class="box-border w-full rounded px-2 py-1 text-lg text-neutral-800 outline-none"
|
|
726
|
-
bind:value={chosenProvider}
|
|
727
|
-
>
|
|
728
|
-
{#each providers as prov}
|
|
729
|
-
<option
|
|
730
|
-
label={prov.domain}
|
|
731
|
-
value={prov}
|
|
732
|
-
class="px-2 py-1 text-lg"
|
|
733
|
-
/>
|
|
734
|
-
{/each}
|
|
735
|
-
</select>
|
|
736
|
-
</div>
|
|
737
|
-
<button
|
|
738
|
-
class="mt-4 block w-full cursor-pointer rounded border-0 px-2 py-1 text-lg text-white disabled:cursor-default disabled:bg-neutral-400 disabled:text-neutral-200 bg-{accent}-900 hover:bg-{accent}-950"
|
|
739
|
-
disabled={!chosenProvider || !nameInputValue || awaitingCreation}
|
|
740
|
-
>
|
|
741
|
-
Continue »
|
|
742
|
-
</button>
|
|
743
|
-
</form>
|
|
675
|
+
<div class="mt-4 text-base leading-5">
|
|
676
|
+
To use this Nostr app you need a profile. The following button opens a
|
|
677
|
+
wizard that help you to create your keypair and safely manage it in a
|
|
678
|
+
few steps. Are you ready?
|
|
679
|
+
</div>
|
|
680
|
+
<button
|
|
681
|
+
class="mt-4 block w-full cursor-pointer rounded border-0 px-2 py-1 text-lg text-white disabled:cursor-default disabled:bg-neutral-400 disabled:text-neutral-200 bg-{accent}-900 hover:bg-{accent}-950"
|
|
682
|
+
on:click={handleCreateAccount}
|
|
683
|
+
disabled={awaitingCreation}
|
|
684
|
+
>Create an account »
|
|
685
|
+
</button>
|
|
744
686
|
<div class="mt-6 text-center text-sm leading-3">
|
|
745
687
|
Do you already have a Nostr address?<br />
|
|
746
688
|
<button
|
|
@@ -795,11 +737,19 @@
|
|
|
795
737
|
</form>
|
|
796
738
|
{#if !connecting}
|
|
797
739
|
<div class="mt-6 text-center text-sm leading-3">
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
740
|
+
{#if hasTriedToConnectButFailed}
|
|
741
|
+
Is this bunker provider broken?<br />
|
|
742
|
+
<button
|
|
743
|
+
class="cursor-pointer border-0 bg-transparent text-sm text-white underline"
|
|
744
|
+
on:click={handleErasePointer}>Clear it</button
|
|
745
|
+
>
|
|
746
|
+
{:else}
|
|
747
|
+
Do you need a Nostr account?<br />
|
|
748
|
+
<button
|
|
749
|
+
class="cursor-pointer border-0 bg-transparent text-sm text-white underline"
|
|
750
|
+
on:click={handleCreateAccount}>Sign up now</button
|
|
751
|
+
>
|
|
752
|
+
{/if}
|
|
803
753
|
</div>
|
|
804
754
|
{/if}
|
|
805
755
|
|