feat: Use Firebase Cloud Messaging over UP

This switches to the FOSS implementation
of the google libraries so we have
no more Blobs there.
This commit is contained in:
Christian Pauly 2022-05-14 12:06:07 +02:00
parent 4e73e6d9f4
commit 23f9be2fd5
13 changed files with 60 additions and 276 deletions

View File

@ -62,7 +62,6 @@ integration_test:
# properly set the homeserver IP for the test
- sed -i "s/10.0.2.2/$(drill docker | grep -m 1 -P "\d+\.\d+\.\d+.\d+" | awk -F ' ' '{print $NF}')/g" integration_test/users.dart
- curl docker:8008/_matrix/static/ 2> /dev/null | grep "It works! Synapse is running"
- git apply ./scripts/enable-android-google-services.patch
- flutter pub get
- flutter test integration_test
tags:

View File

@ -78,8 +78,9 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.multidex:multidex:2.0.1'
implementation('com.github.UnifiedPush:android-foss_embedded_fcm_distributor:1.0.0-beta1')
}
//apply plugin: 'com.google.gms.google-services'

View File

@ -122,7 +122,13 @@
<action android:name="org.unifiedpush.flutter.connector.REGISTRATION_FAILED" />
</intent-filter>
</receiver>
<receiver android:enabled="true" android:name=".EmbeddedDistributor" android:exported="false">
<intent-filter>
<action android:name="org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"/>
<action android:name="org.unifiedpush.android.distributor.REGISTER"/>
<action android:name="org.unifiedpush.android.distributor.UNREGISTER"/>
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

View File

@ -0,0 +1,14 @@
package chat.fluffy.fluffychat
import android.content.Context
import org.unifiedpush.android.foss_embedded_fcm_distributor.EmbeddedDistributorReceiver
class EmbeddedDistributor: EmbeddedDistributorReceiver() {
override val googleProjectNumber = "865731724731" // This value comes from the google-services.json
override fun getEndpoint(context: Context, token: String, instance: String): String {
// This returns the endpoint of your FCM Rewrite-Proxy
return "https://push.fluffychat.im/_matrix/push/v1/notify?token=$token"
}
}

View File

@ -1,36 +0,0 @@
/*package chat.fluffy.fluffychat
import com.famedly.fcm_shared_isolate.FcmSharedIsolateService
import chat.fluffy.fluffychat.MainActivity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.view.FlutterMain
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
class FcmPushService : FcmSharedIsolateService() {
override fun getEngine(): FlutterEngine {
return provideEngine(getApplicationContext())
}
companion object {
fun provideEngine(context: Context): FlutterEngine {
var engine = MainActivity.engine
if (engine == null) {
engine = MainActivity.provideEngine(context)
engine.getLocalizationPlugin().sendLocalesToFlutter(
context.getResources().getConfiguration())
engine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault())
}
return engine
}
}
}
*/

View File

@ -8,7 +8,6 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
//classpath 'com.google.gms:google-services:4.3.8'
}
}

View File

