Client Implementation Approaches
This guide covers how to implement ATP clients across different platforms and environments, including mobile apps, web applications, desktop software, and command-line tools.Mobile Implementations
Native Mobile Integration
Integrating ATP notifications into mobile applications provides a seamless experience for end users.Setting Up the Client
- Android (Kotlin)
- iOS (Swift)
- Flutter
- React Native
Copy
// Add to your app-level build.gradle
dependencies {
implementation("io.atp:android-client:1.0.0")
}
// Initialize client in your Application class
class MyApplication : Application() {
lateinit var atpClient: ATPClient
override fun onCreate() {
super.onCreate()
atpClient = ATPClient.Builder(this)
.apiKey("your_client_api_key")
.endpoint("https://api.atp.example.com")
.build()
}
}
Setting Up Push Notifications
To receive notifications in real-time, configure push notifications:- Android (Kotlin)
- iOS (Swift)
- React Native
Copy
// In your application class
override fun onCreate() {
super.onCreate()
// Initialize Firebase Messaging
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
// Register FCM token with ATP
atpClient.registerPushToken(token, "fcm")
}
}
}
// Create a Firebase Messaging Service
class ATPMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Check if it's an ATP notification
if (remoteMessage.data.containsKey("atp_notification_id")) {
val notificationId = remoteMessage.data["atp_notification_id"]
val app = applicationContext as MyApplication
// Fetch the full notification
app.atpClient.getNotification(notificationId) { notification ->
// Display the notification
showNotification(notification)
}
}
}
}
Displaying Notifications
Creating a custom UI for ATP notifications:- Android (Kotlin)
- iOS (Swift)
- React Native
Copy
// Create a notification view
class NotificationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
// Get notification ID from intent
val notificationId = intent.getStringExtra("notification_id") ?: return
// Get the application instance
val app = application as MyApplication
// Fetch notification details
app.atpClient.getNotification(notificationId) { notification ->
// Update UI
findViewById<TextView>(R.id.notification_title).text = notification.title
findViewById<TextView>(R.id.notification_description).text = notification.description
// Create action buttons dynamically
val actionsContainer = findViewById<LinearLayout>(R.id.actions_container)
notification.actions.forEach { action ->
val button = Button(this)
button.text = action.label
button.setOnClickListener {
handleAction(notificationId, action)
}
actionsContainer.addView(button)
}
}
}
private fun handleAction(notificationId: String, action: ATPAction) {
val app = application as MyApplication
when (action.responseType) {
"simple" -> {
// Simple action - just submit the response
app.atpClient.respondToNotification(
notificationId = notificationId,
actionId = action.id,
responseData = null
)
}
"text" -> {
// Show text input dialog
val editText = EditText(this)
AlertDialog.Builder(this)
.setTitle(action.label)
.setView(editText)
.setPositiveButton("Submit") { _, _ ->
app.atpClient.respondToNotification(
notificationId = notificationId,
actionId = action.id,
responseData = editText.text.toString()
)
}
.setNegativeButton("Cancel", null)
.show()
}
// Handle other response types...
}
}
}
Background Processing
For handling notifications when the app is in the background:- Android (Kotlin)
- iOS (Swift)
Copy
// Create a WorkManager task
class NotificationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val notificationId = inputData.getString("notification_id") ?: return Result.failure()
// Get the application instance
val app = applicationContext as MyApplication
// Process the notification
return try {
val notification = app.atpClient.getNotificationSync(notificationId)
// Create and show a system notification
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(notification.title)
.setContentText(notification.description)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Create an intent to open the notification detail activity
val intent = Intent(applicationContext, NotificationActivity::class.java).apply {
putExtra("notification_id", notificationId)
}
val pendingIntent = PendingIntent.getActivity(
applicationContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
)
builder.setContentIntent(pendingIntent)
// Show the notification
val notificationManager = NotificationManagerCompat.from(applicationContext)
notificationManager.notify(notificationId.hashCode(), builder.build())
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
Web Implementations
Browser-Based Clients
Implementing ATP in web applications:- JavaScript
- React
- Vue
Copy
// Install with npm or yarn
// npm install @atp/browser-client
import { ATPClient } from '@atp/browser-client';
// Initialize the client
const atpClient = new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com',
userId: 'current_user_id' // If available
});
// Set up notification polling or WebSocket connection
atpClient.connectToNotificationStream({
onNotification: (notification) => {
// Display the notification
showNotification(notification);
},
onError: (error) => {
console.error('ATP connection error:', error);
}
});
// Display notification function
function showNotification(notification) {
// Create notification UI
const notificationElement = document.createElement('div');
notificationElement.className = 'atp-notification';
// Add title and description
notificationElement.innerHTML = `
<div class="notification-header">
<h3>${notification.title}</h3>
<button class="close-btn">×</button>
</div>
<p>${notification.description}</p>
<div class="notification-actions"></div>
`;
// Add action buttons
const actionsContainer = notificationElement.querySelector('.notification-actions');
notification.actions.forEach(action => {
const button = document.createElement('button');
button.textContent = action.label;
button.addEventListener('click', () => {
handleAction(notification.id, action);
});
actionsContainer.appendChild(button);
});
// Add close button handler
const closeBtn = notificationElement.querySelector('.close-btn');
closeBtn.addEventListener('click', () => {
notificationElement.remove();
});
// Add to notification container
document.getElementById('notification-container').appendChild(notificationElement);
}
// Handle notification actions
function handleAction(notificationId, action) {
switch (action.response_type) {
case 'simple':
// Simple action - just submit the response
atpClient.respondToNotification(notificationId, action.id)
.then(() => {
// Remove the notification
document.querySelector(`[data-notification-id="${notificationId}"]`).remove();
})
.catch(error => {
console.error('Failed to submit response:', error);
});
break;
case 'text':
// Show text input dialog
const textInput = prompt(action.response_options?.placeholder || 'Enter your response:');
if (textInput !== null) {
atpClient.respondToNotification(notificationId, action.id, textInput)
.then(() => {
// Remove the notification
document.querySelector(`[data-notification-id="${notificationId}"]`).remove();
})
.catch(error => {
console.error('Failed to submit response:', error);
});
}
break;
// Handle other response types...
}
}
Web Push Notifications
Implementing browser push notifications for ATP:- JavaScript
Copy
// Request push notification permission
async function registerForPushNotifications() {
// Check if the browser supports service workers and push notifications
if ('serviceWorker' in navigator && 'PushManager' in window) {
try {
// Register service worker
const registration = await navigator.serviceWorker.register('/service-worker.js');
// Request permission
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
throw new Error('Permission not granted for notifications');
}
// Get push subscription
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('your_vapid_public_key')
});
// Register the subscription with ATP
const atpClient = new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
});
await atpClient.registerPushSubscription(subscription.toJSON());
return {
success: true,
subscription
};
} catch (error) {
console.error('Failed to register for push notifications:', error);
return {
success: false,
error
};
}
} else {
return {
success: false,
error: new Error('Push notifications not supported')
};
}
}
// Helper function to convert base64 to Uint8Array
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
- JavaScript (service-worker.js)
Copy
// service-worker.js
self.addEventListener('push', function(event) {
// Parse the notification data
const data = event.data.json();
// Check if it's an ATP notification
if (data.type === 'atp_notification') {
// Extract notification details
const { notification_id, title, body } = data;
// Show the notification
const showNotification = self.registration.showNotification(title, {
body,
icon: '/icon.png',
badge: '/badge.png',
data: {
atp_notification_id: notification_id
},
actions: [
{
action: 'open',
title: 'View Details'
}
]
});
event.waitUntil(showNotification);
}
});
self.addEventListener('notificationclick', function(event) {
event.notification.close();
// Get the notification data
const notificationData = event.notification.data;
if (notificationData && notificationData.atp_notification_id) {
// Open a window to show the notification details
const url = `/notifications/${notificationData.atp_notification_id}`;
event.waitUntil(
clients.matchAll({ type: 'window' }).then(windowClients => {
// Check if there is already a window/tab open with the target URL
for (let i = 0; i < windowClients.length; i++) {
const client = windowClients[i];
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// If no window/tab is open, open a new one
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
}
});
Desktop Application Integration
Electron Applications
Implementing ATP in Electron desktop applications:- JavaScript (Electron)
Copy
// main.js
const { app, BrowserWindow, ipcMain, Notification } = require('electron');
const { ATPClient } = require('@atp/node-client');
let mainWindow;
let atpClient;
// Initialize ATP client
function initializeATP() {
atpClient = new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
});
// Set up notification stream
atpClient.connectToNotificationStream({
onNotification: (notification) => {
// Show system notification
showSystemNotification(notification);
// Send to renderer process
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('atp-notification', notification);
}
},
onError: (error) => {
console.error('ATP connection error:', error);
}
});
}
function showSystemNotification(notification) {
// Check if Electron notifications are supported
if (!Notification.isSupported()) {
return;
}
const systemNotification = new Notification({
title: notification.title,
body: notification.description,
icon: './app-icon.png'
});
systemNotification.on('click', () => {
// Focus the app and show the notification
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
// Show notification details
mainWindow.webContents.send('show-notification-details', notification);
}
});
systemNotification.show();
}
app.on('ready', () => {
// Create main window
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.loadFile('index.html');
// Initialize ATP after the app is ready
initializeATP();
});
// Handle response submission from renderer
ipcMain.on('submit-notification-response', (event, data) => {
const { notificationId, actionId, responseData } = data;
atpClient.respondToNotification(notificationId, actionId, responseData)
.then(result => {
event.reply('response-submitted', { success: true, notificationId });
})
.catch(error => {
console.error('Failed to submit response:', error);
event.reply('response-submitted', {
success: false,
notificationId,
error: error.message
});
});
});
- JavaScript (Electron Renderer)
Copy
// renderer.js
const { ipcRenderer } = require('electron');
// Notification container element
const notificationContainer = document.getElementById('notification-container');
// Listen for ATP notifications
ipcRenderer.on('atp-notification', (event, notification) => {
// Create notification element
const notificationElement = createNotificationElement(notification);
// Add to container
notificationContainer.appendChild(notificationElement);
});
// Listen for show notification details request
ipcRenderer.on('show-notification-details', (event, notification) => {
// Focus or create notification element
let notificationElement = document.querySelector(`[data-notification-id="${notification.id}"]`);
if (!notificationElement) {
notificationElement = createNotificationElement(notification);
notificationContainer.appendChild(notificationElement);
}
// Scroll to element and highlight it
notificationElement.scrollIntoView({ behavior: 'smooth' });
notificationElement.classList.add('highlight');
// Remove highlight after animation
setTimeout(() => {
notificationElement.classList.remove('highlight');
}, 2000);
});
// Create notification element
function createNotificationElement(notification) {
const element = document.createElement('div');
element.className = 'atp-notification';
element.dataset.notificationId = notification.id;
// Add title and description
element.innerHTML = `
<div class="notification-header">
<h3>${notification.title}</h3>
<button class="close-btn">×</button>
</div>
<p>${notification.description}</p>
<div class="notification-actions"></div>
`;
// Add action buttons
const actionsContainer = element.querySelector('.notification-actions');
notification.actions.forEach(action => {
const actionElement = createActionElement(notification.id, action);
actionsContainer.appendChild(actionElement);
});
// Add close button handler
const closeBtn = element.querySelector('.close-btn');
closeBtn.addEventListener('click', () => {
element.remove();
});
return element;
}
// Create action element based on response type
function createActionElement(notificationId, action) {
const container = document.createElement('div');
container.className = 'action-container';
switch (action.response_type) {
case 'simple':
const button = document.createElement('button');
button.textContent = action.label;
button.addEventListener('click', () => {
submitResponse(notificationId, action.id);
});
container.appendChild(button);
break;
case 'text':
const input = document.createElement('input');
input.type = 'text';
input.placeholder = action.response_options?.placeholder || 'Enter your response';
const submitBtn = document.createElement('button');
submitBtn.textContent = action.label;
submitBtn.addEventListener('click', () => {
submitResponse(notificationId, action.id, input.value);
});
container.appendChild(input);
container.appendChild(submitBtn);
break;
// Handle other response types...
}
return container;
}
// Submit response to main process
function submitResponse(notificationId, actionId, responseData = null) {
ipcRenderer.send('submit-notification-response', {
notificationId,
actionId,
responseData
});
}
// Handle response submission result
ipcRenderer.on('response-submitted', (event, result) => {
if (result.success) {
// Remove the notification element
const notificationElement = document.querySelector(
`[data-notification-id="${result.notificationId}"]`
);
if (notificationElement) {
notificationElement.remove();
}
} else {
// Show error message
alert(`Failed to submit response: ${result.error}`);
}
});
CLI Integration
Command Line Tools
Implementing ATP in command-line applications:- Node.js CLI
- Python CLI
Copy
#!/usr/bin/env node
const { ATPClient } = require('@atp/node-client');
const inquirer = require('inquirer');
const notifier = require('node-notifier');
const path = require('path');
const fs = require('fs');
const os = require('os');
// Configuration file path
const configPath = path.join(os.homedir(), '.atpcli.json');
// Load or create configuration
function loadConfig() {
try {
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
} catch (error) {
console.error('Error loading config:', error.message);
}
return { apiKey: '', endpoint: 'https://api.atp.example.com' };
}
// Save configuration
function saveConfig(config) {
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
} catch (error) {
console.error('Error saving config:', error.message);
}
}
// Initialize ATP client
async function initializeClient() {
let config = loadConfig();
// If no API key, prompt for it
if (!config.apiKey) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'apiKey',
message: 'Enter your ATP API key:',
validate: input => input.trim() !== '' ? true : 'API key is required'
}
]);
config.apiKey = answers.apiKey;
saveConfig(config);
}
return new ATPClient({
apiKey: config.apiKey,
endpoint: config.endpoint
});
}
// Display a desktop notification
function showDesktopNotification(notification) {
notifier.notify({
title: notification.title,
message: notification.description,
icon: path.join(__dirname, 'icon.png'),
sound: true,
wait: true
});
notifier.on('click', () => {
// Show notification details and prompt for response
displayNotificationDetails(notification);
});
}
// Display notification details in console
async function displayNotificationDetails(notification) {
console.log('\n-----------------------------------');
console.log(`Notification: ${notification.title}`);
console.log('-----------------------------------');
console.log(notification.description);
console.log('-----------------------------------');
// Create action choices for inquirer
const choices = notification.actions.map(action => ({
name: action.label,
value: action
}));
// Prompt user to select an action
const { selectedAction } = await inquirer.prompt([
{
type: 'list',
name: 'selectedAction',
message: 'Select an action:',
choices
}
]);
// Handle the selected action
await handleAction(notification.id, selectedAction);
}
// Handle action based on response type
async function handleAction(notificationId, action) {
const client = await initializeClient();
switch (action.response_type) {
case 'simple':
// Simple action - just submit the response
await client.respondToNotification(notificationId, action.id);
console.log('Response submitted successfully!');
break;
case 'text':
// Prompt for text input
const { textResponse } = await inquirer.prompt([
{
type: 'input',
name: 'textResponse',
message: action.response_options?.placeholder || 'Enter your response:',
validate: input => {
if (action.response_options?.required && input.trim() === '') {
return 'Response is required';
}
return true;
}
}
]);
await client.respondToNotification(notificationId, action.id, textResponse);
console.log('Response submitted successfully!');
break;
case 'select':
// Prompt for selection
const choices = action.response_options?.options.map(option => ({
name: option.label,
value: option.value
}));
const { selectedOption } = await inquirer.prompt([
{
type: 'list',
name: 'selectedOption',
message: 'Select an option:',
choices
}
]);
await client.respondToNotification(notificationId, action.id, selectedOption);
console.log('Response submitted successfully!');
break;
// Handle other response types...
default:
console.log(`Response type '${action.response_type}' is not supported in CLI mode`);
}
}
// Main function
async function main() {
try {
const client = await initializeClient();
// Parse command line arguments
const args = process.argv.slice(2);
const command = args[0];
if (command === 'listen') {
// Start listening for notifications
console.log('Listening for ATP notifications. Press Ctrl+C to exit.');
client.connectToNotificationStream({
onNotification: notification => {
showDesktopNotification(notification);
console.log(`\nNew notification: ${notification.title}`);
},
onError: error => {
console.error('ATP connection error:', error);
}
});
// Keep the process running
process.stdin.resume();
} else if (command === 'list') {
// List recent notifications
const notifications = await client.getRecentNotifications();
if (notifications.length === 0) {
console.log('No recent notifications.');
return;
}
console.log('\nRecent notifications:');
notifications.forEach((notification, index) => {
console.log(`${index + 1}. ${notification.title} (${notification.id})`);
});
// Prompt to select a notification
const { selectedIndex } = await inquirer.prompt([
{
type: 'list',
name: 'selectedIndex',
message: 'Select a notification to view:',
choices: notifications.map((n, i) => ({
name: n.title,
value: i
}))
}
]);
displayNotificationDetails(notifications[selectedIndex]);
} else {
// Show help
console.log('Usage:');
console.log(' atp-cli listen Listen for new notifications');
console.log(' atp-cli list List recent notifications');
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
// Run the main function
main();
Verification Examples
Let’s verify that our implementations work correctly with these test examples:Mobile Verification
- Android
- iOS
Copy
// Add this function to your activity or fragment for testing
fun verifyATPImplementation() {
val app = application as MyApplication
// 1. Test client initialization
if (app.atpClient == null) {
Log.e("ATPVerification", "ATP client is not initialized")
return
}
// 2. Test FCM token registration
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
app.atpClient.registerPushToken(token, "fcm", { success ->
Log.d("ATPVerification", "FCM token registration: ${if (success) "SUCCESS" else "FAILED"}")
})
} else {
Log.e("ATPVerification", "Failed to get FCM token")
}
}
// 3. Test notification rendering with a mock notification
val mockNotification = ATPNotification(
id = "test_notification_id",
title = "Test Notification",
description = "This is a test notification to verify the ATP implementation",
priority = "normal",
actions = listOf(
ATPAction(
id = "test_action",
label = "Test Action",
responseType = "simple"
),
ATPAction(
id = "text_action",
label = "Test Text Response",
responseType = "text",
responseOptions = mapOf(
"placeholder" to "Enter test response"
)
)
)
)
// Display the mock notification to verify UI
showNotification(mockNotification)
}
Web Verification
- JavaScript
Copy
// Add this function to your web app for testing
function verifyATPImplementation() {
console.log('Starting ATP verification...');
// 1. Verify client initialization
if (!window.atpClient) {
console.error('ATP client is not initialized');
return;
}
console.log('ATP client initialized successfully');
// 2. Test notification stream connection
try {
const connection = window.atpClient.connectToNotificationStream({
onNotification: (notification) => {
console.log('Test notification stream works!', notification);
// Disconnect after successful test
connection.disconnect();
},
onError: (error) => {
console.error('ATP connection error:', error);
}
});
console.log('Notification stream connection established');
} catch (error) {
console.error('Failed to connect to notification stream:', error);
}
// 3. Test notification rendering with a mock notification
const mockNotification = {
id: 'test_notification_id',
title: 'Test Notification',
description: 'This is a test notification to verify the ATP implementation',
priority: 'normal',
actions: [
{
id: 'test_action',
label: 'Test Action',
response_type: 'simple'
},
{
id: 'text_action',
label: 'Test Text Response',
response_type: 'text',
response_options: {
placeholder: 'Enter test response'
}
}
]
};
// Display the mock notification to verify UI
showNotification(mockNotification);
console.log('Mock notification displayed for verification');
// 4. Test response submission with a mock response
setTimeout(() => {
console.log('Testing response submission...');
window.atpClient.respondToNotification('test_notification_id', 'test_action')
.then(() => {
console.log('Test response submission successful!');
})
.catch(error => {
console.error('Test response submission failed:', error);
});
}, 5000);
}
Verification: Knowledge Check
Before proceeding, let’s verify your understanding of ATP client implementation:Next Steps
Now that you’ve learned how to implement ATP clients across different platforms, explore these additional resources:Best Practices
Learn recommended patterns and practices for robust ATP implementations
Troubleshooting
Common issues and solutions for ATP client implementations