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/sdkexists 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
- Learn about Embedding Streaming Applications
- Set up Webhooks for real-time notifications
- Explore Advanced Integration Patterns
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
- Configure Webhooks to track embed usage
- Learn about Advanced Integration Patterns
- Review the JavaScript/TypeScript SDK documentation
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
- Go to your Project Settings
- Navigate to the "Webhooks" section
- Click "Add Webhook"
- Enter your webhook URL and select events
- 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
- Verify your webhook URL is publicly accessible
- Check your firewall allows incoming requests
- Ensure your server responds with 2xx status code
- Review webhook logs in the dashboard
Signature Verification Fails
- Verify you're using the correct webhook secret
- Ensure you're hashing the raw request body
- Use timing-safe comparison for signatures
- 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
- Set up Advanced Integration Patterns
- Learn about the JavaScript/TypeScript SDK
- Configure Embedding with webhook events
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
- Review the JavaScript/TypeScript SDK documentation
- Learn about Webhooks for event handling
- Explore Embedding options
- Check out Deployment best practices