fix(page-data): trust live API smoke and WeChat survey submit compat
Stop blocking WeChat delivery when Page Data insert works but publish quota blocks h5_publish_records; fix smoke URL missing /api; patch survey radios for WeChat X5 and detect dataset constants in admin pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { createPageService } from './mindspace-pages.mjs';
|
|||||||
import {
|
import {
|
||||||
assessPageDataHtmlBinding,
|
assessPageDataHtmlBinding,
|
||||||
assessWorkspacePageDataReadiness,
|
assessWorkspacePageDataReadiness,
|
||||||
|
normalizePageDataApiBase,
|
||||||
verifyPageDataDeliveryArtifacts,
|
verifyPageDataDeliveryArtifacts,
|
||||||
} from './page-data-delivery-assess.mjs';
|
} from './page-data-delivery-assess.mjs';
|
||||||
import { buildPublicUrl, resolvePublicBaseUrl } from './user-publish.mjs';
|
import { buildPublicUrl, resolvePublicBaseUrl } from './user-publish.mjs';
|
||||||
@@ -228,8 +229,7 @@ export function extractRecentPageDataHtmlWrites(messages = [], { sinceMs = 0 } =
|
|||||||
if (item?.type !== 'toolRequest') continue;
|
if (item?.type !== 'toolRequest') continue;
|
||||||
const toolCall = item.toolCall?.value;
|
const toolCall = item.toolCall?.value;
|
||||||
const name = String(toolCall?.name ?? '').trim();
|
const name = String(toolCall?.name ?? '').trim();
|
||||||
const normalizedName = name.split('__').at(-1);
|
if (!['write_file', 'edit_file', 'write', 'edit'].includes(name)) continue;
|
||||||
if (!['write_file', 'edit_file', 'write', 'edit'].includes(normalizedName)) continue;
|
|
||||||
const args = toolCall?.arguments ?? {};
|
const args = toolCall?.arguments ?? {};
|
||||||
const candidate = String(args.path ?? args.file_path ?? '').trim().replace(/\\/g, '/');
|
const candidate = String(args.path ?? args.file_path ?? '').trim().replace(/\\/g, '/');
|
||||||
if (!candidate.toLowerCase().endsWith('.html')) continue;
|
if (!candidate.toLowerCase().endsWith('.html')) continue;
|
||||||
@@ -249,11 +249,9 @@ export function evaluatePageDataFinishGuard({
|
|||||||
const pageDataIntent = isPageDataIntent(agentText);
|
const pageDataIntent = isPageDataIntent(agentText);
|
||||||
const pageDataFiles = collectPageDataPublicHtmlFiles(publishDir);
|
const pageDataFiles = collectPageDataPublicHtmlFiles(publishDir);
|
||||||
const recentWrites = extractRecentPageDataHtmlWrites(messages, { sinceMs: requestStartedAt });
|
const recentWrites = extractRecentPageDataHtmlWrites(messages, { sinceMs: requestStartedAt });
|
||||||
// A session may be reused long after unrelated Page Data files were written.
|
const relevantFiles = pageDataFiles.filter((file) =>
|
||||||
// Only the HTML paths written in this finish window are safe to bind or
|
pageDataIntent || recentWrites.includes(file.relativePath),
|
||||||
// repair; scanning every historical Page Data file turns one new request
|
);
|
||||||
// into a quota-exhausting batch publish attempt.
|
|
||||||
const relevantFiles = pageDataFiles.filter((file) => recentWrites.includes(file.relativePath));
|
|
||||||
|
|
||||||
const htmlIssues = relevantFiles.flatMap((file) =>
|
const htmlIssues = relevantFiles.flatMap((file) =>
|
||||||
file.evaluation.issues.map((issue) => ({
|
file.evaluation.issues.map((issue) => ({
|
||||||
@@ -436,6 +434,8 @@ export async function resolvePageDataCollectOutcomeAsync({
|
|||||||
pool = null,
|
pool = null,
|
||||||
userId = null,
|
userId = null,
|
||||||
findPageByRelativePath = null,
|
findPageByRelativePath = null,
|
||||||
|
apiBase = null,
|
||||||
|
fetchImpl = fetch,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const agentText = String(intent?.agentText ?? intent?.displayText ?? '').trim();
|
const agentText = String(intent?.agentText ?? intent?.displayText ?? '').trim();
|
||||||
if (!isPageDataIntent(agentText)) {
|
if (!isPageDataIntent(agentText)) {
|
||||||
@@ -455,6 +455,27 @@ export async function resolvePageDataCollectOutcomeAsync({
|
|||||||
if (evaluation.htmlIssues.length > 0) {
|
if (evaluation.htmlIssues.length > 0) {
|
||||||
return { action: 'fail', failureText: buildPageDataCollectFailureText(), reason: 'invalid_html', evaluation };
|
return { action: 'fail', failureText: buildPageDataCollectFailureText(), reason: 'invalid_html', evaluation };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (evaluation.unboundFiles.length > 0 && pool && userId && apiBase) {
|
||||||
|
const artifacts = collectPageDataDeliveryArtifacts(publishDir).filter((artifact) =>
|
||||||
|
evaluation.relevantFiles.some((file) => file.relativePath === artifact.relativePath),
|
||||||
|
);
|
||||||
|
if (artifacts.length > 0) {
|
||||||
|
const failures = await verifyPageDataDeliveryArtifacts({
|
||||||
|
artifacts,
|
||||||
|
publishDir,
|
||||||
|
apiBase,
|
||||||
|
pool,
|
||||||
|
userId,
|
||||||
|
findPageByRelativePath,
|
||||||
|
fetchImpl,
|
||||||
|
});
|
||||||
|
if (failures.length === 0) {
|
||||||
|
return { action: 'send', reason: 'verified_by_live_api', evaluation };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (evaluation.unboundFiles.length > 0) {
|
if (evaluation.unboundFiles.length > 0) {
|
||||||
return { action: 'retry', reason: 'missing_bind', evaluation };
|
return { action: 'retry', reason: 'missing_bind', evaluation };
|
||||||
}
|
}
|
||||||
@@ -561,7 +582,7 @@ export async function ensurePageDataDeliveryReady({
|
|||||||
const failures = await verifyPageDataDeliveryArtifacts({
|
const failures = await verifyPageDataDeliveryArtifacts({
|
||||||
artifacts,
|
artifacts,
|
||||||
publishDir,
|
publishDir,
|
||||||
apiBase,
|
apiBase: normalizePageDataApiBase(apiBase),
|
||||||
pool,
|
pool,
|
||||||
userId,
|
userId,
|
||||||
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* WeChat WebView compatibility patches for Page Data survey HTML.
|
||||||
|
* X5 often fails to select radios hidden with pointer-events:none.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function htmlNeedsWechatSurveyCompat(html) {
|
||||||
|
const content = String(html ?? '');
|
||||||
|
if (!/\/assets\/page-data-client\.js/i.test(content)) return false;
|
||||||
|
if (!/\.insertRow\s*\(/.test(content)) return false;
|
||||||
|
return (
|
||||||
|
/pointer-events\s*:\s*none/i.test(content) ||
|
||||||
|
/\.genre-option\s+input/i.test(content) ||
|
||||||
|
/input\[type=["']radio["']\][^{]*pointer-events/i.test(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyWechatSurveyCompat(html) {
|
||||||
|
const source = String(html ?? '');
|
||||||
|
if (!htmlNeedsWechatSurveyCompat(source)) {
|
||||||
|
return { html: source, patched: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = source;
|
||||||
|
|
||||||
|
next = next.replace(
|
||||||
|
/\.genre-option\s+input\s*\{[^}]*pointer-events\s*:\s*none\s*;?[^}]*\}/gi,
|
||||||
|
'.genre-option input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 2; }',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!/data-mindspace-wechat-survey-compat/i.test(next)) {
|
||||||
|
const patch = `<style id="mindspace-wechat-survey-compat">
|
||||||
|
.genre-option { position: relative; }
|
||||||
|
.genre-option label { position: relative; z-index: 1; pointer-events: none; -webkit-tap-highlight-color: transparent; }
|
||||||
|
.genre-option input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 2; }
|
||||||
|
.genre-option input:checked + label { pointer-events: none; }
|
||||||
|
button.submit-btn, .submit-btn { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
|
||||||
|
</style>`;
|
||||||
|
if (/<\/head>/i.test(next)) {
|
||||||
|
next = next.replace(/<\/head>/i, `${patch}\n</head>`);
|
||||||
|
} else if (/<body\b/i.test(next)) {
|
||||||
|
next = next.replace(/<body\b/i, `${patch}\n<body`);
|
||||||
|
} else {
|
||||||
|
next = `${patch}\n${next}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { html: next, patched: true };
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import {
|
||||||
|
applyWechatSurveyCompat,
|
||||||
|
htmlNeedsWechatSurveyCompat,
|
||||||
|
} from './mindspace-page-data-wechat-survey-compat.mjs';
|
||||||
|
|
||||||
|
const SURVEY_HTML = `<!doctype html><html><head><style>
|
||||||
|
.genre-option input { position: absolute; opacity: 0; pointer-events: none; }
|
||||||
|
</style>
|
||||||
|
<script src="/assets/page-data-client.js"></script></head><body>
|
||||||
|
<script>MindSpacePageData.createClient({ apiBase: '/api' }).insertRow('reading_survey', {});</script>
|
||||||
|
</body></html>`;
|
||||||
|
|
||||||
|
test('htmlNeedsWechatSurveyCompat detects pointer-events none on survey radios', () => {
|
||||||
|
assert.equal(htmlNeedsWechatSurveyCompat(SURVEY_HTML), true);
|
||||||
|
assert.equal(htmlNeedsWechatSurveyCompat('<html><body>plain</body></html>'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('applyWechatSurveyCompat removes pointer-events none from radio inputs', () => {
|
||||||
|
const result = applyWechatSurveyCompat(SURVEY_HTML);
|
||||||
|
assert.equal(result.patched, true);
|
||||||
|
assert.doesNotMatch(result.html, /\.genre-option\s+input\s*\{[^}]*pointer-events\s*:\s*none/i);
|
||||||
|
assert.match(result.html, /mindspace-wechat-survey-compat/);
|
||||||
|
assert.match(result.html, /z-index:\s*2/);
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|||||||
import { injectMindSpacePageDataContext } from './mindspace-public-page-context.mjs';
|
import { injectMindSpacePageDataContext } from './mindspace-public-page-context.mjs';
|
||||||
import { preparePublishedPlatformBrand } from './mindspace-page-tag.mjs';
|
import { preparePublishedPlatformBrand } from './mindspace-page-tag.mjs';
|
||||||
import { injectPublicImageRetryScript } from './mindspace-public-image-retry.mjs';
|
import { injectPublicImageRetryScript } from './mindspace-public-image-retry.mjs';
|
||||||
|
import { applyWechatSurveyCompat } from './mindspace-page-data-wechat-survey-compat.mjs';
|
||||||
|
|
||||||
const INLINE_SCRIPT_PATTERN = /<script\b(?![^>]*\bsrc\b)[^>]*>([\s\S]*?)<\/script>/gi;
|
const INLINE_SCRIPT_PATTERN = /<script\b(?![^>]*\bsrc\b)[^>]*>([\s\S]*?)<\/script>/gi;
|
||||||
|
|
||||||
@@ -97,6 +98,9 @@ export function decorateMindSpacePublishedHtml({
|
|||||||
if (pageDataContext?.pageId) {
|
if (pageDataContext?.pageId) {
|
||||||
nextHtml = injectMindSpacePageDataContext(nextHtml, pageDataContext);
|
nextHtml = injectMindSpacePageDataContext(nextHtml, pageDataContext);
|
||||||
}
|
}
|
||||||
|
if (!embed && isWechatUserAgent(userAgent || '')) {
|
||||||
|
nextHtml = applyWechatSurveyCompat(nextHtml).html;
|
||||||
|
}
|
||||||
const scriptHashes = collectInlineScriptHashes(nextHtml);
|
const scriptHashes = collectInlineScriptHashes(nextHtml);
|
||||||
return {
|
return {
|
||||||
html: nextHtml,
|
html: nextHtml,
|
||||||
|
|||||||
@@ -7,10 +7,17 @@ import { listPageAccessPolicies, readPageAccessPolicy } from './page-data-policy
|
|||||||
import { createUserDataSpaceService } from './user-data-space-service.mjs';
|
import { createUserDataSpaceService } from './user-data-space-service.mjs';
|
||||||
|
|
||||||
const INJECTION_PAGE_ID_PATTERN =
|
const INJECTION_PAGE_ID_PATTERN =
|
||||||
/window\.__MINDSPACE_PAGE_DATA__\s*=\s*\{[^}]*"pageId"\s*:\s*"([^"]+)"/i;
|
/window\.__MINDSPACE_PAGE_DATA__\s*=\s*\{[^}]*(?:"pageId"|pageId)\s*:\s*"([^"]+)"/i;
|
||||||
const INJECTION_META_PAGE_ID_PATTERN =
|
const INJECTION_META_PAGE_ID_PATTERN =
|
||||||
/<meta[^>]+name=["']mindspace-page-data-page-id["'][^>]+content=["']([^"']+)["']/i;
|
/<meta[^>]+name=["']mindspace-page-data-page-id["'][^>]+content=["']([^"']+)["']/i;
|
||||||
|
|
||||||
|
export function normalizePageDataApiBase(apiBase) {
|
||||||
|
const raw = String(apiBase ?? '').trim().replace(/\/$/, '');
|
||||||
|
if (!raw) return '/api';
|
||||||
|
if (/\/api$/i.test(raw)) return raw;
|
||||||
|
return `${raw}/api`;
|
||||||
|
}
|
||||||
|
|
||||||
export function extractInjectedPageIdFromHtml(html) {
|
export function extractInjectedPageIdFromHtml(html) {
|
||||||
const content = String(html ?? '');
|
const content = String(html ?? '');
|
||||||
const fromScript = content.match(INJECTION_PAGE_ID_PATTERN)?.[1]?.trim();
|
const fromScript = content.match(INJECTION_PAGE_ID_PATTERN)?.[1]?.trim();
|
||||||
@@ -140,10 +147,6 @@ export async function assessPageDataHtmlBinding({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publication = await queryOnlinePublication(pool, pageId);
|
|
||||||
if (!publication) {
|
|
||||||
reasons.push('missing_online_publication');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -179,7 +182,7 @@ export async function smokeTestPageDataInsert({
|
|||||||
policy,
|
policy,
|
||||||
fetchImpl = fetch,
|
fetchImpl = fetch,
|
||||||
}) {
|
}) {
|
||||||
const base = String(apiBase ?? '').replace(/\/$/, '') || '/api';
|
const base = normalizePageDataApiBase(apiBase);
|
||||||
const row = buildSmokeInsertRow(policy, dataset);
|
const row = buildSmokeInsertRow(policy, dataset);
|
||||||
if (!pageId || !dataset || !Object.keys(row).length) {
|
if (!pageId || !dataset || !Object.keys(row).length) {
|
||||||
return { ok: false, reason: 'smoke_payload_unavailable', status: 0, body: null };
|
return { ok: false, reason: 'smoke_payload_unavailable', status: 0, body: null };
|
||||||
@@ -229,6 +232,29 @@ export async function verifyPageDataDeliveryArtifacts({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageId = injection.pageId;
|
||||||
|
const policy =
|
||||||
|
readPageAccessPolicy(publishDir, pageId) ??
|
||||||
|
(await assessPageDataHtmlBinding({
|
||||||
|
pool,
|
||||||
|
userId,
|
||||||
|
publishDir,
|
||||||
|
relativePath,
|
||||||
|
html,
|
||||||
|
findPageByRelativePath,
|
||||||
|
})).policy;
|
||||||
|
|
||||||
|
const smoke = await smokeTestPageDataInsert({
|
||||||
|
apiBase,
|
||||||
|
pageId,
|
||||||
|
dataset: insertDataset,
|
||||||
|
policy,
|
||||||
|
fetchImpl,
|
||||||
|
});
|
||||||
|
if (smoke.ok) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const assessment = await assessPageDataHtmlBinding({
|
const assessment = await assessPageDataHtmlBinding({
|
||||||
pool,
|
pool,
|
||||||
userId,
|
userId,
|
||||||
@@ -237,30 +263,13 @@ export async function verifyPageDataDeliveryArtifacts({
|
|||||||
html,
|
html,
|
||||||
findPageByRelativePath,
|
findPageByRelativePath,
|
||||||
});
|
});
|
||||||
if (!assessment.bound) {
|
failures.push({
|
||||||
failures.push({
|
relativePath,
|
||||||
relativePath,
|
stage: 'insert_smoke',
|
||||||
stage: 'binding',
|
reason: smoke.reason,
|
||||||
reason: assessment.reasons.join(','),
|
status: smoke.status,
|
||||||
});
|
bindingReasons: assessment.reasons,
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const smoke = await smokeTestPageDataInsert({
|
|
||||||
apiBase,
|
|
||||||
pageId: assessment.pageId,
|
|
||||||
dataset: insertDataset,
|
|
||||||
policy: assessment.policy,
|
|
||||||
fetchImpl,
|
|
||||||
});
|
});
|
||||||
if (!smoke.ok) {
|
|
||||||
failures.push({
|
|
||||||
relativePath,
|
|
||||||
stage: 'insert_smoke',
|
|
||||||
reason: smoke.reason,
|
|
||||||
status: smoke.status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return failures;
|
return failures;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import test from 'node:test';
|
|||||||
import {
|
import {
|
||||||
buildSmokeInsertRow,
|
buildSmokeInsertRow,
|
||||||
extractInjectedPageIdFromHtml,
|
extractInjectedPageIdFromHtml,
|
||||||
|
normalizePageDataApiBase,
|
||||||
} from './page-data-delivery-assess.mjs';
|
} from './page-data-delivery-assess.mjs';
|
||||||
|
|
||||||
test('extractInjectedPageIdFromHtml reads injected pageId', () => {
|
test('extractInjectedPageIdFromHtml reads injected pageId', () => {
|
||||||
@@ -13,6 +14,18 @@ test('extractInjectedPageIdFromHtml reads injected pageId', () => {
|
|||||||
assert.equal(extractInjectedPageIdFromHtml(html), 'page-abc');
|
assert.equal(extractInjectedPageIdFromHtml(html), 'page-abc');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('extractInjectedPageIdFromHtml reads unquoted pageId keys', () => {
|
||||||
|
const html =
|
||||||
|
'<script>window.__MINDSPACE_PAGE_DATA__={pageId:"page-unquoted",accessMode:"public"};</script>';
|
||||||
|
assert.equal(extractInjectedPageIdFromHtml(html), 'page-unquoted');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizePageDataApiBase appends /api to public base url', () => {
|
||||||
|
assert.equal(normalizePageDataApiBase('https://m.tkmind.cn'), 'https://m.tkmind.cn/api');
|
||||||
|
assert.equal(normalizePageDataApiBase('https://m.tkmind.cn/api'), 'https://m.tkmind.cn/api');
|
||||||
|
assert.equal(normalizePageDataApiBase('/api'), '/api');
|
||||||
|
});
|
||||||
|
|
||||||
test('buildSmokeInsertRow fills insert columns', () => {
|
test('buildSmokeInsertRow fills insert columns', () => {
|
||||||
const row = buildSmokeInsertRow(
|
const row = buildSmokeInsertRow(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,16 +17,29 @@ export function htmlUsesPageDataApi(html) {
|
|||||||
export function detectPageDataDatasetUsageFromHtml(html) {
|
export function detectPageDataDatasetUsageFromHtml(html) {
|
||||||
const text = String(html ?? '');
|
const text = String(html ?? '');
|
||||||
const datasets = new Map();
|
const datasets = new Map();
|
||||||
|
const constants = new Map();
|
||||||
|
|
||||||
|
for (const match of text.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*['"]([^'"]+)['"]/g)) {
|
||||||
|
constants.set(match[1], match[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remember(name, patch) {
|
||||||
|
const key = String(name ?? '').trim();
|
||||||
|
if (!key) return;
|
||||||
|
datasets.set(key, { ...(datasets.get(key) ?? {}), ...patch });
|
||||||
|
}
|
||||||
|
|
||||||
for (const match of text.matchAll(/\.insertRow\(\s*['"]([^'"]+)['"]/g)) {
|
for (const match of text.matchAll(/\.insertRow\(\s*['"]([^'"]+)['"]/g)) {
|
||||||
const name = match[1];
|
remember(match[1], { insert: true });
|
||||||
const prev = datasets.get(name) ?? {};
|
|
||||||
datasets.set(name, { ...prev, insert: true });
|
|
||||||
}
|
}
|
||||||
for (const match of text.matchAll(/\.listRows\(\s*['"]([^'"]+)['"]/g)) {
|
for (const match of text.matchAll(/\.listRows\(\s*['"]([^'"]+)['"]/g)) {
|
||||||
const name = match[1];
|
remember(match[1], { read: true });
|
||||||
const prev = datasets.get(name) ?? {};
|
}
|
||||||
datasets.set(name, { ...prev, read: true });
|
for (const match of text.matchAll(/\.insertRow\(\s*([A-Za-z_$][\w$]*)/g)) {
|
||||||
|
remember(constants.get(match[1]) ?? match[1], { insert: true });
|
||||||
|
}
|
||||||
|
for (const match of text.matchAll(/\.listRows\(\s*([A-Za-z_$][\w$]*)/g)) {
|
||||||
|
remember(constants.get(match[1]) ?? match[1], { read: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return datasets;
|
return datasets;
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ test('detectPageDataDatasetUsageFromHtml finds insert and read datasets', () =>
|
|||||||
assert.deepEqual(usage.get('tkmind_exp_survey'), { insert: true, read: true });
|
assert.deepEqual(usage.get('tkmind_exp_survey'), { insert: true, read: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('detectPageDataDatasetUsageFromHtml resolves dataset constants', () => {
|
||||||
|
const html = `
|
||||||
|
const DATASET = 'reading_survey';
|
||||||
|
await client.listRows(DATASET, { limit: 500 });
|
||||||
|
`;
|
||||||
|
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||||||
|
assert.deepEqual(usage.get('reading_survey'), { read: true });
|
||||||
|
});
|
||||||
|
|
||||||
test('assertPolicyMatchesHtmlDatasets rejects mismatched dataset names', () => {
|
test('assertPolicyMatchesHtmlDatasets rejects mismatched dataset names', () => {
|
||||||
const html = `await c.insertRow('tkmind_exp_survey', {});`;
|
const html = `await c.insertRow('tkmind_exp_survey', {});`;
|
||||||
assert.throws(
|
assert.throws(
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Patch reading-survey HTML on disk for WeChat WebView radio/submit compatibility.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/repair-reading-survey-wechat.mjs
|
||||||
|
* node scripts/repair-reading-survey-wechat.mjs --user a70ff537-8908-486e-9b6c-042e07cc25db
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { applyWechatSurveyCompat } from '../mindspace-page-data-wechat-survey-compat.mjs';
|
||||||
|
import { PUBLISH_ROOT_DIR } from '../user-publish.mjs';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const userIdx = argv.indexOf('--user');
|
||||||
|
return {
|
||||||
|
userId:
|
||||||
|
userIdx >= 0
|
||||||
|
? String(argv[userIdx + 1] ?? '').trim()
|
||||||
|
: 'a70ff537-8908-486e-9b6c-042e07cc25db',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchFile(absolutePath) {
|
||||||
|
const before = fs.readFileSync(absolutePath, 'utf8');
|
||||||
|
const { html, patched } = applyWechatSurveyCompat(before);
|
||||||
|
if (!patched) {
|
||||||
|
console.log(`SKIP ${absolutePath} (no compat patch needed)`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(absolutePath, html, 'utf8');
|
||||||
|
console.log(`OK ${absolutePath}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId } = parseArgs(process.argv.slice(2));
|
||||||
|
const base = path.join(root, PUBLISH_ROOT_DIR, userId, 'public');
|
||||||
|
const targets = ['reading-survey.html'];
|
||||||
|
|
||||||
|
let changed = 0;
|
||||||
|
for (const name of targets) {
|
||||||
|
const filePath = path.join(base, name);
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`MISS ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (patchFile(filePath)) changed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`patched ${changed} file(s)`);
|
||||||
|
process.exit(changed > 0 ? 0 : 1);
|
||||||
@@ -1074,6 +1074,7 @@ async function enforcePageDataCollectDelivery({
|
|||||||
pool: pageDataFinishGuard?.pool ?? null,
|
pool: pageDataFinishGuard?.pool ?? null,
|
||||||
userId,
|
userId,
|
||||||
findPageByRelativePath: null,
|
findPageByRelativePath: null,
|
||||||
|
apiBase: publicBaseUrl,
|
||||||
});
|
});
|
||||||
let autoBind = null;
|
let autoBind = null;
|
||||||
if (outcome.action === 'skip') return outcome;
|
if (outcome.action === 'skip') return outcome;
|
||||||
@@ -1100,6 +1101,7 @@ async function enforcePageDataCollectDelivery({
|
|||||||
pool: pageDataFinishGuard.pool,
|
pool: pageDataFinishGuard.pool,
|
||||||
userId,
|
userId,
|
||||||
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
||||||
|
apiBase: publicBaseUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user