
fullstory-async-methods
by rcmaples
SKILL.md
name: fullstory-async-methods version: v2 description: 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. related_skills:
- fullstory-observe-callbacks
- fullstory-identify-users
- fullstory-analytics-events
- fullstory-capture-control
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/settingsorrec/pagecalls - 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:
- ❌ 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
_fs_orgconfiguration - ❌ 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
.then()with error callback
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.
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です