Add Supa account and search changes

This commit is contained in:
Fabian Freund
2026-05-22 18:10:22 +02:00
parent 3a19865b2e
commit 51289f1266
374 changed files with 54061 additions and 5013 deletions
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:weblibre/features/search_credits/domain/repositories/web_search_settings.dart';
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
part 'proxy_client.g.dart';
/// Resolved Tor SOCKS5 port for search traffic, or `null` when search should
/// route directly. Returns non-null only when the user has enabled the
/// "route search through Tor" toggle AND Tor is currently running with a
/// known SOCKS port.
@Riverpod(keepAlive: true)
int? searchProxyPort(Ref ref) {
final route = ref.watch(
webSearchSettingsControllerProvider.select((s) => s.routeThroughTor),
);
if (!route) return null;
final status = ref.watch(torProxyServiceProvider).value;
if (status == null || !status.isRunning || status.bootstrapProgress < 100) {
return null;
}
return status.socksPort;
}
/// HttpClient used by both WebSocket (via `IOWebSocketChannel.customClient`)
/// and HTTP-based search clients. SOCKS5-routed when Tor toggle is on, plain
/// otherwise. Rebuilt when the proxy port changes.
@Riverpod(keepAlive: true)
HttpClient searchHttpClient(Ref ref) {
final port = ref.watch(searchProxyPortProvider);
final client = HttpClient()
// The default HttpClient has no connection timeout, so a search service
// that is unreachable can stall the UI for ~minute(s) before failing.
// Cap the TCP/TLS handshake at a few seconds so the user sees an error
// quickly and can retry; the WebSocket session itself imposes no
// ceiling on long-running streams once connected.
..connectionTimeout = const Duration(seconds: 25);
if (port != null) {
SocksTCPClient.assignToHttpClient(client, [
ProxySettings(InternetAddress.loopbackIPv4, port),
]);
}
ref.onDispose(() => client.close(force: true));
return client;
}
/// `package:http` Client wrapping [searchHttpClientProvider]. Use for the
/// non-WebSocket parts of the search flow (token issuance, one-shot capture,
/// capture artifact downloads).
@Riverpod(keepAlive: true)
http.Client searchProxyHttpClient(Ref ref) {
final httpClient = ref.watch(searchHttpClientProvider);
// IOClient does not own the HttpClient lifetime here — the underlying
// HttpClient is closed by the searchHttpClientProvider's onDispose. Don't
// close the IOClient on dispose to avoid prematurely tearing down the
// shared HttpClient on rebuild.
return IOClient(httpClient);
}
@@ -0,0 +1,169 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_client.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Resolved Tor SOCKS5 port for search traffic, or `null` when search should
/// route directly. Returns non-null only when the user has enabled the
/// "route search through Tor" toggle AND Tor is currently running with a
/// known SOCKS port.
@ProviderFor(searchProxyPort)
final searchProxyPortProvider = SearchProxyPortProvider._();
/// Resolved Tor SOCKS5 port for search traffic, or `null` when search should
/// route directly. Returns non-null only when the user has enabled the
/// "route search through Tor" toggle AND Tor is currently running with a
/// known SOCKS port.
final class SearchProxyPortProvider
extends $FunctionalProvider<int?, int?, int?>
with $Provider<int?> {
/// Resolved Tor SOCKS5 port for search traffic, or `null` when search should
/// route directly. Returns non-null only when the user has enabled the
/// "route search through Tor" toggle AND Tor is currently running with a
/// known SOCKS port.
SearchProxyPortProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchProxyPortProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchProxyPortHash();
@$internal
@override
$ProviderElement<int?> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
int? create(Ref ref) {
return searchProxyPort(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(int? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<int?>(value),
);
}
}
String _$searchProxyPortHash() => r'759420a65b9a1375ff9595de2a0dcbf174ae3e79';
/// HttpClient used by both WebSocket (via `IOWebSocketChannel.customClient`)
/// and HTTP-based search clients. SOCKS5-routed when Tor toggle is on, plain
/// otherwise. Rebuilt when the proxy port changes.
@ProviderFor(searchHttpClient)
final searchHttpClientProvider = SearchHttpClientProvider._();
/// HttpClient used by both WebSocket (via `IOWebSocketChannel.customClient`)
/// and HTTP-based search clients. SOCKS5-routed when Tor toggle is on, plain
/// otherwise. Rebuilt when the proxy port changes.
final class SearchHttpClientProvider
extends $FunctionalProvider<HttpClient, HttpClient, HttpClient>
with $Provider<HttpClient> {
/// HttpClient used by both WebSocket (via `IOWebSocketChannel.customClient`)
/// and HTTP-based search clients. SOCKS5-routed when Tor toggle is on, plain
/// otherwise. Rebuilt when the proxy port changes.
SearchHttpClientProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchHttpClientProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchHttpClientHash();
@$internal
@override
$ProviderElement<HttpClient> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
HttpClient create(Ref ref) {
return searchHttpClient(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(HttpClient value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<HttpClient>(value),
);
}
}
String _$searchHttpClientHash() => r'b3aa4b658e99fb580031ac412fabfbeece5fe830';
/// `package:http` Client wrapping [searchHttpClientProvider]. Use for the
/// non-WebSocket parts of the search flow (token issuance, one-shot capture,
/// capture artifact downloads).
@ProviderFor(searchProxyHttpClient)
final searchProxyHttpClientProvider = SearchProxyHttpClientProvider._();
/// `package:http` Client wrapping [searchHttpClientProvider]. Use for the
/// non-WebSocket parts of the search flow (token issuance, one-shot capture,
/// capture artifact downloads).
final class SearchProxyHttpClientProvider
extends $FunctionalProvider<http.Client, http.Client, http.Client>
with $Provider<http.Client> {
/// `package:http` Client wrapping [searchHttpClientProvider]. Use for the
/// non-WebSocket parts of the search flow (token issuance, one-shot capture,
/// capture artifact downloads).
SearchProxyHttpClientProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchProxyHttpClientProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchProxyHttpClientHash();
@$internal
@override
$ProviderElement<http.Client> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
http.Client create(Ref ref) {
return searchProxyHttpClient(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(http.Client value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<http.Client>(value),
);
}
}
String _$searchProxyHttpClientHash() =>
r'08acee399abc5c6960a82365219af6b5a44ba5bd';