From bfc1b1c4c29152bc9fc709b58bab8d92a920f590 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Wed, 25 Sep 2024 15:28:28 -0500 Subject: [PATCH] Add simple landing page --- .changeset/hot-llamas-clap.md | 5 ++ .env | 6 +- docker-compose.yml | 3 +- nginx/default.conf | 2 +- public/index.html | 40 +++------ public/main.js | 156 +++++----------------------------- public/upload/index.html | 46 ++++++++++ public/upload/upload.js | 142 +++++++++++++++++++++++++++++++ src/env.ts | 5 +- src/events.ts | 13 ++- src/index.ts | 15 +++- src/ndk.ts | 11 ++- 12 files changed, 265 insertions(+), 179 deletions(-) create mode 100644 .changeset/hot-llamas-clap.md create mode 100644 public/upload/index.html create mode 100644 public/upload/upload.js diff --git a/.changeset/hot-llamas-clap.md b/.changeset/hot-llamas-clap.md new file mode 100644 index 0000000..6bb275b --- /dev/null +++ b/.changeset/hot-llamas-clap.md @@ -0,0 +1,5 @@ +--- +"nsite-ts": minor +--- + +Add simple landing page diff --git a/.env b/.env index bafcc40..30a8ff4 100644 --- a/.env +++ b/.env @@ -1,6 +1,2 @@ CACHE_PATH="in-memory" - -NOSTR_RELAYS=wss://nos.lol,wss://relay.damus.io -BLOSSOM_SERVERS=https://cdn.hzrd149.com - -NGINX_HOST='nginx' +LOOKUP_RELAYS=wss://user.kindpag.es,wss://purplepag.es diff --git a/docker-compose.yml b/docker-compose.yml index 6dd837c..3035b7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,8 @@ services: nsite: build: . + image: ghcr.io/hzrd149/nsite-ts:master environment: - NOSTR_RELAYS: wss://nos.lol,wss://relay.damus.io + LOOKUP_RELAYS: wss://user.kindpag.es,wss://purplepag.es ports: - 3000:80 diff --git a/nginx/default.conf b/nginx/default.conf index 8ff7ac4..c946f5f 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -6,7 +6,7 @@ server { proxy_cache request_cache; proxy_cache_valid 200 60m; proxy_cache_valid 404 10m; - proxy_cache_key $scheme$proxy_host$request_uri; + proxy_cache_key $scheme$host$request_uri; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cache $upstream_cache_status; diff --git a/public/index.html b/public/index.html index 24ab39d..9e3679a 100644 --- a/public/index.html +++ b/public/index.html @@ -14,33 +14,19 @@ - -
- -
-
- -
- -
-
- - -
+

nsite-ts

+ Source Code + +

Latest nsites:

