From 14ab1279fd070ca3065b9eacec2a53aec6c81c39 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 31 Mar 2025 22:39:25 +0200 Subject: [PATCH 1/4] feat: use cupertino http and websocket implementation --- .../lib/src/realtime_client_options.dart | 19 +++++++++++ .../supabase/lib/src/supabase_client.dart | 1 + .../lib/src/platform_http_io.dart | 33 +++++++++++++++++++ .../lib/src/platform_http_web.dart | 10 ++++++ .../supabase_flutter/lib/src/supabase.dart | 17 +++++++++- packages/supabase_flutter/pubspec.yaml | 2 ++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/supabase_flutter/lib/src/platform_http_io.dart create mode 100644 packages/supabase_flutter/lib/src/platform_http_web.dart 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..2681966d --- /dev/null +++ b/packages/supabase_flutter/lib/src/platform_http_io.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:cupertino_http/cupertino_http.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 (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? getPlatformWebSocketChannel(String url) { + if (Platform.isIOS || Platform.isMacOS) { + return AdapterWebSocketChannel( + CupertinoWebSocket.connect(Uri.parse(url)), + ); + } + return null; +} 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..88f2c00f 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,13 @@ class Supabase with WidgetsBindingObserver { ), ); } + if (realtimeClientOptions.webSocketTransport == null) { + final platformWebSocketChannel = getPlatformWebSocketChannel(url); + if (platformWebSocketChannel != null) { + realtimeClientOptions = realtimeClientOptions.copyWith( + webSocketTransport: (url, headers) => platformWebSocketChannel); + } + } _instance._init( url, anonKey, @@ -195,10 +204,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..f9eeb9d7 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: ^2.0.0 + web_socket_channel: '>=2.3.0 <4.0.0' dev_dependencies: dart_jsonwebtoken: ^2.4.1 From e4daf40e14a9efa2df03ffb12f1ced46ccc571cb Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 31 Mar 2025 22:47:05 +0200 Subject: [PATCH 2/4] chore: lower version requirement for cupertino_http --- packages/supabase_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index f9eeb9d7..9f28327f 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: shared_preferences: ^2.0.0 logging: ^1.2.0 web: '>=0.5.0 <2.0.0' - cupertino_http: ^2.0.0 + cupertino_http: '>=1.4.0 <3.0.0' web_socket_channel: '>=2.3.0 <4.0.0' dev_dependencies: From 5f203671a9a5192e6f63268d22283b18e60dc480 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Tue, 6 May 2025 23:24:07 +0200 Subject: [PATCH 3/4] fix: use correct url --- packages/supabase_flutter/lib/src/platform_http_io.dart | 7 +++---- packages/supabase_flutter/lib/src/supabase.dart | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/supabase_flutter/lib/src/platform_http_io.dart b/packages/supabase_flutter/lib/src/platform_http_io.dart index 2681966d..292a15a7 100644 --- a/packages/supabase_flutter/lib/src/platform_http_io.dart +++ b/packages/supabase_flutter/lib/src/platform_http_io.dart @@ -23,11 +23,10 @@ http.Client getPlatformHttpClient() { /// /// It may return `null` because the differentiation for the other platforms /// is done in [RealtimeClient]. -WebSocketChannel? getPlatformWebSocketChannel(String url) { +WebSocketChannel Function(String url)? getPlatformWebSocketChannel() { if (Platform.isIOS || Platform.isMacOS) { - return AdapterWebSocketChannel( - CupertinoWebSocket.connect(Uri.parse(url)), - ); + return (String url) => + AdapterWebSocketChannel(CupertinoWebSocket.connect(Uri.parse(url))); } return null; } diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 88f2c00f..136ffac1 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -120,10 +120,11 @@ class Supabase with WidgetsBindingObserver { ); } if (realtimeClientOptions.webSocketTransport == null) { - final platformWebSocketChannel = getPlatformWebSocketChannel(url); + final platformWebSocketChannel = getPlatformWebSocketChannel(); if (platformWebSocketChannel != null) { realtimeClientOptions = realtimeClientOptions.copyWith( - webSocketTransport: (url, headers) => platformWebSocketChannel); + webSocketTransport: (url, headers) => + platformWebSocketChannel(url)); } } _instance._init( From 93f3f8f552cdab52746329ab0f79fd28ec5b2f2e Mon Sep 17 00:00:00 2001 From: Vinzent Date: Tue, 6 May 2025 23:41:14 +0200 Subject: [PATCH 4/4] fix: guard against widget tests --- packages/supabase_flutter/lib/src/platform_http_io.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/supabase_flutter/lib/src/platform_http_io.dart b/packages/supabase_flutter/lib/src/platform_http_io.dart index 292a15a7..d6677749 100644 --- a/packages/supabase_flutter/lib/src/platform_http_io.dart +++ b/packages/supabase_flutter/lib/src/platform_http_io.dart @@ -1,6 +1,7 @@ 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'; @@ -11,6 +12,7 @@ import 'package:web_socket_channel/web_socket_channel.dart'; /// /// 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 { @@ -24,9 +26,16 @@ http.Client getPlatformHttpClient() { /// 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'); +}