A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

147 lines
4.7 KiB

part of '../neon.dart';
abstract class PushNotificationsBlocEvents {}
abstract class PushNotificationsBlocStates {
Stream<NextcloudPushNotification> get notifications;
}
class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, PushNotificationsBlocStates {
PushNotificationsBloc(
this._accountsBloc,
this._sharedPreferences,
this._globalOptions,
this._env,
this._platform,
) {
if (_platform.canUsePushNotifications) {
unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors));
_globalOptions.pushNotificationsEnabled.stream.listen((final enabled) async {
if (enabled != _pushNotificationsEnabled) {
_pushNotificationsEnabled = enabled;
if (enabled) {
// We just use a single RSA keypair for all accounts
_keypair = await PushUtils.loadRSAKeypair(_storage);
await _setupUnifiedPush();
}
}
});
}
}
final AccountsBloc _accountsBloc;
final NeonPlatform _platform;
final SharedPreferences _sharedPreferences;
late final _storage = AppStorage('notifications', _sharedPreferences);
final GlobalOptions _globalOptions;
final Env? _env;
late RSAKeypair _keypair;
bool? _pushNotificationsEnabled;
final _notificationsController = StreamController<NextcloudPushNotification>();
@override
void dispose() {
unawaited(_notificationsController.close());
}
@override
late Stream<NextcloudPushNotification> notifications = _notificationsController.stream.asBroadcastStream();
String _keyLastEndpoint(final Account account) => 'last-endpoint-${account.id}';
Future _setupUnifiedPush() async {
await UnifiedPush.initialize(
onNewEndpoint: (final endpoint, final instance) async {
Account? account;
for (final a in _accountsBloc.accounts.value) {
if (a.id == instance) {
account = a;
break;
}
}
if (account == null) {
debugPrint('Account for $instance not found, can not process endpoint');
return;
}
if (_storage.getString(_keyLastEndpoint(account)) == endpoint) {
debugPrint('Endpoint not changed');
return;
}
debugPrint('Registering account $instance for push notifications on $endpoint');
var proxyServerForNextcloud = 'https://nc.proxy.neon.provokateurin.de/';
var proxyServerForClient = proxyServerForNextcloud;
if (_env != null) {
proxyServerForNextcloud = 'http://host.docker.internal:8080/';
proxyServerForClient = 'http://${_env!.testHost}:8080/';
}
final subscription = await account.client.notifications.registerDevice(
pushTokenHash: account.client.notifications.generatePushTokenHash(endpoint),
devicePublicKey: _keypair.publicKey.toFormattedPEM(),
proxyServer: proxyServerForNextcloud,
);
await account.client.notifications.registerDeviceAtPushProxy(
endpoint,
subscription.ocs.data,
proxyServerForClient,
);
await _storage.setString(_keyLastEndpoint(account), endpoint);
debugPrint('Account $instance registered for push notifications');
},
onMessage: PushUtils.onMessage,
);
_globalOptions.pushNotificationsDistributor.stream.listen((final distributor) async {
final disabled = distributor == null;
final sameDistributor = distributor == await UnifiedPush.getDistributor();
final accounts = _accountsBloc.accounts.value;
if (disabled || !sameDistributor) {
await _unregisterUnifiedPushInstances(accounts);
}
if (!disabled && !sameDistributor) {
debugPrint('UnifiedPush distributor changed to $distributor');
await UnifiedPush.saveDistributor(distributor);
}
if (!disabled) {
await _registerUnifiedPushInstances(accounts);
}
});
_accountsBloc.accounts.listen(_registerUnifiedPushInstances);
}
Future _unregisterUnifiedPushInstances(final List<Account> accounts) async {
for (final account in accounts) {
await UnifiedPush.unregister(account.client.id);
}
}
Future _registerUnifiedPushInstances(final List<Account> accounts) async {
// Notifications will only work on accounts with app password
for (final account in accounts.where((final a) => a.password != null)) {
await UnifiedPush.registerApp(account.client.id);
}
}
}
class NextcloudPushNotification {
NextcloudPushNotification({
required this.instance,
required this.priority,
required this.type,
required this.subject,
});
final String instance;
final String priority;
final String type;
final NotificationsPushNotificationDecryptedSubject subject;
}