Skip to main content
Production-ready JavaScript patterns, utilities, and advanced techniques for building robust Kash embed integrations.

Embed Manager Class

Reusable class for managing embed lifecycle:
// EmbedManager.js
class KashEmbedManager {
  constructor(config) {
    this.config = {
      threadId: config.threadId,
      containerId: config.containerId,
      source: config.source || window.location.hostname,
      height: config.height || 900,
      onReady: config.onReady || null,
      onMarketClick: config.onMarketClick || null,
      onError: config.onError || null,
    };

    this.iframe = null;
    this.isReady = false;
    this.messageHandler = this.handleMessage.bind(this);
  }

  // Initialize embed
  init() {
    const container = document.getElementById(this.config.containerId);
    if (!container) {
      console.error(`Container ${this.config.containerId} not found`);
      return;
    }

    // Create iframe
    this.iframe = document.createElement('iframe');
    this.iframe.src = this.buildEmbedUrl();
    this.iframe.width = '100%';
    this.iframe.height = this.config.height;
    this.iframe.frameBorder = '0';
    this.iframe.setAttribute('loading', 'lazy');
    this.iframe.setAttribute('allow', 'clipboard-write');
    this.iframe.setAttribute('title', 'Kash prediction markets');

    container.appendChild(this.iframe);

    // Listen for messages
    window.addEventListener('message', this.messageHandler);
  }

  // Build embed URL
  buildEmbedUrl() {
    const url = new URL(
      `https://app.kash.bot/threads/${this.config.threadId}/iframe`
    );
    url.searchParams.set('source', this.config.source);
    return url.toString();
  }

  // Handle messages from iframe
  handleMessage(event) {
    if (event.origin !== 'https://app.kash.bot') return;

    const { type, data } = event.data;

    switch (type) {
      case 'kash-iframe-ready':
        this.isReady = true;
        this.config.onReady?.(data);
        break;

      case 'kash-market-clicked':
        this.config.onMarketClick?.(data);
        break;
    }
  }

  // Clean up
  destroy() {
    window.removeEventListener('message', this.messageHandler);
    if (this.iframe && this.iframe.parentNode) {
      this.iframe.parentNode.removeChild(this.iframe);
    }
    this.iframe = null;
    this.isReady = false;
  }
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = KashEmbedManager;
}
Usage:
const embed = new KashEmbedManager({
  threadId: 'abc123',
  containerId: 'kash-embed-container',
  source: 'my-website',
  height: 900,
  onReady: (data) => {
    console.log(`Loaded ${data.marketCount} markets`);
  },
  onMarketClick: (data) => {
    console.log(`Market clicked: ${data.marketId}`);
  },
});

embed.init();

// Clean up when done
embed.destroy();

Retry Logic Utility

Robust retry mechanism with exponential backoff for API calls:
// RetryUtil.js
async function fetchWithRetry(url, options = {}, retryConfig = {}) {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    maxDelay = 10000,
    backoffFactor = 2,
    retryableStatuses = [408, 429, 500, 502, 503, 504],
  } = retryConfig;

  let lastError;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Success
      if (response.ok) {
        return response;
      }

      // Don't retry non-retryable status codes
      if (!retryableStatuses.includes(response.status)) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      // Rate limit - use Retry-After header
      if (response.status === 429) {
        const retryAfter =
          parseInt(response.headers.get('Retry-After'), 10) || baseDelay / 1000;
        const delay = Math.min(retryAfter * 1000, maxDelay);

        console.warn(`Rate limited. Retrying after ${delay}ms`);
        await sleep(delay);
        continue;
      }

      // For other errors, use exponential backoff
      lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
    } catch (error) {
      lastError = error;
    }

    // Don't delay after last attempt
    if (attempt < maxRetries) {
      const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt), maxDelay);
      console.warn(`Attempt ${attempt + 1}/${maxRetries} failed. Retrying after ${delay}ms`);
      await sleep(delay);
    }
  }

  throw new Error(`Failed after ${maxRetries} retries: ${lastError.message}`);
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
Usage:
try {
  const response = await fetchWithRetry(
    'https://app.kash.bot/api/threads?status=active',
    {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' },
    },
    {
      maxRetries: 3,
      baseDelay: 1000,
      backoffFactor: 2,
    }
  );

  const data = await response.json();
  console.log('Threads:', data.threads);
} catch (error) {
  console.error('Failed to fetch threads:', error);
}

