queue-it-sdk
v1.0.4
Published
Shared types, utilities, and realtime adapters for Queue-it system
Maintainers
Readme
Queue-it SDK
A comprehensive TypeScript SDK for building virtual waiting room systems with real-time capabilities.
🚀 Quick Start
Installation
# In your project
pnpm add @queue-it/queue-sdk
# Or if using the monorepo
pnpm add @queue-it/queue-sdk --workspaceBasic Usage
import {
createRealtimeClient,
TokenUtils,
CookieUtils,
withWaitingRoom,
type QueueConfig,
type QueueState
} from '@queue-it/queue-sdk';
// Initialize realtime client
const realtimeClient = createRealtimeClient({
driver: 'socketio',
url: 'https://your-api.com',
auth: {
token: 'your-auth-token'
}
});
// Connect to realtime service
await realtimeClient.connect();
// Subscribe to queue updates
const unsubscribe = realtimeClient.subscribeQueue('limited-sneakers', (message) => {
console.log('Queue update:', message);
if (message.type === 'position_update') {
console.log(`Your position: ${message.data.position}`);
console.log(`Estimated wait time: ${message.data.estimatedWaitTime} seconds`);
}
if (message.type === 'admitted') {
console.log('You have been admitted!');
unsubscribe();
}
});🔧 Environment Setup
Environment Variables
The Queue SDK uses environment variables for development and testing. Copy the env.example file to .env and modify as needed:
# Copy the example file
cp env.example .envRequired Environment Variables
# API Configuration
QUEUE_API_URL=http://localhost:4000
QUEUE_WS_URL=ws://localhost:4000
# Authentication
QUEUE_AUTH_ENABLED=true
QUEUE_AUTH_COOKIE_NAME=queue-authOptional Environment Variables
# Queue Configuration
QUEUE_DEFAULT_TIMEOUT=30000
QUEUE_MAX_RETRIES=3
# Development
QUEUE_DEBUG_ENABLED=true
QUEUE_LOG_LEVEL=infoEnvironment Setup Checklist
- Copy env.example:
cp env.example .env - Set API URL: Point to your Queue API server
- Configure WebSocket: Set WebSocket URL for real-time updates
- Enable authentication: Configure auth settings for your use case
- Set timeouts: Configure queue timeout and retry settings
- Enable debugging: Set debug flags for development
Production Environment Variables
For production deployment:
- API URLs: Use production Queue API endpoints
- WebSocket URLs: Use production WebSocket endpoints
- Authentication: Ensure proper auth configuration
- Timeouts: Set appropriate timeout values for production
- Debugging: Disable debug features in production
📚 API Reference
Realtime Client
createRealtimeClient(config: RealtimeConfig): RealtimeClient
Creates a realtime client for queue updates.
interface RealtimeConfig {
driver: 'socketio' | 'sse'; // Transport protocol
url: string; // Server URL
auth?: { // Authentication
token?: string;
headers?: Record<string, string>;
};
options?: { // Connection options
reconnection?: boolean;
reconnectionDelay?: number;
maxReconnectionAttempts?: number;
timeout?: number;
};
}RealtimeClient.connect(): Promise<void>
Establishes connection to the realtime service.
try {
await realtimeClient.connect();
console.log('Connected to realtime service');
} catch (error) {
console.error('Connection failed:', error);
}RealtimeClient.subscribeQueue(queueId: string, handler: QueueMessageHandler): () => void
Subscribes to queue updates. Returns an unsubscribe function.
const unsubscribe = realtimeClient.subscribeQueue('queue-id', (message) => {
switch (message.type) {
case 'queue_update':
console.log('Queue state updated:', message.data);
break;
case 'position_update':
console.log('Position:', message.data.position);
break;
case 'admitted':
console.log('Admitted to queue!');
break;
case 'error':
console.error('Error:', message.data.message);
break;
}
});
// Later, unsubscribe
unsubscribe();RealtimeClient.disconnect(): void
Disconnects from the realtime service.
realtimeClient.disconnect();RealtimeClient.isConnected(): boolean
Checks if the client is connected.
if (realtimeClient.isConnected()) {
console.log('Client is connected');
}Token Utilities
TokenUtils
Handles JWT and HMAC token operations.
import { TokenUtils } from '@queue-it/queue-sdk';
const tokenUtils = new TokenUtils(
'your-hmac-secret',
'your-jwt-secret'
);
// Generate HMAC signature for enqueue ticket
const ticket = {
nonce: 'abc123',
visitorId: 'visitor-456',
queueId: 'limited-sneakers',
issuedAt: new Date(),
expiresAt: new Date(Date.now() + 3600000)
};
const signature = tokenUtils.generateTicketSignature(ticket);
// Verify ticket signature
const isValid = tokenUtils.verifyTicketSignature({
...ticket,
signature
});
// Generate JWT for queue access
const queueToken = tokenUtils.generateQueueToken(visitor, '1h');
// Verify JWT token
const decoded = tokenUtils.verifyQueueToken(queueToken);
// Generate admin token
const adminToken = tokenUtils.generateAdminToken('user-123', 'admin', '24h');
// Verify admin token
const adminData = tokenUtils.verifyAdminToken(adminToken);Static Methods
// Generate secure nonce
const nonce = TokenUtils.generateNonce();
// Generate visitor ID
const visitorId = TokenUtils.generateVisitorId();
// Check if token is expired
const isExpired = TokenUtils.isExpired(expiresAt);
// Calculate time until expiration
const timeLeft = TokenUtils.timeUntilExpiration(expiresAt);Cookie Utilities
CookieUtils
Manages secure HTTP-only cookies.
import { CookieUtils } from '@queue-it/queue-sdk';
const cookieUtils = new CookieUtils('yourdomain.com', true);
// Set queue token cookie
cookieUtils.setQueueTokenCookie(res, token);
// Set admin token cookie
cookieUtils.setAdminTokenCookie(res, adminToken);
// Get cookies from request
const queueToken = cookieUtils.getQueueToken(req);
const adminToken = cookieUtils.getAdminToken(req);
// Clear cookies
cookieUtils.clearQueueToken(res);
cookieUtils.clearAdminToken(res);Next.js Middleware
withWaitingRoom(config: WaitingRoomConfig)
Protects routes with waiting room functionality.
// middleware.ts
import { withWaitingRoom } from '@queue-it/queue-sdk';
export default withWaitingRoom({
queueId: 'limited-sneakers',
jwtSecret: process.env.JWT_SECRET!,
hmacSecret: process.env.HMAC_SECRET!,
cookieDomain: process.env.COOKIE_DOMAIN!,
bypassToken: process.env.BYPASS_TOKEN // Optional, for testing
});
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - waiting-room (waiting room pages)
*/
'/((?!api|_next/static|_next/image|favicon.ico|waiting-room).*)',
],
};Alternative Middleware Function
import { createWaitingRoomMiddleware } from '@queue-it/queue-sdk';
export default createWaitingRoomMiddleware({
queueId: 'limited-sneakers',
jwtSecret: process.env.JWT_SECRET!,
hmacSecret: process.env.HMAC_SECRET!,
cookieDomain: process.env.COOKIE_DOMAIN!
});Admin Client
⚠️ IMPORTANT: The AdminClient is designed for server-side use only. It should not be used in browser/client-side code as it contains Node.js-specific dependencies that won't work in browsers. Use it in API routes, server components, or backend services only.
createAdminClient(config: AdminClientConfig): AdminClient
Creates an admin client for managing queues programmatically.
import { createAdminClient } from '@queue-it/queue-sdk';
const adminClient = createAdminClient({
baseUrl: 'https://admin-api.example.com',
apiKey: 'your-admin-api-key' // Optional
});Queue Management Methods
// Create a queue
const queue = await adminClient.createQueue({
name: 'Limited Sneakers',
description: 'Queue for limited sneaker drop',
mode: 'fifo',
capacity: 1000,
triggerThreshold: 100,
rampRate: 10
});
// List all queues
const queues = await adminClient.listQueues();
// Get specific queue
const queue = await adminClient.getQueue('queue-id');
// Update queue
const updatedQueue = await adminClient.updateQueue('queue-id', {
capacity: 1500,
isActive: true
});
// Delete queue
await adminClient.deleteQueue('queue-id');Client-Side Queue Creation
// Create queue based on client endpoint/event
const queue = await adminClient.createQueueFromEndpoint('/api/events/limited-drop', {
mode: 'timed_random',
capacity: 500,
triggerThreshold: 50,
rampRate: 5
});
// This creates a queue with name like "queue-limited-drop"
// and metadata indicating it was created from an endpointRoute-Based User Enqueuing
// Enqueue user when they hit a specific route
const result = await adminClient.enqueueUserOnRoute('/checkout', 'visitor-123', {
userAgent: 'Mozilla/5.0...',
ipAddress: '192.168.1.1'
});
// This automatically finds or creates a queue for the /checkout route
// and enqueues the user with route metadataAdmin UI Socket Connection
// Establish socket connection for admin dashboard
const realtimeClient = adminClient.createAdminRealtimeConnection({
authToken: 'admin-jwt-token',
reconnection: true,
maxReconnectionAttempts: 5
});
// Connect and subscribe to queue updates
await realtimeClient.connect();
const unsubscribe = realtimeClient.subscribeQueue('all', (message) => {
console.log('Admin queue update:', message);
});Visitor Management
// Enqueue a visitor manually
const enqueueResult = await adminClient.enqueueVisitor({
queueId: 'limited-sneakers',
visitorId: 'visitor-123',
metadata: {
source: 'website',
priority: 'vip'
}
});
console.log('Enqueued at position:', enqueueResult.position);
console.log('Estimated wait time:', enqueueResult.estimatedWaitTime);🔧 Usage Examples
1. Client-Side Queue Integration
// components/QueueWidget.tsx
import { createRealtimeClient, type QueueMessage } from '@queue-it/queue-sdk';
import { useEffect, useState } from 'react';
interface QueueWidgetProps {
queueId: string;
authToken: string;
}
export function QueueWidget({ queueId, authToken }: QueueWidgetProps) {
const [position, setPosition] = useState<number | null>(null);
const [estimatedWaitTime, setEstimatedWaitTime] = useState<number | null>(null);
const [isAdmitted, setIsAdmitted] = useState(false);
useEffect(() => {
const realtimeClient = createRealtimeClient({
driver: 'socketio',
url: process.env.NEXT_PUBLIC_WS_URL!,
auth: { token: authToken }
});
const connect = async () => {
try {
await realtimeClient.connect();
const unsubscribe = realtimeClient.subscribeQueue(queueId, (message: QueueMessage) => {
if (message.type === 'position_update') {
setPosition(message.data.position);
setEstimatedWaitTime(message.data.estimatedWaitTime);
} else if (message.type === 'admitted') {
setIsAdmitted(true);
unsubscribe();
}
});
return unsubscribe;
} catch (error) {
console.error('Failed to connect:', error);
}
};
const unsubscribe = connect();
return () => {
unsubscribe?.();
realtimeClient.disconnect();
};
}, [queueId, authToken]);
if (isAdmitted) {
return <div>Welcome! You have been admitted.</div>;
}
return (
<div className="queue-widget">
<h3>Queue Position</h3>
{position !== null ? (
<>
<p>Your position: <strong>{position}</strong></p>
{estimatedWaitTime !== null && (
<p>Estimated wait: <strong>{estimatedWaitTime} seconds</strong></p>
)}
</>
) : (
<p>Loading queue information...</p>
)}
</div>
);
}2. Server-Side Token Validation
// lib/auth.ts
import { TokenUtils } from '@queue-it/queue-sdk';
const tokenUtils = new TokenUtils(
process.env.HMAC_SECRET!,
process.env.JWT_SECRET!
);
export function validateQueueToken(token: string) {
try {
const decoded = tokenUtils.verifyQueueToken(token);
if (!decoded) {
return null;
}
if (TokenUtils.isExpired(decoded.expiresAt)) {
return null;
}
return decoded;
} catch (error) {
return null;
}
}
export function validateAdminToken(token: string) {
try {
return tokenUtils.verifyAdminToken(token);
} catch (error) {
return null;
}
}3. API Route with Queue Management
// pages/api/queue/enqueue.ts
import { TokenUtils } from '@queue-it/queue-sdk';
import type { NextApiRequest, NextApiResponse } from 'next';
const tokenUtils = new TokenUtils(
process.env.HMAC_SECRET!,
process.env.JWT_SECRET!
);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { queueId, visitorId } = req.body;
// Generate enqueue ticket
const nonce = TokenUtils.generateNonce();
const issuedAt = new Date();
const expiresAt = new Date(issuedAt.getTime() + 3600000); // 1 hour
const ticket = {
nonce,
visitorId: visitorId || TokenUtils.generateVisitorId(),
queueId,
issuedAt,
expiresAt
};
const signature = tokenUtils.generateTicketSignature(ticket);
// Here you would typically:
// 1. Add visitor to queue in Redis
// 2. Store ticket information
// 3. Emit realtime updates
res.status(200).json({
success: true,
ticket: { ...ticket, signature },
position: 1, // This would come from your queue logic
estimatedWaitTime: 300 // This would be calculated
});
} catch (error) {
console.error('Enqueue error:', error);
res.status(500).json({ error: 'Failed to enqueue visitor' });
}
}4. Express.js Integration
// app.ts
import express from 'express';
import { CookieUtils, TokenUtils } from '@queue-it/queue-sdk';
const app = express();
const cookieUtils = new CookieUtils('yourdomain.com', true);
const tokenUtils = new TokenUtils(
process.env.HMAC_SECRET!,
process.env.JWT_SECRET!
);
// Middleware to validate queue tokens
app.use('/protected', (req, res, next) => {
const token = cookieUtils.getQueueToken(req);
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const decoded = tokenUtils.verifyQueueToken(token);
if (!decoded) {
return res.status(401).json({ error: 'Invalid token' });
}
if (TokenUtils.isExpired(decoded.expiresAt)) {
cookieUtils.clearQueueToken(res);
return res.status(401).json({ error: 'Token expired' });
}
// Add user info to request
req.user = decoded;
next();
});
app.get('/protected/dashboard', (req, res) => {
res.json({
message: 'Welcome to dashboard',
visitorId: req.user.visitorId,
position: req.user.position
});
});5. React Hook for Queue Management
// hooks/useQueue.ts
import { createRealtimeClient, type QueueMessage } from '@queue-it/queue-sdk';
import { useEffect, useState, useCallback } from 'react';
interface UseQueueOptions {
queueId: string;
authToken: string;
autoConnect?: boolean;
}
export function useQueue({ queueId, authToken, autoConnect = true }: UseQueueOptions) {
const [isConnected, setIsConnected] = useState(false);
const [queueState, setQueueState] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const [realtimeClient, setRealtimeClient] = useState<any>(null);
const connect = useCallback(async () => {
try {
const client = createRealtimeClient({
driver: 'socketio',
url: process.env.NEXT_PUBLIC_WS_URL!,
auth: { token: authToken }
});
await client.connect();
setIsConnected(true);
setRealtimeClient(client);
setError(null);
// Subscribe to queue updates
client.subscribeQueue(queueId, (message: QueueMessage) => {
if (message.type === 'queue_update') {
setQueueState(message.data);
}
});
return client;
} catch (err) {
setError(err instanceof Error ? err.message : 'Connection failed');
setIsConnected(false);
throw err;
}
}, [queueId, authToken]);
const disconnect = useCallback(() => {
if (realtimeClient) {
realtimeClient.disconnect();
setIsConnected(false);
setRealtimeClient(null);
}
}, [realtimeClient]);
useEffect(() => {
if (autoConnect) {
connect();
}
return () => {
disconnect();
};
}, [autoConnect, connect, disconnect]);
return {
isConnected,
queueState,
error,
connect,
disconnect
};
}🖥️ Server vs Client Usage
Server-Side Components (API Routes, Server Components)
Use these in Node.js environments (API routes, server components, backend services):
- AdminClient: For managing queues, creating endpoints, enqueuing users
- TokenUtils: For JWT token generation and validation
- CookieUtils: For secure cookie management
- withWaitingRoom: Next.js middleware for route protection
Client-Side Components (React Components, Browser)
Use these in browser environments:
- RealtimeClient: For real-time queue updates and position tracking
- QueueWidget: Pre-built React components for queue UI
- createRealtimeClient: For establishing WebSocket connections
Example Architecture
// Server-side: API route (pages/api/admin/queues.ts)
import { createAdminClient } from '@queue-it/queue-sdk';
export default async function handler(req, res) {
const adminClient = createAdminClient({
baseUrl: process.env.ADMIN_API_URL,
apiKey: process.env.ADMIN_API_KEY
});
if (req.method === 'POST') {
const queue = await adminClient.createQueueFromEndpoint('/api/events/sale');
res.status(200).json(queue);
}
}
// Client-side: React component
import { createRealtimeClient } from '@queue-it/queue-sdk';
function QueueComponent() {
const realtimeClient = createRealtimeClient({
driver: 'socketio',
url: process.env.NEXT_PUBLIC_WS_URL
});
// Use realtimeClient in component
}🔒 Security Considerations
Token Security
- JWT Secrets: Use strong, randomly generated secrets
- HMAC Secrets: Keep HMAC secrets separate from JWT secrets
- Token Expiration: Set appropriate expiration times
- Secure Cookies: Always use
httpOnlyandsecureflags in production
Rate Limiting
// Example rate limiting with the SDK
import rateLimit from 'express-rate-limit';
const enqueueLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 enqueue requests per windowMs
message: 'Too many enqueue attempts, please try again later'
});
app.post('/api/queue/enqueue', enqueueLimiter, enqueueHandler);🧪 Testing
Unit Tests
// __tests__/TokenUtils.test.ts
import { TokenUtils } from '@queue-it/queue-sdk';
describe('TokenUtils', () => {
const tokenUtils = new TokenUtils('test-hmac-secret', 'test-jwt-secret');
test('should generate and verify HMAC signature', () => {
const ticket = {
nonce: 'test-nonce',
visitorId: 'test-visitor',
queueId: 'test-queue',
issuedAt: new Date(),
expiresAt: new Date(Date.now() + 3600000)
};
const signature = tokenUtils.generateTicketSignature(ticket);
const isValid = tokenUtils.verifyTicketSignature({ ...ticket, signature });
expect(isValid).toBe(true);
});
test('should generate and verify JWT token', () => {
const visitor = {
id: 'test-visitor',
queueId: 'test-queue',
position: 1,
joinedAt: new Date(),
lastHeartbeat: new Date(),
isActive: false,
metadata: {}
};
const token = tokenUtils.generateQueueToken(visitor);
const decoded = tokenUtils.verifyQueueToken(token);
expect(decoded).toBeTruthy();
expect(decoded?.visitorId).toBe('test-visitor');
});
});Integration Tests
// __tests__/integration/RealtimeClient.test.ts
import { createRealtimeClient } from '@queue-it/queue-sdk';
describe('RealtimeClient Integration', () => {
test('should connect and receive messages', async () => {
const client = createRealtimeClient({
driver: 'socketio',
url: 'http://localhost:4000'
});
const messages: any[] = [];
await client.connect();
const unsubscribe = client.subscribeQueue('test-queue', (message) => {
messages.push(message);
});
// Wait for messages
await new Promise(resolve => setTimeout(resolve, 1000));
expect(client.isConnected()).toBe(true);
expect(messages.length).toBeGreaterThan(0);
unsubscribe();
client.disconnect();
});
});📱 Browser Support
- Socket.IO: Modern browsers with WebSocket support
- SSE: All modern browsers (IE 10+)
- Fallback: Automatic fallback to polling if needed
🔧 Configuration
Environment Variables
The Queue SDK requires these environment variables for production use:
# Required
JWT_SECRET=your-super-secure-jwt-secret
HMAC_SECRET=your-super-secure-hmac-secret
# Optional
COOKIE_DOMAIN=yourdomain.com
COOKIE_SECURE=trueNote: For development setup, see the Environment Setup section above.
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"esModuleInterop": true,
"skipLibCheck": true
}
}🆘 Troubleshooting
Common Issues
Connection Failed
- Check server URL and port
- Verify authentication token
- Check CORS configuration
Token Validation Errors
- Verify JWT and HMAC secrets
- Check token expiration
- Ensure proper token format
Cookie Issues
- Verify domain configuration
- Check secure flag in production
- Ensure proper SameSite policy
Debug Mode
// Enable debug logging
const realtimeClient = createRealtimeClient({
driver: 'socketio',
url: 'https://your-api.com',
options: {
reconnection: true,
reconnectionDelay: 1000,
maxReconnectionAttempts: 5
}
});
// Listen for connection events
realtimeClient.on('connect', () => console.log('Connected'));
realtimeClient.on('disconnect', () => console.log('Disconnected'));
realtimeClient.on('error', (error) => console.error('Error:', error));📚 Additional Resources
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
📄 License
MIT License - see LICENSE file for details
