Fix FCM token registration and add queue reminder alerts

- Fix silent insert failure in FCM token registration (missing NOT NULL
  refresh_token column) so WebView app tokens are actually stored
- Add 1-minute queue reminder cron that re-sends FCM alerts for calls
  still waiting, with transient-based throttle to prevent duplicates
- Send FCM cancel on queue dequeue (answered/hangup/timeout), not just
  on final call status webhook
- Clean up new cron hook on plugin deactivation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-03-10 10:29:33 -07:00
parent d00a906d07
commit a2ea99bb09
5 changed files with 108 additions and 8 deletions

View File

@@ -577,6 +577,69 @@ class TWP_Call_Queue {
return $status; return $status;
} }
/**
* Cron callback: re-send FCM queue alerts every minute for calls still waiting.
* Only alerts for calls that have been waiting > 60 seconds (initial alert
* already sent on entry). Skips re-alerting for the same call within 55 seconds
* using a short transient to avoid overlap with the 60-second cron.
*/
public static function send_queue_reminders() {
global $wpdb;
$calls_table = $wpdb->prefix . 'twp_queued_calls';
$queue_table = $wpdb->prefix . 'twp_call_queues';
// Find calls waiting longer than 60 seconds
$waiting_calls = $wpdb->get_results(
"SELECT c.*, q.queue_name, q.user_id AS queue_owner_id, q.agent_group_id
FROM $calls_table c
JOIN $queue_table q ON q.id = c.queue_id
WHERE c.status = 'waiting'
AND c.joined_at <= DATE_SUB(NOW(), INTERVAL 60 SECOND)"
);
if (empty($waiting_calls)) {
return;
}
require_once dirname(__FILE__) . '/class-twp-fcm.php';
$fcm = new TWP_FCM();
foreach ($waiting_calls as $call) {
// Throttle: skip if we reminded for this call within the last 55 seconds
$transient_key = 'twp_queue_remind_' . $call->call_sid;
if (get_transient($transient_key)) {
continue;
}
set_transient($transient_key, 1, 55);
$waiting_minutes = max(1, round((time() - strtotime($call->joined_at)) / 60));
$title = 'Call Still Waiting';
$body = "Call from {$call->from_number} waiting {$waiting_minutes}m in {$call->queue_name}";
$notified_users = array();
// Notify queue owner
if (!empty($call->queue_owner_id)) {
$fcm->notify_queue_alert($call->queue_owner_id, $call->from_number, $call->queue_name, $call->call_sid);
$notified_users[] = $call->queue_owner_id;
}
// Notify agent group members
if (!empty($call->agent_group_id)) {
require_once dirname(__FILE__) . '/class-twp-agent-groups.php';
$members = TWP_Agent_Groups::get_group_members($call->agent_group_id);
foreach ($members as $member) {
if (!in_array($member->user_id, $notified_users)) {
$fcm->notify_queue_alert($member->user_id, $call->from_number, $call->queue_name, $call->call_sid);
$notified_users[] = $member->user_id;
}
}
}
error_log("TWP Queue Reminder: Re-alerted " . count($notified_users) . " user(s) for call {$call->call_sid} waiting {$waiting_minutes}m");
}
}
/** /**
* Notify agents via SMS when a call enters the queue * Notify agents via SMS when a call enters the queue
*/ */

View File

@@ -265,6 +265,9 @@ class TWP_Core {
$queue = new TWP_Call_Queue(); $queue = new TWP_Call_Queue();
$this->loader->add_action('twp_process_queue', $queue, 'process_waiting_calls'); $this->loader->add_action('twp_process_queue', $queue, 'process_waiting_calls');
// Queue reminder alerts (re-send FCM every minute for waiting calls)
add_action('twp_queue_reminders', array('TWP_Call_Queue', 'send_queue_reminders'));
// Callback processing // Callback processing
$this->loader->add_action('twp_process_callbacks', 'TWP_Callback_Manager', 'process_callbacks'); $this->loader->add_action('twp_process_callbacks', 'TWP_Callback_Manager', 'process_callbacks');
@@ -281,6 +284,10 @@ class TWP_Core {
wp_schedule_event(time(), 'twp_every_30_seconds', 'twp_process_queue'); wp_schedule_event(time(), 'twp_every_30_seconds', 'twp_process_queue');
} }
if (!wp_next_scheduled('twp_queue_reminders')) {
wp_schedule_event(time(), 'twp_every_minute', 'twp_queue_reminders');
}
if (!wp_next_scheduled('twp_process_callbacks')) { if (!wp_next_scheduled('twp_process_callbacks')) {
wp_schedule_event(time(), 'twp_every_minute', 'twp_process_callbacks'); wp_schedule_event(time(), 'twp_every_minute', 'twp_process_callbacks');
} }

