browseDirectory method

Future<DirectoryBrowseResult?> browseDirectory({
  1. String? cursor,
  2. int limit = 20,
  3. String? search,
  4. PeerId? fromServer,
})

Browse the user directory on a server

cursor Opaque cursor from a previous page (for pagination) limit Maximum entries per page (default 20, max 100) search Optional search string (matches display name, bio) fromServer Optional: specify which server to browse

Implementation

Future<DirectoryBrowseResult?> browseDirectory({
  String? cursor,
  int limit = 20,
  String? search,
  PeerId? fromServer,
}) async {
  final serverId = fromServer ??
      await _serverSelector.selectServer(config.preferredServers);
  if (serverId == null) {
    _logger.warning('No S&F server available for directory browse');
    return null;
  }

  try {
    final context = Context();
    final stream = await host.newStream(
      serverId,
      [DocumentHandler.protocolId],
      context,
    ).timeout(config.connectionTimeout);

    final response = await DocumentHandler.browseDirectory(
      stream,
      ownerPeerId: host.id,
      cursor: cursor,
      limit: limit,
      search: search,
    ).timeout(config.messageTimeout);

    await stream.close();

    if (response.status != 200) {
      _logger.warning('DIRECTORY browse failed: ${response.status}');
      return null;
    }

    final rawBody = utf8.decode(response.body!);
    _logger.info('DIRECTORY browse raw response: $rawBody');

    final body = jsonDecode(rawBody) as Map<String, dynamic>;
    final entries = ((body['entries'] as List?) ?? []).map((e) {
      final entry = e as Map<String, dynamic>;
      // Go server uses 'ownerPeerId', Dart server uses 'peerId'
      final peerId = (entry['peerId'] ?? entry['ownerPeerId'] ?? '') as String;
      // Go server sends timestamps as RFC3339 strings, Dart server as int ms
      final listedAt = entry['listedAt'] is int
          ? entry['listedAt'] as int
          : DateTime.parse(entry['listedAt'] as String).millisecondsSinceEpoch;
      final updatedAt = entry['updatedAt'] is int
          ? entry['updatedAt'] as int
          : DateTime.parse(entry['updatedAt'] as String).millisecondsSinceEpoch;
      return DirectoryEntryInfo(
        peerId: peerId,
        displayName: entry['displayName'] as String,
        bio: entry['bio'] as String?,
        avatarHash: entry['avatarHash'] as String?,
        listedAt: listedAt,
        updatedAt: updatedAt,
      );
    }).toList();

    // Go server has no 'totalCount' field, uses 'hasMore' instead
    final totalCount = body['totalCount'] as int? ?? entries.length;

    return DirectoryBrowseResult(
      entries: entries,
      nextCursor: body['nextCursor'] as String?,
      totalCount: totalCount,
    );
  } on TimeoutException {
    _logger.warning('DIRECTORY browse timed out');
    return null;
  } catch (e) {
    _logger.warning('Failed to browse directory: $e');
    return null;
  }
}