SDK & Integration

Integrate SimplyStream into your applications with our SDKs and embedding options

JavaScript/TypeScript SDK

Use the official SDK for web applications

JavaScript/TypeScript SDK

Status: @simplystream/sdk exists today as a monorepo workspace package (packages/simplystream-sdk) covering the multiplayer API — auth, friends, presence, sessions/lobbies, realtime session events, matchmaking, and leaderboards. See its README for real usage. It is not yet published to npm, and the streaming/platform surface documented below is still aspirational — the examples beneath this notice describe a planned API.

The SimplyStream JavaScript/TypeScript SDK provides a powerful, type-safe way to integrate streaming functionality into your web applications.

Installation

Install the SDK via npm or yarn:

npm install @simplystream/sdk   # not yet published — see status note above
# or
yarn add @simplystream/sdk

Quick Start

import { SimplyStreamClient } from '@simplystream/sdk';

// Initialize the client
const client = new SimplyStreamClient({
	apiKey: 'your-api-key',
	projectId: 'your-project-id'
});

// Start a streaming session
const session = await client.sessions.create({
	buildId: 'your-build-id',
	region: 'us-west-2'
});

console.log('Session URL:', session.url);

Configuration Options

The SDK accepts several configuration options:

Option Type Required Description
apiKey string Yes Your API authentication key
projectId string Yes The project identifier
environment string No Environment (production, staging, development)
timeout number No Request timeout in milliseconds (default: 30000)

Core Methods

Sessions

Create and manage streaming sessions:

// Create a session
const session = await client.sessions.create({
	buildId: 'build-123',
	region: 'us-west-2',
	maxDuration: 3600, // 1 hour in seconds
	metadata: {
		userId: 'user-456',
		campaign: 'demo'
	}
});

// Get session status
const status = await client.sessions.get(session.id);

// Terminate a session
await client.sessions.terminate(session.id);

// List active sessions
const sessions = await client.sessions.list({
	status: 'active',
	limit: 10
});

Builds

Manage your application builds:

// Upload a new build
const build = await client.builds.upload({
	file: buildFile,
	name: 'MyApp v1.2.0',
	platform: 'windows'
});

// Get build information
const buildInfo = await client.builds.get('build-123');

// List builds
const builds = await client.builds.list({
	limit: 20,
	offset: 0
});

Projects

Access project information and settings:

// Get project details
const project = await client.projects.get('project-123');

// Update project settings
await client.projects.update('project-123', {
	name: 'Updated Project Name',
	defaultRegion: 'eu-west-1'
});

// Get project analytics
const analytics = await client.projects.getAnalytics('project-123', {
	startDate: '2025-01-01',
	endDate: '2025-01-31'
});

Event Handling

Listen to real-time events from your streaming sessions:

// Subscribe to session events
client.on('session.created', (session) => {
	console.log('New session started:', session.id);
});

client.on('session.ready', (session) => {
	console.log('Session is ready for users:', session.url);
});

client.on('session.ended', (session) => {
	console.log('Session ended:', session.id);
	console.log('Duration:', session.duration);
});

client.on('error', (error) => {
	console.error('SDK error:', error);
});

Error Handling

The SDK uses typed errors for better error handling:

import { SimplyStreamError, AuthenticationError, RateLimitError } from '@simplystream/sdk';

try {
	const session = await client.sessions.create({ buildId: 'build-123' });
} catch (error) {
	if (error instanceof AuthenticationError) {
		console.error('Invalid API key');
	} else if (error instanceof RateLimitError) {
		console.error('Rate limit exceeded, retry after:', error.retryAfter);
	} else if (error instanceof SimplyStreamError) {
		console.error('API error:', error.message);
	} else {
		console.error('Unexpected error:', error);
	}
}

TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

import type { Session, Build, Project, CreateSessionOptions } from '@simplystream/sdk';

// TypeScript will provide autocomplete and type checking
const options: CreateSessionOptions = {
	buildId: 'build-123',
	region: 'us-west-2',
	maxDuration: 3600
};

const session: Session = await client.sessions.create(options);

Browser Support

The SDK works in all modern browsers:

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Next Steps

Player SDK

Cloud saves, playtime, leaderboards, achievements, stats, and events for launched games

Player SDK

The Player SDK is available inside games launched through the SimplyStream launcher. GameGhost exposes it as window.Gameghost; the launcher forwards those calls to SimplyStream using the current launch token and session id.

Identity

For GameGhost launches, the browser calls the GameGhost backend first. GameGhost signs a short-lived player payload with GAMEGHOST_SIGNING_SECRET, then SimplyStream verifies that payload before storing the player identity on the launch token.

Configure the same GAMEGHOST_SIGNING_SECRET in both services for production. Without that secret, local development falls back to legacy anonymous or header-based identity.

Cloud Saves

Cloud save list calls return metadata only. Load a slot to retrieve payload data.

Each save has a monotonically increasing version. Pass expectedVersion when writing after a load to avoid overwriting a newer save from another device.

const gameSlug = 'my-game';

const existing = await window.Gameghost.cloudSaves.get(gameSlug, 'autosave').catch(() => null);
const expectedVersion = existing?.save?.version;

const result = await window.Gameghost.cloudSaves.put(
	gameSlug,
	'autosave',
	{ checkpoint: 7, inventory: ['keycard'] },
	{ area: 'orbital-lab' },
	{ expectedVersion }
);

console.log(result.save.version);

If the version is stale, the SDK request returns a conflict response instead of replacing the save.

Playtime

await window.Gameghost.playtime.launch(gameSlug);
await window.Gameghost.playtime.heartbeat(gameSlug, 60);
await window.Gameghost.playtime.end(gameSlug, 12);

const playtime = await window.Gameghost.playtime.get(gameSlug);
console.log(playtime.stats.totalSeconds);

Leaderboards

await window.Gameghost.leaderboards.submit(gameSlug, 12345, 'global', { difficulty: 'hard' });
const board = await window.Gameghost.leaderboards.list(gameSlug, 'global');

Achievements, Stats, And Events

await window.Gameghost.achievements.unlock(gameSlug, 'first_escape', {
	achievementName: 'First Escape',
	points: 10
});

await window.Gameghost.stats.update(gameSlug, 'runs_completed', 1, 'increment');
await window.Gameghost.events.track(gameSlug, 'boss.defeated', { boss: 'warden' });

Limits

Cloud save payloads are limited to 2 MB per slot. Save metadata is limited to 16 KB. Store large binary data in your own asset system and keep only compact game state in cloud saves.

Embedding Streaming Applications

Embed streaming content in your website

Embedding Streaming Applications

Embed SimplyStream applications directly into your website or web application using our embedding SDK and iframe integration.

Embedding with the SDK

The easiest way to embed streaming content is using the SimplyStream SDK:

import { SimplyStreamEmbed } from '@simplystream/sdk';

// Create an embed instance
const embed = new SimplyStreamEmbed({
	container: '#stream-container', // CSS selector or HTMLElement
	sessionId: 'session-123',
	// Optional configuration
	width: '100%',
	height: '720px',
	autoplay: true,
	controls: true
});

// Load and start the stream
await embed.load();

// Control the embed
embed.play();
embed.pause();
embed.setVolume(0.8);
embed.toggleFullscreen();

// Listen to events
embed.on('ready', () => {
	console.log('Stream is ready');
});

embed.on('play', () => {
	console.log('Stream started playing');
});

embed.on('ended', () => {
	console.log('Stream ended');
});

// Clean up when done
embed.destroy();

Iframe Embedding

For simpler use cases, you can embed using a standard iframe:

<iframe
	src="https://stream.simplystream.io/session/session-123"
	width="1280"
	height="720"
	frameborder="0"
	allow="autoplay; fullscreen; microphone; camera; clipboard-write"
	allowfullscreen
></iframe>

Iframe Parameters

Customize the embed behavior with URL parameters:

<iframe
	src="https://stream.simplystream.io/session/session-123?autoplay=true&controls=false&quality=high"
></iframe>

Available parameters:

Parameter Type Default Description
autoplay boolean false Auto-start the stream
controls boolean true Show player controls
quality string 'auto' Stream quality (low, medium, high, auto)
muted boolean false Start with audio muted
loop boolean false Loop the stream when it ends

Responsive Embedding

Make your embeds responsive with CSS:

<div class="stream-wrapper">
	<div class="stream-container" id="stream-container"></div>
</div>

<style>
	.stream-wrapper {
		position: relative;
		padding-bottom: 56.25%; /* 16:9 aspect ratio */
		height: 0;
		overflow: hidden;
	}

	.stream-container {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
	}
</style>

Advanced Embed Configuration

Custom UI Controls

Build your own player controls:

const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123',
	controls: false // Hide default controls
});

// Create custom controls
document.getElementById('play-btn').addEventListener('click', () => {
	embed.play();
});

document.getElementById('pause-btn').addEventListener('click', () => {
	embed.pause();
});

document.getElementById('fullscreen-btn').addEventListener('click', () => {
	embed.toggleFullscreen();
});

// Volume slider
document.getElementById('volume-slider').addEventListener('input', (e) => {
	embed.setVolume(e.target.value / 100);
});

// Get current state
const state = embed.getState();
console.log('Playing:', state.playing);
console.log('Volume:', state.volume);
console.log('Current time:', state.currentTime);

Quality Selection

Allow users to select stream quality:

embed.on('ready', async () => {
	const qualities = await embed.getAvailableQualities();
	console.log('Available qualities:', qualities);
	// ['360p', '720p', '1080p', '4K']

	// Set quality
	embed.setQuality('1080p');
});

embed.on('qualityChanged', (quality) => {
	console.log('Quality changed to:', quality);
});

Input Handling

Forward user input to the streaming application:

// Enable input forwarding
embed.setInputMode('keyboard-mouse');

// Custom input handling
embed.on('input', (event) => {
	console.log('Input event:', event);
});

// Send custom input
embed.sendInput({
	type: 'keyboard',
	key: 'Enter',
	action: 'press'
});

embed.sendInput({
	type: 'mouse',
	x: 100,
	y: 200,
	button: 'left',
	action: 'click'
});

Multi-User Sessions

Embed collaborative sessions with multiple users:

const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123',
	multiUser: true,
	userId: 'user-456'
});

// Get active users
embed.on('usersChanged', (users) => {
	console.log('Active users:', users);
	users.forEach((user) => {
		console.log(`${user.name} - ${user.role}`);
	});
});

// Send messages to other users
embed.sendMessage({
	type: 'chat',
	text: 'Hello everyone!'
});

embed.on('message', (message) => {
	console.log(`${message.from}: ${message.text}`);
});

Performance Optimization

Lazy Loading

Load the embed only when needed:

// Create embed but don't load yet
const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123',
	autoload: false
});

// Load when user clicks
document.getElementById('start-btn').addEventListener('click', async () => {
	await embed.load();
	embed.play();
});

Preloading

Preload the stream for faster startup:

// Preload in the background
SimplyStreamEmbed.preload('session-123');

// Later, create and use the embed
const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123'
});
// Will start faster because it's preloaded

Security Considerations

Content Security Policy (CSP)

Add these directives to your CSP header:

Content-Security-Policy:
  frame-src https://stream.simplystream.io;
  connect-src https://api.simplystream.io wss://stream.simplystream.io;

Secure Embeds

Use signed URLs for private content:

// Backend: Generate signed URL
const signedUrl = simplystream.sessions.getSignedUrl('session-123', {
	expiresIn: 3600, // 1 hour
	permissions: ['view']
});

// Frontend: Use signed URL
const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	url: signedUrl
});

Examples

React Component

import { useEffect, useRef } from 'react';
import { SimplyStreamEmbed } from '@simplystream/sdk';

function StreamPlayer({ sessionId }) {
  const containerRef = useRef(null);
  const embedRef = useRef(null);

  useEffect(() => {
    embedRef.current = new SimplyStreamEmbed({
      container: containerRef.current,
      sessionId,
      autoplay: true
    });

    embedRef.current.load();

    return () => {
      embedRef.current?.destroy();
    };
  }, [sessionId]);

  return <div ref={containerRef} className="stream-player" />;
}