Lazy Load Utility

Load embeds only when visible using IntersectionObserver:
// LazyLoadEmbeds.js
class LazyEmbedLoader {
  constructor(config = {}) {
    this.config = {
      root: config.root || null,
      rootMargin: config.rootMargin || '50px',
      threshold: config.threshold || 0.1,
    };

    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.config
    );

    this.embeds = new Map();
  }

  // Register embed for lazy loading
  register(element, embedConfig) {
    this.embeds.set(element, embedConfig);
    this.observer.observe(element);
  }

  // Handle intersection
  handleIntersection(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const element = entry.target;
        const config = this.embeds.get(element);

        if (config) {
          this.loadEmbed(element, config);
          this.observer.unobserve(element);
          this.embeds.delete(element);
        }
      }
    });
  }

  // Load embed
  loadEmbed(container, config) {
    const iframe = document.createElement('iframe');
    const url = new URL(`https://app.kash.bot/threads/${config.threadId}/iframe`);

    if (config.source) url.searchParams.set('source', config.source);

    iframe.src = url.toString();
    iframe.width = '100%';
    iframe.height = config.height || 900;
    iframe.frameBorder = '0';
    iframe.setAttribute('title', 'Kash prediction markets');
    iframe.setAttribute('allow', 'clipboard-write');

    container.innerHTML = '';
    container.appendChild(iframe);

    console.log(`Lazy loaded embed: ${config.threadId}`);
  }

  // Destroy observer
  destroy() {
    this.observer.disconnect();
    this.embeds.clear();
  }
}
Usage:
<div class="lazy-embed-container" data-thread-id="abc123"></div>
<div class="lazy-embed-container" data-thread-id="def456"></div>
<div class="lazy-embed-container" data-thread-id="ghi789"></div>

<script>
  const loader = new LazyEmbedLoader({
    rootMargin: '100px', // Load 100px before entering viewport
    threshold: 0.1,
  });

  document.querySelectorAll('.lazy-embed-container').forEach((container) => {
    loader.register(container, {
      threadId: container.dataset.threadId,
      source: 'my-website',
      height: 900,
    });
  });
</script>

Event Aggregator

Centralized event handling for multiple embeds:
// EventAggregator.js
class EmbedEventAggregator {
  constructor() {
    this.handlers = new Map();
    this.embedMap = new Map(); // iframe -> embedId mapping

    window.addEventListener('message', this.handleMessage.bind(this));
  }

  // Register embed
  registerEmbed(iframe, embedId) {
    this.embedMap.set(iframe, embedId);
  }

  // Register event handler
  on(embedId, eventType, handler) {
    const key = `${embedId}:${eventType}`;

    if (!this.handlers.has(key)) {
      this.handlers.set(key, []);
    }

    this.handlers.get(key).push(handler);
  }

  // Remove event handler
  off(embedId, eventType, handler) {
    const key = `${embedId}:${eventType}`;
    const handlers = this.handlers.get(key);

    if (handlers) {
      const index = handlers.indexOf(handler);
      if (index !== -1) {
        handlers.splice(index, 1);
      }
    }
  }

  // Handle message
  handleMessage(event) {
    if (event.origin !== 'https://app.kash.bot') return;

    const { type, data } = event.data;

    // Find which embed sent this message
    let embedId = null;
    for (const [iframe, id] of this.embedMap.entries()) {
      if (iframe.contentWindow === event.source) {
        embedId = id;
        break;
      }
    }

    if (!embedId) return;

    // Trigger handlers
    const key = `${embedId}:${type}`;
    const handlers = this.handlers.get(key);

    if (handlers) {
      handlers.forEach((handler) => {
        try {
          handler(data);
        } catch (error) {
          console.error('Handler error:', error);
        }
      });
    }

    // Trigger wildcard handlers
    const wildcardKey = `${embedId}:*`;
    const wildcardHandlers = this.handlers.get(wildcardKey);

    if (wildcardHandlers) {
      wildcardHandlers.forEach((handler) => {
        try {
          handler(type, data);
        } catch (error) {
          console.error('Wildcard handler error:', error);
        }
      });
    }
  }

