Pusher Real-Time Events

Real-time event notifications for the Circuit Auction backoffice using Pusher WebSocket technology.

About Pusher Events

The Circuit Auction API uses Pusher to provide real-time event notifications to connected clients. These events allow the backoffice application to react to changes as they happen, providing immediate feedback for operations like entity updates, queue processing, and bulk operations.

Setup & Installation

1. Include Pusher.js Library

Add the Pusher JavaScript library to your HTML:

<script src="https://js.pusher.com/8.0/pusher.min.js"></script>

Or install via npm:

npm install pusher-js
2. Get Pusher Configuration from API

Retrieve the Pusher configuration from the GET /api/me endpoint. This endpoint returns user information along with configuration settings including:

  • pusherConfig.key: Your Pusher application key
  • pusherConfig.cluster: The Pusher cluster region (e.g., 'eu', 'us2')
  • pusherConfig.privateChannel: The private channel name to subscribe to

For authentication, you'll use the regular access token obtained from /api/login-token. See Authentication documentation for details.

Example API Request
// Get user configuration including Pusher settings
const ACCESS_TOKEN = localStorage.getItem('access_token'); // From /api/login-token

fetch(`https://backoffice.ddev.site/api/me?access_token=${ACCESS_TOKEN}`)
  .then(response => response.json())
  .then(data => {
    const config = data.data[0].config;
    const pusherKey = config.pusherConfig.key;
    const pusherCluster = config.pusherConfig.cluster;
    const privateChannel = config.pusherConfig.privateChannel;

    console.log('Pusher Key:', pusherKey);
    console.log('Pusher Cluster:', pusherCluster);
    console.log('Private Channel:', privateChannel);

    // Use these values to initialize Pusher (see below)
  });
Example API Response (Relevant Fields)
{
  "data": [{
    "id": "1",
    "label": "admin",
    "config": {
      ...
      "pusherConfig": {
        "key": "e589860bfd6885f493bf",
        "cluster": "eu",
        "privateChannel": "private-general@backoffice-hk-eu-local"
      },
      ...
    }
  }]
}
Note: All Pusher configuration is provided in the config.pusherConfig object within the API response. See the API /me Documentation for complete response details.

Pusher Configuration

Initialize Pusher Connection

Create a Pusher instance using configuration from /api/me:

// Get configuration from /api/me endpoint
const API_BASE_URL = 'https://backoffice.ddev.site';
const ACCESS_TOKEN = localStorage.getItem('access_token'); // From /api/login-token

fetch(`${API_BASE_URL}/api/me?access_token=${ACCESS_TOKEN}`)
  .then(response => response.json())
  .then(data => {
    const config = data.data[0].config;

    // Extract Pusher configuration from API response
    const PUSHER_KEY = config.pusherConfig.key;
    const PUSHER_CLUSTER = config.pusherConfig.cluster;
    const PRIVATE_CHANNEL = config.pusherConfig.privateChannel;

    // Initialize Pusher with regular access token for authentication
    const pusher = new Pusher(PUSHER_KEY, {
      cluster: PUSHER_CLUSTER,
      encrypted: true,
      forceTLS: true,
      authEndpoint: `${API_BASE_URL}/api/v1.0/pusher_auth?access_token=${ACCESS_TOKEN}`
    });

    // Continue with channel subscription (see below)
    setupPusherChannels(pusher, PRIVATE_CHANNEL);
  });
Note: Use the regular access token from /api/login-token for Pusher authentication. All Pusher settings (key, cluster, privateChannel) are available in config.pusherConfig.
Authentication

The Pusher connection requires authentication through the API's pusher_auth endpoint:

  • Endpoint: GET /api/v1.0/pusher_auth
  • Authentication: Pass your access token as a query parameter
  • Channel Type: Private channel (requires authentication)
Important: The access token must be a valid token obtained from the /api/login-token endpoint. See the Authentication documentation for details.

Subscribing to Events

Subscribe to Channel and Bind Events
// Subscribe to the private channel
const channel = pusher.subscribe(PRIVATE_CHANNEL);

