diet-guard/app/lib/services/github_client_factory_web.dart
Krzysztof kuhy Rudnicki 7a491d9d91
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
app: use the shared github_device_auth package
The inline `_DeviceCodeDialog` in `github_mirror_screen.dart` (88 lines)
was byte-for-byte identical to wake-alarm's, and
`services/github_device_auth.dart` was a fourth copy of the auth client.
Both now come from `github_device_auth` in the kuhyx/utils monorepo.

This app's copy was the one that mattered most to get right: it took the
device-code and token URLs as parameters because GitHub's device-flow
endpoints send no CORS headers, so the desktop **web** build has to route
both through the local wrapper (`github_client_factory_web.dart` passes
them). Extracting todo's simpler hardcoded version would have broken that
build outright. The package carries the configurable endpoints instead,
defaulting to GitHub's own URLs so the other three consumers are
unaffected -- and `flutter build web` here is verified green, which is the
check that would have caught it.

The dialog also gains a real fix: `on Object` rather than `on Exception`,
so an `ArgumentError` shows an error instead of leaving the dialog
spinning on "Waiting for authorization…" forever.

The unused `token_vault` import in the deleted client confirmed it had no
app-specific behaviour left worth keeping. 548 tests pass, analyze clean,
web build green, and the release APK is installed on the phone with meal
history intact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U4Rj3s7K2qzrLNgJqqPhno
2026-08-15 16:15:24 +02:00

83 lines
2.9 KiB
Dart

/// Wrapper-proxied GitHub client, used by the desktop web build.
library;
import 'package:crdt_sync/crdt_sync.dart';
import 'package:diet_guard_app/services/desktop_wrapper.dart';
import 'package:diet_guard_app/services/sync_settings.dart';
import 'package:github_device_auth/github_device_auth.dart';
import 'package:http/http.dart' as http;
/// This device's id under `diet-guard-sync/devices/`.
///
/// Distinct from the phone's `phone` and the Python side's `pc`: the desktop
/// app is a third device, and sharing an id would make two devices overwrite
/// each other's pushed log on every tick.
const syncDeviceId = 'desktop';
/// Builds a client whose requests go through the desktop wrapper.
///
/// The token is deliberately empty: the wrapper attaches the real one
/// server-side (see `desktop/github_proxy.dart`), so it never enters the
/// browser. `settings.token` on web is only ever the stand-in constant.
GitHubClient createGitHubClient(
SyncSettings settings, {
http.Client? httpClient,
}) => GitHubClient(
owner: settings.owner,
repo: settings.repo,
token: '',
httpClient: WrapperProxyClient(inner: httpClient),
);
/// Rewrites `https://api.github.com/...` to the wrapper's proxy endpoint.
///
/// A decorator rather than a `baseUrl` option on [GitHubClient] so the shared
/// `crdt_sync` library needs no web-specific knowledge; the rewrite is a
/// property of how this app is hosted, not of the transport.
class WrapperProxyClient extends http.BaseClient {
/// Creates a client delegating to [inner] after rewriting GitHub URLs.
WrapperProxyClient({http.Client? inner, this.baseUrl = desktopWrapperOrigin})
: _inner = inner ?? http.Client();
final http.Client _inner;
/// Origin of the desktop wrapper.
final String baseUrl;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
if (request.url.host != 'api.github.com' || request is! http.Request) {
return _inner.send(request);
}
final rewritten =
http.Request(
request.method,
Uri.parse(
'$baseUrl${WrapperPaths.github}api'
'${request.url.path}',
).replace(query: request.url.hasQuery ? request.url.query : null),
)
..headers.addAll(request.headers)
..bodyBytes = request.bodyBytes
..followRedirects = request.followRedirects;
return _inner.send(rewritten);
}
@override
void close() => _inner.close();
}
/// Builds the device-flow client, routed through the desktop wrapper.
///
/// GitHub's device-flow endpoints send no CORS headers, so the page cannot
/// call them; the wrapper does, and keeps the resulting token.
GitHubDeviceAuth createDeviceAuth(
String clientId, {
http.Client? httpClient,
}) => GitHubDeviceAuth(
clientId: clientId,
deviceCodeUrl: '$desktopWrapperOrigin${WrapperPaths.github}auth/device/start',
tokenUrl: '$desktopWrapperOrigin${WrapperPaths.github}auth/device/poll',
httpClient: httpClient,
);