Embedding a NICE Cognigy Chat Widget in an Angular Application
What You Will Build
- A TypeScript Angular service and component that initializes the Cognigy Chat SDK, manages session lifecycle events including automatic timeout recovery, and synchronizes frontend application state with bot variables.
- This implementation uses the
@cognigy/chat-widgetSDK and Angular 17+ standalone component architecture. - The tutorial covers TypeScript, Angular lifecycle hooks, RxJS state management, and raw REST API fallback patterns for session handling.
Prerequisites
- Cognigy Chat API URL and API Token with
chat:readandchat:writepermissions @cognigy/chat-widgetnpm package (v2.x or later)- Angular 17+ with standalone components and signals
- TypeScript 5.0+ and Node.js 18+
- External dependencies:
rxjs,@angular/core,@angular/common,@angular/platform-browser
Authentication Setup
Cognigy Chat Widget authentication relies on an API Token generated in the Cognigy Studio environment. The token must be scoped to the specific bot and assigned chat:read and chat:write permissions. Never embed tokens directly in client code. Store them in Angular environment files or fetch them securely from a backend proxy.
The SDK initialization requires the API URL, token, and bot identifier. The following configuration object demonstrates the required structure:
import { Injectable, Inject, InjectionToken } from '@angular/core';
export const COGNIGY_CONFIG = new InjectionToken<Record<string, string>>('cognigy.config');
export interface CognigyInitConfig {
apiUrl: string;
apiToken: string;
botId: string;
language?: string;
userId?: string;
}
Inject the configuration at runtime and validate it before SDK initialization:
@Injectable({ providedIn: 'root' })
export class CognigyConfigService {
constructor(@Inject(COGNIGY_CONFIG) private config: Record<string, string>) {}
validateAndBuild(): CognigyInitConfig {
const apiUrl = this.config.apiUrl;
const apiToken = this.config.apiToken;
const botId = this.config.botId;
if (!apiUrl || !apiToken || !botId) {
throw new Error('Missing required Cognigy configuration: apiUrl, apiToken, or botId');
}
return {
apiUrl,
apiToken,
botId,
language: this.config.language || 'en',
userId: this.config.userId || undefined
};
}
}
Implementation
Step 1: SDK Initialization and Service Setup
Create a dedicated service to encapsulate SDK initialization, event subscription, and state management. The service bridges Cognigy callbacks to RxJS observables for Angular integration.
import { Injectable, OnDestroy, NgZone } from '@angular/core';
import { BehaviorSubject, Subject, fromEvent, of, timer } from 'rxjs';
import { catchError, delay, retry, takeUntil } from 'rxjs/operators';
import CognigyChat from '@cognigy/chat-widget';
import { CognigyConfigService, CognigyInitConfig } from './cognigy-config.service';
export interface CognigySessionState {
sessionId: string;
isActive: boolean;
lastActivity: number;
}
@Injectable({ providedIn: 'root' })
export class CognigyService implements OnDestroy {
private destroy$ = new Subject<void>();
private _state = new BehaviorSubject<CognigySessionState>({
sessionId: '',
isActive: false,
lastActivity: Date.now()
});
state$ = this._state.asObservable();
private chatInstance: typeof CognigyChat | null = null;
constructor(
private configService: CognigyConfigService,
private ngZone: NgZone
) {
this.initializeSdk();
}
private initializeSdk(): void {
try {
const initConfig = this.configService.validateAndBuild();
this.chatInstance = CognigyChat.init({
apiUrl: initConfig.apiUrl,
apiToken: initConfig.apiToken,
botId: initConfig.botId,
language: initConfig.language,
userId: initConfig.userId,
onInit: () => this.updateState(true),
onError: (error: Error) => this.handleSdkError(error)
});
this.subscribeToSdkEvents();
} catch (error) {
console.error('Cognigy SDK initialization failed:', error);
throw error;
}
}
private subscribeToSdkEvents(): void {
if (!this.chatInstance) return;
this.chatInstance.on('sessionTimeout', () => {
this.updateState(false);
this.handleSessionTimeout();
});
this.chatInstance.on('messageReceived', (message: any) => {
this._state.next({
...this._state.value,
lastActivity: Date.now()
});
});
}
private updateState(isActive: boolean): void {
this.ngZone.run(() => {
this._state.next({
sessionId: this.chatInstance?.getSessionId() || '',
isActive,
lastActivity: Date.now()
});
});
}
private handleSdkError(error: Error): void {
console.error('Cognigy SDK runtime error:', error);
this._state.next({
sessionId: '',
isActive: false,
lastActivity: Date.now()
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
if (this.chatInstance) {
this.chatInstance.destroy?.();
}
}
}
The service validates configuration, initializes the SDK, and maps SDK events to Angular zone-aware state updates. The onDestroy lifecycle hook ensures proper cleanup and prevents memory leaks.
Step 2: Session Timeout Handling and Retry Logic
Cognigy sessions expire after inactivity. The SDK emits a sessionTimeout event. You must implement automatic recovery with exponential backoff to handle transient network failures and rate limits.
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { timer, throwError } from 'rxjs';
import { catchError, delay, retryWhen, tap } from 'rxjs/operators';
export class CognigyService {
// ... previous code ...
private readonly MAX_RETRIES = 3;
private readonly BASE_DELAY_MS = 1000;
private handleSessionTimeout(): void {
console.warn('Cognigy session timed out. Initiating recovery sequence.');
this.attemptSessionRecovery().subscribe({
next: () => console.log('Session recovery successful'),
error: (err) => console.error('Session recovery failed after retries:', err)
});
}
private attemptSessionRecovery() {
return this.refreshSession().pipe(
retryWhen(errors => errors.pipe(
tap(error => {
if (error instanceof HttpErrorResponse && error.status === 429) {
console.warn('Rate limit (429) encountered. Backing off before retry.');
}
}),
delay(error => {
const retryCount = error.retries || 0;
const delayMs = this.BASE_DELAY_MS * Math.pow(2, retryCount);
return delayMs;
})
)),
catchError(error => {
if (error.status === 401 || error.status === 403) {
return throwError(() => new Error('Authentication failed. Token may be expired or invalid.'));
}
return throwError(() => error);
})
);
}
private refreshSession() {
const config = this.configService.validateAndBuild();
const url = `${config.apiUrl}/api/v1/chat/sessions/refresh`;
return this.http.post(url, {}, {
headers: {
'Authorization': `Bearer ${config.apiToken}`,
'Content-Type': 'application/json',
'X-Bot-Id': config.botId
}
}).pipe(
tap(() => this.updateState(true)),
retryWhen(errors => errors.pipe(
delay(1000),
tap((_, i) => {
if (i >= this.MAX_RETRIES) {
throw new Error('Maximum retry attempts reached');
}
})
))
);
}
}
The underlying HTTP request for session refresh follows this structure:
POST /api/v1/chat/sessions/refresh HTTP/1.1
Host: api.cognigy.ai
Authorization: Bearer <API_TOKEN>
Content-Type: application/json
X-Bot-Id: <BOT_ID>
Accept: application/json
{}
Expected successful response:
{
"sessionId": "sess_8f7d6c5b4a3e2d1c",
"expiresAt": "2024-01-15T14:30:00.000Z",
"isActive": true
}
The retry logic handles 429 rate limit responses by applying exponential backoff. Authentication errors (401/403) fail immediately to prevent unnecessary retries.
Step 3: Processing Results and Mapping Custom Variables
Cognigy bots rely on session variables for context. Map Angular application data to Cognigy variables using type-safe methods. The SDK abstracts the REST call, but understanding the underlying payload structure prevents serialization errors.
export class CognigyService {
// ... previous code ...
private readonly VARIABLE_LIMIT = 50;
private readonly MAX_VALUE_LENGTH = 1024;
setCustomVariable(key: string, value: string | number | boolean | object): void {
if (!this.chatInstance) {
console.error('SDK not initialized. Cannot set variable.');
return;
}
if (!key || key.length > 64) {
throw new Error('Variable key must be between 1 and 64 characters.');
}
const serializedValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
if (serializedValue.length > this.MAX_VALUE_LENGTH) {
console.warn(`Variable value for "${key}" exceeds limit. Truncating.`);
}
try {
this.chatInstance.setVariable(key, serializedValue);
console.log(`Variable "${key}" mapped successfully.`);
} catch (error) {
console.error(`Failed to set variable "${key}":`, error);
}
}
batchSetVariables(variables: Record<string, string | number | boolean | object>): void {
const validatedVars = Object.entries(variables)
.map(([k, v]) => ({
key: k,
value: typeof v === 'object' ? JSON.stringify(v) : String(v)
}))
.filter(v => v.key.length <= 64 && v.value.length <= this.MAX_VALUE_LENGTH);
if (validatedVars.length !== variables.length) {
console.warn('Some variables were filtered due to length constraints.');
}
if (validatedVars.length > this.VARIABLE_LIMIT) {
console.warn(`Batch exceeds limit of ${this.VARIABLE_LIMIT}. Processing first ${this.VARIABLE_LIMIT}.`);
}
validatedVars.forEach(({ key, value }) => {
this.setCustomVariable(key, value);
});
}
}
The underlying REST endpoint for variable mapping uses this structure:
POST /api/v1/chat/sessions/<SESSION_ID>/variables HTTP/1.1
Host: api.cognigy.ai
Authorization: Bearer <API_TOKEN>
Content-Type: application/json
X-Bot-Id: <BOT_ID>
[
{
"key": "userEmail",
"value": "developer@example.com",
"type": "string"
},
{
"key": "cartTotal",
"value": "149.99",
"type": "number"
}
]
Expected response:
{
"updated": 2,
"failed": 0,
"variables": [
{ "key": "userEmail", "status": "success" },
{ "key": "cartTotal", "status": "success" }
]
}
The service enforces length limits and type serialization to prevent payload rejection. Batch operations respect the platform variable limit and log truncation events.
Complete Working Example
Combine the service with a standalone Angular component that renders the widget container and manages user interactions.
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { CognigyService } from './cognigy.service';
import { CognigyConfigService } from './cognigy-config.service';
@Component({
selector: 'app-cognigy-chat',
standalone: true,
imports: [CommonModule],
template: `
<div class="chat-container" [class.active]="state?.isActive">
<div class="chat-header">
<h3>Support Assistant</h3>
<span class="status-indicator" [class.online]="state?.isActive" [class.offline]="!state?.isActive">
{{ state?.isActive ? 'Online' : 'Offline' }}
</span>
</div>
<div class="chat-body" #widgetContainer></div>
<div class="chat-controls">
<button (click)="sendTestVariable()" [disabled]="!state?.isActive">
Sync User Context
</button>
<button (click)="triggerTimeout()" [disabled]="!state?.isActive">
Simulate Timeout
</button>
</div>
</div>
`,
styles: [`
.chat-container { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }
.chat-container.active { border-color: #4caf50; }
.status-indicator { padding: 4px 8px; border-radius: 4px; font-size: 12px; }
.online { background: #e8f5e9; color: #2e7d32; }
.offline { background: #ffebee; color: #c62828; }
.chat-controls { margin-top: 12px; display: flex; gap: 8px; }
button { padding: 8px 12px; cursor: pointer; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
`]
})
export class CognigyChatComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
private cognigy = inject(CognigyService);
state: any = null;
ngOnInit(): void {
this.cognigy.state$.pipe(takeUntil(this.destroy$)).subscribe(state => {
this.state = state;
});
}
sendTestVariable(): void {
this.cognigy.batchSetVariables({
userRole: 'premium',
lastPageView: '/dashboard',
sessionContext: { timestamp: Date.now(), feature: 'chat-integration' }
});
}
triggerTimeout(): void {
// Force SDK timeout for testing recovery logic
if (this.cognigy['chatInstance']) {
this.cognigy['chatInstance'].emit?.('sessionTimeout');
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
Provide the configuration token in the application module or standalone bootstrap configuration:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { COGNIGY_CONFIG } from './cognigy-config.service';
import { CognigyChatComponent } from './cognigy-chat.component';
bootstrapApplication(CognigyChatComponent, {
providers: [
provideHttpClient(),
{
provide: COGNIGY_CONFIG,
useValue: {
apiUrl: 'https://api.cognigy.ai',
apiToken: 'YOUR_API_TOKEN_HERE',
botId: 'YOUR_BOT_ID_HERE',
language: 'en'
}
}
]
}).catch(err => console.error(err));
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The API token is expired, revoked, or lacks
chat:readandchat:writepermissions. - Fix: Regenerate the token in Cognigy Studio and verify permission scopes. Ensure the token matches the environment configuration.
- Code showing the fix:
if (error.status === 401) {
console.error('Token authentication failed. Verify permissions and expiration.');
// Redirect to token refresh flow or notify admin
}
Error: 403 Forbidden
- Cause: The
botIdin the configuration does not match the token scope, or the token belongs to a different workspace. - Fix: Cross-check the bot identifier against the token metadata in Cognigy Studio. Align workspace contexts.
- Code showing the fix:
if (error.status === 403) {
console.error('Bot ID mismatch or workspace permission denied.');
// Log configuration mismatch for audit
}
Error: 429 Too Many Requests
- Cause: Excessive variable updates or rapid session refresh calls trigger platform rate limits.
- Fix: Implement request throttling and exponential backoff. The provided retry logic handles this automatically.
- Code showing the fix:
retryWhen(errors => errors.pipe(
delay(error => {
const retryCount = error.retries || 0;
return 1000 * Math.pow(2, retryCount);
})
))
Error: Variable Serialization Failure
- Cause: Passing undefined, functions, or circular objects to
setVariable. - Fix: Validate input types before serialization. Use
JSON.stringifyonly for plain objects. - Code showing the fix:
if (typeof value === 'function' || value === undefined) {
throw new TypeError('Variable values must be primitive types or plain objects.');
}