// Bind to a specific event
channel.bind('entity_update', function(data) {
  console.log('Entity updated:', data);

  // Handle the event
  if (data.bundle === 'item' && data.entity_id === currentItemId) {
    console.log('Current item was updated');
    // Refresh your UI or reload data
  }
});

// Bind to multiple events
channel.bind('advanced_queue__post_execute', function(data) {
  console.log('Queue task completed:', data.name);

  if (data.name === 'print_statement_by_signal') {
    console.log('Statement printing complete');
    // Update PDF links or show notification
  }
});

channel.bind('item_bulk', function(data) {
  console.log('Bulk operation complete:', data);
  alert(`Successfully processed ${data.count} items`);
});
Handle Connection Events
// Connection state changes
pusher.connection.bind('connected', function() {
  console.log('Connected to Pusher');
});

pusher.connection.bind('disconnected', function() {
  console.log('Disconnected from Pusher');
});

// Handle errors
pusher.connection.bind('error', function(error) {
  console.error('Pusher connection error:', error);

  if (error.error && error.error.data) {
    console.error('Error code:', error.error.data.code);
    console.error('Error message:', error.error.data.message);
  }
});
Complete Example
// Full implementation example - get config from /api/me first
const API_BASE_URL = 'https://backoffice.ddev.site';
const ACCESS_TOKEN = localStorage.getItem('access_token'); // From /api/login-token

// Fetch configuration
fetch(`${API_BASE_URL}/api/me?access_token=${ACCESS_TOKEN}`)
  .then(response => response.json())
  .then(data => {
    const config = data.data[0].config;
    const PUSHER_KEY = config.pusherConfig.key;
    const PUSHER_CLUSTER = config.pusherConfig.cluster;
    const PRIVATE_CHANNEL = config.pusherConfig.privateChannel;

    // Initialize Pusher with regular access token
    const pusher = new Pusher(PUSHER_KEY, {
      cluster: PUSHER_CLUSTER,
      encrypted: true,
      forceTLS: true,
      authEndpoint: `${API_BASE_URL}/api/v1.0/pusher_auth?access_token=${ACCESS_TOKEN}`
    });

    // Subscribe to channel
    const channel = pusher.subscribe(PRIVATE_CHANNEL);

    // Listen for entity updates
    channel.bind('entity_update', function(data) {
      const userId = localStorage.getItem('user_id');
      const shouldReload = data.force_reload || (data.uid === userId);

      if (shouldReload && data.bundle === 'item') {
        console.log(`Item ${data.entity_id} was updated`);
        // Refresh the item data in your application
        reloadItem(data.entity_id);
      }
    });

    // Listen for queue completion
    channel.bind('advanced_queue__post_execute', function(data) {
      if (data.name === 'migrate_import_queue' && data.result) {
        const successCount = data.result.result?.successes || 0;
        console.log(`Import complete: ${successCount} items processed`);
        // Show success message and refresh table
        showNotification(`Import done! Processed ${successCount} items.`);
        refreshDataTable();
      }
    });

    // Handle connection errors
    pusher.connection.bind('error', function(error) {
      if (error.error?.data?.code === 4004) {
        console.error('Over connection quota');
      } else if (error.error?.data?.code === 4001) {
        console.error('Application disabled');
      }
    });
  })
  .catch(error => {
    console.error('Failed to initialize Pusher:', error);
  });

Available Events

entity_update

Triggered when an entity (item, client, sale, task) is updated in the system.

Use Cases
  • Reload item page when item data changes
  • Refresh client information when client is updated
  • Update sale details when sale is modified
  • Refresh task list when task status changes
