mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Ai-assisted: true
161 lines
5.0 KiB
Dart
161 lines
5.0 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import '../community/community.dart';
|
|
import '../community/community_provider.dart';
|
|
import '../relay/relay_provider.dart';
|
|
import '../relay/relay_session.dart';
|
|
import '../relay/signed_event_relay.dart';
|
|
import 'dev_push_lease.dart';
|
|
import 'push_bridge.dart';
|
|
import 'push_relay_capability_provider.dart';
|
|
import 'push_subscription.dart';
|
|
|
|
/// Starts the push lifecycle only after authenticated relay connectivity and a
|
|
/// push-capable NIP-11 descriptor are both present.
|
|
class BuzzPushBootstrap extends HookConsumerWidget {
|
|
const BuzzPushBootstrap({required this.child, super.key});
|
|
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
useListenable(apnsDeviceToken);
|
|
final registrationAttempt = useRef<String?>(null);
|
|
final publicationAttempt = useRef<String?>(null);
|
|
final session = ref.watch(relaySessionProvider);
|
|
final config = ref.watch(relayConfigProvider);
|
|
final community = ref.watch(activeCommunityProvider).value;
|
|
final memberPubkey = ref.watch(myPubkeyProvider);
|
|
final descriptor = ref.watch(currentRelayPushDescriptorProvider).value;
|
|
|
|
useEffect(
|
|
() {
|
|
if (!_ready(session, config, community, memberPubkey) ||
|
|
descriptor == null) {
|
|
return null;
|
|
}
|
|
final attempt = '${community!.id}|${config.baseUrl}';
|
|
if (registrationAttempt.value == attempt) return null;
|
|
registrationAttempt.value = attempt;
|
|
unawaited(() async {
|
|
try {
|
|
await startBuzzPushRegistrationIfCapable(
|
|
descriptor,
|
|
startRegistration: startBuzzPushRegistration,
|
|
);
|
|
} catch (error, stack) {
|
|
registrationAttempt.value = null;
|
|
debugPrint('Push registration bootstrap failed: $error');
|
|
debugPrintStack(stackTrace: stack);
|
|
}
|
|
}());
|
|
return null;
|
|
},
|
|
[session.status, config.baseUrl, community?.id, memberPubkey, descriptor],
|
|
);
|
|
|
|
final token = apnsDeviceToken.value;
|
|
useEffect(
|
|
() {
|
|
if (!_ready(session, config, community, memberPubkey) ||
|
|
descriptor == null ||
|
|
token == null) {
|
|
return null;
|
|
}
|
|
final state = community!.pushSubscriptionState;
|
|
if (state.desired.isEmpty) return null;
|
|
final attempt = [
|
|
community.id,
|
|
config.baseUrl,
|
|
token,
|
|
buzzPushSubscriptionsFingerprint(state.desired),
|
|
].join('|');
|
|
if (publicationAttempt.value == attempt) return null;
|
|
publicationAttempt.value = attempt;
|
|
final relay = SignedEventRelay(
|
|
session: ref.read(relaySessionProvider.notifier),
|
|
nsec: config.nsec!,
|
|
);
|
|
unawaited(
|
|
_publish(ref, config, community, memberPubkey!, relay).catchError((
|
|
Object error,
|
|
StackTrace stack,
|
|
) {
|
|
publicationAttempt.value = null;
|
|
debugPrint('Push lease bootstrap failed: $error');
|
|
debugPrintStack(stackTrace: stack);
|
|
}),
|
|
);
|
|
return null;
|
|
},
|
|
[
|
|
session.status,
|
|
config.baseUrl,
|
|
community?.id,
|
|
community?.pushSubscriptionState,
|
|
memberPubkey,
|
|
descriptor,
|
|
token,
|
|
],
|
|
);
|
|
|
|
return child;
|
|
}
|
|
|
|
static bool _ready(
|
|
SessionState session,
|
|
RelayConfig config,
|
|
Community? community,
|
|
String? memberPubkey,
|
|
) =>
|
|
session.status == SessionStatus.connected &&
|
|
community != null &&
|
|
config.nsec != null &&
|
|
config.nsec!.isNotEmpty &&
|
|
memberPubkey != null &&
|
|
memberPubkey.isNotEmpty;
|
|
|
|
static Future<void> _publish(
|
|
WidgetRef ref,
|
|
RelayConfig config,
|
|
Community community,
|
|
String memberPubkey,
|
|
SignedEventRelay relay,
|
|
) async {
|
|
final state = community.pushSubscriptionState;
|
|
final desired = state.desired;
|
|
final descriptor = await fetchBuzzPushLeaseDescriptor(config.baseUrl);
|
|
final grant = await enrollBuzzPush(
|
|
config.wsUrl,
|
|
Env.pushGatewayUrl,
|
|
communitiesForSnapshotRefresh:
|
|
ref.read(communityListProvider).value ?? [community],
|
|
);
|
|
// Relay lease replacement and gateway delegation are independent state
|
|
// machines. Subscription changes advance only the kind-30350 generation;
|
|
// the opaque grant remains reusable until its own authority changes.
|
|
final leaseGeneration = (state.acceptedGeneration ?? 0) + 1;
|
|
|
|
await publishBuzzDevPushLeaseThroughRelay(
|
|
grant: grant,
|
|
leaseGeneration: leaseGeneration,
|
|
descriptor: descriptor,
|
|
nsec: config.nsec!,
|
|
memberPubkey: memberPubkey,
|
|
subscriptions: desired,
|
|
relay: relay,
|
|
);
|
|
await ref
|
|
.read(communityListProvider.notifier)
|
|
.markPushLeaseAccepted(
|
|
community.id,
|
|
subscriptions: desired,
|
|
generation: leaseGeneration,
|
|
);
|
|
}
|
|
}
|