Add consistent User-Agent header to all HTTP clients

Create UserAgentClient (http.BaseClient wrapper) that injects a
User-Agent header into every request, reading app name and version
from VersionService and contact/homepage from dev_config.dart.

Format follows OSM tile usage policy:
  DeFlock/<version> (+https://deflock.org; contact: admin@stopflock.com)

Replaces 4 inconsistent hardcoded UA strings and adds UA to the 9
call sites that previously sent none.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Doug Borg
2026-02-24 19:31:27 -07:00
parent fe20356734
commit 0137fd66aa
16 changed files with 177 additions and 43 deletions
@@ -1,4 +1,3 @@
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/flutter_map.dart';
@@ -7,6 +6,7 @@ import 'package:xml/xml.dart';
import '../../models/node_profile.dart';
import '../../models/osm_node.dart';
import '../../app_state.dart';
import '../http_client.dart';
/// Fetches surveillance nodes from the direct OSM API using bbox query.
/// This is a fallback for when Overpass is not available (e.g., sandbox mode).
@@ -33,6 +33,8 @@ Future<List<OsmNode>> fetchOsmApiNodes({
}
}
final _client = UserAgentClient();
/// Internal method that performs the actual OSM API fetch.
Future<List<OsmNode>> _fetchFromOsmApi({
required LatLngBounds bounds,
@@ -57,7 +59,7 @@ Future<List<OsmNode>> _fetchFromOsmApi({
debugPrint('[fetchOsmApiNodes] Querying OSM API for nodes in bbox...');
debugPrint('[fetchOsmApiNodes] URL: $url');
final response = await http.get(Uri.parse(url));
final response = await _client.get(Uri.parse(url));
if (response.statusCode != 200) {
debugPrint('[fetchOsmApiNodes] OSM API error: ${response.statusCode} - ${response.body}');
@@ -1,11 +1,11 @@
import 'dart:math';
import 'dart:io';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:deflockapp/dev_config.dart';
import 'package:deflockapp/services/http_client.dart';
/// Global semaphore to limit simultaneous tile fetches
final _tileFetchSemaphore = _SimpleSemaphore(kTileFetchConcurrentThreads);
@@ -92,6 +92,8 @@ bool _isTileVisible(int z, int x, int y, LatLngBounds viewBounds) {
final _tileClient = UserAgentClient();
/// Fetches a tile from any remote provider with unlimited retries.
/// Returns tile image bytes. Retries forever until success.
/// Brutalist approach: Keep trying until it works - no arbitrary retry limits.
@@ -113,7 +115,7 @@ Future<List<int>> fetchRemoteTile({
debugPrint('[fetchRemoteTile] Fetching $z/$x/$y from $hostInfo');
}
attempt++;
final resp = await http.get(Uri.parse(url));
final resp = await _tileClient.get(Uri.parse(url));
if (resp.statusCode == 200 && resp.bodyBytes.isNotEmpty) {
// Success!