analytics and embed meta update!!!

This commit is contained in:
legop3
2026-07-31 22:12:04 -04:00
parent 1aecab66e7
commit 8c5d98bed6
45 changed files with 395 additions and 699 deletions
+7 -32
View File
@@ -1,12 +1,11 @@
// Analytics Reporter
// Purpose: Publishes page/session context to the optional build-time analytics
// Purpose: Publishes page/session context to the optional runtime analytics
// adapter. Scope: observes route, layout, nickname, role, verification, and
// rover assignment without owning any analytics vendor implementation.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useSessionSelector } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
import { identifyAnalyticsSession, trackAnalyticsEvent } from './index.js';
import { identifyAnalyticsSession } from './index.js';
function detectLayout() {
if (typeof window === 'undefined') return 'desktop';
@@ -31,15 +30,10 @@ function useAnalyticsLayout() {
}
export default function AnalyticsReporter() {
const location = useLocation();
const layout = useAnalyticsLayout();
const { value: profile } = useSettingsNamespace('profile', { nickname: '' });
const session = useSessionSelector((state) => state.session);
const previousRouteRef = useRef(null);
const previousLayoutRef = useRef(null);
const previousRoverRef = useRef(null);
const previousIdentityRef = useRef('');
const route = location.pathname || '/';
const nickname = String(profile?.nickname || session?.nickname || '').trim();
const roverId = String(session?.assignment?.roverId || '').trim();
const role = String(session?.role || '').trim();
@@ -47,16 +41,14 @@ export default function AnalyticsReporter() {
const identity = useMemo(
() => ({
route,
layout,
nickname,
hasNickname: Boolean(nickname),
roverId,
assignedRover: Boolean(roverId),
role,
verified,
}),
[layout, nickname, role, route, roverId, verified],
[layout, nickname, role, roverId, verified],
);
useEffect(() => {
@@ -65,30 +57,13 @@ export default function AnalyticsReporter() {
previousIdentityRef.current = serialized;
/*
This pushes the current browser/session context into the injected adapter.
The adapter is responsible for applying build-time privacy/config choices,
such as whether nickname and rover id should be sent to Umami.
Session properties describe durable segmentation dimensions. Route is
intentionally absent because Umami attaches the current URL to pageviews
and events automatically, and nickname is reduced to a non-identifying
boolean so aggregate analytics does not store user-entered names.
*/
identifyAnalyticsSession(identity);
}, [identity]);
useEffect(() => {
if (previousRouteRef.current === route) return;
previousRouteRef.current = route;
trackAnalyticsEvent('route_enter', { route, layout });
}, [layout, route]);
useEffect(() => {
if (previousLayoutRef.current === layout) return;
previousLayoutRef.current = layout;
trackAnalyticsEvent('layout_change', { route, layout });
}, [layout, route]);
useEffect(() => {
if (!roverId || previousRoverRef.current === roverId) return;
previousRoverRef.current = roverId;
trackAnalyticsEvent('rover_assigned', { roverId, route, layout });
}, [layout, route, roverId]);
return null;
}
+1 -20
View File
@@ -1,13 +1,12 @@
// Analytics Bridge
// Purpose: Gives React a tiny, provider-neutral analytics surface. Scope:
// forwards optional app events to a build-time injected browser adapter without
// forwards optional app events to a runtime-injected browser adapter without
// importing Umami, embedding website ids, or making rover controls depend on
// analytics availability.
const MAX_EVENT_NAME_LENGTH = 80;
const MAX_PROPERTY_KEY_LENGTH = 80;
const MAX_STRING_VALUE_LENGTH = 240;
const throttledEvents = new Map();
function getAdapter() {
if (typeof window === 'undefined') return null;
@@ -101,21 +100,3 @@ export function identifyAnalyticsSession(payload = {}) {
console.warn('Analytics identify failed', error);
}
}
export function trackAnalyticsEventThrottled(name, payload = {}, options = {}) {
const eventName = normalizeEventName(name);
if (!eventName) return;
const throttleMs = Number.isFinite(options?.throttleMs) ? Math.max(0, options.throttleMs) : 30 * 1000;
const key = `${eventName}:${typeof options?.key === 'string' ? options.key : JSON.stringify(normalizePayload(payload))}`;
const now = Date.now();
const lastTrackedAt = throttledEvents.get(key) || 0;
/*
Reliability signals can repeat every reconnect loop or failed media retry.
Throttling by a caller-owned key keeps Umami useful as an incident signal
without turning transient outages into hundreds of duplicate custom events.
*/
if (now - lastTrackedAt < throttleMs) return;
throttledEvents.set(key, now);
trackAnalyticsEvent(eventName, payload);
}