Event Data Structure
Field Type Description
name string Always "entity_update"
bundle string Entity type: "item", "client", "sale", "task"
entity_id string ID of the updated entity
uid string User ID who made the change
force_reload boolean Whether to force a page reload regardless of user
Example Implementation
// Subscribe to entity_update event
channel.bind('entity_update', function(data) {
  if (data.name === 'entity_update') {
    const userId = localStorage.getItem('user_id');
    const shouldReload = data.force_reload || (data.uid === userId);

    if (shouldReload && data.bundle === 'item' && data.entity_id === currentItemId) {
      // Reload the page to show updated item data
      console.log('Reloading item:', data.entity_id);
      fetchItemData(data.entity_id);
    }

    if (shouldReload && data.bundle === 'client' && data.entity_id === currentClientId) {
      // Refresh client information
      fetchClientData(data.entity_id);
    }

    if (shouldReload && data.bundle === 'sale' && data.entity_id === currentSaleId) {
      // Refresh sale details
      fetchSaleData(data.entity_id);
    }
  }
});

advanced_queue__post_execute

Triggered after a queue task completes execution. This is the most commonly used queue event.

Use Cases
  • Update PDF links after statement generation
  • Display sticker preview after print queue completion
  • Refresh address export list after CSV generation
  • Update order data after bulk operations
  • Show import results after migration queue completes
  • Update control list after verification tasks
Event Data Structure
Field Type Description
name string Queue task name (e.g., "print_statement_by_signal", "backoffice_print_sticker_queue")
label string Optional label for tracking (e.g., "via_bulk_{order_id}")
result object Task result data (structure varies by task type)
result.nid string Entity ID related to the task
result.data object Additional result data
Common Task Names
  • print_statement_by_signal - Statement printing completion
  • backoffice_print_sticker_queue - Sticker printing completion
  • Export addresses to CSV - Address export completion
  • migrate_import_queue - Migration import completion
  • control_list_ih_check - In-house control check completion
  • control_list_cs_check - Consignment statement check completion
  • control_list_order_check - Order control check completion
Example Implementation
// Subscribe to advanced_queue__post_execute event
channel.bind('advanced_queue__post_execute', function(data) {
  // Handle statement printing completion
  if (data.name === 'print_statement_by_signal' && data.result?.nid === entityId) {
    // Update the last emailed invoice information
    console.log('Statement printed for entity:', data.result.nid);
    updateInvoiceDisplay(data.result);
  }

  // Handle sticker printing completion
  if (data.name === 'backoffice_print_sticker_queue') {
    console.log('Sticker print job completed');
    if (data.result[0]?.print_job?.result?.result?.no_print_server) {
      // Show sticker preview if no print server available
      showStickerPreview(data.result[0].print_job.result.url);
    }
  }

  // Handle bulk order operations
  const bulkId = 'via_bulk_' + orderId;
  if (data.label === bulkId) {
    // Refresh order data after bulk operation
    console.log('Bulk operation completed for order:', orderId);
    refreshOrderData(data.result);
  }

  // Handle address export
  if (data.name === 'Export addresses to CSV') {
    console.log('Address export completed');
    refreshAddressTable();
  }

  // Handle import completion
  if (data.name === 'migrate_import_queue' && data.result) {
    const successCount = data.result.result?.successes || 0;
    showMessage('Import done! Total processed: ' + successCount);
  }

  // Handle control list checks
  const controlTasks = ['control_list_ih_check', 'control_list_cs_check', 'control_list_order_check'];
  if (controlTasks.includes(data.name)) {
    console.log('Control check completed');
    loadControlList();
  }
});

advanced_queue__pre_execute

Triggered when a queue task begins execution, before processing starts.

Use Cases
  • Display "processing" or "started" messages to users
  • Initialize progress indicators
  • Prepare UI for incoming results
Event Data Structure
Field Type Description
name string Queue task name (e.g., "migrate_import_queue", "knock_down_all_items")
Example Implementation
// Subscribe to advanced_queue__pre_execute event
channel.bind('advanced_queue__pre_execute', function(data) {
  if (data.name === 'migrate_import_queue') {
    console.log('Import starting...');
    showStatusMessage('Import Started...');
    showProgressBar();
  }

  if (data.name === 'knock_down_all_items') {
    console.log('Knock down process starting...');
    showStatusMessage('Knocking down items...');
    initializeProgressTracker();
  }
});

advanced_queue__create

Triggered when a new queue task is created.

Use Cases
  • Track queue task creation
  • Monitor queue additions
  • Display queue status updates

