159 lines
5.9 KiB
JavaScript
159 lines
5.9 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { PUBLISH_KEY_UUID, PUBLISH_ROOT_DIR } from './user-publish.mjs';
|
|
import { normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
|
|
import { getPageDataPolicyIndexByWorkspacePath } from './page-data-policy-index.mjs';
|
|
|
|
const LOCAL_HOST_PATTERN = /^(localhost|127\.0\.0\.1|\[::1\]|192\.168\.|10\.|100\.)/i;
|
|
|
|
export function resolvePublicRequestOrigin({ hostHeader = '', forwardedProto = '', protocol = 'http' } = {}) {
|
|
const host = String(hostHeader ?? '').split(',')[0].trim();
|
|
if (!host) return '';
|
|
const fwdProto = String(forwardedProto ?? '').split(',')[0].trim();
|
|
const isLocalHost = LOCAL_HOST_PATTERN.test(host);
|
|
const resolvedProtocol = isLocalHost ? fwdProto || protocol || 'http' : 'https';
|
|
return `${resolvedProtocol}://${host}`;
|
|
}
|
|
|
|
export function buildPublishedHtmlViewContext({
|
|
origin = '',
|
|
requestPath = '',
|
|
filePath = '',
|
|
thumbnailPngPathForSvg = (value) => value,
|
|
fileExists = fs.existsSync,
|
|
} = {}) {
|
|
const cleanPath = String(requestPath ?? '').split('?')[0].split('#')[0];
|
|
const servedName = path.basename(String(filePath ?? ''));
|
|
const urlLast = decodeURIComponent(cleanPath.split('/').filter(Boolean).pop() ?? '');
|
|
const isImplicitIndex = urlLast.toLowerCase() !== servedName.toLowerCase();
|
|
const pageUrl = !origin
|
|
? ''
|
|
: `${origin}${isImplicitIndex && !cleanPath.endsWith('/') ? `${cleanPath}/` : cleanPath}`;
|
|
const pageDirUrl = !origin
|
|
? ''
|
|
: isImplicitIndex
|
|
? `${origin}${cleanPath.endsWith('/') ? cleanPath : `${cleanPath}/`}`
|
|
: `${origin}${cleanPath.slice(0, cleanPath.lastIndexOf('/') + 1)}`;
|
|
|
|
let fallbackImageUrl = '';
|
|
const svgSibling = String(filePath ?? '').replace(/\.[^./]+$/, '.thumbnail.svg');
|
|
if (pageDirUrl && fileExists(svgSibling)) {
|
|
const pngName = path.basename(thumbnailPngPathForSvg(svgSibling));
|
|
fallbackImageUrl = `${pageDirUrl}${pngName}`;
|
|
}
|
|
|
|
return {
|
|
origin,
|
|
cleanPath,
|
|
pageUrl,
|
|
pageDirUrl,
|
|
fallbackImageUrl,
|
|
};
|
|
}
|
|
|
|
export function parseMindSpacePublishFilePath(absoluteFilePath, h5Root) {
|
|
const resolved = path.resolve(String(absoluteFilePath ?? ''));
|
|
const publishRoot = path.resolve(h5Root, PUBLISH_ROOT_DIR);
|
|
if (resolved !== publishRoot && !resolved.startsWith(`${publishRoot}${path.sep}`)) {
|
|
return null;
|
|
}
|
|
const rest = resolved.slice(publishRoot.length + 1).split(path.sep).filter(Boolean);
|
|
if (rest.length < 2) return null;
|
|
const ownerKey = String(rest[0] ?? '').trim().toLowerCase();
|
|
if (!PUBLISH_KEY_UUID.test(ownerKey)) return null;
|
|
const relativePath = normalizeWorkspaceRelativePath(rest.slice(1).join('/'));
|
|
if (!relativePath) return null;
|
|
return { userId: ownerKey, relativePath };
|
|
}
|
|
|
|
export function buildMindSpacePageDataPayload({ pageId, accessMode = null } = {}) {
|
|
const id = String(pageId ?? '').trim();
|
|
if (!id) return null;
|
|
const payload = { pageId: id };
|
|
const mode = String(accessMode ?? '').trim();
|
|
if (mode) payload.accessMode = mode;
|
|
return payload;
|
|
}
|
|
|
|
export function injectMindSpacePageDataContext(html, context = {}) {
|
|
const payload = buildMindSpacePageDataPayload(context);
|
|
if (!payload) return String(html ?? '');
|
|
let nextHtml = String(html ?? '');
|
|
const serialized = JSON.stringify(payload).replace(/</g, '\\u003c');
|
|
const escapedPageId = String(payload.pageId).replace(/"/g, '"');
|
|
const injection = [
|
|
`<meta name="mindspace-page-data-page-id" content="${escapedPageId}">`,
|
|
`<script>window.__MINDSPACE_PAGE_DATA__=${serialized};</script>`,
|
|
].join('\n');
|
|
if (/<\/head>/i.test(nextHtml)) {
|
|
return nextHtml.replace(/<\/head>/i, `${injection}\n</head>`);
|
|
}
|
|
if (/<body\b/i.test(nextHtml)) {
|
|
return nextHtml.replace(/<body\b/i, `${injection}\n<body`);
|
|
}
|
|
return `${injection}\n${nextHtml}`;
|
|
}
|
|
|
|
export function injectPublishedPageDataContext(html, { pageSource = null, publication = null } = {}) {
|
|
const pageId = String(pageSource?.pageId ?? publication?.pageId ?? '').trim();
|
|
if (!pageId) return String(html ?? '');
|
|
return injectMindSpacePageDataContext(html, {
|
|
pageId,
|
|
accessMode: publication?.accessMode ?? null,
|
|
});
|
|
}
|
|
|
|
export async function resolveMindSpacePageDataContext({
|
|
pool = null,
|
|
pageService = null,
|
|
userId,
|
|
relativePath,
|
|
logger = console,
|
|
} = {}) {
|
|
const normalizedUserId = String(userId ?? '').trim().toLowerCase();
|
|
const normalizedRelativePath = normalizeWorkspaceRelativePath(relativePath);
|
|
if (!normalizedUserId || !normalizedRelativePath) return null;
|
|
|
|
// Page Data policy lives in the Portal control plane. In split-service mode the
|
|
// generic MindSpace page service is remote, so it may not see a local binding
|
|
// immediately. Resolve the local binding index first and retain the page service
|
|
// as the compatibility fallback for ordinary published pages.
|
|
const indexed = await getPageDataPolicyIndexByWorkspacePath(
|
|
pool,
|
|
normalizedUserId,
|
|
normalizedRelativePath,
|
|
).catch((error) => {
|
|
logger?.warn?.(
|
|
`[MindSpace] failed to resolve local Page Data binding for ${normalizedUserId}/${normalizedRelativePath}: ${error?.message || error}`,
|
|
);
|
|
return null;
|
|
});
|
|
if (indexed?.pageId) {
|
|
return {
|
|
pageId: indexed.pageId,
|
|
accessMode: indexed.accessMode ?? null,
|
|
publicationId: indexed.publicationId ?? null,
|
|
};
|
|
}
|
|
|
|
if (typeof pageService?.findPageByRelativePath !== 'function') return null;
|
|
const page = await pageService
|
|
.findPageByRelativePath(normalizedUserId, normalizedRelativePath)
|
|
.catch((error) => {
|
|
logger?.warn?.(
|
|
`[MindSpace] failed to resolve remote page context for ${normalizedUserId}/${normalizedRelativePath}: ${error?.message || error}`,
|
|
);
|
|
return null;
|
|
});
|
|
if (!page?.id) return null;
|
|
return {
|
|
pageId: page.id,
|
|
accessMode: page.publicationAccessMode ?? null,
|
|
publicationId: page.currentPublishId ?? page.current_publish_id ?? null,
|
|
};
|
|
}
|
|
|
|
export const mindspacePublicPageContextInternals = {
|
|
LOCAL_HOST_PATTERN,
|
|
};
|