Vue Component

<template>
	<div ref="container" class="stream-player"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { SimplyStreamEmbed } from '@simplystream/sdk';

const props = defineProps(['sessionId']);
const container = ref(null);
let embed = null;

onMounted(async () => {
	embed = new SimplyStreamEmbed({
		container: container.value,
		sessionId: props.sessionId,
		autoplay: true
	});

	await embed.load();
});

onUnmounted(() => {
	embed?.destroy();
});
</script>

Next Steps

Webhooks

Receive real-time events from SimplyStream

Webhooks

Webhooks allow you to receive real-time HTTP notifications when events occur in your SimplyStream projects.

Overview

When an event occurs (like a session starting or ending), SimplyStream sends an HTTP POST request to your configured webhook URL with details about the event.

Setting Up Webhooks

Via Dashboard

  1. Go to your Project Settings
  2. Navigate to the "Webhooks" section
  3. Click "Add Webhook"
  4. Enter your webhook URL and select events
  5. Save your configuration

Via API

import { SimplyStreamClient } from '@simplystream/sdk';

const client = new SimplyStreamClient({ apiKey: 'your-api-key' });

const webhook = await client.webhooks.create({
	url: 'https://your-app.com/webhooks/simplystream',
	events: ['session.created', 'session.ended'],
	secret: 'your-webhook-secret'
});

Available Events

SimplyStream supports the following webhook events:

Session Events

Event Description
session.created A new session was created
session.started A session started streaming
session.ready Session is ready for users to connect
session.ended A session ended normally
session.failed A session failed to start or crashed
session.user.joined A user joined a session
session.user.left A user left a session

Build Events

Event Description
build.uploaded A new build was uploaded
build.processing Build is being processed
build.ready Build is ready for deployment
build.failed Build processing failed

Project Events

Event Description
project.created A new project was created
project.updated Project settings were updated
project.deleted A project was deleted

Webhook Payload

All webhooks include a standard payload structure:

{
	"id": "evt_1a2b3c4d5e6f",
	"type": "session.ended",
	"created": "2025-01-08T10:30:00Z",
	"data": {
		"object": {
			"id": "session-123",
			"projectId": "project-456",
			"buildId": "build-789",
			"status": "ended",
			"startedAt": "2025-01-08T10:00:00Z",
			"endedAt": "2025-01-08T10:30:00Z",
			"duration": 1800,
			"region": "us-west-2",
			"metadata": {
				"userId": "user-123"
			}
		}
	}
}

Payload Structure

Field Type Description
id string Unique event identifier
type string Event type (e.g., "session.ended")
created string ISO 8601 timestamp when event occurred
data.object object The resource that triggered the event

Handling Webhooks

Express.js Example

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/simplystream', (req, res) => {
	// Verify webhook signature
	const signature = req.headers['x-simplystream-signature'];
	const secret = process.env.WEBHOOK_SECRET;

	const expectedSignature = crypto
		.createHmac('sha256', secret)
		.update(JSON.stringify(req.body))
		.digest('hex');

	if (signature !== expectedSignature) {
		return res.status(401).send('Invalid signature');
	}

	// Process the webhook
	const event = req.body;

	switch (event.type) {
		case 'session.created':
			handleSessionCreated(event.data.object);
			break;
		case 'session.ended':
			handleSessionEnded(event.data.object);
			break;
		default:
			console.log(`Unhandled event type: ${event.type}`);
	}

	// Acknowledge receipt
	res.status(200).send('Webhook received');
});

function handleSessionCreated(session) {
	console.log('Session created:', session.id);
	// Send notification to user
	// Update database
	// Start monitoring
}

function handleSessionEnded(session) {
	console.log('Session ended:', session.id);
	console.log('Duration:', session.duration, 'seconds');
	// Bill the user
	// Archive session data
	// Send analytics
}

app.listen(3000);

