Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(firebase_messaging)!: upgrade messaging web to Firebase v9 JS SDK. #8860

Merged
merged 11 commits into from
Jul 7, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class FirebaseMessaging extends FirebasePluginPlatform {
return _delegate.onTokenRefresh;
}

bool isSupported() {
Future<bool> isSupported() {
return _delegate.isSupported();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ class MethodChannelFirebaseMessaging extends FirebaseMessagingPlatform {

/// Returns "true" as this API is used to inform users of web browser support
@override
bool isSupported() {
return true;
Future<bool> isSupported() {
return Future.value(true);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ abstract class FirebaseMessagingPlatform extends PlatformInterface {

/// isSupported() informs web users whether
/// the browser supports Firebase.Messaging
bool isSupported() {
Future<bool> isSupported() {
throw UnimplementedError('isSupported() is not implemented');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class FirebaseMessagingWeb extends FirebaseMessagingPlatform {
_webMessaging ??=
messaging_interop.getMessagingInstance(core_interop.app(app.name));

if (!_initialized && messaging_interop.isSupported()) {
if (!_initialized) {
_webMessaging!.onMessage
.listen((messaging_interop.MessagePayload webMessagePayload) {
RemoteMessage remoteMessage =
Expand Down Expand Up @@ -57,8 +57,8 @@ class FirebaseMessagingWeb extends FirebaseMessagingPlatform {

/// Updates user on browser support for Firebase.Messaging
@override
bool isSupported() {
return messaging_interop.isSupported();
Future<bool> isSupported() {
return messaging_interop.Messaging.isSupported();
}

@override
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import 'package:js/js.dart';

import 'package:firebase_core_web/firebase_core_web_interop.dart';
import 'messaging_interop.dart' as messaging_interop;
import 'firebase_interop.dart' as firebase_interop;

export 'messaging_interop.dart';

/// Given an AppJSImp, return the Messaging instance.
Messaging getMessagingInstance([App? app]) {
return Messaging.getInstance(app != null
? firebase_interop.messaging(app.jsObject)
: firebase_interop.messaging());
? messaging_interop.getMessaging(app.jsObject)
: messaging_interop.getMessaging());
}

class Messaging extends JsObjectWrapper<messaging_interop.MessagingJsImpl> {
Expand All @@ -29,23 +28,25 @@ class Messaging extends JsObjectWrapper<messaging_interop.MessagingJsImpl> {
return _expando[jsObject] ??= Messaging._fromJsObject(jsObject);
}

static bool isSupported() => messaging_interop.isSupported();
static Future<bool> isSupported() =>
handleThenable(messaging_interop.isSupported());

Messaging._fromJsObject(messaging_interop.MessagingJsImpl jsObject)
: super.fromJsObject(jsObject);

/// To forcibly stop a registration token from being used, delete it by calling this method.
/// Calling this method will stop the periodic data transmission to the FCM backend.
Future<void> deleteToken() => handleThenable(jsObject.deleteToken());
Future<void> deleteToken() =>
handleThenable(messaging_interop.deleteToken(jsObject));

/// After calling [requestPermission] you can call this method to get an FCM registration token
/// that can be used to send push messages to this user.
Future<String> getToken({String? vapidKey}) =>
handleThenable(jsObject.getToken(vapidKey == null
? null
: {
'vapidKey': vapidKey,
}));
handleThenable(messaging_interop.getToken(
jsObject,
vapidKey == null
? null
: messaging_interop.GetTokenOptions(vapidKey: vapidKey)));

// ignore: close_sinks
StreamController<MessagePayload>? _onMessageController;
Expand All @@ -67,7 +68,8 @@ class Messaging extends JsObjectWrapper<messaging_interop.MessagingJsImpl> {
_controller!.addError(e);
});

jsObject.onMessage(nextWrapper, errorWrapper);
messaging_interop.onMessage(jsObject,
messaging_interop.Observer(next: nextWrapper, error: errorWrapper));
}
return _controller.stream;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,52 @@

// ignore_for_file: public_member_api_docs

@JS('firebase.messaging')
@JS('firebase_messaging')
library firebase_interop.messaging;

import 'package:js/js.dart';
import 'package:firebase_core_web/firebase_core_web_interop.dart';

@JS()
external MessagingJsImpl getMessaging([AppJsImpl? app]);

@JS()
external PromiseJsImpl<bool> deleteToken(MessagingJsImpl messaging);

@JS()
external PromiseJsImpl<String> getToken(
MessagingJsImpl messaging, GetTokenOptions? getTokenOptions);

@JS('isSupported')
external bool isSupported();
external PromiseJsImpl<bool> isSupported();

@JS()
external void Function() onMessage(
MessagingJsImpl messaging,
Observer observer,
);

@JS('Messaging')
abstract class MessagingJsImpl {
external PromiseJsImpl<void> deleteToken();
external PromiseJsImpl<String> getToken(dynamic getTokenOptions);
external void Function() onMessage(
dynamic optionsOrObserverOrOnNext,
dynamic observerOrOnNextOrOnError,
);
abstract class MessagingJsImpl {}

@JS()
@anonymous
class Observer {
external dynamic get next;
external dynamic get error;
external factory Observer({dynamic next, dynamic error});
}

@JS()
@anonymous
class GetTokenOptions {
external String get vapidKey;
// TODO - I imagine we won't be implementing serviceWorkerRegistration type as it extends EventTarget class
// external String get serviceWorkerRegistration
external factory GetTokenOptions({
String? vapidKey,
/*dynamic serviceWorkerRegistration */
});
}

@JS()
Expand Down
52 changes: 40 additions & 12 deletions tests/test_driver/firebase_messaging/firebase_messaging_e2e.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ void setupTests() {
});

group('isSupported()', () {
test('returns "true" value', () {
expect(messaging.isSupported(), isTrue);
test('returns "true" value', () async {
final result = await messaging.isSupported();

expect(result, isA<bool>());
});
});

Expand All @@ -97,6 +99,7 @@ void setupTests() {
'authorizationStatus returns AuthorizationStatus.notDetermined on Web',
() async {
final result = await messaging.requestPermission();

expect(result, isA<NotificationSettings>());
expect(
result.authorizationStatus,
Expand Down Expand Up @@ -136,8 +139,21 @@ void setupTests() {
'getToken()',
() {
test('returns a token', () async {
final result = await messaging.getToken();
expect(result, isA<String>());
final result = await messaging.requestPermission();

if (result.authorizationStatus == AuthorizationStatus.authorized) {
final result = await messaging.getToken();

expect(result, isA<String>());
} else {
await expectLater(
messaging.getToken(),
throwsA(
isA<FirebaseException>()
.having((e) => e.code, 'code', 'permission-blocked'),
),
);
}
});
},
skip: skipManualTests,
Expand All @@ -147,14 +163,26 @@ void setupTests() {
test(
'generate a new token after deleting',
() async {
final token1 = await messaging.getToken();
await Future.delayed(const Duration(seconds: 3));
await messaging.deleteToken();
await Future.delayed(const Duration(seconds: 3));
final token2 = await messaging.getToken();
expect(token1, isA<String>());
expect(token2, isA<String>());
expect(token1, isNot(token2));
final result = await messaging.requestPermission();

if (result.authorizationStatus == AuthorizationStatus.authorized) {
final token1 = await messaging.getToken();
await Future.delayed(const Duration(seconds: 3));
await messaging.deleteToken();
await Future.delayed(const Duration(seconds: 3));
final token2 = await messaging.getToken();
expect(token1, isA<String>());
expect(token2, isA<String>());
expect(token1, isNot(token2));
} else {
await expectLater(
messaging.getToken(),
throwsA(
isA<FirebaseException>()
.having((e) => e.code, 'code', 'permission-blocked'),
),
);
}
},
skip: skipManualTests,
); // only run for manual testing
Expand Down