+
+ + + diff --git a/public/main.js b/public/main.js index a0dda2b..92ba0ed 100644 --- a/public/main.js +++ b/public/main.js @@ -1,142 +1,32 @@ -import { multiServerUpload, BlossomClient } from "blossom-client-sdk"; -import { SimplePool } from "nostr-tools"; +import { nip19, SimplePool } from "nostr-tools"; -const logContainer = document.getElementById("log"); -function log(...args) { - const el = document.createElement("div"); - el.innerText = args.join(" "); - logContainer.appendChild(el); -} +const seen = new Set(); +function addSite(event) { + if (seen.has(event.pubkey)) return; + seen.add(event.pubkey); -const uploadButton = document.getElementById("upload-button"); + try { + const template = document.getElementById("site"); + const site = template.content.cloneNode(true); + const npub = nip19.npubEncode(event.pubkey); -/** @type {HTMLInputElement} */ -const filesInput = document.getElementById("files"); + site.querySelector(".pubkey").textContent = npub; + site.querySelector(".link").href = new URL("/", `${location.protocol}//${npub}.${location.host}`).toString(); -/** - * @param {FileSystemFileEntry} fileEntry - * @returns {File} - */ -export function readFileSystemFile(fileEntry) { - return new Promise((res, rej) => { - fileEntry.file( - (file) => res(file), - (err) => rej(err), - ); - }); -} - -/** - * @param {FileSystemDirectoryEntry} directory - * @returns {FileSystemEntry[]} - */ -export function readFileSystemDirectory(directory) { - return new Promise((res, rej) => { - directory.createReader().readEntries( - (entries) => res(entries), - (err) => rej(err), - ); - }); -} - -/** - * uploads a file system entry to blossom servers - * @param {FileSystemEntry} entry - * @returns {{file: File, path: string, sha256: string}[]} - */ -async function readFileSystemEntry(entry) { - const files = []; - if (entry instanceof FileSystemFileEntry && entry.isFile) { - try { - const file = await readFileSystemFile(entry); - const sha256 = await BlossomClient.getFileSha256(file); - const path = entry.fullPath; - - files.push({ file, path, sha256 }); - } catch (e) { - log("Failed to add" + entry.fullPath); - log(e.message); - } - } else if (entry instanceof FileSystemDirectoryEntry && entry.isDirectory) { - const entries = await readFileSystemDirectory(entry); - for (const e of entries) files.push(...(await readFileSystemEntry(e))); + document.getElementById("sites").appendChild(site); + } catch (error) { + console.log("Failed to add site", event); + console.log(error); } - - return files; -} - -/** - * uploads a file system entry to blossom servers - * @param {FileList} list - * @returns {{file: File, path: string, sha256: string}[]} - */ -async function readFileList(list) { - const files = []; - for (const file of list) { - const path = file.webkitRelativePath ? file.webkitRelativePath : file.name; - const sha256 = await BlossomClient.getFileSha256(file); - files.push({ file, path, sha256 }); - } - return files; } const pool = new SimplePool(); -/** - * uploads a file system entry to blossom servers - * @param {{file:File, path:string}} files - * @param {import("blossom-client-sdk").Signer} signer - * @param {*} auth - * @param {string[]} servers - * @param {string[]} relays - */ -async function uploadFiles(files, signer, auth, servers, relays) { - for (const { file, path, sha256 } of files) { - try { - const upload = multiServerUpload(servers, file, signer, auth); - - let published = false; - for await (let { blob } of upload) { - if (!published) { - const signed = await signer({ - kind: 34128, - content: "", - created_at: Math.round(Date.now() / 1000), - tags: [ - ["d", path], - ["x", sha256], - ], - }); - await pool.publish(relays, signed); - - log("Published", path, sha256, signed.id); - } - } - } catch (error) { - log(`Failed to upload ${path}`, error); - } - } -} - -uploadButton.addEventListener("click", async () => { - if (!window.nostr) return alert("Missing NIP-07 signer"); - - const signer = (draft) => window.nostr.signEvent(draft); - const relays = document.getElementById("relays").value.split(/\n|,/); - const servers = document.getElementById("servers").value.split(/\n|,/); - - try { - if (filesInput.files) { - const files = await readFileList(filesInput.files); - - // strip leading dir - for (const file of files) file.path = file.path.replace(/^[^\/]+\//, "/"); - - log(`Found ${files.length} files`); - - await uploadFiles(files, signer, undefined, servers, relays); - } - } catch (error) { - alert(`Failed to upload files: ${error.message}`); - } -}); +console.log("Loading sites"); +pool.subscribeMany( + ["wss://relay.damus.io", "wss://nos.lol", "wss://nostr.wine"], + [{ kinds: [34128], "#d": ["/index.html"] }], + { + onevent: addSite, + }, +); diff --git a/public/upload/index.html b/public/upload/index.html new file mode 100644 index 0000000..57c2ebd --- /dev/null +++ b/public/upload/index.html @@ -0,0 +1,46 @@ + + + + + + nsite + + + + +
+ +
+
+ +
+ +
+
+ + +
+ + + diff --git a/public/upload/upload.js b/public/upload/upload.js new file mode 100644 index 0000000..a0dda2b --- /dev/null +++ b/public/upload/upload.js @@ -0,0 +1,142 @@ +import { multiServerUpload, BlossomClient } from "blossom-client-sdk"; +import { SimplePool } from "nostr-tools"; + +const logContainer = document.getElementById("log"); +function log(...args) { + const el = document.createElement("div"); + el.innerText = args.join(" "); + logContainer.appendChild(el); +} + +const uploadButton = document.getElementById("upload-button"); + +/** @type {HTMLInputElement} */ +const filesInput = document.getElementById("files"); + +/** + * @param {FileSystemFileEntry} fileEntry + * @returns {File} + */ +export function readFileSystemFile(fileEntry) { + return new Promise((res, rej) => { + fileEntry.file( + (file) => res(file), + (err) => rej(err), + ); + }); +} + +/** + * @param {FileSystemDirectoryEntry} directory + * @returns {FileSystemEntry[]} + */ +export function readFileSystemDirectory(directory) { + return new Promise((res, rej) => { + directory.createReader().readEntries( + (entries) => res(entries), + (err) => rej(err), + ); + }); +} + +/** + * uploads a file system entry to blossom servers + * @param {FileSystemEntry} entry + * @returns {{file: File, path: string, sha256: string}[]} + */ +async function readFileSystemEntry(entry) { + const files = []; + if (entry instanceof FileSystemFileEntry && entry.isFile) { + try { + const file = await readFileSystemFile(entry); + const sha256 = await BlossomClient.getFileSha256(file); + const path = entry.fullPath; + + files.push({ file, path, sha256 }); + } catch (e) { + log("Failed to add" + entry.fullPath); + log(e.message); + } + } else if (entry instanceof FileSystemDirectoryEntry && entry.isDirectory) { + const entries = await readFileSystemDirectory(entry); + for (const e of entries) files.push(...(await readFileSystemEntry(e))); + } + + return files; +} + +/** + * uploads a file system entry to blossom servers + * @param {FileList} list + * @returns {{file: File, path: string, sha256: string}[]} + */ +async function readFileList(list) { + const files = []; + for (const file of list) { + const path = file.webkitRelativePath ? file.webkitRelativePath : file.name; + const sha256 = await BlossomClient.getFileSha256(file); + files.push({ file, path, sha256 }); + } + return files; +} + +const pool = new SimplePool(); + +/** + * uploads a file system entry to blossom servers + * @param {{file:File, path:string}} files + * @param {import("blossom-client-sdk").Signer} signer + * @param {*} auth + * @param {string[]} servers + * @param {string[]} relays + */ +async function uploadFiles(files, signer, auth, servers, relays) { + for (const { file, path, sha256 } of files) { + try { + const upload = multiServerUpload(servers, file, signer, auth); + + let published = false; + for await (let { blob } of upload) { + if (!published) { + const signed = await signer({ + kind: 34128, + content: "", + created_at: Math.round(Date.now() / 1000), + tags: [ + ["d", path], + ["x", sha256], + ], + }); + await pool.publish(relays, signed); + + log("Published", path, sha256, signed.id); + } + } + } catch (error) { + log(`Failed to upload ${path}`, error); + } + } +} + +uploadButton.addEventListener("click", async () => { + if (!window.nostr) return alert("Missing NIP-07 signer"); + + const signer = (draft) => window.nostr.signEvent(draft); + const relays = document.getElementById("relays").value.split(/\n|,/); + const servers = document.getElementById("servers").value.split(/\n|,/); + + try { + if (filesInput.files) { + const files = await readFileList(filesInput.files); + + // strip leading dir + for (const file of files) file.path = file.path.replace(/^[^\/]+\//, "/"); + + log(`Found ${files.length} files`); + + await uploadFiles(files, signer, undefined, servers, relays); + } + } catch (error) { + alert(`Failed to upload files: ${error.message}`); + } +}); diff --git a/src/env.ts b/src/env.ts index d31b1be..57cdc4f 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,6 +1,7 @@ import "dotenv/config"; import xbytes from "xbytes"; +const LOOKUP_RELAYS = process.env.LOOKUP_RELAYS?.split(",") ?? ["wss://user.kindpag.es/", "wss://purplepag.es/"]; const NOSTR_RELAYS = process.env.NOSTR_RELAYS?.split(",") ?? []; const BLOSSOM_SERVERS = process.env.BLOSSOM_SERVERS?.split(",") ?? []; @@ -9,6 +10,4 @@ const MAX_FILE_SIZE = process.env.MAX_FILE_SIZE ? xbytes.parseSize(process.env.M const NGINX_HOST = process.env.NGINX_HOST; const CACHE_PATH = process.env.CACHE_PATH; -if (NOSTR_RELAYS.length === 0) throw new Error("Requires at least one relay in NOSTR_RELAYS"); - -export { NOSTR_RELAYS, BLOSSOM_SERVERS, MAX_FILE_SIZE, NGINX_HOST, CACHE_PATH }; +export { NOSTR_RELAYS, LOOKUP_RELAYS, BLOSSOM_SERVERS, MAX_FILE_SIZE, NGINX_HOST, CACHE_PATH }; diff --git a/src/events.ts b/src/events.ts index 584de88..417b103 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,6 +1,7 @@ import { extname, isAbsolute, join } from "path"; import { NSITE_KIND } from "./const.js"; import ndk from "./ndk.js"; +import { NDKRelaySet } from "@nostr-dev-kit/ndk"; export function getSearchPaths(path: string) { const paths = [path]; @@ -10,10 +11,10 @@ export function getSearchPaths(path: string) { // also look for relative paths for (const p of Array.from(paths)) { - if (isAbsolute(p)) paths.push(path.replace(/^\//, "")); + if (isAbsolute(p)) paths.push(p.replace(/^\//, "")); } - return paths; + return paths.filter((p) => !!p); } export function parseNsiteEvent(event: { pubkey: string; tags: string[][] }) { @@ -28,9 +29,13 @@ export function parseNsiteEvent(event: { pubkey: string; tags: string[][] }) { }; } -export async function getNsiteBlobs(pubkey: string, path: string) { +export async function getNsiteBlobs(pubkey: string, path: string, relays?: string[]) { const paths = getSearchPaths(path); - const events = await ndk.fetchEvents({ kinds: [NSITE_KIND], "#d": paths, authors: [pubkey] }); + const events = await ndk.fetchEvents( + { kinds: [NSITE_KIND], "#d": paths, authors: [pubkey] }, + {}, + relays && NDKRelaySet.fromRelayUrls(relays, ndk, true), + ); return Array.from(events) .map(parseNsiteEvent) diff --git a/src/index.ts b/src/index.ts index 43d9976..8385149 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,8 @@ import { resolveNpubFromHostname } from "./helpers/dns.js"; import { getNsiteBlobs } from "./events.js"; import { downloadFile, getUserBlossomServers } from "./blossom.js"; import { BLOSSOM_SERVERS } from "./env.js"; -import { userServers } from "./cache.js"; +import { userRelays, userServers } from "./cache.js"; +import { getUserOutboxes } from "./ndk.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -49,8 +50,16 @@ app.use(async (ctx, next) => { const pubkey = (ctx.state.pubkey = await resolveNpubFromHostname(ctx.hostname)); if (pubkey) { + console.log(`${pubkey}: Fetching relays`); + + let relays = await userRelays.get(pubkey); + if (!relays) { + relays = await getUserOutboxes(pubkey); + if (relays) await userRelays.set(pubkey, relays); + } + console.log(`${pubkey}: Searching for ${ctx.path}`); - const blobs = await getNsiteBlobs(pubkey, ctx.path); + const blobs = await getNsiteBlobs(pubkey, ctx.path, relays); if (blobs.length === 0) { ctx.status = 404; @@ -58,7 +67,7 @@ app.use(async (ctx, next) => { return; } - let servers = await userServers.get(pubkey); + let servers = await userServers.get(pubkey); if (!servers) { console.log(`${pubkey}: Searching for blossom servers`); servers = (await getUserBlossomServers(pubkey)) ?? []; diff --git a/src/ndk.ts b/src/ndk.ts index 69f4729..4a00495 100644 --- a/src/ndk.ts +++ b/src/ndk.ts @@ -1,10 +1,17 @@ import NDK from "@nostr-dev-kit/ndk"; -import { NOSTR_RELAYS } from "./env.js"; +import { LOOKUP_RELAYS, NOSTR_RELAYS } from "./env.js"; const ndk = new NDK({ - explicitRelayUrls: NOSTR_RELAYS, + explicitRelayUrls: [...LOOKUP_RELAYS, ...NOSTR_RELAYS], }); ndk.connect(); +export async function getUserOutboxes(pubkey: string) { + const mailboxes = await ndk.fetchEvent({ kinds: [10002], authors: [pubkey] }); + if (!mailboxes) return; + + return mailboxes.tags.filter((t) => t[0] === "r" && (t[2] === undefined || t[2] === "write")).map((t) => t[1]); +} + export default ndk;