feature/webview-softphone #1

Merged
jknapp merged 6 commits from feature/webview-softphone into main 2026-04-27 12:54:33 +00:00
5 changed files with 197 additions and 34 deletions
Showing only changes of commit 3407f4c705 - Show all commits

View File

@@ -110,6 +110,8 @@ var twpTwilioEdge = window.twpConfig.twilioEdge;
var serviceWorkerRegistration = null; var serviceWorkerRegistration = null;
var currentCallDirection = null; var currentCallDirection = null;
var callHistory = []; var callHistory = [];
var wakeLock = null;
var deviceKeepaliveTimer = null;
// ============================================================ // ============================================================
// AudioContext & Ringtone // AudioContext & Ringtone
@@ -193,13 +195,79 @@ var twpTwilioEdge = window.twpConfig.twilioEdge;
} }
} }
// ============================================================
// Screen Wake Lock
// ============================================================
async function requestWakeLock() {
if (!('wakeLock' in navigator)) return;
try {
wakeLock = await navigator.wakeLock.request('screen');
console.log('TWP: Wake lock acquired');
wakeLock.addEventListener('release', function() {
console.log('TWP: Wake lock released');
wakeLock = null;
});
} catch (e) {
console.warn('TWP: Wake lock request failed:', e.message);
}
}
function releaseWakeLock() {
if (wakeLock) {
wakeLock.release().catch(function() {});
wakeLock = null;
}
}
// ============================================================
// Device Keepalive Monitor
// ============================================================
function startDeviceKeepalive() {
stopDeviceKeepalive();
deviceKeepaliveTimer = setInterval(function() {
if (device && deviceConnectionState !== 'connected' && !currentCall) {
console.log('TWP: Keepalive — device disconnected, re-registering');
device.register().catch(function(e) {
console.warn('TWP: Keepalive re-register failed:', e.message);
});
}
}, 30000);
}
function stopDeviceKeepalive() {
if (deviceKeepaliveTimer) {
clearInterval(deviceKeepaliveTimer);
deviceKeepaliveTimer = null;
}
}
// ============================================================ // ============================================================
// Page Visibility // Page Visibility
// ============================================================ // ============================================================
function setupPageVisibility() { function setupPageVisibility() {
document.addEventListener('visibilitychange', function() { document.addEventListener('visibilitychange', function() {
isPageVisible = !document.hidden; isPageVisible = !document.hidden;
if (isPageVisible && audioContext) initializeAudioContext(); if (isPageVisible) {
console.log('TWP: Page visible — checking connections');
// Resume audio
if (audioContext) initializeAudioContext();
// Re-request wake lock (auto-released when tab hidden)
requestWakeLock();
// Check token expiry — refresh if expired or within 2 minutes
if (tokenExpiry && (Date.now() > tokenExpiry - 2 * 60 * 1000)) {
console.log('TWP: Token expired or expiring soon, refreshing');
refreshToken();
}
// Check device registration
if (device && deviceConnectionState !== 'connected' && !currentCall) {
console.log('TWP: Device disconnected, re-registering');
device.register().catch(function(e) {
console.warn('TWP: Visibility re-register failed:', e.message);
});
}
} else {
console.log('TWP: Page hidden — wake lock auto-released by browser');
}
}); });
} }
@@ -352,6 +420,8 @@ var twpTwilioEdge = window.twpConfig.twilioEdge;
device.on('tokenWillExpire', function() { refreshToken(); }); device.on('tokenWillExpire', function() { refreshToken(); });
await device.register(); await device.register();
requestWakeLock();
startDeviceKeepalive();
} catch (error) { } catch (error) {
showError('Failed to setup device: ' + error.message); showError('Failed to setup device: ' + error.message);
} }
@@ -363,6 +433,7 @@ var twpTwilioEdge = window.twpConfig.twilioEdge;
function setupCallHandlers(call) { function setupCallHandlers(call) {
call.on('accept', function() { call.on('accept', function() {
stopRingtone(); stopRingtone();
requestWakeLock();
$('#phone-status').text('Connected').css('color', 'var(--accent)'); $('#phone-status').text('Connected').css('color', 'var(--accent)');
$('#call-btn').hide(); $('#call-btn').hide();
$('#answer-btn').hide(); $('#answer-btn').hide();
@@ -1041,6 +1112,8 @@ var twpTwilioEdge = window.twpConfig.twilioEdge;
// ============================================================ // ============================================================
$(window).on('beforeunload', function() { $(window).on('beforeunload', function() {
if (tokenRefreshTimer) clearTimeout(tokenRefreshTimer); if (tokenRefreshTimer) clearTimeout(tokenRefreshTimer);
releaseWakeLock();
stopDeviceKeepalive();
if (device) device.destroy(); if (device) device.destroy();
}); });

View File

@@ -35,11 +35,26 @@ public final class GeneratedPluginRegistrant {
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e); Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e);
} }
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin package_info_plus, dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin", e);
}
try { try {
flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin()); flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e); Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
} }
try {
flutterEngine.getPlugins().add(new com.baseflow.permissionhandler.PermissionHandlerPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin permission_handler_android, com.baseflow.permissionhandler.PermissionHandlerPlugin", e);
}
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.wakelock.WakelockPlusPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin wakelock_plus, dev.fluttercommunity.plus.wakelock.WakelockPlusPlugin", e);
}
try { try {
flutterEngine.getPlugins().add(new io.flutter.plugins.webviewflutter.WebViewFlutterPlugin()); flutterEngine.getPlugins().add(new io.flutter.plugins.webviewflutter.WebViewFlutterPlugin());
} catch (Exception e) { } catch (Exception e) {

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart';
import '../services/push_notification_service.dart'; import '../services/push_notification_service.dart';
@@ -52,10 +53,27 @@ class _PhoneScreenState extends State<PhoneScreen> with WidgetsBindingObserver {
} }
_initWebView(); _initWebView();
_initPush(); _initPush();
WakelockPlus.enable();
debugPrint('TWP: Wake lock enabled');
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
debugPrint('TWP: App resumed — re-enabling wake lock');
WakelockPlus.enable();
// Dispatch synthetic visibilitychange to trigger JS-side recovery
_controller.runJavaScript(
'document.dispatchEvent(new Event("visibilitychange"));'
'Object.defineProperty(document, "hidden", {value: false, configurable: true});',
);
}
} }
@override @override
void dispose() { void dispose() {
WakelockPlus.disable();
debugPrint('TWP: Wake lock disabled');
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }

View File

@@ -37,26 +37,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.0" version: "1.19.1"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@@ -69,10 +69,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@@ -224,30 +224,46 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.7" version: "11.0.2"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.8" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -260,34 +276,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.0" version: "1.17.0"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
url: "https://pub.dev"
source: hosted
version: "9.0.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.1"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@@ -425,18 +457,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@@ -457,10 +489,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3" version: "0.7.10"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@@ -469,14 +501,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.4" version: "0.9.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@@ -485,6 +525,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.0" version: "14.3.0"
wakelock_plus:
dependency: "direct main"
description:
name: wakelock_plus
sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_plus_platform_interface
sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
web: web:
dependency: transitive dependency: transitive
description: description:
@@ -550,5 +606,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.6.0 <4.0.0" dart: ">=3.10.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.38.0"

View File

@@ -1,7 +1,7 @@
name: twp_softphone name: twp_softphone
description: TWP Softphone - WebView client for Twilio WordPress Plugin description: TWP Softphone - WebView client for Twilio WordPress Plugin
publish_to: 'none' publish_to: 'none'
version: 2.0.1+7 version: 2.0.2+8
environment: environment:
sdk: ^3.5.0 sdk: ^3.5.0
@@ -16,6 +16,7 @@ dependencies:
webview_flutter: ^4.10.0 webview_flutter: ^4.10.0
webview_flutter_android: ^4.3.0 webview_flutter_android: ^4.3.0
permission_handler: ^11.3.0 permission_handler: ^11.3.0
wakelock_plus: ^1.2.8
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: