From b37664bc5b6e6226e2c609a038b3f32522f9c8cf Mon Sep 17 00:00:00 2001
From: hzrd149 <github@hzrd149.com>
Date: Sat, 5 Apr 2025 15:31:28 +0100
Subject: [PATCH] Cleanup DNS pubkey resolution

---
 .changeset/pink-regions-exist.md |  5 ++
 src/cache.ts                     |  6 +--
 src/dns.ts                       | 79 ++++++++++++++++++++++++++++
 src/helpers/dns.ts               | 59 ---------------------
 src/index.ts                     | 88 +++++++-------------------------
 src/nostr.ts                     | 12 ++---
 6 files changed, 111 insertions(+), 138 deletions(-)
 create mode 100644 .changeset/pink-regions-exist.md
 create mode 100644 src/dns.ts
 delete mode 100644 src/helpers/dns.ts

diff --git a/.changeset/pink-regions-exist.md b/.changeset/pink-regions-exist.md
new file mode 100644
index 0000000..02464f0
--- /dev/null
+++ b/.changeset/pink-regions-exist.md
@@ -0,0 +1,5 @@
+---
+"nsite-gateway": minor
+---
+
+Cleanup DNS pubkey resolution
diff --git a/src/cache.ts b/src/cache.ts
index c4d2596..9ba6f2e 100644
--- a/src/cache.ts
+++ b/src/cache.ts
@@ -27,21 +27,21 @@ store?.on("error", (err) => {
 const opts = store ? { store } : {};
 
 /** A cache that maps a domain to a pubkey ( domain -> pubkey ) */
-export const userDomains = new Keyv<string | undefined>({
+export const pubkeyDomains = new Keyv<string | undefined>({
   ...opts,
   namespace: "domains",
   ttl: CACHE_TIME * 1000,
 });
 
 /** A cache that maps a pubkey to a set of blossom servers ( pubkey -> servers ) */
-export const userServers = new Keyv<string[] | undefined>({
+export const pubkeyServers = new Keyv<string[] | undefined>({
   ...opts,
   namespace: "servers",
   ttl: CACHE_TIME * 1000,
 });
 
 /** A cache that maps a pubkey to a set of relays ( pubkey -> relays ) */
-export const userRelays = new Keyv<string[] | undefined>({
+export const pubkeyRelays = new Keyv<string[] | undefined>({
   ...opts,
   namespace: "relays",
   ttl: CACHE_TIME * 1000,
diff --git a/src/dns.ts b/src/dns.ts
new file mode 100644
index 0000000..6ab9495
--- /dev/null
+++ b/src/dns.ts
@@ -0,0 +1,79 @@
+import dns from "node:dns";
+import { nip19 } from "nostr-tools";
+import { pubkeyDomains as pubkeyDomains } from "./cache.js";
+import logger from "./logger.js";
+
+export function getCnameRecords(hostname: string): Promise<string[]> {
+  return new Promise<string[]>((res, rej) => {
+    dns.resolveCname(hostname, (err, records) => {
+      if (err) rej(err);
+      else res(records);
+    });
+  });
+}
+export function getTxtRecords(hostname: string): Promise<string[][]> {
+  return new Promise<string[][]>((res, rej) => {
+    dns.resolveTxt(hostname, (err, records) => {
+      if (err) rej(err);
+      else res(records);
+    });
+  });
+}
+
+function extractPubkeyFromHostname(hostname: string): string | undefined {
+  const [npub] = hostname.split(".");
+
+  if (npub.startsWith("npub")) {
+    const parsed = nip19.decode(npub);
+    if (parsed.type !== "npub") throw new Error("Expected npub");
+
+    return parsed.data;
+  }
+}
+
+const log = logger.extend("DNS");
+
+export async function resolvePubkeyFromHostname(hostname: string): Promise<string | undefined> {
+  if (hostname === "localhost") return undefined;
+
+  const cached = await pubkeyDomains.get(hostname);
+  if (cached) return cached;
+
+  // check if domain contains an npub
+  let pubkey = extractPubkeyFromHostname(hostname);
+
+  // try to get npub from CNAME or TXT records
+  if (!pubkey) {
+    try {
+      const cnameRecords = await getCnameRecords(hostname);
+      for (const cname of cnameRecords) {
+        const p = extractPubkeyFromHostname(cname);
+        if (p) {
+          pubkey = p;
+          break;
+        }
+      }
+    } catch (error) {}
+  }
+
+  if (!pubkey) {
+    try {
+      const txtRecords = await getTxtRecords(hostname);
+
+      for (const txt of txtRecords) {
+        for (const entry of txt) {
+          const p = extractPubkeyFromHostname(entry);
+          if (p) {
+            pubkey = p;
+            break;
+          }
+        }
+      }
+    } catch (error) {}
+  }
+
+  log(`Resolved ${hostname} to ${pubkey}`);
+  await pubkeyDomains.set(hostname, pubkey);
+
+  return pubkey;
+}
diff --git a/src/helpers/dns.ts b/src/helpers/dns.ts
deleted file mode 100644
index abaae53..0000000
--- a/src/helpers/dns.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import dns from "node:dns";
-import { nip19 } from "nostr-tools";
-
-export function getCnameRecords(hostname: string) {
-  return new Promise<string[]>((res, rej) => {
-    dns.resolveCname(hostname, (err, records) => {
-      if (err) rej(err);
-      else res(records);
-    });
-  });
-}
-export function getTxtRecords(hostname: string) {
-  return new Promise<string[][]>((res, rej) => {
-    dns.resolveTxt(hostname, (err, records) => {
-      if (err) rej(err);
-      else res(records);
-    });
-  });
-}
-
-function extractNpubFromHostname(hostname: string) {
-  const [npub] = hostname.split(".");
-
-  if (npub.startsWith("npub")) {
-    const parsed = nip19.decode(npub);
-    if (parsed.type !== "npub") throw new Error("Expected npub");
-
-    return parsed.data;
-  }
-}
-
-export async function resolveNpubFromHostname(hostname: string) {
-  // check if domain contains an npub
-  let pubkey = extractNpubFromHostname(hostname);
-
-  if (pubkey) return pubkey;
-
-  if (hostname === "localhost") return undefined;
-
-  // try to get npub from CNAME or TXT records
-  try {
-    const cnameRecords = await getCnameRecords(hostname);
-    for (const cname of cnameRecords) {
-      const p = extractNpubFromHostname(cname);
-      if (p) return p;
-    }
-  } catch (error) {}
-
-  try {
-    const txtRecords = await getTxtRecords(hostname);
-
-    for (const txt of txtRecords) {
-      for (const entry of txt) {
-        const p = extractNpubFromHostname(entry);
-        if (p) return p;
-      }
-    }
-  } catch (error) {}
-}
diff --git a/src/index.ts b/src/index.ts
index 2360f3e..d9def96 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,10 +9,9 @@ import { fileURLToPath } from "node:url";
 import mime from "mime";
 import morgan from "koa-morgan";
 import { npubEncode } from "nostr-tools/nip19";
-import { spawn } from "node:child_process";
 import { nip19 } from "nostr-tools";
 
-import { resolveNpubFromHostname } from "./helpers/dns.js";
+import { resolvePubkeyFromHostname } from "./dns.js";
 import { getNsiteBlob } from "./events.js";
 import { streamBlob } from "./blossom.js";
 import {
@@ -25,7 +24,6 @@ import {
   ONION_HOST,
   SUBSCRIPTION_RELAYS,
 } from "./env.js";
-import { userDomains, userRelays, userServers } from "./cache.js";
 import pool, { getUserBlossomServers, getUserOutboxes } from "./nostr.js";
 import logger from "./logger.js";
 import { watchInvalidation } from "./invalidation.js";
@@ -62,28 +60,20 @@ app.use(async (ctx, next) => {
 
 // handle nsite requests
 app.use(async (ctx, next) => {
-  let pubkey = await userDomains.get<string | undefined>(ctx.hostname);
+  let pubkey = await resolvePubkeyFromHostname(ctx.hostname);
 
-  // resolve pubkey if not in cache
-  if (pubkey === undefined) {
-    logger(`${ctx.hostname}: Resolving`);
-    pubkey = await resolveNpubFromHostname(ctx.hostname);
+  if (!pubkey) {
+    if (NSITE_HOMEPAGE) {
+      const parsed = nip19.decode(NSITE_HOMEPAGE);
+      // TODO: use the relays in the nprofile
 
-    if (pubkey) {
-      await userDomains.set(ctx.hostname, pubkey);
-      logger(`${ctx.hostname}: Found ${pubkey}`);
-    } else {
-      await userDomains.set(ctx.hostname, "");
-    }
+      if (parsed.type === "nprofile") pubkey = parsed.data.pubkey;
+      else if (parsed.type === "npub") pubkey = parsed.data;
+      else return await next();
+    } else return await next();
   }
 
-  if (!pubkey) return await next();
-
-  const npub = npubEncode(pubkey);
-  const log = logger.extend(npub);
-  ctx.state.pubkey = pubkey;
-
-  // fetch relays if not in cache
+  // fetch relays
   const relays = (await getUserOutboxes(pubkey)) || [];
 
   // always check subscription relays
@@ -94,21 +84,13 @@ app.use(async (ctx, next) => {
   // fetch servers and events in parallel
   let [servers, event] = await Promise.all([
     getUserBlossomServers(pubkey, relays).then((s) => s || []),
-    (async () => {
-      let e = await getNsiteBlob(pubkey, ctx.path, relays);
-
-      // fallback to custom 404 page
-      if (!e) {
-        log(`Looking for custom 404 page`);
-        e = await getNsiteBlob(pubkey, "/404.html", relays);
-      }
-
-      return e;
-    })(),
+    getNsiteBlob(pubkey, ctx.path, relays).then((e) => {
+      if (!e) return getNsiteBlob(pubkey, "/404.html", relays);
+      else return e;
+    }),
   ]);
 
   if (!event) {
-    log(`Found 0 events for ${ctx.path}`);
     ctx.status = 404;
     ctx.body = `Not Found: no events found\npath: ${ctx.path}\nkind: ${NSITE_KIND}\npubkey: ${pubkey}\nrelays: ${relays.join(", ")}`;
     return;
@@ -150,11 +132,10 @@ app.use(async (ctx, next) => {
     ctx.body = res;
     return;
   } catch (error) {
-    log(`Failed to stream ${event.sha256}\n${error}`);
+    ctx.status = 500;
+    ctx.body = `Failed to stream blob ${event.path}\n${error}`;
+    return;
   }
-
-  ctx.status = 500;
-  ctx.body = "Failed to find blob";
 });
 
 if (ONION_HOST) {
@@ -168,39 +149,6 @@ if (ONION_HOST) {
   });
 }
 
-// download homepage
-if (NSITE_HOMEPAGE) {
-  try {
-    const log = logger.extend("homepage");
-    // create the public dir
-    try {
-      fs.mkdirSync(NSITE_HOMEPAGE_DIR);
-    } catch (error) {}
-
-    const bin = (await import.meta.resolve("nsite-cli")).replace("file://", "");
-
-    const decode = nip19.decode(NSITE_HOMEPAGE);
-    if (decode.type !== "nprofile") throw new Error("NSITE_HOMEPAGE must be a valid nprofile");
-
-    // use nsite-cli to download the homepage
-    const args = [bin, "download", NSITE_HOMEPAGE_DIR, nip19.npubEncode(decode.data.pubkey)];
-    if (decode.data.relays) args.push("--relays", decode.data.relays?.join(","));
-
-    const child = spawn("node", args, { stdio: "pipe" });
-
-    child.on("spawn", () => log("Downloading..."));
-    child.stdout.on("data", (line) => log(line.toString("utf-8")));
-    child.on("error", (e) => log("Failed", e));
-    child.on("close", (code) => {
-      if (code === 0) log("Finished");
-      else log("Failed");
-    });
-  } catch (error) {
-    console.log(`Failed to download homepage`);
-    console.log(error);
-  }
-}
-
 // serve static files from public
 const serveOptions: serve.Options = {
   hidden: true,
diff --git a/src/nostr.ts b/src/nostr.ts
index cb268e1..6a45982 100644
--- a/src/nostr.ts
+++ b/src/nostr.ts
@@ -2,7 +2,7 @@ import { Filter, NostrEvent, SimplePool } from "nostr-tools";
 import { getServersFromServerListEvent, USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
 
 import { LOOKUP_RELAYS } from "./env.js";
-import { userRelays, userServers } from "./cache.js";
+import { pubkeyRelays, pubkeyServers } from "./cache.js";
 import logger from "./logger.js";
 import { npubEncode } from "nostr-tools/nip19";
 
@@ -12,7 +12,7 @@ const log = logger.extend("nostr");
 
 /** Fetches a pubkeys mailboxes from the cache or relays */
 export async function getUserOutboxes(pubkey: string) {
-  const cached = await userRelays.get(pubkey);
+  const cached = await pubkeyRelays.get(pubkey);
   if (cached) return cached;
   const mailboxes = await pool.get(LOOKUP_RELAYS, { kinds: [10002], authors: [pubkey] });
 
@@ -23,15 +23,15 @@ export async function getUserOutboxes(pubkey: string) {
     .map((t) => t[1]);
 
   log(`Found ${relays.length} relays for ${npubEncode(pubkey)}`);
-  await userRelays.set(pubkey, relays);
+  await pubkeyRelays.set(pubkey, relays);
 
-  await userRelays.set(pubkey, relays);
+  await pubkeyRelays.set(pubkey, relays);
   return relays;
 }
 
 /** Fetches a pubkeys blossom servers from the cache or relays */
 export async function getUserBlossomServers(pubkey: string, relays: string[]) {
-  const cached = await userServers.get(pubkey);
+  const cached = await pubkeyServers.get(pubkey);
   if (cached) return cached;
 
   const blossomServersEvent = await pool.get(relays, { kinds: [USER_BLOSSOM_SERVER_LIST_KIND], authors: [pubkey] });
@@ -42,7 +42,7 @@ export async function getUserBlossomServers(pubkey: string, relays: string[]) {
   // Save servers if found
   if (servers) {
     log(`Found ${servers.length} blossom servers for ${npubEncode(pubkey)}`);
-    await userServers.set(pubkey, servers);
+    await pubkeyServers.set(pubkey, servers);
   }
 
   return servers;