Skip to content

Commit 8cf4a39

Browse files
authored
Merge pull request #167 from cybex-dev/unified_service_worker
Update unified service worker
2 parents 3225f87 + db392f2 commit 8cf4a39

File tree

9 files changed

+96
-132
lines changed

9 files changed

+96
-132
lines changed

.github/workflows/flutter.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ jobs:
5858
- run: flutter config --enable-web
5959
- run: cd ./example; flutter build web --release --target=lib/main.dart --output=build/web
6060

61+
- name: Update service worker
62+
run: cat ./example/service-worker/twilio-sw.js > ./example/build/web/flutter_service_worker.js
63+
6164
- name: Archive Production Artifact
6265
uses: actions/upload-artifact@master
6366
with:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ build/
4747
*.lock
4848

4949
*.log
50+
51+
.firebase/

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
* Fix: call ringing event always showing `CallDirection.outgoing`
4242
* Fix: [Android] Updated CallKit incoming/outgoing name parameter resolution
4343
* Fix: [Web] Notification actions working intermittently.
44+
* Fix: [Web] Added suggested service worker integration with CI/CD unifying with `flutter_service_worker.js`, see [here](https://firebase.flutter.dev/docs/messaging/usage/#background-messages) for more info regarding service worker limitations.
45+
* Update: [Web] Remove additional service-worker files
4446

4547
## 0.0.9
4648

README.md

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -165,50 +165,57 @@ See [example](https://github.com/cybex-dev/twilio_voice/blob/master/example/andr
165165

166166
There are 4 important files for Twilio incoming/missed call notifications to work:
167167

168-
- `twilio.min.js` is the twilio javascript SDK, it is used to handle the incoming calls.
169168
- `notifications.js` is the main file, it handles the notifications and the service worker.
170-
- `load-sw.js` is responsible for (re)loading the twilio service worker.
171-
- `twilio-sw.js` is the service worker, it is used to handle the incoming calls.
169+
- `twilio-sw.js` is the service worker _content_ used to work with the default `flutter_service_worker.js` (this can be found in `build/web/flutter_service_worker.js` after calling `flutter build web`). This file's contents are to be copied into the `flutter_service_worker.js` file after you've built your application.
172170

173171
Also, the twilio javascript SDK itself, `twilio.min.js` is needed.
174172

175-
To ensure proper/as intended setup:
173+
### To ensure proper/as intended setup:
176174

177-
1. Locate 4 files (`twilio.min.js`, `load-sw.js`, `notifications.js` and `twilio-sw.js`) from `example/web` folder
178-
2. Copy all 4 files into your own project,
179-
3. (optional) Review & change the `notifications.js`, `twilio-sw.js` files to match your needs.
175+
1. Copy files `example/web/notifications.js` and `example/web/twilio.min.js` into your application's `web` folder.
176+
2. This step should be done AFTER you've built your application, every time the `flutter_service_worker.js` changes (this includes hot reloads on your local machine unfortunately)
177+
1. Copy the contents of `example/web/twilio-sw.js` into your `build/web/flutter_service_worker.js` file, **at the end of the file**. See [service-worker](#service-worker) for more information.
178+
179+
Note, these files can be changed to suite your needs - however the core functionality should remain the same: responding to `notificationclick`, `notificationclose`, `message` events and associated sub-functions.
180180

181181
Finally, add the following code to your `index.html` file, **at the end of body tag**:
182182

183183
```html
184184
<body>
185+
<!--Start Twilio Voice impl-->
185186
<!--twilio native js library-->
186187
<script type="text/javascript" src="./twilio.min.js"></script>
187-
188-
<!--load twilio service worker-->
189-
<script type="text/javascript" src="./load-sw.js"></script>
188+
<!--End Twilio Voice impl-->
190189

191190
<script>
192-
if ('serviceWorker' in navigator) {
193-
window.addEventListener('load', async () => {
194-
await navigator.serviceWorker.register('twilio-sw.js').then(value => {
195-
console.log('Twilio Voice Service worker registered successfully.');
196-
}).catch((error) => {
197-
console.warn('Error registering Twilio Service Worker: ' + error.message + '. This prevents notifications from working natively');
198-
});
199-
});
200-
}
201-
</script>
191+
window.addEventListener('load', function(ev) {
192+
// Download main.dart.js
193+
...
202194
</body>
203195
```
204196
205197
#### Web Considerations
206198
207-
Notice should be given to using `firebase-messaging-sw.js` in addition to `twilio-sw.js` since these may cause conflict in service worker events being handled.
208-
209199
_If you need to debug the service worker, open up Chrome Devtools, go to Application tab, and select Service Workers from the left menu. There you can see the service workers and their status.
210200
To review service worker `notificationclick`, `notificationclose`, `message`, etc events - do this using Chrome Devtools (Sources tab, left panel below 'site code' the service workers are listed)_
211201
202+
##### Service Worker
203+
204+
Unifying the service worker(s) is best done via post-compilation tools or a CI/CD pipeline (suggested).
205+
206+
A snippet of the suggested service worker integration is as follows:
207+
208+
```yaml
209+
#...
210+
- run: cd ./example; flutter build web --release --target=lib/main.dart --output=build/web
211+
212+
- name: Update service worker
213+
run: cat ./example/web/twilio-sw.js > ./example/build/web/flutter_service_worker.js
214+
#...
215+
```
216+
217+
A complete example could be found in the github workflows `.github/workflows/flutter.yml` file, see [here](https://github.com/cybex-dev/twilio_voice/blob/master/.github/workflows/flutter.yml).
218+
212219
##### Web Notifications
213220
214221
2 types of notifications are shown:

example/lib/main.dart

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,25 @@ enum RegistrationMethod {
4242
void main() async {
4343
WidgetsFlutterBinding.ensureInitialized();
4444
if (kIsWeb) {
45-
// Get registration method
46-
final registrationMethod = RegistrationMethod.loadFromEnvironment() ?? RegistrationMethod.env;
47-
48-
// if web, we use the requested registration method for firebase registration only
49-
if(registrationMethod == RegistrationMethod.firebase) {
50-
// Add firebase config here
51-
const options = FirebaseOptions(
52-
apiKey: '',
53-
appId: '',
54-
messagingSenderId: '',
55-
projectId: '',
56-
authDomain: '',
57-
databaseURL: '',
58-
storageBucket: '',
59-
measurementId: '',
60-
);
61-
// For web apps only
62-
await Firebase.initializeApp(options: options);
63-
}
45+
46+
// Add firebase config here
47+
const options = FirebaseOptions(
48+
apiKey: '',
49+
appId: '',
50+
messagingSenderId: '',
51+
projectId: '',
52+
authDomain: '',
53+
databaseURL: '',
54+
storageBucket: '',
55+
measurementId: '',
56+
);
57+
58+
// For web apps only
59+
// ignore: body_might_complete_normally_catch_error
60+
await Firebase.initializeApp(options: options).catchError((error) {
61+
printDebug("Failed to initialise firebase $error");
62+
});
63+
6464
} else {
6565
// For Android, iOS - Firebase will search for google-services.json in android/app directory or GoogleService-Info.plist in ios/Runner directory respectively.
6666
await Firebase.initializeApp();

example/lib/screens/widgets/permissions_block.dart

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'dart:async';
22
import 'dart:io';
33

4+
import 'package:firebase_core/firebase_core.dart';
45
import 'package:firebase_messaging/firebase_messaging.dart';
6+
import 'package:flutter/foundation.dart';
57
import 'package:flutter/material.dart';
68
import 'package:twilio_voice/twilio_voice.dart';
79
import 'package:twilio_voice_example/screens/widgets/permission_tile.dart';
@@ -116,12 +118,13 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
116118
_stateBluetooth = value;
117119
});
118120
}
121+
119122
//#endregion
120123

121124
@override
122125
void didChangeAppLifecycleState(AppLifecycleState state) {
123126
printDebug("AppLifecycleState: $state");
124-
if(_lastLifecycleState != state && state == AppLifecycleState.resumed) {
127+
if (_lastLifecycleState != state && state == AppLifecycleState.resumed) {
125128
_updatePermissions();
126129
}
127130
_lastLifecycleState = state;
@@ -189,7 +192,9 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
189192
_tv.hasMicAccess().then((value) => setMicPermission = value);
190193
_tv.hasReadPhoneStatePermission().then((value) => setReadPhoneStatePermission = value);
191194
_tv.hasReadPhoneNumbersPermission().then((value) => setReadPhoneNumbersPermission = value);
192-
FirebaseMessaging.instance.requestPermission().then((value) => setBackgroundPermission = value.authorizationStatus == AuthorizationStatus.authorized);
195+
if (Firebase.apps.isNotEmpty) {
196+
FirebaseMessaging.instance.requestPermission().then((value) => setBackgroundPermission = value.authorizationStatus == AuthorizationStatus.authorized);
197+
}
193198
_tv.hasCallPhonePermission().then((value) => setCallPhonePermission = value);
194199
_tv.hasRegisteredPhoneAccount().then((value) => setPhoneAccountRegistered = value);
195200
_tv.isPhoneAccountEnabled().then((value) => setIsPhoneAccountEnabled = value);
@@ -266,22 +271,26 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
266271
icon: Icons.mic,
267272
title: "Microphone",
268273
granted: _hasMicPermission,
269-
onRequestPermission: () => _tv.requestMicAccess(),
270-
),
271-
272-
PermissionTile(
273-
icon: Icons.notifications,
274-
title: "Notifications",
275-
granted: _hasBackgroundPermissions,
276274
onRequestPermission: () async {
277-
await FirebaseMessaging.instance.requestPermission();
278-
final settings = await FirebaseMessaging.instance.getNotificationSettings();
279-
setBackgroundPermission = settings.authorizationStatus == AuthorizationStatus.authorized;
275+
await _tv.requestMicAccess();
276+
setMicPermission = await _tv.hasMicAccess();
280277
},
281278
),
282279

280+
if (Firebase.apps.isNotEmpty)
281+
PermissionTile(
282+
icon: Icons.notifications,
283+
title: "Notifications",
284+
granted: _hasBackgroundPermissions,
285+
onRequestPermission: () async {
286+
await FirebaseMessaging.instance.requestPermission();
287+
final settings = await FirebaseMessaging.instance.getNotificationSettings();
288+
setBackgroundPermission = settings.authorizationStatus == AuthorizationStatus.authorized;
289+
},
290+
),
291+
283292
// if android
284-
if (Platform.isAndroid)
293+
if (!kIsWeb && Platform.isAndroid)
285294
PermissionTile(
286295
icon: Icons.phone,
287296
title: "Read Phone State",
@@ -293,7 +302,7 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
293302
),
294303

295304
// if android
296-
if (Platform.isAndroid)
305+
if (!kIsWeb && Platform.isAndroid)
297306
PermissionTile(
298307
icon: Icons.phone,
299308
title: "Read Phone Numbers",
@@ -305,7 +314,7 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
305314
),
306315

307316
// if android
308-
if (Platform.isAndroid)
317+
if (!kIsWeb && Platform.isAndroid)
309318
PermissionTile(
310319
icon: Icons.call_made,
311320
title: "Call Phone",
@@ -317,7 +326,7 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
317326
),
318327

319328
// if android
320-
if (Platform.isAndroid)
329+
if (!kIsWeb && Platform.isAndroid)
321330
PermissionTile(
322331
icon: Icons.phonelink_setup,
323332
title: "Phone Account",
@@ -329,7 +338,7 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
329338
),
330339

331340
// if android
332-
if (Platform.isAndroid)
341+
if (!kIsWeb && Platform.isAndroid)
333342
ListTile(
334343
enabled: _hasRegisteredPhoneAccount,
335344
dense: true,
@@ -365,4 +374,4 @@ class _PermissionsBlockState extends State<PermissionsBlock> with WidgetsBinding
365374
_subscription.cancel();
366375
super.dispose();
367376
}
368-
}
377+
}

example/web/twilio-sw.js renamed to example/service-worker/twilio-sw.js

Lines changed: 14 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
importScripts('notifications.js')
1+
// Twilio Voice Service Worker
22

3-
// create service worker
3+
importScripts('notifications.js')
44

55
const tag = 'Service Worker';
66

@@ -11,45 +11,10 @@ const _error = (...message) => {
1111
console.error(`[ ${tag} ]`, ...message);
1212
};
1313

14-
_log('Started');
15-
16-
self.addEventListener('activate', (event) => {
17-
_log('activate event', event);
18-
// self.clients.claim();
19-
event.waitUntil(self.clients.claim()); // Become available to all pages
20-
_log('activated!');
21-
22-
// Optional: Get a list of all the current open windows/tabs under
23-
// our service worker's control, and force them to reload.
24-
// This can "unbreak" any open windows/tabs as soon as the new
25-
// service worker activates, rather than users having to manually reload.
26-
// source: https://stackoverflow.com/a/38980776/4628115
27-
// self.clients.matchAll({type: 'window'}).then(windowClients => {
28-
// windowClients.forEach(windowClient => {
29-
// windowClient.navigate(windowClient.url);
30-
// });
31-
// });
32-
});
33-
34-
// disabled due to Chrome no-op warning
35-
//self.addEventListener('fetch', (event) => {
36-
// _log(`fetch event [${event.request.url}]`, event);
37-
//});
38-
39-
self.addEventListener('install', (event) => {
40-
_log('install event, skip waiting', event);
41-
// self.skipWaiting()
42-
event.waitUntil(self.skipWaiting()); // Activate worker immediately
43-
44-
// // Skip over the "waiting" lifecycle state, to ensure that our
45-
// // new service worker is activated immediately, even if there's
46-
// // another tab open controlled by our older service worker code.
47-
// // source: https://stackoverflow.com/a/38980776/4628115
48-
// self.skipWaiting();
49-
});
14+
_log('Twilio Voice service-worker started');
5015

5116
self.addEventListener('message', (event) => {
52-
handleMessage(event);
17+
_handleMessage(event);
5318
});
5419

5520
self.addEventListener('messageerror', (event) => {
@@ -59,42 +24,38 @@ self.addEventListener('messageerror', (event) => {
5924
self.addEventListener('notificationclick', (event) => {
6025
_log(`notificationclick event [${event.action}]`, event);
6126
event.notification.close();
62-
handleNotificationEvent(event.action, event.notification, event.notification.tag);
27+
_handleNotificationEvent(event.action, event.notification, event.notification.tag);
6328
});
6429

6530
self.addEventListener('notificationclose', (event) => {
6631
_log('notificationclose event', event);
6732
event.notification.close();
68-
handleNotificationEvent(null, event.data, event.tag);
69-
});
70-
71-
self.addEventListener('push', (event) => {
72-
_log('push event', event);
33+
_handleNotificationEvent(null, event.data, event.tag);
7334
});
7435

75-
function handleNotificationEvent(action, payload, tag) {
36+
function _handleNotificationEvent(action, payload, tag) {
7637
const message = {
7738
tag: tag,
7839
}
7940
switch (action) {
8041
case 'answer': {
81-
focusClientWindow();
82-
sendToClient(action, message);
42+
_focusClientWindow();
43+
_sendToClient(action, message);
8344
break;
8445
}
8546
case 'hangup':
8647
case 'reject': {
87-
sendToClient(action, message);
48+
_sendToClient(action, message);
8849
break;
8950
}
9051
default: {
91-
focusClientWindow();
52+
_focusClientWindow();
9253
break;
9354
}
9455
}
9556
}
9657

97-
function sendToClient(action, payload) {
58+
function _sendToClient(action, payload) {
9859
const message = {
9960
action: action,
10061
payload: payload
@@ -106,7 +67,7 @@ function sendToClient(action, payload) {
10667
});
10768
}
10869

109-
function focusClientWindow() {
70+
function _focusClientWindow() {
11071
self.clients.matchAll({
11172
type: "window",
11273
}).then((clients) => {
@@ -119,7 +80,7 @@ function focusClientWindow() {
11980
});
12081
}
12182

122-
function handleMessage(event) {
83+
function _handleMessage(event) {
12384
if (!event) {
12485
_error('No event');
12586
return;

0 commit comments

Comments
 (0)