16fd99e8ba
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>
125 lines
3.9 KiB
JavaScript
125 lines
3.9 KiB
JavaScript
/**
|
||
* 从 MindSpace 公开 HTML 中解析 Page Data API 引用的 dataset 与读写意图。
|
||
*/
|
||
|
||
const PAGE_DATA_CLIENT_SCRIPT_PATTERN = /\/assets\/page-data-client\.js/i;
|
||
|
||
export function htmlUsesPageDataApi(html) {
|
||
const content = String(html ?? '');
|
||
const usage = detectPageDataDatasetUsageFromHtml(content);
|
||
return (
|
||
usage.size > 0 ||
|
||
PAGE_DATA_CLIENT_SCRIPT_PATTERN.test(content) ||
|
||
/\bMindSpacePageData\b/.test(content)
|
||
);
|
||
}
|
||
|
||
export function detectPageDataDatasetUsageFromHtml(html) {
|
||
const text = String(html ?? '');
|
||
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)) {
|
||
remember(match[1], { insert: true });
|
||
}
|
||
for (const match of text.matchAll(/\.listRows\(\s*['"]([^'"]+)['"]/g)) {
|
||
remember(match[1], { 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;
|
||
}
|
||
|
||
export function inferPageDataBindAccessMode(relativePath, html) {
|
||
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||
const hasRead = [...usage.values()].some((item) => item.read);
|
||
const hasInsert = [...usage.values()].some((item) => item.insert);
|
||
if (/-admin\.html$/i.test(String(relativePath ?? '')) || (hasRead && !hasInsert)) {
|
||
return 'password';
|
||
}
|
||
return 'public';
|
||
}
|
||
|
||
export function assertPolicyMatchesHtmlDatasets(html, policyDatasets) {
|
||
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||
const htmlNames = [...usage.keys()].sort();
|
||
const policyNames = Object.keys(policyDatasets ?? {}).sort();
|
||
if (!htmlNames.length) return usage;
|
||
if (htmlNames.join(',') !== policyNames.join(',')) {
|
||
throw Object.assign(
|
||
new Error(
|
||
`Page Data 策略 dataset(${policyNames.join(', ') || '无'})与 HTML 中 insertRow/listRows 引用的 dataset(${htmlNames.join(', ')})不一致。请使用与页面脚本相同的 dataset 名称。`,
|
||
),
|
||
{
|
||
code: 'dataset_policy_html_mismatch',
|
||
htmlDatasets: htmlNames,
|
||
policyDatasets: policyNames,
|
||
},
|
||
);
|
||
}
|
||
return usage;
|
||
}
|
||
|
||
export function buildPageDataPolicyDatasetsFromRegistry({ html, registryDatasets, usage }) {
|
||
const detected = usage ?? detectPageDataDatasetUsageFromHtml(html);
|
||
if (!detected.size) return null;
|
||
|
||
const registryMap = new Map(registryDatasets.map((dataset) => [dataset.name, dataset]));
|
||
const datasets = {};
|
||
|
||
for (const [name, perms] of detected) {
|
||
const registered = registryMap.get(name);
|
||
if (!registered) {
|
||
throw Object.assign(
|
||
new Error(`HTML 引用的 dataset「${name}」尚未注册,请先执行 private_data_register_dataset`),
|
||
{ code: 'dataset_not_registered', datasetName: name },
|
||
);
|
||
}
|
||
|
||
const entry = {
|
||
insert: false,
|
||
read: false,
|
||
update: false,
|
||
softDelete: false,
|
||
hardDelete: false,
|
||
columns: {},
|
||
};
|
||
|
||
if (perms.insert && !perms.read) {
|
||
entry.insert = true;
|
||
entry.columns.insert = registered.columns?.insert ?? [];
|
||
} else if (perms.read && !perms.insert) {
|
||
entry.read = true;
|
||
entry.columns.read = registered.columns?.read ?? [];
|
||
} else {
|
||
if (perms.insert) {
|
||
entry.insert = true;
|
||
entry.columns.insert = registered.columns?.insert ?? [];
|
||
}
|
||
if (perms.read) {
|
||
entry.read = true;
|
||
entry.columns.read = registered.columns?.read ?? [];
|
||
}
|
||
}
|
||
|
||
datasets[name] = entry;
|
||
}
|
||
|
||
return datasets;
|
||
}
|