@nsite/stealthis 0.5.0 → 0.7.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/widget.ts CHANGED
@@ -1,64 +1,88 @@
1
- import { STYLES } from './styles';
2
- import * as nostr from './nostr';
3
- import { toSvg } from './qr';
1
+ import STYLES from "./styles.css?inline";
2
+ import * as nostr from "./nostr";
3
+ import { toSvg } from "./qr";
4
4
  import {
5
- hasExtension,
6
- extensionSigner,
7
5
  bunkerConnect,
8
- prepareNostrConnect,
9
6
  DEFAULT_NIP46_RELAY,
10
- type Signer
11
- } from './signer';
7
+ extensionSigner,
8
+ hasExtension,
9
+ prepareNostrConnect,
10
+ type Signer,
11
+ } from "./signer";
12
12
 
13
13
  type State =
14
- | 'idle'
15
- | 'auth'
16
- | 'connecting'
17
- | 'loading'
18
- | 'form'
19
- | 'confirm'
20
- | 'deploying'
21
- | 'success'
22
- | 'error';
14
+ | "idle"
15
+ | "auth"
16
+ | "connecting"
17
+ | "loading"
18
+ | "form"
19
+ | "confirm"
20
+ | "deploying"
21
+ | "success"
22
+ | "error";
23
23
 