advanced_queue__update

Triggered when a queue task is updated (e.g., status change, progress update).

Use Cases
  • Update queue task status
  • Show progress updates
  • Monitor queue changes

item_bulk

Triggered when bulk item operations complete (bulk updates, lot number assignments, revisions).

Use Cases
  • Display bulk operation results
  • Show success messages with item counts
  • Update UI after mass item changes
Event Data Structure
Field Type Description
name string Operation name: "bulk_update", "item_lot_bulk", "item_bulk_retry", etc.
type string Operation type (e.g., "bulk")
count integer Number of items processed
status string Operation status: "success", "error", "warning"
message string Result message or error description
Example Implementation
// Subscribe to item_bulk event
channel.bind('item_bulk', function(data) {
  if (data.name === 'bulk_update' && data.type === 'bulk') {
    console.log('Bulk update completed');
    showSuccessMessage('Successfully created ' + data.count + ' revisions.');
    refreshItemTable();
  }

  if (data.name === 'item_lot_bulk') {
    console.log('Lot bulk operation completed');
    showSuccessMessage('Successfully saved ' + data.count + ' items.');
    refreshItemTable();
  }

  if (data.name === 'item_bulk_retry') {
    console.log('Bulk retry completed with status:', data.status);
    if (data.status === 'success') {
      showSuccessMessage(data.message);
    } else if (data.status === 'error') {
      showErrorMessage(data.message);
    } else if (data.status === 'warning') {
      showWarningMessage(data.message);
    }
  }
});

caller_id

Triggered when an incoming phone call is detected, displaying caller information.

Use Cases
  • Display caller ID popup
  • Show client information for incoming calls
  • Quick access to client records during calls
Event Data Structure
Field Type Description
name string Always "caller_id"
username string Username of the staff member receiving the call
client object Client information for the caller
Example Implementation
// Subscribe to caller_id event
channel.bind('caller_id', function(data) {
  if (data.name === 'caller_id') {
    const currentUsername = localStorage.getItem('username');

    if (currentUsername === data.username) {
      // Display the caller ID popup with client information
      console.log('Incoming call from client:', data.client);
      showCallerIdPopup({
        clientName: data.client.name,
        clientId: data.client.id,
        phoneNumber: data.client.phone
      });

      // Optional: Play notification sound
      playNotificationSound();

      // Optional: Create quick link to client page
      const clientUrl = `/clients/${data.client.id}`;
      createQuickAccessLink(clientUrl);
    }
  }
});

control_update

Triggered during control list verification tasks to provide progress updates.

Use Cases
  • Display real-time progress during control checks
  • Show number of items processed
  • Update progress bars during verification
Event Data Structure
Field Type Description
name string Task name: "control_list_ih_check", "control_list_cs_check", "control_list_order_check"
total integer Number of items processed so far
Example Implementation
// Subscribe to control_update event
const controlListTasks = [
  'control_list_ih_check',
  'control_list_cs_check',
  'control_list_order_check'
];

channel.bind('control_update', function(data) {
  if (controlListTasks.includes(data.name)) {
    console.log('Control check progress:', data.total, 'items processed');

    // Update progress message
    updateProgressMessage('Processed ' + data.total + ' items.');

    // Update progress bar if you have one
    updateProgressBar(data.total, data.expected_total);
  }
});

knock_down_all_items

Triggered during bulk knock-down operations to provide progress updates.

Use Cases
  • Display knock-down progress
  • Show number of items knocked down
  • Update progress bars during bulk knock-down
Event Data Structure
Field Type Description
result string Progress message (e.g., "Knocked down 10 of 100 items...")
Example Implementation
// Subscribe to knock_down_all_items event
channel.bind('knock_down_all_items', function(data) {
  if (data.result) {
    console.log('Knock down progress:', data.result);

    // Update status message
    updateStatusMessage(data.result);

    // Extract progress from message (e.g., "Knocked down 10 of 100 items...")
    const match = data.result.match(/(\d+)\s+of\s+(\d+)/);
    if (match) {
      const current = parseInt(match[1], 10);
      const total = parseInt(match[2], 10);
      const progressPercent = (current / total) * 100;

      // Update progress bar
      updateProgressBar(progressPercent);

      // Update counter display
      updateCounterDisplay(current, total);

      console.log(`Progress: ${current}/${total} (${progressPercent.toFixed(1)}%)`);
    }
  }
});

