All Code Examples

10 examples found

HTML Product Card with BEM

Semantic product card structure using BEM naming conventions and UCP-ready data attributes

html - HTML Product Card with BEM
<!-- Product Card Component with UCP-Ready Data Attributes -->
<div class="product-card"
     data-product-id="prod-12345"
     data-product-sku="HD-CLAY-001"
     data-ucp-entity="product">

  <!-- Product Image -->
  <div class="product-card__image-container">
    <img class="product-card__image"
         src="/images/american-crew-clay.jpg"
         alt="American Crew Matte Clay">
  </div>

  <!-- Product Details -->
  <div class="product-card__details">
    <h3 class="product-card__title" data-field="title">
      American Crew Matte Clay
    </h3>
    
    <p class="product-card__price"
       data-field="price"
       data-currency="USD"
       data-value="24.99">
      $24.99
    </p>
    
    <p class="product-card__description">
      High hold with matte finish. Perfect for short to medium hair styles.
    </p>
  </div>

  <!-- Product Actions -->
  <div class="product-card__actions">
    <button class="product-card__button product-card__button--primary"
            data-action="add-to-cart"
            data-item-id="prod-12345"
            data-item-name="American Crew Matte Clay"
            data-item-brand="American Crew"
            data-item-category="Hair Styling"
            data-item-price="24.99"
            data-currency="USD">
      Add to Cart
    </button>
    
    <button class="product-card__button product-card__button--secondary"
            data-action="view-details"
            data-item-id="prod-12345">
      View Details
    </button>
  </div>
</div>

UCP Integration Class

JavaScript class for managing Universal Commerce Protocol authentication and session state

javascript - UCP Integration Class
/**
 * Universal Commerce Protocol Integration
 * Manages user authentication, session state, and token handling
 */
class UCPIntegration {
  constructor(config) {
    this.apiEndpoint = config.apiEndpoint || 'https://api.ucp.example.com';
    this.middlewareUrl = config.middlewareUrl; // Xano or n8n endpoint
    this.token = null;
    this.userId = null;
    this.sessionId = null;
    this.init();
  }
  
  /**
   * Initialize UCP session
   */
  async init() {
    // Check for existing token in localStorage
    this.token = localStorage.getItem('ucp_token');
    
    if (this.token) {
      await this.validateToken();
    } else {
      await this.createSession();
    }
  }
  
  /**
   * Create new UCP session
   */
  async createSession() {
    try {
      const response = await fetch(`${this.middlewareUrl}/create-session`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          timestamp: Date.now(),
          referrer: document.referrer,
          userAgent: navigator.userAgent
        })
      });
      
      const data = await response.json();
      
      this.token = data.token;
      this.userId = data.userId;
      this.sessionId = data.sessionId;
      
      // Store token
      localStorage.setItem('ucp_token', this.token);
      localStorage.setItem('ucp_session_id', this.sessionId);
      
      console.log('UCP Session Created:', this.sessionId);
    } catch (error) {
      console.error('UCP Session Creation Failed:', error);
    }
  }
  
  /**
   * Validate existing token
   */
  async validateToken() {
    try {
      const response = await fetch(`${this.middlewareUrl}/validate-token`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.token}`
        }
      });
      
      if (response.ok) {
        const data = await response.json();
        this.userId = data.userId;
        this.sessionId = data.sessionId;
        console.log('UCP Token Valid');
      } else {
        // Token invalid, create new session
        await this.createSession();
      }
    } catch (error) {
      console.error('UCP Token Validation Failed:', error);
      await this.createSession();
    }
  }
  
  /**
   * Get hashed token for analytics (privacy-safe)
   */
  getHashedToken() {
    if (!this.token) return null;
    
    // Simple hash for demonstration
    // In production, use proper crypto hashing
    let hash = 0;
    for (let i = 0; i < this.token.length; i++) {
      const char = this.token.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return 'ucp_' + Math.abs(hash).toString(36);
  }
  
  /**
   * Update user context
   */
  async updateContext(contextData) {
    try {
      await fetch(`${this.middlewareUrl}/update-context`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.token}`
        },
        body: JSON.stringify({
          sessionId: this.sessionId,
          context: contextData
        })
      });
    } catch (error) {
      console.error('UCP Context Update Failed:', error);
    }
  }
}

// Initialize UCP
const ucp = new UCPIntegration({
  middlewareUrl: 'https://your-middleware.xano.io/api:v1'
});

GA4 Event Tracker

Complete GA4 ecommerce tracking with UCP integration for purchase, add to cart, and checkout events

javascript - GA4 Event Tracker
/**
 * GA4 Event Tracking with UCP Integration
 * Implements Google Analytics 4 ecommerce tracking with Universal Commerce Protocol
 */
class GA4EventTracker {
  constructor(ucpInstance) {
    this.ucp = ucpInstance;
    this.dataLayer = window.dataLayer || [];
    window.dataLayer = this.dataLayer;
  }
  
