- Set Spanish as default language with ephemeral/encrypted privacy focus - Translate all user-facing strings and legal pages to Spanish - Replace Norwegian flag with Spanish flag in footer - Remove Hemmelig/terces.cloud links, add cloudhost.es sponsorship - Rewrite PrivacyPage: zero data collection, ephemeral design emphasis - Rewrite TermsPage: Spanish law, RGPD, paste.es/CloudHost.es references - Update PWA manifest, HTML meta tags, package.json branding - Rename webhook headers to X-Paste-Event / X-Paste-Signature - Update API docs title and contact to paste.es / cloudhost.es Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { createHmac } from 'crypto';
|
|
import config from '../config';
|
|
|
|
const analyticsConfig = config.get('analytics') as { enabled: boolean; hmacSecret: string };
|
|
|
|
/**
|
|
* Creates a unique, anonymous visitor ID using HMAC-SHA256.
|
|
* This ensures privacy by never storing the raw IP address.
|
|
*/
|
|
export function createVisitorId(ip: string, userAgent: string): string {
|
|
return createHmac('sha256', analyticsConfig.hmacSecret)
|
|
.update(ip + userAgent)
|
|
.digest('hex');
|
|
}
|
|
|
|
/**
|
|
* Validates that a path is safe for analytics tracking.
|
|
* Prevents injection of malicious paths.
|
|
*/
|
|
export function isValidAnalyticsPath(path: string): boolean {
|
|
const pathRegex = /^\/[a-zA-Z0-9\-?=&/#]*$/;
|
|
return pathRegex.test(path) && path.length <= 255;
|
|
}
|
|
|
|
/**
|
|
* Checks if analytics tracking is enabled.
|
|
*/
|
|
export function isAnalyticsEnabled(): boolean {
|
|
return analyticsConfig.enabled;
|
|
}
|
|
|
|
/**
|
|
* Calculates the start date for a given time range.
|
|
* @param timeRange - Time range string (7d, 14d, 30d)
|
|
* @returns Start date for the query
|
|
*/
|
|
export function getStartDateForTimeRange(timeRange: '7d' | '14d' | '30d'): Date {
|
|
const now = new Date();
|
|
const startDate = new Date();
|
|
|
|
switch (timeRange) {
|
|
case '7d':
|
|
startDate.setDate(now.getDate() - 7);
|
|
break;
|
|
case '14d':
|
|
startDate.setDate(now.getDate() - 14);
|
|
break;
|
|
case '30d':
|
|
startDate.setDate(now.getDate() - 30);
|
|
break;
|
|
}
|
|
|
|
return startDate;
|
|
}
|
|
|
|
/**
|
|
* Calculates percentage with fixed decimal places, returning 0 if total is 0.
|
|
*/
|
|
export function calculatePercentage(value: number, total: number, decimals = 2): number {
|
|
if (total === 0) return 0;
|
|
return parseFloat(((value / total) * 100).toFixed(decimals));
|
|
}
|