From c7718c683b216f08865e04edc67f4217f6abc3c1 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 27 Jul 2026 22:10:07 +0800 Subject: [PATCH] fix: inject Rybbit tracking on m.tkmind.cn publication pages Publication routes and share shells were missing the Rybbit tracker, and bootstrap now reuses the stored MindSpace analytics secret when Rybbit env secret is absent. Co-authored-by: Cursor --- docs/local-analytics.md | 8 +- mindspace-rybbit.mjs | 12 +- mindspace-rybbit.test.mjs | 11 ++ server.mjs | 16 ++- server/portal-domain-services-bootstrap.mjs | 5 + .../portal-domain-services-bootstrap.test.mjs | 5 + server/portal-publication-shell.mjs | 2 +- server/portal-published-page-delivery.mjs | 119 +++++++++++++++++- .../portal-published-page-delivery.test.mjs | 29 +++++ 9 files changed, 199 insertions(+), 8 deletions(-) diff --git a/docs/local-analytics.md b/docs/local-analytics.md index af850d2..7cf1b33 100644 --- a/docs/local-analytics.md +++ b/docs/local-analytics.md @@ -58,8 +58,14 @@ Generated Page MEMIND_RYBBIT_ID_SECRET= ``` + If `MEMIND_RYBBIT_ID_SECRET` is omitted, Portal bootstrap reuses the + MindSpace analytics secret stored in `mindspace_config` (the same value + configured below in memind_adm → Analytics 配置). + 3. Restart the local Memind server. Full generated HTML pages receive a - same-origin `/rybbit/script.js` tracker. The tracker identifies the visitor + same-origin `/rybbit/script.js` tracker. Publication routes under + `/u/:owner/pages/:slug` and `/MindSpace/:user/public/...` both receive + the tracker. The tracker identifies the visitor with a stable pseudonymous owner ID, then records engagement events such as `page_click`, scroll depth, and dwell time. Initial pageviews come from Rybbit's site setting `trackInitialPageView`. diff --git a/mindspace-rybbit.mjs b/mindspace-rybbit.mjs index 6b6f2de..4d77aa9 100644 --- a/mindspace-rybbit.mjs +++ b/mindspace-rybbit.mjs @@ -8,10 +8,18 @@ import { const RYBBIT_MARKER = 'data-memind-rybbit="1"'; -export function resolveMindSpaceRybbitConfig(env = process.env) { +export function resolveMindSpaceRybbitConfig( + env = process.env, + { idSecret = '' } = {}, +) { const enabled = String(env.MEMIND_RYBBIT_ENABLED ?? '').toLowerCase() === 'true'; const siteId = String(env.MEMIND_RYBBIT_SITE_ID ?? '').trim(); - const secret = String(env.MEMIND_RYBBIT_ID_SECRET ?? env.MEMIND_ANALYTICS_ID_SECRET ?? '').trim(); + const secret = String( + env.MEMIND_RYBBIT_ID_SECRET ?? + env.MEMIND_ANALYTICS_ID_SECRET ?? + idSecret ?? + '', + ).trim(); const rybbitUrl = String(env.MEMIND_RYBBIT_URL ?? env.RYBBIT_URL ?? 'https://rybbit.tkmind.cn').trim() || 'https://rybbit.tkmind.cn'; return { diff --git a/mindspace-rybbit.test.mjs b/mindspace-rybbit.test.mjs index fed0f98..58bc039 100644 --- a/mindspace-rybbit.test.mjs +++ b/mindspace-rybbit.test.mjs @@ -26,6 +26,17 @@ test('rybbit config is disabled unless explicitly enabled and configured', () => assert.equal(config.scriptPath, '/rybbit/script.js'); }); +test('rybbit config can reuse stored analytics secret', () => { + const config = resolveMindSpaceRybbitConfig({ + MEMIND_RYBBIT_ENABLED: 'true', + MEMIND_RYBBIT_SITE_ID: '2', + }, { + idSecret: 'stored-secret', + }); + assert.equal(config.enabled, true); + assert.equal(config.idSecret, 'stored-secret'); +}); + test('injects one local same-origin rybbit tracker with page dimensions', () => { const html = 'Demo

Demo

'; const out = injectMindSpaceRybbit(html, { diff --git a/server.mjs b/server.mjs index a59fe90..81b01f1 100644 --- a/server.mjs +++ b/server.mjs @@ -348,8 +348,15 @@ async function bootstrapUserAuth() { mindSpaceRuntimeAdapter, resolveUserIdByDirKey, } = domainServices; - mindSpaceAnalyticsConfig = - domainServices.mindSpaceAnalyticsConfig; + Object.assign( + mindSpaceAnalyticsConfig, + domainServices.mindSpaceAnalyticsConfig, + ); + Object.assign( + mindSpaceRybbitConfig, + domainServices.mindSpaceRybbitConfig ?? + mindSpaceRybbitConfig, + ); scheduleService = domainServices.scheduleService; pageDataService = domainServices.pageDataService; pageDataPublicService = @@ -1490,6 +1497,11 @@ const sendPublishedPage = createPortalPublishedPageDelivery({ registerLongImageArtifact: registerPublishedLongImageArtifactForConversation, + analyticsConfig: mindSpaceAnalyticsConfig, + rybbitConfig: mindSpaceRybbitConfig, + getAuthPool: () => authPool, + getUserAuth: () => userAuth, + getMindSpacePages: () => mindSpacePages, }); attachPortalPublicationRoutes({ diff --git a/server/portal-domain-services-bootstrap.mjs b/server/portal-domain-services-bootstrap.mjs index de97459..84367e2 100644 --- a/server/portal-domain-services-bootstrap.mjs +++ b/server/portal-domain-services-bootstrap.mjs @@ -1,4 +1,5 @@ import { ensureMindSpaceConfig, loadMindSpaceConfig } from '../mindspace-config.mjs'; +import { resolveMindSpaceRybbitConfig } from '../mindspace-rybbit.mjs'; import { createMindSearchConfigService } from '../mindsearch-config.mjs'; import { createMindSpaceService } from '../mindspace.mjs'; import { @@ -105,6 +106,9 @@ export async function bootstrapPortalDomainServices({ scriptPath: '/analytics/script.js', }; } + const resolvedRybbitConfig = resolveMindSpaceRybbitConfig(env, { + idSecret: storedMindSpaceConfig?.analytics?.idSecret ?? '', + }); const scheduleService = createScheduleServiceFn(pool, { defaultTimezone: @@ -272,6 +276,7 @@ export async function bootstrapPortalDomainServices({ return { mindSearchConfigService, mindSpaceAnalyticsConfig: resolvedAnalyticsConfig, + mindSpaceRybbitConfig: resolvedRybbitConfig, scheduleService, pageDataService, pageDataPublicService, diff --git a/server/portal-domain-services-bootstrap.test.mjs b/server/portal-domain-services-bootstrap.test.mjs index 8e39196..b745a81 100644 --- a/server/portal-domain-services-bootstrap.test.mjs +++ b/server/portal-domain-services-bootstrap.test.mjs @@ -61,6 +61,8 @@ function createBootstrapSetup(overrides = {}) { env: { H5_DEFAULT_TIMEZONE: 'Asia/Singapore', PLAZA_REDIS_URL: 'redis://plaza', + MEMIND_RYBBIT_ENABLED: 'true', + MEMIND_RYBBIT_SITE_ID: '2', }, runtime: { maxFileBytes: 100, @@ -309,6 +311,9 @@ test('domain bootstrap preserves schema and service assembly order', async () => result.mindSpaceAnalyticsConfig.scriptPath, '/analytics/script.js', ); + assert.equal(result.mindSpaceRybbitConfig.enabled, true); + assert.equal(result.mindSpaceRybbitConfig.siteId, '2'); + assert.equal(result.mindSpaceRybbitConfig.idSecret, 'secret-1'); }); test('domain bootstrap preserves delayed user, snapshot, and Page Data dependencies', async () => { diff --git a/server/portal-publication-shell.mjs b/server/portal-publication-shell.mjs index b9be057..29e5344 100644 --- a/server/portal-publication-shell.mjs +++ b/server/portal-publication-shell.mjs @@ -64,7 +64,7 @@ export function publishedPageShellHtml({ iframeUrl, shareUrl, title, longImageUr - + ${safeTitle}