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()
}
}
Copy
// In your AppDelegate or SceneDelegate
import ATPClient
class AppDelegate: UIResponder, UIApplicationDelegate {
var atpClient: ATPClient!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize ATP client
atpClient = ATPClient(
apiKey: "your_client_api_key",
endpoint: "https://api.atp.example.com"
)
return true
}
}
Copy
// Add to pubspec.yaml
dependencies:
atp_client: ^1.0.0
// Initialize client
import 'package:atp_client/atp_client.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final atpClient = ATPClient(
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com',
);
runApp(MyApp(atpClient: atpClient));
}
Copy
// Install with npm or yarn
// npm install @atp/react-native-client
import { ATPClient } from '@atp/react-native-client';
// In your app entry point
const App = () => {
const [atpClient, setAtpClient] = useState(null);
useEffect(() => {
const initClient = async () => {
const client = new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
});
setAtpClient(client);
};
initClient();
}, []);
return (
// Your app components
);
};
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)
}
}
}
}
Copy
// Register for push notifications
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Request permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
guard granted else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
return true
}
// Register device token with ATP
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
atpClient.registerPushToken(token: tokenString, type: "apns")
}
// Handle incoming notifications
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Check if it's an ATP notification
if let notificationId = userInfo["atp_notification_id"] as? String {
atpClient.getNotification(id: notificationId) { notification in
// Display the notification
self.showNotification(notification)
completionHandler(.newData)
}
} else {
completionHandler(.noData)
}
}
Copy
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import PushNotification from 'react-native-push-notification';
// Configure push notifications
const configurePushNotifications = async (atpClient) => {
// Configure handler
PushNotification.configure({
onRegister: function (tokenData) {
// Register token with ATP
atpClient.registerPushToken(tokenData.token, Platform.OS === 'ios' ? 'apns' : 'fcm');
},
onNotification: function (notification) {
// Check if it's an ATP notification
if (notification.data && notification.data.atp_notification_id) {
// Fetch the full notification
atpClient.getNotification(notification.data.atp_notification_id)
.then(atpNotification => {
// Display the notification
displayAtpNotification(atpNotification);
});
}
// Required for iOS
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
permissions: {
alert: true,
badge: true,
sound: true,
},
popInitialNotification: true,
});
};
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...
}
}
}
Copy
class NotificationViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var actionsStackView: UIStackView!
var notificationId: String!
var atpClient: ATPClient!
override func viewDidLoad() {
super.viewDidLoad()
// Get the app delegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
atpClient = appDelegate.atpClient
// Fetch notification details
atpClient.getNotification(id: notificationId) { [weak self] notification in
guard let self = self else { return }
// Update UI on main thread
DispatchQueue.main.async {
self.titleLabel.text = notification.title
self.descriptionLabel.text = notification.description
// Create action buttons
for action in notification.actions {
let button = UIButton(type: .system)
button.setTitle(action.label, for: .normal)
button.tag = self.actionsStackView.arrangedSubviews.count // Use tag to identify action
button.addTarget(self, action: #selector(self.actionButtonTapped(_:)), for: .touchUpInside)
// Store action data
button.accessibilityIdentifier = action.id
self.actionsStackView.addArrangedSubview(button)
}
}
}
}
@objc func actionButtonTapped(_ sender: UIButton) {
guard let actionId = sender.accessibilityIdentifier else { return }
// Find the action
atpClient.getNotification(id: notificationId) { [weak self] notification in
guard let self = self,
let action = notification.actions.first(where: { $0.id == actionId }) else { return }
DispatchQueue.main.async {
switch action.responseType {
case "simple":
// Simple action - just submit the response
self.atpClient.respondToNotification(
notificationId: self.notificationId,
actionId: actionId,
responseData: nil
) { success in
if success {
self.dismiss(animated: true)
}
}
case "text":
// Show text input alert
let alert = UIAlertController(title: action.label, message: nil, preferredStyle: .alert)
alert.addTextField()
alert.addAction(UIAlertAction(title: "Submit", style: .default) { _ in
if let text = alert.textFields?.first?.text {
self.atpClient.respondToNotification(
notificationId: self.notificationId,
actionId: actionId,
responseData: text
) { success in
if success {
self.dismiss(animated: true)
}
}
}
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(alert, animated: true)
// Handle other response types...
default:
break
}
}
}
}
}
Copy
import React, { useState, useEffect } from 'react';
import { View, Text, Button, TextInput, Alert, StyleSheet } from 'react-native';
const NotificationScreen = ({ route, navigation, atpClient }) => {
const { notificationId } = route.params;
const [notification, setNotification] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch notification details
atpClient.getNotification(notificationId)
.then(data => {
setNotification(data);
setLoading(false);
})
.catch(error => {
Alert.alert('Error', 'Failed to fetch notification details');
setLoading(false);
});
}, [notificationId]);
const handleAction = (actionId, responseType, responseOptions) => {
switch (responseType) {
case 'simple':
// Simple action - just submit the response
atpClient.respondToNotification(notificationId, actionId)
.then(() => navigation.goBack())
.catch(error => Alert.alert('Error', 'Failed to submit response'));
break;
case 'text':
// Show text input dialog
Alert.prompt(
responseOptions?.placeholder || 'Enter your response',
'',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Submit',
onPress: (text) => {
atpClient.respondToNotification(notificationId, actionId, text)
.then(() => navigation.goBack())
.catch(error => Alert.alert('Error', 'Failed to submit response'));
},
},
]
);
break;
// Handle other response types...
default:
Alert.alert('Unsupported', `Response type ${responseType} is not supported yet`);
}
};
if (loading) {
return <Text>Loading notification...</Text>;
}
if (!notification) {
return <Text>Notification not found</Text>;
}
return (
<View style={styles.container}>
<Text style={styles.title}>{notification.title}</Text>
<Text style={styles.description}>{notification.description}</Text>
<View style={styles.actionsContainer}>
{notification.actions.map(action => (
<Button
key={action.id}
title={action.label}
onPress={() => handleAction(action.id, action.response_type, action.response_options)}
/>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 8,
},
description: {
fontSize: 16,
marginBottom: 16,
},
actionsContainer: {
marginTop: 16,
},
});
export default NotificationScreen;
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()
}
}
}
Copy
// In AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Check if it's an ATP notification
if let notificationId = userInfo["atp_notification_id"] as? String {
// Process in background
let task = BGTaskScheduler.shared.submit(BGProcessingTaskRequest(identifier: "com.example.atp.notification")) { task in
self.processATPNotification(notificationId) {
task.setTaskCompleted(success: true)
}
}
if task != nil {
completionHandler(.newData)
} else {
// Process immediately if background task couldn't be scheduled
processATPNotification(notificationId) {
completionHandler(.newData)
}
}
} else {
completionHandler(.noData)
}
}
func processATPNotification(_ notificationId: String, completion: @escaping () -> Void) {
atpClient.getNotification(id: notificationId) { notification in
// Create and display a notification
let content = UNMutableNotificationContent()
content.title = notification.title
content.body = notification.description
content.sound = .default
content.userInfo = ["atp_notification_id": notificationId]
// Create a notification request
let request = UNNotificationRequest(
identifier: notificationId,
content: content,
trigger: nil
)
// Add the request to the notification center
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error displaying notification: \(error)")
}
completion()
}
}
}
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...
}
}
Copy
import React, { useState, useEffect } from 'react';
import { ATPClient } from '@atp/browser-client';
// ATP notification component
const ATPNotification = ({ notification, onClose }) => {
const [responseText, setResponseText] = useState('');
const [selectedOption, setSelectedOption] = useState('');
const [atpClient] = useState(
() => new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
})
);
const handleAction = async (actionId, responseType, responseData = null) => {
try {
await atpClient.respondToNotification(
notification.id,
actionId,
responseData
);
onClose();
} catch (error) {
console.error('Failed to submit response:', error);
}
};
const renderActionButton = (action) => {
switch (action.response_type) {
case 'simple':
return (
<button
key={action.id}
onClick={() => handleAction(action.id, 'simple')}
className="atp-button"
>
{action.label}
</button>
);
case 'text':
return (
<div key={action.id} className="atp-text-response">
<input
type="text"
placeholder={action.response_options?.placeholder || 'Enter your response'}
value={responseText}
onChange={(e) => setResponseText(e.target.value)}
className="atp-input"
/>
<button
onClick={() => handleAction(action.id, 'text', responseText)}
className="atp-button"
>
{action.label}
</button>
</div>
);
case 'select':
return (
<div key={action.id} className="atp-select-response">
<select
value={selectedOption}
onChange={(e) => setSelectedOption(e.target.value)}
className="atp-select"
>
<option value="">Select an option</option>
{action.response_options?.options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<button
onClick={() => handleAction(action.id, 'select', selectedOption)}
className="atp-button"
disabled={!selectedOption}
>
{action.label}
</button>
</div>
);
// Handle other response types...
default:
return null;
}
};
return (
<div className="atp-notification">
<div className="notification-header">
<h3>{notification.title}</h3>
<button className="close-btn" onClick={onClose}>×</button>
</div>
<p className="notification-description">{notification.description}</p>
<div className="notification-actions">
{notification.actions.map(renderActionButton)}
</div>
</div>
);
};
// Notification container component
const NotificationContainer = () => {
const [notifications, setNotifications] = useState([]);
const [atpClient] = useState(
() => new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
})
);
useEffect(() => {
// Set up notification stream
const connection = atpClient.connectToNotificationStream({
onNotification: (notification) => {
setNotifications(prev => [...prev, notification]);
},
onError: (error) => {
console.error('ATP connection error:', error);
}
});
// Clean up on unmount
return () => {
connection.disconnect();
};
}, [atpClient]);
const handleClose = (notificationId) => {
setNotifications(prev =>
prev.filter(notification => notification.id !== notificationId)
);
};
return (
<div className="notification-container">
{notifications.map(notification => (
<ATPNotification
key={notification.id}
notification={notification}
onClose={() => handleClose(notification.id)}
/>
))}
</div>
);
};
export default NotificationContainer;
Copy
<!-- ATPNotification.vue -->
<template>
<div class="atp-notification">
<div class="notification-header">
<h3>{{ notification.title }}</h3>
<button class="close-btn" @click="$emit('close')">×</button>
</div>
<p class="notification-description">{{ notification.description }}</p>
<div class="notification-actions">
<template v-for="action in notification.actions" :key="action.id">
<!-- Simple response -->
<button
v-if="action.response_type === 'simple'"
class="atp-button"
@click="handleAction(action.id, 'simple')"
>
{{ action.label }}
</button>
<!-- Text response -->
<div v-else-if="action.response_type === 'text'" class="atp-text-response">
<input
v-model="responseText"
type="text"
:placeholder="action.response_options?.placeholder || 'Enter your response'"
class="atp-input"
/>
<button
class="atp-button"
@click="handleAction(action.id, 'text', responseText)"
>
{{ action.label }}
</button>
</div>
<!-- Select response -->
<div v-else-if="action.response_type === 'select'" class="atp-select-response">
<select v-model="selectedOption" class="atp-select">
<option value="">Select an option</option>
<option
v-for="option in action.response_options?.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
<button
class="atp-button"
:disabled="!selectedOption"
@click="handleAction(action.id, 'select', selectedOption)"
>
{{ action.label }}
</button>
</div>
</template>
</div>
</div>
</template>
<script>
import { ATPClient } from '@atp/browser-client';
export default {
name: 'ATPNotification',
props: {
notification: {
type: Object,
required: true
}
},
data() {
return {
responseText: '',
selectedOption: '',
atpClient: new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
})
};
},
methods: {
async handleAction(actionId, responseType, responseData = null) {
try {
await this.atpClient.respondToNotification(
this.notification.id,
actionId,
responseData
);
this.$emit('close');
} catch (error) {
console.error('Failed to submit response:', error);
}
}
}
};
</script>
<!-- NotificationContainer.vue -->
<template>
<div class="notification-container">
<ATPNotification
v-for="notification in notifications"
:key="notification.id"
:notification="notification"
@close="removeNotification(notification.id)"
/>
</div>
</template>
<script>
import { ATPClient } from '@atp/browser-client';
import ATPNotification from './ATPNotification.vue';
export default {
name: 'NotificationContainer',
components: {
ATPNotification
},
data() {
return {
notifications: [],
atpClient: new ATPClient({
apiKey: 'your_client_api_key',
endpoint: 'https://api.atp.example.com'
}),
connection: null
};
},
mounted() {
// Set up notification stream
this.connection = this.atpClient.connectToNotificationStream({
onNotification: (notification) => {
this.notifications.push(notification);
},
onError: (error) => {
console.error('ATP connection error:', error);
}
});
},
beforeUnmount() {
// Clean up connection
if (this.connection) {
this.connection.disconnect();
}
},
methods: {
removeNotification(notificationId) {
this.notifications = this.notifications.filter(
notification => notification.id !== notificationId
);
}
}
};
</script>
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();
Copy
#!/usr/bin/env python3
import os
import json
import click
import inquirer
from atp import ATPClient
# Configuration file path
CONFIG_PATH = os.path.expanduser('~/.atpcli.json')
# Load or create configuration
def load_config():
try:
if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH, 'r') as f:
return json.load(f)
except Exception as e:
click.echo(f'Error loading config: {str(e)}')
return {'api_key': '', 'endpoint': 'https://api.atp.example.com'}
# Save configuration
def save_config(config):
try:
with open(CONFIG_PATH, 'w') as f:
json.dump(config, f, indent=2)
except Exception as e:
click.echo(f'Error saving config: {str(e)}')
# Initialize ATP client
def initialize_client():
config = load_config()
# If no API key, prompt for it
if not config['api_key']:
questions = [
inquirer.Text('api_key',
message="Enter your ATP API key",
validate=lambda _, x: x.strip() != '')
]
answers = inquirer.prompt(questions)
config['api_key'] = answers['api_key']
save_config(config)
return ATPClient(
api_key=config['api_key'],
endpoint=config['endpoint']
)
# Display notification details
def display_notification(notification):
click.echo('-' * 50)
click.echo(f"Notification: {notification['title']}")
click.echo('-' * 50)
click.echo(notification['description'])
click.echo('-' * 50)
# Create action choices
choices = [(action['label'], action) for action in notification['actions']]
questions = [
inquirer.List('action',
message="Select an action",
choices=choices)
]
answers = inquirer.prompt(questions)
if answers:
handle_action(notification['id'], answers['action'])
# Handle action based on response type
def handle_action(notification_id, action):
client = initialize_client()
response_type = action['response_type']
action_id = action['id']
if response_type == 'simple':
# Simple action - just submit the response
client.respond_to_notification(notification_id, action_id)
click.echo('Response submitted successfully!')
elif response_type == 'text':
# Prompt for text input
questions = [
inquirer.Text('response',
message=action.get('response_options', {}).get('placeholder', 'Enter your response:'),
validate=lambda _, x: x.strip() != '' if action.get('response_options', {}).get('required', False) else True)
]
answers = inquirer.prompt(questions)
if answers:
client.respond_to_notification(notification_id, action_id, answers['response'])
click.echo('Response submitted successfully!')
elif response_type == 'select':
# Prompt for selection
options = action.get('response_options', {}).get('options', [])
choices = [(opt['label'], opt['value']) for opt in options]
questions = [
inquirer.List('option',
message="Select an option",
choices=choices)
]
answers = inquirer.prompt(questions)
if answers:
client.respond_to_notification(notification_id, action_id, answers['option'])
click.echo('Response submitted successfully!')
else:
click.echo(f"Response type '{response_type}' is not supported in CLI mode")
# CLI commands
@click.group()
def cli():
"""ATP CLI - Command-line interface for Agent Triage Protocol"""
pass
@cli.command()
def listen():
"""Listen for new ATP notifications"""
client = initialize_client()
click.echo('Listening for ATP notifications. Press Ctrl+C to exit.')
def on_notification(notification):
click.echo(f"\nNew notification: {notification['title']}")
display_notification(notification)
def on_error(error):
click.echo(f'ATP connection error: {str(error)}')
# Start listening
client.connect_to_notification_stream(
on_notification=on_notification,
on_error=on_error
)
# Keep the process running until Ctrl+C
try:
import time
while True:
time.sleep(1)
except KeyboardInterrupt:
click.echo('\nExiting...')
@cli.command()
def list():
"""List recent ATP notifications"""
client = initialize_client()
try:
notifications = client.get_recent_notifications()
if not notifications:
click.echo('No recent notifications.')
return
click.echo('\nRecent notifications:')
for i, notification in enumerate(notifications):
click.echo(f"{i + 1}. {notification['title']} ({notification['id']})")
# Prompt to select a notification
choices = [(n['title'], n) for n in notifications]
questions = [
inquirer.List('notification',
message="Select a notification to view",
choices=choices)
]
answers = inquirer.prompt(questions)
if answers:
display_notification(answers['notification'])
except Exception as e:
click.echo(f'Error: {str(e)}')
if __name__ == '__main__':
cli()
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)
}
Copy
// Add this function to your view controller for testing
func verifyATPImplementation() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let atpClient = appDelegate.atpClient else {
print("ATPVerification: ATP client is not initialized")
return
}
// 1. Verify client initialization
print("ATPVerification: ATP client initialized successfully")
// 2. Test push token registration
// This uses a mock token for testing
let mockToken = "test_device_token_for_verification"
atpClient.registerPushToken(token: mockToken, type: "apns") { success in
print("ATPVerification: APNS token registration: \(success ? "SUCCESS" : "FAILED")")
}
// 3. Test notification rendering with a mock notification
let mockNotification = ATPNotification(
id: "test_notification_id",
title: "Test Notification",
description: "This is a test notification to verify the ATP implementation",
priority: "normal",
actions: [
ATPAction(
id: "test_action",
label: "Test Action",
responseType: "simple"
),
ATPAction(
id: "text_action",
label: "Test Text Response",
responseType: "text",
responseOptions: [
"placeholder": "Enter test response"
]
)
]
)
// Display the mock notification to verify UI
DispatchQueue.main.async {
self.displayNotification(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