Next.js API Route

import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

export async function POST(request: NextRequest) {
	const signature = request.headers.get('x-simplystream-signature');
	const body = await request.text();

	// Verify signature
	const expectedSignature = crypto
		.createHmac('sha256', process.env.WEBHOOK_SECRET!)
		.update(body)
		.digest('hex');

	if (signature !== expectedSignature) {
		return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
	}

	const event = JSON.parse(body);

	// Process event
	switch (event.type) {
		case 'session.created':
			await handleSessionCreated(event.data.object);
			break;
		case 'session.ended':
			await handleSessionEnded(event.data.object);
			break;
	}

	return NextResponse.json({ received: true });
}

async function handleSessionCreated(session: any) {
	// Your logic here
	console.log('Session created:', session.id);
}

async function handleSessionEnded(session: any) {
	// Your logic here
	console.log('Session ended:', session.id);
}

Security

Verifying Webhook Signatures

Always verify webhook signatures to ensure requests are from SimplyStream:

import crypto from 'crypto';

function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
	const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex');

	return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
}

// Usage
const isValid = verifyWebhookSignature(
	JSON.stringify(req.body),
	req.headers['x-simplystream-signature'],
	process.env.WEBHOOK_SECRET
);

if (!isValid) {
	throw new Error('Invalid webhook signature');
}

Webhook Secret

When creating a webhook, you'll receive a secret key. Store this securely:

# .env
WEBHOOK_SECRET=whsec_1a2b3c4d5e6f7g8h9i0j

Never commit secrets to version control or expose them in client-side code.

Best Practices

1. Respond Quickly

Respond to webhooks within 5 seconds to avoid timeouts:

app.post('/webhooks', async (req, res) => {
	// Immediately acknowledge receipt
	res.status(200).send('Webhook received');

	// Process asynchronously
	processWebhookAsync(req.body).catch((error) => {
		console.error('Webhook processing error:', error);
	});
});

async function processWebhookAsync(event) {
	// Time-consuming processing here
	await updateDatabase(event);
	await sendNotifications(event);
	await updateAnalytics(event);
}

2. Handle Duplicate Events

Webhooks may be sent multiple times. Use the event ID to deduplicate:

const processedEvents = new Set();

function handleWebhook(event) {
	if (processedEvents.has(event.id)) {
		console.log('Duplicate event, skipping:', event.id);
		return;
	}

	processedEvents.add(event.id);
	// Process event
}

3. Implement Retry Logic

If your webhook endpoint is down, SimplyStream will retry:

  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 30 minutes
  • Retry 4: After 2 hours
  • Retry 5: After 6 hours

4. Monitor Webhook Health

Track webhook delivery success:

await client.webhooks.getDeliveryStats('webhook-123', {
  startDate: '2025-01-01',
  endDate: '2025-01-31'
});

// Returns:
{
  total: 1000,
  successful: 995,
  failed: 5,
  averageLatency: 150 // ms
}

Testing Webhooks

Local Development

Use tools like ngrok to expose your local server:

# Install ngrok
npm install -g ngrok

# Expose your local port
ngrok http 3000

# Use the ngrok URL as your webhook URL
https://abc123.ngrok.io/webhooks/simplystream

Manual Testing

Trigger test events from the dashboard or API:

await client.webhooks.sendTestEvent('webhook-123', {
	type: 'session.created'
});

Webhook Logs

View webhook delivery logs in your dashboard:

  • Request/response details
  • Delivery timestamps
  • Failure reasons
  • Response times

Example Use Cases

Usage Tracking

async function handleSessionEnded(session) {
	const usage = {
		userId: session.metadata.userId,
		sessionId: session.id,
		duration: session.duration,
		cost: calculateCost(session.duration, session.region),
		timestamp: session.endedAt
	};

	await database.usageRecords.create(usage);
}

User Notifications

async function handleSessionReady(session) {
	const userId = session.metadata.userId;
	const user = await database.users.findById(userId);

	await sendEmail({
		to: user.email,
		subject: 'Your stream is ready!',
		body: `Your streaming session is ready: ${session.url}`
	});
}

