feat(analytics): Umami website-analytics suite — world map, realtime, sessions, heatmap, pixel tracking, tracked links
Adds the read-side Umami integration queued in last week's website-analytics plan (Phases 1–6 of `docs/website-analytics-flesh-out-plan.md`): - Realtime panel polls Umami at 5s intervals; world map renders visitor origins via echarts + `public/world-map/echarts-world.json` topo. - Sessions list + session-detail-sheet drill-down (per-session event timeline pulled from `/api/v1/website-analytics`). - Weekly heatmap (day-of-week × hour-of-day) for engagement timing. - Metric-detail pages under `/[portSlug]/website-analytics/[metric]` for pageviews / referrers / events deep-dives. - Email-pixel write path: `/api/public/email-pixel/[sendId]` 1×1 GIF beacon backed by `email_open_tracking` (migration 0076); resolves inline on render in inbox. - Tracked-link redirect: `/q/[slug]` routes through `tracked_links` (migration 0077) and forwards to the canonical destination after logging the click. - Dashboard `website-glance-tile` now reads from the live Umami service instead of placeholder data. Deps: `@umami/node`, `echarts`, `echarts-for-react`, `@types/geojson`, `@types/topojson-client`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
src/lib/email/tracking-pixel.ts
Normal file
46
src/lib/email/tracking-pixel.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Open-tracking pixel injector (Phase 4b). Appends a 1×1 transparent
|
||||
* image pointing at /api/public/email-pixel/[sendId] to outbound HTML
|
||||
* emails. The pixel endpoint records the open + cross-posts the event
|
||||
* to Umami.
|
||||
*
|
||||
* Sites that want to opt out of tracking simply don't call this helper.
|
||||
* The pixel URL is unguessable per-send (UUID), but a `track_opens=false`
|
||||
* row in `document_sends` makes the endpoint a no-op even if someone
|
||||
* does guess one.
|
||||
*
|
||||
* Privacy: respects EMAIL_REDIRECT_TO (no pixel injected when dev
|
||||
* redirect is active) so a re-routed message doesn't fire a fake open.
|
||||
*/
|
||||
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
interface InjectOptions {
|
||||
/** Public base URL of the CRM (e.g. https://crm.portnimara.com).
|
||||
* Required so the pixel link is absolute — relative URLs break in
|
||||
* email clients. */
|
||||
appBaseUrl: string;
|
||||
/** UUID of the row in `document_sends`. */
|
||||
sendId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a 1×1 tracking pixel just before `</body>` (or at the end of the
|
||||
* document if no `</body>` is present). Returns the HTML unchanged when
|
||||
* EMAIL_REDIRECT_TO is set so dev-mode re-routing doesn't generate
|
||||
* misleading open events.
|
||||
*/
|
||||
export function injectTrackingPixel(html: string, opts: InjectOptions): string {
|
||||
if (env.EMAIL_REDIRECT_TO) return html;
|
||||
|
||||
const base = opts.appBaseUrl.replace(/\/$/, '');
|
||||
const pixelUrl = `${base}/api/public/email-pixel/${opts.sendId}`;
|
||||
const pixelTag =
|
||||
`<img src="${pixelUrl}" width="1" height="1" alt="" ` +
|
||||
`style="display:block;border:0;margin:0;padding:0" />`;
|
||||
|
||||
if (html.includes('</body>')) {
|
||||
return html.replace('</body>', `${pixelTag}</body>`);
|
||||
}
|
||||
return html + pixelTag;
|
||||
}
|
||||
Reference in New Issue
Block a user