Best Practices

1. Check Event Names

Always verify the data.name field to ensure you're handling the correct event, as multiple operations may use the same event type.

channel.bind('advanced_queue__post_execute', function(data) {
  if (data.name === 'expected_task_name') {
    // Handle specific task
  }
});
2. User Filtering

For entity updates, check if the update came from the current user to avoid unnecessary reloads:

channel.bind('entity_update', function(data) {
  const userId = localStorage.getItem('user_id');
  const shouldReload = data.force_reload || (data.uid === userId);

  if (shouldReload) {
    // Reload or refresh data
  }
});
3. Entity ID Matching

Verify that the event relates to the currently displayed entity:

channel.bind('entity_update', function(data) {
  if (data.entity_id === currentEntityId && data.bundle === 'item') {
    // Update current entity
    fetchItemData(data.entity_id);
  }
});
4. Safe Property Access

Use optional chaining or null checks when accessing nested properties:

channel.bind('advanced_queue__post_execute', function(data) {
  // Using optional chaining (ES2020+)
  if (data.result?.nid === currentId) {
    handleResult(data.result);
  }

  // Or traditional null check
  if (data.result && data.result.nid === currentId) {
    handleResult(data.result);
  }
});
5. Label Tracking for Bulk Operations

For bulk operations, use labels to match events with specific operations:

const bulkId = 'via_bulk_' + orderId;

channel.bind('advanced_queue__post_execute', function(data) {
  if (data.label === bulkId) {
    // Handle this specific bulk operation
    console.log('Bulk operation completed for order:', orderId);
    refreshOrderData(data.result);
  }
});
6. Unbind Events When Done

Clean up event listeners when they're no longer needed to prevent memory leaks:

// Unbind a specific event
channel.unbind('entity_update');

// Unbind all events on a channel
channel.unbind_all();

// Unsubscribe from a channel
pusher.unsubscribe(PRIVATE_CHANNEL);

// Disconnect from Pusher entirely
pusher.disconnect();
7. Handle Reconnection

Implement reconnection logic to handle temporary connection issues:

pusher.connection.bind('disconnected', function() {
  console.log('Disconnected from Pusher');
  showOfflineIndicator();
});

pusher.connection.bind('connected', function() {
  console.log('Reconnected to Pusher');
  hideOfflineIndicator();
  // Optionally refresh data after reconnection
  refreshCurrentPageData();
});
8. Secure Your Access Token

Never expose your access token in client-side code or version control:

// GOOD: Get token from secure storage
const accessToken = localStorage.getItem('access_token');

// BAD: Hardcoding token in code
// const accessToken = 'abc123...';

Error Handling

Connection Errors

Handle Pusher connection errors to provide feedback to users:

pusher.connection.bind('error', function(error) {
  console.error('Pusher connection error:', error);

  // Extract error details
  const errorMessage = error.error?.data?.message || 'Unknown error';
  const errorCode = error.error?.data?.code || null;

  // Log the error
  console.error('Error code:', errorCode);
  console.error('Error message:', errorMessage);

  // Handle specific error codes
  switch(errorCode) {
    case 4001:
      showError('Pusher application is disabled. Please contact support.');
      break;
    case 4003:
      showError('Invalid Pusher configuration. Please check your settings.');
      break;
    case 4004:
      showError('Connection limit reached. Please try again later.');
      break;
    case 4100:
      showError('Too many connections. Please reduce concurrent connections.');
      break;
    case 4200:
      showError('Connection error. Attempting to reconnect...');
      break;
    default:
      showError('Connection error: ' + errorMessage);
  }

  // Optional: Send error to your error tracking service
  logErrorToService({
    type: 'pusher_error',
    code: errorCode,
    message: errorMessage
  });
});
Common Error Codes
Code Description Recommended Action
4001 Application disabled or deleted Contact administrator to check Pusher app status
4003 Invalid Pusher key Verify the Pusher key in your configuration
4004 Over connection quota Upgrade Pusher plan or reduce connections
4100 Connection limit exceeded Close unused connections or upgrade plan
4200 Generic reconnect error Wait and let Pusher auto-reconnect
4201 Pong reply not received Check network connectivity
Connection State Monitoring
// Monitor all connection state changes
pusher.connection.bind('state_change', function(states) {
  console.log('Connection state changed from', states.previous, 'to', states.current);

  // Update UI based on connection state
  switch(states.current) {
    case 'connected':
      showOnlineIndicator();
      hideReconnectingMessage();
      break;
    case 'connecting':
      showConnectingIndicator();
      break;
    case 'disconnected':
      showOfflineIndicator();
      break;
    case 'unavailable':
      showUnavailableMessage();
      break;
    case 'failed':
      showFailedMessage();
      break;
  }
});