Analytics

async function handleSessionEnded(session) {
	await analytics.track({
		event: 'Session Ended',
		userId: session.metadata.userId,
		properties: {
			sessionId: session.id,
			duration: session.duration,
			region: session.region,
			quality: session.metadata.quality
		}
	});
}

Troubleshooting

Webhook Not Receiving Events

  1. Verify your webhook URL is publicly accessible
  2. Check your firewall allows incoming requests
  3. Ensure your server responds with 2xx status code
  4. Review webhook logs in the dashboard

Signature Verification Fails

  1. Verify you're using the correct webhook secret
  2. Ensure you're hashing the raw request body
  3. Use timing-safe comparison for signatures
  4. Check for any middleware modifying the body

Events Arrive Out of Order

Events may arrive out of order. Use the created timestamp to determine actual order:

function handleEvent(event) {
	const eventTime = new Date(event.created);
	// Process based on eventTime, not arrival time
}

Next Steps

Advanced Integration Patterns

Custom integrations and advanced use cases

Advanced Integration Patterns

Learn advanced techniques for integrating SimplyStream into your applications.

Custom Authentication

Implement custom authentication for your streaming sessions.

Single Sign-On (SSO) Integration

import { SimplyStreamClient } from '@simplystream/sdk';

async function createAuthenticatedSession(user) {
	const client = new SimplyStreamClient({ apiKey: process.env.API_KEY });

	// Create session with user context
	const session = await client.sessions.create({
		buildId: 'build-123',
		region: 'us-west-2',
		metadata: {
			userId: user.id,
			email: user.email,
			role: user.role,
			authenticatedAt: new Date().toISOString()
		},
		// Generate signed URL valid for 1 hour
		authentication: {
			type: 'signed-url',
			expiresIn: 3600,
			permissions: ['view', 'interact']
		}
	});

	// Return signed URL to client
	return session.signedUrl;
}

Token-Based Authentication

// Backend: Generate session token
const sessionToken = await client.sessions.createToken('session-123', {
	userId: user.id,
	expiresIn: 7200, // 2 hours
	permissions: {
		canControl: user.role === 'admin',
		canView: true,
		canShare: user.role !== 'guest'
	}
});

// Frontend: Use token to access session
const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123',
	token: sessionToken
});

Session Pooling

Optimize resource usage by pooling pre-warmed sessions.

class SessionPool {
	private pool: Map<string, Session[]> = new Map();
	private client: SimplyStreamClient;

	constructor(client: SimplyStreamClient) {
		this.client = client;
	}

	async initialize(buildId: string, poolSize: number = 5) {
		const sessions = await Promise.all(
			Array(poolSize)
				.fill(null)
				.map(() =>
					this.client.sessions.create({
						buildId,
						region: 'us-west-2',
						preWarm: true // Keep session ready but not active
					})
				)
		);

		this.pool.set(buildId, sessions);

		// Maintain pool size
		this.startPoolMaintenance(buildId, poolSize);
	}

	async acquire(buildId: string, userId: string): Promise<Session> {
		const sessions = this.pool.get(buildId) || [];
		const session = sessions.pop();

		if (!session) {
			// Pool exhausted, create new session
			return this.client.sessions.create({ buildId });
		}

		// Activate pre-warmed session
		await this.client.sessions.activate(session.id, {
			userId,
			metadata: { acquiredAt: Date.now() }
		});

		// Replenish pool
		this.replenishPool(buildId);

		return session;
	}

	private async replenishPool(buildId: string) {
		const newSession = await this.client.sessions.create({
			buildId,
			preWarm: true
		});

		const sessions = this.pool.get(buildId) || [];
		sessions.push(newSession);
		this.pool.set(buildId, sessions);
	}

	private startPoolMaintenance(buildId: string, targetSize: number) {
		setInterval(async () => {
			const sessions = this.pool.get(buildId) || [];

			// Remove expired sessions
			const activeSessions = sessions.filter((s) => !s.isExpired);

			// Top up to target size
			while (activeSessions.length < targetSize) {
				const newSession = await this.client.sessions.create({
					buildId,
					preWarm: true
				});
				activeSessions.push(newSession);
			}

			this.pool.set(buildId, activeSessions);
		}, 60000); // Check every minute
	}
}

