1// PPOAGroup browser dialer interop layer. 2// Wraps the @twilio/voice-sdk Device API for Blazor WASM JSInterop. 3// 4// Twilio Voice SDK is loaded from a pinned CDN (see index.html). When the SDK is present 5// the global `Twilio` object provides `Device`. We expose a minimal facade matching the 6// plan's contract: register / connect / disconnect / mute / refreshToken / unregister. 7// 8// All calls are fire-and-forget where possible. Errors are surfaced to .NET via the 9// captured DotNetObjectReference's reportError method. 10 11window.ppoaDialer = (function () { 12 let device = null; 13 let activeCall = null; 14 let dotNetRef = null; 15 16 function reportEvent(name, payload) { 17 if (!dotNetRef) return; 18 try { 19 dotNetRef.invokeMethodAsync('OnDialerEvent', name, payload || {}); 20 } catch (e) { 21 // swallow â Blazor disposed before event reached 22 } 23 } 24 25 // True when index.html's Twilio script tag fired onerror, OR when onload fired 26 // but window.Twilio.Device is missing (corrupted bundle). Either way, the SDK 27 // is unusable and the dialer must show a clear error instead of failing silently. 28 function isSdkUnavailable() { 29 if (window.__ppoaTwilioSdkLoadFailed === true) return true; 30 if (typeof window.Twilio === 'undefined') return true; 31 if (!window.Twilio.Device) return true; 32 return false; 33 } 34 35 function attachCallEvents(call) { 36 call.on('accept', () => reportEvent('accept', {})); 37 call.on('disconnect', () => { 38 const sid = call.parameters && call.parameters.CallSid; 39 reportEvent('disconnect', { callSid: sid }); 40 activeCall = null; 41 }); 42 call.on('cancel', () => reportEvent('cancel', {})); 43 call.on('error', (err) => { 44 reportEvent('error', { message: (err && err.message) || 'Unknown call error' }); 45 }); 46 call.on('reject', () => reportEvent('reject', {})); 47 } 48 49 return { 50 // Public synchronous probe Blazor can call before attempting register/connect. 51 // Cheaper than spinning up the JSInterop register path just to discover the SDK is missing. 52 isReady: function () { return !isSdkUnavailable(); }, 53 54 // Initial Device.register(token). Defer this until first dialer-expand to avoid 55 // per-page-load WebSocket overhead on users who never open the dialer. 56 register: function (token, dotNet) { 57 dotNetRef = dotNet; 58 if (isSdkUnavailable()) { 59 console.error('[ppoa] register() called but Twilio Voice SDK is not loaded.'); 60 reportEvent('sdkUnavailable', { message: 'Voice library failed to load. Hard refresh the page (Ctrl+Shift+R), or contact support.' }); 61 return false; 62 } 63 try { 64 if (device) { 65 // Already registered â refresh token in place. 66 device.updateToken(token); 67 reportEvent('ready', {}); 68 return true; 69 } 70 device = new window.Twilio.Device(token, { 71 logLevel: 'warn', 72 closeProtection: true, 73 edge: 'ashburn' 74 }); 75 device.on('registered', () => reportEvent('ready', {})); 76 device.on('error', (err) => { 77 reportEvent('error', { message: (err && err.message) || 'Device error' }); 78 }); 79 device.on('tokenWillExpire', () => reportEvent('tokenWillExpire', {})); 80 device.on('incoming', () => { /* Phase 2 only â ignore inbound */ }); 81 device.register(); 82 return true; 83 } catch (e) { 84 reportEvent('error', { message: e.message || 'register failed' }); 85 return false; 86 } 87 }, 88 89 connect: async function (toNumber, callLogId, initiatedByUserId) { 90 if (isSdkUnavailable()) { 91 reportEvent('sdkUnavailable', { message: 'Voice library failed to load. Hard refresh the page (Ctrl+Shift+R), or contact support.' }); 92 return false; 93 } 94 if (!device) { 95 reportEvent('error', { message: 'Device not registered' }); 96 return false; 97 } 98 try { 99 activeCall = await device.connect({ 100 params: { 101 To: toNumber, 102 callLogId: String(callLogId), 103 initiatedByUserId: initiatedByUserId || '' 104 } 105 }); 106 attachCallEvents(activeCall); 107 return true; 108 } catch (e) { 109 reportEvent('error', { message: e.message || 'connect failed' }); 110 return false; 111 } 112 }, 113 114 disconnect: function () { 115 if (activeCall) { 116 try { activeCall.disconnect(); } catch { /* ignore */ } 117 } 118 }, 119 120 mute: function (shouldMute) { 121 if (activeCall) { 122 try { activeCall.mute(!!shouldMute); } catch { /* ignore */ } 123 } 124 }, 125 126 refreshToken: function (token) { 127 if (device) { 128 try { device.updateToken(token); reportEvent('ready', {}); } catch (e) { 129 reportEvent('error', { message: e.message || 'updateToken failed' }); 130 } 131 } 132 }, 133 134 unregister: function () { 135 try { if (activeCall) activeCall.disconnect(); } catch { /* ignore */ } 136 try { if (device) device.unregister(); } catch { /* ignore */ } 137 activeCall = null; 138 device = null; 139 } 140 }; 141})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.