Session Management Best Practices
Effective session management is crucial for optimizing performance, controlling costs, and ensuring security. This guide covers best practices for both Browser Sessions and Session API approaches.
Session Lifecycle Management
// Implement session lifecycle tracking
class SessionManager {
constructor() {
this.activeSessions = new Map();
}
async createSession(config) {
const response = await fetch('https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
const { sessionId } = await response.json();
this.activeSessions.set(sessionId, {
created: Date.now(),
ttl: config.ttl,
config
});
return sessionId;
}
async deleteSession(sessionId) {
if (this.activeSessions.has(sessionId)) {
await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`,
{ method: 'DELETE' }
);
this.activeSessions.delete(sessionId);
}
}
async cleanupExpiredSessions() {
const now = Date.now();
for (const [sessionId, info] of this.activeSessions) {
if (now - info.created > info.ttl) {
await this.deleteSession(sessionId);
}
}
}
async shutdown() {
// Clean up all active sessions
const deletePromises = Array.from(this.activeSessions.keys()).map(
sessionId => this.deleteSession(sessionId)
);
await Promise.all(deletePromises);
}
}
Error Handling
// Comprehensive error handling for session deletion
const safeDeleteSession = async (sessionId) => {
try {
// First check if session exists
const statusResponse = await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`
);
if (statusResponse.status === 404) {
console.log(`Session ${sessionId} already expired or deleted`);
return { success: true, reason: 'already_deleted' };
}
if (!statusResponse.ok) {
throw new Error(`Session status check failed: ${statusResponse.status}`);
}
// Proceed with deletion
const deleteResponse = await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`,
{ method: 'DELETE' }
);
if (deleteResponse.ok) {
const result = await deleteResponse.json();
return { success: true, result };
} else {
throw new Error(`Delete failed: ${deleteResponse.status} ${deleteResponse.statusText}`);
}
} catch (error) {
console.error(`Error deleting session ${sessionId}:`, error);
return { success: false, error: error.message };
}
};
Resource Optimization
- Delete sessions immediately after workflow completion to free resources
- Set appropriate timeouts based on actual workflow duration
- Monitor session usage to optimize costs and performance
- Implement cleanup routines for application shutdown
Security Considerations
- Clear sensitive data before ending sessions
- Rotate session identifiers regularly for long-running applications
- Log session deletions for audit trails
- Handle deletion failures gracefully to prevent resource leaks
Cost Management
Monitor Session Usage
// Track session costs and usage
const trackSessionUsage = async (sessionId) => {
const startTime = Date.now();
// Your session operations here
const endTime = Date.now();
const durationMinutes = (endTime - startTime) / 60000;
console.log(`Session ${sessionId} active for ${durationMinutes.toFixed(2)} minutes`);
// Estimated cost calculation (adjust based on your plan)
const costPerMinute = 0.01; // Example rate
const estimatedCost = durationMinutes * costPerMinute;
console.log(`Estimated cost: $${estimatedCost.toFixed(4)}`);
};
Optimization Tips
- Use browser sessions for simple workflows where automatic cleanup is sufficient
- Use Session API only when you need explicit control over session lifecycle
- Set conservative timeouts and extend only when necessary
- Clean up immediately after workflow completion
Next Steps
- Creating Sessions - Learn how to create new sessions
- Managing Sessions - Understand session connection and state management
- Closing Sessions - Learn proper session termination techniques
Implement proper session lifecycle tracking to monitor and control your browser sessions effectively.
Session Manager Pattern
// Implement session lifecycle tracking
class SessionManager {
constructor() {
this.activeSessions = new Map();
}
async createSession(config) {
const response = await fetch('https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
const { sessionId } = await response.json();
this.activeSessions.set(sessionId, {
created: Date.now(),
ttl: config.ttl,
config
});
return sessionId;
}
async deleteSession(sessionId) {
if (this.activeSessions.has(sessionId)) {
await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`,
{ method: 'DELETE' }
);
this.activeSessions.delete(sessionId);
}
}
async cleanupExpiredSessions() {
const now = Date.now();
for (const [sessionId, info] of this.activeSessions) {
if (now - info.created > info.ttl) {
await this.deleteSession(sessionId);
}
}
}
async shutdown() {
// Clean up all active sessions
const deletePromises = Array.from(this.activeSessions.keys()).map(
sessionId => this.deleteSession(sessionId)
);
await Promise.all(deletePromises);
}
}
Browser Session Timeout Management
- Adaptive Timeout
- Progressive Extension
- Timeout Monitoring
// Dynamically adjust timeout based on workflow complexity
const getOptimalTimeout = (workflowType) => {
const timeoutPresets = {
'simple-scrape': 30000, // 30 seconds
'form-submission': 60000, // 1 minute
'multi-page-flow': 180000, // 3 minutes
'complex-automation': 300000 // 5 minutes
};
return timeoutPresets[workflowType] || 60000; // Default 1 minute
};
const createOptimizedSession = async (workflowType) => {
const timeout = getOptimalTimeout(workflowType);
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN',
});
const page = await browser.newPage();
const cdp = await page.createCDPSession();
// Set up reconnection with optimized timeout
const { browserWSEndpoint } = await cdp.send('Browserless.reconnect', {
timeout,
});
return { browser, page, reconnectEndpoint: browserWSEndpoint, timeout };
};
// Extend timeout progressively as needed
const progressiveSessionExtension = async (browser) => {
let currentTimeout = 60000; // Start with 1 minute
const extendSession = async (additionalTime) => {
currentTimeout += additionalTime;
// Get a new page and CDP session from current browser
const page = await browser.newPage();
const cdp = await page.createCDPSession();
// Set up new reconnection with extended timeout
const { browserWSEndpoint } = await cdp.send('Browserless.reconnect', {
timeout: currentTimeout,
});
return browserWSEndpoint;
};
return { extendSession, getCurrentTimeout: () => currentTimeout };
};
// Monitor timeout usage and optimize
const monitorTimeoutUsage = () => {
const sessionStats = new Map();
const trackSession = (sessionId, timeout) => {
sessionStats.set(sessionId, {
startTime: Date.now(),
plannedTimeout: timeout,
actualUsage: null
});
};
const recordSessionEnd = (sessionId) => {
const stats = sessionStats.get(sessionId);
if (stats) {
stats.actualUsage = Date.now() - stats.startTime;
sessionStats.set(sessionId, stats);
}
};
const getEfficiencyReport = () => {
const sessions = Array.from(sessionStats.values());
const avgPlanned = sessions.reduce((sum, s) => sum + s.plannedTimeout, 0) / sessions.length;
const avgActual = sessions.reduce((sum, s) => sum + (s.actualUsage || 0), 0) / sessions.length;
return {
averagePlannedTimeout: avgPlanned,
averageActualUsage: avgActual,
efficiency: avgActual / avgPlanned,
recommendation: avgActual < avgPlanned * 0.5 ? 'reduce timeout' : 'timeout appropriate'
};
};
return { trackSession, recordSessionEnd, getEfficiencyReport };
};
Error Handling and Resilience
Implement robust error handling to ensure graceful degradation and proper resource cleanup.
Safe Session Operations
// Comprehensive error handling for session deletion
const safeDeleteSession = async (sessionId) => {
try {
// First check if session exists
const statusResponse = await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`
);
if (statusResponse.status === 404) {
console.log(`Session ${sessionId} already expired or deleted`);
return { success: true, reason: 'already_deleted' };
}
if (!statusResponse.ok) {
throw new Error(`Session status check failed: ${statusResponse.status}`);
}
// Proceed with deletion
const deleteResponse = await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`,
{ method: 'DELETE' }
);
if (deleteResponse.ok) {
const result = await deleteResponse.json();
return { success: true, result };
} else {
throw new Error(`Delete failed: ${deleteResponse.status} ${deleteResponse.statusText}`);
}
} catch (error) {
console.error(`Error deleting session ${sessionId}:`, error);
return { success: false, error: error.message };
}
};
Connection Retry Logic
- Connection Retry
- Circuit Breaker
// Retry session connection with exponential backoff
const connectWithRetry = async (endpoint, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const browser = await puppeteer.connect({
browserWSEndpoint: endpoint,
});
// Test connection
await browser.newPage();
return browser;
} catch (error) {
console.log(`Connection attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Failed to connect after ${maxRetries} attempts: ${error.message}`);
}
// Exponential backoff
const delay = 1000 * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
// Circuit breaker pattern for session operations
class SessionCircuitBreaker {
constructor(threshold = 5, resetTime = 60000) {
this.failureThreshold = threshold;
this.resetTime = resetTime;
this.failures = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.lastFailureTime = null;
}
async execute(operation) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.resetTime) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
}
}
}
// Usage
const circuitBreaker = new SessionCircuitBreaker();
const createSessionSafely = async (config) => {
return circuitBreaker.execute(async () => {
const response = await fetch('https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
if (!response.ok) {
throw new Error(`Session creation failed: ${response.status}`);
}
return response.json();
});
};
Resource Optimization
Optimize resource usage to control costs and improve performance.
Cost Management
// Track session costs and usage
const trackSessionUsage = async (sessionId) => {
const startTime = Date.now();
// Your session operations here
const endTime = Date.now();
const durationMinutes = (endTime - startTime) / 60000;
console.log(`Session ${sessionId} active for ${durationMinutes.toFixed(2)} minutes`);
// Estimated cost calculation (adjust based on your plan)
const costPerMinute = 0.01; // Example rate
const estimatedCost = durationMinutes * costPerMinute;
console.log(`Estimated cost: $${estimatedCost.toFixed(4)}`);
};
// Budget-aware session management
class BudgetAwareSessionManager {
constructor(dailyBudget = 10.00) {
this.dailyBudget = dailyBudget;
this.dailySpend = 0;
this.lastResetDate = new Date().toDateString();
}
checkBudget() {
const today = new Date().toDateString();
if (today !== this.lastResetDate) {
this.dailySpend = 0;
this.lastResetDate = today;
}
return this.dailySpend < this.dailyBudget;
}
recordCost(amount) {
this.dailySpend += amount;
}
async createSessionIfBudgetAllows(config) {
if (!this.checkBudget()) {
throw new Error('Daily budget exceeded');
}
// Proceed with session creation
return this.createSession(config);
}
}
Memory and Performance
- Memory Cleanup
- Connection Pooling
// Comprehensive cleanup to prevent memory leaks
const performCleanup = async (page) => {
try {
// Clear large objects from memory
await page.evaluate(() => {
// Clear intervals and timeouts
const highestTimeoutId = setTimeout(() => {}, 0);
for (let i = 0; i < highestTimeoutId; i++) {
clearTimeout(i);
clearInterval(i);
}
// Clear large variables
window.largeDataStructures = null;
window.cachedResults = null;
// Force garbage collection (if available)
if (window.gc) {
window.gc();
}
});
// Clear browser cache
const client = await page.target().createCDPSession();
await client.send('Network.clearBrowserCache');
await client.send('Network.clearBrowserCookies');
} catch (error) {
console.warn('Cleanup error (non-critical):', error.message);
}
};
// Simple connection pooling for Session API
class SessionPool {
constructor(maxSessions = 5) {
this.maxSessions = maxSessions;
this.availableSessions = [];
this.activeSessions = new Set();
}
async acquireSession(config) {
// Try to reuse existing session
if (this.availableSessions.length > 0) {
const sessionId = this.availableSessions.pop();
this.activeSessions.add(sessionId);
return sessionId;
}
// Create new session if under limit
if (this.activeSessions.size < this.maxSessions) {
const response = await fetch('https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
const { sessionId } = await response.json();
this.activeSessions.add(sessionId);
return sessionId;
}
throw new Error('Session pool exhausted');
}
releaseSession(sessionId) {
this.activeSessions.delete(sessionId);
this.availableSessions.push(sessionId);
}
async cleanup() {
const allSessions = [...this.activeSessions, ...this.availableSessions];
const deletePromises = allSessions.map(sessionId =>
fetch(`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`, {
method: 'DELETE'
})
);
await Promise.all(deletePromises);
this.availableSessions = [];
this.activeSessions.clear();
}
}
Security Best Practices
Implement security measures to protect sensitive data and prevent unauthorized access.
Data Protection
- Sensitive Data Handling
- Session Rotation
- Audit Logging
// Secure handling of sensitive session data
const secureSessionCleanup = async (page) => {
await page.evaluate(() => {
// Clear sensitive data from localStorage
const sensitiveKeys = [
'authToken', 'accessToken', 'refreshToken',
'password', 'creditCard', 'ssn',
'userCredentials', 'paymentInfo', 'personalData'
];
sensitiveKeys.forEach(key => {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
});
// Clear form data
document.querySelectorAll('input[type="password"], input[type="email"]').forEach(input => {
input.value = '';
});
// Clear any global variables that might contain sensitive data
if (window.userData) window.userData = null;
if (window.sessionData) window.sessionData = null;
});
// Clear cookies containing sensitive data
const cookies = await page.cookies();
const sensitiveCookies = cookies.filter(cookie =>
/auth|token|session|login/i.test(cookie.name)
);
if (sensitiveCookies.length > 0) {
await page.deleteCookie(...sensitiveCookies);
}
};
// Rotate sessions for security
class SecureSessionManager {
constructor(rotationInterval = 1800000) { // 30 minutes
this.rotationInterval = rotationInterval;
this.sessionCreationTime = new Map();
}
async createSession(config) {
const sessionId = await this.baseCreateSession(config);
this.sessionCreationTime.set(sessionId, Date.now());
return sessionId;
}
shouldRotate(sessionId) {
const creationTime = this.sessionCreationTime.get(sessionId);
return creationTime && (Date.now() - creationTime) > this.rotationInterval;
}
async rotateIfNeeded(sessionId) {
if (this.shouldRotate(sessionId)) {
console.log(`Rotating session ${sessionId} for security`);
// Create new session
const newSessionId = await this.createSession({
ttl: 300000, // 5 minutes default
stealth: true
});
// Clean up old session
await this.deleteSession(sessionId);
return newSessionId;
}
return sessionId;
}
}
// Audit session operations for security monitoring
class SessionAuditor {
constructor() {
this.auditLog = [];
}
logOperation(operation, sessionId, details = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
operation,
sessionId,
details,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A'
};
this.auditLog.push(logEntry);
// Send to monitoring system (implementation depends on your setup)
this.sendToMonitoring(logEntry);
}
async sendToMonitoring(logEntry) {
try {
// Example: send to your monitoring endpoint
await fetch('/api/audit-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
});
} catch (error) {
console.error('Failed to send audit log:', error);
}
}
getAuditTrail(sessionId) {
return this.auditLog.filter(entry => entry.sessionId === sessionId);
}
}
// Usage in session operations
const auditor = new SessionAuditor();
const auditedCreateSession = async (config) => {
const sessionId = await createSession(config);
auditor.logOperation('CREATE', sessionId, { config });
return sessionId;
};
const auditedDeleteSession = async (sessionId) => {
const result = await deleteSession(sessionId);
auditor.logOperation('DELETE', sessionId, { result });
return result;
};
Monitoring and Alerting
Set up monitoring to track session health and performance.
Health Checks
// Monitor session health
const performHealthCheck = async (sessionId) => {
try {
const response = await fetch(
`https://production-sfo.browserless.io/session/${sessionId}?token=YOUR_API_TOKEN`
);
if (response.ok) {
const sessionInfo = await response.json();
return {
healthy: true,
sessionInfo,
uptime: Date.now() - new Date(sessionInfo.created).getTime()
};
} else {
return {
healthy: false,
status: response.status,
message: response.statusText
};
}
} catch (error) {
return {
healthy: false,
error: error.message
};
}
};
// Automated health monitoring
const startHealthMonitoring = (sessionIds, intervalMs = 30000) => {
const healthChecks = sessionIds.map(sessionId => {
return setInterval(async () => {
const health = await performHealthCheck(sessionId);
if (!health.healthy) {
console.warn(`Session ${sessionId} unhealthy:`, health);
// Trigger alerts or remediation
}
}, intervalMs);
});
return () => healthChecks.forEach(clearInterval);
};
Optimization Guidelines
When to Use Browser Sessions vs Session API
Scenario | Recommendation | Reason |
---|---|---|
Simple scraping workflows | Browser Sessions | Automatic cleanup, simpler setup |
Short-duration tasks (< 5 min) | Browser Sessions | Reconnection timeout handles lifecycle efficiently |
Multi-step workflows | Session API | Better control over session state |
Production applications | Session API | Explicit lifecycle management |
Batch processing | Session API | Programmatic session control |
Development/testing | Browser Sessions | Faster iteration, less complexity |
Timeout Optimization
- Conservative approach: Start with shorter timeouts and increase as needed
- Monitor actual usage: Track how long sessions are actually needed
- Plan-based limits: Respect your plan's maximum timeout limits
- Workflow-based: Different timeouts for different workflow types
Resource Management
- Delete sessions immediately after workflow completion to free resources
- Set appropriate timeouts based on actual workflow duration
- Monitor session usage to optimize costs and performance
- Implement cleanup routines for application shutdown
Security Guidelines
- Clear sensitive data before ending sessions
- Rotate session identifiers regularly for long-running applications
- Log session deletions for audit trails
- Handle deletion failures gracefully to prevent resource leaks
Next Steps
- Creating Sessions - Learn how to create and connect to sessions
- Managing Sessions - Understand session reconnection and state management
- Closing Sessions - Learn proper session termination techniques