import crypto from 'node:crypto'; import { pseudonymizeAnalyticsId, resolveAnalyticsOwnerLabel, resolveAnalyticsOwnerSegment, } from './mindspace-analytics.mjs'; const RYBBIT_MARKER = 'data-memind-rybbit="1"'; 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 = [ env.MEMIND_RYBBIT_ID_SECRET, env.MEMIND_ANALYTICS_ID_SECRET, idSecret, ] .map((value) => String(value ?? '').trim()) .find(Boolean) ?? ''; const rybbitUrl = String(env.MEMIND_RYBBIT_URL ?? env.RYBBIT_URL ?? 'https://rybbit.tkmind.cn').trim() || 'https://rybbit.tkmind.cn'; return { enabled: enabled && Boolean(siteId) && Boolean(secret), siteId, idSecret: secret, rybbitUrl: rybbitUrl.replace(/\/$/, ''), // Same-origin proxy path. Script auto-discovers host by splitting on /script.js, // so /rybbit/script.js makes events go to /rybbit/track → proxied to /api/track. scriptPath: String(env.MEMIND_RYBBIT_SCRIPT_PATH ?? '/rybbit/script.js').trim() || '/rybbit/script.js', hostPath: String(env.MEMIND_RYBBIT_HOST_PATH ?? '/rybbit').trim() || '/rybbit', }; } export { resolveAnalyticsOwnerLabel, resolveAnalyticsOwnerSegment, pseudonymizeAnalyticsId }; function jsonForInlineScript(value) { return JSON.stringify(value) .replaceAll('<', '\\u003c') .replaceAll('>', '\\u003e') .replaceAll('&', '\\u0026'); } export function sendMindSpaceRybbitEvent({ config, eventName, ownerId, pageId = '', publicationId = '', agentRunId = '', channel = 'h5', ownerSegment = 'unknown', ownerLabel = '未命名用户', url = '', } = {}) { if (!config?.enabled || !config.siteId || !config.idSecret || !eventName) return Promise.resolve(false); const owner = pseudonymizeAnalyticsId(ownerId, config.idSecret); if (!owner) return Promise.resolve(false); const endpoint = `${String(config.rybbitUrl || 'https://rybbit.tkmind.cn').replace(/\/$/, '')}/api/track`; const payload = { site_id: String(config.siteId), type: 'custom_event', event_name: String(eventName).slice(0, 256), pathname: url || '/', page_title: '', referrer: '', hostname: '127.0.0.1', user_id: owner, properties: JSON.stringify({ owner_id: owner, page_id: String(pageId || ''), publication_id: String(publicationId || ''), agent_run_id: String(agentRunId || ''), channel, owner_segment: String(ownerSegment || 'unknown'), owner_label: resolveAnalyticsOwnerLabel({ displayName: ownerLabel }), }), }; return fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', 'user-agent': 'Memind/local-rybbit' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(1500), }).then((response) => response.ok).catch(() => false); } export function injectMindSpaceRybbit(html, { ownerId, pageId = '', publicationId = '', ownerSegment = 'unknown', ownerLabel = '未命名用户', channel = 'h5', config = resolveMindSpaceRybbitConfig(), } = {}) { const source = String(html ?? ''); if (!config?.enabled || !config.siteId || !/^\s*(`; if (/<\/head>/i.test(source)) return source.replace(/<\/head>/i, `${block}`); return source.replace(/