feat(analytics): implement advanced tracking with script-less smart proxy
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Failing after 1m18s
Build & Deploy / 🏗️ Build (push) Failing after 2m58s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Failing after 1m18s
Build & Deploy / 🏗️ Build (push) Failing after 2m58s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
- Added Umami Smart Proxy route handler - Refactored Umami adapter to use proxy-based fetch - Implemented TrackedButton, TrackedLink, and ScrollDepthTracker - Integrated event tracking into ContactForm - Enhanced Analytics component with manual pageview and performance tracking
This commit is contained in:
@@ -96,7 +96,6 @@ export function getDefaultAnalytics(): AnalyticsService {
|
||||
|
||||
if (provider === "umami") {
|
||||
defaultAnalytics = createUmamiAnalytics({
|
||||
websiteId: env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || "",
|
||||
hostUrl: env.UMAMI_API_ENDPOINT,
|
||||
});
|
||||
} else if (provider === "plausible") {
|
||||
|
||||
@@ -3,52 +3,76 @@
|
||||
* Decoupled implementation
|
||||
*/
|
||||
|
||||
import type { AnalyticsAdapter, AnalyticsEvent, AnalyticsConfig } from './interfaces';
|
||||
import type {
|
||||
AnalyticsAdapter,
|
||||
AnalyticsEvent,
|
||||
AnalyticsConfig,
|
||||
} from "./interfaces";
|
||||
|
||||
export interface UmamiConfig extends AnalyticsConfig {
|
||||
websiteId: string;
|
||||
hostUrl?: string;
|
||||
hostUrl?: string; // Optional, defaults to env var on server
|
||||
}
|
||||
|
||||
export class UmamiAdapter implements AnalyticsAdapter {
|
||||
private websiteId: string;
|
||||
private hostUrl: string;
|
||||
|
||||
constructor(config: UmamiConfig) {
|
||||
this.websiteId = config.websiteId;
|
||||
this.hostUrl = config.hostUrl || 'https://cloud.umami.is';
|
||||
this.hostUrl = config.hostUrl || "https://analytics.infra.mintel.me";
|
||||
}
|
||||
|
||||
private async sendPayload(type: "event", data: Record<string, any>) {
|
||||
try {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const payload = {
|
||||
hostname: window.location.hostname,
|
||||
screen: `${window.screen.width}x${window.screen.height}`,
|
||||
language: navigator.language,
|
||||
referrer: document.referrer,
|
||||
...data,
|
||||
};
|
||||
|
||||
await fetch("/stats/api/send", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ type, payload }),
|
||||
keepalive: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to send analytics", e);
|
||||
}
|
||||
}
|
||||
|
||||
async track(event: AnalyticsEvent): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const w = window as any;
|
||||
if (w.umami) {
|
||||
w.umami.track(event.name, event.props);
|
||||
}
|
||||
}
|
||||
|
||||
async page(path: string, props?: Record<string, any>): Promise<void> {
|
||||
// Umami tracks pageviews automatically by default,
|
||||
// but we can manually trigger it if needed.
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const w = window as any;
|
||||
if (w.umami) {
|
||||
w.umami.track(props?.name || 'pageview', { url: path, ...props });
|
||||
}
|
||||
}
|
||||
|
||||
async identify(userId: string, traits?: Record<string, any>): Promise<void> {
|
||||
// Umami doesn't have a direct 'identify' like Segment,
|
||||
// but we can track it as an event or session property if supported by the instance.
|
||||
await this.track({
|
||||
name: 'identify',
|
||||
props: { userId, ...traits }
|
||||
await this.sendPayload("event", {
|
||||
name: event.name,
|
||||
data: event.props,
|
||||
url: window.location.pathname + window.location.search,
|
||||
});
|
||||
}
|
||||
|
||||
async page(path: string, props?: Record<string, any>): Promise<void> {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
await this.sendPayload("event", {
|
||||
url: path,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
async identify(
|
||||
_userId: string,
|
||||
_traits?: Record<string, any>,
|
||||
): Promise<void> {
|
||||
// Not implemented in this version
|
||||
}
|
||||
|
||||
// No script tag needed for proxy mode
|
||||
getScriptTag(): string {
|
||||
return `<script async src="${this.hostUrl}/script.js" data-website-id="${this.websiteId}"></script>`;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user