ee8677cc6d
Wire delivery path detection through portal static routes and strip baked share chrome from learn parent/child HTML. Co-authored-by: Cursor <cursoragent@cursor.com>
171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
import crypto from 'node:crypto';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { injectMindSpacePageDataContext } from './mindspace-public-page-context.mjs';
|
|
import { preparePublishedPlatformBrand } from './mindspace-page-tag.mjs';
|
|
import { injectPublicImageRetryScript } from './mindspace-public-image-retry.mjs';
|
|
import { applyWechatSurveyCompat } from './mindspace-page-data-wechat-survey-compat.mjs';
|
|
import { stripPublicationHtmlCspMeta } from './plaza-embed.mjs';
|
|
import {
|
|
decorateMindSpaceSeoGeoHtml,
|
|
} from './mindspace-seo-geo-delivery.mjs';
|
|
import { stripPublicFileShareWidget } from './mindspace-public-share-widget.mjs';
|
|
|
|
const INLINE_SCRIPT_PATTERN = /<script\b(?![^>]*\bsrc\b)[^>]*>([\s\S]*?)<\/script>/gi;
|
|
|
|
export function collectInlineScriptHashes(html) {
|
|
const hashes = [];
|
|
const seen = new Set();
|
|
for (const match of String(html ?? '').matchAll(INLINE_SCRIPT_PATTERN)) {
|
|
const hash = crypto.createHash('sha256').update(match[1] ?? '').digest('base64');
|
|
if (seen.has(hash)) continue;
|
|
seen.add(hash);
|
|
hashes.push(hash);
|
|
}
|
|
return hashes;
|
|
}
|
|
|
|
export function shouldSkipPublicShareWidget({
|
|
htmlFilePath = '',
|
|
pageUrl = '',
|
|
relativePath = '',
|
|
requestPath = '',
|
|
} = {}) {
|
|
const haystack = `${htmlFilePath}\n${pageUrl}\n${relativePath}\n${requestPath}`
|
|
.replace(/\\/g, '/')
|
|
.toLowerCase();
|
|
return (
|
|
haystack.includes('learning-assistant') ||
|
|
haystack.includes('/public/learn/') ||
|
|
/\/learn(?:\/|$|[?#])/.test(haystack)
|
|
);
|
|
}
|
|
|
|
export async function handleMindSpaceLongImageDownload({
|
|
query,
|
|
filePath,
|
|
pageUrl = null,
|
|
res,
|
|
isLongImageDownloadRequest,
|
|
longImagePathForHtml,
|
|
renderLongImage,
|
|
} = {}) {
|
|
if (!isLongImageDownloadRequest?.(query)) return false;
|
|
const longImagePath = longImagePathForHtml(filePath);
|
|
try {
|
|
await renderLongImage(pageUrl
|
|
? { url: pageUrl, outputPath: longImagePath }
|
|
: { htmlPath: filePath, outputPath: longImagePath });
|
|
res.set('Cache-Control', 'no-store');
|
|
res.download(longImagePath, path.basename(longImagePath), (err) => {
|
|
if (err && !res.headersSent) res.status(404).json({ message: '长图文件不存在' });
|
|
});
|
|
} catch (error) {
|
|
res
|
|
.status(500)
|
|
.type('text/plain; charset=utf-8')
|
|
.send(`长图生成失败:${error?.message || '未知错误'}`);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function decorateMindSpacePublishedHtml({
|
|
html,
|
|
embed = false,
|
|
isOwner = true,
|
|
pageDataContext = null,
|
|
context,
|
|
htmlFilePath = '',
|
|
relativePath = '',
|
|
requestPath = '',
|
|
fileExists = fs.existsSync,
|
|
userAgent = '',
|
|
preparePublicationHtmlForEmbed,
|
|
injectOgTags,
|
|
injectWechatShareBridge,
|
|
injectPublicFileShareButton,
|
|
publishedPageCsp,
|
|
isWechatUserAgent,
|
|
seoGeoConfig = null,
|
|
publicationSnapshot = null,
|
|
} = {}) {
|
|
// Preview HTML carries a restrictive inline CSP (often script-src 'none').
|
|
// Published delivery sets the authoritative CSP response header below; keep
|
|
// the preview meta out of the delivered document so Page Data and other
|
|
// same-origin scripts are governed by that header instead of being blocked
|
|
// by a stale preview policy.
|
|
let nextHtml = stripPublicationHtmlCspMeta(html);
|
|
let allowEmbedFrame = false;
|
|
|
|
if (embed) {
|
|
nextHtml = preparePublicationHtmlForEmbed(nextHtml);
|
|
allowEmbedFrame = true;
|
|
}
|
|
|
|
try {
|
|
nextHtml = preparePublishedPlatformBrand(nextHtml);
|
|
nextHtml = injectOgTags(nextHtml, {
|
|
origin: context.origin,
|
|
pageUrl: context.pageUrl,
|
|
pageDirUrl: context.pageDirUrl,
|
|
fallbackImageUrl: context.fallbackImageUrl,
|
|
htmlFilePath,
|
|
fileExists,
|
|
});
|
|
const wechatShare = !embed && isWechatUserAgent(userAgent || '');
|
|
if (wechatShare) {
|
|
nextHtml = injectWechatShareBridge(nextHtml, { pageUrl: context.pageUrl });
|
|
}
|
|
const skipShareWidget = !embed && shouldSkipPublicShareWidget({
|
|
htmlFilePath,
|
|
pageUrl: context?.pageUrl ?? '',
|
|
relativePath,
|
|
requestPath,
|
|
});
|
|
if (skipShareWidget) {
|
|
nextHtml = stripPublicFileShareWidget(nextHtml);
|
|
} else if (!embed) {
|
|
nextHtml = injectPublicFileShareButton(nextHtml, { isOwner }).html;
|
|
}
|
|
// Retries transient asset-download failures client-side before a page's own onerror
|
|
// permanently swaps the <img> for a placeholder. See mindspace-public-image-retry.mjs.
|
|
if (!embed) {
|
|
nextHtml = injectPublicImageRetryScript(nextHtml).html;
|
|
}
|
|
if (pageDataContext?.pageId) {
|
|
nextHtml = injectMindSpacePageDataContext(nextHtml, pageDataContext);
|
|
}
|
|
if (!embed && isWechatUserAgent(userAgent || '')) {
|
|
nextHtml = applyWechatSurveyCompat(nextHtml).html;
|
|
}
|
|
const seoGeoDecorated = decorateMindSpaceSeoGeoHtml(nextHtml, {
|
|
seoGeoConfig,
|
|
publicationSnapshot,
|
|
pageDataContext,
|
|
embed,
|
|
context,
|
|
htmlFilePath,
|
|
fileExists,
|
|
});
|
|
nextHtml = seoGeoDecorated.html;
|
|
const scriptHashes = collectInlineScriptHashes(nextHtml);
|
|
return {
|
|
html: nextHtml,
|
|
csp: publishedPageCsp(nextHtml, {
|
|
embed,
|
|
wechatShare,
|
|
scriptHashes,
|
|
}),
|
|
allowEmbedFrame,
|
|
robotsHeader: seoGeoDecorated.robotsHeader,
|
|
};
|
|
} catch {
|
|
return {
|
|
html: nextHtml,
|
|
csp: publishedPageCsp(nextHtml, { embed }),
|
|
allowEmbedFrame,
|
|
robotsHeader: null,
|
|
};
|
|
}
|
|
}
|