Skip to main content

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

// Add to your app-level build.gradle
dependencies {
    implementation("io.atp:android-client:1.0.0")
}

// Initialize client in your Application class
class MyApplication : Application() {
    lateinit var atpClient: ATPClient
    
    override fun onCreate() {
        super.onCreate()
        
        atpClient = ATPClient.Builder(this)
            .apiKey("your_client_api_key")
            .endpoint("https://api.atp.example.com")
            .build()
    }
}

Setting Up Push Notifications

To receive notifications in real-time, configure push notifications:
// In your application class
override fun onCreate() {
    super.onCreate()
    
    // Initialize Firebase Messaging
    FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val token = task.result
            // Register FCM token with ATP
            atpClient.registerPushToken(token, "fcm")
        }
    }
}

// Create a Firebase Messaging Service
class ATPMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Check if it's an ATP notification
        if (remoteMessage.data.containsKey("atp_notification_id")) {
            val notificationId = remoteMessage.data["atp_notification_id"]
            val app = applicationContext as MyApplication
            
            // Fetch the full notification
            app.atpClient.getNotification(notificationId) { notification ->
                // Display the notification
                showNotification(notification)
            }
        }
    }
}

Displaying Notifications

Creating a custom UI for ATP notifications:
// Create a notification view
class NotificationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_notification)
        
        // Get notification ID from intent
        val notificationId = intent.getStringExtra("notification_id") ?: return
        
        // Get the application instance
        val app = application as MyApplication
        
        // Fetch notification details
        app.atpClient.getNotification(notificationId) { notification ->
            // Update UI
            findViewById<TextView>(R.id.notification_title).text = notification.title
            findViewById<TextView>(R.id.notification_description).text = notification.description
            
            // Create action buttons dynamically
            val actionsContainer = findViewById<LinearLayout>(R.id.actions_container)
            
            notification.actions.forEach { action ->
                val button = Button(this)
                button.text = action.label
                button.setOnClickListener {
                    handleAction(notificationId, action)
                }
                actionsContainer.addView(button)
            }
        }
    }
    
    private fun handleAction(notificationId: String, action: ATPAction) {
        val app = application as MyApplication
        
        when (action.responseType) {
            "simple" -> {
                // Simple action - just submit the response
                app.atpClient.respondToNotification(
                    notificationId = notificationId,
                    actionId = action.id,
                    responseData = null
                )
            }
            "text" -> {
                // Show text input dialog
                val editText = EditText(this)
                AlertDialog.Builder(this)
                    .setTitle(action.label)
                    .setView(editText)
                    .setPositiveButton("Submit") { _, _ ->
                        app.atpClient.respondToNotification(
                            notificationId = notificationId,
                            actionId = action.id,
                            responseData = editText.text.toString()
                        )
                    }
                    .setNegativeButton("Cancel", null)
                    .show()
            }
            // Handle other response types...
        }
    }
}

Background Processing

For handling notifications when the app is in the background:
// Create a WorkManager task
class NotificationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val notificationId = inputData.getString("notification_id") ?: return Result.failure()
        
        // Get the application instance
        val app = applicationContext as MyApplication
        
        // Process the notification
        return try {
            val notification = app.atpClient.getNotificationSync(notificationId)
            
            // Create and show a system notification
            val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_notification)
                .setContentTitle(notification.title)
                .setContentText(notification.description)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            
            // Create an intent to open the notification detail activity
            val intent = Intent(applicationContext, NotificationActivity::class.java).apply {
                putExtra("notification_id", notificationId)
            }
            val pendingIntent = PendingIntent.getActivity(
                applicationContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
            )
            builder.setContentIntent(pendingIntent)
            
            // Show the notification
            val notificationManager = NotificationManagerCompat.from(applicationContext)
            notificationManager.notify(notificationId.hashCode(), builder.build())
            
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

Web Implementations

Browser-Based Clients

Implementing ATP in web applications:
// Install with npm or yarn
// npm install @atp/browser-client

import { ATPClient } from '@atp/browser-client';

// Initialize the client
const atpClient = new ATPClient({
  apiKey: 'your_client_api_key',
  endpoint: 'https://api.atp.example.com',
  userId: 'current_user_id' // If available
});

// Set up notification polling or WebSocket connection
atpClient.connectToNotificationStream({
  onNotification: (notification) => {
    // Display the notification
    showNotification(notification);
  },
  onError: (error) => {
    console.error('ATP connection error:', error);
  }
});

