From 3c6c610f7c0b094b3746cd9305c46b3c7dac768a Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Mon, 5 Oct 2026 17:00:56 -0400 Subject: [PATCH] improve UP timeout behavior Hopefully more reliable. --- lib/controllers/unified_push.dart | 134 ++++++++++++++-------- lib/controllers/unified_push_allowed.dart | 6 +- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/lib/controllers/unified_push.dart b/lib/controllers/unified_push.dart index e9ecf16..8e24d16 100644 --- a/lib/controllers/unified_push.dart +++ b/lib/controllers/unified_push.dart @@ -18,6 +18,8 @@ import "package:unifiedpush_storage_shared_preferences/storage.dart"; import "package:window_manager/window_manager.dart"; class UnifiedPushController extends AsyncNotifier { + Completer? _endpoint; + @override Future build() async { if (!Platform.isLinux && !Platform.isAndroid) return false; @@ -30,33 +32,45 @@ class UnifiedPushController extends AsyncNotifier { shouldWriteService: false, ), onNewEndpoint: (endpoint, instance) async { - final pushKey = endpoint.pubKeySet!.pubKey; - await ref - .read(PushKeyController.provider(instance).notifier) - .set(pushKey); + final task = Future(() async { + final pushKey = endpoint.pubKeySet!.pubKey; + await ref + .read(PushKeyController.provider(instance).notifier) + .set(pushKey); - await ref - .read(ClientController.provider.notifier) - .registerPusher( - .new( - appDisplayName: "Nexus", - appId: "nexus.federated.nexus", - data: .webPush( - url: .parse(endpoint.url), - auth: endpoint.pubKeySet!.auth, + await ref + .read(ClientController.provider.notifier) + .registerPusher( + .new( + appDisplayName: "Nexus", + appId: "nexus.federated.nexus", + data: .webPush( + url: .parse(endpoint.url), + auth: endpoint.pubKeySet!.auth, + ), + deviceDisplayName: + "Nexus on ${toBeginningOfSentenceCase(Platform.operatingSystem)}", + kind: .webPush, + lang: "en", + pushKey: pushKey, ), - deviceDisplayName: - "Nexus on ${toBeginningOfSentenceCase(Platform.operatingSystem)}", - kind: .webPush, - lang: "en", - pushKey: pushKey, - ), - ); + ); - state = .data(true); + state = .data(true); + }); + + if (_endpoint case final completer? when !completer.isCompleted) { + completer.complete(task); + } else { + await task; + } }, onRegistrationFailed: (reason, instance) { - throw reason; + if (_endpoint case final completer? when !completer.isCompleted) { + completer.completeError(reason); + } else { + throw reason; + } }, onMessage: (message, instance) async { debugPrint("UP message received for $instance"); @@ -111,48 +125,68 @@ class UnifiedPushController extends AsyncNotifier { onUnregistered: deregister, ); - ref.listen( - ClientStateController.provider.select((value) => value?.deviceId), - (_, _) => register(), - ); + if (!registered) return false; - if (registered) { - // Needs to be registered every startup - await register(); + try { + return await register(); + } catch (error, stackTrace) { + showError(error, stackTrace); + return false; } - - return registered; } - Future register() async { + Future register() async { state = .loading(); try { - final deviceId = ref.read( + final deviceIdCompleter = Completer(); + final subscription = ref.listen( ClientStateController.provider.select((value) => value?.deviceId), + (_, next) { + if (next != null && !deviceIdCompleter.isCompleted) { + deviceIdCompleter.complete(next); + } + }, + fireImmediately: true, + ); + final deviceId = await deviceIdCompleter.future.whenComplete( + subscription.close, ); - if (deviceId == null) return; - final capabilities = await ref - .read(ClientController.provider.notifier) - .getCapabilities(); + await Future(() async { + final capabilities = await ref + .read(ClientController.provider.notifier) + .getCapabilities(); - if (capabilities.webpush?.vapid == null) { - throw UnsupportedError( - "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.", - ); - } + if (capabilities.webpush?.vapid == null) { + throw UnsupportedError( + "Your homeserver does not support MSC4174 (Web Push), and therefore cannot send notifications to Nexus.", + ); + } - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { - throw Exception("No UnifiedPush distributors found."); - } + if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { + throw Exception("No UnifiedPush distributors found."); + } - await UnifiedPush.register( - instance: deviceId, - vapid: capabilities.webpush?.vapid, - ).timeout( - .new(seconds: 15), + final endpoint = _endpoint = Completer(); + + try { + await Future.wait([ + UnifiedPush.register( + instance: deviceId, + vapid: capabilities.webpush?.vapid, + ), + endpoint.future, + ], eagerError: true); + } finally { + if (_endpoint == endpoint) _endpoint = null; + } + }).timeout( + .new(seconds: 10), onTimeout: () => throw Exception("UnifiedPush registration timed out."), ); + + state = .data(true); + return true; } catch (_) { state = .data(false); rethrow; diff --git a/lib/controllers/unified_push_allowed.dart b/lib/controllers/unified_push_allowed.dart index 8f7fd8e..604a060 100644 --- a/lib/controllers/unified_push_allowed.dart +++ b/lib/controllers/unified_push_allowed.dart @@ -7,7 +7,11 @@ import "package:unifiedpush/unifiedpush.dart"; class UnifiedPushAllowedController extends AsyncNotifier { @override Future build() async { - if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor()) { + if (!await UnifiedPush.tryUseCurrentOrDefaultDistributor().timeout( + .new(seconds: 10), + onTimeout: () => + throw Exception("UnifiedPush distributor check timed out."), + )) { return "No valid distributors found. ${Platform.isLinux ? "Try installing KUnifiedPush" : Platform.isAndroid