Add TWP Softphone Flutter app and complete mobile backend API
All checks were successful
Create Release / build (push) Successful in 4s
All checks were successful
Create Release / build (push) Successful in 4s
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for mobile VoIP, implement unhold_call() with call leg detection, wire FCM push notifications into call queue and webhook missed call handlers, add data-only FCM message support for Android background wake, and add Twilio API Key / Push Credential settings fields. Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth with auto-refresh, SSE real-time queue updates, FCM push notifications, Material 3 UI with dashboard, active call screen, dialpad, and call controls (mute/speaker/hold/transfer). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,13 @@ class TWP_Mobile_API {
|
||||
'callback' => array($this, 'update_agent_phone'),
|
||||
'permission_callback' => array($this->auth, 'verify_token')
|
||||
));
|
||||
|
||||
// Voice token for VoIP
|
||||
register_rest_route('twilio-mobile/v1', '/voice/token', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_voice_token'),
|
||||
'permission_callback' => array($this->auth, 'verify_token')
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -507,11 +514,46 @@ class TWP_Mobile_API {
|
||||
* Unhold a call (resume from hold queue)
|
||||
*/
|
||||
public function unhold_call($request) {
|
||||
// Implementation would retrieve from hold queue and reconnect
|
||||
return new WP_REST_Response(array(
|
||||
'success' => true,
|
||||
'message' => 'Unhold functionality - to be implemented with queue retrieval'
|
||||
), 501);
|
||||
$user_id = $this->auth->get_current_user_id();
|
||||
$call_sid = $request['call_sid'];
|
||||
|
||||
try {
|
||||
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-admin.php';
|
||||
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-twilio-api.php';
|
||||
|
||||
$admin = new TWP_Admin('twilio-wp-plugin', TWP_VERSION);
|
||||
$twilio = new TWP_Twilio_API();
|
||||
|
||||
// Find customer call leg
|
||||
$customer_call_sid = $admin->find_customer_call_leg($call_sid, $twilio);
|
||||
|
||||
if (!$customer_call_sid) {
|
||||
return new WP_Error('call_not_found', 'Could not find customer call leg', array('status' => 404));
|
||||
}
|
||||
|
||||
// Build identity for this agent
|
||||
$user = get_userdata($user_id);
|
||||
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $user->user_login);
|
||||
if (empty($clean_name)) {
|
||||
$clean_name = 'user';
|
||||
}
|
||||
$identity = 'agent' . $user_id . $clean_name;
|
||||
|
||||
// Redirect customer back to agent's client
|
||||
$twiml = new \Twilio\TwiML\VoiceResponse();
|
||||
$dial = $twiml->dial();
|
||||
$dial->client($identity);
|
||||
|
||||
$twilio->update_call($customer_call_sid, array('twiml' => $twiml->asXML()));
|
||||
|
||||
return new WP_REST_Response(array(
|
||||
'success' => true,
|
||||
'message' => 'Call resumed from hold'
|
||||
), 200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return new WP_Error('unhold_error', $e->getMessage(), array('status' => 500));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,6 +683,55 @@ class TWP_Mobile_API {
|
||||
), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Voice access token for VoIP
|
||||
*/
|
||||
public function get_voice_token($request) {
|
||||
$user_id = $this->auth->get_current_user_id();
|
||||
$user = get_userdata($user_id);
|
||||
$identity = 'agent' . $user_id . preg_replace('/[^a-zA-Z0-9]/', '', $user->user_login);
|
||||
|
||||
$account_sid = get_option('twp_twilio_account_sid');
|
||||
$auth_token = get_option('twp_twilio_auth_token');
|
||||
$api_key_sid = get_option('twp_twilio_api_key_sid');
|
||||
$api_key_secret = get_option('twp_twilio_api_key_secret');
|
||||
$twiml_app_sid = get_option('twp_twiml_app_sid');
|
||||
$push_credential_sid = get_option('twp_fcm_push_credential_sid');
|
||||
|
||||
if (empty($api_key_sid) || empty($api_key_secret)) {
|
||||
return new WP_Error('missing_api_key', 'Twilio API Key SID and Secret must be configured', array('status' => 500));
|
||||
}
|
||||
|
||||
try {
|
||||
$token = new \Twilio\Jwt\AccessToken(
|
||||
$account_sid,
|
||||
$api_key_sid,
|
||||
$api_key_secret,
|
||||
3600,
|
||||
$identity
|
||||
);
|
||||
|
||||
$voiceGrant = new \Twilio\Jwt\Grants\VoiceGrant();
|
||||
$voiceGrant->setOutgoingApplicationSid($twiml_app_sid);
|
||||
$voiceGrant->setIncomingAllow(true);
|
||||
|
||||
if (!empty($push_credential_sid)) {
|
||||
$voiceGrant->setPushCredentialSid($push_credential_sid);
|
||||
}
|
||||
|
||||
$token->addGrant($voiceGrant);
|
||||
|
||||
return new WP_REST_Response(array(
|
||||
'token' => $token->toJWT(),
|
||||
'identity' => $identity,
|
||||
'expires_in' => 3600
|
||||
), 200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return new WP_Error('token_error', $e->getMessage(), array('status' => 500));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has access to a queue
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user