Cookbook

Real-world examples and integration patterns for common use cases.

Use Case 1: Automated Testing Environment

Spin up isolated browser instances for each test run, ensuring clean environments and parallel test execution.

Scenario

You need to run automated browser tests in parallel, with each test suite using a fresh, isolated environment to prevent interference.

// test-runner.js
import fetch from 'node-fetch';

const ORG_UUID = process.env.LEGBA_ORG_UUID;
const API_KEY = process.env.LEGBA_API_KEY;
const BASE_URL = `https://api.example.com/orgs/${ORG_UUID}/api`;

async function createTestInstance() {
  const response = await fetch(`${BASE_URL}/instances`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      image: 'ubuntu-22.04',
      size: 'small',
    }),
  });

  return await response.json();
}

async function destroyInstance(instanceUuid) {
  await fetch(`${BASE_URL}/instances/${instanceUuid}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${API_KEY}` },
  });
}

async function runTestSuite(testName) {
  console.log(`Starting test: ${testName}`);

  // Create isolated instance
  const instance = await createTestInstance();
  console.log(`Instance created: ${instance.instance_uuid}`);

  try {
    // Run your tests against instance.access_url
    // ... your test logic here ...

    console.log(`Test ${testName} completed successfully`);
  } finally {
    // Clean up instance
    await destroyInstance(instance.instance_uuid);
    console.log(`Instance ${instance.instance_uuid} destroyed`);
  }
}

// Run tests in parallel
Promise.all([
  runTestSuite('Login Flow'),
  runTestSuite('Checkout Process'),
  runTestSuite('User Profile'),
]).then(() => console.log('All tests completed'));

Use Case 2: On-Demand Secure Browsing

Provide users with secure, isolated browsing sessions on-demand for accessing untrusted websites or handling sensitive data.

Scenario

Your application needs to provide users with temporary, isolated browser sessions that automatically clean up after use.

// secure-session-service.js
class SecureSessionService {
  constructor(orgUuid, apiKey) {
    this.orgUuid = orgUuid;
    this.apiKey = apiKey;
    this.baseUrl = `https://api.example.com/orgs/${orgUuid}/api`;
  }

  async createSession(userId) {
    // Create instance
    const response = await fetch(`${this.baseUrl}/instances`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        image: 'ubuntu-20.04',
        size: 'medium',
      }),
    });

    const instance = await response.json();

    // Store mapping for cleanup
    await this.storeSession(userId, instance.instance_uuid);

    return {
      sessionId: instance.instance_uuid,
      accessUrl: instance.access_url,
      createdAt: instance.created_at,
    };
  }

  async endSession(sessionId) {
    // Destroy instance
    await fetch(`${this.baseUrl}/instances/${sessionId}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${this.apiKey}` },
    });

    // Remove from storage
    await this.removeSession(sessionId);
  }

  async cleanupExpiredSessions() {
    const expiredSessions = await this.getExpiredSessions();

    await Promise.all(
      expiredSessions.map(session => this.endSession(session.id))
    );
  }

  // Helper methods (implement with your database)
  async storeSession(userId, instanceId) { /* ... */ }
  async removeSession(instanceId) { /* ... */ }
  async getExpiredSessions() { /* ... */ }
}

// Usage
const service = new SecureSessionService(
  process.env.LEGBA_ORG_UUID,
  process.env.LEGBA_API_KEY
);

// Create session for user
const session = await service.createSession('user123');
console.log(`Access your secure browser: ${session.accessUrl}`);

// Later, end the session
await service.endSession(session.sessionId);

Use Case 3: Instance Pool Management

Maintain a pool of pre-warmed instances for instant access, optimizing for performance and user experience.

// instance-pool.js
class InstancePool {
  constructor(orgUuid, apiKey, poolSize = 5) {
    this.orgUuid = orgUuid;
    this.apiKey = apiKey;
    this.poolSize = poolSize;
    this.baseUrl = `https://api.example.com/orgs/${orgUuid}/api`;
    this.availableInstances = [];
    this.inUseInstances = new Set();
  }

  async initialize() {
    console.log(`Warming up pool with ${this.poolSize} instances...`);
    const promises = Array(this.poolSize)
      .fill(null)
      .map(() => this.createInstance());

    this.availableInstances = await Promise.all(promises);
    console.log('Pool ready!');
  }

  async createInstance() {
    const response = await fetch(`${this.baseUrl}/instances`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        image: 'ubuntu-22.04',
        size: 'small',
      }),
    });

    return await response.json();
  }

  async acquire() {
    if (this.availableInstances.length === 0) {
      // Pool empty, create new instance
      return await this.createInstance();
    }

    const instance = this.availableInstances.pop();
    this.inUseInstances.add(instance.instance_uuid);
    return instance;
  }

  async release(instanceUuid) {
    this.inUseInstances.delete(instanceUuid);

    // Destroy and replace with fresh instance
    await this.destroyInstance(instanceUuid);
    const newInstance = await this.createInstance();
    this.availableInstances.push(newInstance);
  }

  async destroyInstance(instanceUuid) {
    await fetch(`${this.baseUrl}/instances/${instanceUuid}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${this.apiKey}` },
    });
  }

  async shutdown() {
    console.log('Shutting down pool...');
    const allInstances = [
      ...this.availableInstances.map(i => i.instance_uuid),
      ...Array.from(this.inUseInstances),
    ];

    await Promise.all(
      allInstances.map(uuid => this.destroyInstance(uuid))
    );
    console.log('Pool shutdown complete');
  }
}

// Usage
const pool = new InstancePool(
  process.env.LEGBA_ORG_UUID,
  process.env.LEGBA_API_KEY,
  5
);

await pool.initialize();

// Get instance from pool
const instance = await pool.acquire();
console.log(`Using instance: ${instance.access_url}`);

// Return to pool when done
await pool.release(instance.instance_uuid);

Best Practices

Always Clean Up Instances

Use try/finally blocks to ensure instances are destroyed even if errors occur. This prevents resource leaks and unnecessary costs.

Store API Keys Securely

Never hardcode API keys. Use environment variables or secret management services.

Implement Retry Logic

Add exponential backoff for rate-limited requests and handle transient errors gracefully.

Monitor Usage

Regularly check audit logs to track API usage and detect anomalies.