feat(analytics): retire Rybbit and add SEO/GEO discovery tracking

Document Umami as the sole analytics platform, remove Rybbit proxy and
tracker code, and classify public page traffic with discovery_channel for
adm SEO/GEO dashboards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-12 01:34:05 +08:00
parent c58a23d155
commit c122bd5664
26 changed files with 466 additions and 449 deletions
@@ -1,5 +1,4 @@
import { ensureMindSpaceConfig, loadMindSpaceConfig, loadMindSpaceConfigCached } from '../mindspace-config.mjs';
import { resolveMindSpaceRybbitConfig } from '../mindspace-rybbit.mjs';
import { createMindSearchConfigService } from '../mindsearch-config.mjs';
import { createMindSpaceService } from '../mindspace.mjs';
import {
@@ -109,10 +108,6 @@ export async function bootstrapPortalDomainServices({
scriptPath: '/analytics/script.js',
};
}
const resolvedRybbitConfig = resolveMindSpaceRybbitConfig(env, {
idSecret: storedMindSpaceConfig?.analytics?.idSecret ?? '',
});
const scheduleService = createScheduleServiceFn(pool, {
defaultTimezone:
env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
@@ -289,7 +284,6 @@ export async function bootstrapPortalDomainServices({
return {
mindSearchConfigService,
mindSpaceAnalyticsConfig: resolvedAnalyticsConfig,
mindSpaceRybbitConfig: resolvedRybbitConfig,
scheduleService,
scheduledTaskService,
pageDataService,
@@ -61,8 +61,6 @@ 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,
@@ -325,9 +323,6 @@ 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 () => {
@@ -4,9 +4,6 @@ import {
resolveAnalyticsPlan,
sendMindSpaceAnalyticsEvent,
} from '../mindspace-analytics.mjs';
import {
sendMindSpaceRybbitEvent,
} from '../mindspace-rybbit.mjs';
import { createPageEditSessionService } from '../mindspace-page-edit-session.mjs';
import { createNotificationDispatcher } from '../notification-dispatcher.mjs';
import { startScheduleReminderWorker } from '../schedule-reminder-worker.mjs';
@@ -39,7 +36,6 @@ export async function bootstrapPortalIntegrationServices({
chatIntentRouter,
systemDisclosurePolicyService,
mindSpaceAnalyticsConfig,
mindSpaceRybbitConfig,
subscriptionService,
apiTarget,
apiSecret,
@@ -54,8 +50,6 @@ export async function bootstrapPortalIntegrationServices({
resolveAnalyticsPlanFn = resolveAnalyticsPlan,
sendMindSpaceAnalyticsEventFn =
sendMindSpaceAnalyticsEvent,
sendMindSpaceRybbitEventFn =
sendMindSpaceRybbitEvent,
createNotificationDispatcherFn =
createNotificationDispatcher,
startScheduleReminderWorkerFn =
@@ -214,10 +208,6 @@ export async function bootstrapPortalIntegrationServices({
config: mindSpaceAnalyticsConfig,
...analyticsPayload,
});
void sendMindSpaceRybbitEventFn({
config: mindSpaceRybbitConfig,
...analyticsPayload,
});
}
},
applySessionLlmProvider: (sessionId) =>
@@ -124,7 +124,6 @@ function createSetup(overrides = {}) {
},
sessionSnapshotService,
mindSpaceAnalyticsConfig: { enabled: true },
mindSpaceRybbitConfig: { enabled: true },
subscriptionService,
apiTarget: 'http://api',
apiSecret: 'secret',
@@ -164,10 +163,6 @@ function createSetup(overrides = {}) {
calls.push(['analytics', event]);
return Promise.resolve();
},
sendMindSpaceRybbitEventFn(event) {
calls.push(['rybbit', event]);
return Promise.resolve();
},
createNotificationDispatcherFn(receivedOptions) {
calls.push(['notification-dispatcher']);
notificationOptions = receivedOptions;
@@ -425,13 +420,6 @@ test('preserves generated-page analytics projection', async () => {
analyticsCall[1].generatedAt,
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
);
const rybbitCall = setup.calls.find(
([name]) => name === 'rybbit',
);
assert.deepEqual(rybbitCall[1], {
...analyticsCall[1],
config: { enabled: true },
});
});
test('preserves notification, recharge, reminder, and page-edit wiring', async () => {
+20 -19
View File
@@ -13,8 +13,9 @@ import {
resolveAnalyticsPlan,
} from '../mindspace-analytics.mjs';
import {
injectMindSpaceRybbit,
} from '../mindspace-rybbit.mjs';
appendDiscoveryPropagationParams,
classifyDiscoveryTrafficSource,
} from '../mindspace-analytics-discovery.mjs';
import {
rewriteBrokenMindSpacePublicImageUrls,
rewritePublicationCanonicalAssetUrls,
@@ -58,8 +59,8 @@ async function decoratePublicationHtmlAnalytics(
{
result,
analyticsConfig,
rybbitConfig,
viewer = null,
referrerHint = '',
getAuthPool = () => null,
getUserAuth = () => null,
getMindSpacePages = () => null,
@@ -122,17 +123,9 @@ async function decoratePublicationHtmlAnalytics(
pageId,
publicationId,
viewerIdentity: buildViewerAnalyticsIdentity(viewer, analyticsConfig),
referrerHint,
config: analyticsConfig,
});
decorated = injectMindSpaceRybbit(decorated, {
ownerId: result?.ownerId ?? '',
ownerSegment,
ownerLabel,
pageId,
publicationId,
channel: 'publication',
config: rybbitConfig,
});
return decorated;
}
@@ -141,7 +134,6 @@ export function createPortalPublishedPageDelivery({
renderLongImage =
renderLongImageBuffer,
analyticsConfig = { enabled: false },
rybbitConfig = { enabled: false },
getAuthPool = () => null,
getUserAuth = () => null,
getMindSpacePages = () => null,
@@ -179,6 +171,9 @@ export function createPortalPublishedPageDelivery({
);
}
const origin = resolveRequestOrigin(req);
const referrerHint = String(
req.headers.referer ?? req.headers.referrer ?? req.get?.('referer') ?? '',
).trim();
const originalPath =
req.originalUrl || req.url || '';
const sharePath = removeQueryParam(
@@ -243,8 +238,8 @@ export function createPortalPublishedPageDelivery({
html = await decoratePublicationHtmlAnalytics(html, {
result,
analyticsConfig,
rybbitConfig,
viewer: req.currentUser ?? null,
referrerHint,
getAuthPool,
getUserAuth,
getMindSpacePages,
@@ -345,10 +340,16 @@ export function createPortalPublishedPageDelivery({
if (canWrapWithShell) {
const title =
detectPublishedPageTitle(html);
const rawUrl = appendQueryParam(
sharePath || originalPath,
'view',
'raw',
const rawUrl = appendDiscoveryPropagationParams(
appendQueryParam(
sharePath || originalPath,
'view',
'raw',
),
classifyDiscoveryTrafficSource({
referrer: referrerHint,
pageHost: origin ? new URL(origin).hostname : '',
}),
);
const longImageUrl = appendQueryParam(
sharePath || originalPath,
@@ -386,8 +387,8 @@ export function createPortalPublishedPageDelivery({
shellHtml = await decoratePublicationHtmlAnalytics(shellHtml, {
result,
analyticsConfig,
rybbitConfig,
viewer: req.currentUser ?? null,
referrerHint,
getAuthPool,
getUserAuth,
getMindSpacePages,
+21 -8
View File
@@ -61,16 +61,16 @@ function createResult(overrides = {}) {
};
}
test('injects rybbit tracker for publication HTML delivery', async () => {
test('injects umami tracker for publication HTML delivery', async () => {
const sendPublishedPage =
createPortalPublishedPageDelivery({
async registerLongImageArtifact() {},
rybbitConfig: {
analyticsConfig: {
enabled: true,
siteId: '2',
websiteId: 'local-website',
idSecret: 'secret',
scriptPath: '/rybbit/script.js',
hostPath: '/rybbit',
scriptPath: '/analytics/script.js',
hostPath: '/analytics',
},
});
const response = createResponse();
@@ -85,8 +85,8 @@ test('injects rybbit tracker for publication HTML delivery', async () => {
},
{ raw: true },
);
assert.match(response.body, /src="\/rybbit\/script\.js"/);
assert.match(response.body, /data-site-id="2"/);
assert.match(response.body, /src="\/analytics\/script\.js"/);
assert.match(response.body, /data-website-id="local-website"/);
assert.doesNotMatch(response.body, /user-123/);
});
@@ -97,7 +97,12 @@ test('wraps a normal full HTML publication in the share shell', async () => {
});
const response = createResponse();
await sendPublishedPage(
createRequest(),
createRequest({
headers: {
host: 'portal.example',
referer: 'https://www.google.com/search?q=demo',
},
}),
response,
createResult(),
);
@@ -110,6 +115,14 @@ test('wraps a normal full HTML publication in the share shell', async () => {
response.body,
/view=raw/,
);
assert.match(
response.body,
/memind_discovery=seo/,
);
assert.match(
response.body,
/memind_discovery_source=google/,
);
assert.equal(
response.headers['Cache-Control'],
'public, max-age=60',
-11
View File
@@ -10,9 +10,6 @@ import {
resolveAnalyticsPlan,
sendMindSpaceAnalyticsEvent,
} from '../mindspace-analytics.mjs';
import {
sendMindSpaceRybbitEvent,
} from '../mindspace-rybbit.mjs';
import {
markPageDeliveryContractReady,
preparePageDeliveryContract,
@@ -49,7 +46,6 @@ export function attachPortalSessionRoutes(
getMindSpacePublicFinish = () => null,
getMemoryV2 = () => null,
getMindSpaceAnalyticsConfig = () => null,
getMindSpaceRybbitConfig = () => null,
ownsAgentSession,
unregisterAgentSessionForUser,
beginSessionPageDelivery,
@@ -72,8 +68,6 @@ export function attachPortalSessionRoutes(
hasPublicHtmlWriteRequestInSessionEvent,
sendMindSpaceAnalyticsEventFn =
sendMindSpaceAnalyticsEvent,
sendMindSpaceRybbitEventFn =
sendMindSpaceRybbitEvent,
resolveAnalyticsOwnerSegmentFn =
resolveAnalyticsOwnerSegment,
resolveAnalyticsOwnerLabelFn =
@@ -466,11 +460,6 @@ export function attachPortalSessionRoutes(
getMindSpaceAnalyticsConfig(),
...analyticsPayload,
});
void sendMindSpaceRybbitEventFn({
config:
getMindSpaceRybbitConfig(),
...analyticsPayload,
});
}
})
.catch((error) => {
@@ -5,9 +5,6 @@ import {
resolveAnalyticsOwnerSegment,
resolveAnalyticsPlan,
} from '../mindspace-analytics.mjs';
import {
injectMindSpaceRybbit,
} from '../mindspace-rybbit.mjs';
import {
buildPublishedHtmlViewContext,
resolveMindSpacePageDataContext,
@@ -63,7 +60,6 @@ function deliveryNotFoundMessage(reason) {
export function createPortalWorkspacePublicationDelivery({
internalAgentSecret,
analyticsConfig,
rybbitConfig,
getAuthPool = () => null,
getUserAuth = () => null,
getMindSpacePages = () => null,
@@ -266,24 +262,11 @@ export function createPortalWorkspacePublicationDelivery({
req.currentUser ?? null,
analyticsConfig,
),
referrerHint: String(
req.headers.referer ?? req.headers.referrer ?? req.get?.('referer') ?? '',
).trim(),
config: analyticsConfig,
});
html = injectMindSpaceRybbit(html, {
ownerId: delivery.ownerId ?? '',
ownerSegment:
resolveAnalyticsOwnerSegment(
pageOwner ?? {},
),
ownerLabel: resolveAnalyticsOwnerLabel(
pageOwner ?? {},
),
pageId: pageDataContext?.pageId ?? '',
publicationId:
pageDataContext?.publicationId ??
pageDataContext?.publication_id ??
'',
config: rybbitConfig,
});
const mindSpaceConfig = getAuthPool()
? await getMindSpaceConfig(getAuthPool()).catch(() => ({ seoGeo: { enabled: false } }))
: { seoGeo: { enabled: false } };
@@ -99,7 +99,6 @@ function createSetup(overrides = {}) {
createPortalWorkspacePublicationDelivery({
internalAgentSecret: 'test-secret',
analyticsConfig: { enabled: false },
rybbitConfig: { enabled: false },
getAuthPool: () => null,
getUserAuth: () => userAuth,
getMindSpacePages: () => null,