window.nostr.js 0.1.3 → 0.2.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/src/App.svelte CHANGED
@@ -25,14 +25,23 @@
25
25
  import Spinner from './Spinner.svelte'
26
26
 
27
27
  const mobileMode = mediaQueryStore('only screen and (max-width: 640px)')
28
+ const localStorageKeys = {
29
+ ORIGIN: 'wnj:origin',
30
+ CLIENT_SECRET: 'wnj:clientSecret',
31
+ Y_POS: 'wnj:ypos',
32
+ CALLBACK_TOKEN: 'wnj:callbackToken',
33
+ BUNKER_POINTER: 'wnj:bunkerPointer'
34
+ }
28
35
 
29
36
  let myself: HTMLDivElement
30
37
  export let accent: string
31
38
  export let position: 'top' | 'bottom' = 'top'
32
39
  $: origin = $mobileMode
33
40
  ? 'bottom'
34
- : (localStorage.getItem('wnj:origin') as 'top' | 'bottom' | null) ||
35
- position
41
+ : (localStorage.getItem(localStorageKeys.ORIGIN) as
42
+ | 'top'
43
+ | 'bottom'
44
+ | null) || position
36
45
 
37
46
  const win = window as any
38
47
  const pool = new SimplePool()
@@ -42,23 +51,32 @@
42
51
  let nameInputValue: string
43
52
  let chosenProvider: BunkerProfile | undefined
44
53
  let clientSecret: Uint8Array
45
- const local = localStorage.getItem('wnj:clientSecret')
54
+ const local = localStorage.getItem(localStorageKeys.CLIENT_SECRET)
46
55
  if (local) {
47
56
  clientSecret = hexToBytes(local)
48
57
  } else {
49
58
  clientSecret = generateSecretKey()
50
- localStorage.setItem('wnj:clientSecret', bytesToHex(clientSecret))
59
+ localStorage.setItem(
60
+ localStorageKeys.CLIENT_SECRET,
61
+ bytesToHex(clientSecret)
62
+ )
51
63
  }
52
64
 
53
65
  let state: 'opened' | 'closed' | 'justopened' | 'justclosed' = 'closed'
54
66
  let bunkerPointer: BunkerPointer | null
55
67
  let resolveBunker: (_: BunkerSigner) => void
68
+ let rejectBunker: (_: string) => void
56
69
  let bunker: Promise<BunkerSigner>
57
70
  let connecting: boolean
58
- let longConnecting = false
59
- let cancelConnection = false
71
+ let connected: boolean
72
+ let showAuth: string | null = null
73
+ let showLogin: string | null = null
74
+ let takingTooLong = false
60
75
  let creating: boolean
