Files
buzz/mobile/lib/shared/hosted/hosted_communities.dart
Wes 625dfe0d08 fix(mobile): preserve invite onboarding and offer identity setup
Forward iOS cold-start invite URLs, offer explicit identity creation or matching-key import, and discover account-saved communities through the supported API.

Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
2026-10-26 20:54:07 -07:00

88 lines
3.4 KiB
Dart

import '../deeplink/deep_link.dart';
import 'builderlab_account.dart';
import 'hosted_environment.dart';
/// Account-saved destinations, not evidence that this identity is a member.
/// Keep the existing hosted environment boundary and normal relay admission.
Future<List<AddCommunityDeepLink>> loadKnownHostedCommunities(
BuilderlabAccountClient account,
) async {
final response = await account.post('/v1/buzz/known-communities/list', {});
// Protobuf JSON omits an empty repeated field.
final rows = response['communities'] ?? <dynamic>[];
if (response['error'] != null || rows is! List) {
throw AccountFailure.invalidResponse;
}
final communities = <String, AddCommunityDeepLink>{};
for (final row in rows) {
if (row is! Map<String, dynamic> || row['community_url'] is! String) {
throw AccountFailure.invalidResponse;
}
final relay = Uri.tryParse(row['community_url'] as String);
// The account can also save destinations this hosted build cannot enter.
if (relay == null ||
relay.scheme != 'wss' ||
!RegExp(r'^[a-z0-9]+(?:[.-][a-z0-9]+)*$').hasMatch(relay.host) ||
!HostedEnvironment.supportsRelay(relay)) {
continue;
}
final canonical = Uri(scheme: 'wss', host: relay.host);
communities.putIfAbsent(
canonical.host,
() => AddCommunityDeepLink(
relayUrl: canonical.toString(),
name: canonical.host,
),
);
}
return List.unmodifiable(communities.values);
}
/// Validated, active communities joined by the signed-in Builderlab identity.
/// Discovery never grants admission; each target still goes through HostedAdmission.
Future<List<AddCommunityDeepLink>> loadHostedCommunities(
BuilderlabAccountClient account,
) async {
final response = await account.post('/v1/buzz/communities/joined', {});
final rows = response['communities'];
if (response['error'] != null || rows is! List) {
throw AccountFailure.invalidResponse;
}
final communities = <String, AddCommunityDeepLink>{};
for (final row in rows) {
if (row is! Map<String, dynamic>) throw AccountFailure.invalidResponse;
final archived = row['archived_at'];
if (archived != null) {
if (archived is! String || archived.isEmpty) {
throw AccountFailure.invalidResponse;
}
continue;
}
final host = row['normalized_host'];
final name = row['name'];
if (host is! String || name is! String || name.trim().isEmpty) {
throw AccountFailure.invalidResponse;
}
final relay = Uri.tryParse(host.contains('://') ? host : 'wss://$host');
if (relay == null ||
relay.scheme != 'wss' ||
!RegExp(r'^[a-z0-9]+(?:[.-][a-z0-9]+)*$').hasMatch(relay.host) ||
relay.userInfo.isNotEmpty ||
relay.hasQuery ||
relay.hasFragment ||
(relay.path.isNotEmpty && relay.path != '/') ||
(relay.hasPort && relay.port != 443)) {
throw AccountFailure.invalidResponse;
}
// A hosted listing can contain communities this build cannot enter.
// Never redirect the account session or discard otherwise usable entries.
if (!HostedEnvironment.supportsRelay(relay)) continue;
final canonical = Uri(scheme: 'wss', host: relay.host);
communities.putIfAbsent(
canonical.host,
() => AddCommunityDeepLink(relayUrl: canonical.toString(), name: name),
);
}
return List.unmodifiable(communities.values);
}