fix(analytics): properly forward client IPs to Rybbit and OpenPanel
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests - Extract real client IP from Cloudflare headers (cf-connecting-ip) - Forward X-Forwarded-For and X-Real-IP headers to analytics backends - Update OpenPanel proxy to also forward client IP - Update next.config.ts rewrite to use internal API route This fixes geo-location issues where all traffic appeared to come from Cloudflare edge locations instead of actual visitor countries.
This commit is contained in:
@@ -11,30 +11,104 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
type QueuedEvent = {
|
||||
eventName: string;
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export class RybbitProvider implements AnalyticsProvider {
|
||||
name = "Rybbit";
|
||||
private isClient: boolean;
|
||||
private eventQueue: QueuedEvent[] = [];
|
||||
private flushInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private initialized = false;
|
||||
|
||||
constructor() {
|
||||
this.isClient = typeof window !== "undefined";
|
||||
|
||||
if (this.isClient) {
|
||||
console.log("[RybbitProvider] Constructor called");
|
||||
// Start checking for rybbit availability
|
||||
this.startFlushInterval();
|
||||
|
||||
// Also try to flush immediately in case script is already loaded
|
||||
setTimeout(() => this.tryFlushQueue(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
private startFlushInterval() {
|
||||
// Check every 500ms for up to 15 seconds
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
|
||||
this.flushInterval = setInterval(() => {
|
||||
attempts++;
|
||||
const available = this.isAvailable();
|
||||
|
||||
if (available && !this.initialized) {
|
||||
console.log("[RybbitProvider] Script became available, flushing queue");
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
this.tryFlushQueue();
|
||||
|
||||
if (available || attempts >= maxAttempts) {
|
||||
this.stopFlushInterval();
|
||||
if (attempts >= maxAttempts && !available) {
|
||||
console.warn("[RybbitProvider] Max attempts reached, script not loaded. Queue size:", this.eventQueue.length);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private stopFlushInterval() {
|
||||
if (this.flushInterval) {
|
||||
clearInterval(this.flushInterval);
|
||||
this.flushInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
private tryFlushQueue() {
|
||||
if (!this.isAvailable() || this.eventQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[RybbitProvider] Flushing ${this.eventQueue.length} queued events`);
|
||||
|
||||
// Flush all queued events
|
||||
while (this.eventQueue.length > 0) {
|
||||
const event = this.eventQueue.shift();
|
||||
if (event) {
|
||||
this.sendEvent(event.eventName, event.properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isAvailable(): boolean {
|
||||
return this.isClient && typeof window.rybbit?.event === "function";
|
||||
}
|
||||
|
||||
private trackEvent(eventName: string, properties?: Record<string, unknown>): void {
|
||||
if (!this.isAvailable()) {
|
||||
console.warn(`[Rybbit] Not available for event: ${eventName}`);
|
||||
return;
|
||||
}
|
||||
private sendEvent(eventName: string, properties?: Record<string, unknown>): void {
|
||||
try {
|
||||
window.rybbit!.event(eventName, properties);
|
||||
console.log(`[Rybbit] Event sent: ${eventName}`);
|
||||
} catch (e) {
|
||||
console.warn(`[Rybbit] Tracking error for ${eventName}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
private trackEvent(eventName: string, properties?: Record<string, unknown>): void {
|
||||
if (!this.isClient) return;
|
||||
|
||||
if (this.isAvailable()) {
|
||||
this.sendEvent(eventName, properties);
|
||||
} else {
|
||||
// Queue the event for later
|
||||
this.eventQueue.push({ eventName, properties });
|
||||
console.log(`[Rybbit] Queued event: ${eventName}, queue size: ${this.eventQueue.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
track(event: AnalyticsEvent): void {
|
||||
switch (event.type) {
|
||||
case "product_viewed":
|
||||
|
||||
Reference in New Issue
Block a user