Files
buzz/mobile/lib/shared/relay/nip11_document.dart
Tom Brow b58ee08404 fix(mobile): request hosted relay assertions only when NIP-FI is required (#9225)
Port the desktop NIP-FI requirement rule to mobile: hosted relay connections request an account assertion only when the relay's NIP-11 document requires NIP-FI, so communities that have not enabled it keep connecting with BUZZ_HOSTED_RELAY_AUTH on.

Signed-off-by: Tom Brow <tomb@squareup.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:57:05 -08:00

51 lines
1.7 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
const _maxDocumentBytes = 256 * 1024;
/// How long a NIP-11 read may take, headers and body together.
const nip11Deadline = Duration(seconds: 10);
/// Reads and decodes the NIP-11 document at [uri] with a size cap. One
/// wall-clock deadline covers headers and the whole body, so a relay trickling
/// bytes cannot keep the read in flight forever. No credential goes on the
/// request. Throws on a non-200 status, an oversized body, the deadline or
/// malformed JSON.
Future<Object?> fetchNip11Document(
http.Client client,
Uri uri, {
Duration deadline = nip11Deadline,
bool followRedirects = true,
}) async {
final abort = Completer<void>();
final request = http.AbortableRequest('GET', uri, abortTrigger: abort.future)
..followRedirects = followRedirects
..headers['Accept'] = 'application/nostr+json';
StreamIterator<List<int>>? body;
Future<List<int>> read() async {
final response = await client.send(request);
final iterator = body = StreamIterator(response.stream);
if (response.statusCode != 200) {
throw http.ClientException('NIP-11 status ${response.statusCode}', uri);
}
final bytes = <int>[];
while (await iterator.moveNext()) {
if (bytes.length + iterator.current.length > _maxDocumentBytes) {
throw const FormatException('NIP-11 exceeds size limit');
}
bytes.addAll(iterator.current);
}
return bytes;
}
try {
final bytes = await read().timeout(deadline);
return jsonDecode(utf8.decode(bytes));
} finally {
if (!abort.isCompleted) abort.complete();
unawaited(body?.cancel());
}
}