feat(client): GrpcWebClientChannel on web targets via conditional import
Some checks failed
Security / Security check (push) Failing after 1s

Splits channel construction into two files selected at
compile-time via `if (dart.library.html)`:

  channel_factory_io.dart   — native ClientChannel (HTTP/2)
  channel_factory_web.dart  — GrpcWebClientChannel.xhr (HTTP/1.1)

HubClient._channel is now `ClientChannelBase` so both
implementations satisfy the same field type. dart:html-only
imports (`package:grpc/grpc_web.dart`) stay isolated from the
native compile path.

Pairs with the hub-side `fai_grpc_web` adapter crate
(fai/platform 0d07892) — `tonic-web` on the same port as
native gRPC, so one `fai serve` listener handles both
surfaces.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-26 13:34:26 +02:00
parent 5604e5eaf3
commit f481eee687
3 changed files with 49 additions and 13 deletions

View file

@ -6,8 +6,10 @@
import 'dart:typed_data';
import 'package:grpc/grpc.dart';
import 'package:grpc/grpc_connection_interface.dart';
import 'channel_factory_io.dart'
if (dart.library.html) 'channel_factory_web.dart' as channel_factory;
import 'generated/fai/v1/common.pb.dart' as pb_common;
import 'generated/fai/v1/hub.pb.dart' as pb;
import 'generated/fai/v1/hub.pbgrpc.dart' as grpc_hub;
@ -78,9 +80,15 @@ typedef SubmitResponse = pb.SubmitResponse;
/// Typed client for the FI Hub gRPC surface. Construct once,
/// reuse for the process lifetime, call [close] on shutdown.
///
/// The channel type is picked at compile time:
/// * native targets plain HTTP/2 gRPC via [ClientChannel].
/// * web targets HTTP/1.1 gRPC-Web via [GrpcWebClientChannel].
/// Requires the hub to be running with the
/// `tonic-web` layer enabled.
class HubClient {
final HubEndpoint endpoint;
final ClientChannel _channel;
final ClientChannelBase _channel;
final grpc_hub.HubClient _hub;
final grpc_hub.HubAdminClient _admin;
@ -89,17 +97,7 @@ class HubClient {
_admin = grpc_hub.HubAdminClient(_channel);
factory HubClient({HubEndpoint endpoint = const HubEndpoint()}) {
final channel = ClientChannel(
endpoint.host,
port: endpoint.port,
options: ChannelOptions(
credentials: endpoint.secure
? const ChannelCredentials.secure()
: const ChannelCredentials.insecure(),
idleTimeout: const Duration(minutes: 5),
),
);
return HubClient._(endpoint, channel);
return HubClient._(endpoint, channel_factory.createChannel(endpoint));
}
/// Liveness probe. Returns true when the hub responds with