// Usage
const pool = new SessionPool(client);
await pool.initialize('build-123', 10);

// When user requests a session
const session = await pool.acquire('build-123', 'user-456');

Load Balancing

Distribute users across regions for optimal performance.

class RegionalLoadBalancer {
	private client: SimplyStreamClient;
	private regionCapacity: Map<string, number> = new Map();

	async getBestRegion(userLocation?: string): Promise<string> {
		// Get current load for all regions
		const regions = await this.client.regions.list();

		const availableRegions = regions.filter(
			(r) => r.available && r.currentLoad < r.maxCapacity * 0.9
		);

		if (!availableRegions.length) {
			throw new Error('No regions available');
		}

		if (userLocation) {
			// Find closest region with capacity
			return this.findClosestRegion(userLocation, availableRegions);
		}

		// Return region with lowest load
		return availableRegions.sort((a, b) => a.currentLoad - b.currentLoad)[0].id;
	}

	private findClosestRegion(userLocation: string, regions: Region[]): string {
		const distances = regions.map((region) => ({
			region: region.id,
			latency: this.estimateLatency(userLocation, region.location)
		}));

		return distances.sort((a, b) => a.latency - b.latency)[0].region;
	}

	private estimateLatency(from: string, to: string): number {
		// Simplified latency estimation
		const latencyMap = {
			'us-west': { 'us-west': 10, 'us-east': 70, 'eu-west': 150 },
			'us-east': { 'us-west': 70, 'us-east': 10, 'eu-west': 90 },
			'eu-west': { 'us-west': 150, 'us-east': 90, 'eu-west': 10 }
		};

		return latencyMap[from]?.[to] || 200;
	}
}

// Usage
const balancer = new RegionalLoadBalancer(client);
const region = await balancer.getBestRegion(userLocation);

const session = await client.sessions.create({
	buildId: 'build-123',
	region
});

Custom Analytics

Track detailed analytics about session usage.

class SessionAnalytics {
	private events: AnalyticsEvent[] = [];

	trackEvent(event: AnalyticsEvent) {
		this.events.push({
			...event,
			timestamp: Date.now()
		});

		// Batch send events
		if (this.events.length >= 10) {
			this.flush();
		}
	}

	async flush() {
		if (!this.events.length) return;

		const batch = [...this.events];
		this.events = [];

		await fetch('/api/analytics', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ events: batch })
		});
	}
}

// Usage with embed
const analytics = new SessionAnalytics();

const embed = new SimplyStreamEmbed({
	container: '#stream-container',
	sessionId: 'session-123'
});

embed.on('ready', () => {
	analytics.trackEvent({
		type: 'session_ready',
		sessionId: embed.sessionId,
		loadTime: performance.now()
	});
});

embed.on('play', () => {
	analytics.trackEvent({
		type: 'playback_started',
		sessionId: embed.sessionId
	});
});

embed.on('qualityChanged', (quality) => {
	analytics.trackEvent({
		type: 'quality_changed',
		sessionId: embed.sessionId,
		quality
	});
});

// Flush on page unload
window.addEventListener('beforeunload', () => {
	analytics.flush();
});

Rate Limiting

Implement client-side rate limiting to prevent API abuse.

class RateLimiter {
	private requests: Map<string, number[]> = new Map();

	async checkLimit(key: string, limit: number, windowMs: number): Promise<boolean> {
		const now = Date.now();
		const requests = this.requests.get(key) || [];

		// Remove old requests outside the window
		const recentRequests = requests.filter((time) => now - time < windowMs);

		if (recentRequests.length >= limit) {
			return false; // Rate limit exceeded
		}

		recentRequests.push(now);
		this.requests.set(key, recentRequests);

		return true;
	}