  /**
   * Track Add to Cart Event
   * Fires when user clicks "Add to Cart" button
   */
  trackAddToCart(element) {
    const productCard = element.closest('.product-card');
    
    // Extract data from BEM-structured HTML
    const eventData = {
      event: 'add_to_cart',
      
      // UCP User Context
      user_id: this.ucp.userId,
      ucp_token_hash: this.ucp.getHashedToken(),
      ucp_session_id: this.ucp.sessionId,
      
      // Element Context (captured by GTM)
      element_id: element.id,
      element_class: element.className,
      element_label: element.textContent.trim(),
      element_action: element.getAttribute('data-action'),
      
      // Product Data
      ecommerce: {
        currency: element.getAttribute('data-currency') || 'USD',
        value: parseFloat(element.getAttribute('data-item-price')),
        items: [{
          item_id: element.getAttribute('data-item-id'),
          item_name: element.getAttribute('data-item-name'),
          item_brand: element.getAttribute('data-item-brand'),
          item_category: element.getAttribute('data-item-category'),
          price: parseFloat(element.getAttribute('data-item-price')),
          quantity: 1
        }]
      },
      
      // UCP Entity Context
      ucp_entity_type: productCard.getAttribute('data-ucp-entity'),
      ucp_product_id: productCard.getAttribute('data-product-id'),
      ucp_product_sku: productCard.getAttribute('data-product-sku')
    };
    
    // Push to data layer
    this.dataLayer.push(eventData);
    
    console.log('GA4 Event: add_to_cart', eventData);
  }
  
  /**
   * Track Purchase Event
   * Fires on successful checkout completion
   */
  trackPurchase(orderData) {
    const eventData = {
      event: 'purchase',
      
      // UCP User Context
      user_id: this.ucp.userId,
      ucp_token_hash: this.ucp.getHashedToken(),
      ucp_session_id: this.ucp.sessionId,
      
      // Transaction Data
      ecommerce: {
        transaction_id: orderData.transaction_id,
        affiliation: orderData.affiliation || 'Online Store',
        value: orderData.total,
        tax: orderData.tax,
        shipping: orderData.shipping,
        currency: orderData.currency || 'USD',
        coupon: orderData.coupon || '',
        
        // Items purchased
        items: orderData.items.map(item => ({
          item_id: item.product_id,
          item_name: item.name,
          item_brand: item.brand,
          item_category: item.category,
          item_variant: item.variant || '',
          price: item.price,
          quantity: item.quantity,
          
          // UCP-specific fields
          ucp_product_id: item.ucp_product_id,
          ucp_sku: item.sku
        }))
      },
      
      // UCP Transaction Context
      ucp_checkout_id: orderData.ucp_checkout_id,
      ucp_payment_method: orderData.payment_method,
      ucp_fulfillment_method: orderData.fulfillment_method
    };
    
    // Push to data layer
    this.dataLayer.push(eventData);
    
    console.log('GA4 Event: purchase', eventData);
  }
  
  /**
   * Track Begin Checkout Event
   */
  trackBeginCheckout(cartData) {
    const eventData = {
      event: 'begin_checkout',
      
      // UCP User Context
      user_id: this.ucp.userId,
      ucp_token_hash: this.ucp.getHashedToken(),
      ucp_session_id: this.ucp.sessionId,
      
      // Cart Data
      ecommerce: {
        currency: cartData.currency || 'USD',
        value: cartData.total,
        items: cartData.items.map(item => ({
          item_id: item.product_id,
          item_name: item.name,
          item_brand: item.brand,
          item_category: item.category,
          price: item.price,
          quantity: item.quantity
        }))
      },
      
      // UCP Checkout Context
      ucp_checkout_id: cartData.checkout_id
    };
    
    this.dataLayer.push(eventData);
    console.log('GA4 Event: begin_checkout', eventData);
  }
}

// Initialize GA4 Tracker
const ga4Tracker = new GA4EventTracker(ucp);

Event Listeners with BEM Selectors

Event delegation using stable BEM class selectors for tracking user interactions

javascript - Event Listeners with BEM Selectors
/**
 * Event Listeners with BEM Selectors
 * Uses event delegation and stable semantic class names
 */

// Add to Cart Event Listener
document.addEventListener('click', function(event) {
  const button = event.target.closest('.product-card__button[data-action="add-to-cart"]');
  
  if (button) {
    event.preventDefault();
    
    // Visual feedback
    button.setAttribute('data-state', 'loading');
    button.textContent = 'Adding...';
    
    // Track event
    ga4Tracker.trackAddToCart(button);
    
    // Simulate API call
    setTimeout(() => {
      button.setAttribute('data-state', 'success');
      button.textContent = 'Added to Cart';
      
      // Reset after 2 seconds
      setTimeout(() => {
        button.removeAttribute('data-state');
        button.textContent = 'Add to Cart';
      }, 2000);
    }, 500);
  }
});

