nostr-deploy-server
Version:
Node.js server for hosting static websites under npub subdomains using Nostr protocol and Blossom servers
213 lines • 8.72 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimpleSSRHelper = void 0;
const puppeteer_1 = __importDefault(require("puppeteer"));
const config_1 = require("../utils/config");
const logger_1 = require("../utils/logger");
class SimpleSSRHelper {
constructor() {
this.config = config_1.ConfigManager.getInstance().getConfig();
}
/**
* Render a page using a fresh browser instance (simpler approach)
*/
async renderPage(url, originalContent, contentType) {
const startTime = Date.now();
let browser = null;
let page = null;
try {
// Check if SSR is enabled
if (!this.config.ssrEnabled) {
return {
html: originalContent.toString(),
contentType,
status: 200,
};
}
// Only render HTML files
if (!contentType.includes('text/html')) {
return {
html: originalContent.toString(),
contentType,
status: 200,
};
}
logger_1.logger.debug(`SSR: Starting render for ${url}`);
// Launch fresh browser for each request (simpler but more reliable)
browser = await puppeteer_1.default.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
timeout: 15000,
});
page = await browser.newPage();
// Set viewport
await page.setViewport({
width: this.config.ssrViewportWidth,
height: this.config.ssrViewportHeight,
});
// Set a special user agent to identify SSR requests and prevent recursion
await page.setUserAgent('NostrSSRBot/1.0 (Internal SSR Request)');
// Bypass CSP to allow JavaScript execution
await page.setBypassCSP(true);
// Add console logging for debugging
page.on('console', (msg) => {
logger_1.logger.debug(`SSR Browser [${msg.type()}]: ${msg.text()}`);
});
page.on('pageerror', (error) => {
logger_1.logger.warn(`SSR Page error: ${error.message}`);
});
// Enable request interception to handle asset loading
await page.setRequestInterception(true);
page.on('request', (request) => {
const requestUrl = request.url();
logger_1.logger.debug(`SSR: Request for ${requestUrl}`);
// Handle relative URLs by redirecting to the actual server
if (requestUrl.startsWith('/') && !requestUrl.startsWith('//')) {
const baseUrl = new URL(url);
const fullUrl = `${baseUrl.protocol}//${baseUrl.host}${requestUrl}`;
logger_1.logger.debug(`SSR: Redirecting ${requestUrl} to ${fullUrl}`);
request.continue({ url: fullUrl });
return;
}
// Allow all other requests
request.continue();
});
page.on('response', (response) => {
const responseUrl = response.url();
if (responseUrl.includes('/assets/')) {
logger_1.logger.debug(`SSR: Asset response ${response.status()} for ${responseUrl}`);
}
});
// Set timeout
page.setDefaultTimeout(this.config.ssrTimeoutMs);
// Instead of setContent, use goto to the actual URL (this works!)
logger_1.logger.debug(`SSR: Navigating to ${url}`);
await page.goto(url, {
waitUntil: 'networkidle0', // Wait for all network requests to finish
timeout: 15000,
});
// Wait for any JavaScript to execute
logger_1.logger.debug('SSR: Waiting for JavaScript execution...');
await new Promise((resolve) => setTimeout(resolve, 5000)); // Increased wait time
// Try to wait for root element to be populated
try {
await page.waitForFunction(`() => {
const root = document.getElementById('root');
return root && root.innerHTML.trim().length > 50;
}`, { timeout: 8000 } // Increased timeout
);
logger_1.logger.debug('SSR: Root element populated with content');
}
catch (waitError) {
logger_1.logger.warn('SSR: Root element not populated within timeout, checking current state...');
// Check what's actually in the root element
const rootContent = await page.evaluate(`
const root = document.getElementById('root');
return root ? root.innerHTML : 'ROOT_NOT_FOUND';
`);
logger_1.logger.debug(`SSR: Current root content: "${rootContent}"`);
// Check for any errors or missing assets
const hasErrors = await page.evaluate(`
return window.onerror ? 'Has errors' : 'No errors detected';
`);
logger_1.logger.debug(`SSR: Error status: ${hasErrors}`);
}
// Get the rendered HTML
const renderedHtml = await page.content();
// Add SSR meta tags
const enhancedHtml = this.addSSRMetaTags(renderedHtml, url);
const renderTime = Date.now() - startTime;
logger_1.logger.info(`SSR rendered page in ${renderTime}ms for URL: ${url}`);
return {
html: enhancedHtml,
contentType: 'text/html; charset=utf-8',
status: 200,
};
}
catch (error) {
const renderTime = Date.now() - startTime;
logger_1.logger.warn(`SSR failed after ${renderTime}ms for URL: ${url}: ${error}`);
// Fallback to original content
return {
html: originalContent.toString(),
contentType,
status: 200,
};
}
finally {
try {
if (page)
await page.close();
if (browser)
await browser.close();
}
catch (closeError) {
logger_1.logger.debug('SSR cleanup error (expected):', closeError);
}
}
}
/**
* Add basic SSR meta tags
*/
addSSRMetaTags(html, url) {
const metaTags = `
<meta name="generator" content="Nostr Static Server SSR">
<meta property="og:url" content="${url}">
<meta name="twitter:url" content="${url}">
<link rel="canonical" href="${url}">
`;
if (html.includes('<head>')) {
return html.replace('<head>', `<head>${metaTags}`);
}
else if (html.includes('<html>')) {
return html.replace('<html>', `<html><head>${metaTags}</head>`);
}
return html;
}
/**
* Check if a file should be SSR rendered
*/
shouldRenderSSR(contentType, path, userAgent) {
// Check if SSR is enabled
if (!this.config.ssrEnabled) {
return false;
}
// Skip SSR for internal SSR requests to prevent recursion
if (userAgent && userAgent.includes('NostrSSRBot')) {
return false;
}
// Only render HTML files
if (!contentType.includes('text/html')) {
return false;
}
// Don't render API endpoints or admin paths
if (path.startsWith('/api/') || path.startsWith('/admin/')) {
return false;
}
return true;
}
/**
* Close any open browser instances
*/
async close() {
// This implementation uses fresh browser instances for each request
// so there's nothing persistent to close
logger_1.logger.debug('SimpleSSRHelper: No persistent browser to close');
}
/**
* Get browser statistics
*/
async getBrowserStats() {
// Since we use fresh browser instances for each request,
// we don't have persistent browser connections to report on
return {
isConnected: false, // No persistent browser connection
pagesCount: 0, // No persistent pages
};
}
}
exports.SimpleSSRHelper = SimpleSSRHelper;
//# sourceMappingURL=ssr-simple.js.map