	async waitForSlot(key: string, limit: number, windowMs: number): Promise<void> {
		while (!(await this.checkLimit(key, limit, windowMs))) {
			await new Promise((resolve) => setTimeout(resolve, 1000));
		}
	}
}

// Usage
const limiter = new RateLimiter();

async function createSession(userId: string) {
	const canProceed = await limiter.checkLimit(
		`user:${userId}`,
		5, // 5 requests
		60000 // per minute
	);

	if (!canProceed) {
		throw new Error('Rate limit exceeded. Please try again later.');
	}

	return client.sessions.create({ buildId: 'build-123' });
}

Proxy Pattern

Route requests through your backend for additional security.

// Backend proxy
app.post('/api/sessions/create', async (req, res) => {
	const { userId, buildId } = req.body;

	// Validate user has permission
	const user = await database.users.findById(userId);
	if (!user.canCreateSessions) {
		return res.status(403).json({ error: 'Not authorized' });
	}

	// Check user's quota
	const usage = await database.usage.getUserUsage(userId);
	if (usage.sessionsThisMonth >= user.maxSessions) {
		return res.status(429).json({ error: 'Quota exceeded' });
	}

	// Create session via SimplyStream API
	const session = await client.sessions.create({
		buildId,
		metadata: {
			userId,
			createdBy: user.email
		}
	});

	// Log the creation
	await database.sessionLogs.create({
		userId,
		sessionId: session.id,
		createdAt: new Date()
	});

	res.json({ session });
});

// Frontend
async function createSession(buildId: string) {
	const response = await fetch('/api/sessions/create', {
		method: 'POST',
		headers: { 'Content-Type': 'application/json' },
		body: JSON.stringify({
			userId: currentUser.id,
			buildId
		})
	});

	const { session } = await response.json();
	return session;
}

Multi-Tenancy

Support multiple organizations in a single application.

class TenantManager {
	private client: SimplyStreamClient;

	async createTenantSession(tenantId: string, buildId: string, userId: string) {
		// Get tenant configuration
		const tenant = await this.getTenant(tenantId);

		// Create session with tenant-specific settings
		const session = await this.client.sessions.create({
			buildId,
			region: tenant.preferredRegion,
			maxDuration: tenant.maxSessionDuration,
			metadata: {
				tenantId,
				userId,
				tier: tenant.tier,
				customBranding: tenant.customBranding
			}
		});

		// Apply tenant-specific customization
		if (tenant.customBranding) {
			await this.applyBranding(session.id, tenant.customBranding);
		}

		return session;
	}

	private async getTenant(tenantId: string) {
		// Fetch from database or cache
		return {
			id: tenantId,
			tier: 'enterprise',
			preferredRegion: 'us-west-2',
			maxSessionDuration: 7200,
			customBranding: {
				logo: 'https://cdn.example.com/logo.png',
				primaryColor: '#0066cc'
			}
		};
	}

	private async applyBranding(sessionId: string, branding: any) {
		await this.client.sessions.update(sessionId, {
			customization: {
				logo: branding.logo,
				theme: {
					primaryColor: branding.primaryColor
				}
			}
		});
	}
}

Graceful Degradation

Handle service unavailability gracefully.

class ResilientStreamClient {
	private client: SimplyStreamClient;
	private fallbackMode = false;

	async createSession(buildId: string) {
		try {
			return await this.client.sessions.create({ buildId });
		} catch (error) {
			if (this.isServiceUnavailable(error)) {
				return this.handleFallback(buildId);
			}
			throw error;
		}
	}

	private isServiceUnavailable(error: any): boolean {
		return error.status === 503 || error.code === 'SERVICE_UNAVAILABLE';
	}

	private async handleFallback(buildId: string) {
		this.fallbackMode = true;

		// Try alternative region
		const regions = ['us-west-2', 'us-east-1', 'eu-west-1'];

		for (const region of regions) {
			try {
				return await this.client.sessions.create({
					buildId,
					region
				});
			} catch (error) {
				continue;
			}
		}

		// All regions failed, return offline mode
		return {
			id: 'offline',
			url: '/offline-mode',
			offline: true
		};
	}
}

Next Steps