diff --git a/lib/src/channel_factory_io.dart b/lib/src/channel_factory_io.dart new file mode 100644 index 0000000..4b593da --- /dev/null +++ b/lib/src/channel_factory_io.dart @@ -0,0 +1,21 @@ +// Native channel factory — plain gRPC over HTTP/2. +// Selected on every non-web target (macOS, Linux, Windows, iOS, +// Android) via the conditional import in hub_client.dart. + +import 'package:grpc/grpc.dart'; +import 'package:grpc/grpc_connection_interface.dart'; + +import 'hub_client.dart' show HubEndpoint; + +ClientChannelBase createChannel(HubEndpoint endpoint) { + return ClientChannel( + endpoint.host, + port: endpoint.port, + options: ChannelOptions( + credentials: endpoint.secure + ? const ChannelCredentials.secure() + : const ChannelCredentials.insecure(), + idleTimeout: const Duration(minutes: 5), + ), + ); +} diff --git a/lib/src/channel_factory_web.dart b/lib/src/channel_factory_web.dart new file mode 100644 index 0000000..565f4be --- /dev/null +++ b/lib/src/channel_factory_web.dart @@ -0,0 +1,17 @@ +// Web channel factory — gRPC-Web over HTTP/1.1. +// Selected on dart:html targets (Flutter web, Dart-for-web) via +// the conditional import in hub_client.dart. The hub must be +// running with `tonic-web` enabled — see fai_platform docs +// architecture/protocol-surfaces.md. + +import 'package:grpc/grpc_connection_interface.dart'; +import 'package:grpc/grpc_web.dart'; + +import 'hub_client.dart' show HubEndpoint; + +ClientChannelBase createChannel(HubEndpoint endpoint) { + final scheme = endpoint.secure ? 'https' : 'http'; + return GrpcWebClientChannel.xhr( + Uri.parse('$scheme://${endpoint.host}:${endpoint.port}'), + ); +} diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 5765b8e..7b4fb02 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -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 F∆I 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