Skip to content

Commit 9a4253d

Browse files
authored
Merge pull request #304 from cybex-dev/feat_update_device_token
Enables token update for active Twilio devices
2 parents d39004a + 2c842a9 commit 9a4253d

File tree

5 files changed

+114
-25
lines changed

5 files changed

+114
-25
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Next Release
22

33
* Feat: [Web] Add Twilio Device [DeviceState] accessor protecting un/registration.
4+
* Feat: [Web] Add Twilio Device `updateToken(String)` function to allow updating of active device tokens.
5+
* Feat: update example.
46
* Docs: update CHANGELOG
57

68
## 0.3.2+2
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:flutter/material.dart';
2+
3+
class UpdateTokenDialogContent extends StatelessWidget {
4+
const UpdateTokenDialogContent({super.key});
5+
6+
@override
7+
Widget build(BuildContext context) {
8+
final textController = TextEditingController();
9+
return AlertDialog(
10+
title: Text('Paste your new token'),
11+
content: SingleChildScrollView(
12+
child: ListBody(
13+
children: <Widget>[
14+
const Text('Paste your new token to continue making calls, you\'ll still receive calls to the current device.'),
15+
const SizedBox(height: 16),
16+
TextField(
17+
controller: textController,
18+
decoration: const InputDecoration(
19+
border: OutlineInputBorder(),
20+
labelText: 'New Token',
21+
alignLabelWithHint: true,
22+
),
23+
maxLines: 3,
24+
),
25+
],
26+
),
27+
),
28+
actions: <Widget>[
29+
TextButton(
30+
onPressed: () {
31+
Navigator.of(context).pop(null); // User chose not to update the token
32+
},
33+
child: const Text('Cancel'),
34+
),
35+
ElevatedButton(
36+
onPressed: () {
37+
Navigator.of(context).pop(textController.text); // User chose to update the token
38+
},
39+
child: const Text('Update Token'),
40+
),
41+
],
42+
);
43+
}
44+
}

example/lib/main.dart

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:firebase_analytics/firebase_analytics.dart';
88
import 'package:flutter/foundation.dart';
99
import 'package:flutter/material.dart';
1010
import 'package:twilio_voice/twilio_voice.dart';
11+
import 'package:twilio_voice_example/dialogs/update_token_dialog.dart';
1112
import 'package:twilio_voice_example/screens/ui_call_screen.dart';
1213
import 'package:twilio_voice_example/screens/ui_registration_screen.dart';
1314

