@lattestream/server
v1.1.0
Published
LatteStream server SDK for Node.js and Deno
Maintainers
Readme
@lattestream/server
LatteStream server SDK for Node.js and Deno. Trigger events, authorize channels, and manage real-time connections from your backend.
Features
- Secure Authentication - Channel authorization with encrypted secrets
- High Performance - Connection pooling, batching, caching, and retry logic
- TypeScript First - Full type safety with comprehensive type definitions
- Multi-runtime - Works with Node.js and Deno
- Event Batching - Automatic event batching for better performance
Installation
npm install @lattestream/serverOr with yarn/pnpm:
yarn add @lattestream/server
pnpm add @lattestream/serverQuick Start
import LatteStreamServer from '@lattestream/server';
// Initialize with encrypted secret
const server = new LatteStreamServer('lsk_your_encrypted_secret', {
cluster: 'eu1',
});
// Trigger an event
await server.trigger('my-channel', 'my-event', {
message: 'Hello World!',
});Authentication Keys
Use your lsk_* encrypted secret (server-side only). Never expose this in client code.
When clients subscribe to private/presence channels, they'll call your auth endpoint. Your server uses authorizeChannel() to get an lspc_* token from the LatteStream service.
Triggering Events
Single Channel
await server.trigger('chat-room', 'message', {
user: 'Alice',
text: 'Hello!',
});Multiple Channels
await server.trigger(['room-1', 'room-2', 'room-3'], 'notification', { alert: 'Server maintenance in 5 minutes' });Exclude Socket ID
Useful to exclude the sender from receiving their own event:
await server.trigger('chat', 'message', data, {
socketId: req.body.socket_id, // This socket won't receive the event
});Batch Events
For triggering multiple different events efficiently:
await server.triggerBatch([
{
channel: 'channel-1',
name: 'event-1',
data: { foo: 'bar' },
},
{
channel: 'channel-2',
name: 'event-2',
data: { baz: 'qux' },
socketId: 'exclude-this-socket',
},
]);Channel Authorization
How Authentication Works
- Client connects with
lspk_*public key - Client subscribes to a private/presence channel (must happen within 30 seconds)
- Client SDK calls your
/authendpoint withsocket_idandchannel_name - Your server calls
authorizeChannel()which requests anlspc_*token from LatteStream service - LatteStream service returns the authorization token
- Client completes subscription with the token
Using authorizeChannel()
import LatteStreamServer from '@lattestream/server';
const server = new LatteStreamServer('lsk_your_encrypted_secret');
app.post('/auth', async (req, res) => {
const { socket_id, channel_name } = req.body;
// Your authorization logic
if (!req.user) {
return res.status(403).json({ error: 'Unauthorized' });
}
try {
// For presence channels, provide user data
const userData = channel_name.startsWith('presence-')
? { user_id: req.user.id, user_info: { name: req.user.name } }
: undefined;
const authResponse = await server.authorizeChannel(socket_id, channel_name, userData);
res.json(authResponse);
} catch (error) {
res.status(403).json({ error: error.message });
}
});Express Middleware
For Express.js, use the convenience middleware:
import { createChannelAuthMiddleware } from '@lattestream/server';
app.post(
'/auth',
createChannelAuthMiddleware('lsk_your_encrypted_secret', (req) => ({
user_id: req.user.id,
user_info: { name: req.user.name },
}))
);Channel Information
Get Channel Info
const info = await server.getChannelInfo('presence-lobby', ['user_count']);
console.log(info);
// { occupied: true, userCount: 42, subscriptionCount: 42 }List Channels
const channels = await server.getChannels('presence-', ['user_count']);
console.log(channels);
// {
// channels: {
// 'presence-lobby': { occupied: true, userCount: 42 },
// 'presence-chat': { occupied: true, userCount: 15 }
// }
// }Get Presence Users
const users = await server.getUsers('presence-lobby');
console.log(users);
// { users: [{ id: 'user-1' }, { id: 'user-2' }] }Terminate User Connections
await server.terminateUserConnections('user-123');Webhooks
LatteStream can send webhooks to your server for events like channel occupancy changes, client connections, and more. Configure your webhook URL in the LatteStream Dashboard.
Verifying Webhook Signatures
All webhook requests include an x-lattestream-signature header that you should verify to ensure the request came from LatteStream:
import { verifyWebhookSignature, WebhookEventPayload } from '@lattestream/server';
app.post('/webhooks/lattestream', (req, res) => {
const body = req.body;
const signature = req.headers['x-lattestream-signature'] as string;
const verified = verifyWebhookSignature(
body,
signature,
process.env.LATTESTREAM_WEBHOOK_SECRET as string
);
if (!verified) {
return res.status(401).send('Invalid signature');
}
// Type-safe webhook payload
const payload: WebhookEventPayload = body;
// Process webhook events
payload.events.forEach((event) => {
console.log(`${event.name} on ${event.channel}`);
});
res.status(200).send('OK');
});Important: Store your webhook secret from the LatteStream Dashboard in LATTESTREAM_WEBHOOK_SECRET environment variable.
API Reference
LatteStreamServer
Constructor
new LatteStreamServer(encryptedSecret: string, options?: LatteStreamServerOptions)Methods
Events:
trigger(channel, event, data, options?)- Trigger event on channel(s)triggerBatch(events)- Trigger multiple eventsflushBatch()- Manually flush batched events
Authorization:
authorizeChannel(socketId, channelName, userData?)- Authorize private/presence channel
Helper Functions
import {
createChannelAuthMiddleware,
ServerAuthorizer,
EncryptionHelper,
createAuthHelper,
verifyWebhookSignature,
WebhookEventPayload
} from '@lattestream/server';Authentication:
createChannelAuthMiddleware(secret, getUserData?)- Create Express auth middleware (NOTE: this is NOT encrypted. Recommend to useauthorizeChannel()instead)ServerAuthorizer- Manual authorization class (advanced)EncryptionHelper- Encryption utilities (advanced)createAuthHelper(masterKey)- Create encryption helper (advanced)
Webhooks:
verifyWebhookSignature(body, signature, secret)- Verify webhook signature from LatteStreamWebhookEventPayload- TypeScript type for webhook request body
Configuration Options
interface LatteStreamServerOptions {
wsEndpoint?: string; // Custom WebSocket endpoint
cluster?: string; // Cluster region (default: 'eu1')
useTLS?: boolean; // Use TLS (default: true)
enableLogging?: boolean; // Enable debug logging
timeout?: number; // Request timeout in ms (default: 30000)
// Connection pooling
maxConnections?: number; // Max concurrent connections (default: 20)
connectionMaxAge?: number; // Connection max age in ms (default: 300000)
// Caching
cacheTimeout?: number; // Cache timeout in ms (default: 30000)
// Retry logic
maxRetries?: number; // Max retry attempts (default: 3)
retryDelay?: number; // Retry delay in ms (default: 1000)
// Event batching
enableBatching?: boolean; // Enable event batching (default: true)
batchSize?: number; // Max batch size (default: 50)
batchInterval?: number; // Batch interval in ms (default: 100)
}Performance Features
Automatic Event Batching
Events are automatically batched for better performance:
// These will be batched together
await server.trigger('channel-1', 'event', { data: 1 });
await server.trigger('channel-2', 'event', { data: 2 });
await server.trigger('channel-3', 'event', { data: 3 });
// Force immediate flush
await server.flushBatch();Disable batching if needed:
const server = new LatteStreamServer('lsk_secret', {
enableBatching: false,
});Connection Pooling
Connections are automatically pooled and reused for better performance.
Request Caching
Channel info and user queries are cached automatically (30s default):
const server = new LatteStreamServer('lsk_secret', {
cacheTimeout: 60000, // Cache for 60 seconds
});Advanced Performance Utilities
import { ConnectionPool, BatchProcessor, RequestCache, createRetryWrapper, MemoryMonitor } from '@lattestream/server';
// Use these for custom implementationsExamples
Express.js API
import express from 'express';
import LatteStreamServer from '@lattestream/server';
const app = express();
const server = new LatteStreamServer('lsk_your_secret');
app.use(express.json());
// Trigger event endpoint
app.post('/api/message', async (req, res) => {
try {
await server.trigger(
'chat',
'message',
{
user: req.user.name,
text: req.body.text,
},
{
socketId: req.body.socket_id, // Exclude sender
}
);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Auth endpoint
app.post('/auth', async (req, res) => {
const { socket_id, channel_name } = req.body;
const authResponse = await server.authorizeChannel(socket_id, channel_name, { user_id: req.user.id });
res.json(authResponse);
});
app.listen(3000);Next.js API Route
// pages/api/lattestream/trigger.js
import LatteStreamServer from '@lattestream/server';
const server = new LatteStreamServer(process.env.LATTESTREAM_SECRET);
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
await server.trigger('notifications', 'alert', req.body);
res.status(200).json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
}Deno
import LatteStreamServer from 'npm:@lattestream/server';
const server = new LatteStreamServer(Deno.env.get('LATTESTREAM_SECRET')!);
Deno.serve(async (req) => {
if (req.method === 'POST' && new URL(req.url).pathname === '/trigger') {
const data = await req.json();
await server.trigger('channel', 'event', data);
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
}
return new Response('Not found', { status: 404 });
});TypeScript
This package includes TypeScript definitions out of the box.
import LatteStreamServer, {
LatteStreamServerOptions,
TriggerEventOptions,
BatchTriggerEvent,
ChannelInfo,
WebhookEventPayload,
verifyWebhookSignature,
} from '@lattestream/server';
const server: LatteStreamServer = new LatteStreamServer('lsk_secret', {
cluster: 'eu1',
enableBatching: true,
});Environment Variables
# .env
LATTESTREAM_SECRET=lsk_your_encrypted_secret
LATTESTREAM_CLUSTER=eu1
LATTESTREAM_WEBHOOK_SECRET=your_webhook_secret_from_dashboardconst server = new LatteStreamServer(process.env.LATTESTREAM_SECRET, {
cluster: process.env.LATTESTREAM_CLUSTER,
});Related Packages
- @lattestream/client - Client SDK for browsers and frontend frameworks
License
MIT License - see LICENSE for details.
