Compare commits
3 Commits
2025.12.02
...
2026.01.14
| Author | SHA1 | Date | |
|---|---|---|---|
| f0806d7e67 | |||
| 61beadcd06 | |||
| 026edde33b |
@@ -19,11 +19,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Update version in plugin file
|
- name: Update version in plugin file
|
||||||
run: |
|
run: |
|
||||||
# Replace version in main plugin file
|
# Replace version in main plugin file (both header and constant)
|
||||||
sed -i "s/Version: .*/Version: ${{ env.TAG }}/" twilio-wp-plugin.php
|
sed -i "s/Version: {auto_update_value_on_deploy}/Version: ${{ env.TAG }}/" twilio-wp-plugin.php
|
||||||
|
sed -i "s/TWP_VERSION', '{auto_update_value_on_deploy}/TWP_VERSION', '${{ env.TAG }}/" twilio-wp-plugin.php
|
||||||
|
|
||||||
# Verify change
|
# Verify changes
|
||||||
grep "Version:" twilio-wp-plugin.php
|
grep "Version:" twilio-wp-plugin.php
|
||||||
|
grep "TWP_VERSION" twilio-wp-plugin.php
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
427
DEBUGGING-TABLET.md
Normal file
427
DEBUGGING-TABLET.md
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
# Debugging Browser Phone on Android Tablet
|
||||||
|
|
||||||
|
This guide explains how to debug the Twilio WordPress Plugin browser phone on Android tablets (Samsung and other devices).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Android tablet with Chrome browser
|
||||||
|
- USB cable to connect tablet to computer
|
||||||
|
- Computer with Chrome browser installed
|
||||||
|
- USB debugging enabled on tablet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1: Enable USB Debugging on Android Tablet
|
||||||
|
|
||||||
|
### Step 1: Enable Developer Options
|
||||||
|
|
||||||
|
1. Open **Settings** on your Android tablet
|
||||||
|
2. Scroll down to **About tablet** (or **About device**)
|
||||||
|
3. Find **Build number** (may be under "Software information")
|
||||||
|
4. Tap **Build number** 7 times rapidly
|
||||||
|
5. You'll see a message: "You are now a developer!"
|
||||||
|
|
||||||
|
### Step 2: Enable USB Debugging
|
||||||
|
|
||||||
|
1. Go back to **Settings**
|
||||||
|
2. Scroll down to **Developer options** (newly appeared)
|
||||||
|
3. Toggle **Developer options** to ON
|
||||||
|
4. Find **USB debugging** in the list
|
||||||
|
5. Toggle **USB debugging** to ON
|
||||||
|
6. Confirm the prompt if asked
|
||||||
|
|
||||||
|
### Step 3: Trust Your Computer
|
||||||
|
|
||||||
|
1. Connect tablet to computer via USB cable
|
||||||
|
2. Unlock your tablet screen
|
||||||
|
3. A popup will appear: "Allow USB debugging?"
|
||||||
|
4. Check **Always allow from this computer**
|
||||||
|
5. Tap **OK** or **Allow**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2: Connect Chrome DevTools
|
||||||
|
|
||||||
|
### On Your Computer
|
||||||
|
|
||||||
|
1. Open **Chrome browser** on your computer
|
||||||
|
2. Navigate to: `chrome://inspect`
|
||||||
|
3. You should see your tablet device listed under "Remote Target"
|
||||||
|
4. Wait a few seconds for the device to appear
|
||||||
|
|
||||||
|
### Inspect the Browser Phone Page
|
||||||
|
|
||||||
|
1. On your tablet, open Chrome and navigate to:
|
||||||
|
```
|
||||||
|
https://phone.cloud-hosting.io/wp-admin/admin.php?page=twilio-wp-browser-phone
|
||||||
|
```
|
||||||
|
2. On your computer's Chrome DevTools (`chrome://inspect`), you'll see the page listed
|
||||||
|
3. Click **inspect** next to the browser phone page
|
||||||
|
4. A DevTools window will open showing your tablet's browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3: Debug Browser Phone Issues
|
||||||
|
|
||||||
|
### Check Console Logs
|
||||||
|
|
||||||
|
In the **Console** tab, look for key messages:
|
||||||
|
|
||||||
|
#### Successful Connection
|
||||||
|
```
|
||||||
|
Twilio SDK loaded successfully
|
||||||
|
Setting up Twilio Device...
|
||||||
|
Device detection - Android: true, Mobile: true
|
||||||
|
AudioContext created, state: running
|
||||||
|
Device registered successfully
|
||||||
|
Device connection state: connected
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Connection Issues
|
||||||
|
```
|
||||||
|
Device not connected, state: disconnected
|
||||||
|
Failed to register device
|
||||||
|
Connection error: 31005
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Call Issues
|
||||||
|
```
|
||||||
|
Answer button clicked
|
||||||
|
Device connection state: connected
|
||||||
|
Accepting call...
|
||||||
|
Call accepted and connected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitor Network Requests
|
||||||
|
|
||||||
|
1. Switch to **Network** tab in DevTools
|
||||||
|
2. Filter by: `twp_` or `twilio`
|
||||||
|
3. Check for failed requests (red status codes)
|
||||||
|
4. Look for:
|
||||||
|
- `twp_generate_capability_token` - Should return 200
|
||||||
|
- `twp_get_phone_numbers` - Should return 200
|
||||||
|
- WebSocket connections to Twilio
|
||||||
|
|
||||||
|
### Check Device Registration
|
||||||
|
|
||||||
|
In the **Console** tab, type:
|
||||||
|
```javascript
|
||||||
|
device
|
||||||
|
```
|
||||||
|
This shows the current Twilio Device object. Check:
|
||||||
|
- `device.state` - Should be "registered"
|
||||||
|
- `device.token` - Should exist (long string)
|
||||||
|
|
||||||
|
### Check AudioContext State
|
||||||
|
|
||||||
|
In the **Console** tab, type:
|
||||||
|
```javascript
|
||||||
|
audioContext
|
||||||
|
```
|
||||||
|
Check:
|
||||||
|
- Should exist (not null)
|
||||||
|
- `audioContext.state` - Should be "running" (not "suspended")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4: Common Issues & Solutions
|
||||||
|
|
||||||
|
### Issue 1: "Device not connected" Error
|
||||||
|
|
||||||
|
**Symptoms**: Status shows "Disconnected", calls hang up immediately
|
||||||
|
|
||||||
|
**Debugging**:
|
||||||
|
```javascript
|
||||||
|
// In console, check:
|
||||||
|
deviceConnectionState
|
||||||
|
// Should be: "connected"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Check network connection (try WiFi vs cellular)
|
||||||
|
2. Refresh the page with cache cleared
|
||||||
|
3. Check console for token errors
|
||||||
|
4. Verify Twilio credentials in plugin settings
|
||||||
|
|
||||||
|
### Issue 2: Call Hangs Up Immediately When Answered
|
||||||
|
|
||||||
|
**Symptoms**: Click "Answer" button, call disconnects instantly
|
||||||
|
|
||||||
|
**Debugging**:
|
||||||
|
```javascript
|
||||||
|
// Check device state before answering:
|
||||||
|
deviceConnectionState
|
||||||
|
// Check for errors in call handler
|
||||||
|
```
|
||||||
|
|
||||||
|
**Look for console errors**:
|
||||||
|
- `31005` - Connection/network error
|
||||||
|
- `31201` - ICE connection failure
|
||||||
|
- `31208` - Media connection error
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Grant microphone permissions (Settings > Site settings > Microphone)
|
||||||
|
2. Check AudioContext is running: `audioContext.state`
|
||||||
|
3. Try switching between WiFi and cellular data
|
||||||
|
4. Clear Chrome cache and reload
|
||||||
|
|
||||||
|
### Issue 3: No Sound/Ringtone on Incoming Call
|
||||||
|
|
||||||
|
**Symptoms**: Call arrives but no audio plays, no vibration
|
||||||
|
|
||||||
|
**Debugging**:
|
||||||
|
```javascript
|
||||||
|
// Check audio setup:
|
||||||
|
ringtoneAudio
|
||||||
|
audioContext.state
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Tap screen to unlock AudioContext (mobile restriction)
|
||||||
|
2. Check if ringtone file exists (optional, vibration is fallback)
|
||||||
|
3. Verify device supports Vibration API: `'vibrate' in navigator`
|
||||||
|
4. Check browser volume settings
|
||||||
|
|
||||||
|
### Issue 4: No Browser Notifications
|
||||||
|
|
||||||
|
**Symptoms**: No notification when call arrives in background
|
||||||
|
|
||||||
|
**Debugging**:
|
||||||
|
```javascript
|
||||||
|
// Check notification permission:
|
||||||
|
Notification.permission
|
||||||
|
// Should be: "granted"
|
||||||
|
|
||||||
|
// Check service worker:
|
||||||
|
navigator.serviceWorker.controller
|
||||||
|
// Should exist
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Grant notification permission: Settings > Site settings > Notifications
|
||||||
|
2. Check service worker registration in **Application** tab of DevTools
|
||||||
|
3. Look for service worker logs in console
|
||||||
|
4. Reinstall PWA if installed
|
||||||
|
|
||||||
|
### Issue 5: Microphone Permission Denied
|
||||||
|
|
||||||
|
**Symptoms**: Error message about microphone access
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
1. Chrome Settings > Site settings > Microphone
|
||||||
|
2. Find `phone.cloud-hosting.io`
|
||||||
|
3. Change permission to **Allow**
|
||||||
|
4. Refresh the browser phone page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 5: Tablet-Specific Checks
|
||||||
|
|
||||||
|
### Check User Agent
|
||||||
|
|
||||||
|
In console:
|
||||||
|
```javascript
|
||||||
|
navigator.userAgent
|
||||||
|
```
|
||||||
|
Should include "Android" and "Chrome"
|
||||||
|
|
||||||
|
### Check WebRTC Support
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check getUserMedia support:
|
||||||
|
navigator.mediaDevices.getUserMedia
|
||||||
|
// Should be: function
|
||||||
|
|
||||||
|
// Check Notification support:
|
||||||
|
'Notification' in window
|
||||||
|
// Should be: true
|
||||||
|
|
||||||
|
// Check Service Worker support:
|
||||||
|
'serviceWorker' in navigator
|
||||||
|
// Should be: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Audio Constraints
|
||||||
|
|
||||||
|
Look in console for:
|
||||||
|
```
|
||||||
|
Twilio Device created with audio constraints: {
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true,
|
||||||
|
autoGainControl: true,
|
||||||
|
googEchoCancellation: true,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Vibration
|
||||||
|
|
||||||
|
In console:
|
||||||
|
```javascript
|
||||||
|
navigator.vibrate([300, 200, 300])
|
||||||
|
```
|
||||||
|
Tablet should vibrate if supported.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 6: Advanced Debugging
|
||||||
|
|
||||||
|
### Monitor Twilio Device Events
|
||||||
|
|
||||||
|
In console, add event listeners:
|
||||||
|
```javascript
|
||||||
|
device.on('registered', () => console.log('Device registered!'));
|
||||||
|
device.on('error', (error) => console.error('Device error:', error));
|
||||||
|
device.on('incoming', (call) => console.log('Incoming call:', call));
|
||||||
|
device.on('unregistered', () => console.log('Device unregistered'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Call State During Active Call
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// When in a call:
|
||||||
|
currentCall
|
||||||
|
currentCall.status()
|
||||||
|
currentCall.parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Audio Context Resume
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Try to resume manually:
|
||||||
|
audioContext.resume().then(() => {
|
||||||
|
console.log('AudioContext state:', audioContext.state);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Token Expiry
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// See when token expires:
|
||||||
|
new Date(tokenExpiry)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 7: Logging Important Information
|
||||||
|
|
||||||
|
### Collect Debugging Info
|
||||||
|
|
||||||
|
Run this in console to get a debug report:
|
||||||
|
```javascript
|
||||||
|
console.log('=== Browser Phone Debug Report ===');
|
||||||
|
console.log('User Agent:', navigator.userAgent);
|
||||||
|
console.log('Device State:', deviceConnectionState);
|
||||||
|
console.log('Device Registered:', device ? device.state : 'no device');
|
||||||
|
console.log('AudioContext:', audioContext ? audioContext.state : 'no context');
|
||||||
|
console.log('Notification Permission:', Notification.permission);
|
||||||
|
console.log('Service Worker:', navigator.serviceWorker.controller ? 'active' : 'inactive');
|
||||||
|
console.log('Current Call:', currentCall ? 'in call' : 'no call');
|
||||||
|
console.log('Token Expiry:', tokenExpiry ? new Date(tokenExpiry) : 'unknown');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check WordPress AJAX Responses
|
||||||
|
|
||||||
|
In **Network** tab:
|
||||||
|
1. Filter: `admin-ajax.php`
|
||||||
|
2. Click on request
|
||||||
|
3. Check **Preview** tab for response
|
||||||
|
4. Look for `success: true` or error messages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 8: Testing Checklist
|
||||||
|
|
||||||
|
After fixing issues, test these scenarios:
|
||||||
|
|
||||||
|
- [ ] Device shows "Connected" status
|
||||||
|
- [ ] Can make outbound call successfully
|
||||||
|
- [ ] Can receive incoming call (see notification)
|
||||||
|
- [ ] Can answer incoming call without hang-up
|
||||||
|
- [ ] Ringtone plays or tablet vibrates
|
||||||
|
- [ ] Call audio is clear (echo cancellation working)
|
||||||
|
- [ ] Can switch between WiFi and cellular during call
|
||||||
|
- [ ] Browser notification appears when app in background
|
||||||
|
- [ ] Service worker logs appear in console
|
||||||
|
- [ ] No errors in console during call lifecycle
|
||||||
|
- [ ] Can put call on hold
|
||||||
|
- [ ] Can transfer call
|
||||||
|
- [ ] Call timer updates correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting Specific Error Codes
|
||||||
|
|
||||||
|
### Twilio Error 31005
|
||||||
|
**Meaning**: WebSocket connection failed
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
- Network connectivity issues
|
||||||
|
- Firewall blocking WebSocket
|
||||||
|
- Mobile network switching (WiFi ↔ cellular)
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check internet connection
|
||||||
|
- Try different network
|
||||||
|
- Wait for ICE restart (enabled in config)
|
||||||
|
|
||||||
|
### Twilio Error 31201
|
||||||
|
**Meaning**: ICE connection failed
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
- Restrictive NAT/firewall
|
||||||
|
- Mobile network issues
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Try different network
|
||||||
|
- Check WebRTC connectivity
|
||||||
|
- Enable mobile data if on WiFi only
|
||||||
|
|
||||||
|
### Twilio Error 31204
|
||||||
|
**Meaning**: Connection error
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
- Media connection setup failed
|
||||||
|
- Signaling timeout
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Refresh page
|
||||||
|
- Check microphone permissions
|
||||||
|
- Verify audio constraints applied
|
||||||
|
|
||||||
|
### Twilio Error 31208
|
||||||
|
**Meaning**: Media connection failed
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
- Microphone access denied
|
||||||
|
- Audio device issues
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Grant microphone permission
|
||||||
|
- Check device audio settings
|
||||||
|
- Restart browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- Twilio Voice SDK Errors: https://www.twilio.com/docs/voice/sdks/javascript/errors
|
||||||
|
- Chrome DevTools Remote Debugging: https://developer.chrome.com/docs/devtools/remote-debugging/
|
||||||
|
- WebRTC Troubleshooting: https://webrtc.github.io/samples/
|
||||||
|
- Service Worker Debugging: https://developer.chrome.com/docs/workbox/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contact & Support
|
||||||
|
|
||||||
|
If issues persist after following this guide:
|
||||||
|
|
||||||
|
1. Collect debug report (see Part 7)
|
||||||
|
2. Take screenshots of console errors
|
||||||
|
3. Note tablet model and Android version
|
||||||
|
4. Report issue with collected information
|
||||||
|
|
||||||
|
**Important Files**:
|
||||||
|
- Browser phone implementation: `admin/class-twp-admin.php` (lines 7400-8300)
|
||||||
|
- Service worker: `assets/js/twp-service-worker.js`
|
||||||
|
- CLAUDE.md: Quick reference guide
|
||||||
@@ -6996,6 +6996,7 @@ class TWP_Admin {
|
|||||||
<div class="phone-interface">
|
<div class="phone-interface">
|
||||||
<div class="phone-display">
|
<div class="phone-display">
|
||||||
<div id="phone-status">Ready</div>
|
<div id="phone-status">Ready</div>
|
||||||
|
<div id="device-connection-status" style="font-size: 12px; color: #999; margin-top: 5px;">Connecting...</div>
|
||||||
<div id="phone-number-display"></div>
|
<div id="phone-number-display"></div>
|
||||||
<div id="call-timer" style="display: none;">00:00</div>
|
<div id="call-timer" style="display: none;">00:00</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -7434,7 +7435,211 @@ class TWP_Admin {
|
|||||||
var callStartTime = null;
|
var callStartTime = null;
|
||||||
var tokenRefreshTimer = null;
|
var tokenRefreshTimer = null;
|
||||||
var tokenExpiry = null;
|
var tokenExpiry = null;
|
||||||
|
var audioContext = null;
|
||||||
|
var ringtoneAudio = null;
|
||||||
|
var isPageVisible = true;
|
||||||
|
var deviceConnectionState = 'disconnected'; // disconnected, connecting, connected
|
||||||
|
var serviceWorkerRegistration = null;
|
||||||
|
|
||||||
|
// Initialize AudioContext for mobile audio playback
|
||||||
|
function initializeAudioContext() {
|
||||||
|
try {
|
||||||
|
if (!audioContext) {
|
||||||
|
// Create AudioContext with compatibility
|
||||||
|
var AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
||||||
|
audioContext = new AudioContextClass();
|
||||||
|
console.log('AudioContext created, state:', audioContext.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume AudioContext if suspended (required on mobile)
|
||||||
|
if (audioContext.state === 'suspended') {
|
||||||
|
audioContext.resume().then(function() {
|
||||||
|
console.log('AudioContext resumed successfully');
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error('Failed to resume AudioContext:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize AudioContext:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and setup ringtone audio element
|
||||||
|
function setupRingtone() {
|
||||||
|
if (!ringtoneAudio) {
|
||||||
|
ringtoneAudio = new Audio();
|
||||||
|
// Use a simple sine wave tone or default ringtone
|
||||||
|
// For now, we'll use a data URI for a simple beep tone
|
||||||
|
ringtoneAudio.loop = true;
|
||||||
|
ringtoneAudio.volume = 0.7;
|
||||||
|
|
||||||
|
// Create a simple ringtone using Web Audio API
|
||||||
|
createRingtone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ringtone using Web Audio API for better mobile support
|
||||||
|
function createRingtone() {
|
||||||
|
// Use a simple base64-encoded beep tone (short MP3)
|
||||||
|
// This is a simple 1-second beep at 800Hz
|
||||||
|
// You can replace this with a custom ringtone file URL if you upload one
|
||||||
|
|
||||||
|
// For now, use a simple approach: HTML5 Audio with error fallback
|
||||||
|
// Note: On mobile, audio playback may be restricted, so we rely heavily on vibration
|
||||||
|
var ringtoneUrl = '<?php echo plugins_url('assets/sounds/ringtone.mp3', dirname(__FILE__)); ?>';
|
||||||
|
|
||||||
|
// Try to load the ringtone file
|
||||||
|
ringtoneAudio.src = ringtoneUrl;
|
||||||
|
|
||||||
|
// Fallback: if ringtone file fails to load, we'll just use vibration
|
||||||
|
ringtoneAudio.addEventListener('error', function(e) {
|
||||||
|
console.log('Ringtone file not found (this is normal), using vibration only for mobile');
|
||||||
|
// Don't show error - vibration is sufficient for mobile
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
// Try to preload
|
||||||
|
ringtoneAudio.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play ringtone for incoming call
|
||||||
|
function playRingtone() {
|
||||||
|
try {
|
||||||
|
// Initialize AudioContext on user interaction
|
||||||
|
initializeAudioContext();
|
||||||
|
|
||||||
|
if (ringtoneAudio) {
|
||||||
|
var playPromise = ringtoneAudio.play();
|
||||||
|
|
||||||
|
if (playPromise !== undefined) {
|
||||||
|
playPromise.then(function() {
|
||||||
|
console.log('Ringtone playing');
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error('Ringtone play failed:', error);
|
||||||
|
// Fallback: vibrate on mobile
|
||||||
|
vibrateDevice([300, 200, 300, 200, 300]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always vibrate on mobile for better notification
|
||||||
|
vibrateDevice([300, 200, 300, 200, 300]);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error playing ringtone:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop ringtone
|
||||||
|
function stopRingtone() {
|
||||||
|
try {
|
||||||
|
if (ringtoneAudio) {
|
||||||
|
ringtoneAudio.pause();
|
||||||
|
ringtoneAudio.currentTime = 0;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping ringtone:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vibrate device (mobile)
|
||||||
|
function vibrateDevice(pattern) {
|
||||||
|
if ('vibrate' in navigator) {
|
||||||
|
navigator.vibrate(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register service worker for PWA notifications
|
||||||
|
function registerServiceWorker() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
var swPath = '<?php echo plugins_url('assets/js/twp-service-worker.js', dirname(__FILE__)); ?>';
|
||||||
|
|
||||||
|
navigator.serviceWorker.register(swPath)
|
||||||
|
.then(function(registration) {
|
||||||
|
console.log('Service Worker registered:', registration);
|
||||||
|
serviceWorkerRegistration = registration;
|
||||||
|
|
||||||
|
// Request notification permission
|
||||||
|
if ('Notification' in window && Notification.permission === 'default') {
|
||||||
|
Notification.requestPermission().then(function(permission) {
|
||||||
|
console.log('Notification permission:', permission);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error('Service Worker registration failed:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notification via service worker
|
||||||
|
function sendIncomingCallNotification(callerNumber) {
|
||||||
|
// Try browser notification first
|
||||||
|
if ('Notification' in window && Notification.permission === 'granted') {
|
||||||
|
if (serviceWorkerRegistration && serviceWorkerRegistration.active) {
|
||||||
|
serviceWorkerRegistration.active.postMessage({
|
||||||
|
type: 'SHOW_NOTIFICATION',
|
||||||
|
title: 'Incoming Call',
|
||||||
|
body: 'Call from ' + (callerNumber || 'Unknown Number'),
|
||||||
|
icon: '<?php echo plugins_url('assets/images/phone-icon.png', dirname(__FILE__)); ?>',
|
||||||
|
tag: 'incoming-call',
|
||||||
|
requireInteraction: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback: show notification directly
|
||||||
|
new Notification('Incoming Call', {
|
||||||
|
body: 'Call from ' + (callerNumber || 'Unknown Number'),
|
||||||
|
icon: '<?php echo plugins_url('assets/images/phone-icon.png', dirname(__FILE__)); ?>',
|
||||||
|
tag: 'incoming-call',
|
||||||
|
requireInteraction: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor page visibility for background call handling
|
||||||
|
function setupPageVisibility() {
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
isPageVisible = !document.hidden;
|
||||||
|
console.log('Page visibility changed:', isPageVisible ? 'visible' : 'hidden');
|
||||||
|
|
||||||
|
// If page becomes visible, resume audio context
|
||||||
|
if (isPageVisible && audioContext) {
|
||||||
|
initializeAudioContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update device connection status in UI
|
||||||
|
function updateConnectionStatus(state) {
|
||||||
|
deviceConnectionState = state;
|
||||||
|
var statusText = '';
|
||||||
|
var statusColor = '';
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case 'connected':
|
||||||
|
statusText = 'Connected';
|
||||||
|
statusColor = '#4CAF50';
|
||||||
|
break;
|
||||||
|
case 'connecting':
|
||||||
|
statusText = 'Connecting...';
|
||||||
|
statusColor = '#FF9800';
|
||||||
|
break;
|
||||||
|
case 'disconnected':
|
||||||
|
statusText = 'Disconnected';
|
||||||
|
statusColor = '#f44336';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusText = 'Unknown';
|
||||||
|
statusColor = '#999';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status indicator (we'll add this to the UI)
|
||||||
|
$('#device-connection-status').text(statusText).css('color', statusColor);
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for SDK to load
|
// Wait for SDK to load
|
||||||
function waitForTwilioSDK(callback) {
|
function waitForTwilioSDK(callback) {
|
||||||
if (typeof Twilio !== 'undefined' && Twilio.Device) {
|
if (typeof Twilio !== 'undefined' && Twilio.Device) {
|
||||||
@@ -7450,7 +7655,19 @@ class TWP_Admin {
|
|||||||
// Initialize the browser phone
|
// Initialize the browser phone
|
||||||
function initializeBrowserPhone() {
|
function initializeBrowserPhone() {
|
||||||
$('#phone-status').text('Initializing...');
|
$('#phone-status').text('Initializing...');
|
||||||
|
updateConnectionStatus('connecting');
|
||||||
|
|
||||||
|
// Initialize audio and PWA features
|
||||||
|
setupRingtone();
|
||||||
|
registerServiceWorker();
|
||||||
|
setupPageVisibility();
|
||||||
|
|
||||||
|
// Initialize AudioContext on first user interaction
|
||||||
|
$(document).one('click touchstart', function() {
|
||||||
|
console.log('User interaction detected, initializing AudioContext');
|
||||||
|
initializeAudioContext();
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for SDK before proceeding
|
// Wait for SDK before proceeding
|
||||||
waitForTwilioSDK(function() {
|
waitForTwilioSDK(function() {
|
||||||
// Get capability token (access token for v2)
|
// Get capability token (access token for v2)
|
||||||
@@ -7468,9 +7685,11 @@ class TWP_Admin {
|
|||||||
// WordPress wp_send_json_error sends the error message as response.data
|
// WordPress wp_send_json_error sends the error message as response.data
|
||||||
var errorMsg = response.data || response.error || 'Unknown error';
|
var errorMsg = response.data || response.error || 'Unknown error';
|
||||||
showError('Failed to initialize: ' + errorMsg);
|
showError('Failed to initialize: ' + errorMsg);
|
||||||
|
updateConnectionStatus('disconnected');
|
||||||
}
|
}
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
showError('Failed to connect to server');
|
showError('Failed to connect to server');
|
||||||
|
updateConnectionStatus('disconnected');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -7517,25 +7736,57 @@ class TWP_Admin {
|
|||||||
if (typeof Twilio === 'undefined' || !Twilio.Device) {
|
if (typeof Twilio === 'undefined' || !Twilio.Device) {
|
||||||
throw new Error('Twilio Voice SDK not loaded');
|
throw new Error('Twilio Voice SDK not loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Setting up Twilio Device...');
|
||||||
|
updateConnectionStatus('connecting');
|
||||||
|
|
||||||
// Request media permissions before setting up device
|
// Request media permissions before setting up device
|
||||||
const hasPermissions = await requestMediaPermissions();
|
const hasPermissions = await requestMediaPermissions();
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
|
updateConnectionStatus('disconnected');
|
||||||
return; // Stop setup if permissions denied
|
return; // Stop setup if permissions denied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up existing device if any
|
// Clean up existing device if any
|
||||||
if (device) {
|
if (device) {
|
||||||
|
console.log('Destroying existing device');
|
||||||
await device.destroy();
|
await device.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect if we're on Android/mobile for specific settings
|
||||||
|
var isAndroid = /Android/i.test(navigator.userAgent);
|
||||||
|
var isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
console.log('Device detection - Android:', isAndroid, 'Mobile:', isMobile);
|
||||||
|
|
||||||
|
// Android-specific audio constraints for better WebRTC performance
|
||||||
|
var audioConstraints = {
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true,
|
||||||
|
autoGainControl: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Additional Android-specific settings
|
||||||
|
if (isAndroid) {
|
||||||
|
audioConstraints.googEchoCancellation = true;
|
||||||
|
audioConstraints.googNoiseSuppression = true;
|
||||||
|
audioConstraints.googAutoGainControl = true;
|
||||||
|
audioConstraints.googHighpassFilter = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup Twilio Voice SDK v2 Device
|
// Setup Twilio Voice SDK v2 Device
|
||||||
// Note: Voice SDK v2 uses Twilio.Device directly, not Twilio.Voice.Device
|
// Note: Voice SDK v2 uses Twilio.Device directly, not Twilio.Voice.Device
|
||||||
device = new Twilio.Device(token, {
|
device = new Twilio.Device(token, {
|
||||||
logLevel: 1, // 0 = TRACE, 1 = DEBUG
|
logLevel: 1, // 0 = TRACE, 1 = DEBUG
|
||||||
codecPreferences: ['opus', 'pcmu'],
|
codecPreferences: ['opus', 'pcmu'],
|
||||||
edge: 'sydney' // Or closest edge location
|
edge: 'sydney', // Or closest edge location
|
||||||
|
enableIceRestart: true, // Important for mobile network switching
|
||||||
|
audioConstraints: audioConstraints,
|
||||||
|
maxCallSignalingTimeoutMs: 30000, // 30 seconds timeout for mobile
|
||||||
|
closeProtection: true // Warn before closing during call
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('Twilio Device created with audio constraints:', audioConstraints);
|
||||||
|
|
||||||
// Set up event handlers BEFORE registering
|
// Set up event handlers BEFORE registering
|
||||||
// Device registered and ready
|
// Device registered and ready
|
||||||
@@ -7543,14 +7794,23 @@ class TWP_Admin {
|
|||||||
console.log('Device registered successfully');
|
console.log('Device registered successfully');
|
||||||
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
||||||
$('#call-btn').prop('disabled', false);
|
$('#call-btn').prop('disabled', false);
|
||||||
|
updateConnectionStatus('connected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Device unregistered
|
||||||
|
device.on('unregistered', function() {
|
||||||
|
console.log('Device unregistered');
|
||||||
|
updateConnectionStatus('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
// Handle errors
|
// Handle errors
|
||||||
device.on('error', function(error) {
|
device.on('error', function(error) {
|
||||||
console.error('Twilio Device Error:', error);
|
console.error('Twilio Device Error:', error);
|
||||||
|
console.error('Error code:', error.code, 'Message:', error.message);
|
||||||
|
updateConnectionStatus('disconnected');
|
||||||
|
|
||||||
var errorMsg = error.message || error.toString();
|
var errorMsg = error.message || error.toString();
|
||||||
|
|
||||||
// Provide specific help for common errors
|
// Provide specific help for common errors
|
||||||
if (errorMsg.includes('valid callerId must be provided')) {
|
if (errorMsg.includes('valid callerId must be provided')) {
|
||||||
errorMsg = 'Caller ID error: Make sure you select a verified Twilio phone number as Caller ID. The number must be purchased through your Twilio account.';
|
errorMsg = 'Caller ID error: Make sure you select a verified Twilio phone number as Caller ID. The number must be purchased through your Twilio account.';
|
||||||
@@ -7560,23 +7820,48 @@ class TWP_Admin {
|
|||||||
errorMsg = 'Token error: ' + errorMsg + ' - The page will automatically try to refresh the token.';
|
errorMsg = 'Token error: ' + errorMsg + ' - The page will automatically try to refresh the token.';
|
||||||
// Try to reinitialize after token error
|
// Try to reinitialize after token error
|
||||||
setTimeout(initializeBrowserPhone, 5000);
|
setTimeout(initializeBrowserPhone, 5000);
|
||||||
|
} else if (errorMsg.includes('31005') || errorMsg.includes('Connection error')) {
|
||||||
|
errorMsg = 'Connection error: Check your internet connection. If on mobile, try switching between WiFi and cellular data.';
|
||||||
|
// Retry connection
|
||||||
|
setTimeout(function() {
|
||||||
|
if (device) {
|
||||||
|
console.log('Attempting to reconnect device...');
|
||||||
|
device.register();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(errorMsg);
|
showError(errorMsg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle incoming calls
|
// Handle incoming calls
|
||||||
device.on('incoming', function(call) {
|
device.on('incoming', function(call) {
|
||||||
|
console.log('Incoming call from:', call.parameters.From);
|
||||||
|
console.log('Call SID:', call.parameters.CallSid);
|
||||||
|
console.log('Device connection state:', deviceConnectionState);
|
||||||
|
|
||||||
currentCall = call;
|
currentCall = call;
|
||||||
|
var callerNumber = call.parameters.From || 'Unknown Number';
|
||||||
|
|
||||||
$('#phone-status').text('Incoming Call').css('color', '#FF9800');
|
$('#phone-status').text('Incoming Call').css('color', '#FF9800');
|
||||||
$('#phone-number-display').text(call.parameters.From || 'Unknown Number');
|
$('#phone-number-display').text(callerNumber);
|
||||||
$('#call-btn').hide();
|
$('#call-btn').hide();
|
||||||
$('#answer-btn').show();
|
$('#answer-btn').show();
|
||||||
|
|
||||||
|
// Play ringtone and show notification
|
||||||
|
playRingtone();
|
||||||
|
|
||||||
|
// If page is in background, send notification
|
||||||
|
if (!isPageVisible) {
|
||||||
|
console.log('Page in background, sending notification');
|
||||||
|
sendIncomingCallNotification(callerNumber);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup call event handlers
|
// Setup call event handlers
|
||||||
setupCallHandlers(call);
|
setupCallHandlers(call);
|
||||||
|
|
||||||
if ($('#auto-answer').is(':checked')) {
|
if ($('#auto-answer').is(':checked')) {
|
||||||
|
console.log('Auto-answer enabled, accepting call');
|
||||||
call.accept();
|
call.accept();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -7599,6 +7884,8 @@ class TWP_Admin {
|
|||||||
function setupCallHandlers(call) {
|
function setupCallHandlers(call) {
|
||||||
// Call accepted/connected
|
// Call accepted/connected
|
||||||
call.on('accept', function() {
|
call.on('accept', function() {
|
||||||
|
console.log('Call accepted and connected');
|
||||||
|
stopRingtone();
|
||||||
$('#phone-status').text('Connected').css('color', '#2196F3');
|
$('#phone-status').text('Connected').css('color', '#2196F3');
|
||||||
$('#call-btn').hide();
|
$('#call-btn').hide();
|
||||||
$('#answer-btn').hide();
|
$('#answer-btn').hide();
|
||||||
@@ -7607,9 +7894,11 @@ class TWP_Admin {
|
|||||||
$('#admin-call-controls-panel').show();
|
$('#admin-call-controls-panel').show();
|
||||||
startCallTimer();
|
startCallTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call disconnected
|
// Call disconnected
|
||||||
call.on('disconnect', function() {
|
call.on('disconnect', function() {
|
||||||
|
console.log('Call disconnected');
|
||||||
|
stopRingtone();
|
||||||
currentCall = null;
|
currentCall = null;
|
||||||
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
||||||
$('#hangup-btn').hide();
|
$('#hangup-btn').hide();
|
||||||
@@ -7619,22 +7908,26 @@ class TWP_Admin {
|
|||||||
$('#admin-call-controls-panel').hide();
|
$('#admin-call-controls-panel').hide();
|
||||||
$('#call-timer').hide();
|
$('#call-timer').hide();
|
||||||
stopCallTimer();
|
stopCallTimer();
|
||||||
|
|
||||||
// Reset button states
|
// Reset button states
|
||||||
$('#admin-hold-btn').text('Hold').removeClass('btn-active');
|
$('#admin-hold-btn').text('Hold').removeClass('btn-active');
|
||||||
$('#admin-record-btn').text('Record').removeClass('btn-active');
|
$('#admin-record-btn').text('Record').removeClass('btn-active');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call rejected
|
// Call rejected
|
||||||
call.on('reject', function() {
|
call.on('reject', function() {
|
||||||
|
console.log('Call rejected');
|
||||||
|
stopRingtone();
|
||||||
currentCall = null;
|
currentCall = null;
|
||||||
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
||||||
$('#answer-btn').hide();
|
$('#answer-btn').hide();
|
||||||
$('#call-btn').show();
|
$('#call-btn').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call cancelled (by caller before answer)
|
// Call cancelled (by caller before answer)
|
||||||
call.on('cancel', function() {
|
call.on('cancel', function() {
|
||||||
|
console.log('Call cancelled by caller');
|
||||||
|
stopRingtone();
|
||||||
currentCall = null;
|
currentCall = null;
|
||||||
$('#phone-status').text('Missed Call').css('color', '#FF9800');
|
$('#phone-status').text('Missed Call').css('color', '#FF9800');
|
||||||
$('#answer-btn').hide();
|
$('#answer-btn').hide();
|
||||||
@@ -7643,6 +7936,26 @@ class TWP_Admin {
|
|||||||
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
$('#phone-status').text('Ready').css('color', '#4CAF50');
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Call error
|
||||||
|
call.on('error', function(error) {
|
||||||
|
console.error('Call error:', error);
|
||||||
|
console.error('Error code:', error.code, 'Message:', error.message);
|
||||||
|
stopRingtone();
|
||||||
|
|
||||||
|
var errorMsg = error.message || error.toString();
|
||||||
|
|
||||||
|
// Specific error handling for Android/mobile
|
||||||
|
if (error.code === 31005) {
|
||||||
|
errorMsg = 'Connection failed: Check your network connection. Try switching between WiFi and cellular data.';
|
||||||
|
} else if (error.code === 31201 || error.code === 31204) {
|
||||||
|
errorMsg = 'Call setup failed: Please try again. If the problem persists, refresh the page.';
|
||||||
|
} else if (error.code === 31208) {
|
||||||
|
errorMsg = 'Media connection failed: Check microphone permissions and try again.';
|
||||||
|
}
|
||||||
|
|
||||||
|
showError('Call error: ' + errorMsg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshToken() {
|
function refreshToken() {
|
||||||
@@ -7753,11 +8066,15 @@ class TWP_Admin {
|
|||||||
$('#caller-id-select').html('<option value="">Error loading numbers</option>');
|
$('#caller-id-select').html('<option value="">Error loading numbers</option>');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dialpad functionality
|
// Dialpad functionality (support both click and touch events)
|
||||||
$('.dialpad-btn').on('click', function() {
|
$('.dialpad-btn').on('click touchend', function(e) {
|
||||||
|
e.preventDefault(); // Prevent duplicate events
|
||||||
var digit = $(this).data('digit');
|
var digit = $(this).data('digit');
|
||||||
var currentVal = $('#phone-number-input').val();
|
var currentVal = $('#phone-number-input').val();
|
||||||
$('#phone-number-input').val(currentVal + digit);
|
$('#phone-number-input').val(currentVal + digit);
|
||||||
|
|
||||||
|
// Initialize AudioContext on user interaction (mobile requirement)
|
||||||
|
initializeAudioContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call button
|
// Call button
|
||||||
@@ -7818,8 +8135,45 @@ class TWP_Admin {
|
|||||||
|
|
||||||
// Answer button
|
// Answer button
|
||||||
$('#answer-btn').on('click', function() {
|
$('#answer-btn').on('click', function() {
|
||||||
if (currentCall) {
|
console.log('Answer button clicked');
|
||||||
|
console.log('Device connection state:', deviceConnectionState);
|
||||||
|
console.log('Current call:', currentCall);
|
||||||
|
|
||||||
|
if (!currentCall) {
|
||||||
|
console.error('No current call to answer');
|
||||||
|
showError('No incoming call to answer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check device connection state
|
||||||
|
if (deviceConnectionState !== 'connected') {
|
||||||
|
console.error('Device not connected, state:', deviceConnectionState);
|
||||||
|
showError('Phone not connected. Reconnecting...');
|
||||||
|
|
||||||
|
// Try to reconnect
|
||||||
|
if (device) {
|
||||||
|
device.register().then(function() {
|
||||||
|
console.log('Device reconnected, answering call');
|
||||||
|
if (currentCall) {
|
||||||
|
currentCall.accept();
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error('Failed to reconnect device:', err);
|
||||||
|
showError('Failed to reconnect. Please refresh the page.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize AudioContext before accepting (important for mobile)
|
||||||
|
initializeAudioContext();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Accepting call...');
|
||||||
currentCall.accept();
|
currentCall.accept();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error accepting call:', error);
|
||||||
|
showError('Failed to answer call: ' + error.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
42
assets/images/README.md
Normal file
42
assets/images/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Browser Phone Images
|
||||||
|
|
||||||
|
## Required Images for PWA Notifications
|
||||||
|
|
||||||
|
Place the following image files in this directory for browser push notifications:
|
||||||
|
|
||||||
|
### phone-icon.png
|
||||||
|
- **Purpose**: Main notification icon
|
||||||
|
- **Size**: 192x192 pixels (recommended)
|
||||||
|
- **Format**: PNG with transparency
|
||||||
|
- **Usage**: Shown in browser notifications for incoming calls
|
||||||
|
|
||||||
|
### phone-badge.png
|
||||||
|
- **Purpose**: Small badge icon for notifications
|
||||||
|
- **Size**: 96x96 pixels or 72x72 pixels
|
||||||
|
- **Format**: PNG with transparency
|
||||||
|
- **Usage**: Displayed in the notification badge area (Android)
|
||||||
|
|
||||||
|
## Fallback Behavior
|
||||||
|
|
||||||
|
If these images are not present, the browser will:
|
||||||
|
- Use a default browser notification icon
|
||||||
|
- Still display text-based notifications
|
||||||
|
- Functionality will not be affected
|
||||||
|
|
||||||
|
## Image Creation Tips
|
||||||
|
|
||||||
|
- Use a simple, recognizable phone icon
|
||||||
|
- Ensure good contrast for visibility
|
||||||
|
- Test on both light and dark backgrounds
|
||||||
|
- Transparent backgrounds work best
|
||||||
|
- Use a consistent color scheme with your brand
|
||||||
|
|
||||||
|
## Example Resources
|
||||||
|
|
||||||
|
- Google Material Icons: https://fonts.google.com/icons
|
||||||
|
- Font Awesome: https://fontawesome.com/
|
||||||
|
- Flaticon: https://www.flaticon.com/
|
||||||
|
- Or create custom icons using tools like:
|
||||||
|
- Figma
|
||||||
|
- Adobe Illustrator
|
||||||
|
- Inkscape (free)
|
||||||
@@ -72,7 +72,23 @@ self.addEventListener('push', function(event) {
|
|||||||
|
|
||||||
// Handle messages from the main script
|
// Handle messages from the main script
|
||||||
self.addEventListener('message', function(event) {
|
self.addEventListener('message', function(event) {
|
||||||
|
console.log('TWP Service Worker: Message received', event.data);
|
||||||
|
|
||||||
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
|
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
|
||||||
self.registration.showNotification(event.data.title, event.data.options);
|
const options = {
|
||||||
|
body: event.data.body || 'You have a new call waiting',
|
||||||
|
icon: event.data.icon || '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-icon.png',
|
||||||
|
badge: '/wp-content/plugins/twilio-wp-plugin/assets/images/phone-badge.png',
|
||||||
|
vibrate: [300, 200, 300, 200, 300],
|
||||||
|
tag: event.data.tag || 'incoming-call',
|
||||||
|
requireInteraction: event.data.requireInteraction !== undefined ? event.data.requireInteraction : true,
|
||||||
|
data: event.data.data || {}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('TWP Service Worker: Showing notification', event.data.title, options);
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(event.data.title || 'Incoming Call', options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
36
assets/sounds/README.md
Normal file
36
assets/sounds/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Ringtone Audio Files
|
||||||
|
|
||||||
|
## Custom Ringtone
|
||||||
|
|
||||||
|
To add a custom ringtone for incoming calls in the browser phone:
|
||||||
|
|
||||||
|
1. Place your ringtone audio file in this directory
|
||||||
|
2. Name it: `ringtone.mp3`
|
||||||
|
3. Recommended format: MP3, under 5 seconds, loopable
|
||||||
|
|
||||||
|
## Fallback Behavior
|
||||||
|
|
||||||
|
If no ringtone file is present, the browser phone will:
|
||||||
|
- Use device vibration on mobile devices (Android, iOS)
|
||||||
|
- Rely on browser notifications to alert users
|
||||||
|
- Display visual indicators in the browser interface
|
||||||
|
|
||||||
|
## Supported Audio Formats
|
||||||
|
|
||||||
|
- **MP3** (recommended) - Best compatibility across browsers
|
||||||
|
- **OGG** - Good for Firefox, Chrome
|
||||||
|
- **WAV** - Larger file size but universal support
|
||||||
|
|
||||||
|
## File Requirements
|
||||||
|
|
||||||
|
- Maximum file size: 100KB recommended
|
||||||
|
- Duration: 1-5 seconds (will loop)
|
||||||
|
- Sample rate: 44.1kHz or 48kHz
|
||||||
|
- Bitrate: 128kbps or higher
|
||||||
|
|
||||||
|
## Example Ringtone Sources
|
||||||
|
|
||||||
|
You can find free ringtone files at:
|
||||||
|
- https://freesound.org/
|
||||||
|
- https://incompetech.com/
|
||||||
|
- Or create your own using Audacity or similar tools
|
||||||
Reference in New Issue
Block a user