HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //tmp/bewertemich-tracking-loop.js
import { apiFetch } from '../modules/app/api.js';

const visitorKey = 'bm_visitor_id';
const sessionKey = 'bm_session_id';
const inviteAttributionKey = 'bm_invite_attribution';

const sanitizeKey = (value, maxLength = 80) => {
  const raw = String(value || '').trim().toLowerCase();
  if (!raw) return '';
  return raw.replace(/[^a-z0-9:_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, maxLength);
};

const makeId = () => {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();
  return `bm-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
};

const readStorage = (storage, key) => {
  try {
    return storage.getItem(key) || '';
  } catch {
    return '';
  }
};

const writeStorage = (storage, key, value) => {
  try {
    storage.setItem(key, value);
  } catch {}
};

export const getVisitorId = () => {
  if (typeof window === 'undefined') return '';
  const existing = readStorage(window.localStorage, visitorKey);
  if (existing) return existing;
  const next = makeId();
  writeStorage(window.localStorage, visitorKey, next);
  return next;
};

export const getSessionId = () => {
  if (typeof window === 'undefined') return '';
  const existing = readStorage(window.sessionStorage, sessionKey);
  if (existing) return existing;
  const next = makeId();
  writeStorage(window.sessionStorage, sessionKey, next);
  return next;
};

export const getStoredInviteAttribution = () => {
  if (typeof window === 'undefined') return null;
  try {
    const raw = window.sessionStorage.getItem(inviteAttributionKey);
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null;
  }
};

export const rememberInviteAttribution = (search = '', photoId = '') => {
  if (typeof window === 'undefined') return null;
  const params = new URLSearchParams(search || '');
  const campaign = sanitizeKey(params.get('src'), 30);
  if (campaign !== 'invite') return null;
  const payload = {
    source: sanitizeKey(params.get('via'), 50) || 'direct',
    surface: sanitizeKey(params.get('surface'), 50) || 'share',
    sourcePhotoId: String(photoId || '').replace(/[^0-9]+/g, '').slice(0, 20),
    capturedAt: new Date().toISOString()
  };
  try {
    window.sessionStorage.setItem(inviteAttributionKey, JSON.stringify(payload));
  } catch {}
  return payload;
};

export const getUploadEntry = (search = '') => {
  const params = new URLSearchParams(search || '');
  const directEntry = sanitizeKey(params.get('entry'), 80);
  if (directEntry) return directEntry;
  const invite = getStoredInviteAttribution();
  if (invite?.sourcePhotoId || invite?.source) return 'invite_to_upload';
  return 'direct';
};

export const buildUploadPath = (entry = 'direct') => {
  const safeEntry = sanitizeKey(entry, 80) || 'direct';
  return `/upload?entry=${encodeURIComponent(safeEntry)}`;
};

export const getInviteAttribution = (search = '') => {
  const params = new URLSearchParams(search || '');
  const campaign = sanitizeKey(params.get('src'), 30);
  const source = sanitizeKey(params.get('via'), 50);
  const surface = sanitizeKey(params.get('surface'), 50);
  return {
    isInvite: campaign === 'invite',
    source: source || (campaign === 'invite' ? 'direct' : ''),
    surface: surface || ''
  };
};

export const buildShareUrl = (photoId, { source = 'copy', surface = 'upload_success' } = {}) => {
  if (typeof window === 'undefined') return '';
  const id = Number(photoId);
  if (!Number.isInteger(id) || id <= 0) return '';
  const params = new URLSearchParams({ src: 'invite' });
  const safeSource = sanitizeKey(source, 50);
  const safeSurface = sanitizeKey(surface, 50);
  if (safeSource) params.set('via', safeSource);
  if (safeSurface) params.set('surface', safeSurface);
  return `${window.location.origin}/s/${id}?${params.toString()}`;
};

export const buildComparisonShareUrl = (comparisonId, { source = 'copy', surface = 'compare_page' } = {}) => {
  if (typeof window === 'undefined') return '';
  const id = Number(comparisonId);
  if (!Number.isInteger(id) || id <= 0) return '';
  const params = new URLSearchParams({ src: 'invite' });
  const safeSource = sanitizeKey(source, 50);
  const safeSurface = sanitizeKey(surface, 50);
  if (safeSource) params.set('via', safeSource);
  if (safeSurface) params.set('surface', safeSurface);
  return `${window.location.origin}/s/compare/${id}?${params.toString()}`;
};

const pushMatomo = (eventName, source, surface) => {
  try {
    const _paq = window._paq;
    if (!Array.isArray(_paq)) return;
    _paq.push(['trackEvent', 'Growth', eventName, `${source || 'none'}:${surface || 'none'}`]);
  } catch {}
};

const postEvent = async (body, transport) => {
  if (typeof window === 'undefined') return;
  if (transport === 'beacon' && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
    try {
      const blob = new Blob([JSON.stringify(body)], { type: 'application/json' });
      if (navigator.sendBeacon('/api/public/events', blob)) return;
    } catch {}
  }
  try {
    await apiFetch('/api/public/events', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(body),
      keepalive: transport === 'beacon'
    });
  } catch {}
};

export const trackEvent = async (eventName, payload = {}, options = {}) => {
  const safeEvent = sanitizeKey(eventName, 80);
  if (!safeEvent || typeof window === 'undefined') return;
  const source = sanitizeKey(payload.source, 50);
  const surface = sanitizeKey(payload.surface, 50);
  const entryPoint = sanitizeKey(payload.entryPoint, 80);
  pushMatomo(safeEvent, source, surface);
  await postEvent(
    {
      event: safeEvent,
      visitorId: getVisitorId(),
      sessionId: getSessionId(),
      photoId: Number.isInteger(payload.photoId) && payload.photoId > 0 ? payload.photoId : undefined,
      source: source || undefined,
      surface: surface || undefined,
      entryPoint: entryPoint || undefined,
      path: `${window.location.pathname}${window.location.search}`,
      referrer: document.referrer || '',
      meta: payload.meta || undefined
    },
    options.transport
  );
};