feat(mindspace): gate published page delivery
This commit is contained in:
+78
-2
@@ -131,10 +131,12 @@ import {
|
||||
resolveStaticHtmlContent,
|
||||
} from './mindspace-chat-save.mjs';
|
||||
import {
|
||||
collectPublicHtmlWritePathsFromSessionEvent,
|
||||
materializePublicHtmlWritesFromSessionEvent,
|
||||
normalizePublicHtmlRelativePath,
|
||||
syncPublicHtmlAfterFinish,
|
||||
} from './mindspace-public-finish-sync.mjs';
|
||||
import { getPageDeliveryContract, markPageDeliveryContractReady, preparePageDeliveryContract } from './mindspace-delivery-contract.mjs';
|
||||
import { maybeRepairH5HtmlAfterFinish } from './mindspace-h5-html-finish-guard.mjs';
|
||||
import { maybeRepairPageDataAfterFinish } from './mindspace-page-data-finish-guard.mjs';
|
||||
import { ensurePageDataHtmlPagesBound } from './page-data-workspace-ensure.mjs';
|
||||
@@ -5075,7 +5077,35 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
return sendDirectChatSessionEvents(req, res, portalDirectSnapshot);
|
||||
}
|
||||
const publishDir = resolveMindSpaceUserPublishDir(__dirname, { id: req.currentUser.id });
|
||||
// `proxySessionEvents` deliberately invokes `onEvent` synchronously so an
|
||||
// async callback here would leave rejected database writes unhandled.
|
||||
// Retain every in-flight contract write and await it before marking files
|
||||
// deliverable after Finish.
|
||||
const deliveryContractWrites = new Map();
|
||||
const syncPublicHtmlDuringStream = (event) => {
|
||||
const paths = collectPublicHtmlWritePathsFromSessionEvent(event, { publishDir });
|
||||
const eventMessages = event?.type === 'Message' && event.message
|
||||
? [event.message]
|
||||
: event?.type === 'UpdateConversation' && Array.isArray(event.conversation)
|
||||
? event.conversation
|
||||
: [];
|
||||
const pgRequired = eventMessages.some(
|
||||
(message) => message?.role === 'user' && message?.metadata?.memindRun?.pgRequired === true,
|
||||
);
|
||||
for (const relativePath of paths) {
|
||||
if (deliveryContractWrites.has(relativePath)) continue;
|
||||
const write = preparePageDeliveryContract({
|
||||
pool: authPool,
|
||||
userId: req.currentUser.id,
|
||||
requestId: sid,
|
||||
relativePath,
|
||||
pgRequired,
|
||||
}).catch((error) => {
|
||||
console.warn(`[MindSpace] failed to prepare delivery contract for ${relativePath}: ${error?.message || error}`);
|
||||
return null;
|
||||
});
|
||||
deliveryContractWrites.set(relativePath, write);
|
||||
}
|
||||
materializePublicHtmlWritesFromSessionEvent(event, { publishDir });
|
||||
};
|
||||
// After Finish, refresh the snapshot and persist any newly generated public
|
||||
@@ -5131,7 +5161,7 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
`[MindSpace] missing public download files after finish for user ${uid}: ${syncResult.docxSync.missing.join(', ')}`,
|
||||
);
|
||||
}
|
||||
await maybeRepairH5HtmlAfterFinish({
|
||||
const htmlDelivery = await maybeRepairH5HtmlAfterFinish({
|
||||
sessionId: sid,
|
||||
userId: uid,
|
||||
currentUser: req.currentUser,
|
||||
@@ -5154,7 +5184,7 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
.join('\n')
|
||||
: '';
|
||||
await syncUserGeneratedPages(uid, { sessionId: sid });
|
||||
await maybeRepairPageDataAfterFinish({
|
||||
const pageDataDelivery = await maybeRepairPageDataAfterFinish({
|
||||
sessionId: sid,
|
||||
userId: uid,
|
||||
publishDir,
|
||||
@@ -5165,6 +5195,40 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
tkmindProxy,
|
||||
userText: lastUserText,
|
||||
});
|
||||
const htmlReady = htmlDelivery?.skipped === 'ok';
|
||||
const pageDataReady = ['ok', 'not_page_data'].includes(String(pageDataDelivery?.skipped ?? ''));
|
||||
if (htmlReady && pageDataReady) {
|
||||
const publicHtmlRelativePaths = syncResult?.publicHtmlRelativePaths ?? [];
|
||||
const pgRequired = [...(Array.isArray(messages) ? messages : [])].some(
|
||||
(message) => message?.role === 'user' && message?.metadata?.memindRun?.pgRequired === true,
|
||||
);
|
||||
for (const relativePath of publicHtmlRelativePaths) {
|
||||
// A Finish-only write may not have reached the stream callback. This
|
||||
// also upgrades an early partial stream contract with the definitive
|
||||
// user delivery choice before it becomes ready.
|
||||
await (deliveryContractWrites.get(relativePath) ?? preparePageDeliveryContract({
|
||||
pool: authPool,
|
||||
userId: uid,
|
||||
requestId: sid,
|
||||
relativePath,
|
||||
pgRequired,
|
||||
}));
|
||||
if (deliveryContractWrites.has(relativePath)) {
|
||||
await preparePageDeliveryContract({
|
||||
pool: authPool,
|
||||
userId: uid,
|
||||
requestId: sid,
|
||||
relativePath,
|
||||
pgRequired,
|
||||
});
|
||||
}
|
||||
await markPageDeliveryContractReady({
|
||||
pool: authPool,
|
||||
userId: uid,
|
||||
relativePath,
|
||||
}).catch(() => false);
|
||||
}
|
||||
}
|
||||
if (lastUserMessage && memoryV2?.observePersonalMemory) {
|
||||
await memoryV2.observePersonalMemory({
|
||||
userId: uid,
|
||||
@@ -6174,6 +6238,18 @@ async function serveUserPublishFile(req, res, next) {
|
||||
// On-demand cover: rasterize <base>.thumbnail.svg → .thumbnail.png the first time a
|
||||
// forwarded link's og:image is fetched (and refresh it when the SVG changes).
|
||||
const resolvedPath = result.filePath;
|
||||
if (authPool && result.ownerKey && /\.html$/i.test(resolvedPath)) {
|
||||
const publishDir = resolveMindSpaceUserPublishDir(__dirname, { id: result.ownerKey });
|
||||
const relativePath = path.relative(publishDir, resolvedPath).replace(/\\/g, '/');
|
||||
const contract = await getPageDeliveryContract({
|
||||
pool: authPool,
|
||||
userId: result.ownerKey,
|
||||
relativePath,
|
||||
}).catch(() => null);
|
||||
if (contract && contract.status !== 'ready') {
|
||||
return res.status(409).type('text/plain; charset=utf-8').send('页面已生成,正在完成发布验证,请稍后重试。');
|
||||
}
|
||||
}
|
||||
if (/\.thumbnail\.png$/i.test(resolvedPath)) {
|
||||
const svgSibling = resolvedPath.replace(/\.png$/i, '.svg');
|
||||
if (fs.existsSync(svgSibling)) {
|
||||
|
||||
Reference in New Issue
Block a user