Compare commits

...

3 Commits

Author SHA1 Message Date
hzrd149
193db371e3
Merge pull request #16 from hzrd149/changeset-release/master
Version Packages
2025-09-04 13:55:16 +01:00
github-actions[bot]
4cff73450c Version Packages 2025-09-04 12:36:07 +00:00
hzrd149
100ff4c1dc Update dependencies and improve error handling in blob streaming functions 2025-09-04 13:35:44 +01:00
5 changed files with 495 additions and 405 deletions

View File

@ -1,5 +1,11 @@
# nsite-gateway # nsite-gateway
## 1.1.1
### Patch Changes
- 100ff4c: Add more debug logging to blob streaming
## 1.1.0 ## 1.1.0
### Minor Changes ### Minor Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "nsite-gateway", "name": "nsite-gateway",
"version": "1.1.0", "version": "1.1.1",
"description": "A blossom server implementation written in Typescript", "description": "A blossom server implementation written in Typescript",
"main": "build/index.js", "main": "build/index.js",
"type": "module", "type": "module",
@ -24,24 +24,24 @@
"@koa/cors": "^5.0.0", "@koa/cors": "^5.0.0",
"debug": "^4.4.1", "debug": "^4.4.1",
"dotenv": "^16.6.1", "dotenv": "^16.6.1",
"follow-redirects": "^1.15.9", "follow-redirects": "^1.15.11",
"keyv": "^5.4.0", "keyv": "^5.5.0",
"koa": "^2.16.1", "koa": "^2.16.2",
"koa-morgan": "^1.0.1", "koa-morgan": "^1.0.1",
"koa-range": "^0.3.0", "koa-range": "^0.3.0",
"koa-send": "^5.0.1", "koa-send": "^5.0.1",
"koa-static": "^5.0.0", "koa-static": "^5.0.0",
"mime": "^4.0.7", "mime": "^4.0.7",
"nostr-tools": "^2.15.1", "nostr-tools": "^2.16.2",
"pac-proxy-agent": "^7.2.0", "pac-proxy-agent": "^7.2.0",
"proxy-agent": "^6.5.0", "proxy-agent": "^6.5.0",
"ws": "^8.18.3", "ws": "^8.18.3",
"xbytes": "^1.9.1" "xbytes": "^1.9.1"
}, },
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.29.5", "@changesets/cli": "^2.29.6",
"@swc-node/register": "^1.10.10", "@swc-node/register": "^1.11.1",
"@swc/core": "^1.13.1", "@swc/core": "^1.13.5",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/follow-redirects": "^1.14.4", "@types/follow-redirects": "^1.14.4",
@ -52,13 +52,13 @@
"@types/koa-static": "^4.0.4", "@types/koa-static": "^4.0.4",
"@types/koa__cors": "^5.0.0", "@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4", "@types/koa__router": "^12.0.4",
"@types/node": "^20.19.9", "@types/node": "^20.19.13",
"@types/proxy-from-env": "^1.0.4", "@types/proxy-from-env": "^1.0.4",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"esbuild": "^0.25.8", "esbuild": "^0.25.9",
"nodemon": "^3.1.10", "nodemon": "^3.1.10",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.8.3" "typescript": "^5.9.2"
}, },
"resolutions": { "resolutions": {
"websocket-polyfill": "1.0.0" "websocket-polyfill": "1.0.0"

793
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -12,19 +12,34 @@ export async function findBlobURLs(sha256: string, servers: string[]): Promise<s
const cache = await blobURLs.get(sha256); const cache = await blobURLs.get(sha256);
if (cache) return cache; if (cache) return cache;
const urls = await Promise.all( const id = sha256.slice(0, 6);
const requestLog = log.extend(id);
requestLog(`Checking ${servers.length} servers for ${sha256}`);
const results = await Promise.all(
servers.map(async (server) => { servers.map(async (server) => {
const url = new URL(sha256, server); const url = new URL(sha256, server);
const domain = url.hostname;
const check = await fetch(url, { method: "HEAD" }).catch(() => null); try {
if (check?.status === 200) return url.toString(); const check = await fetch(url, { method: "HEAD" });
else return null; if (check.status === 200) {
requestLog(`${domain} - HTTP ${check.status} (success)`);
return url.toString();
} else {
requestLog(`${domain} - HTTP ${check.status} (failed)`);
return null;
}
} catch (error) {
requestLog(`${domain} - Network error: ${error instanceof Error ? error.message : "Unknown error"}`);
return null;
}
}), }),
); );
const filtered = urls.filter((url) => url !== null); const filtered = results.filter((url) => url !== null);
log(`Found ${filtered.length}/${servers.length} URLs for ${sha256}`); requestLog(`Found ${filtered.length}/${servers.length} Servers`);
await blobURLs.set(sha256, filtered); await blobURLs.set(sha256, filtered);
return filtered; return filtered;
} }
@ -35,38 +50,72 @@ export async function streamBlob(
servers: string[], servers: string[],
headers?: Record<string, string>, headers?: Record<string, string>,
): Promise<IncomingMessage | undefined> { ): Promise<IncomingMessage | undefined> {
if (servers.length === 0) return undefined; const id = sha256.slice(0, 6);
const streamLog = log.extend(id);
if (servers.length === 0) {
streamLog(`No servers provided for blob ${sha256}`);
return undefined;
}
streamLog(`Starting blob stream for ${sha256} from ${servers.length} servers`);
// First find all available URLs // First find all available URLs
const urls = await findBlobURLs(sha256, servers); const urls = await findBlobURLs(sha256, servers);
if (urls.length === 0) return undefined; if (urls.length === 0) {
streamLog(`No available URLs found for blob ${sha256}`);
return undefined;
}
streamLog(`Attempting to stream from ${urls.length} available URLs`);
// Try each URL sequentially with timeout // Try each URL sequentially with timeout
for (const urlString of urls) { for (let i = 0; i < urls.length; i++) {
const urlString = urls[i];
const url = new URL(urlString);
const domain = url.hostname;
streamLog(`Trying server ${i + 1}/${urls.length}: ${domain}`);
const controller = new AbortController(); const controller = new AbortController();
let res: IncomingMessage | undefined = undefined; let res: IncomingMessage | undefined = undefined;
try { try {
// Set up timeout to abort after 10s // Set up timeout to abort after 10s
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
streamLog(`Request to ${domain} timed out after 10s`);
controller.abort(); controller.abort();
}, 10_000); }, 10_000);
const url = new URL(urlString);
const response = await makeRequestWithAbort(url, controller, headers); const response = await makeRequestWithAbort(url, controller, headers);
res = response; res = response;
clearTimeout(timeout); clearTimeout(timeout);
if (!response.statusCode) throw new Error("Missing headers or status code"); if (!response.statusCode) {
throw new Error("Missing headers or status code");
}
const size = response.headers["content-length"]; const size = response.headers["content-length"];
if (size && parseInt(size) > MAX_FILE_SIZE) throw new Error("File too large"); if (size && parseInt(size) > MAX_FILE_SIZE) {
throw new Error(`File too large: ${size} bytes (max: ${MAX_FILE_SIZE})`);
}
// Accept both 200 (full content) and 206 (partial content) status codes // Accept both 200 (full content) and 206 (partial content) status codes
if (response.statusCode >= 200 && response.statusCode < 300) return response; if (response.statusCode >= 200 && response.statusCode < 300) {
streamLog(`${domain} - HTTP ${response.statusCode} - Successfully streaming blob ${sha256}`);
return response;
} else {
throw new Error(`HTTP ${response.statusCode}`);
}
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
streamLog(`${domain} - Failed: ${errorMessage}`);
if (res) res.resume(); if (res) res.resume();
continue; // Try next URL if this one fails continue; // Try next URL if this one fails
} }
} }
streamLog(`All servers failed for blob ${sha256}`);
return undefined;
} }

View File

@ -194,7 +194,7 @@ try {
// start the server // start the server
app.listen({ host: NSITE_HOST, port: NSITE_PORT }, () => { app.listen({ host: NSITE_HOST, port: NSITE_PORT }, () => {
logger("Started on port", HOST); console.log("Started on port", HOST);
}); });
// watch for invalidations // watch for invalidations
@ -205,7 +205,7 @@ process.on("unhandledRejection", (reason, promise) => {
}); });
async function shutdown() { async function shutdown() {
logger("Shutting down..."); console.log("Shutting down...");
pool.destroy(); pool.destroy();
process.exit(0); process.exit(0);
} }