// Display notification function
function showNotification(notification) {
  // Create notification UI
  const notificationElement = document.createElement('div');
  notificationElement.className = 'atp-notification';
  
  // Add title and description
  notificationElement.innerHTML = `
    <div class="notification-header">
      <h3>${notification.title}</h3>
      <button class="close-btn">×</button>
    </div>
    <p>${notification.description}</p>
    <div class="notification-actions"></div>
  `;
  
  // Add action buttons
  const actionsContainer = notificationElement.querySelector('.notification-actions');
  
  notification.actions.forEach(action => {
    const button = document.createElement('button');
    button.textContent = action.label;
    button.addEventListener('click', () => {
      handleAction(notification.id, action);
    });
    
    actionsContainer.appendChild(button);
  });
  
  // Add close button handler
  const closeBtn = notificationElement.querySelector('.close-btn');
  closeBtn.addEventListener('click', () => {
    notificationElement.remove();
  });
  
  // Add to notification container
  document.getElementById('notification-container').appendChild(notificationElement);
}

// Handle notification actions
function handleAction(notificationId, action) {
  switch (action.response_type) {
    case 'simple':
      // Simple action - just submit the response
      atpClient.respondToNotification(notificationId, action.id)
        .then(() => {
          // Remove the notification
          document.querySelector(`[data-notification-id="${notificationId}"]`).remove();
        })
        .catch(error => {
          console.error('Failed to submit response:', error);
        });
      break;
      
    case 'text':
      // Show text input dialog
      const textInput = prompt(action.response_options?.placeholder || 'Enter your response:');
      
      if (textInput !== null) {
        atpClient.respondToNotification(notificationId, action.id, textInput)
          .then(() => {
            // Remove the notification
            document.querySelector(`[data-notification-id="${notificationId}"]`).remove();
          })
          .catch(error => {
            console.error('Failed to submit response:', error);
          });
      }
      break;
      
    // Handle other response types...
  }
}

Web Push Notifications

Implementing browser push notifications for ATP:
// 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;
}
Service Worker Implementation:
// 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:
// 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 
      });
    });
});
Renderer Process Implementation:
// 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:
#!/usr/bin/env node
const { ATPClient } = require('@atp/node-client');
const inquirer = require('inquirer');
const notifier = require('node-notifier');
const path = require('path');
const fs = require('fs');
const os = require('os');

// Configuration file path
const configPath = path.join(os.homedir(), '.atpcli.json');

// Load or create configuration
function loadConfig() {
  try {
    if (fs.existsSync(configPath)) {
      return JSON.parse(fs.readFileSync(configPath, 'utf8'));
    }
  } catch (error) {
    console.error('Error loading config:', error.message);
  }
  
  return { apiKey: '', endpoint: 'https://api.atp.example.com' };
}

// Save configuration
function saveConfig(config) {
  try {
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
  } catch (error) {
    console.error('Error saving config:', error.message);
  }
}

// Initialize ATP client
async function initializeClient() {
  let config = loadConfig();
  
  // If no API key, prompt for it
  if (!config.apiKey) {
    const answers = await inquirer.prompt([
      {
        type: 'input',
        name: 'apiKey',
        message: 'Enter your ATP API key:',
        validate: input => input.trim() !== '' ? true : 'API key is required'
      }
    ]);
    
    config.apiKey = answers.apiKey;
    saveConfig(config);
  }
  
  return new ATPClient({
    apiKey: config.apiKey,
    endpoint: config.endpoint
  });
}

// Display a desktop notification
function showDesktopNotification(notification) {
  notifier.notify({
    title: notification.title,
    message: notification.description,
    icon: path.join(__dirname, 'icon.png'),
    sound: true,
    wait: true
  });
  
  notifier.on('click', () => {
    // Show notification details and prompt for response
    displayNotificationDetails(notification);
  });
}

// Display notification details in console
async function displayNotificationDetails(notification) {
  console.log('\n-----------------------------------');
  console.log(`Notification: ${notification.title}`);
  console.log('-----------------------------------');
  console.log(notification.description);
  console.log('-----------------------------------');
  
  // Create action choices for inquirer
  const choices = notification.actions.map(action => ({
    name: action.label,
    value: action
  }));
  
  // Prompt user to select an action
  const { selectedAction } = await inquirer.prompt([
    {
      type: 'list',
      name: 'selectedAction',
      message: 'Select an action:',
      choices
    }
  ]);
  
  // Handle the selected action
  await handleAction(notification.id, selectedAction);
}