// Handle specific states
pusher.connection.bind('unavailable', function() {
  console.warn('Pusher is unavailable');
  showWarning('Real-time updates are temporarily unavailable');
});

pusher.connection.bind('failed', function() {
  console.error('Pusher connection failed');
  showError('Unable to establish real-time connection. Please refresh the page.');
});
Authentication Errors

Handle errors when subscribing to private channels:

const channel = pusher.subscribe(PRIVATE_CHANNEL);

channel.bind('pusher:subscription_error', function(status) {
  console.error('Subscription error:', status);

  if (status === 403) {
    showError('Authentication failed. Please log in again.');
    // Redirect to login page
    window.location.href = '/login';
  } else if (status === 408) {
    showError('Subscription timeout. Please check your network connection.');
  } else {
    showError('Failed to subscribe to real-time updates.');
  }
});

// Handle successful subscription
channel.bind('pusher:subscription_succeeded', function() {
  console.log('Successfully subscribed to channel');
  showSuccess('Real-time updates enabled');
});

Complete Working Example

Here's a complete example integrating all the concepts:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pusher Events Example</title>
  <script src="https://js.pusher.com/8.0/pusher.min.js"></script>
</head>
<body>
  <div id="status">Connecting...</div>
  <div id="messages"></div>

  <script>
    // Configuration
    const API_BASE_URL = 'https://backoffice.ddev.site';
    const ACCESS_TOKEN = localStorage.getItem('access_token'); // From /api/login-token
    const statusEl = document.getElementById('status');
    const messagesEl = document.getElementById('messages');

    // Fetch Pusher configuration from /api/me
    fetch(`${API_BASE_URL}/api/me?access_token=${ACCESS_TOKEN}`)
      .then(response => response.json())
      .then(data => {
        const config = data.data[0].config;

        // Extract Pusher configuration
        const PUSHER_KEY = config.pusherConfig.key;
        const PUSHER_CLUSTER = config.pusherConfig.cluster;
        const PRIVATE_CHANNEL = config.pusherConfig.privateChannel;

        // Initialize Pusher with regular access token
        const pusher = new Pusher(PUSHER_KEY, {
          cluster: PUSHER_CLUSTER,
          encrypted: true,
          forceTLS: true,
          authEndpoint: `${API_BASE_URL}/api/v1.0/pusher_auth?access_token=${ACCESS_TOKEN}`
        });

        // Monitor connection state
        pusher.connection.bind('state_change', function(states) {
          statusEl.textContent = `Status: ${states.current}`;
          console.log('State changed:', states.previous, '->', states.current);
        });

        pusher.connection.bind('connected', function() {
          statusEl.textContent = 'Connected';
          statusEl.style.color = 'green';
        });

        pusher.connection.bind('error', function(error) {
          const errorCode = error.error?.data?.code;
          const errorMsg = error.error?.data?.message || 'Unknown error';
          statusEl.textContent = `Error: ${errorMsg}`;
          statusEl.style.color = 'red';
          console.error('Connection error:', errorCode, errorMsg);
        });

        // Subscribe to private channel
        const channel = pusher.subscribe(PRIVATE_CHANNEL);

        // Handle subscription events
        channel.bind('pusher:subscription_succeeded', function() {
          console.log('Successfully subscribed to channel');
          addMessage('Subscribed to real-time events', 'success');
        });

        channel.bind('pusher:subscription_error', function(status) {
          console.error('Subscription failed:', status);
          addMessage('Failed to subscribe: ' + status, 'error');
        });

        // Subscribe to application events
        channel.bind('entity_update', function(data) {
          console.log('Entity updated:', data);
          addMessage(`${data.bundle} updated: ${data.entity_id}`, 'info');
        });

        channel.bind('advanced_queue__post_execute', function(data) {
          console.log('Queue task completed:', data.name);
          addMessage(`Task completed: ${data.name}`, 'success');
        });

        channel.bind('item_bulk', function(data) {
          console.log('Bulk operation:', data);
          addMessage(`Bulk ${data.name}: ${data.count} items`, 'info');
        });

        channel.bind('caller_id', function(data) {
          console.log('Incoming call:', data);
          addMessage(`Incoming call from: ${data.client?.name || 'Unknown'}`, 'warning');
        });

        // Cleanup on page unload
        window.addEventListener('beforeunload', function() {
          pusher.disconnect();
        });
      })
      .catch(error => {
        console.error('Failed to initialize Pusher:', error);
        statusEl.textContent = 'Failed to load configuration';
        statusEl.style.color = 'red';
      });

    // Helper function to display messages
    function addMessage(text, type = 'info') {
      const msg = document.createElement('div');
      msg.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
      msg.style.padding = '5px';
      msg.style.margin = '5px 0';
      msg.style.borderLeft = '3px solid ' + getColor(type);
      messagesEl.insertBefore(msg, messagesEl.firstChild);
    }

    function getColor(type) {
      const colors = {
        success: '#28a745',
        error: '#dc3545',
        warning: '#ffc107',
        info: '#17a2b8'
      };
      return colors[type] || colors.info;
    }
  </script>