View File

@@ -11,6 +11,7 @@ class TWP_Deactivator {
// Clear scheduled events // Clear scheduled events
wp_clear_scheduled_hook('twp_check_schedules'); wp_clear_scheduled_hook('twp_check_schedules');
wp_clear_scheduled_hook('twp_process_queue'); wp_clear_scheduled_hook('twp_process_queue');
wp_clear_scheduled_hook('twp_queue_reminders');
wp_clear_scheduled_hook('twp_auto_revert_agents'); wp_clear_scheduled_hook('twp_auto_revert_agents');
// Flush rewrite rules // Flush rewrite rules

View File

@@ -55,17 +55,32 @@ class TWP_Mobile_Phone_Page {
$user_id, $fcm_token $user_id, $fcm_token
)); ));
if (!$existing) { if ($existing) {
// Refresh the expiry on existing session
$wpdb->update($table,
array('expires_at' => date('Y-m-d H:i:s', time() + 7 * DAY_IN_SECONDS)),
array('id' => $existing->id),
array('%s'),
array('%d')
);
} else {
$wpdb->insert($table, array( $wpdb->insert($table, array(
'user_id' => $user_id, 'user_id' => $user_id,
'fcm_token' => $fcm_token, 'refresh_token' => 'webview-' . wp_generate_password(32, false),
'device_info' => 'WebView Mobile App', 'fcm_token' => $fcm_token,
'is_active' => 1, 'device_info' => 'WebView Mobile App',
'created_at' => current_time('mysql'), 'is_active' => 1,
'expires_at' => date('Y-m-d H:i:s', time() + 7 * DAY_IN_SECONDS), 'created_at' => current_time('mysql'),
'expires_at' => date('Y-m-d H:i:s', time() + 7 * DAY_IN_SECONDS),
)); ));
if ($wpdb->last_error) {
error_log('TWP FCM: Failed to insert token: ' . $wpdb->last_error);
wp_send_json_error('Failed to store token');
}
} }
error_log("TWP FCM: Token registered for user $user_id");
wp_send_json_success('FCM token registered'); wp_send_json_success('FCM token registered');
} }

View File

@@ -1276,10 +1276,24 @@ class TWP_Webhooks {
if ($updated) { if ($updated) {
error_log('TWP Queue Action: Updated call status to ' . $status); error_log('TWP Queue Action: Updated call status to ' . $status);
// Cancel FCM queue alerts when call leaves the queue for any reason
if (in_array($status, array('answered', 'hangup', 'transferred', 'timeout', 'completed'))) {
$queued_call = $wpdb->get_row($wpdb->prepare(
"SELECT queue_id FROM $table_name WHERE call_sid = %s",
$call_sid
));
if ($queued_call) {
require_once plugin_dir_path(__FILE__) . 'class-twp-fcm.php';
$fcm = new TWP_FCM();
$fcm->cancel_queue_alert_for_queue($queued_call->queue_id, $call_sid);
error_log('TWP Queue Action: Sent FCM cancel for call ' . $call_sid);
}
}
} else { } else {
error_log('TWP Queue Action: No call found to update with SID ' . $call_sid); error_log('TWP Queue Action: No call found to update with SID ' . $call_sid);
} }
// Return empty response - this is just for tracking // Return empty response - this is just for tracking
return $this->send_twiml_response('<Response></Response>'); return $this->send_twiml_response('<Response></Response>');
} }