// Handle action based on response type
async function handleAction(notificationId, action) {
  const client = await initializeClient();
  
  switch (action.response_type) {
    case 'simple':
      // Simple action - just submit the response
      await client.respondToNotification(notificationId, action.id);
      console.log('Response submitted successfully!');
      break;
      
    case 'text':
      // Prompt for text input
      const { textResponse } = await inquirer.prompt([
        {
          type: 'input',
          name: 'textResponse',
          message: action.response_options?.placeholder || 'Enter your response:',
          validate: input => {
            if (action.response_options?.required && input.trim() === '') {
              return 'Response is required';
            }
            return true;
          }
        }
      ]);
      
      await client.respondToNotification(notificationId, action.id, textResponse);
      console.log('Response submitted successfully!');
      break;
      
    case 'select':
      // Prompt for selection
      const choices = action.response_options?.options.map(option => ({
        name: option.label,
        value: option.value
      }));
      
      const { selectedOption } = await inquirer.prompt([
        {
          type: 'list',
          name: 'selectedOption',
          message: 'Select an option:',
          choices
        }
      ]);
      
      await client.respondToNotification(notificationId, action.id, selectedOption);
      console.log('Response submitted successfully!');
      break;
      
    // Handle other response types...
    default:
      console.log(`Response type '${action.response_type}' is not supported in CLI mode`);
  }
}

// Main function
async function main() {
  try {
    const client = await initializeClient();
    
    // Parse command line arguments
    const args = process.argv.slice(2);
    const command = args[0];
    
    if (command === 'listen') {
      // Start listening for notifications
      console.log('Listening for ATP notifications. Press Ctrl+C to exit.');
      
      client.connectToNotificationStream({
        onNotification: notification => {
          showDesktopNotification(notification);
          console.log(`\nNew notification: ${notification.title}`);
        },
        onError: error => {
          console.error('ATP connection error:', error);
        }
      });
      
      // Keep the process running
      process.stdin.resume();
    } else if (command === 'list') {
      // List recent notifications
      const notifications = await client.getRecentNotifications();
      
      if (notifications.length === 0) {
        console.log('No recent notifications.');
        return;
      }
      
      console.log('\nRecent notifications:');
      notifications.forEach((notification, index) => {
        console.log(`${index + 1}. ${notification.title} (${notification.id})`);
      });
      
      // Prompt to select a notification
      const { selectedIndex } = await inquirer.prompt([
        {
          type: 'list',
          name: 'selectedIndex',
          message: 'Select a notification to view:',
          choices: notifications.map((n, i) => ({
            name: n.title,
            value: i
          }))
        }
      ]);
      
      displayNotificationDetails(notifications[selectedIndex]);
    } else {
      // Show help
      console.log('Usage:');
      console.log('  atp-cli listen         Listen for new notifications');
      console.log('  atp-cli list           List recent notifications');
    }
  } catch (error) {
    console.error('Error:', error.message);
    process.exit(1);
  }
}

// Run the main function
main();

Verification Examples

Let’s verify that our implementations work correctly with these test examples:

Mobile Verification

// Add this function to your activity or fragment for testing
fun verifyATPImplementation() {
    val app = application as MyApplication
    
    // 1. Test client initialization
    if (app.atpClient == null) {
        Log.e("ATPVerification", "ATP client is not initialized")
        return
    }
    
    // 2. Test FCM token registration
    FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val token = task.result
            app.atpClient.registerPushToken(token, "fcm", { success ->
                Log.d("ATPVerification", "FCM token registration: ${if (success) "SUCCESS" else "FAILED"}")
            })
        } else {
            Log.e("ATPVerification", "Failed to get FCM token")
        }
    }
    
    // 3. Test notification rendering with a mock notification
    val mockNotification = ATPNotification(
        id = "test_notification_id",
        title = "Test Notification",
        description = "This is a test notification to verify the ATP implementation",
        priority = "normal",
        actions = listOf(
            ATPAction(
                id = "test_action",
                label = "Test Action",
                responseType = "simple"
            ),
            ATPAction(
                id = "text_action",
                label = "Test Text Response",
                responseType = "text",
                responseOptions = mapOf(
                    "placeholder" to "Enter test response"
                )
            )
        )
    )
    
    // Display the mock notification to verify UI
    showNotification(mockNotification)
}

Web Verification

// 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