</body>
</html>

Testing Pusher Connection

Debug Mode

Enable Pusher's debug mode to see detailed logging:

// Enable Pusher logging (for development only)
Pusher.logToConsole = true;

const pusher = new Pusher(PUSHER_KEY, {
  cluster: PUSHER_CLUSTER,
  encrypted: true,
  forceTLS: true,
  authEndpoint: authEndpoint
});
Test Connection

Verify your Pusher connection is working:

// Check connection state
console.log('Connection state:', pusher.connection.state);

// Available states: initialized, connecting, connected, unavailable, failed, disconnected

// Manually trigger connection (if needed)
pusher.connect();

// Check if connected
if (pusher.connection.state === 'connected') {
  console.log('Pusher is connected');
} else {
  console.log('Pusher is not connected:', pusher.connection.state);
}
Verify Channel Subscription
// Get channel object
const channel = pusher.channel(PRIVATE_CHANNEL);

if (channel && channel.subscribed) {
  console.log('Successfully subscribed to channel');
} else {
  console.log('Not subscribed to channel');
}

// List all subscribed channels
console.log('All channels:', pusher.allChannels());
Test Event Reception

Use Pusher's debug console to send test events:

  1. Go to your Pusher dashboard at dashboard.pusher.com
  2. Select your application
  3. Navigate to "Debug Console"
  4. Send a test event to your private channel to verify it's received
Tip: Check your browser's Network tab to see the WebSocket connection and authentication requests to the /api/v1.0/pusher_auth endpoint.

Quick Reference

Event Name Purpose Frequency
entity_update Entity changes (items, clients, sales, tasks) Very Common
advanced_queue__post_execute Queue task completion Very Common
advanced_queue__pre_execute Queue task start Common
item_bulk Bulk item operations Common
control_update Control verification progress Occasional
knock_down_all_items Bulk knock-down progress Occasional
caller_id Incoming call notifications Occasional
advanced_queue__create Queue task creation Rare (available but unused)
advanced_queue__update Queue task updates Rare (available but unused)
Back to API Home