Compare commits

...

3 Commits

Author SHA1 Message Date
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
hzrd149
f3cf298ea5
Merge pull request #15 from hzrd149/changeset-release/master
Version Packages
2025-07-23 12:22:28 -05:00
5 changed files with 495 additions and 405 deletions

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "nsite-gateway",
"version": "1.1.0",
"version": "1.1.1",
"description": "A blossom server implementation written in Typescript",
"main": "build/index.js",
"type": "module",
@ -24,24 +24,24 @@
"@koa/cors": "^5.0.0",
"debug": "^4.4.1",
"dotenv": "^16.6.1",
"follow-redirects": "^1.15.9",
"keyv": "^5.4.0",
"koa": "^2.16.1",
"follow-redirects": "^1.15.11",
"keyv": "^5.5.0",
"koa": "^2.16.2",
"koa-morgan": "^1.0.1",
"koa-range": "^0.3.0",
"koa-send": "^5.0.1",
"koa-static": "^5.0.0",
"mime": "^4.0.7",
"nostr-tools": "^2.15.1",
"nostr-tools": "^2.16.2",
"pac-proxy-agent": "^7.2.0",
"proxy-agent": "^6.5.0",
"ws": "^8.18.3",
"xbytes": "^1.9.1"
},
"devDependencies": {
"@changesets/cli": "^2.29.5",
"@swc-node/register": "^1.10.10",
"@swc/core": "^1.13.1",
"@changesets/cli": "^2.29.6",
"@swc-node/register": "^1.11.1",
"@swc/core": "^1.13.5",
"@types/better-sqlite3": "^7.6.13",
"@types/debug": "^4.1.12",
"@types/follow-redirects": "^1.14.4",
@ -52,13 +52,13 @@
"@types/koa-static": "^4.0.4",
"@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4",
"@types/node": "^20.19.9",
"@types/node": "^20.19.13",
"@types/proxy-from-env": "^1.0.4",
"@types/ws": "^8.18.1",
"esbuild": "^0.25.8",
"esbuild": "^0.25.9",
"nodemon": "^3.1.10",
"prettier": "^3.6.2",
"typescript": "^5.8.3"
"typescript": "^5.9.2"
},
"resolutions": {
"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);
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) => {
const url = new URL(sha256, server);
const domain = url.hostname;
const check = await fetch(url, { method: "HEAD" }).catch(() => null);
if (check?.status === 200) return url.toString();
else return null;
try {
const check = await fetch(url, { method: "HEAD" });
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);
return filtered;
}
@ -35,38 +50,72 @@ export async function streamBlob(
servers: string[],
headers?: Record<string, string>,
): 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
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
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();
let res: IncomingMessage | undefined = undefined;
try {
// Set up timeout to abort after 10s
const timeout = setTimeout(() => {
streamLog(`Request to ${domain} timed out after 10s`);
controller.abort();
}, 10_000);
const url = new URL(urlString);
const response = await makeRequestWithAbort(url, controller, headers);
res = response;
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"];
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
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) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
streamLog(`${domain} - Failed: ${errorMessage}`);
if (res) res.resume();
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
app.listen({ host: NSITE_HOST, port: NSITE_PORT }, () => {
logger("Started on port", HOST);
console.log("Started on port", HOST);
});
// watch for invalidations
@ -205,7 +205,7 @@ process.on("unhandledRejection", (reason, promise) => {
});
async function shutdown() {
logger("Shutting down...");
console.log("Shutting down...");
pool.destroy();
process.exit(0);
}