61
- let connected: null | {
76
+ let awaitingCreation: boolean
77
+ let errorMessage: string
78
+ let showInfo = false
79
+ let identity: null | {
62
80
  pubkey: string
63
81
  npub: string
64
82
  name?: string
@@ -67,12 +85,16 @@
67
85
  }
68
86
  let metadataSub: SubCloser | null
69
87
  let providers: BunkerProfile[] = []
88
+ const connectBunkerError =
89
+ 'We could not connect to a NIP-46 bunker with that url, are you sure it is set up correctly?'
90
+ const connectNip05Error =
91
+ 'We were not able to connect using this address. For it to work it has to come from a NIP-46 provider.'
70
92
 
71
93
  const BASE_YPOS = 20
72
94
  export let right = 20
73
95
  $: ypos = $mobileMode
74
96
  ? BASE_YPOS
75
- : parseInt(localStorage.getItem('wnj:ypos') || '0') || BASE_YPOS
97
+ : parseInt(localStorage.getItem(localStorageKeys.Y_POS) || '0') || BASE_YPOS
76
98
  let dragStarted = false
77
99
  let hasMoved = false
78
100
  let insidePosition: number
@@ -82,10 +104,10 @@
82
104
  $: opened = state === 'justopened' || state === 'opened'
83
105
 
84
106
  $: movingStyle = hasMoved
85
- ? 'tw-cursor-grabbing tw-outline-dashed tw-outline-' +
107
+ ? 'cursor-grabbing outline-dashed outline-' +
86
108
  accent +
87
- '-500 tw-outline-1 tw-outline-offset-4'
88
- : 'tw-outline-none'
109
+ '-500 outline-1 outline-offset-4'
110
+ : 'outline-none'
89
111
 
90
112
  $: bunkerInputValueIsGood =
91
113
  bunkerInputValue &&
@@ -95,10 +117,18 @@
95
117
  const bunkerSignerParams: BunkerSignerParams = {
96
118
  pool,
97
119
  onauth(url: string) {
98
- window.open(url, 'window.nostr', `width=600,height=800,popup=yes`)
120
+ if (creating) {
121
+ showAuth = url
122
+ } else {
123
+ showLogin = url
124
+ }
99
125
  }
100
126
  }
101
127
 
128
+ function openAuthURLPopup(url: string | null): Window | null {
129
+ return window.open(url!, 'window.nostr', `width=600,height=800,popup=yes`)
130
+ }
131
+
102
132
  const delayedUpdateState = debounce(() => {
103
133
  switch (state) {
104
134
  case 'justopened':
@@ -120,31 +150,40 @@
120
150
  delayedUpdateState()
121
151
  }
122
152
 
153
+ function connectOrOpen() {
154
+ if (bunkerPointer && !connected) {
155
+ connect()
156
+ return
157
+ }
158
+ open()
159
+ }
160
+
123
161
  reset()
124
162
 
125
163
  let windowNostr = {
126
164
  isWnj: true,
127
165
  async getPublicKey(): Promise<string> {
166
+ if (bunkerPointer) return bunkerPointer.pubkey
128
167
  if (!connecting && !connected) open()
129
168
  return (await bunker).bp.pubkey
130
169
  },
131
170
  async signEvent(event: NostrEvent): Promise<VerifiedEvent> {
132
- if (!connecting && !connected) open()
171
+ if (!connecting && !connected) connectOrOpen()
133
172
  return (await bunker).signEvent(event)
134
173
  },
135
174
  async getRelays(): Promise<{
136
175
  [url: string]: {read: boolean; write: boolean}
137
176
  }> {
138
- if (!connecting && !connected) open()
177
+ if (!connecting && !connected) connectOrOpen()
139
178
  return (await bunker).getRelays()
140
179
  },
141
180
  nip04: {
142
181
  async encrypt(pubkey: string, plaintext: string): Promise<string> {
143
- if (!connecting && !connected) open()
182
+ if (!connecting && !connected) connectOrOpen()
144
183
  return (await bunker).nip04Encrypt(pubkey, plaintext)
145
184
  },
146
185
  async decrypt(pubkey: string, ciphertext: string): Promise<string> {
147
- if (!connecting && !connected) open()
186
+ if (!connecting && !connected) connectOrOpen()
148
187
  return (await bunker).nip04Decrypt(pubkey, ciphertext)
149
188
  }
150
189
  }
@@ -153,22 +192,28 @@
153
192
  function reset() {
154
193
  close()
155
194
  bunkerPointer = null
156
- bunker = new Promise(resolve => {
195
+ bunker = new Promise((resolve, reject) => {
157
196
  resolveBunker = resolve
197
+ rejectBunker = reject
158
198
  })
199
+ identity = null
159
200
  connecting = false
201
+ takingTooLong = false
160
202
  creating = false
161
- connected = null
203
+ connected = false
162
204
  metadataSub = null
205
+ errorMessage = ''
163
206
  }
164
207
 
165
208
  onMount(() => {
166
- let data = localStorage.getItem('wnj:bunkerPointer')
167
- if (data) {
168
- bunkerPointer = JSON.parse(data)
169
- connect(
170
- new BunkerSigner(clientSecret, bunkerPointer!, bunkerSignerParams)
171
- )
209
+ if (!bunkerPointer) {
210
+ let data = localStorage.getItem(localStorageKeys.BUNKER_POINTER)
211
+ if (data) {
212
+ bunkerPointer = JSON.parse(data)
213
+ // we have a pointer, which means we can get the public key right away
214
+ // but we will only try to connect when any other method is called on window.nostr
215
+ identify()
216
+ }
172
217
  }
173
218
 
174
219
  if (win.nostr && !win.nostr.isWnj) {
@@ -214,27 +259,54 @@
214
259
  function handleCloseModal(ev: MouseEvent) {
215
260
  close()
216
261
  creating = false
262
+ showAuth = null
263
+ showLogin = null
264
+ ev.stopPropagation()
265
+ }
266
+
267
+ function handleShowInfo(ev: MouseEvent) {
268
+ showInfo = true
269
+ ev.stopPropagation()
270
+ }
271
+
272
+ function handleCloseInfo(ev: MouseEvent) {
273
+ showInfo = false
217
274
  ev.stopPropagation()
218
275
  }
219
276
 
220
277
  async function handleConnect(ev: SubmitEvent) {
221
278
  ev.preventDefault()
222
- bunkerPointer = await parseBunkerInput(bunkerInput.value)
223
- if (!bunkerPointer) {
224
- bunkerInput.setCustomValidity(
225
- 'invalid NIP-05 "name@domain.com" address or bunker:// URI'
226
- )
227
- return
228
- }
279
+ try {
280
+ bunkerPointer = await parseBunkerInput(bunkerInput.value)
281
+ if (!bunkerPointer) {
282
+ if (bunkerInput.value.match(BUNKER_REGEX)) {
283
+ errorMessage = connectBunkerError
284
+ } else {
285
+ errorMessage = connectNip05Error
286
+ }
287
+ return
288
+ }
229
289
 
230
- bunkerInput.setCustomValidity('')
231
- connect(new BunkerSigner(clientSecret, bunkerPointer!, bunkerSignerParams))
290
+ bunkerInput.setCustomValidity('')
291
+ errorMessage = ''
292
+ await connect()
293
+
294
+ // since we are connecting right now after the user has typed stuff
295
+ // wait until the connection has succeeded before loading user data
296
+ identify()
297
+ } catch (error) {
298
+ if (bunkerInput.value.match(BUNKER_REGEX)) {
299
+ errorMessage = connectBunkerError
300
+ } else {
301
+ errorMessage = connectNip05Error
302
+ }
303
+ connecting = false
304
+ }
232
305
  }
233
306
 
234
307
  async function handleDisconnect(ev: MouseEvent) {
235
308
  ev.preventDefault()
236
- localStorage.removeItem('wnj:bunkerPointer')
237
- if (win.isWnj) delete win.nostr
309
+ localStorage.removeItem(localStorageKeys.BUNKER_POINTER)
238
310
  reset()
239
311
  }
240
312
 
@@ -259,16 +331,20 @@
259
331
  ev.preventDefault()
260
332
  if (!chosenProvider) return
261
333
 
334
+ awaitingCreation = true
262
335
  let bunker = await createAccount(
263
336
  chosenProvider,
264
337
  bunkerSignerParams,
265
338
  nameInput.value,
266
339
  chosenProvider.domain
267
340
  )
341
+ awaitingCreation = false
268
342
 
269
343
  open()
270
344
  creating = false
271
345
 
346
+ bunkerPointer = bunker.bp
347
+ identify()
272
348
  connect(bunker)
273
349
  }
274
350
 
@@ -283,64 +359,74 @@
283
359
  }, 500)
284
360
 
285
361
  function handleAbortConnection() {
286
- longConnecting = false
362
+ takingTooLong = false
287
363
  connecting = false
288
- cancelConnection = true
289
- // TODO: Effectively abort the connection
364
+ rejectBunker('connection aborted')
365
+ reset()
290
366
  }
291
367
 
292
- async function connect(bunker: BunkerSigner) {
293
- let connectionTimeout: number
368
+ async function connect(b: BunkerSigner | undefined = undefined) {
369
+ b = b || new BunkerSigner(clientSecret, bunkerPointer!, bunkerSignerParams)
370
+ connecting = true
294
371
 
295
- function allowCancel() {
296
- longConnecting = true
372
+ let connectionTimeout = setTimeout(() => {
373
+ takingTooLong = true
297
374
  opened = true
375
+ }, 5000)
376
+
377
+ try {
378
+ await b.connect()
379
+ connected = true
380
+ localStorage.setItem(
381
+ localStorageKeys.BUNKER_POINTER,
382
+ JSON.stringify(bunkerPointer)
383
+ )
384
+ close()
385
+ resolveBunker(b)
386
+ } catch (err: any) {
387
+ rejectBunker(err?.message || String(err))
388
+ } finally {
389
+ // this still gets executed even if we return above
298
390
  clearTimeout(connectionTimeout)
391
+ connecting = false
392
+ takingTooLong = false
393
+ showAuth = null
394
+ showLogin = null
299
395
  }
396
+ }
300
397
 
301
- connecting = true
302
- connectionTimeout = setTimeout(allowCancel, 5000)
303
-
304
- await bunker.connect()
305
-
306
- clearTimeout(connectionTimeout)
307
- bunkerPointer = bunker.bp
398
+ function identify(onFirstMetadata: (() => void) | null = null) {
399
+ let pubkey = bunkerPointer!.pubkey
308
400
 
309
- // set this so the floating thing will update
310
- connected = {
311
- pubkey: bunker.bp.pubkey,
312
- npub: npubEncode(bunker.bp.pubkey),
401
+ identity = {
402
+ pubkey: pubkey,
403
+ npub: npubEncode(pubkey),
313
404
  event: null
314
405
  }
315
406
 
316
- localStorage.setItem('wnj:bunkerPointer', JSON.stringify(bunkerPointer))
317
-
318
- // load metadata
319
407
  metadataSub = pool.subscribeMany(
320
408
  [
321
409
  'wss://purplepag.es',
322
410
  'wss://relay.snort.social',
323
411
  'wss://relay.nos.social'
324
412
  ],
325
- [{kinds: [0], authors: [bunker.bp.pubkey]}],
413
+ [{kinds: [0], authors: [pubkey]}],
326
414
  {
327
415
  onevent(evt) {
328
- if ((connected!.event?.created_at || 0) >= evt.created_at) return
416
+ if ((identity!.event?.created_at || 0) >= evt.created_at) return
329
417
  try {
330
418
  let {name, picture} = JSON.parse(evt.content)
331
- connected!.event = evt
332
- connected!.name = name
333
- connected!.picture = picture
419
+ identity!.event = evt
420
+ identity!.name = name
421
+ identity!.picture = picture
422
+ onFirstMetadata?.()
423
+ onFirstMetadata = null
334
424
  } catch (err) {
335
425
  /***/
336
426
  }
337
427
  }
338
428
  }
339
429
  )
340
- connecting = false
341
- longConnecting = false
342
- close()
343
- resolveBunker(bunker)
344
430
  }
345
431
 
346
432
  function handleMouseDown(ev: MouseEvent) {
@@ -396,8 +482,8 @@
396
482
  ypos = BASE_YPOS
397
483
  }
398
484
 
399
- localStorage.setItem('wnj:origin', origin)
400
- localStorage.setItem('wnj:ypos', ypos.toString())
485
+ localStorage.setItem(localStorageKeys.ORIGIN, origin)
486
+ localStorage.setItem(localStorageKeys.Y_POS, ypos.toString())
401
487
  }
402
488
  }
403
489
  </script>
@@ -410,8 +496,8 @@
410
496
 
411
497
  <!-- svelte-ignore a11y-no-static-element-interactions -->
412
498
  <div
413
- class="tw-text-white tw-font-sans draggable tw-animate-fadein"
414
- class:tw-cursor-pointer={!connected && !opened}
499
+ class="draggable animate-fadein font-sans text-white"
500
+ class:cursor-pointer={!identity && !opened}
415
501
  style="position: fixed; {opened && $mobileMode
416
502
  ? 'width: 100%;'
417
503
  : ''}; right: {opened && $mobileMode
@@ -425,29 +511,29 @@
425
511
  <!-- Close status ################### -->
426
512
  {#if !opened}
427
513
  <div
428
- class="tw-px-4 tw-py-2 tw-bg-{accent}-700 hover:tw-bg-{accent}-800 tw-rounded tw-shadow-[0_0px_10px_0px_rgba(0,0,0,0.3)] tw-transition-all tw-duration-200 {movingStyle}"
514
+ class="rounded px-4 py-2 shadow-[0_0px_10px_0px_rgba(0,0,0,0.3)] transition-all duration-200 bg-{accent}-700 hover:bg-{accent}-800 {movingStyle}"
429
515
  >
430
516
  <!-- Connecting view ################### -->
431
517
  {#if connecting}
432
- <div class="tw-flex tw-items-center">
518
+ <div class="flex items-center">
433
519
  Connecting to bunker
434
520
  <Spinner />
435
521
  </div>
436
- {:else if !connected}
522
+ {:else if !identity}
437
523
  Connect with Nostr
438
524
  {:else}
439
- <div class="tw-flex tw-items-center">
440
- {#if connected.picture}
525
+ <div class="flex items-center">
526
+ {#if identity.picture}
441
527
  <img
442
- src={connected.picture}
528
+ src={identity.picture}
443
529
  alt=""
444
- class="tw-w-5 tw-h-5 tw-rounded-full tw-mr-2"
530
+ class="mr-2 h-5 w-5 rounded-full"
445
531
  />
446
532
  {:else}
447
533
 
448
534
  {/if}
449
- {connected.name ||
450
- connected.npub.slice(0, 7) + '…' + connected.npub.slice(-4)}
535
+ {identity.name ||
536
+ identity.npub.slice(0, 7) + '…' + identity.npub.slice(-4)}
451
537
  </div>
452
538
  {/if}
453
539
  </div>
@@ -455,77 +541,148 @@
455
541
  <!-- Open status ################### -->
456
542
  {:else}
457
543
  <div
458
- class="sm:tw-w-80 tw-px-6 tw-py-8 tw-bg-{accent}-700 tw-rounded-md tw-shadow-[0_0px_30px_0px_rgba(0,0,0,0.6)] tw-transition-all tw-animate-show {movingStyle}"
544
+ class="animate-show rounded-md bg-gradient-to-b px-8 py-8 shadow-[0_0px_30px_0px_rgba(0,0,0,0.6)] transition-all sm:w-96 from-{accent}-900 to-{accent}-700 {movingStyle}"
459
545
  >
460
546
  <button
461
- on:click={handleCloseModal}
462
- class="tw-absolute tw-top-0 tw-right-0.5 tw-bg-transparent tw-border-none tw-cursor-pointer tw-text-{accent}-950 tw-text-3xl"
547
+ on:click={showInfo ? handleCloseInfo : handleCloseModal}
548
+ class="absolute right-2 top-0 cursor-pointer bg-transparent text-3xl text-{accent}-600"
463
549
  >⤫</button
464
550
  >
465
- <!-- Create account view ################### -->
466
- {#if creating}
467
- <div class="tw-text-lg tw-text-center">Create a Nostr account</div>
468
- <form class="tw-mt-4 tw-mb-1" on:submit={handleCreate}>
469
- <div class="tw-flex items-center">
551
+
552
+ {#if !showInfo && !showAuth && !showLogin}
553
+ <button
554
+ on:click={handleShowInfo}
555
+ class="absolute bottom-1 right-3 cursor-pointer bg-transparent text-xl text-{accent}-600"
556
+ >?</button
557
+ >
558
+ {/if}
559
+
560
+ {#if showAuth}
561
+ <div class="m-auto w-full">
562
+ <div class="text-center text-lg">Create a Nostr account</div>
563
+ <div class="mt-4 text-center text-sm leading-4">
564
+ Now you a new window will bring you to <strong>{new URL(showAuth).host}</strong> where the account creation will take place. If nothing happens check that if your browser is blocking popups, pleaase.<br/>
565
+ After that you will be returned to this page.
566
+ </div>
567
+ <button
568
+ 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"
569
+ on:click={() => openAuthURLPopup(showAuth)}>
570
+ Start account creation »
571
+ </button>
572
+ </div>
573
+
574
+ {:else if showLogin}
575
+ <div class="m-auto w-full">
576
+ <div class="text-center text-lg">Login into a Nostr account</div>
577
+ <div class="mt-4 text-center text-sm leading-4">
578
+ Now you a new window will bring you to <strong>{new URL(showLogin).host}</strong> where you can login and approve the permissions. If nothing happens check that if your browser is blocking popups, pleaase.<br/>
579
+ After that you will be returned to this page.
580
+ </div>
581
+ <button
582
+ 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"
583
+ on:click={() => openAuthURLPopup(showLogin)}>
584
+ Login now »
585
+ </button>
586
+ </div>
587
+
588
+ <!-- Show info ################### -->
589
+ {:else if showInfo}
590
+ <div class="text-center text-lg">What is that?</div>
591
+ <div class="text-base leading-5">
592
+ <p class="mb mt-4">
593
+ This widget is created with <i>window.nostr.js</i>, a small script
594
+ you can drop in any page that already uses NIP-07 and make it also
595
+ work with NIP-46 automatically when the user doesn't have an
596
+ extension installed.
597
+ <br />
598
+ It adds a small floating button on the side of the window that users
599
+ can use to create Nostr accuonts or connect to their NIP-46 bunkers.
600
+ </p>
601
+ <p class="mt-4">
602
+ This tool is opensource, get the code from the <a
603
+ target="_blank"
604
+ class="underline"
605
+ href="https://github.com/fiatjaf/window.nostr.js"
606
+ >project's page</a
607
+ >.
608
+ </p>
609
+ <p class="mt-4">
610
+ You don't know what Nostr is?
611
+ <a target="_blank" class="underline" href="https://www.nostr.com"
612
+ >Learn more</a
613
+ >.
614
+ </p>
615
+ </div>
616
+
617
+ <!-- Create account view ################### -->
618
+ {:else if creating}
619
+ <div class="text-center text-lg">Create a Nostr account</div>
620
+ <form class="mb-1 mt-4" on:submit={handleCreate}>
621
+ <div class="flex flex-row">
470
622
  <!-- svelte-ignore a11y-autofocus -->
471
623
  <input
472
- class="tw-box-border tw-w-40 tw-px-2 tw-py-1 tw-rounded tw-text-lg tw-border-none tw-outline-none"
624
+ class="box-border w-40 rounded px-2 py-1 text-lg text-neutral-800 outline-none"
473
625
  placeholder="bob"
474
626
  bind:this={nameInput}
475
627
  bind:value={nameInputValue}
476
628
  on:input={checkNameInput}
477
629
  autofocus
630
+ autocapitalize="none"
478
631
  />
479
- <div class="tw-mx-2 tw-text-2xl">@</div>
632
+ <div class="mx-2 text-2xl">@</div>
480
633
  <select
481
- class="tw-w-full tw-box-border tw-px-2 tw-py-1 tw-rounded tw-text-lg tw-border-none tw-outline-none"
634
+ class="box-border w-full rounded px-2 py-1 text-lg text-neutral-800 outline-none"
482
635
  bind:value={chosenProvider}
483
636
  >
484
637
  {#each providers as prov}
485
638
  <option
486
639
  label={prov.domain}
487
640
  value={prov}
488
- class="tw-px-2 tw-py-1 tw-text-lg"
641
+ class="px-2 py-1 text-lg"
489
642
  />
490
643
  {/each}
491
644
  </select>
492
645
  </div>
493
- <div class="tw-text-sm tw-text-center tw-mt-4 tw-leading-4">
494
- A window from the selected provider will pop up to finalize the
495
- creation; if it doesn't display check if the browser is blocking it
496
- </div>
497
646
  <button
498
- class="tw-block tw-w-full tw-mt-4 tw-px-2 tw-py-1 tw-text-lg tw-rounded tw-border-0 tw-bg-{accent}-900 hover:tw-bg-{accent}-950 tw-hover:bg-indigo-900 tw-cursor-pointer tw-text-white disabled:tw-bg-neutral-400 disabled:tw-text-neutral-200 disabled:tw-cursor-default"
499
- disabled={!chosenProvider || !nameInputValue}
647
+ 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"
648
+ disabled={!chosenProvider || !nameInputValue || awaitingCreation}
500
649
  >
501
- Create »
650
+ Continue »
502
651
  </button>
503
652
  </form>
504
- <div class="tw-mt-6 tw-text-center tw-text-sm tw-leading-3">
653
+ <div class="mt-6 text-center text-sm leading-3">
505
654
  Do you already have a Nostr address?<br />
506
655
  <button
507
- class="tw-border-0 tw-bg-transparent tw-text-white tw-cursor-pointer tw-underline tw-text-sm"
656
+ class="cursor-pointer border-0 bg-transparent text-sm text-white underline"
508
657
  on:click={handleOpenLogin}>Login now</button
509
658
  >
510
659
  </div>
511
660
 
512
661
  <!-- Login view ################### -->
513
- {:else if !connected}
514
- <div class="tw-text-lg tw-text-center">
662
+ {:else if !identity}
663
+ <div class="text-center text-lg">
515
664
  How do you want to connect to Nostr?
516
665
  </div>
517
- <form class="flex tw-mt-4 tw-mb-1" on:submit={handleConnect}>
666
+ <form class="mb-1 mt-4 flex flex-col" on:submit={handleConnect}>
518
667
  <!-- svelte-ignore a11y-autofocus -->
519
668
  <input
520
- class="tw-box-border tw-w-full tw-px-2 tw-py-1 tw-rounded tw-text-lg tw-border-none tw-outline-none"
669
+ class="box-border w-full rounded px-2 py-1 text-lg text-neutral-800 outline-none"
521
670
  placeholder="user@provider or bunker://..."
522
671
  bind:this={bunkerInput}
523
672
  bind:value={bunkerInputValue}
524
673
  autofocus
525
674
  disabled={connecting}
675
+ autocapitalize="none"
526
676
  />
677
+ {#if errorMessage}
678
+ <div
679
+ class="my-2 rounded bg-yellow-100 p-2 text-center text-sm leading-4 text-red-400"
680
+ >
681
+ {errorMessage}
682
+ </div>
683
+ {/if}
527
684
  <button
528
- class="tw-flex tw-w-full tw-mt-4 tw-px-2 tw-py-1 tw-text-lg tw-rounded tw-border-0 tw-bg-{accent}-900 hover:tw-bg-{accent}-950 tw-hover:bg-indigo-900 tw-cursor-pointer tw-text-white disabled:tw-bg-neutral-400 disabled:tw-text-neutral-200 disabled:tw-cursor-default tw-items-center tw-justify-center"
685
+ class="mt-4 flex w-full cursor-pointer items-center justify-center 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"
529
686
  disabled={!bunkerInputValueIsGood || connecting}
530
687
  >
531
688
  {#if connecting}
@@ -535,63 +692,61 @@
535
692
  Connect »
536
693
  {/if}
537
694
  </button>
538
- {#if connecting && longConnecting}
539
- <div class="tw-mt-6 tw-text-center tw-text-sm tw-leading-3">
695
+ {#if connecting && takingTooLong}
696
+ <div class="mt-6 text-center text-sm leading-3">
540
697
  Waiting too much?
541
698
  <button
542
- class="tw-border-0 tw-bg-transparent tw-text-white tw-cursor-pointer tw-underline tw-text-sm"
699
+ class="cursor-pointer border-0 bg-transparent text-sm text-white underline"
543
700
  on:click={handleAbortConnection}>Cancel the connection</button
544
701
  >
545
702
  </div>
546
703
  {/if}
547
704
  </form>
548
705
  {#if !connecting}
549
- <div class="tw-mt-6 tw-text-center tw-text-sm tw-leading-3">
706
+ <div class="mt-6 text-center text-sm leading-3">
550
707
  Do you need a Nostr account?<br />
551
708
  <button
552
- class="tw-border-0 tw-bg-transparent tw-text-white tw-cursor-pointer tw-underline tw-text-sm"
709
+ class="cursor-pointer border-0 bg-transparent text-sm text-white underline"
553
710
  on:click={handleOpenCreate}>Sign up now</button
554
711
  >
555
712
  </div>
556
713
  {/if}
557
714
 
558
715
  <!-- Connected view ################### -->
559
- {:else if connected}
560
- <div class="tw-text-center">
561
- <div class="tw-text-sm tw-mb-4">You are connected to Nostr as</div>
716
+ {:else}
717
+ <div class="text-center">
718
+ <div class="mb-4 text-sm">You are connected to Nostr as</div>
562
719
  <a
563
720
  target="_blank"
564
- href={'https://nosta.me/' + connected.npub}
565
- class="tw-text-white tw-no-underline tw-group"
721
+ href={'https://nosta.me/' + identity.npub}
722
+ class="group text-white no-underline"
566
723
  >
567
- {#if connected.picture || connected.name}
568
- <div
569
- class="tw-flex tw-items-center tw-justify-center tw-gap-2 tw-mb-2"
570
- >
571
- {#if connected.picture}
724
+ {#if identity.picture || identity.name}
725
+ <div class="mb-2 flex items-center justify-center gap-2">
726
+ {#if identity.picture}
572
727
  <img
573
- src={connected.picture}
728
+ src={identity.picture}
574
729
  alt=""
575
- class="tw-w-10 tw-h-10 tw-rounded-full tw-border-solid tw-border-2 tw-border-transparent group-hover:tw-border-{accent}-100"
730
+ class="h-10 w-10 rounded-full border-2 border-solid border-transparent group-hover:border-{accent}-100"
576
731
  />
577
732
  {/if}
578
- {#if connected.name}
733
+ {#if identity.name}
579
734
  <div
580
- class="tw-text-3xl group-hover:tw-underline tw-decoration-2 tw-underline-offset-4"
735
+ class="text-3xl decoration-2 underline-offset-4 group-hover:underline"
581
736
  >
582
- {connected.name}
737
+ {identity.name}
583
738
  </div>
584
739
  {/if}
585
740
  </div>
586
741
  {/if}
587
- <div class="tw-block tw-break-all">{connected.npub}</div>
742
+ <div class="block break-all">{identity.npub}</div>
588
743
  </a>
589
744
  </div>
590
745
  <button
591
- class="tw-block tw-w-full tw-my-2 tw-mt-6 tw-px-2 tw-py-1 tw-text-lg tw-rounded tw-border-0 tw-bg-{accent}-900 hover:tw-bg-{accent}-950 tw-hover:bg-indigo-900 tw-cursor-pointer tw-text-white"
746
+ class="my-2 mt-6 block w-full cursor-pointer rounded border-0 px-2 py-1 text-lg text-white bg-{accent}-900 hover:bg-{accent}-950"
592
747
  on:click={handleDisconnect}>Disconnect</button
593
748
  >
594
- <div class="tw-block tw-break-all tw-mt-6 tw-text-center tw-text-sm">
749
+ <div class="mt-6 block break-all text-center text-sm">
595
750
  This webpage is using the public key:<br />
596
751
  {getPublicKey(clientSecret)}
597
752
  </div>
@@ -599,13 +754,3 @@
599
754
  </div>
600
755
  {/if}
601
756
  </div>
602
-
603
- <!-- hack to preload tailwind colors:
604
- tw-bg-cyan-700 tw-bg-cyan-800 tw-bg-cyan-900 tw-bg-cyan-950 tw-text-cyan-950 tw-outline-cyan-500
605
- tw-bg-green-700 tw-bg-green-800 tw-bg-green-900 tw-bg-green-950 tw-text-green-950 tw-outline-green-500
606
- tw-bg-purple-700 tw-bg-purple-800 tw-bg-purple-900 tw-bg-purple-950 tw-text-purple-950 tw-outline-purple-500
607
- tw-bg-red-700 tw-bg-red-800 tw-bg-red-900 tw-bg-red-950 tw-text-red-950 tw-outline-red-500
608
- tw-bg-orange-700 tw-bg-orange-800 tw-bg-orange-900 tw-bg-orange-950 tw-text-orange-950 tw-outline-orange-500
609
- tw-bg-neutral-700 tw-bg-neutral-800 tw-bg-neutral-900 tw-bg-neutral-950 tw-text-neutral-950 tw-outline-neutral-500
610
- tw-bg-stone-700 tw-bg-stone-800 tw-bg-stone-900 tw-bg-stone-950 tw-text-stone-950 tw-outline-stone-500
611
- -->