From 14ab1279fd070ca3065b9eacec2a53aec6c81c39 Mon Sep 17 00:00:00 2001 From: Vinzent Date: Mon, 31 Mar 2025 22:39:25 +0200 Subject: [PATCH 1/6] 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/6] 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/6] 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/6] 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'); +} From 2d12a6c5ad9305060bd18005e419cfbff8d99f9c Mon Sep 17 00:00:00 2001 From: Vinzent Date: Thu, 12 Jun 2025 22:57:14 +0200 Subject: [PATCH 5/6] refactor: remove usage of cupertino client --- .../lib/src/platform_http_io.dart | 41 ------------------- .../lib/src/platform_http_web.dart | 10 ----- .../supabase_flutter/lib/src/supabase.dart | 18 +------- packages/supabase_flutter/pubspec.yaml | 2 - 4 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 packages/supabase_flutter/lib/src/platform_http_io.dart delete mode 100644 packages/supabase_flutter/lib/src/platform_http_web.dart diff --git a/packages/supabase_flutter/lib/src/platform_http_io.dart b/packages/supabase_flutter/lib/src/platform_http_io.dart deleted file mode 100644 index d6677749..00000000 --- a/packages/supabase_flutter/lib/src/platform_http_io.dart +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index afd73e31..00000000 --- a/packages/supabase_flutter/lib/src/platform_http_web.dart +++ /dev/null @@ -1,10 +0,0 @@ -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 136ffac1..8ead5e17 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -14,8 +14,6 @@ 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'); @@ -119,14 +117,6 @@ 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, @@ -205,16 +195,10 @@ 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: platformHttpClient, + httpClient: httpClient, headers: headers, realtimeClientOptions: realtimeClientOptions, postgrestOptions: postgrestOptions, diff --git a/packages/supabase_flutter/pubspec.yaml b/packages/supabase_flutter/pubspec.yaml index 9f28327f..428d28f2 100644 --- a/packages/supabase_flutter/pubspec.yaml +++ b/packages/supabase_flutter/pubspec.yaml @@ -23,8 +23,6 @@ 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 From 90027406ad27a653f92267a945446f6536506c5c Mon Sep 17 00:00:00 2001 From: Vinzent Date: Sun, 15 Jun 2025 23:38:35 +0200 Subject: [PATCH 6/6] refactor: move RealtimeClientOptions to general options file --- .../lib/src/realtime_client_options.dart | 45 ------------------- .../lib/src/supabase_client_options.dart | 44 ++++++++++++++++++ packages/supabase/lib/supabase.dart | 1 - 3 files changed, 44 insertions(+), 46 deletions(-) delete mode 100644 packages/supabase/lib/src/realtime_client_options.dart diff --git a/packages/supabase/lib/src/realtime_client_options.dart b/packages/supabase/lib/src/realtime_client_options.dart deleted file mode 100644 index dee5c814..00000000 --- a/packages/supabase/lib/src/realtime_client_options.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:realtime_client/realtime_client.dart'; - -/// {@template realtime_client_options} -/// Options to pass to the RealtimeClient. -/// {@endtemplate} -class RealtimeClientOptions { - /// How many events the RealtimeClient can push in a second - /// - /// Defaults to 10 events per second - @Deprecated( - 'Client side rate limit has been removed. This option will be ignored.') - final int? eventsPerSecond; - - /// Level of realtime server logs to be logged - final RealtimeLogLevel? logLevel; - - /// 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_options.dart b/packages/supabase/lib/src/supabase_client_options.dart index 3587e618..af24c51d 100644 --- a/packages/supabase/lib/src/supabase_client_options.dart +++ b/packages/supabase/lib/src/supabase_client_options.dart @@ -23,3 +23,47 @@ class StorageClientOptions { const StorageClientOptions({this.retryAttempts = 0}); } + +/// {@template realtime_client_options} +/// Options to pass to the RealtimeClient. +/// {@endtemplate} +class RealtimeClientOptions { + /// How many events the RealtimeClient can push in a second + /// + /// Defaults to 10 events per second + @Deprecated( + 'Client side rate limit has been removed. This option will be ignored.') + final int? eventsPerSecond; + + /// Level of realtime server logs to be logged + final RealtimeLogLevel? logLevel; + + /// 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/supabase.dart b/packages/supabase/lib/supabase.dart index dc899cea..176b244e 100644 --- a/packages/supabase/lib/supabase.dart +++ b/packages/supabase/lib/supabase.dart @@ -11,7 +11,6 @@ export 'package:realtime_client/realtime_client.dart'; export 'package:storage_client/storage_client.dart'; export 'src/auth_user.dart'; -export 'src/realtime_client_options.dart'; export 'src/remove_subscription_result.dart'; export 'src/supabase_client.dart'; export 'src/supabase_client_options.dart';