24
24
  export class NsiteDeployButton extends HTMLElement {
25
- static observedAttributes = ['button-text', 'stat-text', 'no-trail'];
25
+ static observedAttributes = [
26
+ "button-text",
27
+ "stat-text",
28
+ "no-trail",
29
+ "obfuscate-npubs",
30
+ "do-not-fetch-muse-data",
31
+ ];
26
32
 
27
33
  private shadow: ShadowRoot;
28
- private state: State = 'idle';
34
+ private state: State = "idle";
29
35
  private ctx: nostr.NsiteContext | null = null;
30
36
  private manifest: nostr.SignedEvent | null = null;
31
37
  private signer: Signer | null = null;
32
- private userPubkey = '';
38
+ private userPubkey = "";
33
39
  private userRelays: string[] = [];
34
- private deployedUrl = '';
35
- private errorMsg = '';
36
- private statusMsg = '';
37
- private slug = '';
38
- private siteTitle = '';
39
- private siteDescription = '';
40
+ private deployedUrl = "";
41
+ private errorMsg = "";
42
+ private statusMsg = "";
43
+ private slug = "";
44
+ private siteTitle = "";
45
+ private siteDescription = "";
40
46
  private deployAsRoot = true;
41
47
  private hasRootSite: boolean | null = null; // null = still checking
42
- private nostrConnectUri = '';
48
+ private nostrConnectUri = "";
43
49
  private nip46Relay = DEFAULT_NIP46_RELAY;
44
50
  private qrAbort: AbortController | null = null;
45
51
  private ncConnect: ReturnType<typeof prepareNostrConnect> | null = null;
46
52
  private manifestPromise: Promise<nostr.SignedEvent | null> | null = null;
47
53
  private relaysPromise: Promise<string[]> | null = null;
48
54
  private muses: nostr.Muse[] = [];
55
+ private museProfiles = new Map<string, nostr.MuseProfile>();
49
56
  private musesExpanded = false;
50
57
 
51
58
  constructor() {
52
59
  super();
53
- this.shadow = this.attachShadow({ mode: 'open' });
60
+ this.shadow = this.attachShadow({ mode: "open" });
54
61
  this.ctx = nostr.parseContext();
55
62
  if (this.ctx) {
56
63
  this.manifestPromise = nostr.fetchManifest(this.ctx);
57
- if (!this.hasAttribute('no-trail')) {
64
+ if (!this.hasAttribute("no-trail")) {
58
65
  this.manifestPromise.then((manifest) => {
59
66
  if (manifest) {
60
67
  this.muses = nostr.extractMuses(manifest);
61
- if (this.muses.length > 0 && this.state === 'idle') this.render();
68
+ if (this.muses.length > 0 && this.state === "idle") this.render();
69
+ // Start streaming profile enrichment unless opted out
70
+ if (
71
+ this.muses.length > 0 &&
72
+ !this.hasAttribute("do-not-fetch-muse-data") &&
73
+ !this.hasAttribute("obfuscate-npubs")
74
+ ) {
75
+ nostr.fetchMuseProfiles(
76
+ this.muses,
77
+ this.ctx!.baseDomain,
78
+ (pubkey, profile) => {
79
+ this.museProfiles.set(pubkey, profile);
80
+ if (this.state === "idle" && this.musesExpanded) {
81
+ this.render();
82
+ }
83
+ },
84
+ );
85
+ }
62
86
  }
63
87
  });
64
88
  }
@@ -67,23 +91,23 @@ export class NsiteDeployButton extends HTMLElement {
67
91
  }
68
92
 
69
93
  attributeChangedCallback() {
70
- if (this.state === 'idle') this.render();
94
+ if (this.state === "idle") this.render();
71
95
  }
72
96
 
73
97
  private get buttonText(): string {
74
- return this.getAttribute('button-text') || 'Borrow this nsite';
98
+ return this.getAttribute("button-text") || "Borrow this nsite";
75
99
  }
76
100
 
77
101
  private get statText(): string {
78
- return this.getAttribute('stat-text') || '%s npubs borrowed this nsite';
102
+ return this.getAttribute("stat-text") || "%s npubs borrowed this nsite";
79
103
  }
80
104
 
81
105
  private esc(s: string): string {
82
106
  return s
83
- .replace(/&/g, '&amp;')
84
- .replace(/</g, '&lt;')
85
- .replace(/>/g, '&gt;')
86
- .replace(/"/g, '&quot;');
107
+ .replace(/&/g, "&amp;")
108
+ .replace(/</g, "&lt;")
109
+ .replace(/>/g, "&gt;")
110
+ .replace(/"/g, "&quot;");
87
111
  }
88
112
 
89
113
  private modal(body: string): string {
@@ -95,53 +119,65 @@ export class NsiteDeployButton extends HTMLElement {
95
119
  let html = `<style>${STYLES}</style>`;
96
120
 
97
121
  switch (this.state) {
98
- case 'idle':
99
- html += `<button class="nd-trigger" part="trigger">${this.esc(this.buttonText)}</button>`;
122
+ case "idle":
123
+ html += `<button class="nd-trigger" part="trigger">${
124
+ this.esc(this.buttonText)
125
+ }</button>`;
100
126
  html += this.paperTrailContent();
101
127
  break;
102
128
 
103
- case 'auth':
104
- html += `<button class="nd-trigger" disabled>${this.esc(this.buttonText)}</button>`;
129
+ case "auth":
130
+ html += `<button class="nd-trigger" disabled>${
131
+ this.esc(this.buttonText)
132
+ }</button>`;
105
133
  html += this.modal(this.authContent());
106
134
  break;
107
135
 
108
- case 'connecting':
136
+ case "connecting":
109
137
  html += `<button class="nd-trigger" disabled>Connecting...</button>`;
110
138
  html += this.modal(`
111
139
  <div class="nd-header">
112
140
  <h2 class="nd-title">Connecting</h2>
113
141
  <button class="nd-close" data-action="close">&times;</button>
114
142
  </div>
115
- <div class="nd-msg"><span class="nd-spinner"></span>${this.esc(this.statusMsg)}</div>
143
+ <div class="nd-msg"><span class="nd-spinner"></span>${
144
+ this.esc(this.statusMsg)
145
+ }</div>
116
146
  `);
117
147
  break;
118
148
 
119
- case 'loading':
149
+ case "loading":
120
150
  html += `<button class="nd-trigger" disabled>Loading...</button>`;
121
151
  html += this.modal(`
122
152
  <div class="nd-msg"><span class="nd-spinner"></span>Fetching site manifest...</div>
123
153
  `);
124
154
  break;
125
155
 
126
- case 'form':
127
- html += `<button class="nd-trigger" disabled>${this.esc(this.buttonText)}</button>`;
156
+ case "form":
157
+ html += `<button class="nd-trigger" disabled>${
158
+ this.esc(this.buttonText)
159
+ }</button>`;
128
160
  html += this.modal(this.formContent());
129
161
  break;
130
162
 
131
- case 'confirm':
132
- html += `<button class="nd-trigger" disabled>${this.esc(this.buttonText)}</button>`;
163
+ case "confirm":
164
+ html += `<button class="nd-trigger" disabled>${
165
+ this.esc(this.buttonText)
166
+ }</button>`;
133
167
  html += this.modal(this.confirmContent());
134
168
  break;
135
169
 
136
- case 'deploying':
170
+ case "deploying":
137
171
  html += `<button class="nd-trigger" disabled>Deploying...</button>`;
138
172
  html += this.modal(`
139
173
  <h2 class="nd-title">Deploying</h2>
140
- <div class="nd-msg"><span class="nd-spinner"></span>${this.esc(this.statusMsg)}</div>
174
+ <div class="nd-msg"><span class="nd-spinner"></span>${
175
+ this.esc(this.statusMsg)
176
+ }</div>
141
177
  `);
142
178
  break;
143
179
 
144
- case 'success':
180
+ case "success":
145
181
  html += `<button class="nd-trigger" disabled>Deployed!</button>`;
146
182
  html += this.modal(`
147
183
  <div class="nd-header">
@@ -149,15 +185,19 @@ export class NsiteDeployButton extends HTMLElement {
149
185
  <button class="nd-close" data-action="close">&times;</button>
150
186
  </div>
151
187
  <div class="nd-status nd-status-ok">Your site is live</div>
152
- <a class="nd-link" href="${this.esc(this.deployedUrl)}" target="_blank" rel="noopener">${this.esc(this.deployedUrl)}</a>
188
+ <a class="nd-link" href="${
189
+ this.esc(this.deployedUrl)
190
+ }" target="_blank" rel="noopener">${this.esc(this.deployedUrl)}</a>
153
191
  <div class="nd-actions">
154
192
  <button class="nd-btn nd-btn-secondary" data-action="close">Close</button>
155
193
  </div>
156
194
  `);
157
195
  break;
158
196
 
159
- case 'error':
160
- html += `<button class="nd-trigger" part="trigger">${this.esc(this.buttonText)}</button>`;
197
+ case "error":
198
+ html += `<button class="nd-trigger" part="trigger">${
199
+ this.esc(this.buttonText)
200
+ }</button>`;
161
201
  html += this.modal(`
162
202
  <div class="nd-header">
163
203
  <h2 class="nd-title">Error</h2>
@@ -178,10 +218,12 @@ export class NsiteDeployButton extends HTMLElement {
178
218
  // --- Content builders ---
179
219
 
180
220
  private paperTrailContent(): string {
181
- if (this.hasAttribute('no-trail') || this.muses.length === 0) return '';
221
+ if (this.hasAttribute("no-trail") || this.muses.length === 0) return "";
182
222
 
183
223
  let html = `<div class="nd-trail">
184
- <button class="nd-trail-toggle" data-action="toggle-trail">${this.esc(this.statText.replace('%s', String(this.muses.length)))}</button>`;
224
+ <button class="nd-trail-toggle" data-action="toggle-trail">${
225
+ this.esc(this.statText.replace("%s", String(this.muses.length)))
226
+ }</button>`;
185
227
 
186
228
  if (this.musesExpanded) {
187
229
  html += `<div class="nd-trail-list">`;
@@ -210,16 +252,37 @@ export class NsiteDeployButton extends HTMLElement {
210
252
 
211
253
  private museItem(muse: nostr.Muse): string {
212
254
  const npub = nostr.npubEncode(muse.pubkey);
213
- const short = npub.slice(0, 12) + '...' + npub.slice(-4);
255
+
256
+ // obfuscate-npubs: truncated npub, no link (legacy behavior)
257
+ if (this.hasAttribute("obfuscate-npubs")) {
258
+ const short = npub.slice(0, 12) + "..." + npub.slice(-4);
259
+ return `<div class="nd-trail-item">
260
+ <span class="nd-trail-idx">#${muse.index}</span>
261
+ <span class="nd-trail-pk">${short}</span>
262
+ </div>`;
263
+ }
264
+
265
+ // do-not-fetch-muse-data (without obfuscate): full npub linked to njump
266
+ if (this.hasAttribute("do-not-fetch-muse-data")) {
267
+ return `<div class="nd-trail-item">
268
+ <span class="nd-trail-idx">#${muse.index}</span>
269
+ <a class="nd-trail-link" href="https://njump.me/${this.esc(npub)}" target="_blank" rel="noopener">${this.esc(npub)}</a>
270
+ </div>`;
271
+ }
272
+
273
+ // Default: enriched display — name + link to nsite or njump
274
+ const profile = this.museProfiles.get(muse.pubkey);
275
+ const displayName = profile?.name || npub;
276
+ const href = profile?.nsiteUrl || `https://njump.me/${npub}`;
214
277
  return `<div class="nd-trail-item">
215
278
  <span class="nd-trail-idx">#${muse.index}</span>
216
- <span class="nd-trail-pk">${short}</span>
279
+ <a class="nd-trail-link" href="${this.esc(href)}" target="_blank" rel="noopener">${this.esc(displayName)}</a>
217
280
  </div>`;
218
281
  }
219
282
 
220
283
  private authContent(): string {
221
284
  const ext = hasExtension();
222
- const qrSvg = this.nostrConnectUri ? toSvg(this.nostrConnectUri) : '';
285
+ const qrSvg = this.nostrConnectUri ? toSvg(this.nostrConnectUri) : "";
223
286
 
224
287
  let html = `
225
288
  <div class="nd-header">
@@ -250,10 +313,14 @@ export class NsiteDeployButton extends HTMLElement {
250
313
  <div class="nd-qr-code">${qrSvg}</div>
251
314
  <div class="nd-relay-row">
252
315
  <label>relay</label>
253
- <input type="text" id="nd-nip46-relay" value="${this.esc(this.nip46Relay)}" />
316
+ <input type="text" id="nd-nip46-relay" value="${
317
+ this.esc(this.nip46Relay)
318
+ }" />
254
319
  </div>
255
320
  <div class="nd-qr-uri">
256
- <input readonly value="${this.esc(this.nostrConnectUri)}" id="nd-nc-uri" />
321
+ <input readonly value="${
322
+ this.esc(this.nostrConnectUri)
323
+ }" id="nd-nc-uri" />
257
324
  <button data-action="copy-uri">Copy</button>
258
325
  </div>
259
326
  </div>
@@ -268,19 +335,30 @@ export class NsiteDeployButton extends HTMLElement {
268
335
  <h2 class="nd-title">Deploy this Page</h2>
269
336
  <button class="nd-close" data-action="close">&times;</button>
270
337
  </div>
271
- ${this.hasRootSite ? `<div class="nd-toggle">
272
- <button class="nd-toggle-btn ${this.deployAsRoot ? 'active' : ''}" data-action="type-root">Root Site</button>
273
- <button class="nd-toggle-btn ${!this.deployAsRoot ? 'active' : ''}" data-action="type-named">Named Site</button>
274
- </div>` : ''}`;
338
+ ${
339
+ this.hasRootSite
340
+ ? `<div class="nd-toggle">
341
+ <button class="nd-toggle-btn ${
342
+ this.deployAsRoot ? "active" : ""
343
+ }" data-action="type-root">Root Site</button>
344
+ <button class="nd-toggle-btn ${
345
+ !this.deployAsRoot ? "active" : ""
346
+ }" data-action="type-named">Named Site</button>
347
+ </div>`
348
+ : ""
349
+ }`;
275
350
 
276
351
  if (this.deployAsRoot) {
277
- html += `<div class="nd-root-hint">Your primary site, served at your npub subdomain.</div>`;
352
+ html +=
353
+ `<div class="nd-root-hint">Your primary site, served at your npub subdomain.</div>`;
278
354
  } else {
279
355
  html += `
280
356
  <div class="nd-root-hint">A sub-site with its own name, served alongside your root site.</div>
281
357
  <div class="nd-field">
282
358
  <label for="nd-slug">Site name</label>
283
- <input id="nd-slug" type="text" placeholder="my-site" value="${this.esc(this.slug)}" maxlength="13" autocomplete="off" />
359
+ <input id="nd-slug" type="text" placeholder="my-site" value="${
360
+ this.esc(this.slug)
361
+ }" maxlength="13" autocomplete="off" />
284
362
  <div class="nd-hint">Lowercase a-z, 0-9, hyphens. 1-13 chars.</div>
285
363
  <div class="nd-field-error" id="nd-slug-err"></div>
286
364
  </div>`;
@@ -289,16 +367,26 @@ export class NsiteDeployButton extends HTMLElement {
289
367
  html += `
290
368
  <div class="nd-field">
291
369
  <label for="nd-title">Title</label>
292
- <input id="nd-title" type="text" placeholder="Optional" value="${this.esc(this.siteTitle)}" />
370
+ <input id="nd-title" type="text" placeholder="Optional" value="${
371
+ this.esc(this.siteTitle)
372
+ }" />
293
373
  </div>
294
374
  <div class="nd-field">
295
375
  <label for="nd-desc">Description</label>
296
- <textarea id="nd-desc" placeholder="Optional">${this.esc(this.siteDescription)}</textarea>
376
+ <textarea id="nd-desc" placeholder="Optional">${
377
+ this.esc(this.siteDescription)
378
+ }</textarea>
297
379
  </div>
298
380
  <div class="nd-actions">
299
381
  <button class="nd-btn nd-btn-secondary" data-action="close">Cancel</button>
300
- <button class="nd-btn nd-btn-primary" data-action="deploy" ${this.hasRootSite === null ? 'disabled' : ''}>
301
- ${this.hasRootSite === null ? '<span class="nd-spinner"></span>Checking...' : 'Deploy'}
382
+ <button class="nd-btn nd-btn-primary" data-action="deploy" ${
383
+ this.hasRootSite === null ? "disabled" : ""
384
+ }>
385
+ ${
386
+ this.hasRootSite === null
387
+ ? '<span class="nd-spinner"></span>Checking...'
388
+ : "Deploy"
389
+ }
302
390
  </button>
303
391
  </div>`;
304
392
 
@@ -306,7 +394,9 @@ export class NsiteDeployButton extends HTMLElement {
306
394
  }
307
395
 
308
396
  private confirmContent(): string {
309
- const what = this.deployAsRoot ? 'a root site' : `a site named "${this.esc(this.slug)}"`;
397
+ const what = this.deployAsRoot
398
+ ? "a root site"
399
+ : `a site named "${this.esc(this.slug)}"`;
310
400
  return `
311
401
  <div class="nd-header">
312
402
  <h2 class="nd-title">Site Already Exists</h2>
@@ -325,53 +415,57 @@ export class NsiteDeployButton extends HTMLElement {
325
415
 
326
416
  private bind() {
327
417
  this.shadow
328
- .querySelector('.nd-trigger:not([disabled])')
329
- ?.addEventListener('click', () => this.open());
418
+ .querySelector(".nd-trigger:not([disabled])")
419
+ ?.addEventListener("click", () => this.open());
330
420
  this.shadow
331
421
  .querySelectorAll('[data-action="close"]')
332
- .forEach((el) => el.addEventListener('click', () => this.close()));
422
+ .forEach((el) => el.addEventListener("click", () => this.close()));
333
423
  this.shadow
334
424
  .querySelector('[data-action="deploy"]')
335
- ?.addEventListener('click', () => this.onDeploy());
425
+ ?.addEventListener("click", () => this.onDeploy());
336
426
  this.shadow
337
427
  .querySelector('[data-action="confirm-deploy"]')
338
- ?.addEventListener('click', () => this.executeDeploy());
428
+ ?.addEventListener("click", () => this.executeDeploy());
339
429
  this.shadow
340
430
  .querySelector('[data-action="back"]')
341
- ?.addEventListener('click', () => this.setState('form'));
431
+ ?.addEventListener("click", () => this.setState("form"));
342
432
  this.shadow
343
433
  .querySelector('[data-action="ext"]')
344
- ?.addEventListener('click', () => this.authExtension());
434
+ ?.addEventListener("click", () => this.authExtension());
345
435
  this.shadow
346
436
  .querySelector('[data-action="bunker"]')
347
- ?.addEventListener('click', () => this.authBunker());
437
+ ?.addEventListener("click", () => this.authBunker());
348
438
  this.shadow
349
439
  .querySelector('[data-action="copy-uri"]')
350
- ?.addEventListener('click', () => this.copyUri());
440
+ ?.addEventListener("click", () => this.copyUri());
351
441
  this.shadow
352
442
  .querySelector('[data-action="toggle-trail"]')
353
- ?.addEventListener('click', () => {
443
+ ?.addEventListener("click", () => {
354
444
  this.musesExpanded = !this.musesExpanded;
355
445
  this.render();
356
446
  });
357
447
  this.shadow
358
448
  .querySelector('[data-action="type-named"]')
359
- ?.addEventListener('click', () => {
449
+ ?.addEventListener("click", () => {
360
450
  this.deployAsRoot = false;
361
- this.setState('form');
451
+ this.setState("form");
362
452
  });
363
453
  this.shadow
364
454
  .querySelector('[data-action="type-root"]')
365
- ?.addEventListener('click', () => {
455
+ ?.addEventListener("click", () => {
366
456
  this.deployAsRoot = true;
367
- this.setState('form');
457
+ this.setState("form");
368
458
  });
369
- this.shadow.querySelector('.nd-overlay')?.addEventListener('click', (e) => {
370
- if ((e.target as HTMLElement).classList.contains('nd-overlay')) this.close();
459
+ this.shadow.querySelector(".nd-overlay")?.addEventListener("click", (e) => {
460
+ if ((e.target as HTMLElement).classList.contains("nd-overlay")) {
461
+ this.close();
462
+ }
371
463
  });
372
464
 
373
465
  // Relay editing — regenerate QR on blur or Enter
374
- const relayInput = this.shadow.querySelector('#nd-nip46-relay') as HTMLInputElement | null;
466
+ const relayInput = this.shadow.querySelector("#nd-nip46-relay") as
467
+ | HTMLInputElement
468
+ | null;
375
469
  if (relayInput) {
376
470
  const commitRelay = () => {
377
471
  const v = relayInput.value.trim();
@@ -380,9 +474,9 @@ export class NsiteDeployButton extends HTMLElement {
380
474
  this.startNostrConnect();
381
475
  }
382
476
  };
383
- relayInput.addEventListener('blur', commitRelay);
384
- relayInput.addEventListener('keydown', (e) => {
385
- if (e.key === 'Enter') {
477
+ relayInput.addEventListener("blur", commitRelay);
478
+ relayInput.addEventListener("keydown", (e) => {
479
+ if (e.key === "Enter") {
386
480
  e.preventDefault();
387
481
  commitRelay();
388
482
  }
@@ -391,10 +485,16 @@ export class NsiteDeployButton extends HTMLElement {
391
485
  }
392
486
 
393
487
  private preserveFormValues() {
394
- if (this.state !== 'form') return;
395
- const slug = this.shadow.querySelector('#nd-slug') as HTMLInputElement | null;
396
- const title = this.shadow.querySelector('#nd-title') as HTMLInputElement | null;
397
- const desc = this.shadow.querySelector('#nd-desc') as HTMLTextAreaElement | null;
488
+ if (this.state !== "form") return;
489
+ const slug = this.shadow.querySelector("#nd-slug") as
490
+ | HTMLInputElement
491
+ | null;
492
+ const title = this.shadow.querySelector("#nd-title") as
493
+ | HTMLInputElement
494
+ | null;
495
+ const desc = this.shadow.querySelector("#nd-desc") as
496
+ | HTMLTextAreaElement
497
+ | null;
398
498
  if (slug) this.slug = slug.value;
399
499
  if (title) this.siteTitle = title.value;
400
500
  if (desc) this.siteDescription = desc.value;
@@ -409,7 +509,7 @@ export class NsiteDeployButton extends HTMLElement {
409
509
  this.cancelQr();
410
510
  this.signer?.close();
411
511
  this.signer = null;
412
- this.setState('idle');
512
+ this.setState("idle");
413
513
  }
414
514
 
415
515
  private cancelQr() {
@@ -420,7 +520,7 @@ export class NsiteDeployButton extends HTMLElement {
420
520
 
421
521
  private showError(msg: string) {
422
522
  this.errorMsg = msg;
423
- this.setState('error');
523
+ this.setState("error");
424
524
  }
425
525
 
426
526
  // --- Nostrconnect lifecycle ---
@@ -432,7 +532,7 @@ export class NsiteDeployButton extends HTMLElement {
432
532
  this.nostrConnectUri = this.ncConnect.uri;
433
533
 
434
534
  // Re-render to show new QR (only if we're on the auth screen)
435
- if (this.state === 'auth') {
535
+ if (this.state === "auth") {
436
536
  this.render();
437
537
  }
438
538
 
@@ -441,7 +541,7 @@ export class NsiteDeployButton extends HTMLElement {
441
541
  this.ncConnect
442
542
  .connect(this.qrAbort.signal)
443
543
  .then((signer) => {
444
- if (this.state === 'auth' || this.state === 'connecting') {
544
+ if (this.state === "auth" || this.state === "connecting") {
445
545
  this.signer = signer;
446
546
  this.onAuthenticated();
447
547
  } else {
@@ -461,7 +561,7 @@ export class NsiteDeployButton extends HTMLElement {
461
561
  this.manifestPromise = nostr.fetchManifest(this.ctx);
462
562
  }
463
563
 
464
- this.state = 'auth';
564
+ this.state = "auth";
465
565
  this.startNostrConnect();
466
566
  }
467
567
 
@@ -476,12 +576,13 @@ export class NsiteDeployButton extends HTMLElement {
476
576
  }
477
577
 
478
578
  private async authBunker() {
479
- const input = (this.shadow.querySelector('#nd-bunker') as HTMLInputElement)?.value.trim();
579
+ const input = (this.shadow.querySelector("#nd-bunker") as HTMLInputElement)
580
+ ?.value.trim();
480
581
  if (!input) return;
481
582
 
482
583
  this.cancelQr();
483
- this.statusMsg = 'Connecting to bunker...';
484
- this.setState('connecting');
584
+ this.statusMsg = "Connecting to bunker...";
585
+ this.setState("connecting");
485
586
 
486
587
  try {
487
588
  this.signer = await bunkerConnect(input);
@@ -494,11 +595,13 @@ export class NsiteDeployButton extends HTMLElement {
494
595
  private async copyUri() {
495
596
  try {
496
597
  await navigator.clipboard.writeText(this.nostrConnectUri);
497
- const btn = this.shadow.querySelector('[data-action="copy-uri"]') as HTMLButtonElement;
598
+ const btn = this.shadow.querySelector(
599
+ '[data-action="copy-uri"]',
600
+ ) as HTMLButtonElement;
498
601
  if (btn) {
499
- btn.textContent = 'Copied!';
602
+ btn.textContent = "Copied!";
500
603
  setTimeout(() => {
501
- if (btn.isConnected) btn.textContent = 'Copy';
604
+ if (btn.isConnected) btn.textContent = "Copy";
502
605
  }, 2000);
503
606
  }
504
607
  } catch {
@@ -509,25 +612,25 @@ export class NsiteDeployButton extends HTMLElement {
509
612
  // --- Post-auth ---
510
613
 
511
614
  private async onAuthenticated() {
512
- this.setState('loading');
615
+ this.setState("loading");
513
616
 
514
617
  try {
515
618
  this.userPubkey = await this.signer!.getPublicKey();
516
619
  this.manifest = (await this.manifestPromise) as nostr.SignedEvent | null;
517
620
 
518
621
  if (!this.manifest) {
519
- this.showError('Could not find the site manifest on any relay.');
622
+ this.showError("Could not find the site manifest on any relay.");
520
623
  return;
521
624
  }
522
625
 
523
- this.siteTitle = '';
524
- this.siteDescription = '';
525
- this.slug = this.ctx!.identifier ?? '';
626
+ this.siteTitle = "";
627
+ this.siteDescription = "";
628
+ this.slug = this.ctx!.identifier ?? "";
526
629
  this.deployAsRoot = true;
527
630
  this.hasRootSite = null;
528
631
 
529
632
  // Show form immediately — don't block on relay discovery
530
- this.setState('form');
633
+ this.setState("form");
531
634
 
532
635
  // Background: fetch relays, then check if root site exists
533
636
  this.relaysPromise = nostr.getWriteRelays(this.userPubkey);
@@ -535,10 +638,10 @@ export class NsiteDeployButton extends HTMLElement {
535
638
  this.userRelays = relays;
536
639
  const hasRoot = await nostr.checkExistingSite(relays, this.userPubkey);
537
640
  this.hasRootSite = hasRoot;
538
- if (hasRoot && this.state === 'form' && this.deployAsRoot) {
641
+ if (hasRoot && this.state === "form" && this.deployAsRoot) {
539
642
  this.deployAsRoot = false;
540
643
  }
541
- if (this.state === 'form') this.render();
644
+ if (this.state === "form") this.render();
542
645
  });
543
646
  } catch (err) {
544
647
  this.showError(err instanceof Error ? err.message : String(err));
@@ -549,11 +652,17 @@ export class NsiteDeployButton extends HTMLElement {
549
652
 
550
653
  private readFormValues() {
551
654
  if (!this.deployAsRoot) {
552
- const slugEl = this.shadow.querySelector('#nd-slug') as HTMLInputElement | null;
655
+ const slugEl = this.shadow.querySelector("#nd-slug") as
656
+ | HTMLInputElement
657
+ | null;
553
658
  if (slugEl) this.slug = slugEl.value.trim();
554
659
  }
555
- const titleEl = this.shadow.querySelector('#nd-title') as HTMLInputElement | null;
556
- const descEl = this.shadow.querySelector('#nd-desc') as HTMLTextAreaElement | null;
660
+ const titleEl = this.shadow.querySelector("#nd-title") as
661
+ | HTMLInputElement
662
+ | null;
663
+ const descEl = this.shadow.querySelector("#nd-desc") as
664
+ | HTMLTextAreaElement
665
+ | null;
557
666
  if (titleEl) this.siteTitle = titleEl.value.trim();
558
667
  if (descEl) this.siteDescription = descEl.value.trim();
559
668
  }
@@ -562,19 +671,20 @@ export class NsiteDeployButton extends HTMLElement {
562
671
  this.readFormValues();
563
672
 
564
673
  if (!this.deployAsRoot) {
565
- const errEl = this.shadow.querySelector('#nd-slug-err') as HTMLElement;
674
+ const errEl = this.shadow.querySelector("#nd-slug-err") as HTMLElement;
566
675
  if (!this.slug) {
567
- errEl.textContent = 'Site name is required';
676
+ errEl.textContent = "Site name is required";
568
677
  return;
569
678
  }
570
679
  if (!nostr.isValidDTag(this.slug)) {
571
- errEl.textContent = 'Lowercase a-z, 0-9, hyphens only. Cannot end with hyphen.';
680
+ errEl.textContent =
681
+ "Lowercase a-z, 0-9, hyphens only. Cannot end with hyphen.";
572
682
  return;
573
683
  }
574
684
  }
575
685
 
576
- this.statusMsg = 'Checking for existing site...';
577
- this.setState('deploying');
686
+ this.statusMsg = "Checking for existing site...";
687
+ this.setState("deploying");
578
688
 
579
689
  try {
580
690
  // Ensure relays are ready (usually already resolved by now)
@@ -586,10 +696,14 @@ export class NsiteDeployButton extends HTMLElement {
586
696
  }
587
697
 
588
698
  const slug = this.deployAsRoot ? undefined : this.slug;
589
- const exists = await nostr.checkExistingSite(this.userRelays, this.userPubkey, slug);
699
+ const exists = await nostr.checkExistingSite(
700
+ this.userRelays,
701
+ this.userPubkey,
702
+ slug,
703
+ );
590
704
 
591
705
  if (exists) {
592
- this.setState('confirm');
706
+ this.setState("confirm");
593
707
  return;
594
708
  }
595
709
 
@@ -600,8 +714,8 @@ export class NsiteDeployButton extends HTMLElement {
600
714
  }
601
715
 
602
716
  private async executeDeploy() {
603
- this.statusMsg = 'Creating event...';
604
- this.setState('deploying');
717
+ this.statusMsg = "Creating event...";
718
+ this.setState("deploying");
605
719
 
606
720
  try {
607
721
  const slug = this.deployAsRoot ? undefined : this.slug;
@@ -611,24 +725,30 @@ export class NsiteDeployButton extends HTMLElement {
611
725
  description: this.siteDescription || undefined,
612
726
  deployerPubkey: this.userPubkey,
613
727
  deployerRelays: this.userRelays,
614
- noTrail: this.hasAttribute('no-trail')
728
+ noTrail: this.hasAttribute("no-trail"),
615
729
  });
616
730
 
617
- this.statusMsg = 'Waiting for signature...';
731
+ this.statusMsg = "Waiting for signature...";
618
732
  this.render();
619
733
  const signed = await this.signer!.signEvent(unsigned);
620
734
 
621
- this.statusMsg = `Publishing to ${this.userRelays.length} relay${this.userRelays.length === 1 ? '' : 's'}...`;
735
+ this.statusMsg = `Publishing to ${this.userRelays.length} relay${
736
+ this.userRelays.length === 1 ? "" : "s"
737
+ }...`;
622
738
  this.render();
623
739
  const published = await nostr.publishToRelays(this.userRelays, signed);
624
740
 
625
741
  if (published === 0) {
626
- this.showError('Failed to publish to any relay. Please try again.');
742
+ this.showError("Failed to publish to any relay. Please try again.");
627
743
  return;
628
744
  }
629
745
 
630
- this.deployedUrl = nostr.buildSiteUrl(this.ctx!.baseDomain, this.userPubkey, slug);
631
- this.setState('success');
746
+ this.deployedUrl = nostr.buildSiteUrl(
747
+ this.ctx!.baseDomain,
748
+ this.userPubkey,
749
+ slug,
750
+ );
751
+ this.setState("success");
632
752
  } catch (err) {
633
753
  this.showError(err instanceof Error ? err.message : String(err));
634
754
  }