// View Details Event Listener
document.addEventListener('click', function(event) {
  const button = event.target.closest('.product-card__button[data-action="view-details"]');
  
  if (button) {
    event.preventDefault();
    
    const itemId = button.getAttribute('data-item-id');
    
    // Track view_item event
    window.dataLayer.push({
      event: 'view_item',
      user_id: ucp.userId,
      ucp_token_hash: ucp.getHashedToken(),
      item_id: itemId
    });
    
    // Navigate to product page
    window.location.href = `/products/${itemId}`;
  }
});

// Checkout Button Event Listener
document.addEventListener('click', function(event) {
  const button = event.target.closest('.checkout-button');
  
  if (button) {
    event.preventDefault();
    
    // Get cart data
    const cartData = getCartData();
    
    // Track begin_checkout
    ga4Tracker.trackBeginCheckout(cartData);
    
    // Navigate to checkout
    window.location.href = '/checkout';
  }
});

/**
 * Helper: Get cart data from DOM or localStorage
 */
function getCartData() {
  const cartItems = JSON.parse(localStorage.getItem('cart') || '[]');
  
  const total = cartItems.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
  }, 0);
  
  return {
    checkout_id: 'chk_' + Date.now(),
    currency: 'USD',
    total: total,
    items: cartItems
  };
}

/**
 * Helper: Complete purchase tracking
 */
