Exposing the Customer Mapping API to CCXai
The requirement is to create a GET endpoint (/api/customer-mapping) to retrieve customer details for populating a dropdown in the Agent Widget. Below is a professional approach to designing and implementing this API:
API Design
- Endpoint: /api/customer-mapping
- Method: GET (restricted as specified)
Query Parameters
- ApplicationCode (string, required): Unique identifier for the tenant and application (e.g., CFSS_GGL2)
- CustomerType (string, required): Type of customer (e.g., Client, Employee, Staff, Teacher, Student).
- Response: A JSON array of customer details with CustomerId and CustomerName fields.
- HTTP Status: 200 OK for successful responses, with appropriate error codes (e.g., 400 for invalid parameters, 401 for unauthorized access, 500 for server errors).
Implementation in C#
Assuming a .NET Core/ASP.NET Core backend, the API can be implemented as follows:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TenantApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CustomerMappingController : ControllerBase
{
private readonly ICustomerService _customerService;
public CustomerMappingController(ICustomerService customerService)
{
_customerService = customerService;
}
[HttpGet]
public async Task<ActionResult<List<DropdownBO>>> GetCustomerMapping([FromQuery] string ApplicationCode, [FromQuery] string CustomerType)
{
if (string.IsNullOrEmpty(ApplicationCode) || string.IsNullOrEmpty(CustomerType))
{
return BadRequest("ApplicationCode and CustomerType are required.");
}
try
{
var customers = await _customerService.GetCustomerMappingsAsync(ApplicationCode, CustomerType);
return Ok(customers);
}
catch (Exception ex)
{
// Log the exception (use a logging framework like Serilog or NLog)
return StatusCode(500, "An error occurred while retrieving customer mappings.");
}
}
}
public class DropdownBO
{
public string CustomerId { get; set; }
public string CustomerName { get; set; }
}
public interface ICustomerService
{
Task<List<DropdownBO>> GetCustomerMappingsAsync(string applicationCode, string customerType);
}
public class CustomerService : ICustomerService
{
public async Task<List<DropdownBO>> GetCustomerMappingsAsync(string applicationCode, string customerType)
{
// Simulate database call or external service integration
// Replace with actual logic to fetch data based on ApplicationCode and CustomerType
return new List<DropdownBO>
{
new DropdownBO { CustomerId = "12345", CustomerName = "John Doe" },
new DropdownBO { CustomerId = "67890", CustomerName = "Jane Smith" }
};
}
}
}
Best Practices
- Validation: Validate query parameters to ensure ApplicationCode and CustomerType are provided and meet expected formats (e.g., regex for ApplicationCode to enforce {AppName}_{TenantCode}).
Security
Authentication: Secure the endpoint with an authentication mechanism (e.g., JWT tokens via OAuth 2.0 or API keys) to ensure only authorized CCX clients can access it. For example, integrate with Auth0 or Azure AD for tenant-based authentication
Authorization: Restrict access based on tenant-specific permissions, ensuring the ApplicationCode corresponds to the authenticated tenant
Data Isolation: Ensure tenant data is isolated by filtering results based on the ApplicationCode to prevent cross-tenant data leakage.
Error Handling: Return meaningful error messages and HTTP status codes. Log exceptions for debugging and monitoring.
Scalability: Cache frequently accessed customer mappings (e.g., using Redis or in-memory caching) to reduce database load for high-traffic scenarios.
Documentation: Use OpenAPI/Swagger to document the endpoint, including parameter descriptions, response models, and example responses.
Example Usage
API Request: Get Customer Mapping
Request:
GET /api/customer-mapping?ApplicationCode=CFSS_GGL2&CustomerType=Client
Expected Response (HTTP 200 OK):
[
{
"CustomerId": "12345",
"CustomerName": "John Doe"
},
{
"CustomerId": "67890",
"CustomerName": "Jane Smith"
}
]
Error Responses:
400 Bad Request (Missing Parameters):
{
"error": "ApplicationCode and CustomerType are required."
}
500 Internal Server Error:
{
"error": "An error occurred while retrieving customer mappings."
}
Response:
[
{
"CustomerId": "12345",
"CustomerName": "John Doe"
},
{
"CustomerId": "67890",
"CustomerName": "Jane Smith"
}
]
Internal APIs (If Required)
For internal use within the tenant application, you may need APIs to fetch the current user’s details (e.g., UserId, FullName, UserType, AppCode) to pass to the Agent and Customer Widgets. These APIs should not be exposed publicly and should be secured for internal use only.
Internal API Design
- Endpoint: /api/internal/user-details (example)
- Method: GET
- Response: JSON object containing user details.
Implementation
[Route("api/internal/[controller]")]
[ApiController]
[Authorize] // Requires authentication (e.g., JWT)
public class UserDetailsController : ControllerBase
{
private readonly IUserService _userService;
public UserDetailsController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public async Task> GetUserDetails()
{
try
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Unauthorized("User not authenticated.");
}
var userDetails = await _userService.GetUserDetailsAsync(userId);
return Ok(userDetails);
}
catch (Exception ex)
{
// Log exception
return StatusCode(500, "An error occurred while retrieving user details.");
}
}
}
public class UserDetailsBO
{
public string UserId { get; set; }
public string FullName { get; set; }
public string UserType { get; set; }
public string AppCode { get; set; }
public Dictionary AdditionalDetails { get; set; }
}
public interface IUserService
{
Task GetUserDetailsAsync(string userId);
}
public class UserService : IUserService
{
public async Task GetUserDetailsAsync(string userId)
{
// Simulate fetching user details from database or identity provider
return new UserDetailsBO
{
UserId = userId,
FullName = "John Doe",
UserType = "EMPLOYEE",
AppCode = "CFSS_ABC",
AdditionalDetails = new Dictionary
{
{ "phone", "+1-234-567-890" },
{ "email", "johndoe@example.com" }
}
};
}
}
Best Practices
- Security: Use internal network restrictions (e.g., private subnets, VPC endpoints) and authentication to prevent external access.
- Performance: Optimize database queries and consider caching user details for frequently accessed users.
- Extensibility: Allow AdditionalDetails to be flexible (e.g., using a dictionary or JSON) to accommodate future requirements.
Tenant App UI Development (Angular)
Project Setup
To integrate the Agent and Customer Widgets in an Angular application, follow these steps to set up the project and ensure compatibility with the provided requirements.Prerequisites
Install the required dependency for widget interactions:
npm install interactjs
This library enables drag, resize, and move functionality for the widgets.
ng new tenant-app --routing --style=css
Folder structure as specified:
src/
├── app/
│ ├── components/
│ │ ├── header/ # Shared UI controls
│ │ ├── agent-widget/ # Agent interaction component
│ │ ├── customer-widget/ # Customer interaction component
│ ├── global/ # Global settings and shared logic
│ ├── service/
│ │ ├── iframe.ts # Iframe communication service
│ │ ├── widgetreset.service.ts # Widget state reset service
│ ├── environment.service.ts # Environment-specific variables
│ ├── app.settings.ts # App-wide configuration
│ ├── app.settings.model.ts # Type model for settings
Configuration Files
- app.settings.model.ts:
export interface AppSettings {
appColor: string;
appCode: string;
apiBaseUrl: string;
// Add other settings as needed
}
- app.settings.ts:
import { AppSettings } from './app.settings.model';
export const APP_SETTINGS: AppSettings = {
appColor: '#00786a',
appCode: 'CFSS_ABC',
apiBaseUrl: 'https://api.tenant-app.com'
};
- environment.service.ts
import { Injectable } from '@angular/core';
import { APP_SETTINGS } from './app.settings';
@Injectable({ providedIn: 'root' })
export class EnvironmentService {
getSettings() {
return APP_SETTINGS;
}
}
Widget Integration
The Agent and Customer Widgets are embedded via iframes, and data is passed using URL query parameters. Below is the implementation for both widgets.
Customer Widget
The Customer Widget requires passing AppColor, CustomerAppCode, CustomerType, CustomerId, CustomerName, and CustomerDetails.
Component (customer-widget.component.ts)
import { Component, OnInit } from '@angular/core';
import { EnvironmentService } from '../environment.service';
import { GlobalService } from '../global/global.service';
@Component({
selector: 'app-customer-widget',
templateUrl: './customer-widget.component.html'
})
export class CustomerWidgetComponent implements OnInit {
iframeUrl: string;
constructor(
private envService: EnvironmentService,
private globalService: GlobalService
) {}
ngOnInit() {
const settings = this.envService.getSettings();
const customerDetails = {
phone: '+1-234-567-890',
email: 'johndoe@example.com',
address: '123 Main St, Houston, TX'
};
const params = new URLSearchParams();
params.append('AppColor', settings.appColor);
params.append('CustomerAppCode', `CFSS_${this.globalService.cfssOnboardAgencyCode}`);
params.append('CustomerType', 'CLIENT');
params.append('CustomerId', this.globalService.cfssLoginEntityId);
params.append('CustomerName', this.globalService.fullName);
params.append('CustomerDetails', JSON.stringify(customerDetails));
this.iframeUrl = `${settings.apiBaseUrl}/customer-widget?${params.toString()}`;
}
}
Template (customer-widget.component.html)
<iframe [src]="iframeUrl | safe: 'url'" width="100%" height="400px" frameborder="0"></iframe>
Safe Pipe (safe.pipe.ts)
To sanitize the iframe URL:
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
@Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url: string, type: string): SafeUrl {
return this.sanitizer.bypassSecurityTrustUrl(url);
}
}
Agent Widget
The Agent Widget follows a similar approach, passing AppColor, AgentAppCode, AgentType, AgentID, AgentName, and AgentDetails.
Component (agent-widget.component.ts)
import { Component, OnInit } from '@angular/core';
import { EnvironmentService } from '../environment.service';
import { GlobalService } from '../global/global.service';
@Component({
selector: 'app-agent-widget',
templateUrl: './agent-widget.component.html'
})
export class AgentWidgetComponent implements OnInit {
iframeUrl: string;
constructor(
private envService: EnvironmentService,
private globalService: GlobalService
) {}
ngOnInit() {
const settings = this.envService.getSettings();
const agentDetails = {
phone: '+1-234-567-890',
email: 'johndoe@example.com',
address: '123 Main St, Houston, TX'
};
const params = new URLSearchParams();
params.append('AppColor', settings.appColor);
params.append('AgentAppCode', `CFSS_${this.globalService.agencyCode}`);
params.append('AgentType', 'EMPLOYEE');
params.append('AgentID', this.globalService.userID);
params.append('AgentName', this.globalService.fullName);
params.append('AgentDetails', JSON.stringify(agentDetails));
this.iframeUrl = `${settings.apiBaseUrl}/agent-widget?${params.toString()}`;
}
}
Template (agent-widget.component.html)
<iframe [src]="iframeUrl | safe: 'url'" width="100%" height="400px" frameborder="0"></iframe>
Iframe Communication Service (iframe.ts)
To enable communication between the iframe and parent application:
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class IframeService {
private messageSubject = new Subject();
constructor() {
window.addEventListener('message', (event) => {
if (event.origin === 'https://api.tenant-app.com') { // Validate origin
this.messageSubject.next(event.data);
}
});
}
sendMessage(message: any) {
const iframe = document.querySelector('iframe');
iframe?.contentWindow?.postMessage(message, 'https://api.tenant-app.com');
}
getMessages() {
return this.messageSubject.asObservable();
}
}
Widget Reset Service (widgetreset.service.ts)
To reset widget state:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class WidgetResetService {
resetWidget(iframeId: string) {
const iframe = document.getElementById(iframeId) as HTMLIFrameElement;
if (iframe) {
iframe.src = iframe.src; // Reload iframe to reset state
}
}
}
Global Service (global/global.service.ts)
To manage global variables like cfssLoginEntityId, fullName, etc.:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class GlobalService {
cfssLoginEntityId = '30051';
fullName = 'John Doe';
cfssOnboardAgencyCode = 'ABC';
agencyCode = 'ABC';
userID = 'ab7c094c-2ecd-4f68-b70e-6c1728fa9eb4';
}
Widget Interactivity with Interact.js
To enable drag, resize, and move functionality for widgets:
import interact from 'interactjs';
export class WidgetComponent implements OnInit {
ngOnInit() {
interact('.widget')
.draggable({
listeners: {
move: (event) => {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0 Multi-Tenancy Considerations
For multi-tenant support in the Angular UI:
- Tenant Identification:
- Use the ApplicationCode (e.g., CFSS_ABC) to identify the tenant, extracted from the URL or a configuration file.
- Configure the environment.prod.ts to support tenant-specific URLs
export const environment = {
production: true,
application: {
baseUrl: 'https://{0}.mydomain.com/'
}
};
- Tenant Switching: Implement a tenant-switching component in the UI to allow users to switch between tenants if needed. Hide the switching component if using a tenant-specific URL with the {0} placeholder.
- Data Isolation: Ensure the CustomerAppCode and AgentAppCode are used in API calls to filter data by tenant, preventing cross-tenant data access.
Best Practices for UI Development
- Modularity: Keep components modular (e.g., separate agent-widget and customer-widget) for reusability and maintainability.
- Security:
- Sanitize iframe URLs to prevent XSS attacks using the SafePipe.
- Validate postMessage origins to ensure secure communication between the iframe and parent.
- Performance: Use Angular’s ChangeDetectionStrategy.OnPush for widget components to optimize rendering performance.
- Error Handling: Implement error boundaries in components to handle API or iframe failures gracefully.
- Testing: Write unit tests for services (e.g., IframeService, WidgetResetService) and components using Jasmine/Karma, and consider end-to-end tests with Cypress for widget interactions.
Additional Considerations
- Scalability:
- For high-traffic applications, consider load balancing the API backend and caching customer mappings.
- Use lazy loading for Angular modules to improve frontend performance.
Monitoring and Logging:- Implement logging for API requests and iframe communications to troubleshoot issues.
- Use tools like Application Insights or ELK Stack for monitoring.
Documentation:- Document the API using Swagger and provide a developer guide for widget integration.
- Include examples for single-tenant and multi-tenant scenarios.
- Extensibility:
- Design the CustomerDetails and AgentDetails JSON structures to be flexible for future additions.
- Allow configuration of additional widget parameters via app.settings.ts.
Sample Integration Flow
- User Logs In:
- The application authenticates the user (e.g., via Auth0 or Azure AD) and retrieves UserId, FullName, UserType, and AppCode from the internal API or identity provider.
- Widget Initialization:
- The Angular app constructs the iframe URLs for the Agent and Customer Widgets, appending the required parameters.
- The IframeService handles two-way communication for data sharing.
- Customer Mapping:
- The Agent Widget calls the /api/customer-mapping endpoint with the appropriate ApplicationCode and CustomerType to populate the dropdown.
- Interactivity:
- Users can drag, resize, or move widgets using Interact.js.
- The WidgetResetService resets widget state when needed.
This implementation provides a robust, secure, and scalable solution for integrating the tenant application with the Agent and Customer Widgets while adhering to multi-tenancy best practices.
Prerequisites
WordPress Environment: Ensure your site runs WordPress 6.0+ (recommended for security and compatibility, though not explicitly stated). Test on a staging site first.
CCXAI Account: Register at https://ccxai.com and obtain your Company Name and Identifier Code from the CCXAI dashboard. These are essential for authentication.
Server Requirements: PHP 7.4+ (common for modern WP plugins), with cURL enabled for iframe communication. No additional libraries are needed.
Development Tools: Access to FTP/SFTP for manual uploads, or use the WP dashboard. For customization, you’ll need knowledge of PHP, CSS, and WordPress hooks/filters (though the plugin doesn’t expose many).
Installation
Follow these steps to install the plugin:
Via WordPress Dashboard
- Log in to your WP admin panel
- Navigate to Plugins > Add New.
- Search for "ccxai-chat-widget" or "SBNA Chat Widget for CCXAI".
- Click Install Now, then Activate.
Manual Upload
- Download the plugin ZIP from https://wordpress.org/plugins/ccxai-chat-widget/.
- Unzip it to a folder named ccxai-chat-widget.
- Upload the folder to /wp-content/plugins/ via FTP.
- Activate the plugin in Plugins > Installed Plugins.
Verification:
- After activation, check for a new menu item: Settings > SBNA Chat
- If errors occur (e.g., missing dependencies), review server logs or deactivate conflicting plugins.
Configuration
The plugin provides a simple admin panel for setup:
Access Settings
- Go to Settings > SBNA Chat
Enter Credentials:
- Company Name: Input your CCXAI-registered company name.
- Identifier Code: Paste the unique code from your CCXAI dashboard.
- Save changes. This authenticates the plugin with CCXAI and enables the iframe-based chat widget.
Widget Placement
- The widget automatically appears on your site (likely in the footer or a fixed position, based on typical chat plugins). No shortcodes or widgets are mentioned, so it may be globally enabled.
- Test on frontend pages to ensure it loads. The widget triggers on user interaction, sending data like a UUID (LoginUserId) for session management.
User Consent and Data Handling:
- The plugin includes a GDPR-compliant consent checkbox before chat starts.
- Optional fields (name, email, phone) can be submitted by users.
- Ensure your site's privacy policy references CCXAI's policies.
Customization
Customization is limited but feasible through CSS and potential code modifications:
Styling with Custom CSS
- The plugin supports custom CSS for theming. Add styles via Appearance > Customize > Additional CSS or a child theme's stylesheet.
- Target classes like .ccxai-chat-widget (inspect the widget in browser dev tools to confirm selectors).
- Example CSS for a custom theme color:
.ccxai-chat-widget {
--primary-color: #00786a; /* Replace with your brand color */
background-color: var(--primary-color);
border-radius: 10px;
}
.ccxai-chat-widget button {
color: white;
background-color: var(--primary-color);
}
Future updates may add built-in styling options in the admin panel. Positioning and Visibility:
- If the widget is fixed-positioned, use CSS to adjust (e.g., bottom: 20px; right: 20px;).
- For conditional display (e.g., only on specific pages), enqueue a script in your theme's functions.php:
add_action('wp_enqueue_scripts', 'custom_ccxai_widget_visibility');
function custom_ccxai_widget_visibility() {
if (is_page('contact')) { // Show only on 'Contact' page
wp_enqueue_style('ccxai-hide', get_stylesheet_directory_uri() . '/ccxai-hide.css');
}
}
In ccxai-hide.css: .ccxai-chat-widget { display: none; } (reverse for showing). Extending Functionality:
- No Built-in Hooks/APIs: The plugin doesn't document WordPress hooks, filters, or REST APIs. For advanced integration (e.g., passing user data from WP to the widget), inspect the plugin's PHP files (e.g., ccxai-chat-widget.php) and add custom filters if needed.
- Iframe Communication: The chat loads in an iframe from CCXAI. Use JavaScript's postMessage for cross-origin communication if extending (e.g., pre-filling user info):
// In your theme's JS file
window.addEventListener('message', function(event) {
if (event.origin === 'https://ccxai.com') {
// Handle messages from iframe
}
});
// Send data to iframe
const iframe = document.querySelector('iframe[src*="ccxai.com"]');
iframe.contentWindow.postMessage({ userName: 'display_name; ?>' }, 'https://ccxai.com');
Enqueue this script via functions.php. Integration with Other Systems
- CCXAI API Tie-Ins: If you have access to CCXAI's APIs (not detailed in the plugin), extend the widget to fetch customer mappings (e.g., via a custom endpoint like /api/customer-mapping). Reference the plugin's iframe params for alignment.
- Theme/Plugin Compatibility: Test with popular themes (e.g., Astra, Elementor). If conflicts arise (e.g., JS errors), use WP's wp_enqueue_script to manage dependencies.
Analytics Tracking: Add Google Analytics events for chat interactions:
document.querySelector('.ccxai-chat-widget').addEventListener('click', function() {
gtag('event', 'chat_open', { 'event_category': 'engagement' });
});
Custom Development: Since it's open-source, fork the GitHub repo (if available via CCXAI) or modify locally. Add features like agent-side widgets by integrating with CCXAI's tenant app guidelines. Testing and Debugging
- Frontend Testing: Use browser dev tools to inspect the iframe and console for errors (e.g., authentication failures).
- Security Scans: Run tools like WPScan to check for vulnerabilities, especially post-version 1.1.6 updates.
- Performance: Monitor load times; the lightweight design should not impact speed, but optimize if using on high-traffic sites.
- Error Handling: If the widget fails to load, check WP error logs (wp-content/debug.log – enable via wp-config.php: define('WP_DEBUG', true);).
Maintenance and Updates
- Updates: Regularly check for plugin updates via the WP dashboard. Review changelogs for security fixes.
- Support: Contact SBNA via https://ccxai.com for issues. Contribute to the open-source repo if enhancements are needed.
- Deprecation Risks: Monitor CCXAI's service changes, as the plugin depends on their backend.