@@ -103,25 +104,25 @@ class _AppState extends State<App> {
103104

104105
// Use for locally provided token generator e.g. Twilio's quickstarter project: https://github.com/twilio/voice-quickstart-server-node
105106
// if (!kIsWeb) {
106-
bool success = false;
107-
// if not web, we use the requested registration method
108-
switch (widget.registrationMethod) {
109-
case RegistrationMethod.env:
110-
success = await _registerFromEnvironment();
111-
break;
112-
case RegistrationMethod.url:
113-
success = await _registerUrl();
114-
break;
115-
case RegistrationMethod.firebase:
116-
success = await _registerFirebase();
117-
break;
118-
}
107+
bool success = false;
108+
// if not web, we use the requested registration method
109+
switch (widget.registrationMethod) {
110+
case RegistrationMethod.env:
111+
success = await _registerFromEnvironment();
112+
break;
113+
case RegistrationMethod.url:
114+
success = await _registerUrl();
115+
break;
116+
case RegistrationMethod.firebase:
117+
success = await _registerFirebase();
118+
break;
119+
}
119120

120-
if (success) {
121-
setState(() {
122-
twilioInit = true;
123-
});
124-
}
121+
if (success) {
122+
setState(() {
123+
twilioInit = true;
124+
});
125+
}
125126
// } else {
126127
// // for web, we always show the initialisation screen unless we specified an
127128
// if (widget.registrationMethod == RegistrationMethod.env) {
@@ -286,7 +287,7 @@ class _AppState extends State<App> {
286287

287288
switch (event) {
288289
case CallEvent.incoming:
289-
// applies to web only
290+
// applies to web only
290291
if (kIsWeb || Platform.isAndroid) {
291292
final activeCall = TwilioVoice.instance.call.activeCall;
292293
if (activeCall != null && activeCall.callDirection == CallDirection.incoming) {
@@ -338,7 +339,8 @@ class _AppState extends State<App> {
338339
showDialog(
339340
// ignore: use_build_context_synchronously
340341
context: context,
341-
builder: (context) => const AlertDialog(
342+
builder: (context) =>
343+
const AlertDialog(
342344
title: Text("Error"),
343345
content: Text("Failed to register for calls"),
344346
),
@@ -357,6 +359,10 @@ class _AppState extends State<App> {
357359
appBar: AppBar(
358360
title: const Text("Plugin example app"),
359361
actions: [
362+
if(twilioInit) ...[
363+
const SizedBox(width: 8),
364+
const _UpdateTokenAction(),
365+
],
360366
_LogoutAction(
361367
onSuccess: () {
362368
setState(() {
@@ -381,12 +387,12 @@ class _AppState extends State<App> {
381387
padding: const EdgeInsets.symmetric(horizontal: 8),
382388
child: twilioInit
383389
? UICallScreen(
384-
userId: userId,
385-
onPerformCall: _onPerformCall,
386-
)
390+
userId: userId,
391+
onPerformCall: _onPerformCall,
392+
)
387393
: UIRegistrationScreen(
388-
onRegister: _onRegisterWithToken,
389-
),
394+
onRegister: _onRegisterWithToken,
395+
),
390396
),
391397
),
392398
),
@@ -463,3 +469,30 @@ class _LogoutAction extends StatelessWidget {
463469
icon: const Icon(Icons.logout, color: Colors.white));
464470
}
465471
}
472+
473+
class _UpdateTokenAction extends StatelessWidget {
474+
const _UpdateTokenAction({super.key});
475+
476+
@override
477+
Widget build(BuildContext context) {
478+
return TextButton.icon(
479+
onPressed: () async {
480+
// show dialog to enter new token
481+
final token = await showDialog<String?>(
482+
context: context,
483+
builder: (context) => const UpdateTokenDialogContent(),
484+
);
485+
if (token?.isEmpty ?? true) {
486+
return;
487+
}
488+
final result = await TwilioVoice.instance.setTokens(accessToken: token!);
489+
final message = (result ?? false) ? "Successfully updated token" : "Failed to update token";
490+
ScaffoldMessenger.of(context).showSnackBar(
491+
SnackBar(content: Text(message)),
492+
);
493+
},
494+
label: const Text("Update Token", style: TextStyle(color: Colors.white)),
495+
icon: const Icon(Icons.refresh, color: Colors.white),
496+
);
497+
}
498+
}

lib/_internal/js/device/device.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ class Device extends Twilio {
8787
/// Documentation: https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice#devicestate
8888
@JS("status")
8989
external String state();
90+
91+
/// Update the device's access token.
92+
/// Documentation: https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice#deviceupdatetokentoken
93+
@JS("updateToken")
94+
external void updateToken(String token);
9095
}
9196

9297
/// Device options

lib/_internal/twilio_voice_web.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ class TwilioVoiceWeb extends MethodChannelTwilioVoice {
363363
// }
364364
// }
365365
try {
366+
final shouldUpdate = device != null && getDeviceState(device!) == DeviceState.registered;
367+
if (shouldUpdate) {
368+
device!.updateToken(accessToken);
369+
} else {
366370
/// opus set as primary code
367371
/// https://www.twilio.com/blog/client-javascript-sdk-1-7-ga
368372
List<String> codecs = ["opus", "pcmu"];
@@ -378,6 +382,7 @@ class TwilioVoiceWeb extends MethodChannelTwilioVoice {
378382

379383
// Register device to accept notifications
380384
device!.register();
385+
}
381386

382387
return true;
383388
} catch (e) {

0 commit comments

Comments
 (0)