@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/README.md CHANGED
@@ -4,7 +4,7 @@ A drop-in web component that adds a "Borrow this" button to any nsite. Visitors
4
4
 
5
5
  ## How it works
6
6
 
7
- When loaded on an nsite, `<nsite-deploy>` detects the site owner's pubkey from the subdomain, fetches the site manifest from nostr relays, and lets authenticated visitors publish a copy under their own npub. Each deployment appends a `muse` tag to the manifest, creating a paper trail of who was inspired by the site.
7
+ When loaded on an nsite, `<steal-this>` detects the site owner's pubkey from the subdomain, fetches the site manifest from nostr relays, and lets authenticated visitors publish a copy under their own npub. Each deployment appends a `muse` tag to the manifest, creating a paper trail of who was inspired by the site.
8
8
 
9
9
  ## Install
10
10
 
@@ -19,7 +19,7 @@ The widget auto-injects a fixed-position button in the bottom-right corner.
19
19
  If you want to control where the button appears, add the element yourself (the auto-inject will skip if one already exists):
20
20
 
21
21
  ```html
22
- <nsite-deploy button-text="Cop this joint" stat-text="%s npubs copped this"></nsite-deploy>
22
+ <steal-this button-text="Cop this joint" stat-text="%s npubs copped this"></steal-this>
23
23
  ```
24
24
 
25
25
  ## Attributes
@@ -28,7 +28,9 @@ If you want to control where the button appears, add the element yourself (the a
28
28
  |-----------|---------|-------------|
29
29
  | `button-text` | `"Borrow this nsite"` | Button text |
30
30
  | `stat-text` | `"%s npubs borrowed this nsite"` | Paper trail summary. `%s` is replaced with the count. |
31
- | `no-trail` | _(absent)_ | Boolean attribute. When present, hides the paper trail. |
31
+ | `no-trail` | _(absent)_ | Boolean attribute. When present, disables the paper trail entirely -- no `muse` tags are written and the trail UI is not rendered. |
32
+ | `obfuscate-npubs` | _(absent)_ | Boolean attribute. Shows truncated npubs with no links in the paper trail. Disables profile fetching. |
33
+ | `do-not-fetch-muse-data` | _(absent)_ | Boolean attribute. Skips profile enrichment but still shows full npubs linked to njump. |
32
34
 
33
35
  The button's `trigger` part is exposed via `::part(trigger)` for CSS customization.
34
36
 
@@ -51,6 +53,8 @@ If the visitor already has a root site, the form defaults to a named site. Overw
51
53
 
52
54
  Each deployment adds a `muse` tag with the deployer's pubkey and relay list. The widget shows an expandable "Inspired N npubs" counter when muse tags are present. A maximum of 9 muse tags are kept per manifest (originator + 8 most recent), with FIFO truncation of the middle.
53
55
 
56
+ By default, the trail streams kind-0 profiles from relays to show display names and links to each muse's nsite (or njump fallback). Use `obfuscate-npubs` to show only truncated npubs, or `do-not-fetch-muse-data` to skip profile fetching while still linking to njump.
57
+
54
58
  ## Development
55
59
 
56
60
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nsite/stealthis",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/stealthis.js",
6
6
  "module": "dist/stealthis.mjs",
@@ -15,10 +15,11 @@
15
15
  "build": "vite build"
16
16
  },
17
17
  "dependencies": {
18
- "nostr-tools": "^2.23.3",
19
- "qrcode-generator": "^1.4.4"
18
+ "@libs/qrcode": "jsr:^3.0.1",
19
+ "nostr-tools": "^2.23.3"
20
20
  },
21
21
  "devDependencies": {
22
+ "cssnano": "^7.1.3",
22
23
  "typescript": "^5.9.3",
23
24
  "vite": "^7.3.1"
24
25
  }
package/src/css.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*.css?inline" {
2
+ const css: string;
3
+ export default css;
4
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { NsiteDeployButton } from './widget';
2
2
 
3
- customElements.define('nsite-deploy', NsiteDeployButton);
3
+ customElements.define('steal-this', NsiteDeployButton);
4
4
 