function completePurchase(orderData) {
  // Track purchase event
  ga4Tracker.trackPurchase(orderData);
  
  // Send to UCP backend
  fetch(ucp.middlewareUrl + '/complete-purchase', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${ucp.token}`
    },
    body: JSON.stringify({
      sessionId: ucp.sessionId,
      order: orderData
    })
  }).then(response => response.json())
    .then(data => {
      console.log('Purchase recorded in UCP:', data);
    })
    .catch(error => {
      console.error('UCP purchase recording failed:', error);
    });
  
  // Clear cart
  localStorage.removeItem('cart');
  
  // Redirect to confirmation
  window.location.href = '/order-confirmation?id=' + orderData.transaction_id;
}

Product Card CSS with BEM

BEM-based styling with data attribute state management for product cards

css - Product Card CSS with BEM
/**
 * Product Card Component Styles
 * Uses BEM naming convention for maintainability
 */

/* Block: product-card */
.product-card {
  display: flex;
  flex-direction: column;
  background: #ffffff;
  border: 1px solid #e0e0e0;
  transition: box-shadow 0.3s ease;
}

.product-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Element: product-card__image-container */
.product-card__image-container {
  position: relative;
  width: 100%;
  padding-top: 100%; /* 1:1 aspect ratio */
  overflow: hidden;
  background: #f5f5f5;
}

/* Element: product-card__image */
.product-card__image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.product-card:hover .product-card__image {
  transform: scale(1.05);
}

/* Element: product-card__details */
.product-card__details {
  padding: 1rem;
  flex: 1;
}

/* Element: product-card__title */
.product-card__title {
  font-size: 1.125rem;
  font-weight: 600;
  color: #1a1a1a;
  margin: 0 0 0.5rem 0;
  line-height: 1.4;
}

/* Element: product-card__price */
.product-card__price {
  font-size: 1.25rem;
  font-weight: 700;
  color: #d32f2f;
  margin: 0 0 0.75rem 0;
}

/* Element: product-card__description */
.product-card__description {
  font-size: 0.875rem;
  color: #666666;
  line-height: 1.6;
  margin: 0;
}

/* Element: product-card__actions */
.product-card__actions {
  padding: 1rem;
  display: flex;
  gap: 0.5rem;
  border-top: 1px solid #e0e0e0;
}

/* Element: product-card__button */
.product-card__button {
  flex: 1;
  padding: 0.75rem 1rem;
  font-size: 0.875rem;
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: all 0.2s ease;
  text-align: center;
}

/* Modifier: product-card__button--primary */
.product-card__button--primary {
  background: #d32f2f;
  color: #ffffff;
}

.product-card__button--primary:hover {
  background: #b71c1c;
}

.product-card__button--primary:active {
  background: #8b0000;
}

/* Modifier: product-card__button--secondary */
.product-card__button--secondary {
  background: #ffffff;
  color: #1a1a1a;
  border: 1px solid #e0e0e0;
}

.product-card__button--secondary:hover {
  background: #f5f5f5;
}

/* State: Button loading */
.product-card__button[data-state="loading"] {
  opacity: 0.6;
  cursor: wait;
  pointer-events: none;
}

/* State: Button success */
.product-card__button[data-state="success"] {
  background: #2e7d32;
  color: #ffffff;
}

/* State: Button disabled */
.product-card__button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .product-card__actions {
    flex-direction: column;
  }
  
  .product-card__button {
    width: 100%;
  }
}

GTM Tag Configuration

Complete Google Tag Manager setup for GA4 events with UCP parameters

javascript - GTM Tag Configuration
/**
 * Google Tag Manager Configuration
 * 
 * Tag: GA4 - Add to Cart Event
 * Type: Google Analytics: GA4 Event
 * Trigger: Custom Event = 'add_to_cart'
 */

// Event Name: add_to_cart

// Event Parameters:
{
  // User identification
  user_id: {{DLV - user_id}},
  
  // UCP Context
  ucp_token_hash: {{DLV - ucp_token_hash}},
  ucp_session_id: {{DLV - ucp_session_id}},
  ucp_entity_type: {{DLV - ucp_entity_type}},
  ucp_product_id: {{DLV - ucp_product_id}},
  ucp_product_sku: {{DLV - ucp_product_sku}},
  
  // Element Context
  element_id: {{DLV - element_id}},
  element_class: {{DLV - element_class}},
  element_label: {{DLV - element_label}},
  element_action: {{DLV - element_action}},
  
  // Ecommerce Data (automatically captured)
  currency: {{DLV - ecommerce.currency}},
  value: {{DLV - ecommerce.value}},
  items: {{DLV - ecommerce.items}}
}

/**
 * Data Layer Variables (DLV) to Create:
 */

// Variable: DLV - user_id
// Type: Data Layer Variable
// Data Layer Variable Name: user_id

// Variable: DLV - ucp_token_hash
// Type: Data Layer Variable
// Data Layer Variable Name: ucp_token_hash

// Variable: DLV - ucp_session_id
// Type: Data Layer Variable
// Data Layer Variable Name: ucp_session_id

// Variable: DLV - element_class
// Type: Data Layer Variable
// Data Layer Variable Name: element_class

// Variable: DLV - element_action
// Type: Data Layer Variable
// Data Layer Variable Name: element_action

// Variable: DLV - ecommerce.items
// Type: Data Layer Variable
// Data Layer Variable Name: ecommerce.items

/**
 * Trigger Configuration:
 */

// Trigger: Custom Event - add_to_cart
// Type: Custom Event
// Event name: add_to_cart
// This trigger fires on: All Custom Events

/**
 * Tag: GA4 - Purchase Event
 * Type: Google Analytics: GA4 Event
 * Trigger: Custom Event = 'purchase'
 */

// Event Name: purchase

// Event Parameters:
{
  user_id: {{DLV - user_id}},
  ucp_token_hash: {{DLV - ucp_token_hash}},
  ucp_session_id: {{DLV - ucp_session_id}},
  ucp_checkout_id: {{DLV - ucp_checkout_id}},
  ucp_payment_method: {{DLV - ucp_payment_method}},
  ucp_fulfillment_method: {{DLV - ucp_fulfillment_method}},
  
  // Transaction data (automatically captured from ecommerce object)
  transaction_id: {{DLV - ecommerce.transaction_id}},
  affiliation: {{DLV - ecommerce.affiliation}},
  value: {{DLV - ecommerce.value}},
  tax: {{DLV - ecommerce.tax}},
  shipping: {{DLV - ecommerce.shipping}},
  currency: {{DLV - ecommerce.currency}},
  coupon: {{DLV - ecommerce.coupon}},
  items: {{DLV - ecommerce.items}}
}

/**
 * Debug Mode:
 * Enable GA4 DebugView by adding ?debug_mode=true to URL
 * Or set debug_mode: true in dataLayer push
 */

window.dataLayer.push({
  event: 'add_to_cart',
  debug_mode: true, // Enable for testing
  // ... rest of event data
});

Complete Integration Flow

Full implementation showing how all components work together from HTML to GA4

javascript - Complete Integration Flow
/**
 * Complete GA4 + UCP Integration Flow
 * This example shows the full implementation from page load to event tracking
 */

// Step 1: Initialize on page load
document.addEventListener('DOMContentLoaded', async function() {
  
  // Initialize UCP Integration
  const ucp = new UCPIntegration({
    middlewareUrl: 'https://your-middleware.xano.io/api:v1'
  });
  
  // Wait for UCP to initialize
  await ucp.init();
  
  // Initialize GA4 Tracker
  const ga4Tracker = new GA4EventTracker(ucp);
  
  // Step 2: Set up event listeners
  setupEventListeners(ga4Tracker);
  
  // Step 3: Track page view
  trackPageView(ucp);
});

/**
 * Set up all event listeners
 */
function setupEventListeners(ga4Tracker) {
  
  // Add to Cart
  document.addEventListener('click', function(event) {
    const button = event.target.closest('.product-card__button[data-action="add-to-cart"]');
    if (button) {
      event.preventDefault();
      handleAddToCart(button, ga4Tracker);
    }
  });
  
  // Begin Checkout
  document.addEventListener('click', function(event) {
    const button = event.target.closest('.checkout-button');
    if (button) {
      event.preventDefault();
      handleBeginCheckout(ga4Tracker);
    }
  });
}

/**
 * Handle Add to Cart action
 */
function handleAddToCart(button, ga4Tracker) {
  // Update button state
  button.setAttribute('data-state', 'loading');
  button.textContent = 'Adding...';
  
  // Track GA4 event
  ga4Tracker.trackAddToCart(button);
  
  // Add to cart (API call)
  const itemData = {
    product_id: button.getAttribute('data-item-id'),
    name: button.getAttribute('data-item-name'),
    price: parseFloat(button.getAttribute('data-item-price')),
    quantity: 1
  };
  
  addToCartAPI(itemData)
    .then(() => {
      // Success state
      button.setAttribute('data-state', 'success');
      button.textContent = 'Added!';
      
      // Update cart count in header
      updateCartCount();
      
      // Reset button after 2 seconds
      setTimeout(() => {
        button.removeAttribute('data-state');
        button.textContent = 'Add to Cart';
      }, 2000);
    })
    .catch(error => {
      console.error('Add to cart failed:', error);
      button.removeAttribute('data-state');
      button.textContent = 'Error - Try Again';
    });
}

/**
 * Handle Begin Checkout
 */
function handleBeginCheckout(ga4Tracker) {
  const cartData = getCartData();
  
  if (cartData.items.length === 0) {
    alert('Your cart is empty');
    return;
  }
  
  // Track begin_checkout
  ga4Tracker.trackBeginCheckout(cartData);
  
  // Navigate to checkout
  window.location.href = '/checkout';
}

/**
 * Track page view
 */
function trackPageView(ucp) {
  window.dataLayer.push({
    event: 'page_view',
    user_id: ucp.userId,
    ucp_token_hash: ucp.getHashedToken(),
    ucp_session_id: ucp.sessionId,
    page_path: window.location.pathname,
    page_title: document.title
  });
}

/**
 * API: Add item to cart
 */
function addToCartAPI(itemData) {
  return fetch('/api/cart/add', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(itemData)
  }).then(response => {
    if (!response.ok) throw new Error('API Error');
    return response.json();
  });
}

/**
 * Get cart data from localStorage
 */
function getCartData() {
  const items = JSON.parse(localStorage.getItem('cart') || '[]');
  const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  
  return {
    checkout_id: 'chk_' + Date.now(),
    currency: 'USD',
    total: total,
    items: items
  };
}

/**
 * Update cart count badge
 */
function updateCartCount() {
  const cart = JSON.parse(localStorage.getItem('cart') || '[]');
  const count = cart.reduce((sum, item) => sum + item.quantity, 0);
  
  const badge = document.querySelector('.cart-badge');
  if (badge) {
    badge.textContent = count;
    badge.style.display = count > 0 ? 'block' : 'none';
  }
}

Xano: Idempotent Keys for AP2

Xano serverless function implementing idempotent keys to prevent duplicate API requests in Analytics Protocol 2

javascript - Xano: Idempotent Keys for AP2
/**
 * Xano Serverless Function: Idempotent Keys for AP2
 * Prevents duplicate GA4 Measurement Protocol events
 * 
 * Use Case: Ensure purchase events are only sent once,
 * even if the client retries due to network issues
 */

// Xano Function Stack Configuration
// Endpoint: POST /api/v1/ga4/event
// Authentication: API Key or Bearer Token

/**
 * Step 1: Generate Idempotency Key (Client-side)
 */
function generateIdempotencyKey(eventData) {
  // Create deterministic key from event data
  const keyComponents = [
    eventData.client_id,
    eventData.event_name,
    eventData.transaction_id || eventData.timestamp,
    eventData.items?.map(i => i.item_id).join('-') || ''
  ];
  
  // SHA-256 hash for consistent key generation
  return crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(keyComponents.join('|'))
  ).then(hash => {
    return Array.from(new Uint8Array(hash))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  });
}

/**
 * Step 2: Xano Function - Check and Process Event
 * (Xano No-Code Logic / Custom Function)
 */

// --- Xano Function Stack ---
// 1. Get idempotency_key from request headers
// 2. Query idempotency_cache table
// 3. If exists and not expired -> return cached response
// 4. If not exists -> process event, cache response

// Xano Custom Function (JavaScript)
const xanoIdempotentHandler = async (input) => {
  const { idempotencyKey, eventPayload, ttlMinutes = 60 } = input;
  
  // Check cache (Xano Database Query)
  const cached = await xano.db.query(
    'idempotency_cache',
    { idempotency_key: idempotencyKey }
  );
  
  if (cached && !isExpired(cached.created_at, ttlMinutes)) {
    return {
      status: 'duplicate',
      message: 'Event already processed',
      original_response: cached.response,
      idempotency_key: idempotencyKey
    };
  }
  
  // Forward to GA4 Measurement Protocol
  const ga4Response = await sendToGA4(eventPayload);
  
  // Cache the response
  await xano.db.insert('idempotency_cache', {
    idempotency_key: idempotencyKey,
    event_name: eventPayload.events[0].name,
    response: ga4Response,
    created_at: new Date().toISOString()
  });
  
  return {
    status: 'processed',
    response: ga4Response,
    idempotency_key: idempotencyKey
  };
};

/**
 * Step 3: Send to GA4 Measurement Protocol
 */
async function sendToGA4(payload) {
  const GA4_ENDPOINT = 'https://www.google-analytics.com/mp/collect';
  const MEASUREMENT_ID = process.env.GA4_MEASUREMENT_ID;
  const API_SECRET = process.env.GA4_API_SECRET;
  
  const response = await fetch(
    `${GA4_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );
  
  return {
    status: response.status,
    timestamp: new Date().toISOString()
  };
}

/**
 * Step 4: Client Usage Example
 */
async function sendGA4Event(eventData) {
  const idempotencyKey = await generateIdempotencyKey(eventData);
  
  const response = await fetch('https://your-xano.xano.io/api/v1/ga4/event', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Idempotency-Key': idempotencyKey,
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    body: JSON.stringify({
      client_id: getClientId(),
      events: [{
        name: 'purchase',
        params: eventData
      }]
    })
  });
  
  return response.json();
}

