import { injectPublishedPageDataContext, } from '../mindspace-public-page-context.mjs'; import { collectInlineScriptHashes, } from '../mindspace-public-delivery.mjs'; import { rewriteBrokenMindSpacePublicImageUrls, rewritePublicationCanonicalAssetUrls, } from '../mindspace-publications.mjs'; import { allowPlazaEmbedFrame, preparePublicationHtmlForEmbed, stripPublicationHtmlCspMeta, } from '../plaza-embed.mjs'; import { publishedPageCsp, } from '../mindspace-published-page-csp.mjs'; import { injectOgTags, injectWechatShareBridge, } from '../mindspace-og-tags.mjs'; import { isLongImageDownloadRequest, renderLongImageBuffer, } from '../mindspace-long-image.mjs'; import { isWechatUserAgent } from '../wechat-oauth.mjs'; import { appendQueryParam, detectPublishedPageTitle, extractOgImageUrl, extractShareMetaFromPageHtml, publishedPageShellHtml, removeQueryParam, resolveRequestOrigin, } from './portal-publication-shell.mjs'; export function createPortalPublishedPageDelivery({ registerLongImageArtifact, renderLongImage = renderLongImageBuffer, } = {}) { if ( typeof registerLongImageArtifact !== 'function' ) { throw new Error( 'createPortalPublishedPageDelivery requires artifact registration', ); } return async function sendPublishedPage( req, res, result, { embed = false, raw = false, ownerSlug = null, } = {}, ) { let html = rewriteBrokenMindSpacePublicImageUrls( result.html, ); if (ownerSlug) { html = rewritePublicationCanonicalAssetUrls( html, ownerSlug, ); } const origin = resolveRequestOrigin(req); const originalPath = req.originalUrl || req.url || ''; const sharePath = removeQueryParam( removeQueryParam( originalPath, 'download', ), 'export', ); const pageUrl = originalPath ? new URL( sharePath, origin || 'http://localhost', ) .toString() .split('#')[0] : ''; const pageDirUrl = pageUrl ? pageUrl.slice( 0, pageUrl.lastIndexOf('/') + 1, ) : ''; const wechatShare = !embed && isWechatUserAgent( req.get('user-agent') || '', ); if (embed) { html = preparePublicationHtmlForEmbed(html); allowPlazaEmbedFrame(res); } else if (raw) { html = stripPublicationHtmlCspMeta(html); } html = injectPublishedPageDataContext( html, { pageSource: result.pageSource, publication: result.publication, }, ); if (!embed) { try { html = injectOgTags(html, { origin, pageUrl, pageDirUrl, }); if (wechatShare) { html = injectWechatShareBridge( html, { pageUrl }, ); } } catch { // Never block delivery on share metadata. } } const isFullHtml = /^\s*]/i.test(html); if ( !embed && !raw && isFullHtml && isLongImageDownloadRequest(req.query) ) { try { const rawUrl = new URL( appendQueryParam( sharePath || originalPath, 'view', 'raw', ), origin || 'http://localhost', ); const image = await renderLongImage({ url: rawUrl.toString(), }); const longImageUrl = new URL( appendQueryParam( sharePath || originalPath, 'download', 'long-image', ), origin || 'http://localhost', ).toString(); await registerLongImageArtifact({ result, image, canonicalUrl: longImageUrl, }); res.set('Content-Type', 'image/png'); res.set( 'Content-Disposition', 'attachment; filename="mindspace-public-page.long.png"', ); res.set( 'Cache-Control', 'no-store', ); return res.send(image); } catch (error) { return res .status(500) .type( 'text/plain; charset=utf-8', ) .send( `长图生成失败:${ error?.message || '未知错误' }`, ); } } const canWrapWithShell = !embed && !raw && isFullHtml && result.publication?.accessMode !== 'password'; if (canWrapWithShell) { const title = detectPublishedPageTitle(html); const rawUrl = appendQueryParam( sharePath || originalPath, 'view', 'raw', ); const longImageUrl = appendQueryParam( sharePath || originalPath, 'download', 'long-image', ); let shellHtml = publishedPageShellHtml({ iframeUrl: rawUrl, shareUrl: pageUrl, title, longImageUrl, }); try { shellHtml = injectOgTags(shellHtml, { origin, pageUrl, pageDirUrl, fallbackImageUrl: extractOgImageUrl(html), meta: extractShareMetaFromPageHtml( html, ), }); if (wechatShare) { shellHtml = injectWechatShareBridge( shellHtml, { pageUrl }, ); } } catch { // Keep the share shell usable. } res.set( 'Content-Type', 'text/html; charset=utf-8', ); res.set( 'Content-Security-Policy', wechatShare ? "default-src 'none'; style-src 'unsafe-inline'; img-src data: https:; font-src 'none'; connect-src 'self' https://cdn.jsdelivr.net; script-src 'unsafe-inline' https://cdn.jsdelivr.net https://res.wx.qq.com; frame-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'" : "default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src 'none'; connect-src 'self' https://cdn.jsdelivr.net; script-src 'unsafe-inline' https://cdn.jsdelivr.net; frame-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'", ); res.set( 'Cache-Control', result.publication.accessMode === 'public' ? 'public, max-age=60' : 'private, no-store', ); return res.send(shellHtml); } res.set( 'Content-Type', 'text/html; charset=utf-8', ); res.set( 'Content-Security-Policy', publishedPageCsp(html, { embed, raw, wechatShare, scriptHashes: collectInlineScriptHashes(html), }), ); res.set( 'Cache-Control', result.publication.accessMode === 'public' ? 'public, max-age=60' : 'private, no-store', ); return res.send(html); }; }