From e2014e05e6f0f83ec3e8be7918cf095bbfbc83ab Mon Sep 17 00:00:00 2001 From: john Date: Wed, 5 Aug 2026 13:05:18 +0800 Subject: [PATCH 1/2] fix(analytics): stop identifying Public visitors as page creators Public generated pages were calling umami.identify(owner_id), collapsing every visitor into the creator session. Anonymous visits now skip identify; logged-in viewers identify with their own distinct id. Co-authored-by: Cursor --- mindspace-analytics.mjs | 18 ++++- mindspace-analytics.test.mjs | 65 ++++++++++++++++--- server/portal-published-page-delivery.mjs | 5 ++ .../portal-workspace-publication-delivery.mjs | 5 ++ 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/mindspace-analytics.mjs b/mindspace-analytics.mjs index 9b85f8f..969949d 100644 --- a/mindspace-analytics.mjs +++ b/mindspace-analytics.mjs @@ -68,6 +68,20 @@ export function resolveAnalyticsOwnerLabel(user = {}) { return label.replace(/[\r\n\t]+/g, ' ').slice(0, 80) || '未命名用户'; } +export function buildViewerAnalyticsIdentity(viewer, config = {}) { + if (!viewer?.id) return null; + const distinctId = resolveAnalyticsIdentity(viewer.id, config); + if (!distinctId) return null; + return { + distinctId, + username: resolveAnalyticsOwnerLabel(viewer), + ownerSegment: resolveAnalyticsOwnerSegment(viewer), + planType: resolveAnalyticsPlan(viewer), + channel: 'public', + identityMode: config.identityMode === 'raw' ? 'raw' : 'pseudonymous', + }; +} + export function buildProductAnalyticsContext({ config, user = null } = {}) { if (!config?.enabled || !config.websiteId) return { enabled: false }; const distinctId = user?.id ? resolveAnalyticsIdentity(user.id, config) : ''; @@ -152,6 +166,7 @@ export function injectMindSpaceAnalytics(html, { planType = 'unknown', generatedAt = '', channel = 'h5', + viewerIdentity = null, config = resolveMindSpaceAnalyticsConfig(), } = {}) { const source = String(html ?? ''); @@ -167,7 +182,8 @@ export function injectMindSpaceAnalytics(html, { `data-host-url="${config.hostPath}"`, ]; if (config.domains) attrs.push(`data-domains="${config.domains.replaceAll('"', '"')}"`); - const block = ``; + const viewerJson = viewerIdentity ? jsonForInlineScript(viewerIdentity) : 'null'; + const block = ``; if (/<\/head>/i.test(source)) return source.replace(/<\/head>/i, `${block}`); return source.replace(/ { assert.match(out, /src="\/analytics\/script\.js"/); assert.match(out, /data-host-url="\/analytics"/); assert.match(out, /data-auto-track="false"/); - assert.match(out, /window\.umami\.identify\(d\.owner_id,\{username:d\.username,memind_page_url:location\.href,owner_segment:d\.owner_segment,plan_type:d\.plan_type,channel:d\.channel,surface:d\.surface,identity_mode:d\.identity_mode\}\)/); + assert.doesNotMatch(out, /umami\.identify\(d\.owner_id/); + assert.match(out, /function identifyViewer\(\)/); + assert.match(out, /identifyViewer\(\);pageview\(\)/); assert.match(out, /function pageview\(\).*window\.umami\.track\(\)/); - assert.ok(out.indexOf('identify();pageview();') > 0); assert.doesNotMatch(out, /t\('page_view'\)/); assert.match(out, /page_id/); assert.match(out, /owner_segment/); @@ -143,7 +145,7 @@ test('injects one local same-origin tracker with page dimensions', () => { assert.equal(injectMindSpaceAnalytics(out, { ownerId: 'user-123', config: { enabled: true, websiteId: 'local-website', idSecret: 'secret' } }), out); }); -test('identifies the pseudonymous owner before sending a standard page view', () => { +test('public visitors skip creator identify and only send a standard page view', () => { const out = injectMindSpaceAnalytics('', { ownerId: 'user-123', ownerSegment: 'plan:pro', @@ -177,22 +179,67 @@ test('identifies the pseudonymous owner before sending a standard page view', () documentElement: { scrollHeight: 1600 }, addEventListener: () => {}, }, - location: { href: 'https://m.tkmind.cn/MindSpace/demo/public/page.html' }, + location: { href: 'https://m.tkmind.cn/MindSpace/demo/public/page.html', pathname: '/MindSpace/demo/public/page.html' }, + setTimeout: () => {}, + }); + + assert.deepEqual(JSON.parse(JSON.stringify(calls)), [['track']]); +}); + +test('logged-in public visitors identify themselves without using the page creator id', () => { + const viewerId = pseudonymizeAnalyticsId('viewer-456', 'secret'); + const out = injectMindSpaceAnalytics('', { + ownerId: 'user-123', + ownerLabel: '张三', + viewerIdentity: buildViewerAnalyticsIdentity( + { id: 'viewer-456', displayName: '李四', role: 'user', planType: 'free' }, + { idSecret: 'secret', identityMode: 'pseudonymous' }, + ), + config: { + enabled: true, + websiteId: 'local-website', + idSecret: 'secret', + scriptPath: '/analytics/script.js', + hostPath: '/analytics', + }, + }); + const inlineScript = out.match(/