Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    rcmaples

    fullstory-async-methods

    rcmaples/fullstory-async-methods
    Coding
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Comprehensive guide for implementing Fullstory's Asynchronous API methods (Async suffix variants) for web applications...

    SKILL.md

    Fullstory Asynchronous Methods API

    Overview

    Fullstory's Browser API provides asynchronous versions of all methods by appending Async to the method name. These async methods return Promise-like objects that resolve when Fullstory has started and the action completes. This is essential for:

    • Initialization Waiting: Wait for Fullstory to fully bootstrap before taking actions
    • Session URL Retrieval: Get the session replay URL for logging, support tickets, etc.
    • Error Handling: Know if an API call succeeded or failed
    • Sequential Operations: Ensure operations complete in order
    • Conditional Logic: Take action based on Fullstory state

    Core Concepts

    Sync vs Async Methods

    Method Type Returns Use When
    FS('methodName') undefined Fire-and-forget, don't need result
    FS('methodNameAsync') Promise-like Need result, error handling, or sequencing

    Promise-like Object

    The object returned from async methods:

    • Can be awaited
    • Supports .then() chaining
    • Important: .catch() may not work in older browsers without Promise polyfill
    • May reject if Fullstory fails to initialize

    Available Async Methods

    Every FS method has an async variant:

    Sync Method Async Method
    FS('setIdentity', {...}) FS('setIdentityAsync', {...})
    FS('setProperties', {...}) FS('setPropertiesAsync', {...})
    FS('trackEvent', {...}) FS('trackEventAsync', {...})
    FS('getSession') FS('getSessionAsync')
    FS('shutdown') FS('shutdownAsync')
    FS('restart') FS('restartAsync')
    FS('log', {...}) FS('logAsync', {...})

    API Reference

    Basic Syntax

    // Async/await pattern
    const result = await FS('methodNameAsync', params)
    
    // Promise pattern
    FS('methodNameAsync', params).then((result) => {
      /* handle result */
    })
    

    Return Values

    Method Resolves With
    getSessionAsync Session URL string
    setIdentityAsync undefined (completion signal)
    setPropertiesAsync undefined
    trackEventAsync undefined
    shutdownAsync undefined
    restartAsync undefined
    observeAsync Observer object with .disconnect()

    Rejection Scenarios

    The Promise may reject when:

    • Malformed or missing configuration (no _fs_org)
    • User on unsupported browser
    • Error in rec/settings or rec/page calls
    • Organization over quota
    • Fullstory script blocked by ad blocker (may not reliably reject)

    ✅ GOOD IMPLEMENTATION EXAMPLES

    Example 1: Get Session URL for Support

    // GOOD: Get session URL for support ticket
    async function attachSessionToSupportTicket(ticketId) {
      try {
        const sessionUrl = await FS('getSessionAsync')
    
        // Attach to support ticket
        await updateSupportTicket(ticketId, {
          fullstoryUrl: sessionUrl,
          attachedAt: new Date().toISOString(),
        })
    
        console.log('Session attached to ticket:', sessionUrl)
        return sessionUrl
      } catch (error) {
        console.warn('Could not get Fullstory session:', error)
        // Continue without session URL - non-critical
        return null
      }
    }
    
    // Usage
    document.getElementById('help-button').addEventListener('click', async () => {
      const ticket = await createSupportTicket(userIssue)
      await attachSessionToSupportTicket(ticket.id)
      showTicketConfirmation(ticket)
    })
    

    Why this is good:

    • ✅ Uses try/catch for error handling
    • ✅ Gracefully handles Fullstory being unavailable
    • ✅ Non-blocking failure (user can still submit ticket)
    • ✅ Returns null on failure for caller to handle

    Example 2: Wait for Fullstory Before Critical Actions

    // GOOD: Ensure Fullstory is ready before identifying
    async function initializeAnalytics(user) {
      try {
        // Wait for Fullstory to be ready
        await FS('setIdentityAsync', {
          uid: user.id,
          properties: {
            displayName: user.name,
            email: user.email,
          },
        })
    
        console.log('User identified successfully')
    
        // Now safe to track initial events
        await FS('trackEventAsync', {
          name: 'Session Started',
          properties: {
            entryPage: window.location.pathname,
            referrer: document.referrer,
          },
        })
    
        return true
      } catch (error) {
        console.error('Fullstory initialization failed:', error)
        // Analytics failure shouldn't break the app
        return false
      }
    }
    
    // Usage in app bootstrap
    async function bootstrap() {
      const user = await authenticateUser()
    
      // Initialize analytics (don't block on failure)
      initializeAnalytics(user)
    
      // Continue app initialization
      renderApp()
    }
    

    Why this is good:

    • ✅ Waits for identification to complete
    • ✅ Sequential: identify before tracking events
    • ✅ Handles errors gracefully
    • ✅ Doesn't block app on analytics failure

    Example 3: Session URL in Error Reports

    // GOOD: Include session URL in error logging
    async function captureError(error, context = {}) {
      let sessionUrl = null
    
      try {
        // Try to get session URL, but don't let it block error reporting
        sessionUrl = await Promise.race([
          FS('getSessionAsync'),
          new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000)),
        ])
      } catch (e) {
        // Session URL unavailable - continue without it
      }
    
      // Send error to monitoring service
      await errorMonitor.captureException(error, {
        ...context,
        fullstoryUrl: sessionUrl,
        timestamp: new Date().toISOString(),
      })
    
      // Also log to Fullstory if available
      if (typeof FS !== 'undefined') {
        FS('log', {
          level: 'error',
          msg: error.message,
        })
      }
    }
    
    // Usage
    window.addEventListener('error', (event) => {
      captureError(event.error, {
        source: 'window.onerror',
        filename: event.filename,
        lineno: event.lineno,
      })
    })
    

    Why this is good:

    • ✅ Timeout prevents hanging on unresponsive FS
    • ✅ Error reporting continues without session URL
    • ✅ Enriches error context when available
    • ✅ Logs error to Fullstory too

    Example 4: Observer Pattern with Async

    // GOOD: Set up Fullstory observers with proper cleanup
    async function setupFullstoryObservers() {
      const observers = []
    
      try {
        // Observer for when Fullstory starts capturing
        const startObserver = await FS('observeAsync', {
          type: 'start',
          callback: () => {
            console.log('Fullstory started capturing')
            initializeSessionTracking()
          },
        })
        observers.push(startObserver)
    
        // Observer for session URL availability
        const sessionObserver = await FS('observeAsync', {
          type: 'session',
          callback: (session) => {
            console.log('Session URL:', session.url)
            storeSessionUrl(session.url)
          },
        })
        observers.push(sessionObserver)
    
        // Return cleanup function
        return () => {
          observers.forEach((obs) => obs.disconnect())
        }
      } catch (error) {
        console.warn('Could not set up Fullstory observers:', error)
        return () => {} // No-op cleanup
      }
    }
    
    // Usage with React
    function App() {
      useEffect(() => {
        let cleanup = () => {}
    
        setupFullstoryObservers().then((cleanupFn) => {
          cleanup = cleanupFn
        })
    
        return () => cleanup()
      }, [])
    
      return <AppContent />
    }
    

    Why this is good:

    • ✅ Proper async observer setup
    • ✅ Cleanup function for component unmount
    • ✅ Handles initialization failure
    • ✅ Multiple observers managed together

    Example 5: Conditional Feature Based on FS Status

    // GOOD: Enable features only if Fullstory is working
    class SessionReplayFeature {
      constructor() {
        this.isAvailable = false
        this.sessionUrl = null
      }
    
      async initialize() {
        try {
          // Check if Fullstory is capturing
          this.sessionUrl = await FS('getSessionAsync')
          this.isAvailable = true
          return true
        } catch (error) {
          this.isAvailable = false
          console.info('Session replay feature unavailable:', error.message)
          return false
        }
      }
    
      getShareableLink() {
        if (!this.isAvailable || !this.sessionUrl) {
          return null
        }
        return this.sessionUrl
      }
    
      renderShareButton() {
        if (!this.isAvailable) {
          return null // Don't show button if FS unavailable
        }
    
        return `<button onclick="copySessionLink()">Share Session</button>`
      }
    }
    
    // Usage
    const sessionReplay = new SessionReplayFeature()
    
    async function initializeUI() {
      await sessionReplay.initialize()
    
      if (sessionReplay.isAvailable) {
        showSessionReplayUI()
      }
    }
    

    Why this is good:

    • ✅ Graceful degradation when FS unavailable
    • ✅ Feature flag based on actual FS status
    • ✅ No broken UI if FS blocked
    • ✅ Clear availability check

    Example 6: Sequential Operations

    // GOOD: Ensure proper sequence of FS operations
    async function completeCheckout(orderData) {
      try {
        // 1. First, ensure user is identified
        await FS('setIdentityAsync', {
          uid: orderData.userId,
          properties: {
            displayName: orderData.customerName,
            email: orderData.customerEmail,
          },
        })
    
        // 2. Update user properties with purchase info
        await FS('setPropertiesAsync', {
          type: 'user',
          properties: {
            lifetimeValue: orderData.customerLTV,
            totalOrders: orderData.customerOrderCount,
            lastOrderAt: new Date().toISOString(),
          },
        })
    
        // 3. Track the purchase event
        await FS('trackEventAsync', {
          name: 'Order Completed',
          properties: {
            orderId: orderData.id,
            revenue: orderData.total,
            itemCount: orderData.items.length,
          },
        })
    
        // 4. Get session URL for order records
        const sessionUrl = await FS('getSessionAsync')
    
        // 5. Update order with session URL
        await saveOrderSessionUrl(orderData.id, sessionUrl)
    
        console.log('Checkout tracked successfully')
      } catch (error) {
        // Log but don't fail checkout
        console.error('Analytics tracking failed:', error)
      }
    }
    

    Why this is good:

    • ✅ Operations happen in correct order
    • ✅ User identified before properties set
    • ✅ Event tracked after user data set
    • ✅ Session URL captured at end
    • ✅ Errors don't break checkout

    ❌ BAD IMPLEMENTATION EXAMPLES

    Example 1: Blocking App on Fullstory

    // BAD: Blocking application startup on Fullstory
    async function startApp() {
      // This will hang if Fullstory is blocked!
      const sessionUrl = await FS('getSessionAsync')
    
      // App never starts if FS fails
      renderApp()
    }
    

    Why this is bad:

    • ❌ App hangs if Fullstory blocked by ad blocker
    • ❌ Promise may never resolve
    • ❌ Critical path depends on non-critical service
    • ❌ No timeout or error handling

    CORRECTED VERSION:

    // GOOD: Non-blocking initialization
    async function startApp() {
      // Start app immediately
      renderApp()
    
      // Initialize analytics separately
      try {
        await Promise.race([
          FS('getSessionAsync'),
          new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000)),
        ])
        enableAnalyticsFeatures()
      } catch (error) {
        console.warn('Fullstory unavailable, continuing without analytics')
      }
    }
    

    Example 2: Missing Error Handling

    // BAD: No error handling for async call
    async function trackPurchase(order) {
      const sessionUrl = await FS('getSessionAsync') // May throw!
      saveSessionToOrder(order.id, sessionUrl) // Never runs if above fails
    
      await FS('trackEventAsync', {
        // Also may throw
        name: 'Purchase',
        properties: {orderId: order.id},
      })
    }
    

    Why this is bad:

    • ❌ Unhandled promise rejection
    • ❌ Subsequent code won't run on failure
    • ❌ No graceful degradation
    • ❌ Could crash in strict mode

    CORRECTED VERSION:

    // GOOD: Proper error handling
    async function trackPurchase(order) {
      let sessionUrl = null
    
      try {
        sessionUrl = await FS('getSessionAsync')
      } catch (error) {
        console.warn('Could not get session URL:', error)
      }
    
      if (sessionUrl) {
        saveSessionToOrder(order.id, sessionUrl)
      }
    
      try {
        await FS('trackEventAsync', {
          name: 'Purchase',
          properties: {
            orderId: order.id,
            hasSessionUrl: !!sessionUrl,
          },
        })
      } catch (error) {
        console.warn('Could not track purchase event:', error)
      }
    }
    

    Example 3: Using .catch() Without Polyfill

    // BAD: .catch() may not work in older browsers
    FS('getSessionAsync')
      .then((url) => console.log('Session:', url))
      .catch((err) => console.error('Error:', err)) // May fail silently in IE11!
    

    Why this is bad:

    • ❌ .catch() not supported in browsers without Promise
    • ❌ Fullstory's Promise-like object may not implement catch
    • ❌ Errors may go unhandled

    CORRECTED VERSION:

    // GOOD: Use try/catch with async/await
    async function getSession() {
      try {
        const url = await FS('getSessionAsync')
        console.log('Session:', url)
        return url
      } catch (err) {
        console.error('Error:', err)
        return null
      }
    }
    
    // OR: Use .then() only with error callback
    FS('getSessionAsync').then(
      (url) => console.log('Session:', url),
      (err) => console.error('Error:', err), // Second arg to .then() works
    )
    

    Example 4: Unnecessary Async Usage

    // BAD: Using async when you don't need the result
    async function handleButtonClick() {
      // Don't need to await fire-and-forget events
      await FS('trackEventAsync', {
        name: 'Button Clicked',
        properties: {buttonId: 'submit'},
      })
    
      // User waits unnecessarily
      proceedWithAction()
    }
    

    Why this is bad:

    • ❌ Adds unnecessary latency to user action
    • ❌ User waits for analytics to complete
    • ❌ No value from awaiting (result not used)

    CORRECTED VERSION:

    // GOOD: Fire-and-forget for events
    function handleButtonClick() {
      // Don't await - fire and forget
      FS('trackEvent', {
        name: 'Button Clicked',
        properties: {buttonId: 'submit'},
      })
    
      // Proceed immediately
      proceedWithAction()
    }
    

    Example 5: Race Condition with Async

    // BAD: Race condition between identify and track
    async function onLogin(user) {
      // These run in parallel - trackEvent may fire before identity!
      FS('setIdentityAsync', {uid: user.id})
      FS('trackEventAsync', {name: 'Login'})
    }
    

    Why this is bad:

    • ❌ Event may fire before identity is set
    • ❌ Event could be attributed to anonymous user
    • ❌ Data integrity issue

    CORRECTED VERSION:

    // GOOD: Sequential with proper awaiting
    async function onLogin(user) {
      // First identify
      await FS('setIdentityAsync', {
        uid: user.id,
        properties: {displayName: user.name},
      })
    
      // Then track event (now properly attributed)
      await FS('trackEventAsync', {
        name: 'Login',
        properties: {method: 'password'},
      })
    }
    
    // OR: For non-critical, use sync versions (they queue properly)
    function onLogin(user) {
      FS('setIdentity', {uid: user.id}) // Queued first
      FS('trackEvent', {name: 'Login'}) // Queued second
      // Fullstory processes queue in order
    }
    

    COMMON IMPLEMENTATION PATTERNS

    Pattern 1: Safe Async Wrapper

    // Wrapper for safe FS async calls with timeout
    async function safeFS(method, params, options = {}) {
      const {timeout = 5000, fallback = null} = options
    
      // Check if FS exists
      if (typeof FS === 'undefined') {
        console.warn(`FS not available for ${method}`)
        return fallback
      }
    
      try {
        const result = await Promise.race([
          FS(method, params),
          new Promise((_, reject) =>
            setTimeout(() => reject(new Error(`FS ${method} timeout`)), timeout),
          ),
        ])
        return result
      } catch (error) {
        console.warn(`FS ${method} failed:`, error.message)
        return fallback
      }
    }
    
    // Usage
    const sessionUrl = await safeFS('getSessionAsync', undefined, {
      timeout: 3000,
      fallback: null,
    })
    
    await safeFS('trackEventAsync', {
      name: 'Page View',
      properties: {page: '/home'},
    })
    

    Pattern 2: Initialization Status Manager

    // Track Fullstory initialization status
    class CaptureStatusManager {
      constructor() {
        this.status = 'pending'
        this.sessionUrl = null
        this.error = null
        this.callbacks = []
      }
    
      async initialize() {
        try {
          this.sessionUrl = await FS('getSessionAsync')
          this.status = 'ready'
          this.callbacks.forEach((cb) => cb(this.sessionUrl))
        } catch (error) {
          this.status = 'failed'
          this.error = error
        }
    
        return this.status === 'ready'
      }
    
      onReady(callback) {
        if (this.status === 'ready') {
          callback(this.sessionUrl)
        } else if (this.status === 'pending') {
          this.callbacks.push(callback)
        }
        // If failed, don't call
      }
    
      isReady() {
        return this.status === 'ready'
      }
    
      getSessionUrl() {
        return this.sessionUrl
      }
    }
    
    // Global instance
    const fsStatus = new CaptureStatusManager()
    
    // Initialize once
    fsStatus.initialize()
    
    // Use anywhere
    fsStatus.onReady((url) => {
      console.log('FS ready with session:', url)
    })
    

    Pattern 3: Analytics Queue with Fallback

    // Queue analytics calls with sync fallback
    class AnalyticsQueue {
      constructor() {
        this.useAsync = true
        this.pending = []
      }
    
      async track(eventName, properties) {
        if (this.useAsync) {
          try {
            await FS('trackEventAsync', {
              name: eventName,
              properties,
            })
          } catch (error) {
            // Fall back to sync
            console.warn('Async tracking failed, using sync')
            this.useAsync = false
            FS('trackEvent', {name: eventName, properties})
          }
        } else {
          FS('trackEvent', {name: eventName, properties})
        }
      }
    
      async identify(uid, properties) {
        if (this.useAsync) {
          try {
            await FS('setIdentityAsync', {uid, properties})
          } catch (error) {
            this.useAsync = false
            FS('setIdentity', {uid, properties})
          }
        } else {
          FS('setIdentity', {uid, properties})
        }
      }
    }
    

    WHEN TO USE ASYNC VS SYNC

    Use Async When:

    Scenario Why
    Need session URL Must wait for URL to be available
    Error handling needed Need to know if call failed
    Sequential operations Must ensure order of operations
    Conditional logic Need result to decide next action
    Initialization checks Need to know when FS is ready

    Use Sync (Fire-and-Forget) When:

    Scenario Why
    Simple event tracking Don't need confirmation
    Non-critical operations Failure is acceptable
    Performance critical paths Don't want to add latency
    Rapid-fire events Queueing handles order
    User-facing actions Don't delay user experience

    TROUBLESHOOTING

    Promise Never Resolves

    Symptom: await FS('methodAsync') hangs forever

    Common Causes:

    1. ❌ Fullstory script blocked by ad blocker
    2. ❌ Script failed to load
    3. ❌ Network issues preventing initialization

    Solutions:

    • ✅ Always use timeout wrapper
    • ✅ Don't block critical paths
    • ✅ Implement fallback behavior

    Rejection Errors

    Symptom: Promise rejects with error

    Common Causes:

    1. ❌ Missing _fs_org configuration
    2. ❌ Unsupported browser
    3. ❌ Organization over quota
    4. ❌ Configuration error

    Solutions:

    • ✅ Check Fullstory setup
    • ✅ Verify configuration
    • ✅ Handle rejections gracefully

    .catch() Not Working

    Symptom: Errors not caught by .catch()

    Common Causes:

    1. ❌ Browser doesn't have native Promise
    2. ❌ Fullstory's Promise-like doesn't implement catch

    Solutions:

    • ✅ Use async/await with try/catch
    • ✅ Use .then() with error callback

    KEY TAKEAWAYS FOR AGENT

    When helping developers with Async Methods:

    1. Always emphasize:

      • Use timeouts to prevent hanging
      • Handle rejections gracefully
      • Don't block critical paths on FS
      • Use try/catch, not .catch()
    2. Common mistakes to watch for:

      • Blocking app startup on FS
      • Missing error handling
      • Using async when sync would work
      • Race conditions between calls
      • .catch() without polyfill check
    3. Questions to ask developers:

      • Do you need the result?
      • Is this on a critical path?
      • What should happen if FS fails?
      • Is proper sequencing required?
    4. Best practices to recommend:

      • Wrap in timeout for safety
      • Use sync for fire-and-forget
      • Graceful degradation always
      • Don't let analytics break core features

    REFERENCE LINKS

    • Asynchronous Methods: https://developer.fullstory.com/browser/asynchronous-methods/
    • Get Session Details: https://developer.fullstory.com/browser/get-session-details/
    • Callbacks and Delegates: https://developer.fullstory.com/browser/fullcapture/callbacks-and-delegates/

    This skill document was created to help Agent understand and guide developers in implementing Fullstory's Asynchronous Methods correctly for web applications.

    Recommended Servers
    Vercel Grep
    Vercel Grep
    Cloudflare
    Cloudflare
    Nimble MCP Server
    Nimble MCP Server
    Repository
    rcmaples/btx
    Files