@nsite/stealthis 0.6.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/README.md +6 -2
- package/package.json +4 -3
- package/src/css.d.ts +4 -0
- package/src/index.ts +3 -3
- package/src/nostr.ts +201 -70
- package/src/qr.ts +2 -5
- package/src/styles.css +452 -0
- package/src/widget.ts +253 -133
- package/vite.config.ts +14 -7
- package/src/styles.ts +0 -440
package/src/widget.ts
CHANGED
|
@@ -1,64 +1,88 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as nostr from
|
|
3
|
-
import { toSvg } from
|
|
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
|
-
|
|
11
|
-
|
|
7
|
+
extensionSigner,
|
|
8
|
+
hasExtension,
|
|
9
|
+
prepareNostrConnect,
|
|
10
|
+
type Signer,
|
|
11
|
+
} from "./signer";
|
|
12
12
|
|
|
13
13
|
type State =
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
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 = [
|
|
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 =
|
|
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:
|
|
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(
|
|
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 ===
|
|
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 ===
|
|
94
|
+
if (this.state === "idle") this.render();
|
|
71
95
|
}
|
|
72
96
|
|
|
73
97
|
private get buttonText(): string {
|
|
74
|
-
return this.getAttribute(
|
|
98
|
+
return this.getAttribute("button-text") || "Borrow this nsite";
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
private get statText(): string {
|
|
78
|
-
return this.getAttribute(
|
|
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,
|
|
84
|
-
.replace(/</g,
|
|
85
|
-
.replace(/>/g,
|
|
86
|
-
.replace(/"/g,
|
|
107
|
+
.replace(/&/g, "&")
|
|
108
|
+
.replace(/</g, "<")
|
|
109
|
+
.replace(/>/g, ">")
|
|
110
|
+
.replace(/"/g, """);
|
|
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
|
|
99
|
-
html += `<button class="nd-trigger" part="trigger">${
|
|
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
|
|
104
|
-
html += `<button class="nd-trigger" disabled>${
|
|
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
|
|
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">×</button>
|
|
114
142
|
</div>
|
|
115
|
-
<div class="nd-msg"><span class="nd-spinner"></span>${
|
|
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
|
|
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
|
|
127
|
-
html += `<button class="nd-trigger" disabled>${
|
|
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
|
|
132
|
-
html += `<button class="nd-trigger" disabled>${
|
|
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
|
|
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>${
|
|
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
|
|
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">×</button>
|
|
150
186
|
</div>
|
|
151
187
|
<div class="nd-status nd-status-ok">Your site is live</div>
|
|
152
|
-
<a class="nd-link" href="${
|
|
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
|
|
160
|
-
html += `<button class="nd-trigger" part="trigger">${
|
|
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(
|
|
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">${
|
|
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
|
-
|
|
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
|
-
<
|
|
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="${
|
|
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="${
|
|
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">×</button>
|
|
270
337
|
</div>
|
|
271
|
-
${
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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 +=
|
|
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="${
|
|
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="${
|
|
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">${
|
|
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" ${
|
|
301
|
-
|
|
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
|
|
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(
|
|
329
|
-
?.addEventListener(
|
|
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(
|
|
422
|
+
.forEach((el) => el.addEventListener("click", () => this.close()));
|
|
333
423
|
this.shadow
|
|
334
424
|
.querySelector('[data-action="deploy"]')
|
|
335
|
-
?.addEventListener(
|
|
425
|
+
?.addEventListener("click", () => this.onDeploy());
|
|
336
426
|
this.shadow
|
|
337
427
|
.querySelector('[data-action="confirm-deploy"]')
|
|
338
|
-
?.addEventListener(
|
|
428
|
+
?.addEventListener("click", () => this.executeDeploy());
|
|
339
429
|
this.shadow
|
|
340
430
|
.querySelector('[data-action="back"]')
|
|
341
|
-
?.addEventListener(
|
|
431
|
+
?.addEventListener("click", () => this.setState("form"));
|
|
342
432
|
this.shadow
|
|
343
433
|
.querySelector('[data-action="ext"]')
|
|
344
|
-
?.addEventListener(
|
|
434
|
+
?.addEventListener("click", () => this.authExtension());
|
|
345
435
|
this.shadow
|
|
346
436
|
.querySelector('[data-action="bunker"]')
|
|
347
|
-
?.addEventListener(
|
|
437
|
+
?.addEventListener("click", () => this.authBunker());
|
|
348
438
|
this.shadow
|
|
349
439
|
.querySelector('[data-action="copy-uri"]')
|
|
350
|
-
?.addEventListener(
|
|
440
|
+
?.addEventListener("click", () => this.copyUri());
|
|
351
441
|
this.shadow
|
|
352
442
|
.querySelector('[data-action="toggle-trail"]')
|
|
353
|
-
?.addEventListener(
|
|
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(
|
|
449
|
+
?.addEventListener("click", () => {
|
|
360
450
|
this.deployAsRoot = false;
|
|
361
|
-
this.setState(
|
|
451
|
+
this.setState("form");
|
|
362
452
|
});
|
|
363
453
|
this.shadow
|
|
364
454
|
.querySelector('[data-action="type-root"]')
|
|
365
|
-
?.addEventListener(
|
|
455
|
+
?.addEventListener("click", () => {
|
|
366
456
|
this.deployAsRoot = true;
|
|
367
|
-
this.setState(
|
|
457
|
+
this.setState("form");
|
|
368
458
|
});
|
|
369
|
-
this.shadow.querySelector(
|
|
370
|
-
if ((e.target as HTMLElement).classList.contains(
|
|
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(
|
|
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(
|
|
384
|
-
relayInput.addEventListener(
|
|
385
|
-
if (e.key ===
|
|
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 !==
|
|
395
|
-
const slug = this.shadow.querySelector(
|
|
396
|
-
|
|
397
|
-
|
|
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(
|
|
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(
|
|
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 ===
|
|
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 ===
|
|
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 =
|
|
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(
|
|
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 =
|
|
484
|
-
this.setState(
|
|
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(
|
|
598
|
+
const btn = this.shadow.querySelector(
|
|
599
|
+
'[data-action="copy-uri"]',
|
|
600
|
+
) as HTMLButtonElement;
|
|
498
601
|
if (btn) {
|
|
499
|
-
btn.textContent =
|
|
602
|
+
btn.textContent = "Copied!";
|
|
500
603
|
setTimeout(() => {
|
|
501
|
-
if (btn.isConnected) btn.textContent =
|
|
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(
|
|
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(
|
|
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(
|
|
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 ===
|
|
641
|
+
if (hasRoot && this.state === "form" && this.deployAsRoot) {
|
|
539
642
|
this.deployAsRoot = false;
|
|
540
643
|
}
|
|
541
|
-
if (this.state ===
|
|
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(
|
|
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(
|
|
556
|
-
|
|
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(
|
|
674
|
+
const errEl = this.shadow.querySelector("#nd-slug-err") as HTMLElement;
|
|
566
675
|
if (!this.slug) {
|
|
567
|
-
errEl.textContent =
|
|
676
|
+
errEl.textContent = "Site name is required";
|
|
568
677
|
return;
|
|
569
678
|
}
|
|
570
679
|
if (!nostr.isValidDTag(this.slug)) {
|
|
571
|
-
errEl.textContent =
|
|
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 =
|
|
577
|
-
this.setState(
|
|
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(
|
|
699
|
+
const exists = await nostr.checkExistingSite(
|
|
700
|
+
this.userRelays,
|
|
701
|
+
this.userPubkey,
|
|
702
|
+
slug,
|
|
703
|
+
);
|
|
590
704
|
|
|
591
705
|
if (exists) {
|
|
592
|
-
this.setState(
|
|
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 =
|
|
604
|
-
this.setState(
|
|
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(
|
|
728
|
+
noTrail: this.hasAttribute("no-trail"),
|
|
615
729
|
});
|
|
616
730
|
|
|
617
|
-
this.statusMsg =
|
|
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${
|
|
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(
|
|
742
|
+
this.showError("Failed to publish to any relay. Please try again.");
|
|
627
743
|
return;
|
|
628
744
|
}
|
|
629
745
|
|
|
630
|
-
this.deployedUrl = nostr.buildSiteUrl(
|
|
631
|
-
|
|
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
|
}
|