-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(neon_framework): Migrate to notifications_push_repository
Signed-off-by: provokateurin <[email protected]>
- Loading branch information
1 parent
0f0a799
commit d3ef216
Showing
27 changed files
with
143 additions
and
533 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 21 additions & 106 deletions
127
packages/neon_framework/lib/src/blocs/push_notifications.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,145 +1,60 @@ | ||
import 'dart:async'; | ||
import 'dart:convert'; | ||
|
||
import 'package:built_collection/built_collection.dart'; | ||
import 'package:logging/logging.dart'; | ||
import 'package:meta/meta.dart'; | ||
import 'package:neon_framework/models.dart'; | ||
import 'package:neon_framework/src/bloc/bloc.dart'; | ||
import 'package:neon_framework/src/platform/platform.dart'; | ||
import 'package:neon_framework/src/storage/keys.dart'; | ||
import 'package:neon_framework/src/utils/findable.dart'; | ||
import 'package:neon_framework/src/utils/global_options.dart'; | ||
import 'package:neon_framework/src/utils/push_utils.dart'; | ||
import 'package:neon_framework/storage.dart'; | ||
import 'package:nextcloud/notifications.dart' as notifications; | ||
import 'package:rxdart/rxdart.dart'; | ||
import 'package:unifiedpush/unifiedpush.dart'; | ||
import 'package:notifications_push_repository/notifications_push_repository.dart'; | ||
import 'package:permission_handler/permission_handler.dart'; | ||
|
||
/// Bloc for managing push notifications and registration. | ||
/// Bloc for managing push subscriptions. | ||
sealed class PushNotificationsBloc { | ||
@internal | ||
factory PushNotificationsBloc({ | ||
required BehaviorSubject<BuiltList<Account>> accountsSubject, | ||
required GlobalOptions globalOptions, | ||
required NotificationsPushRepository notificationsPushRepository, | ||
}) = _PushNotificationsBloc; | ||
} | ||
|
||
class _PushNotificationsBloc extends Bloc implements PushNotificationsBloc { | ||
_PushNotificationsBloc({ | ||
required this.accountsSubject, | ||
required this.globalOptions, | ||
required this.notificationsPushRepository, | ||
}) { | ||
if (NeonPlatform.instance.canUsePushNotifications) { | ||
unawaited(UnifiedPush.getDistributors().then(globalOptions.updateDistributors)); | ||
|
||
globalOptions.pushNotificationsEnabled.addListener(pushNotificationsEnabledListener); | ||
// Call the listener to update everything | ||
unawaited(pushNotificationsEnabledListener()); | ||
unawaited(changeDistributor()); | ||
globalOptions.pushNotificationsDistributor.addListener(changeDistributor); | ||
} | ||
} | ||
|
||
@override | ||
final log = Logger('PushNotificationsBloc'); | ||
|
||
final BehaviorSubject<BuiltList<Account>> accountsSubject; | ||
late final storage = NeonStorage().settingsStore(StorageKeys.lastEndpoint); | ||
final GlobalOptions globalOptions; | ||
|
||
StreamSubscription<BuiltList<Account>>? accountsListener; | ||
final NotificationsPushRepository notificationsPushRepository; | ||
String? oldDistributor; | ||
|
||
@override | ||
void dispose() { | ||
unawaited(accountsListener?.cancel()); | ||
globalOptions.pushNotificationsEnabled.removeListener(pushNotificationsEnabledListener); | ||
globalOptions.pushNotificationsDistributor.removeListener(changeDistributor); | ||
} | ||
|
||
Future<void> pushNotificationsEnabledListener() async { | ||
if (globalOptions.pushNotificationsEnabled.value) { | ||
await setupUnifiedPush(); | ||
|
||
globalOptions.pushNotificationsDistributor.addListener(distributorListener); | ||
accountsListener = accountsSubject.listen(registerUnifiedPushInstances); | ||
} else { | ||
globalOptions.pushNotificationsDistributor.removeListener(distributorListener); | ||
unawaited(accountsListener?.cancel()); | ||
Future<void> changeDistributor() async { | ||
final newDistributor = globalOptions.pushNotificationsDistributor.value; | ||
if (newDistributor == oldDistributor) { | ||
return; | ||
} | ||
} | ||
|
||
Future<void> setupUnifiedPush() async { | ||
// We just use a single RSA keypair for all accounts | ||
final keypair = PushUtils.loadRSAKeypair(); | ||
|
||
await UnifiedPush.initialize( | ||
onNewEndpoint: (endpoint, instance) async { | ||
final account = accountsSubject.value.tryFind(instance); | ||
if (account == null) { | ||
log.fine('Account for $instance not found, can not process endpoint'); | ||
return; | ||
} | ||
|
||
if (storage.getString(account.id) == endpoint) { | ||
log.fine('Endpoint not changed'); | ||
return; | ||
} | ||
|
||
log.fine('Registering account $instance for push notifications on $endpoint'); | ||
|
||
final subscription = await account.client.notifications.push.registerDevice( | ||
$body: notifications.PushRegisterDeviceRequestApplicationJson( | ||
(b) => b | ||
..pushTokenHash = notifications.generatePushTokenHash(endpoint) | ||
..devicePublicKey = keypair.publicKey.toFormattedPEM() | ||
..proxyServer = '$endpoint#', // This is a hack to make the Nextcloud server directly push to the endpoint | ||
), | ||
); | ||
oldDistributor = newDistributor; | ||
|
||
await storage.setString(account.id, endpoint); | ||
final response = await Permission.notification.request(); | ||
if (!response.isGranted) { | ||
log.fine('Notifications permission denied, disabling push notifications'); | ||
|
||
log.fine( | ||
'Account $instance registered for push notifications ${json.encode(subscription.body.ocs.data.toJson())}', | ||
); | ||
}, | ||
onMessage: PushUtils.onMessage, | ||
); | ||
} | ||
|
||
Future<void> distributorListener() async { | ||
final distributor = globalOptions.pushNotificationsDistributor.value; | ||
final disabled = distributor == null; | ||
final sameDistributor = distributor == await UnifiedPush.getDistributor(); | ||
final accounts = accountsSubject.value; | ||
if (disabled || !sameDistributor) { | ||
await unregisterUnifiedPushInstances(accounts); | ||
} | ||
if (!disabled && !sameDistributor) { | ||
log.fine('UnifiedPush distributor changed to $distributor'); | ||
await UnifiedPush.saveDistributor(distributor); | ||
globalOptions.pushNotificationsDistributor.reset(); | ||
return; | ||
} | ||
if (!disabled) { | ||
await registerUnifiedPushInstances(accounts); | ||
} | ||
} | ||
|
||
Future<void> unregisterUnifiedPushInstances(BuiltList<Account> accounts) async { | ||
for (final account in accounts) { | ||
try { | ||
await account.client.notifications.push.removeDevice(); | ||
await UnifiedPush.unregister(account.id); | ||
await storage.remove(account.id); | ||
} on Exception catch (error) { | ||
log.warning( | ||
'Failed to unregister device.', | ||
error, | ||
); | ||
} | ||
} | ||
} | ||
|
||
Future<void> registerUnifiedPushInstances(BuiltList<Account> accounts) async { | ||
// Notifications will only work on accounts with app password | ||
for (final account in accounts.where((a) => a.password != null)) { | ||
await UnifiedPush.registerApp(account.id); | ||
} | ||
await notificationsPushRepository.changeDistributor(newDistributor); | ||
} | ||
} |
Oops, something went wrong.