  // Unregister embed
  unregisterEmbed(iframe) {
    this.embedMap.delete(iframe);
  }
}
Usage:
const aggregator = new EmbedEventAggregator();

// Setup embeds
const iframe1 = document.getElementById('embed-1');
const iframe2 = document.getElementById('embed-2');

aggregator.registerEmbed(iframe1, 'embed-1');
aggregator.registerEmbed(iframe2, 'embed-2');

// Listen to specific embed events
aggregator.on('embed-1', 'kash-iframe-ready', (data) => {
  console.log('Embed 1 ready:', data.marketCount, 'markets');
});

aggregator.on('embed-2', 'kash-market-clicked', (data) => {
  console.log('Embed 2 market click:', data.marketId);
});

// Listen to all events from embed
aggregator.on('embed-1', '*', (eventType, data) => {
  console.log(`Embed 1 event: ${eventType}`, data);
});

Performance Monitor

Track embed performance metrics:
// PerformanceMonitor.js
class EmbedPerformanceMonitor {
  constructor(embedId) {
    this.embedId = embedId;
    this.metrics = {
      loadTime: null,
      interactionCount: 0,
      errors: [],
    };

    this.startTime = performance.now();
  }

  // Mark embed as loaded
  markLoaded() {
    this.metrics.loadTime = performance.now() - this.startTime;
    console.log(`Embed ${this.embedId} loaded in ${this.metrics.loadTime.toFixed(2)}ms`);
  }

  // Track interaction
  trackInteraction() {
    this.metrics.interactionCount++;
  }

  // Track error
  trackError(error) {
    this.metrics.errors.push({
      message: error.message,
      timestamp: Date.now(),
    });
  }

  // Get metrics summary
  getSummary() {
    return {
      embedId: this.embedId,
      loadTime: this.metrics.loadTime,
      interactionCount: this.metrics.interactionCount,
      errorCount: this.metrics.errors.length,
    };
  }
}
Usage:
const monitor = new EmbedPerformanceMonitor('embed-123');

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://app.kash.bot') return;

  switch (event.data.type) {
    case 'kash-iframe-ready':
      monitor.markLoaded();
      break;

    case 'kash-market-clicked':
      monitor.trackInteraction();
      break;
  }
});

// Log metrics on page unload
window.addEventListener('beforeunload', () => {
  console.log('Embed metrics:', monitor.getSummary());
});

State Manager

Manage embed state across page lifecycle:
// StateManager.js
class EmbedStateManager {
  constructor(storageKey = 'kash_embed_state') {
    this.storageKey = storageKey;
    this.state = this.loadState();
  }

  // Load state from localStorage
  loadState() {
    try {
      const stored = localStorage.getItem(this.storageKey);
      return stored ? JSON.parse(stored) : {};
    } catch (error) {
      console.error('Failed to load state:', error);
      return {};
    }
  }

  // Save state to localStorage
  saveState() {
    try {
      localStorage.setItem(this.storageKey, JSON.stringify(this.state));
    } catch (error) {
      console.error('Failed to save state:', error);
    }
  }

  // Set embed state
  setEmbedState(embedId, key, value) {
    if (!this.state[embedId]) {
      this.state[embedId] = {};
    }

    this.state[embedId][key] = value;
    this.saveState();
  }

  // Get embed state
  getEmbedState(embedId, key, defaultValue = null) {
    return this.state[embedId]?.[key] ?? defaultValue;
  }

  // Clear embed state
  clearEmbedState(embedId) {
    delete this.state[embedId];
    this.saveState();
  }

  // Clear all state
  clearAllState() {
    this.state = {};
    this.saveState();
  }
}
Usage:
const stateManager = new EmbedStateManager();

// Save visited markets
stateManager.setEmbedState('embed-1', 'visitedMarkets', ['market-1', 'market-2']);

// Restore on page load
const visitedMarkets = stateManager.getEmbedState('embed-1', 'visitedMarkets', []);
console.log('Previously visited:', visitedMarkets);

Next Steps