diff --git a/packages/supabase/lib/src/realtime_client_options.dart b/packages/supabase/lib/src/realtime_client_options.dart index 2c1ffb39..dee5c814 100644 --- a/packages/supabase/lib/src/realtime_client_options.dart +++ b/packages/supabase/lib/src/realtime_client_options.dart @@ -17,10 +17,29 @@ class RealtimeClientOptions { /// the timeout to trigger push timeouts final Duration? timeout; + /// The WebSocket implementation to use + final WebSocketTransport? webSocketTransport; + /// {@macro realtime_client_options} const RealtimeClientOptions({ this.eventsPerSecond, this.logLevel, this.timeout, + this.webSocketTransport, }); + + RealtimeClientOptions copyWith({ + int? eventsPerSecond, + RealtimeLogLevel? logLevel, + Duration? timeout, + WebSocketTransport? webSocketTransport, + }) { + return RealtimeClientOptions( + // ignore: deprecated_member_use_from_same_package + eventsPerSecond: eventsPerSecond ?? this.eventsPerSecond, + logLevel: logLevel ?? this.logLevel, + timeout: timeout ?? this.timeout, + webSocketTransport: webSocketTransport ?? this.webSocketTransport, + ); + } } diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 382569f8..aa51c921 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -330,6 +330,7 @@ class SupabaseClient { 'apikey': _supabaseKey, }, headers: {'apikey': _supabaseKey, ...headers}, + transport: options.webSocketTransport, logLevel: options.logLevel, httpClient: _authHttpClient, timeout: options.timeout ?? RealtimeConstants.defaultTimeout, diff --git a/packages/supabase_flutter/lib/src/platform_http_io.dart b/packages/supabase_flutter/lib/src/platform_http_io.dart new file mode 100644 index 00000000..d6677749 --- /dev/null +++ b/packages/supabase_flutter/lib/src/platform_http_io.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:cupertino_http/cupertino_http.dart'; +import 'package:flutter/widgets.dart'; +import 'package:http/http.dart' as http; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:web_socket_channel/adapter_web_socket_channel.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +/// For iOS and macOS this returns a `CupertinoClient` and [http.Client] for the +/// rest of the platforms. +/// +/// This is used to make HTTP requests use the platform's native HTTP client. +http.Client getPlatformHttpClient() { + if (isInWidgetTest) return http.Client(); + if (Platform.isIOS || Platform.isMacOS) { + return CupertinoClient.defaultSessionConfiguration(); + } else { + return http.Client(); + } +} + +/// For iOS and macOS this returns a `CupertinoWebSocket` wrapped in a +/// `AdapterWebSocketChannel` and `null` for the rest of the platforms. +/// +/// It may return `null` because the differentiation for the other platforms +/// is done in [RealtimeClient]. +WebSocketChannel Function(String url)? getPlatformWebSocketChannel() { + if (isInWidgetTest) return null; + if (Platform.isIOS || Platform.isMacOS) { + return (String url) => + AdapterWebSocketChannel(CupertinoWebSocket.connect(Uri.parse(url))); + } + return null; +} + +bool get isInWidgetTest { + return WidgetsBinding.instance.runtimeType + .toString() + .contains('TestWidgetsFlutterBinding'); +} diff --git a/packages/supabase_flutter/lib/src/platform_http_web.dart b/packages/supabase_flutter/lib/src/platform_http_web.dart new file mode 100644 index 00000000..afd73e31 --- /dev/null +++ b/packages/supabase_flutter/lib/src/platform_http_web.dart @@ -0,0 +1,10 @@ +import 'package:http/http.dart' as http; +import 'package:web_socket_channel/web_socket_channel.dart'; + +http.Client getPlatformHttpClient() { + return http.Client(); +} + +WebSocketChannel? getPlatformWebSocketChannel(String url) { + return null; +} diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 8ead5e17..136ffac1 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -14,6 +14,8 @@ import 'package:supabase_flutter/src/supabase_auth.dart'; import 'hot_restart_cleanup_stub.dart' if (dart.library.js_interop) 'hot_restart_cleanup_web.dart'; +import 'platform_http_io.dart' + if (dart.library.js_interop) 'platform_http_web.dart'; import 'version.dart'; final _log = Logger('supabase.supabase_flutter'); @@ -117,6 +119,14 @@ class Supabase with WidgetsBindingObserver { ), ); } + if (realtimeClientOptions.webSocketTransport == null) { + final platformWebSocketChannel = getPlatformWebSocketChannel(); + if (platformWebSocketChannel != null) { + realtimeClientOptions = realtimeClientOptions.copyWith( + webSocketTransport: (url, headers) => + platformWebSocketChannel(url)); + } + } _instance._init( url, anonKey, @@ -195,10 +205,16 @@ class Supabase with WidgetsBindingObserver { ...Constants.defaultHeaders, if (customHeaders != null) ...customHeaders }; + final Client platformHttpClient; + if (httpClient != null) { + platformHttpClient = httpClient; + } else { + platformHttpClient = getPlatformHttpClient(); + } client = SupabaseClient( supabaseUrl, supabaseAnonKey, - httpClient: httpClient, + httpClient: platformHttpClient, headers: headers, realtimeClientOptions: realtimeClientOptions, postgrestOptions: postgrestOptions, diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index 428d28f2..9f28327f 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: shared_preferences: ^2.0.0 logging: ^1.2.0 web: '>=0.5.0 <2.0.0' + cupertino_http: '>=1.4.0 <3.0.0' + web_socket_channel: '>=2.3.0 <4.0.0' dev_dependencies: dart_jsonwebtoken: ^2.4.1