@ -34,6 +34,7 @@ import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart';
import 'package:fluffychat/utils/push_helper.dart';
import 'package:fluffychat/utils/sentry_controller.dart';
import '../config/app_config.dart';
import '../config/setting_keys.dart';
import 'famedlysdk_store.dart';
@ -52,7 +53,6 @@ class BackgroundPush {
Client client;
BuildContext? context;
GlobalKey<VRouterState>? router;
String? _fcmToken;
void Function(String errorMsg, {Uri? link})? onFcmError;
L10n? l10n;
Store? _store;
@ -65,8 +65,6 @@ class BackgroundPush {
final pendingTests = <String, Completer<void>>{};
final dynamic firebase = null; //FcmSharedIsolate();
DateTime? lastReceivedPush;
bool upAction = false;
@ -77,17 +75,6 @@ class BackgroundPush {
onRoomSync ??= client.onSync.stream
.where((s) => s.hasRoomUpdate)
.listen((s) => _onClearingPush(getFromServer: false));
firebase?.setListeners(
onMessage: (message) => pushHelper(
PushNotification.fromJson(
Map<String, dynamic>.from(message['data'] ?? message)),
client: client,
l10n: l10n,
activeRoomId: router?.currentState?.pathParameters['roomid'],
onSelectNotification: goToRoom,
),
onNewToken: _newFcmToken,
);
if (Platform.isAndroid) {
UnifiedPush.initialize(
onNewEndpoint: _newUpEndpoint,
@ -123,20 +110,12 @@ class BackgroundPush {
StreamSubscription<LoginState>? onLogin;
StreamSubscription<SyncUpdate>? onRoomSync;
void _newFcmToken(String token) {
_fcmToken = token;
setupPush();
}
Future<void> setupPusher({
String? gatewayUrl,
String? token,
Set<String?>? oldTokens,
bool useDeviceSpecificAppId = false,
}) async {
if (PlatformInfos.isIOS) {
await firebase?.requestPermission();
}
final clientName = PlatformInfos.clientName;
oldTokens ??= <String>{};
final pushers = await (client.getPushers().catchError((e) {
@ -229,11 +208,10 @@ class BackgroundPush {
if (upAction) {
return;
}
if (!PlatformInfos.isIOS &&
(await UnifiedPush.getDistributors()).isNotEmpty) {
if ((await UnifiedPush.getDistributors()).isNotEmpty) {
await setupUp();
} else {
await setupFirebase();
_noFcmWarning();
}
// ignore: unawaited_futures
@ -273,24 +251,6 @@ class BackgroundPush {
}
}
Future<void> setupFirebase() async {
Logs().v('Setup firebase');
if (_fcmToken?.isEmpty ?? true) {
try {
_fcmToken = await firebase?.getToken();
if (_fcmToken == null) throw ('PushToken is null');
} catch (e, s) {
Logs().w('[Push] cannot get token', e, e is String ? null : s);
await _noFcmWarning();
return;
}
}
await setupPusher(
gatewayUrl: AppConfig.pushNotificationsGatewayUrl,
token: _fcmToken,
);
}
Future<void> goToRoom(String? roomId) async {
try {
Logs().v('[Push] Attempting to go to room $roomId...');
@ -321,8 +281,20 @@ class BackgroundPush {
await _upUnregistered(i);
return;
}
var endpoint =
'https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify';
if (newEndpoint.startsWith(AppConfig.pushNotificationsGatewayUrl)) {
await setupPusher(
gatewayUrl: AppConfig.pushNotificationsGatewayUrl,
token: Uri.parse(newEndpoint).queryParameters['token'],
);
await store.setItem(SettingKeys.unifiedPushEndpoint, newEndpoint);
await store.setItemBool(SettingKeys.unifiedPushRegistered, true);
return;
}
try {
final url = Uri.parse(newEndpoint)
.replace(
@ -344,16 +316,11 @@ class BackgroundPush {
'[Push] No self-hosted unified push gateway present: ' + newEndpoint);
}
Logs().i('[Push] UnifiedPush using endpoint ' + endpoint);
final oldTokens = <String?>{};
try {
final fcmToken = await firebase?.getToken();
oldTokens.add(fcmToken);
} catch (_) {}
await setupPusher(
gatewayUrl: endpoint,
token: newEndpoint,
oldTokens: oldTokens,
useDeviceSpecificAppId: true,
useDeviceSpecificAppId: false,
);
await store.setItem(SettingKeys.unifiedPushEndpoint, newEndpoint);
await store.setItemBool(SettingKeys.unifiedPushRegistered, true);
@ -374,17 +341,24 @@ class BackgroundPush {
}
Future<void> _onUpMessage(Uint8List message, String i) async {
upAction = true;
final data = Map<String, dynamic>.from(
json.decode(utf8.decode(message))['notification']);
// UP may strip the devices list
data['devices'] ??= [];
await pushHelper(
PushNotification.fromJson(data),
client: client,
l10n: l10n,
activeRoomId: router?.currentState?.pathParameters['roomid'],
);
try {
Logs().d('Received Push Notification over UP');
final jsonMessage = json.decode(utf8.decode(message));
upAction = true;
final data =
Map<String, dynamic>.from(jsonMessage['notification'] ?? jsonMessage);
// UP may strip the devices list
data['devices'] ??= [];
await pushHelper(
PushNotification.fromJson(data),
client: client,
l10n: l10n,
activeRoomId: router?.currentState?.pathParameters['roomid'],
);
} catch (e, s) {
Logs().wtf('Push Helper has crashed!', e, s);
SentryController.captureException(e, s);
}
}
/// Workaround for the problem that local notification IDs must be int but we

View File

@ -25,7 +25,6 @@ dependencies:
email_validator: ^2.0.1
emoji_picker_flutter: ^1.1.2
encrypt: ^5.0.1
#fcm_shared_isolate: ^0.1.0
file_picker_cross: ^4.6.0
flutter:
sdk: flutter

View File

@ -1,5 +1,4 @@
#!/usr/bin/env bash
git apply ./scripts/enable-android-google-services.patch
FLUFFYCHAT_ORIG_GROUP="im.fluffychat"
FLUFFYCHAT_ORIG_TEAM="4NXF6Z997G"
#FLUFFYCHAT_NEW_GROUP="com.example.fluffychat"

View File

@ -2,7 +2,6 @@
flutter pub get
flutter pub run import_sorter:main --no-comments --exit-if-changed
flutter format lib/ test/ --set-exit-if-changed
git apply ./scripts/enable-android-google-services.patch
flutter pub get
flutter analyze
flutter pub run dart_code_metrics:metrics lib -r gitlab > code-quality-report.json || true

View File

@ -1,168 +0,0 @@
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 001fbd72..339b35af 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -44,7 +44,7 @@ android {
defaultConfig {
applicationId "chat.fluffy.fluffychat"
- minSdkVersion 16
+ minSdkVersion 19
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -68,6 +68,10 @@ android {
}
release {
signingConfig signingConfigs.release
+ minifyEnabled false
+ shrinkResources false
+
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
@@ -78,8 +82,11 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- //implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698
+ implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.multidex:multidex:2.0.1'
}
-//apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.gms.google-services'
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 00000000..40570865
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,41 @@
+-optimizationpasses 5
+## Flutter wrapper
+-keep class io.flutter.app.** { *; }
+-keep class io.flutter.plugin.** { *; }
+-keep class io.flutter.util.** { *; }
+-keep class io.flutter.view.** { *; }
+-keep class io.flutter.** { *; }
+-keep class io.flutter.plugins.** { *; }
+-dontwarn io.flutter.embedding.**
+
+##---------------Begin: proguard configuration for Gson (Needed for flutter_local_notifications) ----------
+# Gson uses generic type information stored in a class file when working with fields. Proguard
+# removes such information by default, so configure it to keep all of it.
+-keepattributes Signature
+
+# For using GSON @Expose annotation
+-keepattributes *Annotation*
+
+# Gson specific classes
+-dontwarn sun.misc.**
+
+# Application classes that will be serialized/deserialized over Gson
+-keep class com.google.gson.examples.android.model.** { <fields>; }
+
+# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
+# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
+-keep class * extends com.google.gson.TypeAdapter
+-keep class * implements com.google.gson.TypeAdapterFactory
+-keep class * implements com.google.gson.JsonSerializer
+-keep class * implements com.google.gson.JsonDeserializer
+
+# Prevent R8 from leaving Data object members always null
+-keepclassmembers,allowobfuscation class * {
+ @com.google.gson.annotations.SerializedName <fields>;
+}
+
+# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
+-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
+
+##---------------End: proguard configuration for Gson (Needed for flutter_local_notifications) ----------
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/chat/fluffy/fluffychat/FcmPushService.kt b/android/app/src/main/kotlin/chat/fluffy/fluffychat/FcmPushService.kt
index d9930f55..510e9845 100644
--- a/android/app/src/main/kotlin/chat/fluffy/fluffychat/FcmPushService.kt
+++ b/android/app/src/main/kotlin/chat/fluffy/fluffychat/FcmPushService.kt
@@ -1,4 +1,4 @@
-/*package chat.fluffy.fluffychat
+package chat.fluffy.fluffychat
import com.famedly.fcm_shared_isolate.FcmSharedIsolateService
@@ -33,4 +33,3 @@ class FcmPushService : FcmSharedIsolateService() {
}
}
}
-*/
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt b/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt
index 1afc4606..894d1571 100644
--- a/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt
+++ b/android/app/src/main/kotlin/chat/fluffy/fluffychat/MainActivity.kt
@@ -7,13 +7,11 @@ import android.content.Context
import androidx.multidex.MultiDex
class MainActivity : FlutterActivity() {
-
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
-
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return provideEngine(this)
}
diff --git a/android/build.gradle b/android/build.gradle
index 85aa8647..3b7e09e7 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -8,7 +8,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- //classpath 'com.google.gms:google-services:4.3.8'
+ classpath 'com.google.gms:google-services:4.3.8'
}
}
diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart
index cd79b0ab..c2db0f1e 100644
--- a/lib/utils/background_push.dart
+++ b/lib/utils/background_push.dart
@@ -39,7 +39,7 @@ import '../config/setting_keys.dart';
import 'famedlysdk_store.dart';
import 'platform_infos.dart';
-//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
+import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
class NoTokenException implements Exception {
String get cause => 'Cannot get firebase token';
@@ -65,7 +65,7 @@ class BackgroundPush {
final pendingTests = <String, Completer<void>>{};
- final dynamic firebase = null; //FcmSharedIsolate();
+ final dynamic firebase = FcmSharedIsolate();
DateTime? lastReceivedPush;
diff --git a/pubspec.yaml b/pubspec.yaml
index 6999d0b8..b2c9144f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -25,7 +25,7 @@ dependencies:
email_validator: ^2.0.1
emoji_picker_flutter: ^1.1.2
encrypt: ^5.0.1
- #fcm_shared_isolate: ^0.1.0
+ fcm_shared_isolate: ^0.1.0
file_picker_cross: ^4.6.0
flutter:
sdk: flutter

View File

@ -1,5 +1,4 @@
#!/usr/bin/env bash
git apply ./scripts/enable-android-google-services.patch
cd android
echo $FDROID_KEY | base64 --decode --ignore-garbage > key.jks
echo "storePassword=${FDROID_KEY_PASS}" >> key.properties

View File

@ -1,5 +1,4 @@
#!/bin/sh -ve
git apply ./scripts/enable-android-google-services.patch
flutter clean
flutter pub get
cd ios