mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-28 05:57:43 +02:00
chore(analytics): Umami-Kern entfernen — Injection, Client-Lib, Auth-Hook, Container, DB
Erster Schritt der Umami-Komplett-Entfernung (Entscheidung: kein Web-Analytics): - hooks.server.ts: injectUmamiAnalytics-Injection raus (stoppt Script-Load in der Unified-App) - packages/shared-utils/analytics-server.ts: GELÖSCHT (Script-Injection-Util) - packages/shared-utils/analytics.ts: zu No-op entkernt — window.umami/isUmamiAvailable raus, trackEvent no-op; alle 28 *Events-Aufrufer kompilieren weiter (senden nichts) - packages/shared-auth/authService.ts: inline-Umami-trackAuth-Hook + Aufrufe raus - infrastructure/docker-compose.gpu-box.yml: umami-Service (mana-mon-umami) raus - docker/init-db: CREATE DATABASE umami + GRANT raus - gelöscht: docs/ANALYTICS.md, scripts/mac-mini/setup-umami-db.sh, picture-landing .env.example VERBLEIBEND (separat, größer): ~60 weitere Dateien — 7 Landing-Layout.astro (eigene Script-Injection), website-blocks Analytics-Feature, Legal/Datenschutz, i18n×5, Admin-UI, ~20 Docs. Teils produkt-/rechts-sensibel → in Wellen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d9a19d40f
commit
9720cd9516
9 changed files with 10 additions and 585 deletions
|
|
@ -23,20 +23,6 @@ import {
|
|||
getAppSettings as getAppSettingsFromToken,
|
||||
} from './jwtUtils';
|
||||
|
||||
/**
|
||||
* Inline analytics helper - tracks auth events via Umami if available.
|
||||
* No-ops silently in environments without Umami (mobile, SSR, dev).
|
||||
*/
|
||||
function trackAuth(event: string, data?: Record<string, string | number | boolean>): void {
|
||||
if (typeof window !== 'undefined' && (window as any).umami?.track) {
|
||||
try {
|
||||
(window as any).umami.track(event, { ...data, module: 'auth' });
|
||||
} catch {
|
||||
// Silently ignore tracking errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default storage keys
|
||||
*/
|
||||
|
|
@ -135,11 +121,9 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
// SSO cookie is nice-to-have, don't fail login if this fails
|
||||
}
|
||||
|
||||
trackAuth('login', { method: 'email' });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error signing in:', error);
|
||||
trackAuth('login_failed', { method: 'email' });
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error during sign in',
|
||||
|
|
@ -175,11 +159,9 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
|
||||
// If emailVerified is false, the user needs to verify their email before login
|
||||
const needsVerification = data?.user?.emailVerified === false;
|
||||
trackAuth('signup', { method: 'email' });
|
||||
return { success: true, needsVerification };
|
||||
} catch (error) {
|
||||
console.error('Error signing up:', error);
|
||||
trackAuth('signup_failed', { method: 'email' });
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error during sign up',
|
||||
|
|
@ -203,7 +185,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
}).catch((err) => console.error('Error logging out on server:', err));
|
||||
}
|
||||
|
||||
trackAuth('logout');
|
||||
await service.clearAuthStorage();
|
||||
} catch (error) {
|
||||
console.error('Error signing out:', error);
|
||||
|
|
@ -228,7 +209,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
return service.handleAuthError(response.status, errorData);
|
||||
}
|
||||
|
||||
trackAuth('password_reset_requested');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error sending password reset email:', error);
|
||||
|
|
@ -484,7 +464,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
return { success: false, error: err.message || 'Passkey registration failed' };
|
||||
}
|
||||
|
||||
trackAuth('passkey_registered');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
// User cancelled or WebAuthn error
|
||||
|
|
@ -572,14 +551,12 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
storage.setItem(storageKeys.USER_EMAIL, data.user?.email || ''),
|
||||
]);
|
||||
|
||||
trackAuth('login', { method: 'passkey' });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'NotAllowedError') {
|
||||
return { success: false, error: 'Passkey authentication was cancelled' };
|
||||
}
|
||||
console.error('Passkey authentication error:', error);
|
||||
trackAuth('login_failed', { method: 'passkey' });
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Passkey authentication failed',
|
||||
|
|
@ -758,7 +735,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
}
|
||||
}
|
||||
|
||||
trackAuth('login', { method: '2fa' });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
|
|
@ -805,7 +781,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
}
|
||||
}
|
||||
|
||||
trackAuth('login', { method: 'backup_code' });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
|
|
@ -892,7 +867,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
return { success: false, error: err.message || 'Failed to send magic link' };
|
||||
}
|
||||
|
||||
trackAuth('magic_link_sent');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
|
|
@ -1297,7 +1271,6 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
]);
|
||||
|
||||
console.log('SSO: Successfully authenticated via session cookie');
|
||||
trackAuth('login', { method: 'sso' });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
// SSO failed - this is expected if user hasn't logged in anywhere
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Server-side Umami Analytics Utilities
|
||||
*
|
||||
* Used in SvelteKit hooks.server.ts to inject the Umami analytics script.
|
||||
* Reads the website ID from the PUBLIC_UMAMI_WEBSITE_ID environment variable.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { injectUmamiAnalytics } from '@mana/shared-utils/analytics-server';
|
||||
*
|
||||
* export const handle: Handle = async ({ event, resolve }) => {
|
||||
* return resolve(event, {
|
||||
* transformPageChunk: ({ html }) => injectUmamiAnalytics(html),
|
||||
* });
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
|
||||
const UMAMI_SCRIPT_URL = 'https://stats.mana.how/script.js';
|
||||
|
||||
/**
|
||||
* Get the Umami analytics script tag.
|
||||
* Returns empty string if no website ID is configured.
|
||||
*/
|
||||
export function getUmamiScriptTag(websiteId?: string): string {
|
||||
const id = websiteId || process.env.PUBLIC_UMAMI_WEBSITE_ID || '';
|
||||
if (!id) return '';
|
||||
return `<script defer src="${UMAMI_SCRIPT_URL}" data-website-id="${id}"></script>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the Umami analytics script into HTML.
|
||||
* Designed to be used in SvelteKit's transformPageChunk.
|
||||
*
|
||||
* @param html - The HTML string to inject the script into
|
||||
* @param websiteId - Optional website ID override (defaults to PUBLIC_UMAMI_WEBSITE_ID env var)
|
||||
* @returns The HTML with the Umami script injected before </head>
|
||||
*/
|
||||
export function injectUmamiAnalytics(html: string, websiteId?: string): string {
|
||||
const scriptTag = getUmamiScriptTag(websiteId);
|
||||
if (!scriptTag) return html;
|
||||
return html.replace('</head>', `${scriptTag}\n</head>`);
|
||||
}
|
||||
|
|
@ -1,37 +1,13 @@
|
|||
/**
|
||||
* Umami Analytics Utility
|
||||
* Event-API — Web-Analytics DEAKTIVIERT (2026-05-26).
|
||||
*
|
||||
* Provides type-safe event tracking for all Mana apps.
|
||||
* Events are automatically sent to Umami at stats.mana.how
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { trackEvent, trackClick } from '@mana/shared-utils/analytics';
|
||||
*
|
||||
* // Track a custom event
|
||||
* trackEvent('signup_completed', { method: 'email' });
|
||||
*
|
||||
* // Track a button click
|
||||
* trackClick('cta_hero', 'Get Started');
|
||||
* ```
|
||||
* Verein-weit kein Besucher-Tracking mehr (keine Tracking-Pixel, auch
|
||||
* nicht „nur Analytics" — siehe mana/docs/MISSION.md). Die `*Events`-
|
||||
* Objekte und `track*`-Funktionen bleiben als No-ops bestehen, damit die
|
||||
* bestehenden Aufruf-Stellen quer durch die Module weiter kompilieren —
|
||||
* sie senden nichts mehr.
|
||||
*/
|
||||
|
||||
// Umami types
|
||||
declare global {
|
||||
interface Window {
|
||||
umami?: {
|
||||
track: (eventName: string, eventData?: Record<string, string | number | boolean>) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Umami is available
|
||||
*/
|
||||
export function isUmamiAvailable(): boolean {
|
||||
return typeof window !== 'undefined' && typeof window.umami?.track === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a custom event
|
||||
*
|
||||
|
|
@ -45,15 +21,9 @@ export function trackEvent(
|
|||
eventName: string,
|
||||
data?: Record<string, string | number | boolean>
|
||||
): void {
|
||||
if (!isUmamiAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.umami!.track(eventName, data);
|
||||
} catch (error) {
|
||||
console.warn('[Analytics] Failed to track event:', eventName, error);
|
||||
}
|
||||
// No-op: Web-Analytics entfernt (2026-05-26). Bewusst kein Tracking.
|
||||
void eventName;
|
||||
void data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue