MeetingRouter

Best Practices for v2 API

@meeting-baas/sdk - v2 API Reference / v2-best-practices

Best Practices for v2 API

This guide covers recommended patterns and best practices for using the Meeting BaaS v2 API effectively.

Client Configuration

Use Environment Variables

import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: process.env.MEETING_BAAS_API_KEY!,
  api_version: "v2",
  timeout: 60000 // 60 seconds for long-running operations
});

Singleton Pattern

Create a single client instance and reuse it:

// lib/meeting-baas.ts
import { createBaasClient } from "@meeting-baas/sdk";

let clientInstance: ReturnType<typeof createBaasClient> | null = null;

export function getMeetingBaasClient() {
  if (!clientInstance) {
    clientInstance = createBaasClient({
      api_key: process.env.MEETING_BAAS_API_KEY!,
      api_version: "v2"
    });
  }
  return clientInstance;
}

Error Handling

Structured Error Handling

async function createBotSafely(meetingUrl: string, botName: string) {
  const result = await client.createBot({
    meeting_url: meetingUrl,
    bot_name: botName
  });

  if (!result.success) {
    // Log full error for debugging
    console.error("Failed to create bot:", {
      error: result.error,
      code: result.code,
      statusCode: result.statusCode,
      details: result.details
    });

    // Return user-friendly message
    throw new Error(getErrorMessage(result.code));
  }

  return result.data;
}

function getErrorMessage(code: string): string {
  const messages: Record<string, string> = {
    INVALID_MEETING_URL: "Please provide a valid meeting URL",
    RATE_LIMIT_EXCEEDED: "Too many requests. Please try again later",
    UNAUTHORIZED: "Invalid API key or insufficient permissions",
    BOT_ALREADY_IN_MEETING: "A bot is already in this meeting"
  };

  return messages[code] || "An unexpected error occurred";
}

Retry with Exponential Backoff

async function withRetry\<T\>(
  operation: () => Promise\<T\>,
  maxRetries = 3,
  baseDelay = 1000
): Promise\<T\> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: any) {
      const isLastAttempt = attempt === maxRetries;
      const shouldRetry = error.code === "RATE_LIMIT_EXCEEDED" ||
                         error.statusCode >= 500;

      if (!shouldRetry || isLastAttempt) {
        throw error;
      }

      const delay = baseDelay * Math.pow(2, attempt - 1);
      console.log(`Retry attempt ${attempt}/${maxRetries} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw new Error("Max retries exceeded");
}

// Usage
const bot = await withRetry(() =>
  client.createBot({
    meeting_url: "https://meet.google.com/abc-def-ghi",
    bot_name: "Assistant"
  })
);

Bot Management

Poll for Completion

async function waitForBotCompletion(
  botId: string,
  maxWaitMs = 3600000, // 1 hour
  pollIntervalMs = 5000 // 5 seconds
): Promise<V2.GetBotResponse> {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitMs) {
    const result = await client.getBot({ bot_id: botId });

    if (!result.success) {
      throw new Error(`Failed to get bot status: ${result.error}`);
    }

    const status = result.data.status;

    if (status === "completed" || status === "failed") {
      return result.data;
    }

    await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
  }

  throw new Error("Bot did not complete within timeout period");
}

Resource Cleanup

Always clean up resources:

async function processMeeting(meetingUrl: string) {
  let botId: string | null = null;

  try {
    // Create bot
    const createResult = await client.createBot({
      meeting_url: meetingUrl,
      bot_name: "Meeting Processor"
    });

    if (!createResult.success) {
      throw new Error(createResult.error);
    }

    botId = createResult.data.bot_id;

    // Wait for completion
    const bot = await waitForBotCompletion(botId);

    // Process data
    if (bot.mp4_url) {
      await downloadRecording(bot.mp4_url);
    }

    return { success: true, botId };

  } catch (error) {
    console.error("Error processing meeting:", error);
    return { success: false, error };

  } finally {
    // Clean up bot data if needed
    if (botId) {
      await client.deleteBotData({ bot_id: botId }).catch(err => {
        console.error("Failed to delete bot data:", err);
      });
    }
  }
}

Batch Operations

Process in Chunks

async function createBotsInChunks(
  meetings: Array<{ url: string; name: string }>,
  chunkSize = 10
) {
  const chunks = [];
  for (let i = 0; i < meetings.length; i += chunkSize) {
    chunks.push(meetings.slice(i, i + chunkSize));
  }

  const results = [];

  for (const chunk of chunks) {
    const batchResult = await client.batchCreateBots({
      bots: chunk.map(m => ({
        meeting_url: m.url,
        bot_name: m.name
      }))
    });

    if (batchResult.success) {
      results.push(...batchResult.data);

      if (batchResult.errors && batchResult.errors.length > 0) {
        console.error(`Failed to create ${batchResult.errors.length} bots in chunk`);
        batchResult.errors.forEach(err => {
          console.error(`  - ${err.error} (${err.code})`);
        });
      }
    }

    // Rate limiting: wait between chunks
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  return results;
}

TypeScript Best Practices

Use Type Guards

import type { V2 } from "@meeting-baas/sdk";

function isBotCompleted(bot: V2.GetBotResponse): bot is V2.GetBotResponse & { status: "completed" } {
  return bot.status === "completed";
}

async function processBotIfCompleted(botId: string) {
  const result = await client.getBot({ bot_id: botId });

  if (!result.success) {
    return;
  }

  if (isBotCompleted(result.data)) {
    // TypeScript knows status is "completed" here
    console.log("Processing completed bot:", result.data.bot_id);

    if (result.data.mp4_url) {
      await downloadRecording(result.data.mp4_url);
    }
  }
}

Leverage Discriminated Unions

async function handleBotOperation() {
  const result = await client.createBot({
    meeting_url: "https://meet.google.com/abc",
    bot_name: "Test"
  });

  // Type narrowing with success field
  if (result.success) {
    // TypeScript knows result.data exists
    console.log("Bot ID:", result.data.bot_id);
  } else {
    // TypeScript knows result.error, code, statusCode exist
    console.error("Error:", result.error);
    console.error("Code:", result.code);
  }
}

Calendar Integration

Sync Patterns

async function syncCalendarEvents(calendarId: string) {
  const now = new Date();
  const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);

  const result = await client.listCalendarEvents({
    calendar_id: calendarId,
    start_date_gte: now.toISOString(),
    start_date_lte: nextWeek.toISOString()
  });

  if (!result.success) {
    console.error("Failed to list events:", result.error);
    return [];
  }

  return result.data.events;
}

// Schedule regular syncs
setInterval(async () => {
  const calendars = await client.listCalendars();

  if (calendars.success) {
    for (const calendar of calendars.data.calendars) {
      await syncCalendarEvents(calendar.uuid);
    }
  }
}, 5 * 60 * 1000); // Every 5 minutes

Security

Validate Input

import { z } from "zod";

const meetingUrlSchema = z.string().url().refine(
  (url) => {
    const validDomains = [
      'meet.google.com',
      'zoom.us',
      'teams.microsoft.com'
    ];
    try {
      const hostname = new URL(url).hostname;
      return validDomains.some(domain => hostname.includes(domain));
    } catch {
      return false;
    }
  },
  { message: "Invalid meeting platform URL" }
);

async function createBotWithValidation(meetingUrl: string, botName: string) {
  // Validate input
  const urlValidation = meetingUrlSchema.safeParse(meetingUrl);

  if (!urlValidation.success) {
    throw new Error(`Invalid meeting URL: ${urlValidation.error.message}`);
  }

  // Proceed with API call
  return await client.createBot({
    meeting_url: meetingUrl,
    bot_name: botName
  });
}

Never Log Sensitive Data

function safeLog(operation: string, data: any) {
  const sanitized = { ...data };

  // Remove sensitive fields
  delete sanitized.api_key;
  delete sanitized.oauth_client_secret;
  delete sanitized.oauth_refresh_token;

  console.log(`[${operation}]`, sanitized);
}

// Usage
safeLog("Creating bot", { meeting_url, bot_name });

Testing

Mock API Responses

import { vi } from "vitest";

// Mock successful response
const mockClient = {
  createBot: vi.fn().mockResolvedValue({
    success: true,
    data: {
      bot_id: "test-bot-id",
      status: "joining",
      meeting_url: "https://meet.google.com/abc"
    }
  })
};

// Test your code
test("creates bot successfully", async () => {
  const result = await mockClient.createBot({
    meeting_url: "https://meet.google.com/abc",
    bot_name: "Test Bot"
  });

  expect(result.success).toBe(true);
  expect(result.data.bot_id).toBe("test-bot-id");
});

Performance

Parallel Requests

async function getBotDetails(botIds: string[]) {
  // Execute requests in parallel
  const results = await Promise.allSettled(
    botIds.map(id => client.getBot({ bot_id: id }))
  );

  const bots = results
    .filter((r): r is PromiseFulfilledResult<any> =>
      r.status === "fulfilled" && r.value.success
    )
    .map(r => r.value.data);

  return bots;
}

Caching

const botCache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getCachedBot(botId: string) {
  const cached = botCache.get(botId);
  const now = Date.now();

  if (cached && now - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const result = await client.getBot({ bot_id: botId });

  if (result.success) {
    botCache.set(botId, {
      data: result.data,
      timestamp: now
    });
    return result.data;
  }

  return null;
}

On this page