UNPKG

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
"use strict"; 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