More platform-independent approach to managing registered state

This commit is contained in:
Henry Hiles 2026-09-22 14:34:08 -04:00
commit 41806be9bf
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
2 changed files with 49 additions and 42 deletions

View file

@ -15,6 +15,7 @@ import "package:nexus/controllers/rooms.dart";
import "package:nexus/controllers/space_edges.dart"; import "package:nexus/controllers/space_edges.dart";
import "package:nexus/controllers/sync_status.dart"; import "package:nexus/controllers/sync_status.dart";
import "package:nexus/controllers/top_level_spaces.dart"; import "package:nexus/controllers/top_level_spaces.dart";
import "package:nexus/controllers/unified_push.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/models/capabilities.dart"; import "package:nexus/models/capabilities.dart";
@ -344,7 +345,10 @@ class ClientController extends AsyncNotifier<int> {
Future<File> downloadMedia(DownloadMediaRequest request) async => Future<File> downloadMedia(DownloadMediaRequest request) async =>
.new((await _sendCommand("download_media", request.toJson()))["path"]); .new((await _sendCommand("download_media", request.toJson()))["path"]);
Future<void> logout() => _sendCommand("logout"); Future<void> logout() async {
await ref.watch(UnifiedPushController.provider.notifier).deregister();
await _sendCommand("logout");
}
Future<void> markRead(Room room) async { Future<void> markRead(Room room) async {
final eventRowId = room.timeline[room.timeline.keys.reduce(max)]; final eventRowId = room.timeline[room.timeline.keys.reduce(max)];

View file

@ -32,11 +32,11 @@ class UnifiedPushController extends AsyncNotifier<bool> {
onNewEndpoint: (endpoint, instance) async { onNewEndpoint: (endpoint, instance) async {
final pushKey = endpoint.pubKeySet!.pubKey; final pushKey = endpoint.pubKeySet!.pubKey;
await ref await ref
.watch(PushKeyController.provider(instance).notifier) .read(PushKeyController.provider(instance).notifier)
.set(pushKey); .set(pushKey);
await ref await ref
.watch(ClientController.provider.notifier) .read(ClientController.provider.notifier)
.registerPusher( .registerPusher(
.new( .new(
appDisplayName: "Nexus", appDisplayName: "Nexus",
@ -52,6 +52,8 @@ class UnifiedPushController extends AsyncNotifier<bool> {
pushKey: pushKey, pushKey: pushKey,
), ),
); );
state = .data(true);
}, },
onMessage: (message, instance) async { onMessage: (message, instance) async {
debugPrint("UP message received for $instance"); debugPrint("UP message received for $instance");
@ -61,14 +63,14 @@ class UnifiedPushController extends AsyncNotifier<bool> {
); );
} }
final event = await ref final event = await ref
.watch(ClientController.provider.notifier) .read(ClientController.provider.notifier)
.handlePush(json.decode(String.fromCharCodes(message.content))); .handlePush(json.decode(String.fromCharCodes(message.content)));
if (event == null || if (event == null ||
event.unreadType?.shouldNotify() != true || event.unreadType?.shouldNotify() != true ||
(!isInBackground && (!isInBackground &&
await windowManager.isFocused().onError((_, _) => false) && await windowManager.isFocused().onError((_, _) => false) &&
await ref.watch( await ref.read(
KeyController.provider(KeyController.roomKey).future, KeyController.provider(KeyController.roomKey).future,
) == ) ==
event.roomId)) { event.roomId)) {
@ -103,13 +105,7 @@ class UnifiedPushController extends AsyncNotifier<bool> {
if (isInBackground) exit(0); if (isInBackground) exit(0);
}, },
onRegistrationFailed: (error, instance) => throw error, onUnregistered: deregister,
onUnregistered: (instance) async {
await ref
.watch(PushKeyController.provider(instance).notifier)
.set(null);
ref.invalidateSelf();
},
); );
if (registered) { if (registered) {
@ -121,53 +117,60 @@ class UnifiedPushController extends AsyncNotifier<bool> {
} }
Future<void> register([bool alreadyRegistered = false]) async { Future<void> register([bool alreadyRegistered = false]) async {
final clientStateProvider = ClientStateController.provider; state = .loading();
while (ref.watch(clientStateProvider)?.deviceId == null) { try {
await Future.delayed(.new(milliseconds: 250)); final clientStateProvider = ClientStateController.provider;
} while (ref.read(clientStateProvider)?.deviceId == null) {
final clientState = ref.watch(clientStateProvider); await Future.delayed(.new(milliseconds: 250));
}
final clientState = ref.read(clientStateProvider);
final capabilities = await ref final capabilities = await ref
.watch(ClientController.provider.notifier) .read(ClientController.provider.notifier)
.getCapabilities(); .getCapabilities();
if (capabilities.webpush?.vapid == null) { if (capabilities.webpush?.vapid == null) {
throw UnsupportedError( throw UnsupportedError(
"Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.", "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.",
);
}
if (!alreadyRegistered &&
!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) {
throw Exception("No UnifiedPush distributors found");
}
await UnifiedPush.register(
instance: clientState!.deviceId!,
vapid: capabilities.webpush?.vapid,
); );
} catch (_) {
state = .data(false);
rethrow;
} }
if (!alreadyRegistered &&
!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) {
throw Exception("No UnifiedPush distributors found");
}
await UnifiedPush.register(
instance: clientState!.deviceId!,
vapid: capabilities.webpush?.vapid,
);
if (!alreadyRegistered) ref.invalidateSelf();
} }
Future<void> deregister() async { Future<void> deregister([String? instance]) async {
final clientState = ref.watch(ClientStateController.provider); final clientState = ref.read(ClientStateController.provider);
final key = await ref.watch(
PushKeyController.provider(clientState!.deviceId!).future, final keyProvider = PushKeyController.provider(
instance ?? clientState!.deviceId!,
); );
final key = await ref.read(keyProvider.future);
if (key != null) { if (key != null) {
await ref await ref
.watch(ClientController.provider.notifier) .read(ClientController.provider.notifier)
.deregisterPusher(.new(appId: "nexus.federated.nexus", pushKey: key)); .deregisterPusher(.new(appId: "nexus.federated.nexus", pushKey: key));
await ref.read(keyProvider.notifier).set(null);
} else { } else {
debugPrint( debugPrint(
"No matching pushKey found. Skipping deregistration from homeserver.", "No matching pushKey found. Skipping deregistration from homeserver.",
); );
} }
await UnifiedPush.unregister(clientState.deviceId!); await UnifiedPush.unregister(instance ?? clientState!.deviceId!);
ref.invalidateSelf(); state = .data(false);
} }
static final provider = AsyncNotifierProvider<UnifiedPushController, bool>( static final provider = AsyncNotifierProvider<UnifiedPushController, bool>(