// Example: Safe purchase tracking
sendGA4Event({
  transaction_id: 'T-12345',
  value: 99.99,
  currency: 'USD',
  items: [{ item_id: 'SKU-001', item_name: 'Product', price: 99.99 }]
});

Cloudflare Workers: Edge Serving for EMEA

Cloudflare Workers for GDPR-compliant GA4 event processing at the edge with EU data residency

javascript - Cloudflare Workers: Edge Serving for EMEA
/**
 * Cloudflare Worker: Edge Serving for EMEA
 * GDPR-compliant GA4 event processing with EU data residency
 * 
 * Use Case: Process analytics events at EU edge locations,
 * strip PII before forwarding, ensure data never leaves EU
 */

// wrangler.toml configuration:
// [env.production]
// routes = [{ pattern = "analytics.yourdomain.com/*", zone_name = "yourdomain.com" }]
// [placement]
// mode = "smart" # Runs at nearest edge location

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // Only handle POST requests to /collect endpoint
    if (request.method !== 'POST' || !url.pathname.startsWith('/collect')) {
      return new Response('Not Found', { status: 404 });
    }
    
    // Detect user region from Cloudflare headers
    const country = request.cf?.country || 'US';
    const isEMEA = isEMEARegion(country);
    const colo = request.cf?.colo; // Edge location code
    
    try {
      const payload = await request.json();
      
      // EMEA-specific processing
      if (isEMEA) {
        // 1. Strip/hash PII for GDPR compliance
        const sanitizedPayload = sanitizeForGDPR(payload, country);
        
        // 2. Add edge metadata
        sanitizedPayload.edge_metadata = {
          processed_at: colo,
          region: 'EMEA',
          country: country,
          timestamp: Date.now()
        };
        
        // 3. Route to EU GA4 endpoint (if using server-side GTM in EU)
        const ga4Response = await forwardToGA4(sanitizedPayload, env, 'EU');
        
        // 4. Optional: Store in EU-based KV for audit trail
        if (env.EMEA_AUDIT_LOG) {
          ctx.waitUntil(
            env.EMEA_AUDIT_LOG.put(
              `event:${Date.now()}:${sanitizedPayload.client_id}`,
              JSON.stringify({
                event: sanitizedPayload.events[0].name,
                country,
                colo,
                timestamp: new Date().toISOString()
              }),
              { expirationTtl: 86400 * 30 } // 30 days retention
            )
          );
        }
        
        return new Response(JSON.stringify({
          status: 'ok',
          region: 'EMEA',
          edge_location: colo,
          gdpr_compliant: true
        }), {
          headers: {
            'Content-Type': 'application/json',
            'X-Edge-Location': colo,
            'X-Data-Region': 'EU'
          }
        });
      }
      
      // Non-EMEA: Standard processing
      const ga4Response = await forwardToGA4(payload, env, 'US');
      
      return new Response(JSON.stringify({
        status: 'ok',
        region: 'ROW',
        edge_location: colo
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
      
    } catch (error) {
      return new Response(JSON.stringify({ error: error.message }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }
};

/**
 * Check if country is in EMEA region
 */
function isEMEARegion(country) {
  const emeaCountries = [
    // EU Members
    'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
    'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
    'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
    // EEA + UK + CH
    'GB', 'NO', 'IS', 'LI', 'CH',
    // Middle East & Africa (selected)
    'AE', 'SA', 'ZA', 'EG', 'IL', 'TR'
  ];
  return emeaCountries.includes(country);
}

/**
 * Sanitize payload for GDPR compliance
 */
function sanitizeForGDPR(payload, country) {
  const sanitized = JSON.parse(JSON.stringify(payload));
  
  // Hash client_id instead of raw value
  if (sanitized.client_id) {
    sanitized.client_id = hashValue(sanitized.client_id);
  }
  
  // Hash user_id if present
  if (sanitized.user_id) {
    sanitized.user_id = hashValue(sanitized.user_id);
  }
  
  // Remove precise geolocation
  if (sanitized.events) {
    sanitized.events.forEach(event => {
      if (event.params) {
        delete event.params.latitude;
        delete event.params.longitude;
        // Keep only country-level geo
        event.params.geo_country = country;
      }
    });
  }
  
  // Remove IP (Cloudflare provides this)
  delete sanitized.ip_address;
  
  // Add GDPR processing flag
  sanitized.gdpr_processed = true;
  
  return sanitized;
}

/**
 * Hash sensitive values
 */
function hashValue(value) {
  // Simple hash for demo - use proper crypto in production
  let hash = 0;
  for (let i = 0; i < value.length; i++) {
    const char = value.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }
  return 'gdpr_' + Math.abs(hash).toString(36);
}

/**
 * Forward to GA4 Measurement Protocol
 */
async function forwardToGA4(payload, env, region) {
  const endpoint = 'https://www.google-analytics.com/mp/collect';
  
  return fetch(
    `${endpoint}?measurement_id=${env.GA4_MEASUREMENT_ID}&api_secret=${env.GA4_API_SECRET}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );
}

/**
 * Client-side usage:
 */
// const EDGE_ENDPOINT = 'https://analytics.yourdomain.com/collect';
// fetch(EDGE_ENDPOINT, { method: 'POST', body: JSON.stringify(eventData) });

Netlify Functions: GA4 Privacy Fires

Netlify serverless functions for consent-based GA4 event firing with privacy controls

javascript - Netlify Functions: GA4 Privacy Fires
/**
 * Netlify Function: GA4 Privacy Fires
 * Consent-aware event processing with privacy controls
 * 
 * Use Case: Fire GA4 events only when user consent is valid,
 * apply data minimization based on consent level
 */

// netlify/functions/ga4-privacy-fire.js

const CONSENT_LEVELS = {
  NONE: 0,           // No tracking
  ESSENTIAL: 1,      // Anonymous aggregate only
  FUNCTIONAL: 2,     // Session tracking, no user ID
  ANALYTICS: 3,      // Full analytics with hashed IDs
  MARKETING: 4       // Full tracking including ads
};

exports.handler = async (event, context) => {
  // Only accept POST requests
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }
  
  try {
    const payload = JSON.parse(event.body);
    const consentString = event.headers['x-consent-status'] || 'NONE';
    const consentLevel = CONSENT_LEVELS[consentString] || 0;
    
    // Get user region from headers (set by Netlify Edge)
    const country = event.headers['x-country'] || 
                    event.headers['x-nf-client-connection-ip-country'] || 'US';
    
    // Determine applicable privacy law
    const privacyLaw = getApplicablePrivacyLaw(country);
    
    // Check if we can fire this event
    const eventType = payload.events?.[0]?.name || 'unknown';
    const canFire = canFireEvent(eventType, consentLevel, privacyLaw);
    
    if (!canFire.allowed) {
      return {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          status: 'blocked',
          reason: canFire.reason,
          consent_required: canFire.requiredLevel,
          current_consent: consentString
        })
      };
    }
    
    // Apply privacy transformations based on consent level
    const transformedPayload = applyPrivacyTransforms(
      payload,
      consentLevel,
      privacyLaw
    );
    
    // Add privacy metadata
    transformedPayload.events[0].params = {
      ...transformedPayload.events[0].params,
      consent_level: consentString,
      privacy_law: privacyLaw,
      data_minimized: consentLevel < CONSENT_LEVELS.MARKETING
    };
    
    // Forward to GA4
    const ga4Response = await sendToGA4(transformedPayload);
    
    // Log for audit (optional - use Netlify Blobs or external service)
    await logPrivacyAudit({
      event_type: eventType,
      consent_level: consentString,
      privacy_law: privacyLaw,
      country,
      timestamp: new Date().toISOString(),
      allowed: true
    });
    
    return {
      statusCode: 200,
      headers: { 
        'Content-Type': 'application/json',
        'X-Privacy-Law': privacyLaw,
        'X-Consent-Applied': consentString
      },
      body: JSON.stringify({
        status: 'fired',
        event: eventType,
        consent_level: consentString,
        privacy_law: privacyLaw,
        transformations_applied: getTransformationList(consentLevel)
      })
    };
    
  } catch (error) {
    console.error('Privacy fire error:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Processing failed' })
    };
  }
};

/**
 * Determine which privacy law applies
 */
function getApplicablePrivacyLaw(country) {
  const laws = {
    // GDPR countries
    'AT': 'GDPR', 'BE': 'GDPR', 'BG': 'GDPR', 'HR': 'GDPR', 'CY': 'GDPR',
    'CZ': 'GDPR', 'DK': 'GDPR', 'EE': 'GDPR', 'FI': 'GDPR', 'FR': 'GDPR',
    'DE': 'GDPR', 'GR': 'GDPR', 'HU': 'GDPR', 'IE': 'GDPR', 'IT': 'GDPR',
    'LV': 'GDPR', 'LT': 'GDPR', 'LU': 'GDPR', 'MT': 'GDPR', 'NL': 'GDPR',
    'PL': 'GDPR', 'PT': 'GDPR', 'RO': 'GDPR', 'SK': 'GDPR', 'SI': 'GDPR',
    'ES': 'GDPR', 'SE': 'GDPR', 'GB': 'UK-GDPR',
    // US States
    'US-CA': 'CCPA', 'US-VA': 'VCDPA', 'US-CO': 'CPA',
    // Other
    'BR': 'LGPD', 'CA': 'PIPEDA'
  };
  return laws[country] || 'NONE';
}

/**
 * Check if event can fire based on consent
 */
function canFireEvent(eventType, consentLevel, privacyLaw) {
  // Events that require marketing consent
  const marketingEvents = ['view_promotion', 'select_promotion', 'ad_click'];
  
  // Events that require analytics consent
  const analyticsEvents = ['purchase', 'add_to_cart', 'begin_checkout', 'view_item'];
  
  // Essential events (can fire with minimal consent)
  const essentialEvents = ['page_view', 'scroll', 'first_visit'];
  
  // GDPR/UK-GDPR requires explicit consent for non-essential
  if (['GDPR', 'UK-GDPR'].includes(privacyLaw)) {
    if (marketingEvents.includes(eventType) && consentLevel < CONSENT_LEVELS.MARKETING) {
      return { allowed: false, reason: 'Marketing consent required', requiredLevel: 'MARKETING' };
    }
    if (analyticsEvents.includes(eventType) && consentLevel < CONSENT_LEVELS.ANALYTICS) {
      return { allowed: false, reason: 'Analytics consent required', requiredLevel: 'ANALYTICS' };
    }
  }
  
  // CCPA allows opt-out model (can fire unless explicitly opted out)
  if (privacyLaw === 'CCPA' && consentLevel === CONSENT_LEVELS.NONE) {
    if (marketingEvents.includes(eventType)) {
      return { allowed: false, reason: 'User opted out of sale', requiredLevel: 'FUNCTIONAL' };
    }
  }
  
  return { allowed: true };
}

/**
 * Apply privacy transformations based on consent
 */
function applyPrivacyTransforms(payload, consentLevel, privacyLaw) {
  const transformed = JSON.parse(JSON.stringify(payload));
  
  // Level 1 (Essential): Remove all identifiers
  if (consentLevel <= CONSENT_LEVELS.ESSENTIAL) {
    delete transformed.user_id;
    transformed.client_id = 'anonymous';
    if (transformed.events?.[0]?.params) {
      delete transformed.events[0].params.user_properties;
    }
  }
  
  // Level 2 (Functional): Hash client_id, no user_id
  else if (consentLevel === CONSENT_LEVELS.FUNCTIONAL) {
    delete transformed.user_id;
    if (transformed.client_id) {
      transformed.client_id = hashValue(transformed.client_id);
    }
  }
  
  // Level 3 (Analytics): Hash all IDs
  else if (consentLevel === CONSENT_LEVELS.ANALYTICS) {
    if (transformed.user_id) {
      transformed.user_id = hashValue(transformed.user_id);
    }
    if (transformed.client_id) {
      transformed.client_id = hashValue(transformed.client_id);
    }
  }
  
  // Level 4 (Marketing): Full data, no transformations
  
  return transformed;
}

/**
 * Hash sensitive values
 */
function hashValue(value) {
  let hash = 5381;
  for (let i = 0; i < value.length; i++) {
    hash = ((hash << 5) + hash) + value.charCodeAt(i);
  }
  return 'h_' + Math.abs(hash).toString(36);
}

/**
 * Get list of transformations applied
 */
function getTransformationList(consentLevel) {
  const transforms = [];
  if (consentLevel <= CONSENT_LEVELS.ESSENTIAL) {
    transforms.push('anonymized_ids', 'removed_user_properties');
  } else if (consentLevel === CONSENT_LEVELS.FUNCTIONAL) {
    transforms.push('hashed_client_id', 'removed_user_id');
  } else if (consentLevel === CONSENT_LEVELS.ANALYTICS) {
    transforms.push('hashed_all_ids');
  }
  return transforms;
}

/**
 * Send to GA4 Measurement Protocol
 */
async function sendToGA4(payload) {
  const fetch = (await import('node-fetch')).default;
  return fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=${process.env.GA4_MEASUREMENT_ID}&api_secret=${process.env.GA4_API_SECRET}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );
}

/**
 * Log for privacy audit trail
 */
async function logPrivacyAudit(auditData) {
  // Implement with Netlify Blobs, external DB, or logging service
  console.log('Privacy Audit:', JSON.stringify(auditData));
}

/**
 * Client-side usage:
 */
// fetch('/.netlify/functions/ga4-privacy-fire', {
//   method: 'POST',
//   headers: {
//     'Content-Type': 'application/json',
//     'X-Consent-Status': 'ANALYTICS' // or 'NONE', 'ESSENTIAL', 'FUNCTIONAL', 'MARKETING'
//   },
//   body: JSON.stringify({
//     client_id: 'GA1.1.123456789',
//     events: [{ name: 'purchase', params: { value: 99.99 } }]
//   })
// });