diff --git a/CLAUDE.md b/CLAUDE.md index e8c8128..d3f3ee0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,7 +154,7 @@ twilio-wp-plugin/ #### TWP_Shortcodes - **Purpose**: WordPress shortcodes for frontend features -- **Shortcode**: `[twp_browser_phone]` - Browser phone interface +- **Shortcode**: `[twp_browser_phone]` - Admin browser phone redirect interface ## πΎ Database Schema @@ -240,7 +240,7 @@ twilio-wp-plugin/ - `/smart-routing-fallback` - Routing error handler - `/browser-fallback` - Browser phone fallback -## ποΈ AJAX Endpoints (Admin & Frontend) +## ποΈ AJAX Endpoints (Admin Only) ### Total: 68 AJAX Actions @@ -271,12 +271,31 @@ All call control functions (`twp_toggle_hold`, `twp_transfer_call`, `twp_requeue ## π¨ Frontend Components +### Shortcode Implementation +The `[twp_browser_phone]` shortcode now provides a **redirect interface** instead of a full browser phone: + +#### Shortcode Attributes +- **`title`**: Display title (default: "Browser Phone") +- **`button_text`**: Button text (default: "Access Browser Phone") +- **`target`**: Link target (default: "_blank" - opens in new tab) + +#### Security Features +- **Login Required**: Users must be logged in to see the redirect +- **Permission Check**: Requires `twp_access_browser_phone` or `manage_options` capability +- **Error Messages**: Clear feedback for unauthorized access + +#### Styling +- **Inline CSS Only**: No external CSS files loaded +- **Minimal Assets**: Reduces frontend bloat +- **Responsive Design**: Works on all device sizes + ### JavaScript Files 1. **admin.js** (116KB) - Admin interface functionality -2. **browser-phone-frontend.js** (85KB) - Browser phone implementation -3. **twp-service-worker.js** (2.5KB) - Push notifications +2. **twp-service-worker.js** (2.5KB) - Push notifications -### Browser Phone Features +### Browser Phone Features (Admin Only) +- **Enhanced Security**: All browser phone functionality restricted to admin area +- **Admin URL**: `admin.php?page=twilio-wp-browser-phone` - Twilio Device SDK integration - Real-time call controls (hold, transfer, record) - Queue monitoring dashboard @@ -285,11 +304,36 @@ All call control functions (`twp_toggle_hold`, `twp_transfer_call`, `twp_requeue - Visual call state indicators ### CSS Files -- **admin.css** - Admin interface styling -- **browser-phone-frontend.css** - Browser phone UI +- **admin.css** - Admin interface styling (includes browser phone UI) ## π§ Recent Fixes & Improvements +### SECURITY ENHANCEMENT: Frontend Browser Phone Removal (September 2025) - PRODUCTION READY +Major security enhancement by removing frontend browser phone interface and implementing admin-only access. + +#### Browser Phone Security Enhancement +- **Frontend Interface Removed**: Eliminated full browser phone interface from frontend shortcode +- **Admin-Only Access**: All browser phone functionality moved to secure admin area +- **Asset Reduction**: Removed 108KB of frontend assets (browser-phone-frontend.js and browser-phone-frontend.css) +- **Redirect Interface**: Shortcode now provides secure redirect to admin browser phone page +- **Enhanced Permissions**: Strict capability checking with clear error messages for unauthorized users +- **Reduced Attack Surface**: Minimized frontend JavaScript exposure and potential security vectors +- **Performance Improvement**: Reduced frontend asset loading and improved page load times + +#### Shortcode Transformation +- **Security-First Design**: Login and permission validation before any functionality access +- **Minimal Asset Loading**: Only essential inline CSS for redirect interface styling +- **Responsive Redirect**: Professional redirect interface works on all devices +- **Customizable Attributes**: title, button_text, and target attributes for flexibility +- **Clear Error Messaging**: Informative error messages for authentication and authorization failures + +#### Technical Implementation +- **File Removal**: `assets/js/browser-phone-frontend.js` and `assets/css/browser-phone-frontend.css` eliminated +- **Inline Styling**: Minimal CSS injected only when shortcode is present on page +- **Permission System**: Leverages WordPress capability system (`twp_access_browser_phone` or `manage_options`) +- **Admin URL Generation**: Secure admin_url() function for proper WordPress admin integration +- **Target Control**: Configurable link target (_blank by default for better UX) + ### PRODUCTION READY: Extension Transfer System Overhaul (September 2025) - FULLY RESOLVED Comprehensive overhaul of extension-based call transfers with enterprise-grade reliability. diff --git a/README.md b/README.md index a50fffe..546493b 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will - **Queue Notifications**: SMS alerts to designated numbers when calls enter queues ### π Browser Phone (WebRTC) -- **In-Browser Calling**: Make and receive calls directly from WordPress admin or frontend +- **Admin-Only Access**: Secure browser phone interface available only in WordPress admin area +- **Enhanced Security**: All browser phone functionality moved from frontend for improved security - **Twilio Voice SDK v2**: Uses latest SDK for WebRTC functionality - **Visual Dialpad**: Click-to-dial interface with DTMF support - **Call Controls**: Mute, hold indicators, call timer @@ -54,7 +55,7 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will - **Queue Integration**: Accept calls from specific queues - **Token Management**: Automatic token refresh for uninterrupted service - **Mobile-Friendly**: Responsive design optimized for smartphones and tablets -- **Shortcode Support**: Embed browser phone on any page with `[twp_browser_phone]` +- **Shortcode Redirect**: `[twp_browser_phone]` shortcode provides secure redirect to admin interface ### π Business Hours Management - **Schedule-based Routing**: Different call flows for business hours vs after-hours @@ -113,7 +114,15 @@ This plugin **requires** the Twilio PHP SDK v8.7.0 to function. The plugin will ## Recent Updates ### MAJOR RELEASE v2.3.0 (September 2025) - Enterprise Ready -Comprehensive system overhaul with production-ready reliability improvements. +Comprehensive system overhaul with production-ready reliability improvements and enhanced security. + +#### π Security Enhancement: Frontend Browser Phone Removal +- **Security-First Approach**: Removed full browser phone interface from frontend shortcode +- **Admin-Only Access**: All browser phone functionality now restricted to secure WordPress admin area +- **Asset Reduction**: Eliminated 108KB of frontend assets (browser-phone-frontend.js and CSS) +- **Reduced Attack Surface**: Minimized frontend JavaScript exposure and potential security vectors +- **Performance Boost**: Improved page load times with reduced frontend asset loading +- **Professional Redirect**: Shortcode now provides clean redirect interface to admin browser phone #### π Extension Transfer System - Complete Solution - **FIXED: Direct Voicemail Issue**: Extension transfers no longer go directly to voicemail for available agents @@ -326,11 +335,16 @@ Comprehensive redesign of hold, transfer, and requeue functionality with profess - Enter TwiML App SID - Save settings -3. **Access Browser Phone**: - - Navigate to **Twilio** β **Browser Phone** +3. **Access Browser Phone** (Admin Only): + - Navigate to **WordPress Admin** β **Twilio** β **Browser Phone** - Select caller ID from available numbers - Start making/receiving calls +4. **Frontend Access** (Optional): + - Use `[twp_browser_phone]` shortcode on any page + - Shortcode provides secure redirect to admin interface + - Requires user login and appropriate permissions + ### Agent Configuration 1. **Create Agent Groups** (Twilio β Agent Groups) 2. **Add Agents** to groups with priorities @@ -356,37 +370,41 @@ Comprehensive redesign of hold, transfer, and requeue functionality with profess - **Queue Timeouts**: Alert when calls wait too long (configurable threshold) - **Missed Calls**: Alert when calls are abandoned or timeout -## Frontend Browser Phone +## Browser Phone Access -### Shortcode Usage -The browser phone can be embedded on any WordPress page using the `[twp_browser_phone]` shortcode. This is perfect for creating dedicated agent pages or customer service portals. +### Shortcode Usage (Redirect Interface) +The `[twp_browser_phone]` shortcode now provides a **secure redirect interface** that directs users to the WordPress admin browser phone page for enhanced security. **Basic Usage:** ``` [twp_browser_phone] ``` -**With Options:** +**With Custom Options:** ``` -[twp_browser_phone title="Customer Service Phone" show_title="true" compact="false"] +[twp_browser_phone title="Customer Service Phone" button_text="Access Phone System" target="_blank"] ``` ### Shortcode Parameters -- **title**: Custom title for the phone widget (default: "Browser Phone") -- **show_title**: Display the title above the phone (default: "true") -- **compact**: Use compact layout for smaller spaces (default: "false") +- **title**: Custom title for the redirect interface (default: "Browser Phone") +- **button_text**: Text for the admin link button (default: "Access Browser Phone") +- **target**: Link target behavior (default: "_blank" - opens in new tab) -### Permission Requirements -- Users must be **logged in** to access the browser phone -- Users need the **`twp_access_browser_phone`** capability or **`manage_options`** -- Phone Agent role users have access by default +### Security Features +- **Authentication Required**: Users must be logged in to see the redirect interface +- **Permission Validation**: Requires `twp_access_browser_phone` capability or `manage_options` +- **Clear Error Messages**: Informative feedback for unauthorized access attempts +- **Admin-Only Interface**: All browser phone functionality restricted to WordPress admin area -### Mobile Optimization -- **Responsive Design**: Adapts to all screen sizes -- **Touch-Friendly**: Large buttons optimized for mobile devices -- **Haptic Feedback**: Vibration on button press (where supported) -- **Auto-Zoom Prevention**: Proper viewport handling for mobile browsers -- **Dark Mode Support**: Automatically adapts to user's system preference +### Browser Phone Interface (Admin Area) +Access the full browser phone interface at: **WordPress Admin β Twilio β Browser Phone** + +#### Features: +- **Enhanced Security**: Protected by WordPress admin authentication +- **Full Functionality**: Complete browser phone features available in secure environment +- **Responsive Design**: Adapts to all screen sizes within admin interface +- **Professional Interface**: Integrated with WordPress admin styling +- **No Frontend Assets**: Eliminates security risks from frontend JavaScript exposure ## Voice Configuration @@ -407,10 +425,12 @@ The browser phone can be embedded on any WordPress page using the `[twp_browser_ ### Common Issues -#### Browser Phone "Client version not supported" -- **Fixed in latest version**: Upgraded to Voice SDK v2 -- Clear browser cache and reload page -- Check TwiML App SID is configured +#### Browser Phone Access Issues +- **Access via Admin Only**: Browser phone moved to WordPress admin area for security +- **Shortcode Redirect**: Frontend shortcode now provides redirect to admin interface +- **Permission Required**: Ensure user has `twp_access_browser_phone` or `manage_options` capability +- **Login Required**: Users must be logged in to access browser phone functionality +- Check TwiML App SID is configured in WordPress admin settings #### "Twilio SDK classes not available" ```bash @@ -519,6 +539,10 @@ All webhooks are REST API endpoints under `/wp-json/twilio-webhook/v1/`: - **Mobile Browsers**: Limited WebRTC support ### Security Considerations +- **Admin-Only Browser Phone**: All browser phone functionality restricted to WordPress admin area +- **Frontend Asset Elimination**: Removed 108KB of frontend JavaScript and CSS to reduce attack surface +- **Authentication Required**: Shortcode requires user login and appropriate permissions +- **Secure Redirects**: Frontend shortcode provides authenticated redirect to admin interface - All webhooks use WordPress nonce verification - Phone numbers validated and sanitized - SQL queries use prepared statements @@ -528,6 +552,10 @@ All webhooks are REST API endpoints under `/wp-json/twilio-webhook/v1/`: ## Version History ### v2.3.0 (Current - September 2025) - ENTERPRISE READY +- **SECURITY ENHANCEMENT**: Removed frontend browser phone interface, moved to admin-only access for enhanced security +- **ASSET REDUCTION**: Eliminated 108KB of frontend assets (browser-phone-frontend.js and CSS files) +- **SHORTCODE SECURITY**: Browser phone shortcode now provides secure redirect with authentication checks +- **ATTACK SURFACE REDUCTION**: Minimized frontend JavaScript exposure and potential security vectors - **EXTENSION TRANSFER SYSTEM**: Complete overhaul with queue-based routing, 2-minute timeout, and automatic voicemail fallback - **BROWSER PHONE UNIVERSAL**: Firefox support, permission management, call stability, and cross-browser compatibility - **AGENT STATUS AUTOMATION**: 1-minute auto-revert system with WordPress cron job integration diff --git a/assets/css/browser-phone-frontend.css b/assets/css/browser-phone-frontend.css deleted file mode 100644 index bf06278..0000000 --- a/assets/css/browser-phone-frontend.css +++ /dev/null @@ -1,1340 +0,0 @@ -/* Twilio Browser Phone Frontend Styles - Mobile First */ - -/* Call Control Panel Styles */ -.twp-call-controls-panel { - margin: 15px 0; - padding: 15px; - background: #fff; - border-radius: 8px; - border: 1px solid #dee2e6; -} - -.call-control-buttons { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 10px; -} - -.twp-btn-control { - padding: 10px 15px; - background: #6c757d; - color: white; - border: none; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; -} - -.twp-btn-control:hover { - background: #5a6268; - transform: translateY(-1px); -} - -.twp-btn-control.btn-active { - background: #007bff; -} - -.twp-btn-control.btn-recording { - background: #dc3545; - animation: recording-pulse 1.5s infinite; -} - -@keyframes recording-pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -/* Dialog Overlay Styles */ -.twp-dialog-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 10000; -} - -.twp-dialog { - background: white; - border-radius: 12px; - padding: 25px; - max-width: 400px; - width: 90%; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); -} - -.twp-dialog h3 { - margin: 0 0 15px 0; - font-size: 1.3rem; - color: #212529; -} - -.twp-dialog p { - margin: 0 0 20px 0; - color: #6c757d; -} - -.twp-dialog input[type="tel"], -.twp-dialog select { - width: 100%; - padding: 10px; - border: 2px solid #dee2e6; - border-radius: 6px; - font-size: 16px; - margin-bottom: 20px; -} - -.twp-dialog input[type="tel"]:focus, -.twp-dialog select:focus { - outline: none; - border-color: #007bff; -} - -.dialog-buttons { - display: flex; - gap: 10px; - justify-content: flex-end; -} - -.dialog-buttons .twp-btn { - padding: 10px 20px; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; -} - -.dialog-buttons .twp-btn-primary { - background: #007bff; - color: white; - border: none; -} - -.dialog-buttons .twp-btn-primary:hover { - background: #0056b3; -} - -.dialog-buttons .twp-btn-secondary { - background: #6c757d; - color: white; - border: none; -} - -.dialog-buttons .twp-btn-secondary:hover { - background: #5a6268; -} - -/* Agent Transfer Dialog Styles */ -.twp-agent-transfer-dialog { - max-width: 500px; -} - -.agent-list { - max-height: 300px; - overflow-y: auto; - margin-bottom: 20px; - border: 1px solid #dee2e6; - border-radius: 6px; -} - -.agent-option { - padding: 12px; - border-bottom: 1px solid #e9ecef; - cursor: pointer; - transition: background 0.2s; - display: flex; - justify-content: space-between; - align-items: center; -} - -.agent-option:last-child { - border-bottom: none; -} - -.agent-option:hover:not(.offline) { - background: #f8f9fa; -} - -.agent-option.selected { - background: #e7f3ff; - border-left: 3px solid #007bff; -} - -.agent-option.offline { - opacity: 0.5; - cursor: not-allowed; -} - -.agent-info { - display: flex; - align-items: center; - gap: 10px; -} - -.agent-name { - font-weight: 500; - color: #212529; -} - -.agent-method { - font-size: 18px; -} - -.agent-status { - font-size: 14px; - white-space: nowrap; -} - -.manual-option { - margin-top: 20px; - padding-top: 20px; - border-top: 1px solid #dee2e6; -} - -.manual-option p { - margin-bottom: 10px; - font-size: 14px; - color: #6c757d; -} -.twp-browser-phone-container { - max-width: 400px; - margin: 0 auto; - padding: 20px; - background: #f8f9fa; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - box-sizing: border-box; -} - -.twp-browser-phone-container * { - box-sizing: border-box; -} - -.twp-browser-phone-container.compact { - padding: 15px; - max-width: 320px; -} - -.twp-browser-phone-title { - text-align: center; - margin: 0 0 20px 0; - color: #212529; - font-size: 1.5rem; - font-weight: 600; -} - -/* Connection Status */ -.twp-connection-status { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 20px; - padding: 12px; - background: #fff; - border-radius: 8px; - border: 2px solid #e9ecef; -} - -.status-indicator { - width: 12px; - height: 12px; - border-radius: 50%; - margin-right: 8px; - animation: pulse 2s infinite; -} - -.status-indicator.offline { - background-color: #dc3545; -} - -.status-indicator.connecting { - background-color: #ffc107; -} - -.status-indicator.online { - background-color: #28a745; - animation: none; -} - -.status-text { - font-weight: 600; - color: #212529; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.5; } - 100% { opacity: 1; } -} - -/* Phone Number Selection */ -.twp-phone-selection { - margin-bottom: 20px; -} - -.twp-phone-selection label { - display: block; - margin-bottom: 8px; - font-weight: 600; - color: #212529; -} - -.twp-select { - width: 100%; - padding: 12px 16px; - border: 2px solid #e9ecef; - border-radius: 8px; - font-size: 16px; - background: #fff; - color: #212529; - appearance: none; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); - background-position: right 12px center; - background-repeat: no-repeat; - background-size: 16px; - padding-right: 40px; -} - -.twp-select:focus { - outline: none; - border-color: #007cba; - box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); -} - -/* Dial Pad */ -.twp-dialpad { - margin-bottom: 20px; -} - -.twp-number-display { - position: relative; - margin-bottom: 20px; -} - -#twp-dial-number { - width: 100%; - padding: 16px 50px 16px 16px; - border: 2px solid #e9ecef; - border-radius: 8px; - font-size: 18px; - text-align: center; - background: #fff; - letter-spacing: 2px; -} - -#twp-dial-number:focus { - outline: none; - border-color: #007cba; - box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); -} - -.twp-btn-clear { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - background: #dc3545; - color: white; - border: none; - border-radius: 50%; - width: 32px; - height: 32px; - font-size: 18px; - font-weight: bold; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; -} - -.twp-btn-clear:hover { - background: #c82333; -} - -/* Dial Grid */ -.twp-dial-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 12px; - max-width: 280px; - margin: 0 auto; -} - -.twp-dial-btn { - background: #fff; - border: 2px solid #e9ecef; - border-radius: 12px; - padding: 16px 8px; - font-size: 20px; - font-weight: 600; - color: #212529; - cursor: pointer; - transition: all 0.2s ease; - min-height: 60px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - user-select: none; - -webkit-user-select: none; - -webkit-tap-highlight-color: transparent; -} - -.twp-dial-btn:hover { - background: #f8f9fa; - border-color: #007cba; - transform: translateY(-1px); -} - -.twp-dial-btn:active { - transform: translateY(0); - background: #e9ecef; -} - -.twp-dial-btn span { - font-size: 11px; - color: #6c757d; - margin-top: 2px; - font-weight: 400; -} - -/* Call Controls */ -.twp-call-controls { - display: flex; - gap: 12px; - margin-bottom: 20px; -} - -.twp-btn { - flex: 1; - padding: 16px 24px; - border: none; - border-radius: 8px; - font-size: 16px; - font-weight: 600; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - min-height: 56px; -} - -.twp-btn-primary { - background: #28a745; - color: white; -} - -.twp-btn-primary:hover { - background: #218838; - transform: translateY(-1px); -} - -.twp-btn-primary:disabled { - background: #6c757d; - cursor: not-allowed; - transform: none; -} - -.twp-btn-danger { - background: #dc3545; - color: white; -} - -.twp-btn-danger:hover { - background: #c82333; - transform: translateY(-1px); -} - -.twp-btn-warning { - background: #ffc107; - color: #212529; -} - -.twp-btn-warning:hover { - background: #e0a800; - transform: translateY(-1px); -} - -.twp-btn-success { - background: #007cba; - color: white; -} - -.twp-btn-success:hover { - background: #005a87; - transform: translateY(-1px); -} - -.twp-btn-info { - background: #17a2b8; - color: white; - border: none; - padding: 10px 15px; - border-radius: 6px; - cursor: pointer; - font-size: 13px; - font-weight: 500; - transition: all 0.2s ease; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 120px; - max-width: 200px; - display: inline-block; - text-align: center; - line-height: 1.4; -} - -.twp-btn-info:hover { - background: #138496; - transform: translateY(-1px); -} - -/* Call Info */ -.twp-call-info { - background: #fff; - padding: 16px; - border-radius: 8px; - border: 2px solid #e9ecef; - margin-bottom: 20px; -} - -.call-timer { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.timer-label { - color: #6c757d; - font-weight: 500; -} - -.timer-value { - font-size: 18px; - font-weight: 600; - color: #212529; - font-family: 'Courier New', monospace; -} - -.call-status { - text-align: center; - padding: 8px; - background: #f8f9fa; - border-radius: 4px; - color: #212529; - font-weight: 600; -} - -/* Queue Management Section */ -.twp-queue-section { - background: #fff; - padding: 16px; - border-radius: 8px; - border: 2px solid #e9ecef; - margin-bottom: 20px; -} - -.twp-queue-section h4 { - margin: 0 0 16px 0; - color: #212529; - font-size: 1.1rem; - text-align: center; -} - -/* Queue List */ -.twp-queue-list { - margin-bottom: 16px; -} - -.queue-loading, -.no-queues { - text-align: center; - color: #495057; - font-style: italic; - padding: 20px; - font-weight: 500; -} - -.no-queues { - background: #f8f9fa; - border: 2px dashed #e9ecef; - border-radius: 8px; -} - -.queue-item { - background: #f8f9fa; - border: 2px solid #e9ecef; - border-radius: 8px; - padding: 12px; - margin-bottom: 8px; - cursor: pointer; - transition: all 0.2s ease; - position: relative; -} - -.queue-item:hover { - border-color: #007cba; - background: #e3f2fd; -} - -.queue-item.selected { - border-color: #007cba; - background: #e3f2fd; - box-shadow: 0 0 0 1px #007cba; -} - -.queue-item.has-calls { - border-left: 4px solid #28a745; -} - -.queue-item.has-calls::after { - content: ''; - position: absolute; - top: 8px; - right: 8px; - width: 8px; - height: 8px; - background: #28a745; - border-radius: 50%; - animation: pulse 2s infinite; -} - -.queue-name { - font-weight: 600; - color: #212529; - margin-bottom: 4px; -} - -.queue-info { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.9rem; - color: #495057; -} - -.queue-waiting { - font-weight: 500; -} - -.queue-waiting.has-calls { - color: #28a745; - font-weight: 600; -} - -.queue-capacity { - font-size: 0.8rem; -} - -/* Queue Controls */ -.twp-queue-controls { - background: #f8f9fa; - padding: 16px; - border-radius: 8px; - border: 2px solid #e9ecef; - margin-top: 16px; -} - -.selected-queue-info { - margin-bottom: 16px; - text-align: center; -} - -.selected-queue-info h5 { - margin: 0 0 8px 0; - color: #212529; - font-size: 1rem; -} - -.queue-stats { - display: flex; - justify-content: center; - gap: 20px; - font-size: 0.9rem; - color: #495057; -} - -.queue-stats span { - font-weight: 500; -} - -.queue-actions { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.queue-actions .twp-btn { - flex: 1; - min-width: 80px; -} - -/* Global Queue Actions */ -.queue-global-actions { - margin-top: 16px; - margin-bottom: 16px; -} - -.global-queue-actions { - display: flex; - gap: 8px; - flex-wrap: wrap; - justify-content: center; -} - -.global-queue-actions .twp-btn { - flex: 1; - min-width: 100px; -} - -/* Alert Toggle Button */ -#twp-alert-toggle { - position: relative; -} - -#twp-alert-toggle.alert-on { - background-color: #28a745 !important; - border-color: #28a745; - color: white; -} - -#twp-alert-toggle.alert-off { - background-color: #6c757d !important; - border-color: #6c757d; - color: white; -} - -#twp-alert-toggle:hover { - opacity: 0.8; -} - -/* Voicemail Section */ -.twp-voicemail-section { - background: #fff; - border-radius: 8px; - border: 2px solid #e9ecef; - margin-bottom: 20px; - overflow: hidden; -} - -.voicemail-header { - padding: 16px; - cursor: pointer; - background: #f8f9fa; - border-bottom: 1px solid #e9ecef; - transition: background-color 0.2s ease; -} - -.voicemail-header:hover { - background: #e9ecef; -} - -.voicemail-header h4 { - margin: 0; - color: #212529; - font-size: 1.1rem; - display: inline-block; -} - -.voicemail-summary { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 8px; -} - -.summary-stats { - font-size: 0.9rem; - color: #6c757d; -} - -.voicemail-toggle { - background: none; - border: none; - cursor: pointer; - padding: 4px 8px; - border-radius: 4px; - transition: background-color 0.2s ease; -} - -.voicemail-toggle:hover { - background: #dee2e6; -} - -.toggle-icon { - font-size: 1.2rem; - color: #495057; - transition: transform 0.2s ease; -} - -.voicemail-content { - padding: 16px; -} - -.twp-voicemail-list { - margin-bottom: 16px; - max-height: 200px; - overflow-y: auto; -} - -.voicemail-item { - padding: 12px; - border: 1px solid #dee2e6; - border-radius: 6px; - margin-bottom: 8px; - cursor: pointer; - transition: all 0.2s ease; -} - -.voicemail-item:hover { - background: #f8f9fa; - border-color: #007bff; -} - -.voicemail-item.has-recording { - border-left: 4px solid #28a745; -} - -.voicemail-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.voicemail-from { - display: flex; - align-items: center; - gap: 6px; -} - -.from-number { - font-weight: 600; - color: #495057; -} - -.voicemail-time { - font-size: 0.85rem; - color: #6c757d; -} - -.voicemail-details { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; -} - -.voicemail-duration { - display: flex; - align-items: center; - gap: 4px; - font-size: 0.9rem; - color: #6c757d; -} - -.recording-indicator { - background: #28a745; - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 0.8rem; -} - -.voicemail-transcription { - font-size: 0.9rem; - color: #495057; - font-style: italic; - padding: 8px; - background: #f8f9fa; - border-radius: 4px; - border-left: 3px solid #007bff; -} - -.voicemail-actions { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.voicemail-actions .twp-btn { - flex: 1; - min-width: 80px; -} - -.no-voicemails, -.voicemail-loading { - text-align: center; - padding: 20px; - color: #6c757d; - font-style: italic; -} - -.twp-btn-secondary { - background: #6c757d; - color: white; -} - -.twp-btn-secondary:hover { - background: #5a6268; - transform: translateY(-1px); -} - -/* Messages */ -.twp-messages { - margin-top: 16px; -} - -.twp-error { - background: #f8d7da; - color: #721c24; - padding: 12px 16px; - border-radius: 8px; - border: 1px solid #f5c6cb; - margin-bottom: 16px; -} - -.twp-success { - background: #d4edda; - color: #155724; - padding: 12px 16px; - border-radius: 8px; - border: 1px solid #c3e6cb; - margin-bottom: 16px; -} - -.twp-info { - background: #d1ecf1; - color: #0c5460; - padding: 12px 16px; - border-radius: 8px; - border: 1px solid #bee5eb; - margin-bottom: 16px; -} - -/* Tablet optimizations */ -@media (max-width: 768px) and (min-width: 481px) { - .twp-btn-info { - font-size: 12px; - padding: 8px 12px; - min-width: 140px; - max-width: 180px; - } - - .global-queue-actions { - flex-direction: column; - gap: 10px; - } - - .global-queue-actions .twp-btn { - min-width: 150px; - } -} - -/* Responsive Design */ -@media (max-width: 480px) { - .twp-browser-phone-container { - margin: 0; - padding: 16px; - border-radius: 0; - max-width: none; - } - - .twp-browser-phone-title { - font-size: 1.3rem; - margin-bottom: 16px; - } - - .twp-dial-grid { - gap: 8px; - max-width: 240px; - } - - .twp-dial-btn { - padding: 12px 6px; - min-height: 50px; - font-size: 18px; - } - - .twp-dial-btn span { - font-size: 10px; - } - - .twp-btn { - padding: 14px 20px; - font-size: 15px; - min-height: 50px; - } - - #twp-dial-number { - font-size: 16px; - padding: 14px 45px 14px 14px; - } -} - -@media (max-width: 320px) { - .twp-dial-grid { - max-width: 200px; - } - - .twp-dial-btn { - padding: 10px 4px; - min-height: 45px; - font-size: 16px; - } - - /* Queue section mobile adjustments */ - .queue-stats { - flex-direction: column; - gap: 8px; - } - - .queue-actions { - flex-direction: column; - } - - .queue-item { - padding: 10px; - } - - .queue-info { - flex-direction: column; - align-items: flex-start; - gap: 4px; - } -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .twp-browser-phone-container { - background: #1a202c; - color: #f7fafc; - } - - .twp-connection-status, - .twp-call-info, - .twp-queue-controls, - .twp-queue-section, - .twp-voicemail-section, - .queue-global-actions { - background: #2d3748; - border-color: #4a5568; - } - - .twp-select, - #twp-dial-number, - .twp-dial-btn { - background: #2d3748; - border-color: #4a5568; - color: #f7fafc; - } - - .call-status { - background: #1a202c; - color: #f7fafc; - } - - .queue-item { - background: #2d3748; - border-color: #4a5568; - color: #f7fafc; - } - - .queue-item:hover, - .queue-item.selected { - background: #4a5568; - border-color: #63b3ed; - } - - .queue-name, - .status-text, - .twp-phone-selection label, - .twp-browser-phone-title, - .twp-queue-section h4, - .twp-voicemail-section h4, - .selected-queue-info h5, - .timer-value, - .from-number, - .stat-value { - color: #f7fafc; - } - - .twp-dial-btn { - color: #f7fafc; - } - - .queue-info, - .queue-stats, - .queue-loading, - .no-queues, - .voicemail-stats, - .voicemail-time, - .voicemail-duration, - .stat-label, - .voicemail-loading, - .no-voicemails { - color: #cbd5e0; - } - - .no-queues, - .voicemail-header, - .voicemail-transcription { - background: #2d3748; - border-color: #4a5568; - } - - .voicemail-header:hover { - background: #4a5568; - } - - .summary-stats { - color: #cbd5e0; - } - - .voicemail-item { - background: #2d3748; - border-color: #4a5568; - color: #f7fafc; - } - - .voicemail-item:hover { - background: #4a5568; - border-color: #63b3ed; - } -} - -/* Animation for incoming calls */ -@keyframes incoming-call { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.05); } -} - -.twp-browser-phone-container.incoming-call { - animation: incoming-call 1s ease-in-out infinite; -} - -/* Loading state */ -.twp-loading { - opacity: 0.6; - pointer-events: none; -} - -.twp-loading::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - margin: -10px 0 0 -10px; - border: 2px solid #f3f3f3; - border-top: 2px solid #007cba; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Agent Transfer Dialog Styles */ -.twp-agent-transfer-dialog { - max-width: 500px; - width: 90vw; -} - -.agent-list { - max-height: 300px; - overflow-y: auto; - margin: 15px 0; - border: 1px solid #ddd; - border-radius: 6px; - background: #f9f9f9; -} - -.agent-option { - padding: 12px; - border-bottom: 1px solid #eee; - background: white; - margin-bottom: 1px; -} - -.agent-option:last-child { - border-bottom: none; -} - -.agent-option.offline { - opacity: 0.6; - background: #f5f5f5; -} - -.agent-info { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.agent-name { - font-weight: 600; - font-size: 0.95rem; -} - -.agent-status { - font-size: 0.85rem; - font-weight: 500; -} - -.transfer-methods { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.transfer-option { - background: #e9ecef; - border: 2px solid #e9ecef; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 0.85rem; - transition: all 0.2s ease; - user-select: none; -} - -.transfer-option:hover { - background: #dee2e6; - border-color: #adb5bd; -} - -.transfer-option.selected { - background: #e3f2fd; - border-color: #2196f3; - color: #1976d2; -} - -.transfer-option:active { - transform: scale(0.98); -} - -.agent-option.offline .transfer-option { - opacity: 0.5; - cursor: not-allowed; -} - -.agent-option.offline .transfer-option:hover { - background: #e9ecef; - border-color: #e9ecef; -} - -.manual-option { - margin-top: 20px; - padding-top: 15px; - border-top: 1px solid #ddd; -} - -.manual-option h4 { - margin: 0 0 8px 0; - font-size: 1rem; - color: #495057; -} - -.manual-option p { - margin: 0 0 10px 0; - font-size: 0.9rem; - color: #6c757d; -} - -.no-agents { - text-align: center; - padding: 20px; - color: #6c757d; - font-style: italic; -} - -/* Hold button active state */ -.twp-btn.btn-active { - background-color: #ff6b35 !important; - color: white !important; - border-color: #e55100 !important; - font-weight: bold; -} - -.twp-btn.btn-active:hover { - background-color: #e55100 !important; - border-color: #bf360c !important; -} - -/* Enhanced Queue Display Styles */ -.user-extension-display { - background: #e8f4f8; - padding: 8px 12px; - border-radius: 4px; - margin-bottom: 10px; - font-size: 14px; - color: #2c5282; - text-align: center; -} - -.queue-header { - display: flex; - align-items: center; - gap: 8px; -} - -.queue-type-icon { - font-size: 16px; - flex-shrink: 0; -} - -.queue-type-personal { - border-left: 4px solid #28a745; -} - -.queue-type-hold { - border-left: 4px solid #ffc107; -} - -.queue-type-general { - border-left: 4px solid #007bff; -} - -.queue-item.queue-type-personal .queue-name { - color: #155724; - font-weight: 600; -} - -.queue-item.queue-type-hold .queue-name { - color: #856404; - font-weight: 500; -} - -.no-queues small { - color: #6c757d; - font-size: 12px; -} - -/* Responsive queue enhancements */ -@media (max-width: 480px) { - .user-extension-display { - font-size: 12px; - padding: 6px 10px; - } - - .queue-type-icon { - font-size: 14px; - } -} \ No newline at end of file diff --git a/assets/js/browser-phone-frontend.js b/assets/js/browser-phone-frontend.js deleted file mode 100644 index 771f811..0000000 --- a/assets/js/browser-phone-frontend.js +++ /dev/null @@ -1,2373 +0,0 @@ -/** - * Frontend Browser Phone for TWP Plugin - * Mobile-friendly implementation - */ -(function($) { - 'use strict'; - - let twilioDevice = null; - let currentCall = null; - let callTimer = null; - let callStartTime = null; - let isConnected = false; - let availableNumbers = []; - let userQueues = []; - let selectedQueue = null; - let tokenRefreshTimer = null; - let tokenExpiry = null; - let queuePollingTimer = null; - let lastQueueUpdate = {}; - let alertSound = null; - let alertInterval = null; - let alertEnabled = false; - let notificationPermission = 'default'; - let backgroundAlertInterval = null; - let isPageVisible = true; - let isOnHold = false; - let isRecording = false; - let recordingSid = null; - let personalQueueTimer = null; - let personalQueueName = null; - - // Initialize when document is ready - $(document).ready(function() { - if (!twp_frontend_ajax.is_logged_in) { - showMessage('You must be logged in to use the browser phone.', 'error'); - return; - } - - initializeBrowserPhone(); - bindEvents(); - loadPhoneNumbers(); - loadUserQueues(); - initVoicemailSection(); - initializeNotifications(); - initializePageVisibility(); - initializePersonalQueue(); - }); - - /** - * Wait for Twilio SDK to load - */ - function waitForTwilioSDK(callback) { - let attempts = 0; - const maxAttempts = 150; // 15 seconds max (100ms * 150) - - function checkTwilio() { - console.log('Checking Twilio SDK availability, attempt:', attempts + 1); - - if (typeof Twilio !== 'undefined' && Twilio.Device) { - console.log('Twilio SDK loaded successfully'); - callback(); - return; - } - - attempts++; - - // Update status message periodically - if (attempts === 30) { // 3 seconds - updateStatus('connecting', 'Loading Twilio SDK...'); - } else if (attempts === 60) { // 6 seconds - updateStatus('connecting', 'Still loading SDK...'); - } else if (attempts === 100) { // 10 seconds - updateStatus('connecting', 'SDK taking longer than expected...'); - } - - if (attempts >= maxAttempts) { - console.error('Twilio SDK failed to load. Window.Twilio:', typeof Twilio); - updateStatus('offline', 'SDK load timeout'); - showMessage('Twilio SDK failed to load within 15 seconds. This may be due to a slow connection or network restrictions. Please refresh the page and try again.', 'error'); - return; - } - - setTimeout(checkTwilio, 100); - } - - checkTwilio(); - } - - /** - * Initialize the browser phone - */ - function initializeBrowserPhone() { - updateStatus('connecting', 'Initializing...'); - - // Wait for Twilio SDK to load, then initialize - waitForTwilioSDK(function() { - // Generate capability token - $.ajax({ - url: twp_frontend_ajax.ajax_url, - method: 'POST', - data: { - action: 'twp_generate_capability_token', - nonce: twp_frontend_ajax.nonce - }, - success: function(response) { - if (response.success) { - setupTwilioDevice(response.data.token); - // Set token expiry time (expires in 1 hour) - tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000; - scheduleTokenRefresh(); - } else { - updateStatus('offline', 'Failed to initialize'); - showMessage('Failed to initialize browser phone: ' + (response.data || 'Unknown error'), 'error'); - } - }, - error: function() { - updateStatus('offline', 'Connection failed'); - showMessage('Failed to connect to server', 'error'); - } - }); - }); - } - - /** - * Setup Twilio Device - */ - // Request microphone and speaker permissions - async function requestMediaPermissions() { - try { - console.log('Requesting media permissions...'); - - // Request microphone permission - const stream = await navigator.mediaDevices.getUserMedia({ - audio: true, - video: false - }); - - // Stop the stream immediately as we just needed permission - stream.getTracks().forEach(track => track.stop()); - - console.log('Media permissions granted'); - return true; - } catch (error) { - console.error('Media permission denied or not available:', error); - - // Show user-friendly error message - let errorMessage = 'Microphone access is required for browser phone functionality. '; - - if (error.name === 'NotAllowedError') { - errorMessage += 'Please allow microphone access in your browser settings and refresh the page.'; - } else if (error.name === 'NotFoundError') { - errorMessage += 'No microphone found. Please connect a microphone and try again.'; - } else { - errorMessage += 'Please check your browser settings and try again.'; - } - - showMessage(errorMessage, 'error'); - updateStatus('offline', 'Permission denied'); - return false; - } - } - - async function setupTwilioDevice(token) { - // Check if Twilio SDK is loaded - if (typeof Twilio === 'undefined' || !Twilio.Device) { - updateStatus('offline', 'Twilio SDK not loaded'); - showMessage('Twilio SDK failed to load. Please refresh the page.', 'error'); - console.error('Twilio SDK is not available'); - return; - } - - // Request media permissions before setting up device - const hasPermissions = await requestMediaPermissions(); - if (!hasPermissions) { - return; // Stop setup if permissions denied - } - - try { - // If device already exists, destroy it first to prevent multiple connections - if (twilioDevice) { - twilioDevice.destroy(); - twilioDevice = null; - } - - twilioDevice = new Twilio.Device(token, { - logLevel: 1, - answerOnBridge: true - }); - - twilioDevice.on('registered', function() { - updateStatus('online', 'Ready'); - isConnected = true; - // Only show success message on initial connection - if (!tokenRefreshTimer) { - showMessage('Browser phone ready!', 'success'); - } - }); - - twilioDevice.on('error', function(error) { - console.error('Twilio Device Error:', error); - updateStatus('offline', 'Error: ' + error.message); - showMessage('Device error: ' + error.message, 'error'); - - // If token expired error, refresh immediately - if (error.message && error.message.toLowerCase().includes('token')) { - refreshToken(); - } - }); - - twilioDevice.on('incoming', function(call) { - handleIncomingCall(call); - }); - - twilioDevice.on('disconnect', function() { - updateStatus('offline', 'Disconnected'); - isConnected = false; - }); - - // Register device asynchronously - await twilioDevice.register(); - - } catch (error) { - console.error('Failed to setup Twilio Device:', error); - updateStatus('offline', 'Setup failed'); - showMessage('Failed to setup device: ' + error.message, 'error'); - } - } - - /** - * Load available phone numbers - */ - function loadPhoneNumbers() { - $.ajax({ - url: twp_frontend_ajax.ajax_url, - method: 'POST', - data: { - action: 'twp_get_phone_numbers', - nonce: twp_frontend_ajax.nonce - }, - success: function(response) { - if (response.success && response.data) { - availableNumbers = response.data; - populateCallerIdSelect(); - } else { - showMessage('Failed to load phone numbers', 'error'); - } - }, - error: function() { - showMessage('Failed to load phone numbers', 'error'); - } - }); - } - - /** - * Populate caller ID select - */ - function populateCallerIdSelect() { - const $select = $('#twp-caller-id'); - $select.empty(); - - if (availableNumbers.length === 0) { - $select.append(''); - return; - } - - $select.append(''); - - availableNumbers.forEach(function(number) { - const friendlyName = number.friendly_name || number.phone_number; - $select.append(``); - }); - } - - /** - * Bind event handlers - */ - function bindEvents() { - // Dial pad buttons - $('.twp-dial-btn').on('click', function() { - const digit = $(this).data('digit'); - addDigit(digit); - - // Haptic feedback on mobile - if (navigator.vibrate) { - navigator.vibrate(50); - } - }); - - // Clear number button - $('#twp-clear-number').on('click', function() { - $('#twp-dial-number').val(''); - }); - - // Call button - $('#twp-call-btn').on('click', function() { - if (!isConnected) { - showMessage('Device not connected', 'error'); - return; - } - - const number = $('#twp-dial-number').val().trim(); - const callerId = $('#twp-caller-id').val(); - - if (!number) { - showMessage('Please enter a number to call', 'error'); - return; - } - - if (!callerId) { - showMessage('Please select a caller ID', 'error'); - return; - } - - makeCall(number, callerId); - }); - - // Hang up button - $('#twp-hangup-btn').on('click', function() { - hangupCall(); - }); - - $('#twp-resume-btn').on('click', function() { - toggleHold(); // Resume the call - }); - - // Call control buttons - $('#twp-hold-btn').on('click', function() { - toggleHold(); - }); - - $('#twp-transfer-btn').on('click', function() { - showTransferDialog(); - }); - - $('#twp-requeue-btn').on('click', function() { - showRequeueDialog(); - }); - - $('#twp-record-btn').on('click', function() { - toggleRecording(); - }); - - // Transfer dialog handlers - $(document).on('click', '#twp-confirm-transfer', function() { - const agentNumber = $('#twp-transfer-agent-number').val(); - if (agentNumber) { - transferCall(agentNumber); - } - }); - - $(document).on('click', '#twp-cancel-transfer', function() { - hideTransferDialog(); - }); - - // Requeue dialog handlers - $(document).on('click', '#twp-confirm-requeue', function() { - const queueId = $('#twp-requeue-select').val(); - if (queueId) { - requeueCall(queueId); - } - }); - - $(document).on('click', '#twp-cancel-requeue', function() { - hideRequeueDialog(); - }); - - // Accept queue call button - $('#twp-accept-queue-call').on('click', function() { - acceptQueueCall(); - stopAlert(); // Stop alert when accepting call - }); - - // Refresh queues button - $('#twp-refresh-queues').on('click', function() { - loadUserQueues(); - }); - - // Alert toggle button - $(document).on('click', '#twp-alert-toggle', function() { - toggleAlert(); - }); - - // Voicemail refresh button - $('#twp-refresh-voicemails').on('click', function() { - loadUserVoicemails(); - }); - - // View all voicemails button - $('#twp-view-all-voicemails').on('click', function() { - // Open admin voicemails page in new tab - window.open(twp_frontend_ajax.admin_url + 'admin.php?page=twilio-wp-voicemails', '_blank'); - }); - - // Voicemail toggle button - $('#twp-voicemail-toggle').on('click', function() { - toggleVoicemailSection(); - }); - - // Voicemail header click (also toggles) - $('#twp-voicemail-header').on('click', function(e) { - // Don't toggle if clicking the toggle button itself - if (!$(e.target).closest('.voicemail-toggle').length) { - toggleVoicemailSection(); - } - }); - - // Voicemail item click handler - $(document).on('click', '.voicemail-item', function() { - const voicemailId = $(this).data('voicemail-id'); - playVoicemail(voicemailId); - }); - - // Queue item selection - $(document).on('click', '.queue-item', function() { - const queueId = $(this).data('queue-id'); - selectQueue(queueId); - }); - - // Manual number input - $('#twp-dial-number').on('input', function() { - // Only allow valid phone number characters - let value = $(this).val().replace(/[^\d\+\-\(\)\s]/g, ''); - $(this).val(value); - }); - - // Handle enter key in dial number input - $('#twp-dial-number').on('keypress', function(e) { - if (e.which === 13) { // Enter key - $('#twp-call-btn').click(); - } - }); - } - - /** - * Add digit to dial pad - */ - function addDigit(digit) { - const $input = $('#twp-dial-number'); - $input.val($input.val() + digit); - - // Send DTMF if in a call - if (currentCall && currentCall.status() === 'open') { - currentCall.sendDigits(digit); - } - } - - /** - * Make outbound call - */ - async function makeCall(number, callerId) { - if (currentCall) { - showMessage('Already in a call', 'error'); - return; - } - - // Stop alerts when making a call - stopAlert(); - - updateCallState('connecting'); - showCallInfo('Connecting...'); - - const params = { - To: number, - From: callerId - }; - - try { - console.log('Making call with params:', params); - currentCall = await twilioDevice.connect({params: params}); - - // Setup call event handlers - currentCall.on('accept', function() { - updateCallState('connected'); - showCallInfo('Connected'); - startCallTimer(); - showMessage('Call connected!', 'success'); - }); - - currentCall.on('disconnect', function() { - endCall(); - showMessage('Call ended', 'info'); - }); - - currentCall.on('error', function(error) { - console.error('Call error:', error); - endCall(); - showMessage('Call failed: ' + error.message, 'error'); - }); - - } catch (error) { - console.error('Failed to make call:', error); - endCall(); - showMessage('Failed to make call: ' + error.message, 'error'); - } - } - - /** - * Handle incoming call - */ - function handleIncomingCall(call) { - currentCall = call; - - // Add visual indication - $('.twp-browser-phone-container').addClass('incoming-call'); - - updateCallState('ringing'); - showCallInfo('Incoming call from: ' + (call.parameters.From || 'Unknown')); - showMessage('Incoming call! Click Accept to answer.', 'info'); - - // Auto-answer after a delay (optional) - setTimeout(function() { - if (currentCall === call && call.status() === 'pending') { - acceptCall(); - } - }, 2000); - - call.on('accept', function() { - $('.twp-browser-phone-container').removeClass('incoming-call'); - updateCallState('connected'); - showCallInfo('Connected'); - startCallTimer(); - showMessage('Call answered!', 'success'); - }); - - call.on('disconnect', function() { - $('.twp-browser-phone-container').removeClass('incoming-call'); - endCall(); - showMessage('Call ended', 'info'); - }); - - call.on('error', function(error) { - $('.twp-browser-phone-container').removeClass('incoming-call'); - endCall(); - showMessage('Call error: ' + error.message, 'error'); - }); - } - - /** - * Accept incoming call - */ - function acceptCall() { - if (currentCall && currentCall.status() === 'pending') { - currentCall.accept(); - } - } - - /** - * Hang up current call - */ - function hangupCall() { - if (currentCall) { - currentCall.disconnect(); - } - endCall(); - } - - /** - * End call and cleanup - */ - function endCall() { - // Stop recording if active - if (isRecording) { - stopRecording(); - } - - currentCall = null; - isOnHold = false; - isRecording = false; - recordingSid = null; - stopCallTimer(); - updateCallState('idle'); - hideCallInfo(); - $('.twp-browser-phone-container').removeClass('incoming-call'); - - // Reset control buttons - console.log('endCall() called - resetting hold button from:', $('#twp-hold-btn').text()); - $('#twp-hold-btn').text('Hold').removeClass('btn-active'); - $('#twp-record-btn').text('Record').removeClass('btn-active'); - - // Reset resume button - const $resumeBtn = $('#twp-resume-btn'); - $resumeBtn.hide(); - - // Restart alerts if enabled and there are waiting calls - if (alertEnabled) { - const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0); - if (hasWaitingCalls) { - setTimeout(startAlert, 1000); // Small delay to avoid immediate alert - } - } - } - - /** - * Load user's assigned queues - */ - function loadUserQueues(silent = false) { - $.ajax({ - url: twp_frontend_ajax.ajax_url, - method: 'POST', - data: { - action: 'twp_get_agent_queues', - nonce: twp_frontend_ajax.nonce - }, - success: function(response) { - if (response.success) { - // Check for new calls in queues - checkForNewCalls(response.data); - userQueues = response.data; - displayQueues(); - } else if (!silent) { - showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error'); - } - }, - error: function() { - if (!silent) { - showMessage('Failed to load queues', 'error'); - } - } - }); - } - - /** - * Check for new calls in queues and trigger alerts - */ - function checkForNewCalls(newQueues) { - let hasWaitingCalls = false; - let newCallDetected = false; - - newQueues.forEach(function(queue) { - const queueId = queue.id; - const currentWaiting = parseInt(queue.current_waiting) || 0; - const previousWaiting = lastQueueUpdate[queueId] || 0; - - // Track if any queue has waiting calls - if (currentWaiting > 0) { - hasWaitingCalls = true; - } - - // If waiting count increased, we have new calls - if (currentWaiting > previousWaiting) { - console.log('New call detected in queue:', queue.queue_name); - newCallDetected = true; - - // Show browser notification for new call - if (notificationPermission === 'granted') { - showBrowserNotification('π New Call in Queue!', { - body: `${queue.queue_name}: ${currentWaiting} call${currentWaiting > 1 ? 's' : ''} waiting`, - icon: 'π', - vibrate: [300, 200, 300], - requireInteraction: true, - tag: `queue-${queue.id}`, - data: { - queueId: queue.id - } - }); - } - } - - lastQueueUpdate[queueId] = currentWaiting; - }); - - // Manage alerts based on queue state - if (alertEnabled && !currentCall) { - if (newCallDetected) { - // Start alert for new calls - startAlert(); - } else if (!hasWaitingCalls) { - // Stop alert if no calls are waiting in any queue - console.log('No calls waiting in any queue, stopping alerts'); - stopAlert(); - } - } - } - - /** - * Display queues in the UI - */ - function displayQueues() { - const $queueList = $('#twp-queue-list'); - - if (userQueues.length === 0) { - $queueList.html('