282 lines
6.9 KiB
JavaScript
282 lines
6.9 KiB
JavaScript
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*<!doctype html/i.test(html) ||
|
|
/^\s*<html[\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);
|
|
};
|
|
}
|