@atel-callcenter/sdk
v2.0.8
Published
ATEL Call Center SDK V2 - Framework and media-provider agnostic core with adapters for React and LiveKit
Downloads
642
Maintainers
Readme
ATEL Call Center SDK v2
Framework and Media Provider Agnostic Call Center SDK
A modern TypeScript SDK for building queue-based video call applications with clean separation between business logic (Core) and platform implementations (Adapters).
🏗️ Architecture
The SDK follows a strict CORE/ADAPTER pattern:
sdk-ts/
├─ core/ # ✅ Framework & Media Agnostic
│ ├─ events.ts # Strongly-typed event system
│ ├─ media-adapter.ts # MediaAdapter interface
│ ├─ state.ts # Call state machine
│ ├─ CustomerCore.ts # Customer business logic
│ └─ AgentCore.ts # Agent business logic
│
├─ adapters/
│ ├─ web/ # Web platform adapters
│ │ └─ livekit/ # LiveKit media provider
│ └─ react/ # React framework adapters
│ ├─ customer/ # Customer React hooks
│ └─ agent/ # Agent React hooks
│
└─ shared/ # Shared utilities (sockets, etc)Core Principles
- Core SDK is Framework-Agnostic: NO React, NO browser APIs
- Core SDK is Media-Agnostic: NO LiveKit, NO Agora, NO Twilio
- Dependency Injection: Adapters are injected via factory pattern
- Strongly-Typed Events: All events use discriminated unions
- State Machine: Call lifecycle managed with validated transitions
📦 Installation
npm install @atel-callcenter/sdk
# or
yarn add @atel-callcenter/sdk🚀 Quick Start
Customer (React + LiveKit)
import {
CustomerProvider,
useCustomer,
} from '@atel-callcenter/sdk/react/customer';
function App() {
return (
<CustomerProvider config={{ url: 'http://localhost:3000' }}>
<CustomerInterface />
</CustomerProvider>
);
}
function CustomerInterface() {
const customer = useCustomer();
const handleCall = async () => {
await customer.requestCall({
name: 'Jane Doe',
queueId: 'sales',
});
};
return (
<div>
<p>Status: {customer.currentCallState}</p>
{customer.currentCallState === 'idle' && (
<button onClick={handleCall}>Request Call</button>
)}
{customer.currentCallState === 'in-call' && (
<button onClick={() => customer.endCall()}>End Call</button>
)}
</div>
);
}Agent (React + LiveKit)
import { AgentProvider, useAgent } from '@atel-callcenter/sdk/react/agent';
function App() {
return (
<AgentProvider config={{ url: 'http://localhost:3000' }}>
<AgentDashboard />
</AgentProvider>
);
}
function AgentDashboard() {
const agent = useAgent();
useEffect(() => {
agent.register({
agentId: 'agent-123',
agentName: 'John Smith',
queueIds: ['sales', 'support'],
});
}, []);
const handleAccept = () => {
agent.acceptCall();
};
const handleReject = () => {
agent.rejectCall('Not available');
};
return (
<div>
<p>Status: {agent.status}</p>
{agent.incomingCall && (
<div>
<p>Call from: {agent.incomingCall.customerName}</p>
<button onClick={handleAccept}>Accept</button>
<button onClick={handleReject}>Reject</button>
</div>
)}
{agent.currentCallState === 'in-call' && (
<button onClick={() => agent.endCall()}>End Call</button>
)}
</div>
);
}
## 📖 Advanced Usage
### Using Core SDK Directly (Framework-Agnostic)
For non-React applications or custom integrations:
```typescript
import { CustomerClient } from '@atel-callcenter/sdk/core/CustomerCore';
import { createLiveKitAdapter } from '@atel-callcenter/sdk/adapters/web';
// Create customer client with LiveKit adapter
const customer = new CustomerClient({
url: 'http://localhost:3000',
mediaAdapterFactory: createLiveKitAdapter,
});
// Listen to strongly-typed events
customer.on('CALL_ACCEPTED', (event) => {
console.log('Call accepted:', event.ticketId);
console.log('Media info:', event.media);
});
customer.on('MEDIA_READY', () => {
console.log('Media connection ready');
});
customer.on('CALL_ENDED', (event) => {
console.log('Call ended:', event.reason);
});
// Connect and request call
customer.connect();
await customer.requestCall({
name: 'Jane Doe',
queueId: 'sales',
});Custom Media Adapter
Implement MediaAdapter interface for other providers (Agora, Twilio, etc):
import {
MediaAdapter,
MediaInfo,
} from '@atel-callcenter/sdk/core/media-adapter';
export class AgoraMediaAdapter implements MediaAdapter {
private client: AgoraRTCClient | null = null;
async connect(mediaInfo: MediaInfo): Promise<void> {
// Implement Agora connection logic
this.client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' });
await this.client.join(
mediaInfo.roomUrl,
mediaInfo.roomName,
mediaInfo.token,
);
this.onReadyCallback?.();
}
async disconnect(): Promise<void> {
await this.client?.leave();
this.client = null;
}
onReady(callback: () => void): void {
this.onReadyCallback = callback;
}
// ... implement other methods
}
// Use custom adapter
const customer = new CustomerClient({
url: 'http://localhost:3000',
mediaAdapterFactory: () => new AgoraMediaAdapter(),
});Event System
All events are strongly-typed discriminated unions:
import type { CallEvent } from '@atel-callcenter/sdk/core/events';
customer.on('CALL_ACCEPTED', (event) => {
// TypeScript knows this is CallAcceptedEvent
console.log(event.ticketId);
console.log(event.media.provider); // 'livekit'
console.log(event.media.roomUrl);
});
customer.on('ERROR', (event) => {
// TypeScript knows this is ErrorEvent
console.log(event.code);
console.log(event.message);
});🔄 Call State Machine
The SDK manages call lifecycle through a state machine:
idle → requesting → ringing → connecting → in-call → ending → ended
↓
failedAccess current state:
console.log(customer.currentCallState); // 'idle' | 'requesting' | ...
console.log(agent.currentCallState);📚 API Reference
CustomerClient
Constructor:
new CustomerClient(config: CustomerConfig)
Methods:
connect(): void- Connect to signaling serverdisconnect(): Promise<void>- Disconnect and cleanuprequestCall(request: CallRequest): Promise<void>- Request a callendCall(): Promise<void>- End current call
Properties:
currentCallState: CallState- Current call stateticketId?: string- Current ticket IDqueueId?: string- Current queue IDname?: string- Customer name
Events:
CALL_REQUESTED- Call request sentCALL_RINGING- Waiting for agentCALL_ACCEPTED- Agent accepted, media info providedCALL_REJECTED- Agent rejected callCALL_ENDED- Call ended by either partyMEDIA_READY- Media connection establishedMEDIA_DISCONNECTED- Media connection lostERROR- Error occurred
AgentClient
Constructor:
new AgentClient(config: AgentConfig)
Methods:
connect(): void- Connect to signaling serverdisconnect(): Promise<void>- Disconnect and cleanupregister(registration: AgentRegistration): Promise<void>- Register as agentunregister(): Promise<void>- Unregister agentacceptCall(): Promise<void>- Accept incoming callrejectCall(reason?: string): void- Reject incoming callendCall(): Promise<void>- End current call
Properties:
status: AgentStatus- 'offline' | 'available' | 'busy'currentCallState: CallState- Current call stateagentId?: string- Agent IDagentName?: string- Agent namequeueIds: string[]- Registered queue IDsincomingCall?: IncomingCallData- Incoming call info
Events:
- Same as CustomerClient, plus legacy events for backward compatibility
🎯 Migration from v1
If upgrading from SDK v1:
Import Paths Changed:
// Before import { CustomerCore } from '@atel-callcenter/sdk/customer'; // After import { CustomerClient } from '@atel-callcenter/sdk/core/CustomerCore'; // Or use React adapters (recommended) import { useCustomer } from '@atel-callcenter/sdk/react/customer';Event Names Changed:
// Before customer.on('callAccepted', ...); // After customer.on('CALL_ACCEPTED', ...); // Strongly-typedMedia Adapter Required:
// Before - LiveKit was hardcoded const customer = new CustomerCore({ url: '...' }); // After - Inject media adapter const customer = new CustomerClient({ url: '...', mediaAdapterFactory: createLiveKitAdapter, }); // Or use React Provider (handles this automatically)
🛠️ Development
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run watch
# Clean
npm run clean📄 License
MIT
// Customer import { CustomerCore } from '@atel-callcenter/sdk/customer';
const customer = new CustomerCore({ url: 'http://localhost:3000' });
customer.on('callAccepted', (data) => { console.log('Connected to:', data.agentName); });
customer.connect(); await customer.requestCall({ customerName: 'Customer Name', queueId: 'sales', });
## Package Exports
- `@atel-callcenter/sdk` - Main entry (exports all)
- `@atel-callcenter/sdk/shared` - Shared core only
- `@atel-callcenter/sdk/agent` - Agent core only
- `@atel-callcenter/sdk/customer` - Customer core only
- `@atel-callcenter/sdk/react/agent` - React agent adapter
- `@atel-callcenter/sdk/react/customer` - React customer adapter
## Design Principles
1. **Shared Core** - Only low-level infrastructure (socket, events, types)
2. **Role Separation** - Agent and Customer logic completely independent
3. **Framework Agnostic** - Cores work with any framework
4. **Adapter Pattern** - React adapters manage lifecycle and provide hooks
5. **Clean Dependencies** - No circular deps, no role-based conditionals