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[UnifiedPush]: notifications for multiple accounts #1443

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void main() async {

// In the background fetch mode we do not want to waste ressources with
// starting the Flutter engine but process incoming push notifications.
BackgroundPush.clientOnly(clients.first);
BackgroundPush.clientsOnly(clients);
// To start the flutter engine afterwards we add an custom observer.
WidgetsBinding.instance.addObserver(AppStarter(clients, store));
Logs().i(
Expand Down
4 changes: 3 additions & 1 deletion lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ class ChatController extends State<ChatPageWithRoom>
_setReadMarkerFuture = null;
});
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
Matrix.of(context)
.backgroundPush
?.cancelNotification(Matrix.of(context).client, roomId);
}
}

Expand Down
4 changes: 3 additions & 1 deletion lib/pages/chat_list/chat_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,9 @@ class ChatListController extends State<ChatList>
if (mounted) {
searchServer =
Matrix.of(context).store.getString(_serverStoreNamespace);
Matrix.of(context).backgroundPush?.setupPush();
Matrix.of(context)
.backgroundPush
?.setupPush(Matrix.of(context).widget.clients);
UpdateNotifier.showUpdateSnackBar(context);
}

Expand Down
156 changes: 118 additions & 38 deletions lib/utils/background_push.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BackgroundPush {
static BackgroundPush? _instance;
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Client client;
List<Client> clients;
MatrixState? matrix;
String? _fcmToken;
void Function(String errorMsg, {Uri? link})? onFcmError;
Expand Down Expand Up @@ -81,49 +81,53 @@ class BackgroundPush {
);
Logs().v('Flutter Local Notifications initialized');
firebase?.setListeners(
onMessage: (message) => pushHelper(
onMessage: (message) => PushHelper.pushHelper(
PushNotification.fromJson(
Map<String, dynamic>.from(message['data'] ?? message),
),
client: client,
clients: clients,
//TODO: figure out if firebase supports
// multiple instances
instance: clients.first.clientName,
l10n: l10n,
activeRoomId: matrix?.activeRoomId,
activeClient: matrix?.client,
flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin,
),
);
if (Platform.isAndroid) {
await UnifiedPush.initialize(
onNewEndpoint: _newUpEndpoint,
onRegistrationFailed: _upUnregistered,
onUnregistered: _upUnregistered,
onMessage: _onUpMessage,
onNewEndpoint: _newUPEndpoint,
onRegistrationFailed: _onUPUnregistered,
onUnregistered: _onUPUnregistered,
onMessage: _onUPMessage,
);
}
} catch (e, s) {
Logs().e('Unable to initialize Flutter local notifications', e, s);
}
}

