Documentation Index
Fetch the complete documentation index at: https://atp.hypertext.studio/llms.txt
Use this file to discover all available pages before exploring further.
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
// 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()
}
}
// 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
}
}
// 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));
}
// 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
// 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)
}
}
}
}
// 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)
}
}
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
// 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...
}
}
}
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
}
}
}
}
}
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)
// 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()
}
}
}
// 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
// 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...
}
}
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;
<!-- 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
// 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)
// 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)
// 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)
// 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
#!/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();
#!/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
// 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)
}
// 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
// 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