Claude-skill-registry fullstory-async-methods
Comprehensive guide for implementing Fullstory's Asynchronous API methods (Async suffix variants) for web applications. Teaches proper Promise handling, await patterns, error handling, and when to use async vs fire-and-forget methods. Includes detailed good/bad examples for initialization waiting, session URL retrieval, and conditional flows to help developers handle Fullstory's asynchronous nature correctly.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fullstory-async-methods" ~/.claude/skills/majiayu000-claude-skill-registry-fullstory-async-methods && rm -rf "$T"
skills/data/fullstory-async-methods/SKILL.mdFullstory 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 |
|---|---|---|
| undefined | Fire-and-forget, don't need result |
| Promise-like | Need result, error handling, or sequencing |
Promise-like Object
The object returned from async methods:
- Can be
edawait - Supports
chaining.then() - Important:
may not work in older browsers without Promise polyfill.catch() - May reject if Fullstory fails to initialize
Available Async Methods
Every FS method has an async variant:
| Sync Method | Async Method |
|---|---|
| |
| |
| |
| |
| |
| |
| |
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 |
|---|---|
| Session URL string |
| undefined (completion signal) |
| undefined |
| undefined |
| undefined |
| undefined |
| Observer object with |
Rejection Scenarios
The Promise may reject when:
- Malformed or missing configuration (no
)_fs_org - User on unsupported browser
- Error in
orrec/settings
callsrec/page - 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:
- ❌
not supported in browsers without Promise.catch() - ❌ 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:
- ❌ Fullstory script blocked by ad blocker
- ❌ Script failed to load
- ❌ 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:
- ❌ Missing
configuration_fs_org - ❌ Unsupported browser
- ❌ Organization over quota
- ❌ Configuration error
Solutions:
- ✅ Check Fullstory setup
- ✅ Verify configuration
- ✅ Handle rejections gracefully
.catch() Not Working
Symptom: Errors not caught by
.catch()
Common Causes:
- ❌ Browser doesn't have native Promise
- ❌ Fullstory's Promise-like doesn't implement catch
Solutions:
- ✅ Use async/await with try/catch
- ✅ Use
with error callback.then()
KEY TAKEAWAYS FOR AGENT
When helping developers with Async Methods:
-
Always emphasize:
- Use timeouts to prevent hanging
- Handle rejections gracefully
- Don't block critical paths on FS
- Use try/catch, not .catch()
-
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
-
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?
-
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.