5
5
  function autoInject() {
6
- if (!document.querySelector('nsite-deploy')) {
7
- const el = document.createElement('nsite-deploy');
6
+ if (!document.querySelector('steal-this')) {
7
+ const el = document.createElement('steal-this');
8
8
  el.classList.add('nd-fixed');
9
9
  document.body.appendChild(el);
10
10
  }
package/src/nostr.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { npubEncode } from 'nostr-tools/nip19';
2
- import type { SignedEvent, EventTemplate } from './signer';
1
+ import { decode, npubEncode } from "nostr-tools/nip19";
2
+ import type { EventTemplate, SignedEvent } from "./signer";
3
3
 
4
- export type { SignedEvent, EventTemplate };
4
+ export type { EventTemplate, SignedEvent };
5
5
 
6
6
  export interface NsiteContext {
7
7
  pubkey: string;
@@ -15,9 +15,24 @@ export interface Muse {
15
15
  relays: string[];
16
16
  }
17
17
 
18
- export { npubEncode };
18
+ export interface MuseProfile {
19
+ npub: string;
20
+ name?: string;
21
+ nsiteUrl?: string;
22
+ }
19
23
 
20
- const BOOTSTRAP_RELAYS = ['wss://purplepag.es', 'wss://relay.damus.io', 'wss://nos.lol'];
24
+ const BOOTSTRAP_RELAYS = [
25
+ "wss://purplepag.es",
26
+ "wss://relay.damus.io",
27
+ "wss://nos.lol",
28
+ ];
29
+ const PROFILE_RELAYS = [
30
+ "wss://purplepag.es",
31
+ "wss://user.kindpag.es",
32
+ "wss://indexer.hzrd149.com",
33
+ "wss://profiles.nostr1.com",
34
+ "wss://nos.lol",
35
+ ];
21
36
  const B36_LEN = 50;
22
37
  const D_TAG_RE = /^[a-z0-9-]{1,13}$/;
23
38
  const NAMED_LABEL_RE = /^[0-9a-z]{50}[a-z0-9-]{1,13}$/;
@@ -25,53 +40,40 @@ const NAMED_LABEL_RE = /^[0-9a-z]{50}[a-z0-9-]{1,13}$/;
25
40
  // --- Base36 ---
26
41
 
27
42
  export function pubkeyToBase36(hex: string): string {
28
- return BigInt('0x' + hex)
43
+ return BigInt("0x" + hex)
29
44
  .toString(36)
30
- .padStart(B36_LEN, '0');
45
+ .padStart(B36_LEN, "0");
31
46
  }
32
47
 
33
48
  function base36ToHex(b36: string): string {
34
49
  let n = 0n;
35
50
  for (const c of b36) n = n * 36n + BigInt(parseInt(c, 36));
36
- return n.toString(16).padStart(64, '0');
51
+ return n.toString(16).padStart(64, "0");
37
52
  }
38
53
 
39
- // --- Minimal bech32 decode (npub only) ---
54
+ // --- NIP-19 ---
40
55
 
41
56
  function npubDecode(npub: string): string | null {
42
- if (!npub.startsWith('npub1')) return null;
43
- const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
44
- const values: number[] = [];
45
- for (const c of npub.slice(5)) {
46
- const v = CHARSET.indexOf(c);
47
- if (v === -1) return null;
48
- values.push(v);
57
+ try {
58
+ const result = decode(npub);
59
+ if (result.type !== "npub") return null;
60
+ return result.data;
61
+ } catch (error) {
62
+ return null;
49
63
  }
50
- const payload = values.slice(0, -6);
51
- let acc = 0,
52
- bits = 0;
53
- const bytes: number[] = [];
54
- for (const v of payload) {
55
- acc = (acc << 5) | v;
56
- bits += 5;
57
- while (bits >= 8) {
58
- bits -= 8;
59
- bytes.push((acc >> bits) & 0xff);
60
- }
61
- }
62
- if (bytes.length !== 32) return null;
63
- return bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
64
64
  }
65
65
 
66
+ export { npubEncode };
67
+
66
68
  // --- Context ---
67
69
 
68
70
  export function parseContext(): NsiteContext | null {
69
- const parts = window.location.hostname.split('.');
71
+ const parts = window.location.hostname.split(".");
70
72
 
71
73
  for (let i = 0; i < parts.length; i++) {
72
- if (parts[i].startsWith('npub1') && parts[i].length >= 63) {
74
+ if (parts[i].startsWith("npub1") && parts[i].length >= 63) {
73
75
  const pubkey = npubDecode(parts[i]);
74
- if (pubkey) return { pubkey, baseDomain: parts.slice(i + 1).join('.') };
76
+ if (pubkey) return { pubkey, baseDomain: parts.slice(i + 1).join(".") };
75
77
  }
76
78
  }
77
79
 
@@ -81,11 +83,15 @@ export function parseContext(): NsiteContext | null {
81
83
  label.length > B36_LEN &&
82
84
  label.length <= 63 &&
83
85
  NAMED_LABEL_RE.test(label) &&
84
- !label.endsWith('-')
86
+ !label.endsWith("-")
85
87
  ) {
86
88
  try {
87
89
  const pubkey = base36ToHex(label.slice(0, B36_LEN));
88
- return { pubkey, identifier: label.slice(B36_LEN), baseDomain: parts.slice(1).join('.') };
90
+ return {
91
+ pubkey,
92
+ identifier: label.slice(B36_LEN),
93
+ baseDomain: parts.slice(1).join("."),
94
+ };
89
95
  } catch {
90
96
  /* invalid */
91
97
  }
@@ -95,7 +101,7 @@ export function parseContext(): NsiteContext | null {
95
101
  }
96
102
 
97
103
  export function isValidDTag(s: string): boolean {
98
- return D_TAG_RE.test(s) && !s.endsWith('-');
104
+ return D_TAG_RE.test(s) && !s.endsWith("-");
99
105
  }
100
106
 
101
107
  // --- Relay communication ---
@@ -114,23 +120,29 @@ function withSocket(
114
120
  url: string,
115
121
  sendMsg: unknown[],
116
122
  onMsg: (data: unknown[]) => boolean,
117
- timeout = 5000
123
+ timeout = 5000,
118
124
  ): Promise<void> {
119
125
  return new Promise((resolve) => {
120
126
  try {
121
127
  const ws = new WebSocket(url);
122
128
  const timer = setTimeout(() => {
123
- try { ws.close(); } catch { /* */ }
129
+ try {
130
+ ws.close();
131
+ } catch { /* */ }
124
132
  resolve();
125
133
  }, timeout);
126
134
  const finish = () => {
127
135
  clearTimeout(timer);
128
- try { ws.close(); } catch { /* */ }
136
+ try {
137
+ ws.close();
138
+ } catch { /* */ }
129
139
  resolve();
130
140
  };
131
141
  ws.onopen = () => ws.send(JSON.stringify(sendMsg));
132
142
  ws.onmessage = (e) => {
133
- try { if (onMsg(JSON.parse(e.data))) finish(); } catch { /* */ }
143
+ try {
144
+ if (onMsg(JSON.parse(e.data))) finish();
145
+ } catch { /* */ }
134
146
  };
135
147
  ws.onerror = () => finish();
136
148
  } catch {
@@ -139,25 +151,29 @@ function withSocket(
139
151
  });
140
152
  }
141
153
 
142
- async function queryRelays(urls: string[], filter: Record<string, unknown>): Promise<RelayEvent[]> {
154
+ async function queryRelays(
155
+ urls: string[],
156
+ filter: Record<string, unknown>,
157
+ ): Promise<RelayEvent[]> {
143
158
  const events = new Map<string, RelayEvent>();
144
159
  const subId = Math.random().toString(36).slice(2, 8);
145
160
  await Promise.allSettled(
146
161
  urls.map((url) =>
147
- withSocket(url, ['REQ', subId, filter], (msg) => {
148
- if (msg[0] === 'EVENT' && msg[1] === subId)
162
+ withSocket(url, ["REQ", subId, filter], (msg) => {
163
+ if (msg[0] === "EVENT" && msg[1] === subId) {
149
164
  events.set((msg[2] as RelayEvent).id, msg[2] as RelayEvent);
150
- return msg[0] === 'EOSE' && msg[1] === subId;
165
+ }
166
+ return msg[0] === "EOSE" && msg[1] === subId;
151
167
  })
152
- )
168
+ ),
153
169
  );
154
170
  return [...events.values()];
155
171
  }
156
172
 
157
173
  async function publishRelay(url: string, event: SignedEvent): Promise<boolean> {
158
174
  let ok = false;
159
- await withSocket(url, ['EVENT', event], (msg) => {
160
- if (msg[0] === 'OK') {
175
+ await withSocket(url, ["EVENT", event], (msg) => {
176
+ if (msg[0] === "OK") {
161
177
  ok = msg[2] === true;
162
178
  return true;
163
179
  }
@@ -166,9 +182,14 @@ async function publishRelay(url: string, event: SignedEvent): Promise<boolean> {
166
182
  return ok;
167
183
  }
168
184
 
169
- export async function publishToRelays(urls: string[], event: SignedEvent): Promise<number> {
170
- const results = await Promise.allSettled(urls.map((url) => publishRelay(url, event)));
171
- return results.filter((r) => r.status === 'fulfilled' && r.value).length;
185
+ export async function publishToRelays(
186
+ urls: string[],
187
+ event: SignedEvent,
188
+ ): Promise<number> {
189
+ const results = await Promise.allSettled(
190
+ urls.map((url) => publishRelay(url, event)),
191
+ );
192
+ return results.filter((r) => r.status === "fulfilled" && r.value).length;
172
193
  }
173
194
 
174
195
  // --- High-level operations ---
@@ -177,22 +198,32 @@ function extractWriteRelays(events: RelayEvent[]): string[] {
177
198
  const relays = new Set<string>();
178
199
  for (const e of events) {
179
200
  for (const t of e.tags) {
180
- if (t[0] === 'r' && t[1]?.startsWith('wss://') && (!t[2] || t[2] === 'write'))
201
+ if (
202
+ t[0] === "r" && t[1]?.startsWith("wss://") &&
203
+ (!t[2] || t[2] === "write")
204
+ ) {
181
205
  relays.add(t[1].trim());
206
+ }
182
207
  }
183
208
  }
184
209
  return [...relays];
185
210
  }
186
211
 
187
- export async function fetchManifest(ctx: NsiteContext): Promise<RelayEvent | null> {
212
+ export async function fetchManifest(
213
+ ctx: NsiteContext,
214
+ ): Promise<RelayEvent | null> {
188
215
  const manifestFilter = ctx.identifier
189
- ? { kinds: [35128], authors: [ctx.pubkey], '#d': [ctx.identifier] }
216
+ ? { kinds: [35128], authors: [ctx.pubkey], "#d": [ctx.identifier] }
190
217
  : { kinds: [15128], authors: [ctx.pubkey], limit: 1 };
191
218
 
192
219
  // Query bootstrap relays for manifest AND relay list in parallel
193
220
  const [bootstrapManifests, relayEvents] = await Promise.all([
194
221
  queryRelays(BOOTSTRAP_RELAYS, manifestFilter),
195
- queryRelays(BOOTSTRAP_RELAYS, { kinds: [10002], authors: [ctx.pubkey], limit: 5 })
222
+ queryRelays(BOOTSTRAP_RELAYS, {
223
+ kinds: [10002],
224
+ authors: [ctx.pubkey],
225
+ limit: 5,
226
+ }),
196
227
  ]);
197
228
 
198
229
  // If bootstrap already found it, return immediately
@@ -202,7 +233,7 @@ export async function fetchManifest(ctx: NsiteContext): Promise<RelayEvent | nul
202
233
 
203
234
  // Otherwise try the owner's relays
204
235
  const ownerRelays = extractWriteRelays(relayEvents).filter(
205
- (r) => !BOOTSTRAP_RELAYS.includes(r)
236
+ (r) => !BOOTSTRAP_RELAYS.includes(r),
206
237
  );
207
238
  if (ownerRelays.length === 0) return null;
208
239
 
@@ -214,19 +245,21 @@ export async function getWriteRelays(pubkey: string): Promise<string[]> {
214
245
  const events = await queryRelays(BOOTSTRAP_RELAYS, {
215
246
  kinds: [10002],
216
247
  authors: [pubkey],
217
- limit: 5
248
+ limit: 5,
218
249
  });
219
250
  const relays = extractWriteRelays(events);
220
- return relays.length > 0 ? relays : BOOTSTRAP_RELAYS.filter((r) => r !== 'wss://purplepag.es');
251
+ return relays.length > 0
252
+ ? relays
253
+ : BOOTSTRAP_RELAYS.filter((r) => r !== "wss://purplepag.es");
221
254
  }
222
255
 
223
256
  export async function checkExistingSite(
224
257
  relays: string[],
225
258
  pubkey: string,
226
- slug?: string
259
+ slug?: string,
227
260
  ): Promise<boolean> {
228
261
  const filter = slug
229
- ? { kinds: [35128], authors: [pubkey], '#d': [slug], limit: 1 }
262
+ ? { kinds: [35128], authors: [pubkey], "#d": [slug], limit: 1 }
230
263
  : { kinds: [15128], authors: [pubkey], limit: 1 };
231
264
  const events = await queryRelays(relays, filter);
232
265
  return events.length > 0;
@@ -236,8 +269,12 @@ const MAX_MUSE_TAGS = 9;
236
269
 
237
270
  export function extractMuses(event: RelayEvent): Muse[] {
238
271
  return event.tags
239
- .filter((t) => t[0] === 'muse' && t[1] && t[2])
240
- .map((t) => ({ index: parseInt(t[1], 10), pubkey: t[2], relays: t.slice(3) }))
272
+ .filter((t) => t[0] === "muse" && t[1] && t[2])
273
+ .map((t) => ({
274
+ index: parseInt(t[1], 10),
275
+ pubkey: t[2],
276
+ relays: t.slice(3),
277
+ }))
241
278
  .sort((a, b) => a.index - b.index);
242
279
  }
243
280
 
@@ -250,25 +287,30 @@ export function createDeployEvent(
250
287
  deployerPubkey: string;
251
288
  deployerRelays: string[];
252
289
  noTrail?: boolean;
253
- }
290
+ },
254
291
  ): EventTemplate {
255
292
  const tags: string[][] = [];
256
- if (options.slug) tags.push(['d', options.slug]);
293
+ if (options.slug) tags.push(["d", options.slug]);
257
294
  for (const t of source.tags) {
258
- if (t[0] === 'path' || t[0] === 'server') tags.push([...t]);
295
+ if (t[0] === "path" || t[0] === "server") tags.push([...t]);
259
296
  }
260
297
 
261
298
  if (!options.noTrail) {
262
299
  // Paper trail: copy muse tags, add new one, enforce max 9
263
300
  const sourceMuses = source.tags
264
- .filter((t) => t[0] === 'muse' && t[1] && t[2])
301
+ .filter((t) => t[0] === "muse" && t[1] && t[2])
265
302
  .map((t) => [...t])
266
303
  .sort((a, b) => parseInt(a[1], 10) - parseInt(b[1], 10));
267
304
 
268
305
  const maxIndex = sourceMuses.length > 0
269
306
  ? Math.max(...sourceMuses.map((t) => parseInt(t[1], 10)))
270
307
  : -1;
271
- const newMuse = ['muse', String(maxIndex + 1), options.deployerPubkey, ...options.deployerRelays];
308
+ const newMuse = [
309
+ "muse",
310
+ String(maxIndex + 1),
311
+ options.deployerPubkey,
312
+ ...options.deployerRelays,
313
+ ];
272
314
  const allMuses = [...sourceMuses, newMuse];
273
315
 
274
316
  // Keep index 0 (originator) + newest, FIFO truncate the middle
@@ -281,17 +323,106 @@ export function createDeployEvent(
281
323
  }
282
324
  }
283
325
 
284
- if (options.title) tags.push(['title', options.title]);
285
- if (options.description) tags.push(['description', options.description]);
326
+ if (options.title) tags.push(["title", options.title]);
327
+ if (options.description) tags.push(["description", options.description]);
286
328
  return {
287
329
  kind: options.slug ? 35128 : 15128,
288
330
  created_at: Math.floor(Date.now() / 1000),
289
331
  tags,
290
- content: ''
332
+ content: "",
291
333
  };
292
334
  }
293
335
 
294
- export function buildSiteUrl(baseDomain: string, pubkey: string, slug?: string): string {
336
+ // --- Muse profile enrichment (streaming, non-blocking) ---
337
+
338
+ export function fetchMuseProfiles(
339
+ muses: Muse[],
340
+ baseDomain: string,
341
+ onUpdate: (pubkey: string, profile: MuseProfile) => void,
342
+ ): void {
343
+ const pubkeys = [...new Set(muses.map((m) => m.pubkey))];
344
+ if (pubkeys.length === 0) return;
345
+
346
+ // Track best kind-0 per pubkey (highest created_at wins)
347
+ const bestK0 = new Map<
348
+ string,
349
+ { created_at: number; profile: MuseProfile }
350
+ >();
351
+ const profileResolved = new Set<string>();
352
+
353
+ const handleEvent = (event: RelayEvent) => {
354
+ if (event.kind === 0) {
355
+ const existing = bestK0.get(event.pubkey);
356
+ if (existing && existing.created_at >= event.created_at) return;
357
+ try {
358
+ const meta = JSON.parse(event.content);
359
+ const name = meta.display_name || meta.name || meta.displayName;
360
+ if (!name) return;
361
+ const profile: MuseProfile = { npub: npubEncode(event.pubkey), name };
362
+ bestK0.set(event.pubkey, { created_at: event.created_at, profile });
363
+ profileResolved.add(event.pubkey);
364
+ onUpdate(event.pubkey, { ...profile });
365
+ } catch { /* invalid json */ }
366
+ }
367
+ };
368
+
369
+ // Stream kind 0 from profile relays — fire all in parallel, callback per event
370
+ const subId = Math.random().toString(36).slice(2, 8);
371
+ const filter = { kinds: [0], authors: pubkeys };
372
+
373
+ Promise.allSettled(
374
+ PROFILE_RELAYS.map((url) =>
375
+ withSocket(url, ["REQ", subId, filter], (msg) => {
376
+ if (msg[0] === "EVENT" && msg[1] === subId) {
377
+ handleEvent(msg[2] as RelayEvent);
378
+ }
379
+ return msg[0] === "EOSE" && msg[1] === subId;
380
+ })
381
+ ),
382
+ ).then(() => {
383
+ // After profiles are gathered, check for nsites on bootstrap relays
384
+ const resolved = [...profileResolved];
385
+ if (resolved.length === 0) return;
386
+
387
+ queryRelays(BOOTSTRAP_RELAYS, {
388
+ kinds: [15128, 35128],
389
+ authors: resolved,
390
+ }).then((events) => {
391
+ // Group by pubkey — prefer root (15128), fall back to first named (35128)
392
+ const nsiteMap = new Map<string, string>();
393
+ for (const e of events) {
394
+ if (e.kind === 15128 && !nsiteMap.has(e.pubkey)) {
395
+ nsiteMap.set(e.pubkey, buildSiteUrl(baseDomain, e.pubkey));
396
+ }
397
+ }
398
+ for (const e of events) {
399
+ if (e.kind === 35128 && !nsiteMap.has(e.pubkey)) {
400
+ const dTag = e.tags.find((t) => t[0] === "d")?.[1];
401
+ if (dTag) {
402
+ nsiteMap.set(
403
+ e.pubkey,
404
+ buildSiteUrl(baseDomain, e.pubkey, dTag),
405
+ );
406
+ }
407
+ }
408
+ }
409
+
410
+ for (const [pk, url] of nsiteMap) {
411
+ const entry = bestK0.get(pk);
412
+ if (entry) {
413
+ entry.profile.nsiteUrl = url;
414
+ onUpdate(pk, { ...entry.profile });
415
+ }
416
+ }
417
+ });
418
+ });
419
+ }
420
+
421
+ export function buildSiteUrl(
422
+ baseDomain: string,
423
+ pubkey: string,
424
+ slug?: string,
425
+ ): string {
295
426
  if (slug) {
296
427
  return `https://${pubkeyToBase36(pubkey)}${slug}.${baseDomain}`;
297
428
  }
package/src/qr.ts CHANGED
@@ -1,8 +1,5 @@
1
- import qrcode from 'qrcode-generator';
1
+ import { qrcode } from "@libs/qrcode";
2
2
 
3
3
  export function toSvg(text: string): string {
4
- const qr = qrcode(0, 'L');
5
- qr.addData(text);
6
- qr.make();
7
- return qr.createSvgTag({ cellSize: 3, margin: 2, scalable: true });
4
+ return qrcode(text, { output: "svg", ecl: "LOW" });
8
5
  }