BackgroundPush._(this.client) {
BackgroundPush._(this.clients) {
_init();
}

factory BackgroundPush.clientOnly(Client client) {
return _instance ??= BackgroundPush._(client);
factory BackgroundPush.clientsOnly(List<Client> clients) {
return _instance ??= BackgroundPush._(clients);
}

factory BackgroundPush(
MatrixState matrix, {
final void Function(String errorMsg, {Uri? link})? onFcmError,
}) {
final instance = BackgroundPush.clientOnly(matrix.client);
final instance = BackgroundPush.clientsOnly(matrix.widget.clients);
instance.matrix = matrix;
// ignore: prefer_initializing_formals
instance.onFcmError = onFcmError;
return instance;
}

Future<void> cancelNotification(String roomId) async {
Future<void> cancelNotification(Client client, String roomId) async {
Logs().v('Cancel notification for room', roomId);
await _flutterLocalNotificationsPlugin.cancel(roomId.hashCode);

Expand All @@ -146,6 +150,7 @@ class BackgroundPush {
String? token,
Set<String?>? oldTokens,
bool useDeviceSpecificAppId = false,
required Client client,
}) async {
if (PlatformInfos.isIOS) {
await firebase?.requestPermission();
Expand Down Expand Up @@ -192,7 +197,7 @@ class BackgroundPush {
)) {
Logs().i('[Push] Pusher already set');
} else {
Logs().i('Need to set new pusher');
Logs().i('[Push] Need to set new pusher');
oldTokens.add(token);
if (client.isLogged()) {
setNewPusher = true;
Expand Down Expand Up @@ -246,9 +251,32 @@ class BackgroundPush {

static bool _wentToRoomOnStartup = false;

Future<void> setupPush() async {
Future<void> setupPush(List<Client> clients) async {
Logs().d("SetupPush");
if (client.onLoginStateChanged.value != LoginState.loggedIn ||

{
// migrate single client push settings to multiclient settings
final endpoint = matrix!.store.getString(SettingKeys.unifiedPushEndpoint);
if (endpoint != null) {
matrix!.store.setString(
clients.first.clientName + SettingKeys.unifiedPushEndpoint,
endpoint,
);
matrix!.store.remove(SettingKeys.unifiedPushEndpoint);
}

final registered =
matrix!.store.getBool(SettingKeys.unifiedPushRegistered);
if (registered != null) {
matrix!.store.setBool(
clients.first.clientName + SettingKeys.unifiedPushRegistered,
registered,
);
matrix!.store.remove(SettingKeys.unifiedPushRegistered);
}
}

if (clients.first.onLoginStateChanged.value != LoginState.loggedIn ||
!PlatformInfos.isMobile ||
matrix == null) {
return;
Expand All @@ -260,7 +288,7 @@ class BackgroundPush {
}
if (!PlatformInfos.isIOS &&
(await UnifiedPush.getDistributors()).isNotEmpty) {
await setupUp();
await setupUP(clients);
} else {
await setupFirebase();
}
Expand Down Expand Up @@ -316,42 +344,66 @@ class BackgroundPush {
await setupPusher(
gatewayUrl: AppConfig.pushNotificationsGatewayUrl,
token: _fcmToken,
client: clients.first, // Workaround: see todo in _init
);
}

Future<void> goToRoom(NotificationResponse? response) async {
try {
final roomId = response?.payload;
Logs().v('[Push] Attempting to go to room $roomId...');
if (roomId == null) {
final payloadEncoded = response?.payload;
if (payloadEncoded == null || payloadEncoded.isEmpty) {
return;
}
final payload =
NotificationResponsePayload.fromJson(jsonDecode(payloadEncoded));
if (payload.roomId.isEmpty) {
return;
}
Logs().v('[Push] Attempting to go to room ${payload.roomId}...');

Client? client;
for (final c in clients) {
if (c.clientName == payload.clientName) {
client = c;
break;
}
}
if (client == null) {
Logs()
.w('[Push] No client could be found for room ${payload.roomId}...');
return;
}
if (matrix!.client != client) {
matrix!.setActiveClient(client);
}

await client.roomsLoading;
await client.accountDataLoading;
if (client.getRoomById(roomId) == null) {
if (client.getRoomById(payload.roomId) == null) {
await client
.waitForRoomInSync(roomId)
.waitForRoomInSync(payload.roomId)
.timeout(const Duration(seconds: 30));
}
FluffyChatApp.router.go(
client.getRoomById(roomId)?.membership == Membership.invite
client.getRoomById(payload.roomId)?.membership == Membership.invite
? '/rooms'
: '/rooms/$roomId',
: '/rooms/${payload.roomId}',
);
} catch (e, s) {
Logs().e('[Push] Failed to open room', e, s);
}
}

Future<void> setupUp() async {
await UnifiedPushUi(matrix!.context, ["default"], UPFunctions())
Future<void> setupUP(List<Client> clients) async {
final names = clients.map((c) => c.clientName);
await UnifiedPushUi(matrix!.context, List.from(names), UPFunctions())
.registerAppWithDialog();
}

Future<void> _newUpEndpoint(String newEndpoint, String i) async {
Future<void> _newUPEndpoint(String newEndpoint, String instance) async {
upAction = true;
if (newEndpoint.isEmpty) {
await _upUnregistered(i);
await _onUPUnregistered(instance);
return;
}
var endpoint =
Expand All @@ -377,48 +429,67 @@ class BackgroundPush {
'[Push] No self-hosted unified push gateway present: $newEndpoint',
);
}
Logs().i('[Push] UnifiedPush using endpoint $endpoint');
Logs().i('[Push] UnifiedPush $instance using endpoint $endpoint');
final oldTokens = <String?>{};
try {
final fcmToken = await firebase?.getToken();
oldTokens.add(fcmToken);
} catch (_) {}
final client = clientFromInstance(instance, clients);
if (client == null) {
Logs().e("Not client found for $instance");
return;
}
await setupPusher(
gatewayUrl: endpoint,
token: newEndpoint,
oldTokens: oldTokens,
useDeviceSpecificAppId: true,
client: client,
);
await matrix?.store.setString(
client.clientName + SettingKeys.unifiedPushEndpoint,
newEndpoint,
);
await matrix?.store.setString(SettingKeys.unifiedPushEndpoint, newEndpoint);
await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, true);
await matrix?.store
.setBool(client.clientName + SettingKeys.unifiedPushRegistered, true);
}

Future<void> _upUnregistered(String i) async {
Future<void> _onUPUnregistered(String instance) async {
upAction = true;
final client = clientFromInstance(instance, clients);
if (client == null) {
return;
}
Logs().i('[Push] Removing UnifiedPush endpoint...');
final oldEndpoint =
matrix?.store.getString(SettingKeys.unifiedPushEndpoint);
await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, false);
await matrix?.store.remove(SettingKeys.unifiedPushEndpoint);
final oldEndpoint = matrix?.store
.getString(client.clientName + SettingKeys.unifiedPushEndpoint);
await matrix?.store
.setBool(client.clientName + SettingKeys.unifiedPushRegistered, false);
await matrix?.store
.remove(client.clientName + SettingKeys.unifiedPushEndpoint);
if (oldEndpoint?.isNotEmpty ?? false) {
// remove the old pusher
await setupPusher(
oldTokens: {oldEndpoint},
client: client,
);
}
}

Future<void> _onUpMessage(Uint8List message, String i) async {
Future<void> _onUPMessage(Uint8List message, String instance) 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(
await PushHelper.pushHelper(
PushNotification.fromJson(data),
client: client,
clients: clients,
instance: instance,
l10n: l10n,
activeClient: matrix?.client,
activeRoomId: matrix?.activeRoomId,
flutterLocalNotificationsPlugin: _flutterLocalNotificationsPlugin,
);
Expand Down Expand Up @@ -447,3 +518,12 @@ class UPFunctions extends UnifiedPushFunctions {
await UnifiedPush.saveDistributor(distributor);
}
}

Client? clientFromInstance(String? instance, List<Client> clients) {
for (final c in clients) {
if (c.clientName == instance) {
return c;
}
}
return null;
}
Loading
Loading